Secure socks proxy: use Grafana Plugin SDK (#71616)

This commit is contained in:
Stephanie Hingtgen 2023-07-18 16:23:02 -05:00 committed by GitHub
parent 600f623610
commit 4ece133fce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 101 additions and 226 deletions

2
.github/CODEOWNERS vendored
View File

@ -87,7 +87,6 @@
/pkg/infra/metrics/ @grafana/backend-platform
/pkg/infra/network/ @grafana/backend-platform
/pkg/infra/process/ @grafana/backend-platform
/pkg/infra/proxy/ @grafana/hosted-grafana-team
/pkg/infra/remotecache/ @grafana/backend-platform
/pkg/infra/serverlock/ @grafana/backend-platform
/pkg/infra/slugify/ @grafana/backend-platform
@ -147,6 +146,7 @@
/pkg/tsdb/legacydata/ @grafana/backend-platform
/pkg/tsdb/opentsdb/ @grafana/backend-platform
/pkg/tsdb/sqleng/ @grafana/backend-platform
/pkg/tsdb/sqleng/proxyutil @grafana/hosted-grafana-team
/pkg/util/ @grafana/backend-platform
/pkg/web/ @grafana/backend-platform

4
go.mod
View File

@ -66,7 +66,7 @@ require (
github.com/grafana/cuetsy v0.1.10 // @grafana/grafana-as-code
github.com/grafana/grafana-aws-sdk v0.15.0 // @grafana/aws-datasources
github.com/grafana/grafana-azure-sdk-go v1.7.0 // @grafana/backend-platform
github.com/grafana/grafana-plugin-sdk-go v0.170.0 // @grafana/plugins-platform-backend
github.com/grafana/grafana-plugin-sdk-go v0.171.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-plugin v1.4.9 // @grafana/plugins-platform-backend
@ -110,7 +110,7 @@ require (
go.opentelemetry.io/otel/trace v1.16.0 // @grafana/backend-platform
golang.org/x/crypto v0.11.0 // @grafana/backend-platform
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // @grafana/alerting-squad-backend
golang.org/x/net v0.10.0 // @grafana/grafana-bi-squad
golang.org/x/net v0.12.0 // @grafana/grafana-bi-squad
golang.org/x/oauth2 v0.8.0 // @grafana/grafana-authnz-team
golang.org/x/sync v0.3.0 // @grafana/alerting-squad-backend
golang.org/x/time v0.3.0 // @grafana/backend-platform

6
go.sum
View File

@ -599,6 +599,7 @@ github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac h1:XDAn206aIqKPdF5
github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac h1:9yrT5tmn9Zc0ytWPASlaPwQfQMQYnRf0RSDe1XvHw0Q=
github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ=
@ -1342,6 +1343,8 @@ github.com/grafana/grafana-plugin-sdk-go v0.165.0 h1:PTCW1bSlqPr5/k/9oz4R8NLZIiQ
github.com/grafana/grafana-plugin-sdk-go v0.165.0/go.mod h1:dPhljkVno3Bg/ZYafMrR/BfYjtCRJD2hU2719Nl3QzM=
github.com/grafana/grafana-plugin-sdk-go v0.170.0 h1:6vT+AcruJ7do4uISu+sXJCB8DSJh85SWNzClyM1caOU=
github.com/grafana/grafana-plugin-sdk-go v0.170.0/go.mod h1:4jbsFzZvseLhsEmsPhI18fZG40mkJ2xIKPj9UopNz/8=
github.com/grafana/grafana-plugin-sdk-go v0.171.0 h1:f4W4sTgm3zJzh5EewTORMoKax4PorCmNeqINzIZ0mJw=
github.com/grafana/grafana-plugin-sdk-go v0.171.0/go.mod h1:SvYXsAQWSuV9TtHqXZJdOFCaC8G0q02U+IHWMTKIGIQ=
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/phlare/api v0.1.4-0.20230426005640-f90edba05413 h1:bBzCezZNRyYlJpXTkyZdY4fpPxHZUdyeyRWzhtw/P6I=
@ -2782,6 +2785,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
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=
@ -2812,6 +2817,7 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.0.0-20221006150949-b44042a4b9c1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=

View File

@ -9,7 +9,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics/metricutil"
"github.com/grafana/grafana/pkg/infra/proxy"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/grafana/grafana/pkg/setting"
@ -54,13 +53,6 @@ func New(cfg *setting.Cfg, validator validations.PluginRequestValidator, tracer
return
}
if cfg.SecureSocksDSProxy.Enabled && proxy.SecureSocksProxyEnabledOnDS(opts) {
err = proxy.NewSecureSocksHTTPProxy(&cfg.SecureSocksDSProxy, transport)
if err != nil {
logger.Error("Failed to enable secure socks proxy", "error", err.Error(), "datasource", datasourceName)
}
}
newConntrackRoundTripper(datasourceLabelName, transport)
},
})

View File

@ -1,81 +0,0 @@
package proxy
import (
"crypto/tls"
"crypto/x509"
"errors"
"net/http"
"os"
"strings"
"github.com/grafana/grafana-plugin-sdk-go/backend"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/setting"
"golang.org/x/net/proxy"
)
// NewSecureSocksHTTPProxy takes a http.DefaultTransport and wraps it in a socks5 proxy with TLS
func NewSecureSocksHTTPProxy(cfg *setting.SecureSocksDSProxySettings, transport *http.Transport) error {
dialSocksProxy, err := NewSecureSocksProxyContextDialer(cfg)
if err != nil {
return err
}
contextDialer, ok := dialSocksProxy.(proxy.ContextDialer)
if !ok {
return errors.New("unable to cast socks proxy dialer to context proxy dialer")
}
transport.DialContext = contextDialer.DialContext
return nil
}
// NewSecureSocksProxyContextDialer returns a proxy context dialer that will wrap connections in a secure socks proxy
func NewSecureSocksProxyContextDialer(cfg *setting.SecureSocksDSProxySettings) (proxy.Dialer, error) {
certPool := x509.NewCertPool()
for _, rootCAFile := range strings.Split(cfg.RootCA, " ") {
// nolint:gosec
// The gosec G304 warning can be ignored because `rootCAFile` comes from config ini.
pem, err := os.ReadFile(rootCAFile)
if err != nil {
return nil, err
}
if !certPool.AppendCertsFromPEM(pem) {
return nil, errors.New("failed to append CA certificate " + rootCAFile)
}
}
cert, err := tls.LoadX509KeyPair(cfg.ClientCert, cfg.ClientKey)
if err != nil {
return nil, err
}
tlsDialer := &tls.Dialer{
Config: &tls.Config{
Certificates: []tls.Certificate{cert},
ServerName: cfg.ServerName,
RootCAs: certPool,
},
}
dialSocksProxy, err := proxy.SOCKS5("tcp", cfg.ProxyAddress, nil, tlsDialer)
if err != nil {
return nil, err
}
return dialSocksProxy, nil
}
// SecureSocksProxyEnabledOnDS checks the datasource json data to see if the secure socks proxy is enabled on it
func SecureSocksProxyEnabledOnDS(opts sdkhttpclient.Options) bool {
jsonData := backend.JSONDataFromHTTPClientOptions(opts)
res, enabled := jsonData["enableSecureSocksProxy"]
if !enabled {
return false
}
if val, ok := res.(bool); ok {
return val
}
return false
}

View File

@ -1,91 +0,0 @@
package proxy
import (
"net/http"
"os"
"path/filepath"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/proxy/proxyutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewSecureSocksProxy(t *testing.T) {
settings := proxyutil.SetupTestSecureSocksProxySettings(t)
// create empty file for testing invalid configs
tempDir := t.TempDir()
tempEmptyFile := filepath.Join(tempDir, "emptyfile.txt")
// nolint:gosec
// The gosec G304 warning can be ignored because all values come from the test
_, err := os.Create(tempEmptyFile)
require.NoError(t, err)
t.Run("New socks proxy should be properly configured when all settings are valid", func(t *testing.T) {
require.NoError(t, NewSecureSocksHTTPProxy(settings, &http.Transport{}))
})
t.Run("Client cert must be valid", func(t *testing.T) {
clientCertBefore := settings.ClientCert
settings.ClientCert = tempEmptyFile
t.Cleanup(func() {
settings.ClientCert = clientCertBefore
})
require.Error(t, NewSecureSocksHTTPProxy(settings, &http.Transport{}))
})
t.Run("Client key must be valid", func(t *testing.T) {
clientKeyBefore := settings.ClientKey
settings.ClientKey = tempEmptyFile
t.Cleanup(func() {
settings.ClientKey = clientKeyBefore
})
require.Error(t, NewSecureSocksHTTPProxy(settings, &http.Transport{}))
})
t.Run("Root CA must be valid", func(t *testing.T) {
rootCABefore := settings.RootCA
settings.RootCA = tempEmptyFile
t.Cleanup(func() {
settings.RootCA = rootCABefore
})
require.Error(t, NewSecureSocksHTTPProxy(settings, &http.Transport{}))
})
}
func TestSecureSocksProxyEnabledOnDS(t *testing.T) {
t.Run("Secure socks proxy should only be enabled when the json data contains enableSecureSocksProxy=true", func(t *testing.T) {
tests := []struct {
instanceSettings *backend.AppInstanceSettings
enabled bool
}{
{
instanceSettings: &backend.AppInstanceSettings{
JSONData: []byte("{}"),
},
enabled: false,
},
{
instanceSettings: &backend.AppInstanceSettings{
JSONData: []byte("{ \"enableSecureSocksProxy\": false }"),
},
enabled: false,
},
{
instanceSettings: &backend.AppInstanceSettings{
JSONData: []byte("{ \"enableSecureSocksProxy\": true }"),
},
enabled: true,
},
}
for _, tt := range tests {
opts, err := tt.instanceSettings.HTTPClientOptions()
assert.NoError(t, err)
assert.Equal(t, tt.enabled, SecureSocksProxyEnabledOnDS(opts))
}
})
}

View File

@ -3,6 +3,7 @@ package setting
import (
"errors"
sdkproxy "github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
"gopkg.in/ini.v1"
)
@ -42,5 +43,21 @@ func readSecureSocksDSProxySettings(iniFile *ini.File) (SecureSocksDSProxySettin
return s, errors.New("proxy address required")
}
setDefaultProxyCli(s)
return s, nil
}
// setDefaultProxyCli overrides the default proxy cli for the sdk
//
// Note: Not optimal changing global state, but hard to not do in this case.
func setDefaultProxyCli(cfg SecureSocksDSProxySettings) {
sdkproxy.Cli = sdkproxy.NewWithCfg(&sdkproxy.ClientCfg{
Enabled: cfg.Enabled,
ClientCert: cfg.ClientCert,
ClientKey: cfg.ClientKey,
ServerName: cfg.ServerName,
RootCA: cfg.RootCA,
ProxyAddress: cfg.ProxyAddress,
})
}

View File

@ -14,12 +14,14 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
sdkproxy "github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana-plugin-sdk-go/data/sqlutil"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"github.com/grafana/grafana/pkg/tsdb/sqleng/proxyutil"
"github.com/grafana/grafana/pkg/util"
)
@ -94,8 +96,9 @@ func newInstanceSettings(cfg *setting.Cfg) datasource.InstanceFactoryFunc {
driverName := "mssql"
// register a new proxy driver if the secure socks proxy is enabled
if cfg.SecureSocksDSProxy.Enabled && jsonData.SecureDSProxy {
driverName, err = createMSSQLProxyDriver(&cfg.SecureSocksDSProxy, cnnstr)
proxyOpts := proxyutil.GetSQLProxyOptions(dsInfo)
if sdkproxy.Cli.SecureSocksProxyEnabled(proxyOpts) {
driverName, err = createMSSQLProxyDriver(cnnstr, proxyOpts)
if err != nil {
return nil, err
}

View File

@ -7,8 +7,7 @@ import (
"errors"
mssql "github.com/grafana/go-mssqldb"
iproxy "github.com/grafana/grafana/pkg/infra/proxy"
"github.com/grafana/grafana/pkg/setting"
sdkproxy "github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"github.com/grafana/grafana/pkg/util"
"golang.org/x/net/proxy"
@ -17,7 +16,7 @@ import (
// createMSSQLProxyDriver creates and registers a new sql driver that uses a mssql connector and updates the dialer to
// route connections through the secure socks proxy
func createMSSQLProxyDriver(settings *setting.SecureSocksDSProxySettings, cnnstr string) (string, error) {
func createMSSQLProxyDriver(cnnstr string, opts *sdkproxy.Options) (string, error) {
sqleng.XormDriverMu.Lock()
defer sqleng.XormDriverMu.Unlock()
@ -35,7 +34,7 @@ func createMSSQLProxyDriver(settings *setting.SecureSocksDSProxySettings, cnnstr
return "", err
}
driver, err := newMSSQLProxyDriver(settings, connector)
driver, err := newMSSQLProxyDriver(connector, opts)
if err != nil {
return "", err
}
@ -57,8 +56,8 @@ var _ core.Driver = (*mssqlProxyDriver)(nil)
// newMSSQLProxyDriver updates the dialer for a mssql connector with a dialer that proxys connections through the secure socks proxy
// and returns a new mssql driver to register
func newMSSQLProxyDriver(cfg *setting.SecureSocksDSProxySettings, connector *mssql.Connector) (*mssqlProxyDriver, error) {
dialer, err := iproxy.NewSecureSocksProxyContextDialer(cfg)
func newMSSQLProxyDriver(connector *mssql.Connector, opts *sdkproxy.Options) (*mssqlProxyDriver, error) {
dialer, err := sdkproxy.Cli.NewSecureSocksProxyContextDialer(opts)
if err != nil {
return nil, err
}

View File

@ -6,7 +6,8 @@ import (
"testing"
mssql "github.com/grafana/go-mssqldb"
"github.com/grafana/grafana/pkg/infra/proxy/proxyutil"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"github.com/grafana/grafana/pkg/tsdb/sqleng/proxyutil"
"github.com/stretchr/testify/require"
"xorm.io/core"
)
@ -14,18 +15,19 @@ import (
func TestMSSQLProxyDriver(t *testing.T) {
settings := proxyutil.SetupTestSecureSocksProxySettings(t)
dialect := "mssql"
opts := proxyutil.GetSQLProxyOptions(sqleng.DataSourceInfo{UID: "1", JsonData: sqleng.JsonData{SecureDSProxy: true}})
cnnstr := "server=127.0.0.1;port=1433;user id=sa;password=yourStrong(!)Password;database=db"
driverName, err := createMSSQLProxyDriver(settings, cnnstr)
driverName, err := createMSSQLProxyDriver(cnnstr, opts)
require.NoError(t, err)
t.Run("Driver should not be registered more than once", func(t *testing.T) {
testDriver, err := createMSSQLProxyDriver(settings, cnnstr)
testDriver, err := createMSSQLProxyDriver(cnnstr, opts)
require.NoError(t, err)
require.Equal(t, driverName, testDriver)
})
t.Run("A new driver should be created for a new connection string", func(t *testing.T) {
testDriver, err := createMSSQLProxyDriver(settings, "server=localhost;user id=sa;password=yourStrong(!)Password;database=db2")
testDriver, err := createMSSQLProxyDriver("server=localhost;user id=sa;password=yourStrong(!)Password;database=db2", opts)
require.NoError(t, err)
require.NotEqual(t, driverName, testDriver)
})
@ -44,7 +46,7 @@ func TestMSSQLProxyDriver(t *testing.T) {
t.Run("Connector should use dialer context that routes through the socks proxy to db", func(t *testing.T) {
connector, err := mssql.NewConnector(cnnstr)
require.NoError(t, err)
driver, err := newMSSQLProxyDriver(settings, connector)
driver, err := newMSSQLProxyDriver(connector, opts)
require.NoError(t, err)
conn, err := driver.OpenConnector(cnnstr)
@ -57,7 +59,7 @@ func TestMSSQLProxyDriver(t *testing.T) {
t.Run("Open should use the connector that routes through the socks proxy to db", func(t *testing.T) {
connector, err := mssql.NewConnector(cnnstr)
require.NoError(t, err)
driver, err := newMSSQLProxyDriver(settings, connector)
driver, err := newMSSQLProxyDriver(connector, opts)
require.NoError(t, err)
_, err = driver.Open(cnnstr)

View File

@ -16,6 +16,7 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
sdkproxy "github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana-plugin-sdk-go/data/sqlutil"
@ -23,6 +24,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"github.com/grafana/grafana/pkg/tsdb/sqleng/proxyutil"
)
const (
@ -85,10 +87,11 @@ func newInstanceSettings(cfg *setting.Cfg, httpClientProvider httpclient.Provide
}
// register the secure socks proxy dialer context, if enabled
if cfg.SecureSocksDSProxy.Enabled && jsonData.SecureDSProxy {
proxyOpts := proxyutil.GetSQLProxyOptions(dsInfo)
if sdkproxy.Cli.SecureSocksProxyEnabled(proxyOpts) {
// UID is only unique per org, the only way to ensure uniqueness is to do it by connection information
uniqueIdentifier := dsInfo.User + dsInfo.DecryptedSecureJSONData["password"] + dsInfo.URL + dsInfo.Database
protocol, err = registerProxyDialerContext(&cfg.SecureSocksDSProxy, protocol, uniqueIdentifier)
protocol, err = registerProxyDialerContext(protocol, uniqueIdentifier, proxyOpts)
if err != nil {
return nil, err
}

View File

@ -5,17 +5,16 @@ import (
"net"
"github.com/go-sql-driver/mysql"
iproxy "github.com/grafana/grafana/pkg/infra/proxy"
"github.com/grafana/grafana/pkg/setting"
sdkproxy "github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
"github.com/grafana/grafana/pkg/util"
"golang.org/x/net/proxy"
)
// registerProxyDialerContext registers a new dialer context to be used by mysql when the proxy network is
// specified in the connection string
func registerProxyDialerContext(settings *setting.SecureSocksDSProxySettings, protocol, cnnstr string) (string, error) {
func registerProxyDialerContext(protocol, cnnstr string, opts *sdkproxy.Options) (string, error) {
// the dialer contains the true network used behind the scenes
dialer, err := getProxyDialerContext(settings, protocol)
dialer, err := getProxyDialerContext(protocol, opts)
if err != nil {
return "", err
}
@ -39,8 +38,8 @@ type mySQLContextDialer struct {
}
// getProxyDialerContext returns a context dialer that will send the request through to the secure socks proxy
func getProxyDialerContext(cfg *setting.SecureSocksDSProxySettings, actualNetwork string) (*mySQLContextDialer, error) {
dialer, err := iproxy.NewSecureSocksProxyContextDialer(cfg)
func getProxyDialerContext(actualNetwork string, opts *sdkproxy.Options) (*mySQLContextDialer, error) {
dialer, err := sdkproxy.Cli.NewSecureSocksProxyContextDialer(opts)
if err != nil {
return nil, err
}

View File

@ -6,7 +6,8 @@ import (
"testing"
"github.com/go-sql-driver/mysql"
"github.com/grafana/grafana/pkg/infra/proxy/proxyutil"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"github.com/grafana/grafana/pkg/tsdb/sqleng/proxyutil"
"github.com/stretchr/testify/require"
)
@ -14,10 +15,11 @@ func TestMySQLProxyDialer(t *testing.T) {
settings := proxyutil.SetupTestSecureSocksProxySettings(t)
protocol := "tcp"
network, err := registerProxyDialerContext(settings, protocol, "1")
opts := proxyutil.GetSQLProxyOptions(sqleng.DataSourceInfo{UID: "1", JsonData: sqleng.JsonData{SecureDSProxy: true}})
dbURL := "localhost:5432"
network, err := registerProxyDialerContext(protocol, dbURL, opts)
require.NoError(t, err)
driver := mysql.MySQLDriver{}
dbURL := "localhost:5432"
cnnstr := fmt.Sprintf("test:test@%s(%s)/db",
network,
dbURL,
@ -28,7 +30,7 @@ func TestMySQLProxyDialer(t *testing.T) {
})
t.Run("Multiple networks can be created", func(t *testing.T) {
network, err := registerProxyDialerContext(settings, protocol, "2")
network, err := registerProxyDialerContext(protocol, dbURL, opts)
require.NoError(t, err)
cnnstr2 := fmt.Sprintf("test:test@%s(%s)/db",
network,

View File

@ -11,12 +11,14 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
sdkproxy "github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana-plugin-sdk-go/data/sqlutil"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"github.com/grafana/grafana/pkg/tsdb/sqleng/proxyutil"
)
var logger = log.New("tsdb.postgres")
@ -95,8 +97,9 @@ func (s *Service) newInstanceSettings(cfg *setting.Cfg) datasource.InstanceFacto
driverName := "postgres"
// register a proxy driver if the secure socks proxy is enabled
if cfg.SecureSocksDSProxy.Enabled && jsonData.SecureDSProxy {
driverName, err = createPostgresProxyDriver(&cfg.SecureSocksDSProxy, cnnstr)
proxyOpts := proxyutil.GetSQLProxyOptions(dsInfo)
if sdkproxy.Cli.SecureSocksProxyEnabled(proxyOpts) {
driverName, err = createPostgresProxyDriver(cnnstr, proxyOpts)
if err != nil {
return "", nil
}

View File

@ -7,8 +7,7 @@ import (
"net"
"time"
iproxy "github.com/grafana/grafana/pkg/infra/proxy"
"github.com/grafana/grafana/pkg/setting"
sdkproxy "github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"github.com/grafana/grafana/pkg/util"
"github.com/lib/pq"
@ -18,7 +17,7 @@ import (
// createPostgresProxyDriver creates and registers a new sql driver that uses a postgres connector and updates the dialer to
// route connections through the secure socks proxy
func createPostgresProxyDriver(settings *setting.SecureSocksDSProxySettings, cnnstr string) (string, error) {
func createPostgresProxyDriver(cnnstr string, opts *sdkproxy.Options) (string, error) {
sqleng.XormDriverMu.Lock()
defer sqleng.XormDriverMu.Unlock()
@ -36,7 +35,7 @@ func createPostgresProxyDriver(settings *setting.SecureSocksDSProxySettings, cnn
return "", err
}
driver, err := newPostgresProxyDriver(settings, connector)
driver, err := newPostgresProxyDriver(connector, opts)
if err != nil {
return "", err
}
@ -58,8 +57,8 @@ var _ core.Driver = (*postgresProxyDriver)(nil)
// newPostgresProxyDriver updates the dialer for a postgres connector with a dialer that proxys connections through the secure socks proxy
// and returns a new postgres driver to register
func newPostgresProxyDriver(cfg *setting.SecureSocksDSProxySettings, connector *pq.Connector) (*postgresProxyDriver, error) {
dialer, err := iproxy.NewSecureSocksProxyContextDialer(cfg)
func newPostgresProxyDriver(connector *pq.Connector, opts *sdkproxy.Options) (*postgresProxyDriver, error) {
dialer, err := sdkproxy.Cli.NewSecureSocksProxyContextDialer(opts)
if err != nil {
return nil, err
}

View File

@ -5,7 +5,8 @@ import (
"fmt"
"testing"
"github.com/grafana/grafana/pkg/infra/proxy/proxyutil"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"github.com/grafana/grafana/pkg/tsdb/sqleng/proxyutil"
"github.com/lib/pq"
"github.com/stretchr/testify/require"
"xorm.io/core"
@ -13,20 +14,21 @@ import (
func TestPostgresProxyDriver(t *testing.T) {
dialect := "postgres"
opts := proxyutil.GetSQLProxyOptions(sqleng.DataSourceInfo{UID: "1", JsonData: sqleng.JsonData{SecureDSProxy: true}})
settings := proxyutil.SetupTestSecureSocksProxySettings(t)
dbURL := "localhost:5432"
cnnstr := fmt.Sprintf("postgres://auser:password@%s/db?sslmode=disable", dbURL)
driverName, err := createPostgresProxyDriver(settings, cnnstr)
driverName, err := createPostgresProxyDriver(cnnstr, opts)
require.NoError(t, err)
t.Run("Driver should not be registered more than once", func(t *testing.T) {
testDriver, err := createPostgresProxyDriver(settings, cnnstr)
testDriver, err := createPostgresProxyDriver(cnnstr, opts)
require.NoError(t, err)
require.Equal(t, driverName, testDriver)
})
t.Run("A new driver should be created for a new connection string", func(t *testing.T) {
testDriver, err := createPostgresProxyDriver(settings, "server=localhost;user id=sa;password=yourStrong(!)Password;database=db2")
testDriver, err := createPostgresProxyDriver("server=localhost;user id=sa;password=yourStrong(!)Password;database=db2", opts)
require.NoError(t, err)
require.NotEqual(t, driverName, testDriver)
})
@ -45,7 +47,7 @@ func TestPostgresProxyDriver(t *testing.T) {
t.Run("Connector should use dialer context that routes through the socks proxy to db", func(t *testing.T) {
connector, err := pq.NewConnector(cnnstr)
require.NoError(t, err)
driver, err := newPostgresProxyDriver(settings, connector)
driver, err := newPostgresProxyDriver(connector, opts)
require.NoError(t, err)
conn, err := driver.OpenConnector(cnnstr)
@ -58,7 +60,7 @@ func TestPostgresProxyDriver(t *testing.T) {
t.Run("Connector should use dialer context that routes through the socks proxy to db", func(t *testing.T) {
connector, err := pq.NewConnector(cnnstr)
require.NoError(t, err)
driver, err := newPostgresProxyDriver(settings, connector)
driver, err := newPostgresProxyDriver(connector, opts)
require.NoError(t, err)
conn, err := driver.OpenConnector(cnnstr)

View File

@ -12,11 +12,12 @@ import (
"testing"
"time"
"github.com/grafana/grafana/pkg/setting"
sdkproxy "github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
"github.com/stretchr/testify/require"
)
func SetupTestSecureSocksProxySettings(t *testing.T) *setting.SecureSocksDSProxySettings {
func SetupTestSecureSocksProxySettings(t *testing.T) *sdkproxy.ClientCfg {
t.Helper()
proxyAddress := "localhost:3000"
serverName := "localhost"
tempDir := t.TempDir()
@ -98,11 +99,15 @@ func SetupTestSecureSocksProxySettings(t *testing.T) *setting.SecureSocksDSProxy
err = keyFile.Close()
require.NoError(t, err)
return &setting.SecureSocksDSProxySettings{
settings := &sdkproxy.ClientCfg{
Enabled: true,
ClientCert: clientCert,
ClientKey: clientKey,
RootCA: rootCACert,
ServerName: serverName,
ProxyAddress: proxyAddress,
}
sdkproxy.Cli = sdkproxy.NewWithCfg(settings)
return settings
}

View File

@ -0,0 +1,15 @@
package proxyutil
import (
sdkproxy "github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
)
func GetSQLProxyOptions(dsInfo sqleng.DataSourceInfo) *sdkproxy.Options {
return &sdkproxy.Options{
Enabled: dsInfo.JsonData.SecureDSProxy,
Auth: &sdkproxy.AuthOptions{
Username: dsInfo.UID,
},
}
}