Plugins: Add option to disable TLS in the socks proxy (#79246)

* Plugins: add option to disable TLS in the socks proxy

* fix allow_insecure docs

* upgrade github.com/grafana/grafana-plugin-sdk-go from v0.196.0 to v0.197.0

* fix conflicts
This commit is contained in:
Bruno 2023-12-14 12:16:32 -03:00 committed by GitHub
parent d492100adc
commit 58678f5879
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 211 additions and 41 deletions

View File

@ -1736,6 +1736,8 @@ server_name =
proxy_address =
# Determines if the secure socks proxy should be shown on the datasources page, defaults to true if the feature is enabled
show_ui = true
# Disables TLS in the secure socks proxy
allow_insecure = false
################################## Feature Management ##############################################
# Options to configure the experimental Feature Toggle Admin Page feature, which is behind the `featureToggleAdminPage` feature toggle. Use at your own risk.

View File

@ -1595,6 +1595,7 @@
# The address of the socks5 proxy datasources should connect to
; proxy_address =
; show_ui = true
; allow_insecure = false
################################## Feature Management ##############################################
[feature_management]

View File

@ -32,14 +32,15 @@ To complete this task, you must first deploy a socks proxy server that supports
1. For Grafana to send data source connections to the socks5 server, use the following table to configure the `secure_socks_datasource_proxy` section of the `config.ini`:
| Key | Description | Example |
| --------------- | ------------------------------------------ | ------------------------------- |
| `enabled` | Enable this feature in Grafana | true |
| `root_ca_cert` | The file path of the root ca cert | /etc/ca.crt |
| `client_key` | The file path of the client private key | /etc/client.key |
| `client_cert` | The file path of the client public key | /etc/client.crt |
| `server_name` | The domain name of the proxy, used for SNI | proxy.grafana.svc.cluster.local |
| `proxy_address` | the address of the proxy | localhost:9090 |
| Key | Description | Example |
| ---------------- | ------------------------------------------ | ------------------------------- |
| `enabled` | Enable this feature in Grafana | true |
| `root_ca_cert` | The file path of the root ca cert | /etc/ca.crt |
| `client_key` | The file path of the client private key | /etc/client.key |
| `client_cert` | The file path of the client public key | /etc/client.crt |
| `server_name` | The domain name of the proxy, used for SNI | proxy.grafana.svc.cluster.local |
| `proxy_address` | The address of the proxy | localhost:9090 |
| `allow_insecure` | Disable TLS in the socks proxy | false |
1. Set up a data source and configure it to send data source connections through the proxy.

8
go.mod
View File

@ -67,9 +67,9 @@ require (
github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code
github.com/grafana/grafana-aws-sdk v0.19.1 // @grafana/aws-datasources
github.com/grafana/grafana-azure-sdk-go v1.11.0 // @grafana/backend-platform
github.com/grafana/grafana-plugin-sdk-go v0.196.0 // @grafana/plugins-platform-backend
github.com/grafana/grafana-plugin-sdk-go v0.197.0 // @grafana/plugins-platform-backend
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // @grafana/backend-platform
github.com/hashicorp/go-hclog v1.5.0 // @grafana/plugins-platform-backend
github.com/hashicorp/go-hclog v1.6.1 // @grafana/plugins-platform-backend
github.com/hashicorp/go-plugin v1.6.0 // @grafana/plugins-platform-backend
github.com/hashicorp/go-version v1.6.0 // @grafana/backend-platform
github.com/hashicorp/hcl/v2 v2.17.0 // @grafana/alerting-squad-backend
@ -125,7 +125,7 @@ require (
gopkg.in/mail.v2 v2.3.1 // @grafana/backend-platform
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // @grafana/alerting-squad-backend
xorm.io/builder v0.3.6 // @grafana/backend-platform
xorm.io/builder v0.3.6 // indirect; @grafana/backend-platform
xorm.io/core v0.7.3 // @grafana/backend-platform
xorm.io/xorm v0.8.2 // @grafana/alerting-squad-backend
)
@ -177,7 +177,7 @@ require (
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-msgpack v0.5.5 // indirect
github.com/hashicorp/go-multierror v1.1.1 // @grafana/alerting-squad
github.com/hashicorp/go-multierror v1.1.1 // indirect; @grafana/alerting-squad
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.6.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect

4
go.sum
View File

@ -1824,8 +1824,8 @@ github.com/grafana/grafana-google-sdk-go v0.1.0 h1:LKGY8z2DSxKjYfr2flZsWgTRTZ6HG
github.com/grafana/grafana-google-sdk-go v0.1.0/go.mod h1:Vo2TKWfDVmNTELBUM+3lkrZvFtBws0qSZdXhQxRdJrE=
github.com/grafana/grafana-plugin-sdk-go v0.94.0/go.mod h1:3VXz4nCv6wH5SfgB3mlW39s+c+LetqSCjFj7xxPC5+M=
github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk=
github.com/grafana/grafana-plugin-sdk-go v0.196.0 h1:yDopQZ76Ug1UKZd2V4/MSi1k6SbeVz6o6ApwY6UP19U=
github.com/grafana/grafana-plugin-sdk-go v0.196.0/go.mod h1:YB/80C5jTFj2jGAZVuuwsU6EVr63jjKcMgEB1nlSgiY=
github.com/grafana/grafana-plugin-sdk-go v0.197.0 h1:5oUAQfa3gv5AX8Qhkoyuaj5E4hXbdR5mfa9P4zNQ0IE=
github.com/grafana/grafana-plugin-sdk-go v0.197.0/go.mod h1:HC20FRnHgZprNqfcMRbrQ35gV25RctpHnRO+JbgmdqQ=
github.com/grafana/kindsys v0.0.0-20230508162304-452481b63482 h1:1YNoeIhii4UIIQpCPU+EXidnqf449d0C3ZntAEt4KSo=
github.com/grafana/kindsys v0.0.0-20230508162304-452481b63482/go.mod h1:GNcfpy5+SY6RVbNGQW264gC0r336Dm+0zgQ5vt6+M8Y=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20231027171310-70c52bf65758 h1:ATUhvJSJwzdzhnmzUI92fxVFqyqmcnzJ47wtHTK3LW4=

View File

@ -143,6 +143,7 @@ func (s *Service) GetConfigMap(ctx context.Context, pluginID string, _ *auth.Ext
m[proxy.PluginSecureSocksProxyRootCACert] = s.cfg.ProxySettings.RootCA
m[proxy.PluginSecureSocksProxyProxyAddress] = s.cfg.ProxySettings.ProxyAddress
m[proxy.PluginSecureSocksProxyServerName] = s.cfg.ProxySettings.ServerName
m[proxy.PluginSecureSocksProxyAllowInsecure] = strconv.FormatBool(s.cfg.ProxySettings.AllowInsecure)
}
// Settings here will be extracted by grafana-azure-sdk-go from the plugin context
@ -271,6 +272,7 @@ func (s *Service) secureSocksProxyEnvVars() []string {
proxy.PluginSecureSocksProxyProxyAddress + "=" + s.cfg.ProxySettings.ProxyAddress,
proxy.PluginSecureSocksProxyServerName + "=" + s.cfg.ProxySettings.ServerName,
proxy.PluginSecureSocksProxyEnabled + "=" + strconv.FormatBool(s.cfg.ProxySettings.Enabled),
proxy.PluginSecureSocksProxyAllowInsecure + "=" + strconv.FormatBool(s.cfg.ProxySettings.AllowInsecure),
}
}
return nil

View File

@ -649,13 +649,14 @@ func TestService_GetConfigMap(t *testing.T) {
cfg: &config.Cfg{
Features: featuremgmt.WithFeatures("feat-2", "feat-500", "feat-1"),
ProxySettings: setting.SecureSocksDSProxySettings{
Enabled: true,
ShowUI: true,
ClientCert: "c3rt",
ClientKey: "k3y",
RootCA: "ca",
ProxyAddress: "https://proxy.grafana.com",
ServerName: "secureProxy",
Enabled: true,
ShowUI: true,
ClientCert: "c3rt",
ClientKey: "k3y",
RootCA: "ca",
ProxyAddress: "https://proxy.grafana.com",
ServerName: "secureProxy",
AllowInsecure: true,
},
},
expected: map[string]string{
@ -666,6 +667,7 @@ func TestService_GetConfigMap(t *testing.T) {
"GF_SECURE_SOCKS_DATASOURCE_PROXY_ROOT_CA_CERT": "ca",
"GF_SECURE_SOCKS_DATASOURCE_PROXY_PROXY_ADDRESS": "https://proxy.grafana.com",
"GF_SECURE_SOCKS_DATASOURCE_PROXY_SERVER_NAME": "secureProxy",
"GF_SECURE_SOCKS_DATASOURCE_PROXY_ALLOW_INSECURE": "true",
},
},
{

View File

@ -528,11 +528,12 @@ func (s *Service) httpClientOptions(ctx context.Context, ds *datasources.DataSou
},
Timeouts: &sdkproxy.DefaultTimeoutOptions,
ClientCfg: &sdkproxy.ClientCfg{
ClientCert: s.cfg.SecureSocksDSProxy.ClientCert,
ClientKey: s.cfg.SecureSocksDSProxy.ClientKey,
RootCA: s.cfg.SecureSocksDSProxy.RootCA,
ProxyAddress: s.cfg.SecureSocksDSProxy.ProxyAddress,
ServerName: s.cfg.SecureSocksDSProxy.ServerName,
ClientCert: s.cfg.SecureSocksDSProxy.ClientCert,
ClientKey: s.cfg.SecureSocksDSProxy.ClientKey,
RootCA: s.cfg.SecureSocksDSProxy.RootCA,
ProxyAddress: s.cfg.SecureSocksDSProxy.ProxyAddress,
ServerName: s.cfg.SecureSocksDSProxy.ServerName,
AllowInsecure: s.cfg.SecureSocksDSProxy.AllowInsecure,
},
}

View File

@ -7,13 +7,14 @@ import (
)
type SecureSocksDSProxySettings struct {
Enabled bool
ShowUI bool
ClientCert string
ClientKey string
RootCA string
ProxyAddress string
ServerName string
Enabled bool
ShowUI bool
AllowInsecure bool
ClientCert string
ClientKey string
RootCA string
ProxyAddress string
ServerName string
}
func readSecureSocksDSProxySettings(iniFile *ini.File) (SecureSocksDSProxySettings, error) {
@ -26,21 +27,27 @@ func readSecureSocksDSProxySettings(iniFile *ini.File) (SecureSocksDSProxySettin
s.ProxyAddress = secureSocksProxySection.Key("proxy_address").MustString("")
s.ServerName = secureSocksProxySection.Key("server_name").MustString("")
s.ShowUI = secureSocksProxySection.Key("show_ui").MustBool(true)
s.AllowInsecure = secureSocksProxySection.Key("allow_insecure").MustBool(false)
if !s.Enabled {
return s, nil
}
// all fields must be specified to use the proxy
if s.RootCA == "" {
return s, errors.New("rootCA required")
} else if s.ClientCert == "" || s.ClientKey == "" {
return s, errors.New("client key pair required")
} else if s.ServerName == "" {
return s, errors.New("server name required")
} else if s.ProxyAddress == "" {
if s.ProxyAddress == "" {
return s, errors.New("proxy address required")
}
// If the proxy is going to use TLS.
if !s.AllowInsecure {
// all fields must be specified to use the proxy
if s.RootCA == "" {
return s, errors.New("rootCA required")
} else if s.ClientCert == "" || s.ClientKey == "" {
return s, errors.New("client key pair required")
} else if s.ServerName == "" {
return s, errors.New("server name required")
}
}
return s, nil
}

View File

@ -0,0 +1,154 @@
package setting
import (
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"gopkg.in/ini.v1"
)
func mustNewIniFile(fileContents string) *ini.File {
file, err := ini.Load([]byte(fileContents))
if err != nil {
panic(fmt.Sprintf("creating ini file for test: %s", err))
}
return file
}
func TestReadSecureSocksDSProxySettings(t *testing.T) {
t.Parallel()
cases := []struct {
description string
iniFile *ini.File
expectedSettings SecureSocksDSProxySettings
expectedErr error
}{
{
description: "default values",
iniFile: mustNewIniFile(`
[secure_socks_datasource_proxy]
`),
expectedSettings: SecureSocksDSProxySettings{
Enabled: false,
ClientCert: "",
ClientKey: "",
RootCA: "",
ProxyAddress: "",
ServerName: "",
ShowUI: true,
AllowInsecure: false,
},
},
{
description: "root ca is required",
iniFile: mustNewIniFile(`
[secure_socks_datasource_proxy]
enabled = true
proxy_address = address
`),
expectedErr: errors.New("rootCA required"),
},
{
description: "client cert is required",
iniFile: mustNewIniFile(`
[secure_socks_datasource_proxy]
enabled = true
proxy_address = address
root_ca_cert = cert
`),
expectedErr: errors.New("client key pair required"),
},
{
description: "client key is required",
iniFile: mustNewIniFile(`
[secure_socks_datasource_proxy]
enabled = true
proxy_address = address
root_ca_cert = cert1
client_cert = cert2
`),
expectedErr: errors.New("client key pair required"),
},
{
description: "server name is required",
iniFile: mustNewIniFile(`
[secure_socks_datasource_proxy]
enabled = true
proxy_address = address
root_ca_cert = cert1
client_cert = cert2
client_key = key
`),
expectedErr: errors.New("server name required"),
},
{
description: "proxy address is required",
iniFile: mustNewIniFile(`
[secure_socks_datasource_proxy]
enabled = true
root_ca_cert = cert1
client_cert = cert2
client_key = key
server_name = name
`),
expectedErr: errors.New("proxy address required"),
},
{
description: "root ca, client cert and client key are not required in insecure more",
iniFile: mustNewIniFile(`
[secure_socks_datasource_proxy]
enabled = true
proxy_address = address
server_name = name
allow_insecure = true
`),
expectedSettings: SecureSocksDSProxySettings{
Enabled: true,
ProxyAddress: "address",
ServerName: "name",
ShowUI: true,
AllowInsecure: true,
},
},
{
description: "custom values",
iniFile: mustNewIniFile(`
[secure_socks_datasource_proxy]
enabled = true
client_cert = cert
client_key = key
root_ca_cert = root_ca
proxy_address = proxy_address
server_name = server_name
show_ui = false
allow_insecure = true
`),
expectedSettings: SecureSocksDSProxySettings{
Enabled: true,
ClientCert: "cert",
ClientKey: "key",
RootCA: "root_ca",
ProxyAddress: "proxy_address",
ServerName: "server_name",
ShowUI: false,
AllowInsecure: true,
},
},
}
for _, tt := range cases {
t.Run(tt.description, func(t *testing.T) {
settings, err := readSecureSocksDSProxySettings(tt.iniFile)
if tt.expectedErr != nil {
assert.Equal(t, tt.expectedErr, err)
} else {
assert.Equal(t, tt.expectedSettings, settings)
assert.NoError(t, err)
}
})
}
}