mirror of
https://github.com/grafana/grafana.git
synced 2025-12-20 19:44:55 +08:00
Compare commits
3 Commits
erhilse/up
...
sriram/pos
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97b8e98a2e | ||
|
|
6518ae1398 | ||
|
|
8cf48c7180 |
142
pkg/tsdb/grafana-postgresql-datasource/connection_string.go
Normal file
142
pkg/tsdb/grafana-postgresql-datasource/connection_string.go
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||||
|
"github.com/grafana/grafana/pkg/tsdb/grafana-postgresql-datasource/sqleng"
|
||||||
|
"github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateConnectionString(dsInfo sqleng.DataSourceInfo, tlsManager tlsSettingsProvider, logger log.Logger) (string, error) {
|
||||||
|
connStr, err := getInitialConnectionString(dsInfo, logger)
|
||||||
|
if err != nil {
|
||||||
|
return connStr, err
|
||||||
|
}
|
||||||
|
return getTLSIncludedConnectionString(connStr, tlsManager, dsInfo, logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInitialConnectionString(dsInfo sqleng.DataSourceInfo, logger log.Logger) (string, error) {
|
||||||
|
if dsInfo.JsonData.ConnectionType == sqleng.ConnectionTypeConnectionString {
|
||||||
|
return dsInfo.DecryptedSecureJSONData["connectionString"], validateConnectionString(dsInfo)
|
||||||
|
}
|
||||||
|
var host string
|
||||||
|
var port int
|
||||||
|
if strings.HasPrefix(dsInfo.URL, "/") {
|
||||||
|
host = dsInfo.URL
|
||||||
|
logger.Debug("Generating connection string with Unix socket specifier", "address", dsInfo.URL)
|
||||||
|
} else {
|
||||||
|
index := strings.LastIndex(dsInfo.URL, ":")
|
||||||
|
v6Index := strings.Index(dsInfo.URL, "]")
|
||||||
|
sp := strings.SplitN(dsInfo.URL, ":", 2)
|
||||||
|
host = sp[0]
|
||||||
|
if v6Index == -1 {
|
||||||
|
if len(sp) > 1 {
|
||||||
|
var err error
|
||||||
|
port, err = strconv.Atoi(sp[1])
|
||||||
|
if err != nil {
|
||||||
|
logger.Debug("Error parsing the IPv4 address", "address", dsInfo.URL)
|
||||||
|
return "", sqleng.ErrParsingPostgresURL
|
||||||
|
}
|
||||||
|
logger.Debug("Generating IPv4 connection string with network host/port pair", "host", host, "port", port, "address", dsInfo.URL)
|
||||||
|
} else {
|
||||||
|
logger.Debug("Generating IPv4 connection string with network host", "host", host, "address", dsInfo.URL)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if index == v6Index+1 {
|
||||||
|
host = dsInfo.URL[1 : index-1]
|
||||||
|
var err error
|
||||||
|
port, err = strconv.Atoi(dsInfo.URL[index+1:])
|
||||||
|
if err != nil {
|
||||||
|
logger.Debug("Error parsing the IPv6 address", "address", dsInfo.URL)
|
||||||
|
return "", sqleng.ErrParsingPostgresURL
|
||||||
|
}
|
||||||
|
logger.Debug("Generating IPv6 connection string with network host/port pair", "host", host, "port", port, "address", dsInfo.URL)
|
||||||
|
} else {
|
||||||
|
host = dsInfo.URL[1 : len(dsInfo.URL)-1]
|
||||||
|
logger.Debug("Generating IPv6 connection string with network host", "host", host, "address", dsInfo.URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connStr := fmt.Sprintf("user='%s' password='%s' host='%s' dbname='%s'",
|
||||||
|
escape(dsInfo.User), escape(dsInfo.DecryptedSecureJSONData["password"]), escape(host), escape(dsInfo.Database))
|
||||||
|
if port > 0 {
|
||||||
|
connStr += fmt.Sprintf(" port=%d", port)
|
||||||
|
}
|
||||||
|
return connStr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTLSIncludedConnectionString(connStr string, tlsManager tlsSettingsProvider, dsInfo sqleng.DataSourceInfo, logger log.Logger) (string, error) {
|
||||||
|
tlsSettings, err := tlsManager.getTLSSettings(dsInfo)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if dsInfo.JsonData.ConnectionType == sqleng.ConnectionTypeConnectionString {
|
||||||
|
connStr, err = removeTLSSettingsFromConnectionString(connStr)
|
||||||
|
if err != nil {
|
||||||
|
return connStr, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connStr += fmt.Sprintf(" sslmode='%s'", escape(string(tlsSettings.Mode)))
|
||||||
|
|
||||||
|
// there is an issue with the lib/pq module, the `verify-ca` tls mode
|
||||||
|
// does not work correctly. ( see https://github.com/lib/pq/issues/1106 )
|
||||||
|
// to workaround the problem, if the `verify-ca` mode is chosen,
|
||||||
|
// we disable sslsni.
|
||||||
|
if tlsSettings.Mode == TLSModeVerifyCA {
|
||||||
|
connStr += " sslsni=0"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach root certificate if provided
|
||||||
|
if tlsSettings.RootCertFile != "" {
|
||||||
|
logger.Debug("Setting server root certificate", "tlsRootCert", tlsSettings.RootCertFile)
|
||||||
|
connStr += fmt.Sprintf(" sslrootcert='%s'", escape(tlsSettings.RootCertFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach client certificate and key if both are provided
|
||||||
|
if tlsSettings.CertFile != "" && tlsSettings.CertKeyFile != "" {
|
||||||
|
logger.Debug("Setting TLS/SSL client auth", "tlsCert", tlsSettings.CertFile, "tlsKey", tlsSettings.CertKeyFile)
|
||||||
|
connStr += fmt.Sprintf(" sslcert='%s' sslkey='%s'", escape(tlsSettings.CertFile), escape(tlsSettings.CertKeyFile))
|
||||||
|
} else if tlsSettings.CertFile != "" || tlsSettings.CertKeyFile != "" {
|
||||||
|
return "", fmt.Errorf("TLS/SSL client certificate and key must both be specified")
|
||||||
|
}
|
||||||
|
return connStr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateConnectionString(dsInfo sqleng.DataSourceInfo) error {
|
||||||
|
connectionString := strings.ToLower(dsInfo.DecryptedSecureJSONData["connectionString"])
|
||||||
|
if dsInfo.JsonData.ConnectionType == sqleng.ConnectionTypeConnectionString && strings.TrimSpace(connectionString) == "" {
|
||||||
|
return errors.New("invalid / empty connection string")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeTLSSettingsFromConnectionString(connStr string) (string, error) {
|
||||||
|
sslPrefixes := []string{"sslmode", "sslsni", "sslrootcert", "sslcert", "sslkey"}
|
||||||
|
parsedConnectionString, err := pq.ParseURL(connStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
kv := strings.Split(parsedConnectionString, " ")
|
||||||
|
newKv := []string{}
|
||||||
|
for _, v := range kv {
|
||||||
|
lowerV := strings.ToLower(v)
|
||||||
|
isSSLParam := false
|
||||||
|
for _, prefix := range sslPrefixes {
|
||||||
|
if strings.HasPrefix(lowerV, prefix) {
|
||||||
|
isSSLParam = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isSSLParam {
|
||||||
|
newKv = append(newKv, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(newKv, " "), nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||||
|
"github.com/grafana/grafana/pkg/tsdb/grafana-postgresql-datasource/sqleng"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateConnectionString(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
dsInfo sqleng.DataSourceInfo
|
||||||
|
tlsSettings *tlsSettings
|
||||||
|
want string
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default settings shouldn't throw error",
|
||||||
|
want: "user='' password='' host='' dbname='' sslmode=''",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default settings with host, port, dbname",
|
||||||
|
dsInfo: sqleng.DataSourceInfo{URL: "host:1234", User: "user", Database: "db", DecryptedSecureJSONData: map[string]string{"password": "pass"}},
|
||||||
|
tlsSettings: &tlsSettings{Mode: "require"},
|
||||||
|
want: "user='user' password='pass' host='host' dbname='db' port=1234 sslmode='require'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default settings with host, port, dbname",
|
||||||
|
dsInfo: sqleng.DataSourceInfo{URL: "host:1234", User: "user", Database: "db", DecryptedSecureJSONData: map[string]string{"password": "pass"}},
|
||||||
|
tlsSettings: &tlsSettings{Mode: "verify-ca", ConfigurationMethod: "file-content", RootCertFile: "root", CertFile: "cert", CertKeyFile: "key"},
|
||||||
|
want: "user='user' password='pass' host='host' dbname='db' port=1234 sslmode='verify-ca' sslsni=0 sslrootcert='root' sslcert='cert' sslkey='key'",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tlssettings := tt.tlsSettings
|
||||||
|
if tlssettings == nil {
|
||||||
|
tlssettings = &tlsSettings{}
|
||||||
|
}
|
||||||
|
tlsManager := &tlsTestManager{settings: *tlssettings}
|
||||||
|
got, err := GenerateConnectionString(tt.dsInfo, tlsManager, log.DefaultLogger)
|
||||||
|
if tt.wantErr != nil {
|
||||||
|
require.NotNil(t, err)
|
||||||
|
assert.Equal(t, tt.wantErr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_removeTLSSettingsFromConnectionString(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
connStr string
|
||||||
|
want string
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should send original connection string if no ssl settings present",
|
||||||
|
connStr: "postgres://bob:secret@1.2.3.4:5432/mydb",
|
||||||
|
want: "dbname='mydb' host='1.2.3.4' password='secret' port='5432' user='bob'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should remove sslmode",
|
||||||
|
connStr: "postgres://bob:secret@1.2.3.4:5432/mydb?sslmode=verify-full",
|
||||||
|
want: "dbname='mydb' host='1.2.3.4' password='secret' port='5432' user='bob'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should respect case sensitive password",
|
||||||
|
connStr: "postgres://bob:sEcret@1.2.3.4:5432/mydb?sslmode=verify-full",
|
||||||
|
want: "dbname='mydb' host='1.2.3.4' password='sEcret' port='5432' user='bob'",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := removeTLSSettingsFromConnectionString(tt.connStr)
|
||||||
|
if tt.wantErr != nil {
|
||||||
|
require.NotNil(t, err)
|
||||||
|
assert.Equal(t, tt.wantErr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -118,7 +118,7 @@ func (s *Service) newInstanceSettings() datasource.InstanceFactoryFunc {
|
|||||||
MaxIdleConns: sqlCfg.DefaultMaxIdleConns,
|
MaxIdleConns: sqlCfg.DefaultMaxIdleConns,
|
||||||
ConnMaxLifetime: sqlCfg.DefaultMaxConnLifetimeSeconds,
|
ConnMaxLifetime: sqlCfg.DefaultMaxConnLifetimeSeconds,
|
||||||
Timescaledb: false,
|
Timescaledb: false,
|
||||||
ConfigurationMethod: "file-path",
|
ConfigurationMethod: string(TLSConfigurationMethodFilePath),
|
||||||
SecureDSProxy: false,
|
SecureDSProxy: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ func (s *Service) newInstanceSettings() datasource.InstanceFactoryFunc {
|
|||||||
DecryptedSecureJSONData: settings.DecryptedSecureJSONData,
|
DecryptedSecureJSONData: settings.DecryptedSecureJSONData,
|
||||||
}
|
}
|
||||||
|
|
||||||
cnnstr, err := s.generateConnectionString(dsInfo)
|
cnnstr, err := GenerateConnectionString(dsInfo, s.tlsManager, logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -170,86 +170,6 @@ func escape(input string) string {
|
|||||||
return strings.ReplaceAll(strings.ReplaceAll(input, `\`, `\\`), "'", `\'`)
|
return strings.ReplaceAll(strings.ReplaceAll(input, `\`, `\\`), "'", `\'`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) generateConnectionString(dsInfo sqleng.DataSourceInfo) (string, error) {
|
|
||||||
logger := s.logger
|
|
||||||
var host string
|
|
||||||
var port int
|
|
||||||
if strings.HasPrefix(dsInfo.URL, "/") {
|
|
||||||
host = dsInfo.URL
|
|
||||||
logger.Debug("Generating connection string with Unix socket specifier", "address", dsInfo.URL)
|
|
||||||
} else {
|
|
||||||
index := strings.LastIndex(dsInfo.URL, ":")
|
|
||||||
v6Index := strings.Index(dsInfo.URL, "]")
|
|
||||||
sp := strings.SplitN(dsInfo.URL, ":", 2)
|
|
||||||
host = sp[0]
|
|
||||||
if v6Index == -1 {
|
|
||||||
if len(sp) > 1 {
|
|
||||||
var err error
|
|
||||||
port, err = strconv.Atoi(sp[1])
|
|
||||||
if err != nil {
|
|
||||||
logger.Debug("Error parsing the IPv4 address", "address", dsInfo.URL)
|
|
||||||
return "", sqleng.ErrParsingPostgresURL
|
|
||||||
}
|
|
||||||
logger.Debug("Generating IPv4 connection string with network host/port pair", "host", host, "port", port, "address", dsInfo.URL)
|
|
||||||
} else {
|
|
||||||
logger.Debug("Generating IPv4 connection string with network host", "host", host, "address", dsInfo.URL)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if index == v6Index+1 {
|
|
||||||
host = dsInfo.URL[1 : index-1]
|
|
||||||
var err error
|
|
||||||
port, err = strconv.Atoi(dsInfo.URL[index+1:])
|
|
||||||
if err != nil {
|
|
||||||
logger.Debug("Error parsing the IPv6 address", "address", dsInfo.URL)
|
|
||||||
return "", sqleng.ErrParsingPostgresURL
|
|
||||||
}
|
|
||||||
logger.Debug("Generating IPv6 connection string with network host/port pair", "host", host, "port", port, "address", dsInfo.URL)
|
|
||||||
} else {
|
|
||||||
host = dsInfo.URL[1 : len(dsInfo.URL)-1]
|
|
||||||
logger.Debug("Generating IPv6 connection string with network host", "host", host, "address", dsInfo.URL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connStr := fmt.Sprintf("user='%s' password='%s' host='%s' dbname='%s'",
|
|
||||||
escape(dsInfo.User), escape(dsInfo.DecryptedSecureJSONData["password"]), escape(host), escape(dsInfo.Database))
|
|
||||||
if port > 0 {
|
|
||||||
connStr += fmt.Sprintf(" port=%d", port)
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsSettings, err := s.tlsManager.getTLSSettings(dsInfo)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
connStr += fmt.Sprintf(" sslmode='%s'", escape(tlsSettings.Mode))
|
|
||||||
|
|
||||||
// there is an issue with the lib/pq module, the `verify-ca` tls mode
|
|
||||||
// does not work correctly. ( see https://github.com/lib/pq/issues/1106 )
|
|
||||||
// to workaround the problem, if the `verify-ca` mode is chosen,
|
|
||||||
// we disable sslsni.
|
|
||||||
if tlsSettings.Mode == "verify-ca" {
|
|
||||||
connStr += " sslsni=0"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach root certificate if provided
|
|
||||||
if tlsSettings.RootCertFile != "" {
|
|
||||||
logger.Debug("Setting server root certificate", "tlsRootCert", tlsSettings.RootCertFile)
|
|
||||||
connStr += fmt.Sprintf(" sslrootcert='%s'", escape(tlsSettings.RootCertFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach client certificate and key if both are provided
|
|
||||||
if tlsSettings.CertFile != "" && tlsSettings.CertKeyFile != "" {
|
|
||||||
logger.Debug("Setting TLS/SSL client auth", "tlsCert", tlsSettings.CertFile, "tlsKey", tlsSettings.CertKeyFile)
|
|
||||||
connStr += fmt.Sprintf(" sslcert='%s' sslkey='%s'", escape(tlsSettings.CertFile), escape(tlsSettings.CertKeyFile))
|
|
||||||
} else if tlsSettings.CertFile != "" || tlsSettings.CertKeyFile != "" {
|
|
||||||
return "", fmt.Errorf("TLS/SSL client certificate and key must both be specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug("Generated Postgres connection string successfully")
|
|
||||||
return connStr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type postgresQueryResultTransformer struct{}
|
type postgresQueryResultTransformer struct{}
|
||||||
|
|
||||||
func (t *postgresQueryResultTransformer) TransformQueryError(_ log.Logger, err error) error {
|
func (t *postgresQueryResultTransformer) TransformQueryError(_ log.Logger, err error) error {
|
||||||
|
|||||||
@@ -146,8 +146,9 @@ func TestIntegrationGenerateConnectionString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range testCases {
|
for _, tt := range testCases {
|
||||||
t.Run(tt.desc, func(t *testing.T) {
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
|
tlsManager := &tlsTestManager{settings: tt.tlsSettings}
|
||||||
svc := Service{
|
svc := Service{
|
||||||
tlsManager: &tlsTestManager{settings: tt.tlsSettings},
|
tlsManager: tlsManager,
|
||||||
logger: backend.NewLoggerWith("logger", "tsdb.postgres"),
|
logger: backend.NewLoggerWith("logger", "tsdb.postgres"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +160,7 @@ func TestIntegrationGenerateConnectionString(t *testing.T) {
|
|||||||
UID: tt.uid,
|
UID: tt.uid,
|
||||||
}
|
}
|
||||||
|
|
||||||
connStr, err := svc.generateConnectionString(ds)
|
connStr, err := GenerateConnectionString(ds, tlsManager, svc.logger)
|
||||||
|
|
||||||
if tt.expErr == "" {
|
if tt.expErr == "" {
|
||||||
require.NoError(t, err, tt.desc)
|
require.NoError(t, err, tt.desc)
|
||||||
|
|||||||
@@ -37,7 +37,16 @@ type SqlQueryResultTransformer interface {
|
|||||||
GetConverterList() []sqlutil.StringConverter
|
GetConverterList() []sqlutil.StringConverter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConnectionType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConnectionTypeDefault ConnectionType = "default"
|
||||||
|
ConnectionTypeConnectionString ConnectionType = "connectionString"
|
||||||
|
)
|
||||||
|
|
||||||
type JsonData struct {
|
type JsonData struct {
|
||||||
|
ConnectionType ConnectionType `json:"connectionType"`
|
||||||
|
|
||||||
MaxOpenConns int `json:"maxOpenConns"`
|
MaxOpenConns int `json:"maxOpenConns"`
|
||||||
MaxIdleConns int `json:"maxIdleConns"`
|
MaxIdleConns int `json:"maxIdleConns"`
|
||||||
ConnMaxLifetime int `json:"connMaxLifetime"`
|
ConnMaxLifetime int `json:"connMaxLifetime"`
|
||||||
|
|||||||
@@ -47,9 +47,25 @@ func newTLSManager(logger log.Logger, dataPath string) tlsSettingsProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TLSMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TLSModeDisable TLSMode = "disable"
|
||||||
|
TLSModeRequire TLSMode = "require"
|
||||||
|
TLSModeVerifyCA TLSMode = "verify-ca"
|
||||||
|
TLSModeVerifyFull TLSMode = "verify-full"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TLSConfigurationMethod string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TLSConfigurationMethodFilePath TLSConfigurationMethod = "file-path"
|
||||||
|
TLSConfigurationMethodFileContent TLSConfigurationMethod = "file-content"
|
||||||
|
)
|
||||||
|
|
||||||
type tlsSettings struct {
|
type tlsSettings struct {
|
||||||
Mode string
|
Mode TLSMode
|
||||||
ConfigurationMethod string
|
ConfigurationMethod TLSConfigurationMethod
|
||||||
RootCertFile string
|
RootCertFile string
|
||||||
CertFile string
|
CertFile string
|
||||||
CertKeyFile string
|
CertKeyFile string
|
||||||
@@ -57,10 +73,10 @@ type tlsSettings struct {
|
|||||||
|
|
||||||
func (m *tlsManager) getTLSSettings(dsInfo sqleng.DataSourceInfo) (tlsSettings, error) {
|
func (m *tlsManager) getTLSSettings(dsInfo sqleng.DataSourceInfo) (tlsSettings, error) {
|
||||||
tlsconfig := tlsSettings{
|
tlsconfig := tlsSettings{
|
||||||
Mode: dsInfo.JsonData.Mode,
|
Mode: TLSMode(dsInfo.JsonData.Mode),
|
||||||
}
|
}
|
||||||
|
|
||||||
isTLSDisabled := (tlsconfig.Mode == "disable")
|
isTLSDisabled := (tlsconfig.Mode == TLSModeDisable)
|
||||||
|
|
||||||
if isTLSDisabled {
|
if isTLSDisabled {
|
||||||
m.logger.Debug("Postgres TLS/SSL is disabled")
|
m.logger.Debug("Postgres TLS/SSL is disabled")
|
||||||
@@ -69,12 +85,12 @@ func (m *tlsManager) getTLSSettings(dsInfo sqleng.DataSourceInfo) (tlsSettings,
|
|||||||
|
|
||||||
m.logger.Debug("Postgres TLS/SSL is enabled", "tlsMode", tlsconfig.Mode)
|
m.logger.Debug("Postgres TLS/SSL is enabled", "tlsMode", tlsconfig.Mode)
|
||||||
|
|
||||||
tlsconfig.ConfigurationMethod = dsInfo.JsonData.ConfigurationMethod
|
tlsconfig.ConfigurationMethod = TLSConfigurationMethod(dsInfo.JsonData.ConfigurationMethod)
|
||||||
tlsconfig.RootCertFile = dsInfo.JsonData.RootCertFile
|
tlsconfig.RootCertFile = dsInfo.JsonData.RootCertFile
|
||||||
tlsconfig.CertFile = dsInfo.JsonData.CertFile
|
tlsconfig.CertFile = dsInfo.JsonData.CertFile
|
||||||
tlsconfig.CertKeyFile = dsInfo.JsonData.CertKeyFile
|
tlsconfig.CertKeyFile = dsInfo.JsonData.CertKeyFile
|
||||||
|
|
||||||
if tlsconfig.ConfigurationMethod == "file-content" {
|
if tlsconfig.ConfigurationMethod == TLSConfigurationMethodFileContent {
|
||||||
if err := m.writeCertFiles(dsInfo, &tlsconfig); err != nil {
|
if err := m.writeCertFiles(dsInfo, &tlsconfig); err != nil {
|
||||||
return tlsconfig, err
|
return tlsconfig, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
SecretInput,
|
SecretInput,
|
||||||
Field,
|
Field,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
|
RadioButtonGroup,
|
||||||
Label,
|
Label,
|
||||||
Icon,
|
Icon,
|
||||||
Switch,
|
Switch,
|
||||||
@@ -24,7 +25,13 @@ import {
|
|||||||
Collapse,
|
Collapse,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
|
|
||||||
import { PostgresOptions, PostgresTLSMethods, PostgresTLSModes, SecureJsonData } from '../types';
|
import {
|
||||||
|
PostgresConnectionType,
|
||||||
|
PostgresOptions,
|
||||||
|
PostgresTLSMethods,
|
||||||
|
PostgresTLSModes,
|
||||||
|
SecureJsonData,
|
||||||
|
} from '../types';
|
||||||
|
|
||||||
import { useAutoDetectFeatures } from './useAutoDetectFeatures';
|
import { useAutoDetectFeatures } from './useAutoDetectFeatures';
|
||||||
|
|
||||||
@@ -58,6 +65,10 @@ export const PostgresConfigEditor = (props: DataSourcePluginOptionsEditorProps<P
|
|||||||
updateDatasourcePluginResetOption(props, 'password');
|
updateDatasourcePluginResetOption(props, 'password');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onResetConnectionString = () => {
|
||||||
|
updateDatasourcePluginResetOption(props, 'connectionString');
|
||||||
|
};
|
||||||
|
|
||||||
const tlsModes: Array<SelectableValue<PostgresTLSModes>> = [
|
const tlsModes: Array<SelectableValue<PostgresTLSModes>> = [
|
||||||
{ value: PostgresTLSModes.disable, label: 'disable' },
|
{ value: PostgresTLSModes.disable, label: 'disable' },
|
||||||
{ value: PostgresTLSModes.require, label: 'require' },
|
{ value: PostgresTLSModes.require, label: 'require' },
|
||||||
@@ -80,6 +91,13 @@ export const PostgresConfigEditor = (props: DataSourcePluginOptionsEditorProps<P
|
|||||||
updateDatasourcePluginJsonDataOption(props, 'timescaledb', event.currentTarget.checked);
|
updateDatasourcePluginJsonDataOption(props, 'timescaledb', event.currentTarget.checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onConnectionTypeChanged = (connectionType: PostgresConnectionType) => {
|
||||||
|
updateDatasourcePluginJsonDataOption(props, 'connectionType', connectionType);
|
||||||
|
if (connectionType === 'connectionString') {
|
||||||
|
onOptionsChange({ ...options, url: options.url || 'localhost:5432', jsonData: { ...jsonData, connectionType } });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onDSOptionChanged = (property: keyof PostgresOptions) => {
|
const onDSOptionChanged = (property: keyof PostgresOptions) => {
|
||||||
return (event: SyntheticEvent<HTMLInputElement>) => {
|
return (event: SyntheticEvent<HTMLInputElement>) => {
|
||||||
onOptionsChange({ ...options, ...{ [property]: event.currentTarget.value } });
|
onOptionsChange({ ...options, ...{ [property]: event.currentTarget.value } });
|
||||||
@@ -88,6 +106,8 @@ export const PostgresConfigEditor = (props: DataSourcePluginOptionsEditorProps<P
|
|||||||
|
|
||||||
const WIDTH_LONG = 40;
|
const WIDTH_LONG = 40;
|
||||||
|
|
||||||
|
const EXAMPLE_CONNECTION_STRING = 'postgresql://username:password@localhost:5432/database_name?connect_timeout=10';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DataSourceDescription
|
<DataSourceDescription
|
||||||
@@ -110,49 +130,91 @@ export const PostgresConfigEditor = (props: DataSourcePluginOptionsEditorProps<P
|
|||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<ConfigSection title="Connection">
|
<ConfigSection title="Connection">
|
||||||
<Field label="Host URL" required>
|
<Field label="Connection Type" noMargin={false}>
|
||||||
<Input
|
<RadioButtonGroup<PostgresConnectionType>
|
||||||
width={WIDTH_LONG}
|
value={jsonData.connectionType || 'default'}
|
||||||
name="host"
|
options={[
|
||||||
type="text"
|
{ value: 'default', label: 'Connection Parameters' },
|
||||||
value={options.url || ''}
|
{ value: 'connectionString', label: 'Connection String' },
|
||||||
placeholder="localhost:5432"
|
]}
|
||||||
onChange={onDSOptionChanged('url')}
|
onChange={onConnectionTypeChanged}
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Field label="Database name" required>
|
|
||||||
<Input
|
|
||||||
width={WIDTH_LONG}
|
|
||||||
name="database"
|
|
||||||
value={jsonData.database || ''}
|
|
||||||
placeholder="Database"
|
|
||||||
onChange={onUpdateDatasourceJsonDataOption(props, 'database')}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
</ConfigSection>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<ConfigSection title="Authentication">
|
|
||||||
<Field label="Username" required>
|
|
||||||
<Input
|
|
||||||
width={WIDTH_LONG}
|
|
||||||
value={options.user || ''}
|
|
||||||
placeholder="Username"
|
|
||||||
onChange={onDSOptionChanged('user')}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Field label="Password" required>
|
|
||||||
<SecretInput
|
|
||||||
width={WIDTH_LONG}
|
|
||||||
placeholder="Password"
|
|
||||||
isConfigured={options.secureJsonFields && options.secureJsonFields.password}
|
|
||||||
onReset={onResetPassword}
|
|
||||||
onBlur={onUpdateDatasourceSecureJsonDataOption(props, 'password')}
|
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
|
{jsonData.connectionType === 'connectionString' && (
|
||||||
|
<Field
|
||||||
|
noMargin={false}
|
||||||
|
label={
|
||||||
|
<Label>
|
||||||
|
<EditorStack gap={0.5}>
|
||||||
|
<span>Connection String</span>
|
||||||
|
<Tooltip
|
||||||
|
content={
|
||||||
|
<span>
|
||||||
|
Postgres connection string.
|
||||||
|
<br />
|
||||||
|
Example: "{EXAMPLE_CONNECTION_STRING}".
|
||||||
|
<br />
|
||||||
|
Note: Don't include the TLS/SSL related settings here. Use "TLS/SSL Auth Details" section
|
||||||
|
instead.
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Icon name="info-circle" size="sm" />
|
||||||
|
</Tooltip>
|
||||||
|
</EditorStack>
|
||||||
|
</Label>
|
||||||
|
}
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<SecretInput
|
||||||
|
width={WIDTH_LONG}
|
||||||
|
placeholder={EXAMPLE_CONNECTION_STRING}
|
||||||
|
isConfigured={options.secureJsonFields && options.secureJsonFields.connectionString}
|
||||||
|
onReset={onResetConnectionString}
|
||||||
|
onBlur={onUpdateDatasourceSecureJsonDataOption(props, 'connectionString')}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
{jsonData.connectionType !== 'connectionString' && (
|
||||||
|
<>
|
||||||
|
<Field label="Host URL" required>
|
||||||
|
<Input
|
||||||
|
width={WIDTH_LONG}
|
||||||
|
name="host"
|
||||||
|
type="text"
|
||||||
|
value={options.url || ''}
|
||||||
|
placeholder="localhost:5432"
|
||||||
|
onChange={onDSOptionChanged('url')}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="Database name" required>
|
||||||
|
<Input
|
||||||
|
width={WIDTH_LONG}
|
||||||
|
name="database"
|
||||||
|
value={jsonData.database || ''}
|
||||||
|
placeholder="Database"
|
||||||
|
onChange={onUpdateDatasourceJsonDataOption(props, 'database')}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="Username" required>
|
||||||
|
<Input
|
||||||
|
width={WIDTH_LONG}
|
||||||
|
value={options.user || ''}
|
||||||
|
placeholder="Username"
|
||||||
|
onChange={onDSOptionChanged('user')}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field label="Password" required>
|
||||||
|
<SecretInput
|
||||||
|
width={WIDTH_LONG}
|
||||||
|
placeholder="Password"
|
||||||
|
isConfigured={options.secureJsonFields && options.secureJsonFields.password}
|
||||||
|
onReset={onResetPassword}
|
||||||
|
onBlur={onUpdateDatasourceSecureJsonDataOption(props, 'password')}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</ConfigSection>
|
</ConfigSection>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|||||||
@@ -11,7 +11,11 @@ export enum PostgresTLSMethods {
|
|||||||
filePath = 'file-path',
|
filePath = 'file-path',
|
||||||
fileContent = 'file-content',
|
fileContent = 'file-content',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PostgresConnectionType = 'default' | 'connectionString';
|
||||||
|
|
||||||
export interface PostgresOptions extends SQLOptions {
|
export interface PostgresOptions extends SQLOptions {
|
||||||
|
connectionType?: PostgresConnectionType;
|
||||||
tlsConfigurationMethod?: PostgresTLSMethods;
|
tlsConfigurationMethod?: PostgresTLSMethods;
|
||||||
sslmode?: PostgresTLSModes;
|
sslmode?: PostgresTLSModes;
|
||||||
sslRootCertFile?: string;
|
sslRootCertFile?: string;
|
||||||
@@ -24,4 +28,5 @@ export interface PostgresOptions extends SQLOptions {
|
|||||||
|
|
||||||
export interface SecureJsonData {
|
export interface SecureJsonData {
|
||||||
password?: string;
|
password?: string;
|
||||||
|
connectionString?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user