From 9e4aafa719b3f601469fc3e366eb576148a745ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Nygaard=20=C3=85snes?= Date: Wed, 26 Jan 2022 15:00:18 +0100 Subject: [PATCH] MSSQL: Configuration of certificate verification for TLS connection (#31865) Fixes #24589 Co-authored-by: Marcus Efraimsson --- devenv/datasources.yaml | 11 ++++++ devenv/datasources_docker.yaml | 15 +++++++- .../docker/blocks/mssql_tls/build/Dockerfile | 21 +++++++++++ .../blocks/mssql_tls/build/entrypoint.sh | 2 + .../docker/blocks/mssql_tls/build/mssql.conf | 5 +++ devenv/docker/blocks/mssql_tls/build/setup.sh | 13 +++++++ .../blocks/mssql_tls/build/setup.sql.template | 26 +++++++++++++ .../blocks/mssql_tls/docker-compose.yaml | 18 +++++++++ docs/sources/administration/provisioning.md | 6 +-- pkg/tsdb/mssql/mssql.go | 21 +++++++---- pkg/tsdb/sqleng/sql_engine.go | 2 + .../plugins/datasource/mssql/config_ctrl.ts | 15 +++++++- .../datasource/mssql/partials/config.html | 37 ++++++++++++++++++- 13 files changed, 175 insertions(+), 17 deletions(-) create mode 100644 devenv/docker/blocks/mssql_tls/build/Dockerfile create mode 100644 devenv/docker/blocks/mssql_tls/build/entrypoint.sh create mode 100644 devenv/docker/blocks/mssql_tls/build/mssql.conf create mode 100644 devenv/docker/blocks/mssql_tls/build/setup.sh create mode 100644 devenv/docker/blocks/mssql_tls/build/setup.sql.template create mode 100644 devenv/docker/blocks/mssql_tls/docker-compose.yaml diff --git a/devenv/datasources.yaml b/devenv/datasources.yaml index 883c2ef2c3b..94b419e3cca 100644 --- a/devenv/datasources.yaml +++ b/devenv/datasources.yaml @@ -255,6 +255,17 @@ datasources: secureJsonData: password: Password! + - name: gdev-mssql-tls + type: mssql + url: localhost:1434 + database: grafana + user: grafana + jsonData: + encrypt: "true" + tlsSkipVerify: true + secureJsonData: + password: Password! + - name: gdev-mssql-ds-tests type: mssql url: localhost:1433 diff --git a/devenv/datasources_docker.yaml b/devenv/datasources_docker.yaml index 922a1ccc1c5..833960cc4ae 100644 --- a/devenv/datasources_docker.yaml +++ b/devenv/datasources_docker.yaml @@ -42,7 +42,7 @@ datasources: version: Flux organization: myorg defaultBucket: mybucket - + - name: gdev-influxdb-influxql type: influxdb access: proxy @@ -220,6 +220,17 @@ datasources: secureJsonData: password: Password! + - name: gdev-mssql-tls + type: mssql + url: localhost:1434 + database: grafana + user: grafana + jsonData: + encrypt: "true" + tlsSkipVerify: true + secureJsonData: + password: Password! + - name: gdev-mssql-ds-tests type: mssql url: mssqltests:1433 @@ -259,4 +270,4 @@ datasources: type: loki access: proxy url: http://loki:3100 - editable: false \ No newline at end of file + editable: false diff --git a/devenv/docker/blocks/mssql_tls/build/Dockerfile b/devenv/docker/blocks/mssql_tls/build/Dockerfile new file mode 100644 index 00000000000..1c6399f14d7 --- /dev/null +++ b/devenv/docker/blocks/mssql_tls/build/Dockerfile @@ -0,0 +1,21 @@ +FROM mcr.microsoft.com/mssql/server:2019-CU8-ubuntu-18.04 + +WORKDIR /usr/setup +COPY setup.sh setup.sql.template entrypoint.sh ./ +COPY mssql.conf /var/opt/mssql/mssql.conf + +USER root + +RUN chmod +x setup.sh +RUN chown -R mssql ./ +RUN mkdir -p /home/mssql +RUN chown -R mssql /home/mssql + +USER mssql + +RUN touch ~/.rnd +RUN openssl req -x509 -nodes -newkey rsa:2048 -subj '/CN=mssql_tls' -keyout /var/opt/mssql/mssql.key -out /var/opt/mssql/mssql.pem -days 365 +RUN chmod 440 /var/opt/mssql/mssql.key +RUN chmod 440 /var/opt/mssql/mssql.pem + +CMD /bin/bash ./entrypoint.sh diff --git a/devenv/docker/blocks/mssql_tls/build/entrypoint.sh b/devenv/docker/blocks/mssql_tls/build/entrypoint.sh new file mode 100644 index 00000000000..de5aef5124a --- /dev/null +++ b/devenv/docker/blocks/mssql_tls/build/entrypoint.sh @@ -0,0 +1,2 @@ +#start SQL Server and run setup script +/usr/setup/setup.sh & /opt/mssql/bin/sqlservr \ No newline at end of file diff --git a/devenv/docker/blocks/mssql_tls/build/mssql.conf b/devenv/docker/blocks/mssql_tls/build/mssql.conf new file mode 100644 index 00000000000..830ea13f9ff --- /dev/null +++ b/devenv/docker/blocks/mssql_tls/build/mssql.conf @@ -0,0 +1,5 @@ +[network] +tlscert = /var/opt/mssql/mssql.pem +tlskey = /var/opt/mssql/mssql.key +tlsprotocols = 1.2 +forceencryption = 1 diff --git a/devenv/docker/blocks/mssql_tls/build/setup.sh b/devenv/docker/blocks/mssql_tls/build/setup.sh new file mode 100644 index 00000000000..5d22062bd2f --- /dev/null +++ b/devenv/docker/blocks/mssql_tls/build/setup.sh @@ -0,0 +1,13 @@ +#/bin/bash +set -eo pipefail + +#wait for the SQL Server to come up +sleep 15s + +cat /usr/setup/setup.sql.template | awk '{ + gsub(/%%DB%%/,"'$MSSQL_DATABASE'"); + gsub(/%%USER%%/,"'$MSSQL_USER'"); + gsub(/%%PWD%%/,"'$MSSQL_PASSWORD'") +}1' > /usr/setup/setup.sql + +/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P $MSSQL_SA_PASSWORD -d master -i /usr/setup/setup.sql diff --git a/devenv/docker/blocks/mssql_tls/build/setup.sql.template b/devenv/docker/blocks/mssql_tls/build/setup.sql.template new file mode 100644 index 00000000000..5ff4e194df3 --- /dev/null +++ b/devenv/docker/blocks/mssql_tls/build/setup.sql.template @@ -0,0 +1,26 @@ +CREATE LOGIN %%USER%% WITH PASSWORD = '%%PWD%%' +GO + +CREATE DATABASE %%DB%% +ON +( NAME = %%DB%%, + FILENAME = '/var/opt/mssql/data/%%DB%%.mdf', + SIZE = 500MB, + MAXSIZE = 1000MB, + FILEGROWTH = 100MB ) +LOG ON +( NAME = %%DB%%_log, + FILENAME = '/var/opt/mssql/data/%%DB%%_log.ldf', + SIZE = 500MB, + MAXSIZE = 1000MB, + FILEGROWTH = 100MB ); +GO + +USE %%DB%%; +GO + +CREATE USER %%USER%% FOR LOGIN %%USER%%; +GO + +EXEC sp_addrolemember 'db_owner', '%%USER%%'; +GO diff --git a/devenv/docker/blocks/mssql_tls/docker-compose.yaml b/devenv/docker/blocks/mssql_tls/docker-compose.yaml new file mode 100644 index 00000000000..ab8599666c5 --- /dev/null +++ b/devenv/docker/blocks/mssql_tls/docker-compose.yaml @@ -0,0 +1,18 @@ + mssql_tls: + build: + context: docker/blocks/mssql_tls/build + environment: + ACCEPT_EULA: Y + MSSQL_SA_PASSWORD: Password! + MSSQL_PID: Developer + MSSQL_DATABASE: grafana + MSSQL_USER: grafana + MSSQL_PASSWORD: Password! + ports: + - "1434:1433" + + fake-mssql-tls-data: + image: grafana/fake-data-gen + environment: + FD_DATASOURCE: mssql_tls + FD_PORT: 1434 diff --git a/docs/sources/administration/provisioning.md b/docs/sources/administration/provisioning.md index 92838238730..910e0512ef0 100644 --- a/docs/sources/administration/provisioning.md +++ b/docs/sources/administration/provisioning.md @@ -147,8 +147,8 @@ Since not all datasources have the same configuration settings we only have the | -------------------------- | ------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | tlsAuth | boolean | _HTTP\*_, MySQL | Enable TLS authentication using client cert configured in secure json data | | tlsAuthWithCACert | boolean | _HTTP\*_, MySQL, PostgreSQL | Enable TLS authentication using CA cert | -| tlsSkipVerify | boolean | _HTTP\*_, MySQL, PostgreSQL | Controls whether a client verifies the server's certificate chain and host name. | -| serverName | string | _HTTP\*_ | Optional. Controls the server name used for certificate common name/subject alternative name verification. Defaults to using the data source URL. | +| tlsSkipVerify | boolean | _HTTP\*_, MySQL, PostgreSQL, MSSQL | Controls whether a client verifies the server's certificate chain and host name. | +| serverName | string | _HTTP\*_, MSSQL | Optional. Controls the server name used for certificate common name/subject alternative name verification. Defaults to using the data source URL. | | timeout | string | _HTTP\*_ | Request timeout in seconds. Overrides dataproxy.timeout option | | graphiteVersion | string | Graphite | Graphite version | | timeInterval | string | Prometheus, Elasticsearch, InfluxDB, MySQL, PostgreSQL and MSSQL | Lowest interval/step value that should be used for this data source. | @@ -179,7 +179,7 @@ Since not all datasources have the same configuration settings we only have the | tsdbResolution | string | OpenTSDB | Resolution | | sslmode | string | PostgreSQL | SSLmode. 'disable', 'require', 'verify-ca' or 'verify-full' | | tlsConfigurationMethod | string | PostgreSQL | SSL Certificate configuration, either by 'file-path' or 'file-content' | -| sslRootCertFile | string | PostgreSQL | SSL server root certificate file, must be readable by the Grafana user | +| sslRootCertFile | string | PostgreSQL, MSSQL | 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' | diff --git a/pkg/tsdb/mssql/mssql.go b/pkg/tsdb/mssql/mssql.go index 5145a692da0..c130dc2411e 100644 --- a/pkg/tsdb/mssql/mssql.go +++ b/pkg/tsdb/mssql/mssql.go @@ -82,7 +82,6 @@ func newInstanceSettings(cfg *setting.Cfg) datasource.InstanceFactoryFunc { if cfg.Env == setting.Dev { logger.Debug("getEngine", "connection", cnnstr) } - config := sqleng.DataPluginConfiguration{ DriverName: "mssql", ConnectionString: cnnstr, @@ -145,8 +144,12 @@ func generateConnectionString(dsInfo sqleng.DataSourceInfo) (string, error) { if addr.Port != "0" { args = append(args, "port", addr.Port) } - logger.Debug("Generating connection string", args...) + + encrypt := dsInfo.JsonData.Encrypt + tlsSkipVerify := dsInfo.JsonData.TlsSkipVerify + hostNameInCertificate := dsInfo.JsonData.Servername + certificate := dsInfo.JsonData.RootCertFile connStr := fmt.Sprintf("server=%s;database=%s;user id=%s;password=%s;", addr.Host, dsInfo.Database, @@ -157,13 +160,15 @@ func generateConnectionString(dsInfo sqleng.DataSourceInfo) (string, error) { if addr.Port != "0" { connStr += fmt.Sprintf("port=%s;", addr.Port) } + if encrypt == "true" { + connStr += fmt.Sprintf("encrypt=%s;TrustServerCertificate=%t;", encrypt, tlsSkipVerify) + if hostNameInCertificate != "" { + connStr += fmt.Sprintf("hostNameInCertificate=%s;", hostNameInCertificate) + } - if dsInfo.JsonData.Encrypt == "" { - dsInfo.JsonData.Encrypt = "false" - } - - if dsInfo.JsonData.Encrypt != "false" { - connStr += fmt.Sprintf("encrypt=%s;", dsInfo.JsonData.Encrypt) + if certificate != "" { + connStr += fmt.Sprintf("certificate=%s;", certificate) + } } return connStr, nil } diff --git a/pkg/tsdb/sqleng/sql_engine.go b/pkg/tsdb/sqleng/sql_engine.go index fbfb933c8e1..51c286201ad 100644 --- a/pkg/tsdb/sqleng/sql_engine.go +++ b/pkg/tsdb/sqleng/sql_engine.go @@ -56,11 +56,13 @@ type JsonData struct { Timescaledb bool `json:"timescaledb"` Mode string `json:"sslmode"` ConfigurationMethod string `json:"tlsConfigurationMethod"` + TlsSkipVerify bool `json:"tlsSkipVerify"` RootCertFile string `json:"sslRootCertFile"` CertFile string `json:"sslCertFile"` CertKeyFile string `json:"sslKeyFile"` Timezone string `json:"timezone"` Encrypt string `json:"encrypt"` + Servername string `json:"servername"` TimeInterval string `json:"timeInterval"` } diff --git a/public/app/plugins/datasource/mssql/config_ctrl.ts b/public/app/plugins/datasource/mssql/config_ctrl.ts index af84f70c9c6..dd61fa2d17a 100644 --- a/public/app/plugins/datasource/mssql/config_ctrl.ts +++ b/public/app/plugins/datasource/mssql/config_ctrl.ts @@ -12,16 +12,22 @@ export class MssqlConfigCtrl { onPasswordReset: ReturnType; onPasswordChange: ReturnType; - showUserCredentials: boolean; + showUserCredentials: boolean = false; + showTlsConfig: boolean = false; + showCertificateConfig: boolean = false; /** @ngInject */ constructor($scope: any) { this.current = $scope.ctrl.current; this.current.jsonData.encrypt = this.current.jsonData.encrypt || 'false'; + this.current.jsonData.sslRootCertFile = this.current.jsonData.sslRootCertFile || ''; + this.current.jsonData.tlsSkipVerify = this.current.jsonData.tlsSkipVerify || false; + this.current.jsonData.serverName = this.current.jsonData.serverName || ''; this.current.jsonData.authenticationType = this.current.jsonData.authenticationType || 'SQL Server Authentication'; this.onPasswordReset = createResetHandler(this, PasswordFieldEnum.Password); this.onPasswordChange = createChangeHandler(this, PasswordFieldEnum.Password); - this.showUserCredentials = this.current.jsonData.authenticationType !== 'Windows Authentication'; + this.onAuthenticationTypeChange(); + this.onEncryptChange(); } onAuthenticationTypeChange() { @@ -33,4 +39,9 @@ export class MssqlConfigCtrl { this.showUserCredentials = this.current.jsonData.authenticationType !== 'Windows Authentication'; } + + onEncryptChange() { + this.showTlsConfig = this.current.jsonData.encrypt === 'true'; + this.showCertificateConfig = this.showTlsConfig && this.current.jsonData.tlsSkipVerify === false; + } } diff --git a/public/app/plugins/datasource/mssql/partials/config.html b/public/app/plugins/datasource/mssql/partials/config.html index e0df505c4d9..6d3cb01e3d5 100644 --- a/public/app/plugins/datasource/mssql/partials/config.html +++ b/public/app/plugins/datasource/mssql/partials/config.html @@ -42,10 +42,16 @@ + + +

TLS/SSL Auth

+ +
+
- +
- + Determines whether or to which extent a secure SSL TCP/IP connection will be negotiated with the server.
    @@ -57,6 +63,33 @@
+ +
+ +
+ +
+ TLS/SSL Root Certificate + + + Path to file containing the public key certificate of the CA that signed the SQL Server certificate. Needed when + the server certificate is self signed. + +
+ +
+ Hostname in server certificate + + + Specifies the Common Name (CN) in the server certificate. Default is the server host. + +
+

Connection limits