Secure socks proxy: use Grafana Plugin SDK (#71616)
This commit is contained in:
@@ -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)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+8
-3
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user