From d26a1454c8f4af2f4f8bed9939d54f6986751e9c Mon Sep 17 00:00:00 2001 From: Will Browne Date: Tue, 26 Mar 2024 12:52:13 +0100 Subject: [PATCH] [v10.4.x] Plugins: Pass PDC file contents in requests (#85144) * Plugins: Pass PDC file contents in requests (#84783) * Plugins: Pass PDC file contents in requests * go mod tidy * undo go.mod changes * fix linter * fix tests * undo unnecessary changes * update dep * join with comma * update naming * bump SDK (cherry picked from commit b765c21d4c88c96d8a68d55d03fbbd33307bd4cf) * set env + req config * fix linter --- go.mod | 12 +- go.sum | 20 ++- pkg/plugins/envvars/envvars.go | 23 ++- pkg/plugins/envvars/envvars_test.go | 6 +- .../datasources/service/datasource.go | 2 +- pkg/setting/setting_secure_socks_proxy.go | 74 +++++++-- .../setting_secure_socks_proxy_test.go | 152 +++++++++++++----- 7 files changed, 210 insertions(+), 79 deletions(-) diff --git a/go.mod b/go.mod index f6da3e46fcf..932950ff026 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code github.com/grafana/grafana-aws-sdk v0.23.1 // @grafana/aws-datasources github.com/grafana/grafana-azure-sdk-go v1.12.0 // @grafana/partner-datasources - github.com/grafana/grafana-plugin-sdk-go v0.215.0 // @grafana/plugins-platform-backend + github.com/grafana/grafana-plugin-sdk-go v0.217.0 // @grafana/plugins-platform-backend github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // @grafana/backend-platform github.com/hashicorp/go-hclog v1.6.2 // @grafana/plugins-platform-backend github.com/hashicorp/go-plugin v1.6.0 // @grafana/plugins-platform-backend @@ -105,9 +105,9 @@ require ( go.opentelemetry.io/otel/exporters/jaeger v1.10.0 // @grafana/backend-platform go.opentelemetry.io/otel/sdk v1.24.0 // @grafana/backend-platform go.opentelemetry.io/otel/trace v1.24.0 // @grafana/backend-platform - golang.org/x/crypto v0.19.0 // @grafana/backend-platform + golang.org/x/crypto v0.21.0 // @grafana/backend-platform golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // @grafana/alerting-squad-backend - golang.org/x/net v0.21.0 // @grafana/oss-big-tent @grafana/partner-datasources + golang.org/x/net v0.22.0 // @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/oauth2 v0.16.0 // @grafana/grafana-authnz-team golang.org/x/sync v0.6.0 // @grafana/alerting-squad-backend golang.org/x/time v0.5.0 // @grafana/backend-platform @@ -115,7 +115,7 @@ require ( gonum.org/v1/gonum v0.12.0 // @grafana/observability-metrics google.golang.org/api v0.155.0 // @grafana/backend-platform google.golang.org/grpc v1.62.1 // @grafana/plugins-platform-backend - google.golang.org/protobuf v1.32.0 // @grafana/plugins-platform-backend + google.golang.org/protobuf v1.33.0 // @grafana/plugins-platform-backend gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/ini.v1 v1.67.0 // @grafana/alerting-squad-backend gopkg.in/mail.v2 v2.3.1 // @grafana/backend-platform @@ -214,7 +214,7 @@ require ( go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.11.0 // @grafana/alerting-squad-backend go.uber.org/goleak v1.3.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // @grafana/backend-platform golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/appengine v1.6.8 // indirect @@ -403,7 +403,7 @@ require ( go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/term v0.17.0 // indirect + golang.org/x/term v0.18.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect diff --git a/go.sum b/go.sum index 81c8d674e23..99600516ac9 100644 --- a/go.sum +++ b/go.sum @@ -2535,8 +2535,8 @@ github.com/grafana/grafana-google-sdk-go v0.1.0/go.mod h1:Vo2TKWfDVmNTELBUM+3lkr github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs= github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ= 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.215.0 h1:02gwVsqYi1I+U48/MQR61eOMxiXE7KNKC8QsiMJ//qA= -github.com/grafana/grafana-plugin-sdk-go v0.215.0/go.mod h1:nBsh3jRItKQUXDF2BQkiQCPxqrsSQeb+7hiFyJTO1RE= +github.com/grafana/grafana-plugin-sdk-go v0.217.0 h1:oQjq5KRrVrhweXHxFtEMgjv1KW7hujGiRPIYrsPZ9PE= +github.com/grafana/grafana-plugin-sdk-go v0.217.0/go.mod h1:FdvSvOliqpVLnytM7e89zCFyYPDE6VOn9SIjVQRvVxM= 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.20240208102907-e82436ce63e6 h1:CBm0rwLCPDyarg9/bHJ50rBLYmyMDoyCWpgRMITZhdA= @@ -3893,8 +3893,9 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -4080,8 +4081,8 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -4319,8 +4320,9 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -4341,8 +4343,9 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -4930,8 +4933,9 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/DataDog/dd-trace-go.v1 v1.27.0/go.mod h1:Sp1lku8WJMvNV0kjDI4Ni/T7J/U3BO5ct5kEaoVU8+I= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= diff --git a/pkg/plugins/envvars/envvars.go b/pkg/plugins/envvars/envvars.go index 62c769290f8..9b343281b47 100644 --- a/pkg/plugins/envvars/envvars.go +++ b/pkg/plugins/envvars/envvars.go @@ -152,7 +152,7 @@ func (s *Service) GetConfigMap(ctx context.Context, pluginID string, _ *auth.Ext m[proxy.PluginSecureSocksProxyEnabled] = "true" m[proxy.PluginSecureSocksProxyClientCert] = s.cfg.ProxySettings.ClientCert m[proxy.PluginSecureSocksProxyClientKey] = s.cfg.ProxySettings.ClientKey - m[proxy.PluginSecureSocksProxyRootCACert] = s.cfg.ProxySettings.RootCA + m[proxy.PluginSecureSocksProxyRootCAs] = strings.Join(s.cfg.ProxySettings.RootCAs, " ") m[proxy.PluginSecureSocksProxyProxyAddress] = s.cfg.ProxySettings.ProxyAddress m[proxy.PluginSecureSocksProxyServerName] = s.cfg.ProxySettings.ServerName m[proxy.PluginSecureSocksProxyAllowInsecure] = strconv.FormatBool(s.cfg.ProxySettings.AllowInsecure) @@ -287,13 +287,20 @@ func (s *Service) awsEnvVars() []string { func (s *Service) secureSocksProxyEnvVars() []string { if s.cfg.ProxySettings.Enabled { return []string{ - proxy.PluginSecureSocksProxyClientCert + "=" + s.cfg.ProxySettings.ClientCert, - proxy.PluginSecureSocksProxyClientKey + "=" + s.cfg.ProxySettings.ClientKey, - proxy.PluginSecureSocksProxyRootCACert + "=" + s.cfg.ProxySettings.RootCA, - 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), + // nolint:staticcheck + proxy.PluginSecureSocksProxyClientCertFilePathEnvVarName + "=" + s.cfg.ProxySettings.ClientCertFilePath, + // nolint:staticcheck + proxy.PluginSecureSocksProxyClientKeyFilePathEnvVarName + "=" + s.cfg.ProxySettings.ClientKeyFilePath, + // nolint:staticcheck + proxy.PluginSecureSocksProxyRootCACertFilePathsEnvVarName + "=" + strings.Join(s.cfg.ProxySettings.RootCAFilePaths, " "), + // nolint:staticcheck + proxy.PluginSecureSocksProxyAddressEnvVarName + "=" + s.cfg.ProxySettings.ProxyAddress, + // nolint:staticcheck + proxy.PluginSecureSocksProxyServerNameEnvVarName + "=" + s.cfg.ProxySettings.ServerName, + // nolint:staticcheck + proxy.PluginSecureSocksProxyEnabledEnvVarName + "=" + strconv.FormatBool(s.cfg.ProxySettings.Enabled), + // nolint:staticcheck + proxy.PluginSecureSocksProxyAllowInsecureEnvVarName + "=" + 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 f782ddefba8..9e626fe532f 100644 --- a/pkg/plugins/envvars/envvars_test.go +++ b/pkg/plugins/envvars/envvars_test.go @@ -686,7 +686,7 @@ func TestService_GetConfigMap(t *testing.T) { ShowUI: true, ClientCert: "c3rt", ClientKey: "k3y", - RootCA: "ca", + RootCAs: []string{"ca"}, ProxyAddress: "https://proxy.grafana.com", ServerName: "secureProxy", AllowInsecure: true, @@ -712,7 +712,7 @@ func TestService_GetConfigMap(t *testing.T) { ShowUI: true, ClientCert: "c3rt", ClientKey: "k3y", - RootCA: "ca", + RootCAs: []string{"ca"}, ProxyAddress: "https://proxy.grafana.com", ServerName: "secureProxy", }, @@ -730,7 +730,7 @@ func TestService_GetConfigMap(t *testing.T) { ShowUI: true, ClientCert: "c3rt", ClientKey: "k3y", - RootCA: "ca", + RootCAs: []string{"ca"}, ProxyAddress: "https://proxy.grafana.com", ServerName: "secureProxy", }, diff --git a/pkg/services/datasources/service/datasource.go b/pkg/services/datasources/service/datasource.go index 593da43dede..43f2ccd7946 100644 --- a/pkg/services/datasources/service/datasource.go +++ b/pkg/services/datasources/service/datasource.go @@ -534,7 +534,7 @@ func (s *Service) httpClientOptions(ctx context.Context, ds *datasources.DataSou ClientCfg: &sdkproxy.ClientCfg{ ClientCert: s.cfg.SecureSocksDSProxy.ClientCert, ClientKey: s.cfg.SecureSocksDSProxy.ClientKey, - RootCA: s.cfg.SecureSocksDSProxy.RootCA, + RootCAs: s.cfg.SecureSocksDSProxy.RootCAs, 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 815d7f60006..6e034cfa313 100644 --- a/pkg/setting/setting_secure_socks_proxy.go +++ b/pkg/setting/setting_secure_socks_proxy.go @@ -1,33 +1,41 @@ package setting import ( + "encoding/pem" "errors" + "os" "gopkg.in/ini.v1" ) type SecureSocksDSProxySettings struct { - Enabled bool - ShowUI bool - AllowInsecure bool - ClientCert string - ClientKey string - RootCA string - ProxyAddress string - ServerName string + Enabled bool + ShowUI bool + AllowInsecure bool + ClientCert string + ClientCertFilePath string + ClientKey string + ClientKeyFilePath string + RootCAs []string + RootCAFilePaths []string + ProxyAddress string + ServerName string } func readSecureSocksDSProxySettings(iniFile *ini.File) (SecureSocksDSProxySettings, error) { - s := SecureSocksDSProxySettings{} + s := SecureSocksDSProxySettings{ + RootCAs: []string{}, + RootCAFilePaths: []string{}, + } secureSocksProxySection := iniFile.Section("secure_socks_datasource_proxy") s.Enabled = secureSocksProxySection.Key("enabled").MustBool(false) - s.ClientCert = secureSocksProxySection.Key("client_cert").MustString("") - s.ClientKey = secureSocksProxySection.Key("client_key").MustString("") - s.RootCA = secureSocksProxySection.Key("root_ca_cert").MustString("") 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) + s.ClientCertFilePath = secureSocksProxySection.Key("client_cert").MustString("") + s.ClientKeyFilePath = secureSocksProxySection.Key("client_key").MustString("") + s.RootCAFilePaths = secureSocksProxySection.Key("root_ca_cert").Strings(" ") if !s.Enabled { return s, nil @@ -40,14 +48,50 @@ func readSecureSocksDSProxySettings(iniFile *ini.File) (SecureSocksDSProxySettin // 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 == "" { + if len(s.RootCAFilePaths) == 0 { + return s, errors.New("one or more rootCA required") + } else if s.ClientCertFilePath == "" || s.ClientKeyFilePath == "" { return s, errors.New("client key pair required") } else if s.ServerName == "" { return s, errors.New("server name required") } + } else { + return s, nil } + if s.ClientCertFilePath != "" { + certPEMBlock, err := os.ReadFile(s.ClientCertFilePath) + if err != nil { + return s, err + } + s.ClientCert = string(certPEMBlock) + } + + if s.ClientKeyFilePath != "" { + keyPEMBlock, err := os.ReadFile(s.ClientKeyFilePath) + if err != nil { + return s, err + } + s.ClientKey = string(keyPEMBlock) + } + + var rootCAs []string + for _, rootCAFile := range s.RootCAFilePaths { + // nolint:gosec + // The gosec G304 warning can be ignored because `rootCAFile` comes from config ini, and we check below if + // it's the right file type. + pemBytes, err := os.ReadFile(rootCAFile) + if err != nil { + return s, err + } + + pemDecoded, _ := pem.Decode(pemBytes) + if pemDecoded == nil || pemDecoded.Type != "CERTIFICATE" { + return s, errors.New("root ca is invalid") + } + rootCAs = append(rootCAs, string(pemBytes)) + } + s.RootCAs = rootCAs + return s, nil } diff --git a/pkg/setting/setting_secure_socks_proxy_test.go b/pkg/setting/setting_secure_socks_proxy_test.go index 3d8ac9f301c..c198f70a556 100644 --- a/pkg/setting/setting_secure_socks_proxy_test.go +++ b/pkg/setting/setting_secure_socks_proxy_test.go @@ -1,25 +1,46 @@ package setting import ( + "bytes" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" "errors" "fmt" + "math/big" + "os" + "path/filepath" "testing" + "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "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() + tempDir := t.TempDir() + testFilePath := filepath.Join(tempDir, "test") + testFileData := "foobar" + err := os.WriteFile(testFilePath, []byte(testFileData), 0600) + require.NoError(t, err) + + rootCACertFilePath := filepath.Join(tempDir, "ca.cert") + // nolint:gosec + caCertFile, err := os.Create(rootCACertFilePath) + require.NoError(t, err) + defer func() { + err = caCertFile.Close() + require.NoError(t, err) + }() + + rootCaFileData := createTestRootCAFile(t, rootCACertFilePath) + require.NoError(t, err) + cases := []struct { description string iniFile *ini.File @@ -32,24 +53,27 @@ func TestReadSecureSocksDSProxySettings(t *testing.T) { [secure_socks_datasource_proxy] `), expectedSettings: SecureSocksDSProxySettings{ - Enabled: false, - ClientCert: "", - ClientKey: "", - RootCA: "", - ProxyAddress: "", - ServerName: "", - ShowUI: true, - AllowInsecure: false, + Enabled: false, + ShowUI: true, + AllowInsecure: false, + ClientCertFilePath: "", + ClientCert: "", + ClientKey: "", + ClientKeyFilePath: "", + RootCAs: []string{}, + RootCAFilePaths: []string{}, + ProxyAddress: "", + ServerName: "", }, }, { - description: "root ca is required", + description: "one or more root ca is required", iniFile: mustNewIniFile(` [secure_socks_datasource_proxy] enabled = true proxy_address = address `), - expectedErr: errors.New("rootCA required"), + expectedErr: errors.New("one or more rootCA required"), }, { description: "client cert is required", @@ -106,35 +130,40 @@ server_name = name allow_insecure = true `), expectedSettings: SecureSocksDSProxySettings{ - Enabled: true, - ProxyAddress: "address", - ServerName: "name", - ShowUI: true, - AllowInsecure: true, + Enabled: true, + ProxyAddress: "address", + ServerName: "name", + ShowUI: true, + AllowInsecure: true, + RootCAFilePaths: []string{}, + RootCAs: []string{}, }, }, { description: "custom values", - iniFile: mustNewIniFile(` + iniFile: mustNewIniFile(fmt.Sprintf(` [secure_socks_datasource_proxy] enabled = true - client_cert = cert - client_key = key - root_ca_cert = root_ca + client_cert = %s + client_key = %s + root_ca_cert = %s proxy_address = proxy_address server_name = server_name show_ui = false - allow_insecure = true - `), + allow_insecure = false + `, testFilePath, testFilePath, rootCACertFilePath)), expectedSettings: SecureSocksDSProxySettings{ - Enabled: true, - ClientCert: "cert", - ClientKey: "key", - RootCA: "root_ca", - ProxyAddress: "proxy_address", - ServerName: "server_name", - ShowUI: false, - AllowInsecure: true, + Enabled: true, + ShowUI: false, + AllowInsecure: false, + ClientCert: testFileData, + ClientCertFilePath: testFilePath, + ClientKey: testFileData, + ClientKeyFilePath: testFilePath, + RootCAs: []string{rootCaFileData}, + RootCAFilePaths: []string{rootCACertFilePath}, + ProxyAddress: "proxy_address", + ServerName: "server_name", }, }, } @@ -146,9 +175,56 @@ allow_insecure = true if tt.expectedErr != nil { assert.Equal(t, tt.expectedErr, err) } else { - assert.Equal(t, tt.expectedSettings, settings) assert.NoError(t, err) + assert.Equal(t, tt.expectedSettings, settings) } }) } } + +func createTestRootCAFile(t *testing.T, path string) string { + t.Helper() + + ca := &x509.Certificate{ + SerialNumber: big.NewInt(2019), + Subject: pkix.Name{ + Organization: []string{"Grafana Labs"}, + CommonName: "Grafana", + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + require.NoError(t, err) + caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey) + require.NoError(t, err) + + // nolint:gosec + caCertFile, err := os.Create(path) + require.NoError(t, err) + + block := &pem.Block{ + Type: "CERTIFICATE", + Bytes: caBytes, + } + err = pem.Encode(caCertFile, block) + require.NoError(t, err) + + buf := new(bytes.Buffer) + err = pem.Encode(buf, block) + require.NoError(t, err) + + return buf.String() +} + +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 +}