diff --git a/conf/defaults.ini b/conf/defaults.ini index cbccd094bd0..7d2c9686eee 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -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. diff --git a/conf/sample.ini b/conf/sample.ini index 98385adedce..74f806e7bc2 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -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] diff --git a/docs/sources/setup-grafana/configure-grafana/proxy/index.md b/docs/sources/setup-grafana/configure-grafana/proxy/index.md index 2ad831764cc..51e83cd326b 100644 --- a/docs/sources/setup-grafana/configure-grafana/proxy/index.md +++ b/docs/sources/setup-grafana/configure-grafana/proxy/index.md @@ -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. diff --git a/go.mod b/go.mod index aa301b2529b..f0d9601a881 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index af7077dd383..af58a98685a 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/pkg/plugins/envvars/envvars.go b/pkg/plugins/envvars/envvars.go index a2770d41b8f..91a8d7926e6 100644 --- a/pkg/plugins/envvars/envvars.go +++ b/pkg/plugins/envvars/envvars.go @@ -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 diff --git a/pkg/plugins/envvars/envvars_test.go b/pkg/plugins/envvars/envvars_test.go index 993f246735a..daf8df83c75 100644 --- a/pkg/plugins/envvars/envvars_test.go +++ b/pkg/plugins/envvars/envvars_test.go @@ -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", }, }, { diff --git a/pkg/services/datasources/service/datasource.go b/pkg/services/datasources/service/datasource.go index 0f7b98e5e63..be9dc343378 100644 --- a/pkg/services/datasources/service/datasource.go +++ b/pkg/services/datasources/service/datasource.go @@ -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, }, } diff --git a/pkg/setting/setting_secure_socks_proxy.go b/pkg/setting/setting_secure_socks_proxy.go index 41689d4ef02..815d7f60006 100644 --- a/pkg/setting/setting_secure_socks_proxy.go +++ b/pkg/setting/setting_secure_socks_proxy.go @@ -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 } diff --git a/pkg/setting/setting_secure_socks_proxy_test.go b/pkg/setting/setting_secure_socks_proxy_test.go new file mode 100644 index 00000000000..3d8ac9f301c --- /dev/null +++ b/pkg/setting/setting_secure_socks_proxy_test.go @@ -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) + } + }) + } +}