diff --git a/docs/sources/administration/provisioning.md b/docs/sources/administration/provisioning.md index 381247b3db6..0025afe9d70 100755 --- a/docs/sources/administration/provisioning.md +++ b/docs/sources/administration/provisioning.md @@ -160,6 +160,9 @@ Since not all datasources have the same configuration settings we only have the | tsdbVersion | string | OpenTSDB | Version | | tsdbResolution | string | OpenTSDB | Resolution | | sslmode | string | PostgreSQL | SSLmode. 'disable', 'require', 'verify-ca' or 'verify-full' | +| sslRootCertFile | string | PostgreSQL | SSL server root certificate file, must be readable by the Grafana user | +| sslCertFile | string | PostgreSQL | SSL client certificate file, must be readable by the Grafana user | +| sslKeyFile | string | PostgreSQL | SSL client key file, must be readable by *only* the Grafana user | | encrypt | string | MSSQL | Connection SSL encryption handling. 'disable', 'false' or 'true' | | postgresVersion | number | PostgreSQL | Postgres version as a number (903/904/905/906/1000) meaning v9.3, v9.4, ..., v10 | | timescaledb | boolean | PostgreSQL | Enable usage of TimescaleDB extension | diff --git a/pkg/tsdb/postgres/postgres.go b/pkg/tsdb/postgres/postgres.go index f06a8eac577..36c53150dfe 100644 --- a/pkg/tsdb/postgres/postgres.go +++ b/pkg/tsdb/postgres/postgres.go @@ -2,8 +2,10 @@ package postgres import ( "database/sql" + "fmt" "net/url" "strconv" + "strings" "github.com/grafana/grafana/pkg/setting" @@ -20,8 +22,13 @@ func init() { func newPostgresQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndpoint, error) { logger := log.New("tsdb.postgres") + logger.Debug("Creating Postgres query endpoint") + + cnnstr, err := generateConnectionString(datasource, logger) + if err != nil { + return nil, err + } - cnnstr := generateConnectionString(datasource) if setting.Env == setting.DEV { logger.Debug("getEngine", "connection", cnnstr) } @@ -39,19 +46,51 @@ func newPostgresQueryEndpoint(datasource *models.DataSource) (tsdb.TsdbQueryEndp timescaledb := datasource.JsonData.Get("timescaledb").MustBool(false) - return sqleng.NewSqlQueryEndpoint(&config, &queryResultTransformer, newPostgresMacroEngine(timescaledb), logger) + endpoint, err := sqleng.NewSqlQueryEndpoint(&config, &queryResultTransformer, newPostgresMacroEngine(timescaledb), logger) + if err == nil { + logger.Debug("Successfully connected to Postgres") + } else { + logger.Debug("Failed connecting to Postgres", "err", err) + } + return endpoint, err } -func generateConnectionString(datasource *models.DataSource) string { - sslmode := datasource.JsonData.Get("sslmode").MustString("verify-full") +func generateConnectionString(datasource *models.DataSource, logger log.Logger) (string, error) { + sslMode := strings.TrimSpace(strings.ToLower(datasource.JsonData.Get("sslmode").MustString("verify-full"))) + isSSLDisabled := sslMode == "disable" + + // Always pass SSL mode + sslOpts := fmt.Sprintf("sslmode=%s", url.QueryEscape(sslMode)) + if isSSLDisabled { + logger.Debug("Postgres SSL is disabled") + } else { + logger.Debug("Postgres SSL is enabled", "sslMode", sslMode) + + // Attach root certificate if provided + if sslRootCert := datasource.JsonData.Get("sslRootCertFile").MustString(""); sslRootCert != "" { + logger.Debug("Setting server root certificate", "sslRootCert", sslRootCert) + sslOpts = fmt.Sprintf("%s&sslrootcert=%s", sslOpts, url.QueryEscape(sslRootCert)) + } + + // Attach client certificate and key if both are provided + sslCert := datasource.JsonData.Get("sslCertFile").MustString("") + sslKey := datasource.JsonData.Get("sslKeyFile").MustString("") + if sslCert != "" && sslKey != "" { + logger.Debug("Setting SSL client auth", "sslCert", sslCert, "sslKey", sslKey) + sslOpts = fmt.Sprintf("%s&sslcert=%s&sslkey=%s", sslOpts, url.QueryEscape(sslCert), url.QueryEscape(sslKey)) + } else if sslCert != "" || sslKey != "" { + return "", fmt.Errorf("SSL client certificate and key must both be specified") + } + } + u := &url.URL{ Scheme: "postgres", User: url.UserPassword(datasource.User, datasource.DecryptedPassword()), Host: datasource.Url, Path: datasource.Database, - RawQuery: "sslmode=" + url.QueryEscape(sslmode), + RawQuery: sslOpts, } - return u.String() + return u.String(), nil } type postgresQueryResultTransformer struct { diff --git a/public/app/plugins/datasource/postgres/partials/config.html b/public/app/plugins/datasource/postgres/partials/config.html index c26d234720d..c10ccc842e3 100644 --- a/public/app/plugins/datasource/postgres/partials/config.html +++ b/public/app/plugins/datasource/postgres/partials/config.html @@ -3,18 +3,18 @@
- Host + Host
- Database + Database
- User + User
@@ -26,17 +26,40 @@ inputWidth="9" />
-
- -
- -
- - - This option determines whether or with what priority a secure SSL TCP/IP connection will be negotiated with the server. - -
-
+
+
+ +
+ + + This option determines whether or with what priority a secure SSL (TLS) TCP/IP connection will be negotiated with the server. + +
+
+
+ SSL Root Certificate + + + If the selected SSL mode requires a server root certificate, provide the path to the file here. + Be sure that the file is readable by the user executing the grafana process. + +
+
+ SSL Client Certificate + + + To authenticate with an SSL/TLS client certificate, provide the path to the file here. + Be sure that the file is readable by the user executing the grafana process. + +
+
+ SSL Client Key + + + To authenticate with a client SSL/TLS certificate, provide the path to the corresponding key file here. + Be sure that the file is only readable by the user executing the grafana process. + +
Connection limits @@ -132,4 +155,3 @@

-