MSSQL: Configuration of certificate verification for TLS connection (#31865)

Fixes #24589

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
This commit is contained in:
Morten Nygaard Åsnes 2022-01-26 15:00:18 +01:00 committed by GitHub
parent 50fabe8a87
commit 9e4aafa719
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 175 additions and 17 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,2 @@
#start SQL Server and run setup script
/usr/setup/setup.sh & /opt/mssql/bin/sqlservr

View File

@ -0,0 +1,5 @@
[network]
tlscert = /var/opt/mssql/mssql.pem
tlskey = /var/opt/mssql/mssql.key
tlsprotocols = 1.2
forceencryption = 1

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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' |

View File

@ -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 dsInfo.JsonData.Encrypt == "" {
dsInfo.JsonData.Encrypt = "false"
if encrypt == "true" {
connStr += fmt.Sprintf("encrypt=%s;TrustServerCertificate=%t;", encrypt, tlsSkipVerify)
if hostNameInCertificate != "" {
connStr += fmt.Sprintf("hostNameInCertificate=%s;", hostNameInCertificate)
}
if dsInfo.JsonData.Encrypt != "false" {
connStr += fmt.Sprintf("encrypt=%s;", dsInfo.JsonData.Encrypt)
if certificate != "" {
connStr += fmt.Sprintf("certificate=%s;", certificate)
}
}
return connStr, nil
}

View File

@ -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"`
}

View File

@ -12,16 +12,22 @@ export class MssqlConfigCtrl {
onPasswordReset: ReturnType<typeof createResetHandler>;
onPasswordChange: ReturnType<typeof createChangeHandler>;
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;
}
}

View File

@ -42,10 +42,16 @@
</div>
</div>
</div>
<h3 class="page-heading">TLS/SSL Auth</h3>
<div class="gf-form-group">
<div class="gf-form">
<label class="gf-form-label width-7" for="encrypt-select">Encrypt</label>
<label class="gf-form-label width-15" for="encrypt-select">Encrypt</label>
<div class="gf-form-select-wrapper max-width-15 gf-form-select-wrapper--has-help-icon">
<select id="encrypt-select" class="gf-form-input" ng-model="ctrl.current.jsonData.encrypt" ng-options="mode for mode in ['disable', 'false', 'true']" ng-init="ctrl.current.jsonData.encrypt" aria-labelledby="encrypt-label"></select>
<select id="encrypt-select" class="gf-form-input" ng-model="ctrl.current.jsonData.encrypt" ng-options="mode for mode in ['disable', 'false', 'true']" ng-init="ctrl.current.jsonData.encrypt" ng-change="ctrl.onEncryptChange()" aria-labelledby="encrypt-label"></select>
<info-popover mode="right-absolute">
Determines whether or to which extent a secure SSL TCP/IP connection will be negotiated with the server.
<ul>
@ -57,6 +63,33 @@
</info-popover>
</div>
</div>
<div class="gf-form" ng-show="ctrl.showTlsConfig">
<gf-form-switch class="gf-form" label="Skip TLS/SSL Verify" label-class="width-15"
tooltip="Skip verifying Server Certificate for TLS/SSL. If this is enabled, any certificate presented by the server and any host name in that certificate will be accepted. In this mode, TLS is susceptible to man-in-the-middle attacks. This should be used only for testing."
checked="ctrl.current.jsonData.tlsSkipVerify" switch-class="max-width-8"
on-change="ctrl.onEncryptChange()"></gf-form-switch>
</div>
<div class="gf-form max-width-30" ng-show="ctrl.showCertificateConfig">
<span class="gf-form-label width-15">TLS/SSL Root Certificate</span>
<input type="text" class="gf-form-input" style="width: 352px" ng-model='ctrl.current.jsonData.sslRootCertFile'
placeholder="TLS/SSL root certificate file"></input>
<info-popover mode="right-absolute">
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.
</info-popover>
</div>
<div class="gf-form max-width-30" ng-show="ctrl.showCertificateConfig">
<span class="gf-form-label width-15">Hostname in server certificate</span>
<input type="text" class="gf-form-input" style="width: 352px" ng-model='ctrl.current.jsonData.serverName'
placeholder="Common Name (CN) in server certificate"></input>
<info-popover mode="right-absolute">
Specifies the Common Name (CN) in the server certificate. Default is the server host.
</info-popover>
</div>
</div>
<h3 class="page-heading">Connection limits</h3>