MSSQL: Add support for MI authentication to MSSQL (#73597)

* Add support for MI authentication to MSSQL

This adds support for managed identity authentication for MSSQL managed
instances running in Azure.

Co-authored-by: baldm0mma <jev.forsberg@grafana.com>
This commit is contained in:
Oscar Kilhed
2023-09-06 18:27:19 +02:00
committed by GitHub
parent 3ccfa5620f
commit 579709c7a6
16 changed files with 853 additions and 72 deletions

18
go.mod
View File

@@ -132,7 +132,7 @@ require (
)
require (
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
@@ -167,7 +167,7 @@ require (
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-openapi/validate v0.22.1 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // @grafana/backend-platform
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang/glog v1.1.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // @grafana/backend-platform
@@ -228,8 +228,8 @@ require (
)
require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // @grafana/backend-platform
cloud.google.com/go/kms v1.15.0 // @grafana/backend-platform
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0 // @grafana/backend-platform
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0 // @grafana/backend-platform
github.com/Azure/azure-storage-blob-go v0.15.0 // @grafana/backend-platform
github.com/Azure/go-autorest/autorest/adal v0.9.22 // @grafana/backend-platform
@@ -265,7 +265,7 @@ require (
github.com/go-jose/go-jose/v3 v3.0.0 // @grafana/backend-platform
github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics
github.com/grafana/dataplane/sdata v0.0.6 // @grafana/observability-metrics
github.com/grafana/go-mssqldb v0.9.1 // @grafana/grafana-bi-squad
github.com/microsoft/go-mssqldb v1.5.0 // @grafana/grafana-bi-squad
github.com/grafana/kindsys v0.0.0-20230508162304-452481b63482 // @grafana/grafana-as-code
github.com/grafana/tempo v1.5.1-0.20230524121406-1dc1bfe7085b // @grafana/observability-traces-and-profiling
github.com/grafana/thema v0.0.0-20230712153715-375c1b45f3ed // @grafana/grafana-as-code
@@ -329,6 +329,7 @@ require (
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/getsentry/sentry-go v0.12.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/goccy/go-json v0.9.11 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/status v1.1.1 // indirect
@@ -435,9 +436,9 @@ require (
cloud.google.com/go/compute v1.23.0 // indirect
cloud.google.com/go/iam v1.1.1 // indirect
filippo.io/age v1.1.1 // @grafana/grafana-authnz-team
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.2 // @grafana/backend-platform
github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230426101702-58e86b294756 // @grafana/plugins-platform-backend
@@ -497,9 +498,8 @@ replace github.com/hashicorp/go-hclog => github.com/hashicorp/go-hclog v0.16.1
// This is a patched v0.8.2 intended to fix session.Find (and others) silently ignoring SQLITE_BUSY errors. This could
// happen, for example, during a read when the sqlite db is under heavy write load.
// This patch cherry picks compatible fixes from upstream xorm PR#1998 and can be reverted on upgrade to xorm v1.2.0+.
replace xorm.io/xorm => github.com/grafana/xorm v0.8.3-0.20220614223926-2fcda7565af6
// replace xorm.io/xorm => ./pkg/util/xorm
// This has also been patched to support the azuresql driver that is a thin wrapper for the mssql driver with azure authentication support.
replace xorm.io/xorm => github.com/grafana/xorm v0.8.3-0.20230627081928-d04aa38aa209
// Use our fork of the upstream alertmanagers.
// This is required in order to get notification delivery errors from the receivers API.

51
go.sum
View File

@@ -556,16 +556,33 @@ github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9mo
github.com/Azure/azure-sdk-for-go v59.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v65.0.0+incompatible h1:HzKLt3kIwMm4KeJYTdx9EbjRYTySD/t8i1Ee/W5EGXw=
github.com/Azure/azure-sdk-for-go v65.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.2.0 h1:sVW/AFBTGyJxDaMYlq0ct3jUXTtj12tQ6zE2GZUgVQw=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.2.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSajX3Oz4G5Gm7P+mbqE9FVaXXFYTkCM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0 h1:t/W5MYAuQy81cvM8VUNfRLzhtKpXhVUAN7Cd7KVbTyc=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0/go.mod h1:NBanQUfSWiWn3QEpWDTCU0IjBECKOYvl2R8xdRtMtiM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1 h1:T8quHYlUGyb/oqtSTwqlCr1ilJHrDv+ZtpSfo+hm1BU=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.1/go.mod h1:gLa1CL2RNE4s7M3yopJ/p0iq5DdY6Yv5ZUt9MTRZOQM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 h1:jp0dGvZ7ZK0mgqnTSClMxa5xuRL7NZgHameVYF6BurY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0 h1:TOFrNxfjslms5nLLIMjW7N0+zSALX4KiGsptmpb16AA=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0/go.mod h1:EAyXOW1F6BTJPiK2pDvmnvxOHPxoTYWoqBeIlql+QhI=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 h1:m/sWOGCREuSBqg2htVQTBY8nOZpyajYztF0vUvSZTuM=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0/go.mod h1:Pu5Zksi2KrU7LPbZbNINx6fuVrUp/ffvpxdDj+i8LeE=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 h1:Lg6BW0VPmCwcMlvOviL3ruHFO+H9tZNqscK0AeuFjGM=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw=
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA=
github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU=
github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck=
github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk=
@@ -606,8 +623,13 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU=
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=
github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0 h1:VgSJlZH5u0k2qxSpqyghcFQKmvYckj46uymKK5XzkBM=
github.com/AzureAD/microsoft-authentication-library-for-go v0.7.0/go.mod h1:BDJ5qMFKx9DugEg3+uQSDCdbYPr5s9vBTrL9P8TpqOU=
github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1 h1:oPdPEZFSbl7oSPEAIPMPBMUmiL+mqgzBJwM/9qYcwNg=
github.com/AzureAD/microsoft-authentication-library-for-go v0.8.1/go.mod h1:4qFor3D/HDsvBME35Xy9rwW9DecL+M2sNw1ybjPtwA0=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
@@ -994,6 +1016,7 @@ github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQ
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/dlmiddlecote/sqlstats v1.0.2 h1:gSU11YN23D/iY50A2zVYwgXgy072khatTsIW6UPjUtI=
github.com/dlmiddlecote/sqlstats v1.0.2/go.mod h1:0CWaIh/Th+z2aI6Q9Jpfg/o21zmGxWhbByHgQSCUQvY=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
@@ -1570,6 +1593,7 @@ github.com/gogo/status v1.0.3/go.mod h1:SavQ51ycCLnc7dGyJxp8YAmudx8xqiVrRf+6IXRs
github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM=
github.com/gogo/status v1.1.1 h1:DuHXlSFHNKqTQ+/ACf5Vs6r4X/dH2EgIzR9Vr+H65kg=
github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci7oU=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
@@ -1581,6 +1605,11 @@ github.com/golang-migrate/migrate/v4 v4.7.0 h1:gONcHxHApDTKXDyLH/H97gEHmpu1zcnnb
github.com/golang-migrate/migrate/v4 v4.7.0/go.mod h1:Qvut3N4xKWjoH3sokBccML6WyHSnggXm/DvMMnTsQIc=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/gddo v0.0.0-20180828051604-96d2a289f41e/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4=
github.com/golang/gddo v0.0.0-20190904175337-72a348e765d2/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4=
@@ -1755,6 +1784,7 @@ github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.1.2/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@@ -1810,6 +1840,8 @@ github.com/grafana/thema v0.0.0-20230712153715-375c1b45f3ed h1:TMtHc+B0SSNw2in6R
github.com/grafana/thema v0.0.0-20230712153715-375c1b45f3ed/go.mod h1:3zLJnssFRPCnebCBRlq53t5LgYv9P1mbj0XMozZMTww=
github.com/grafana/xorm v0.8.3-0.20220614223926-2fcda7565af6 h1:I9dh1MXGX0wGyxdV/Sl7+ugnki4Dfsy8lv2s5Yf887o=
github.com/grafana/xorm v0.8.3-0.20220614223926-2fcda7565af6/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY=
github.com/grafana/xorm v0.8.3-0.20230627081928-d04aa38aa209 h1:o0QTKP6oKduDIW8yA0fBSv5SjLZR6lTB116eBIeomg0=
github.com/grafana/xorm v0.8.3-0.20230627081928-d04aa38aa209/go.mod h1:ZkJLEYLoVyg7amJK/5r779bHyzs2AU8f8VMiP6BM7uY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@@ -1879,6 +1911,7 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
@@ -2011,6 +2044,12 @@ github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv
github.com/jandelgado/gcov2lcov v1.0.4-0.20210120124023-b83752c6dc08/go.mod h1:NnSxK6TMlg1oGDBfGelGbjgorT5/L3cchlbtgFYZSss=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
@@ -2249,6 +2288,8 @@ github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/microsoft/go-mssqldb v1.5.0 h1:CgENxkwtOBNj3Jg6T1X209y2blCfTTcwuOlznd2k9fk=
github.com/microsoft/go-mssqldb v1.5.0/go.mod h1:lmWsjHD8XX/Txr0f8ZqgbEZSC+BZjmEQy/Ms+rLrvho=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
@@ -2311,6 +2352,8 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
@@ -2483,6 +2526,8 @@ github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
@@ -3083,13 +3128,16 @@ golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -3212,6 +3260,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
@@ -3458,6 +3507,7 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -3979,6 +4029,7 @@ gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKx
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=

View File

@@ -10,16 +10,19 @@ import (
"strconv"
"strings"
mssql "github.com/grafana/go-mssqldb"
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
"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"
mssql "github.com/microsoft/go-mssqldb"
_ "github.com/microsoft/go-mssqldb/azuread"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/mssql/utils"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"github.com/grafana/grafana/pkg/tsdb/sqleng/proxyutil"
"github.com/grafana/grafana/pkg/util"
@@ -31,6 +34,12 @@ type Service struct {
im instancemgmt.InstanceManager
}
const (
azureAuthentication = "Azure AD Authentication"
windowsAuthentication = "Windows Authentication"
sqlServerAuthentication = "SQL Server Authentication"
)
func ProvideService(cfg *setting.Cfg) *Service {
return &Service{
im: datasource.NewInstanceManager(newInstanceSettings(cfg)),
@@ -64,8 +73,11 @@ func newInstanceSettings(cfg *setting.Cfg) datasource.InstanceFactoryFunc {
ConnectionTimeout: 0,
SecureDSProxy: false,
}
err := json.Unmarshal(settings.JSONData, &jsonData)
azureCredentials, err := utils.GetAzureCredentials(settings)
if err != nil {
return nil, fmt.Errorf("error reading azure credentials")
}
err = json.Unmarshal(settings.JSONData, &jsonData)
if err != nil {
return nil, fmt.Errorf("error reading settings: %w", err)
}
@@ -85,7 +97,7 @@ func newInstanceSettings(cfg *setting.Cfg) datasource.InstanceFactoryFunc {
UID: settings.UID,
DecryptedSecureJSONData: settings.DecryptedSecureJSONData,
}
cnnstr, err := generateConnectionString(dsInfo)
cnnstr, err := generateConnectionString(dsInfo, cfg, azureCredentials)
if err != nil {
return nil, err
}
@@ -93,12 +105,19 @@ func newInstanceSettings(cfg *setting.Cfg) datasource.InstanceFactoryFunc {
if cfg.Env == setting.Dev {
logger.Debug("GetEngine", "connection", cnnstr)
}
driverName := "mssql"
if jsonData.AuthenticationType == azureAuthentication {
driverName = "azuresql"
}
// register a new proxy driver if the secure socks proxy is enabled
proxyOpts := proxyutil.GetSQLProxyOptions(dsInfo)
if sdkproxy.Cli.SecureSocksProxyEnabled(proxyOpts) {
driverName, err = createMSSQLProxyDriver(cnnstr, proxyOpts)
URL, err := ParseURL(dsInfo.URL)
if err != nil {
return nil, err
}
driverName, err = createMSSQLProxyDriver(cnnstr, URL.Hostname(), proxyOpts)
if err != nil {
return nil, err
}
@@ -141,7 +160,7 @@ func ParseURL(u string) (*url.URL, error) {
}, nil
}
func generateConnectionString(dsInfo sqleng.DataSourceInfo) (string, error) {
func generateConnectionString(dsInfo sqleng.DataSourceInfo, cfg *setting.Cfg, azureCredentials azcredentials.AzureCredentials) (string, error) {
const dfltPort = "0"
var addr util.NetworkAddress
if dsInfo.URL != "" {
@@ -172,12 +191,24 @@ func generateConnectionString(dsInfo sqleng.DataSourceInfo) (string, error) {
tlsSkipVerify := dsInfo.JsonData.TlsSkipVerify
hostNameInCertificate := dsInfo.JsonData.Servername
certificate := dsInfo.JsonData.RootCertFile
connStr := fmt.Sprintf("server=%s;database=%s;user id=%s;password=%s;",
connStr := fmt.Sprintf("server=%s;database=%s;",
addr.Host,
dsInfo.Database,
dsInfo.User,
dsInfo.DecryptedSecureJSONData["password"],
)
switch dsInfo.JsonData.AuthenticationType {
case azureAuthentication:
azureCredentialDSNFragment, err := getAzureCredentialDSNFragment(azureCredentials, cfg)
if err != nil {
return "", err
}
connStr += azureCredentialDSNFragment
case windowsAuthentication:
// No user id or password. We're using windows single sign on.
default:
connStr += fmt.Sprintf("user id=%s;password=%s;", dsInfo.User, dsInfo.DecryptedSecureJSONData["password"])
}
// Port number 0 means to determine the port automatically, so we can let the driver choose
if addr.Port != "0" {
connStr += fmt.Sprintf("port=%s;", addr.Port)
@@ -202,6 +233,28 @@ func generateConnectionString(dsInfo sqleng.DataSourceInfo) (string, error) {
return connStr, nil
}
func getAzureCredentialDSNFragment(azureCredentials azcredentials.AzureCredentials, cfg *setting.Cfg) (string, error) {
connStr := ""
switch c := azureCredentials.(type) {
case *azcredentials.AzureManagedIdentityCredentials:
if cfg.Azure.ManagedIdentityClientId != "" {
connStr += fmt.Sprintf("user id=%s;", cfg.Azure.ManagedIdentityClientId)
}
connStr += fmt.Sprintf("fedauth=%s;",
"ActiveDirectoryManagedIdentity")
case *azcredentials.AzureClientSecretCredentials:
connStr += fmt.Sprintf("user id=%s@%s;password=%s;fedauth=%s;",
c.ClientId,
c.TenantId,
c.ClientSecret,
"ActiveDirectoryApplication",
)
default:
return "", fmt.Errorf("unsupported azure authentication type")
}
return connStr, nil
}
type mssqlQueryResultTransformer struct {
userError string
}

View File

@@ -1460,7 +1460,7 @@ func TestGenerateConnectionString(t *testing.T) {
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
connStr, err := generateConnectionString(tc.dataSource)
connStr, err := generateConnectionString(tc.dataSource, nil, nil)
require.NoError(t, err)
assert.Equal(t, tc.expConnStr, connStr)
})

View File

@@ -5,18 +5,19 @@ import (
"database/sql"
"database/sql/driver"
"errors"
"net"
mssql "github.com/grafana/go-mssqldb"
sdkproxy "github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"github.com/grafana/grafana/pkg/util"
mssql "github.com/microsoft/go-mssqldb"
"golang.org/x/net/proxy"
"xorm.io/core"
)
// 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(cnnstr string, opts *sdkproxy.Options) (string, error) {
func createMSSQLProxyDriver(cnnstr string, hostName string, opts *sdkproxy.Options) (string, error) {
sqleng.XormDriverMu.Lock()
defer sqleng.XormDriverMu.Unlock()
@@ -34,7 +35,7 @@ func createMSSQLProxyDriver(cnnstr string, opts *sdkproxy.Options) (string, erro
return "", err
}
driver, err := newMSSQLProxyDriver(connector, opts)
driver, err := newMSSQLProxyDriver(connector, hostName, opts)
if err != nil {
return "", err
}
@@ -45,6 +46,19 @@ func createMSSQLProxyDriver(cnnstr string, opts *sdkproxy.Options) (string, erro
return driverName, nil
}
type HostTransportDialer struct {
Dialer proxy.ContextDialer
Host string
}
func (m HostTransportDialer) DialContext(ctx context.Context, network string, addr string) (conn net.Conn, err error) {
return m.Dialer.DialContext(ctx, network, addr)
}
func (m HostTransportDialer) HostName() string {
return m.Host
}
// mssqlProxyDriver is a regular mssql driver with an updated dialer.
// This is needed because there is no way to save a dialer to the mssql driver in xorm
type mssqlProxyDriver struct {
@@ -56,7 +70,7 @@ 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(connector *mssql.Connector, opts *sdkproxy.Options) (*mssqlProxyDriver, error) {
func newMSSQLProxyDriver(connector *mssql.Connector, hostName string, opts *sdkproxy.Options) (*mssqlProxyDriver, error) {
dialer, err := sdkproxy.Cli.NewSecureSocksProxyContextDialer(opts)
if err != nil {
return nil, err
@@ -67,7 +81,7 @@ func newMSSQLProxyDriver(connector *mssql.Connector, opts *sdkproxy.Options) (*m
return nil, errors.New("unable to cast socks proxy dialer to context proxy dialer")
}
connector.Dialer = contextDialer
connector.Dialer = HostTransportDialer{contextDialer, hostName}
return &mssqlProxyDriver{c: connector}, nil
}

View File

@@ -5,9 +5,9 @@ import (
"fmt"
"testing"
mssql "github.com/grafana/go-mssqldb"
"github.com/grafana/grafana/pkg/tsdb/sqleng"
"github.com/grafana/grafana/pkg/tsdb/sqleng/proxyutil"
mssql "github.com/microsoft/go-mssqldb"
"github.com/stretchr/testify/require"
"xorm.io/core"
)
@@ -17,17 +17,17 @@ func TestMSSQLProxyDriver(t *testing.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(cnnstr, opts)
driverName, err := createMSSQLProxyDriver(cnnstr, "127.0.0.1", opts)
require.NoError(t, err)
t.Run("Driver should not be registered more than once", func(t *testing.T) {
testDriver, err := createMSSQLProxyDriver(cnnstr, opts)
testDriver, err := createMSSQLProxyDriver(cnnstr, "127.0.0.1", 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("server=localhost;user id=sa;password=yourStrong(!)Password;database=db2", opts)
testDriver, err := createMSSQLProxyDriver("server=localhost;user id=sa;password=yourStrong(!)Password;database=db2", "localhost", opts)
require.NoError(t, err)
require.NotEqual(t, driverName, testDriver)
})
@@ -46,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(connector, opts)
driver, err := newMSSQLProxyDriver(connector, "127.0.0.1", opts)
require.NoError(t, err)
conn, err := driver.OpenConnector(cnnstr)
@@ -59,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(connector, opts)
driver, err := newMSSQLProxyDriver(connector, "127.0.0.1", opts)
require.NoError(t, err)
_, err = driver.Open(cnnstr)

View File

@@ -0,0 +1,28 @@
package utils
import (
"encoding/json"
"fmt"
"github.com/grafana/grafana-azure-sdk-go/azcredentials"
"github.com/grafana/grafana-plugin-sdk-go/backend"
)
// GetJsonData just gets the json in easier to work with type. It's used on multiple places which isn't super effective
// but only when creating a client which should not happen often anyway.
func getJsonData(settings backend.DataSourceInstanceSettings) (map[string]interface{}, error) {
var jsonData map[string]interface{}
err := json.Unmarshal(settings.JSONData, &jsonData)
if err != nil {
return nil, fmt.Errorf("error unmarshalling JSONData: %w", err)
}
return jsonData, nil
}
func GetAzureCredentials(settings backend.DataSourceInstanceSettings) (azcredentials.AzureCredentials, error) {
jsonData, err := getJsonData(settings)
if err != nil {
return nil, err
}
return azcredentials.FromDatasourceData(jsonData, settings.DecryptedSecureJSONData)
}

View File

@@ -74,6 +74,7 @@ type JsonData struct {
Database string `json:"database"`
SecureDSProxy bool `json:"enableSecureSocksProxy"`
AllowCleartextPasswords bool `json:"allowCleartextPasswords"`
AuthenticationType string `json:"authenticationType"`
}
type DataSourceInfo struct {

View File

@@ -0,0 +1,164 @@
import { AzureAuthType, AzureCloud, AzureCredentialsType, ConcealedSecretType } from '../types';
import {
configWithManagedIdentityEnabled,
configWithManagedIdentityDisabled,
dataSourceSettingsWithMsiCredentials,
dataSourceSettingsWithClientSecretOnServer,
dataSourceSettingsWithClientSecretInSecureJSONData,
} from './AzureAuth.testMocks';
import { getDefaultCredentials, getSecret, getCredentials, updateCredentials } from './AzureCredentialsConfig';
// NOTE: @ts-ignores are used to ignore the type errors that are thrown when passing in the mocks.
// This is because the mocks are partials of the actual types, so the types are not complete.
export const CLIENT_SECRET_SYMBOL: ConcealedSecretType = Symbol('Concealed client secret');
export const CLIENT_SECRET_STRING = 'XXXX-super-secret-secret-XXXX';
describe('AzureAuth', () => {
describe('AzureCredentialsConfig', () => {
it('`getDefaultCredentials()` should return the correct credentials based on whether the managed identity is enabled', () => {
const resultForManagedIdentityEnabled = getDefaultCredentials(true, AzureCloud.Public);
const resultForManagedIdentityDisabled = getDefaultCredentials(false, AzureCloud.Public);
expect(resultForManagedIdentityEnabled).toEqual({ authType: 'msi' });
expect(resultForManagedIdentityDisabled).toEqual({ authType: 'clientsecret', azureCloud: 'AzureCloud' });
});
it("`getSecret()` should correctly return the client secret if it's not concealed", () => {
const resultFromServerSideSecret = getSecret(false, CLIENT_SECRET_STRING);
expect(resultFromServerSideSecret).toBe(CLIENT_SECRET_STRING);
const resultFromSecureJSONDataSecret = typeof getSecret(true, '');
expect(resultFromSecureJSONDataSecret).toBe('symbol');
});
describe('getCredentials()', () => {
it('should return the correct managed identity credentials', () => {
// If `dataSourceSettings.authType === AzureAuthType.MSI` && `config.azure.managedIdentityEnabled === true`.
const resultForManagedIdentityEnabled = getCredentials(
// @ts-ignore
dataSourceSettingsWithMsiCredentials,
configWithManagedIdentityEnabled
);
expect(resultForManagedIdentityEnabled).toEqual({ authType: AzureAuthType.MSI });
// If `dataSourceSettings.authType === AzureAuthType.MSI` but `config.azure.managedIdentityEnabled !== true`.
// Default to basic client secret credentials.
const resultForManagedIdentityEnabledInJSONButDisabledInConfig = getCredentials(
// @ts-ignore
dataSourceSettingsWithMsiCredentials,
configWithManagedIdentityDisabled
);
expect(resultForManagedIdentityEnabledInJSONButDisabledInConfig).toEqual({
authType: AzureAuthType.CLIENT_SECRET,
azureCloud: 'AzureCloud',
});
});
it('should return the correct client secret credentials', () => {
const basicExpectedResult = {
authType: AzureAuthType.CLIENT_SECRET,
azureCloud: 'AzureCloud',
tenantId: 'XXXX-tenant-id-XXXX',
clientId: 'XXXX-client-id-XXXX',
};
// If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == true`,
// i.e. the client secret is stored on the server.
const resultForClientSecretCredentialsOnServer = getCredentials(
// @ts-ignore
dataSourceSettingsWithClientSecretOnServer,
configWithManagedIdentityDisabled
);
// Here we test the properties separately because the client secret is a symbol,
// and since JS symobls are unique, we test via the `typeof` operator.
expect(resultForClientSecretCredentialsOnServer.authType).toEqual(AzureAuthType.CLIENT_SECRET);
expect(resultForClientSecretCredentialsOnServer.azureCloud).toEqual('AzureCloud');
expect(resultForClientSecretCredentialsOnServer.tenantId).toEqual('XXXX-tenant-id-XXXX');
expect(resultForClientSecretCredentialsOnServer.clientId).toEqual('XXXX-client-id-XXXX');
expect(typeof resultForClientSecretCredentialsOnServer.clientSecret).toEqual('symbol');
// If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == false`,
// i.e. the client secret is stored in the secureJson.
const resultForClientSecretCredentialsInSecureJSON = getCredentials(
// @ts-ignore
dataSourceSettingsWithClientSecretInSecureJSONData,
configWithManagedIdentityDisabled
);
expect(resultForClientSecretCredentialsInSecureJSON).toEqual({
...basicExpectedResult,
clientSecret: CLIENT_SECRET_STRING,
});
});
});
describe('updateCredentials()', () => {
it('should update the credentials for managed service identity correctly', () => {
// If `dataSourceSettings.authType === AzureAuthType.MSI` && `config.azure.managedIdentityEnabled === true`.
const resultForMsiCredentials = updateCredentials(
// @ts-ignore
dataSourceSettingsWithMsiCredentials,
configWithManagedIdentityEnabled,
{
authType: AzureAuthType.MSI,
}
);
expect(resultForMsiCredentials).toEqual({ jsonData: { azureCredentials: { authType: 'msi' } } });
// If `dataSourceSettings.authType === AzureAuthType.MSI` but `config.azure.managedIdentityEnabled !== true`.
expect(() =>
updateCredentials(
// @ts-ignore
dataSourceSettingsWithMsiCredentials,
configWithManagedIdentityDisabled,
{
authType: AzureAuthType.MSI,
}
)
).toThrow('Managed Identity authentication is not enabled in Grafana config.');
});
it('should update the credentials for client secret correctly', () => {
const basicClientSecretCredentials: AzureCredentialsType = {
authType: AzureAuthType.CLIENT_SECRET,
azureCloud: 'AzureCloud',
tenantId: 'XXXX-tenant-id-XXXX',
clientId: 'XXXX-client-id-XXXX',
};
// If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == true`.
const resultForClientSecretCredentials1 = updateCredentials(
// @ts-ignore
dataSourceSettingsWithClientSecretOnServer,
configWithManagedIdentityDisabled,
basicClientSecretCredentials
);
expect(resultForClientSecretCredentials1).toEqual({
jsonData: {
azureCredentials: { ...basicClientSecretCredentials },
},
secureJsonData: { azureClientSecret: undefined },
secureJsonFields: { azureClientSecret: false },
});
// If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == false`.
const resultForClientSecretCredentials2 = updateCredentials(
// @ts-ignore
dataSourceSettingsWithClientSecretInSecureJSONData,
configWithManagedIdentityDisabled,
{ ...basicClientSecretCredentials, clientSecret: 'XXXX-super-secret-secret-XXXX' }
);
expect(resultForClientSecretCredentials2).toEqual({
jsonData: {
azureCredentials: { ...basicClientSecretCredentials },
},
secureJsonData: { azureClientSecret: 'XXXX-super-secret-secret-XXXX' },
secureJsonFields: { azureClientSecret: false },
});
});
});
});
});

View File

@@ -0,0 +1,41 @@
import { DataSourceSettings } from '@grafana/data';
import { GrafanaBootConfig } from '@grafana/runtime';
import { AzureAuthSecureJSONDataType, AzureAuthJSONDataType, AzureAuthType } from '../types';
export const configWithManagedIdentityEnabled: Partial<GrafanaBootConfig> = {
azure: { managedIdentityEnabled: true, userIdentityEnabled: false },
};
export const configWithManagedIdentityDisabled: Partial<GrafanaBootConfig> = {
azure: { managedIdentityEnabled: false, userIdentityEnabled: false, cloud: 'AzureCloud' },
};
export const dataSourceSettingsWithMsiCredentials: Partial<
DataSourceSettings<AzureAuthJSONDataType, AzureAuthSecureJSONDataType>
> = {
jsonData: { azureCredentials: { authType: AzureAuthType.MSI } },
};
const basicJSONData = {
jsonData: {
azureCredentials: {
authType: AzureAuthType.CLIENT_SECRET,
tenantId: 'XXXX-tenant-id-XXXX',
clientId: 'XXXX-client-id-XXXX',
},
},
};
// Will return symbol as the secret is concealed
export const dataSourceSettingsWithClientSecretOnServer: Partial<
DataSourceSettings<AzureAuthJSONDataType, AzureAuthSecureJSONDataType>
> = { ...basicJSONData, secureJsonFields: { azureClientSecret: true } };
// Will return the secret as a string from the secureJsonData
export const dataSourceSettingsWithClientSecretInSecureJSONData: Partial<
DataSourceSettings<AzureAuthJSONDataType, AzureAuthSecureJSONDataType>
> = {
...basicJSONData,
secureJsonData: { azureClientSecret: 'XXXX-super-secret-secret-XXXX' },
};

View File

@@ -0,0 +1,33 @@
import React, { useMemo } from 'react';
import { config } from '@grafana/runtime';
import { HttpSettingsBaseProps } from '@grafana/ui/src/components/DataSourceSettings/types';
import { AzureCredentialsType } from '../types';
import { KnownAzureClouds } from './AzureCredentials';
import { getCredentials, updateCredentials } from './AzureCredentialsConfig';
import { AzureCredentialsForm } from './AzureCredentialsForm';
export const AzureAuthSettings = (props: HttpSettingsBaseProps) => {
const { dataSourceConfig: dsSettings, onChange } = props;
const managedIdentityEnabled = config.azure.managedIdentityEnabled;
const credentials = useMemo(() => getCredentials(dsSettings, config), [dsSettings]);
const onCredentialsChange = (credentials: AzureCredentialsType): void => {
onChange(updateCredentials(dsSettings, config, credentials));
};
return (
<AzureCredentialsForm
managedIdentityEnabled={managedIdentityEnabled}
credentials={credentials}
azureCloudOptions={KnownAzureClouds}
onCredentialsChange={onCredentialsChange}
disabled={dsSettings.readOnly}
/>
);
};
export default AzureAuthSettings;

View File

@@ -0,0 +1,19 @@
import { SelectableValue } from '@grafana/data';
import { AzureCredentialsType, AzureAuthType } from '../types';
export enum AzureCloud {
Public = 'AzureCloud',
None = '',
}
export const KnownAzureClouds: Array<SelectableValue<AzureCloud>> = [{ value: AzureCloud.Public, label: 'Azure' }];
export function isCredentialsComplete(credentials: AzureCredentialsType): boolean {
switch (credentials.authType) {
case AzureAuthType.MSI:
return true;
case AzureAuthType.CLIENT_SECRET:
return !!(credentials.azureCloud && credentials.tenantId && credentials.clientId && credentials.clientSecret);
}
}

View File

@@ -0,0 +1,134 @@
import { DataSourceSettings } from '@grafana/data';
import { GrafanaBootConfig } from '@grafana/runtime';
import {
AzureCloud,
AzureCredentialsType,
ConcealedSecretType,
AzureAuthSecureJSONDataType,
AzureAuthJSONDataType,
AzureAuthType,
} from '../types';
export const getDefaultCredentials = (managedIdentityEnabled: boolean, cloud: string): AzureCredentialsType => {
if (managedIdentityEnabled) {
return { authType: AzureAuthType.MSI };
} else {
return { authType: AzureAuthType.CLIENT_SECRET, azureCloud: cloud };
}
};
export const getSecret = (
clientSecretStoredServerSide: boolean,
clientSecret: string | symbol | undefined
): undefined | string | ConcealedSecretType => {
const concealedSecret: ConcealedSecretType = Symbol('Concealed client secret');
if (clientSecretStoredServerSide) {
// The secret is concealed server side, so return the symbol
return concealedSecret;
} else {
return typeof clientSecret === 'string' && clientSecret.length > 0 ? clientSecret : undefined;
}
};
export const getCredentials = (
dsSettings: DataSourceSettings<AzureAuthJSONDataType, AzureAuthSecureJSONDataType>,
bootConfig: GrafanaBootConfig
): AzureCredentialsType => {
// JSON data
const credentials = dsSettings.jsonData?.azureCredentials;
// Secure JSON data/fields
const clientSecretStoredServerSide = dsSettings.secureJsonFields?.azureClientSecret;
const clientSecret = dsSettings.secureJsonData?.azureClientSecret;
// BootConfig data
const managedIdentityEnabled = !!bootConfig.azure?.managedIdentityEnabled;
const cloud = bootConfig.azure?.cloud || AzureCloud.Public;
// If no credentials saved, then return empty credentials
// of type based on whether the managed identity enabled
if (!credentials) {
return getDefaultCredentials(managedIdentityEnabled, cloud);
}
switch (credentials.authType) {
case AzureAuthType.MSI:
if (managedIdentityEnabled) {
return {
authType: AzureAuthType.MSI,
};
} else {
// If authentication type is managed identity but managed identities were disabled in Grafana config,
// then we should fallback to an empty app registration (client secret) configuration
return {
authType: AzureAuthType.CLIENT_SECRET,
azureCloud: cloud,
};
}
case AzureAuthType.CLIENT_SECRET:
return {
authType: AzureAuthType.CLIENT_SECRET,
azureCloud: credentials.azureCloud || cloud,
tenantId: credentials.tenantId,
clientId: credentials.clientId,
clientSecret: getSecret(clientSecretStoredServerSide, clientSecret),
};
}
};
export const updateCredentials = (
dsSettings: DataSourceSettings<AzureAuthJSONDataType>,
bootConfig: GrafanaBootConfig,
credentials: AzureCredentialsType
): DataSourceSettings<AzureAuthJSONDataType> => {
// BootConfig data
const managedIdentityEnabled = !!bootConfig.azure?.managedIdentityEnabled;
const cloud = bootConfig.azure?.cloud || AzureCloud.Public;
switch (credentials.authType) {
case AzureAuthType.MSI:
if (!managedIdentityEnabled) {
throw new Error('Managed Identity authentication is not enabled in Grafana config.');
}
dsSettings = {
...dsSettings,
jsonData: {
...dsSettings.jsonData,
azureCredentials: {
authType: AzureAuthType.MSI,
},
},
};
return dsSettings;
case AzureAuthType.CLIENT_SECRET:
dsSettings = {
...dsSettings,
jsonData: {
...dsSettings.jsonData,
azureCredentials: {
authType: AzureAuthType.CLIENT_SECRET,
azureCloud: credentials.azureCloud || cloud,
tenantId: credentials.tenantId,
clientId: credentials.clientId,
},
},
secureJsonData: {
...dsSettings.secureJsonData,
azureClientSecret:
typeof credentials.clientSecret === 'string' && credentials.clientSecret.length > 0
? credentials.clientSecret
: undefined,
},
secureJsonFields: {
...dsSettings.secureJsonFields,
azureClientSecret: typeof credentials.clientSecret === 'symbol',
},
};
return dsSettings;
}
};

View File

@@ -0,0 +1,172 @@
import React, { ChangeEvent } from 'react';
import { SelectableValue } from '@grafana/data';
import { InlineFormLabel, Button } from '@grafana/ui/src/components';
import { Input } from '@grafana/ui/src/components/Forms/Legacy/Input/Input';
import { Select } from '@grafana/ui/src/components/Forms/Legacy/Select/Select';
import { AzureCredentialsType, AzureAuthType } from '../types';
export interface Props {
managedIdentityEnabled: boolean;
credentials: AzureCredentialsType;
azureCloudOptions?: SelectableValue[];
onCredentialsChange: (updatedCredentials: AzureCredentialsType) => void;
disabled?: boolean;
}
const authTypeOptions: Array<SelectableValue<AzureAuthType>> = [
{
value: AzureAuthType.MSI,
label: 'Managed Identity',
},
{
value: AzureAuthType.CLIENT_SECRET,
label: 'App Registration',
},
];
export const AzureCredentialsForm = (props: Props) => {
const { managedIdentityEnabled, credentials, azureCloudOptions, onCredentialsChange, disabled } = props;
const onAuthTypeChange = (selected: SelectableValue<AzureAuthType>) => {
if (onCredentialsChange) {
const updated: AzureCredentialsType = {
...credentials,
authType: selected.value || AzureAuthType.MSI,
};
onCredentialsChange(updated);
}
};
const onInputChange = ({ property, value }: { property: keyof AzureCredentialsType; value: string }) => {
if (onCredentialsChange && credentials.authType === 'clientsecret') {
const updated: AzureCredentialsType = {
...credentials,
[property]: value,
};
onCredentialsChange(updated);
}
};
return (
<div>
{managedIdentityEnabled && (
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel tooltip="Choose the type of authentication to Azure services">
Authentication
</InlineFormLabel>
<Select
value={authTypeOptions.find((opt) => opt.value === credentials.authType)}
options={authTypeOptions}
onChange={onAuthTypeChange}
isDisabled={disabled}
/>
</div>
</div>
)}
{credentials.authType === 'clientsecret' && (
<>
{azureCloudOptions && (
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12" tooltip="Choose an Azure Cloud">
Azure Cloud
</InlineFormLabel>
<Select
value={azureCloudOptions.find((opt) => opt.value === credentials.azureCloud)}
options={azureCloudOptions}
onChange={(selected: SelectableValue<AzureAuthType>) => {
const value = selected.value || '';
onInputChange({ property: 'azureCloud', value });
}}
isDisabled={disabled}
/>
</div>
</div>
)}
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12">Directory (tenant) ID</InlineFormLabel>
<div className="width-15">
<Input
className="width-30"
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value={credentials.tenantId || ''}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
onInputChange({ property: 'tenantId', value });
}}
disabled={disabled}
/>
</div>
</div>
</div>
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12">Application (client) ID</InlineFormLabel>
<div className="width-15">
<Input
className="width-30"
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value={credentials.clientId || ''}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
onInputChange({ property: 'clientId', value });
}}
disabled={disabled}
/>
</div>
</div>
</div>
{typeof credentials.clientSecret === 'symbol' ? (
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel htmlFor="azure-client-secret" className="width-12">
Client Secret
</InlineFormLabel>
<Input id="azure-client-secret" className="width-25" placeholder="configured" disabled />
</div>
{!disabled && (
<div className="gf-form">
<div className="max-width-30 gf-form-inline">
<Button
variant="secondary"
type="button"
onClick={() => {
onInputChange({ property: 'clientSecret', value: '' });
}}
>
reset
</Button>
</div>
</div>
)}
</div>
) : (
<div className="gf-form-inline">
<div className="gf-form">
<InlineFormLabel className="width-12">Client Secret</InlineFormLabel>
<div className="width-15">
<Input
className="width-30"
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value={credentials.clientSecret || ''}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
onInputChange({ property: 'clientSecret', value });
}}
disabled={disabled}
/>
</div>
</div>
</div>
)}
</>
)}
</div>
);
};
export default AzureCredentialsForm;

View File

@@ -28,22 +28,34 @@ import { config } from 'app/core/config';
import { ConnectionLimits } from 'app/features/plugins/sql/components/configuration/ConnectionLimits';
import { useMigrateDatabaseFields } from 'app/features/plugins/sql/components/configuration/useMigrateDatabaseFields';
import { MSSQLAuthenticationType, MSSQLEncryptOptions, MssqlOptions } from '../types';
import { AzureAuthSettings } from '../azureauth/AzureAuthSettings';
import { MSSQLAuthenticationType, MSSQLEncryptOptions, MssqlOptions, AzureAuthConfigType } from '../types';
const SHORT_WIDTH = 15;
const LONG_WIDTH = 46;
const LABEL_WIDTH_SSL = 25;
const LABEL_WIDTH_DETAILS = 20;
export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<MssqlOptions>) => {
const { options, onOptionsChange } = props;
const styles = useStyles2(getStyles);
const jsonData = options.jsonData;
useMigrateDatabaseFields(props);
const { options: dsSettings, onOptionsChange } = props;
const styles = useStyles2(getStyles);
const jsonData = dsSettings.jsonData;
const azureAuthIsSupported = config.azureAuthEnabled;
const azureAuthSettings: AzureAuthConfigType = {
azureAuthIsSupported,
azureAuthSettingsUI: AzureAuthSettings,
};
const onResetPassword = () => {
updateDatasourcePluginResetOption(props, 'password');
};
const onDSOptionChanged = (property: keyof MssqlOptions) => {
return (event: SyntheticEvent<HTMLInputElement>) => {
onOptionsChange({ ...options, ...{ [property]: event.currentTarget.value } });
onOptionsChange({ ...dsSettings, ...{ [property]: event.currentTarget.value } });
};
};
@@ -57,11 +69,11 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
const onAuthenticationMethodChanged = (value: SelectableValue) => {
onOptionsChange({
...options,
...dsSettings,
...{
jsonData: { ...jsonData, ...{ authenticationType: value.value } },
secureJsonData: { ...options.secureJsonData, ...{ password: '' } },
secureJsonFields: { ...options.secureJsonFields, ...{ password: false } },
jsonData: { ...jsonData, ...{ authenticationType: value.value }, azureCredentials: undefined },
secureJsonData: { ...dsSettings.secureJsonData, ...{ password: '' } },
secureJsonFields: { ...dsSettings.secureJsonFields, ...{ password: false } },
user: '',
},
});
@@ -71,10 +83,21 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
updateDatasourcePluginJsonDataOption(props, 'connectionTimeout', connectionTimeout ?? 0);
};
const authenticationOptions: Array<SelectableValue<MSSQLAuthenticationType>> = [
{ value: MSSQLAuthenticationType.sqlAuth, label: 'SQL Server Authentication' },
{ value: MSSQLAuthenticationType.windowsAuth, label: 'Windows Authentication' },
];
const buildAuthenticationOptions = (): Array<SelectableValue<MSSQLAuthenticationType>> => {
const basicAuthenticationOptions: Array<SelectableValue<MSSQLAuthenticationType>> = [
{ value: MSSQLAuthenticationType.sqlAuth, label: 'SQL Server Authentication' },
{ value: MSSQLAuthenticationType.windowsAuth, label: 'Windows Authentication' },
];
if (azureAuthIsSupported) {
return [
...basicAuthenticationOptions,
{ value: MSSQLAuthenticationType.azureAuth, label: 'Azure AD Authentication' },
];
}
return basicAuthenticationOptions;
};
const encryptOptions: Array<SelectableValue<string>> = [
{ value: MSSQLEncryptOptions.disable, label: 'disable' },
@@ -82,27 +105,22 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
{ value: MSSQLEncryptOptions.true, label: 'true' },
];
const shortWidth = 15;
const longWidth = 46;
const labelWidthSSL = 25;
const labelWidthDetails = 20;
return (
<>
<FieldSet label="MS SQL Connection" width={400}>
<InlineField labelWidth={shortWidth} label="Host">
<InlineField labelWidth={SHORT_WIDTH} label="Host">
<Input
width={longWidth}
width={LONG_WIDTH}
name="host"
type="text"
value={options.url || ''}
value={dsSettings.url || ''}
placeholder="localhost:1433"
onChange={onDSOptionChanged('url')}
></Input>
</InlineField>
<InlineField labelWidth={shortWidth} label="Database">
<InlineField labelWidth={SHORT_WIDTH} label="Database">
<Input
width={longWidth}
width={LONG_WIDTH}
name="database"
value={jsonData.database || ''}
placeholder="database name"
@@ -111,7 +129,7 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
</InlineField>
<InlineField
label="Authentication"
labelWidth={shortWidth}
labelWidth={SHORT_WIDTH}
htmlFor="authenticationType"
tooltip={
<ul className={styles.ulPadding}>
@@ -123,31 +141,40 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
<i>Windows Authentication</i> Windows Integrated Security - single sign on for users who are already
logged onto Windows and have enabled this option for MS SQL Server.
</li>
{azureAuthIsSupported && (
<li>
<i>Azure Authentication</i> Securely authenticate and access Azure resources and applications using
Azure AD credentials - Managed Service Identity and Client Secret Credentials are supported.
</li>
)}
</ul>
}
>
<Select
// Default to basic authentication of none is set
value={jsonData.authenticationType || MSSQLAuthenticationType.sqlAuth}
inputId="authenticationType"
options={authenticationOptions}
options={buildAuthenticationOptions()}
onChange={onAuthenticationMethodChanged}
></Select>
</InlineField>
{jsonData.authenticationType === MSSQLAuthenticationType.windowsAuth ? null : (
{/* Basic SQL auth. Render if authType === MSSQLAuthenticationType.sqlAuth OR
if no authType exists, which will be the case when creating a new data source */}
{(jsonData.authenticationType === MSSQLAuthenticationType.sqlAuth || !jsonData.authenticationType) && (
<InlineFieldRow>
<InlineField labelWidth={shortWidth} label="User">
<InlineField labelWidth={SHORT_WIDTH} label="User">
<Input
width={shortWidth}
value={options.user || ''}
width={SHORT_WIDTH}
value={dsSettings.user || ''}
placeholder="user"
onChange={onDSOptionChanged('user')}
></Input>
</InlineField>
<InlineField label="Password" labelWidth={shortWidth}>
<InlineField label="Password" labelWidth={SHORT_WIDTH}>
<SecretInput
width={shortWidth}
width={SHORT_WIDTH}
placeholder="Password"
isConfigured={options.secureJsonFields && options.secureJsonFields.password}
isConfigured={dsSettings.secureJsonFields && dsSettings.secureJsonFields.password}
onReset={onResetPassword}
onBlur={onUpdateDatasourceSecureJsonDataOption(props, 'password')}
></SecretInput>
@@ -157,12 +184,12 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
</FieldSet>
{config.secureSocksDSProxyEnabled && (
<SecureSocksProxySettings options={options} onOptionsChange={onOptionsChange} />
<SecureSocksProxySettings options={dsSettings} onOptionsChange={onOptionsChange} />
)}
<FieldSet label="TLS/SSL Auth">
<InlineField
labelWidth={labelWidthSSL}
labelWidth={LABEL_WIDTH_SSL}
htmlFor="encrypt"
tooltip={
<>
@@ -194,7 +221,7 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
{jsonData.encrypt === MSSQLEncryptOptions.true ? (
<>
<InlineField labelWidth={labelWidthSSL} htmlFor="skipTlsVerify" label="Skip TLS Verify">
<InlineField labelWidth={LABEL_WIDTH_SSL} htmlFor="skipTlsVerify" label="Skip TLS Verify">
<InlineSwitch
id="skipTlsVerify"
onChange={onSkipTLSVerifyChanged}
@@ -204,7 +231,7 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
{jsonData.tlsSkipVerify ? null : (
<>
<InlineField
labelWidth={labelWidthSSL}
labelWidth={LABEL_WIDTH_SSL}
tooltip={
<span>
Path to file containing the public key certificate of the CA that signed the SQL Server
@@ -219,7 +246,7 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
placeholder="TLS/SSL root certificate file path"
></Input>
</InlineField>
<InlineField labelWidth={labelWidthSSL} label="Hostname in server certificate">
<InlineField labelWidth={LABEL_WIDTH_SSL} label="Hostname in server certificate">
<Input
placeholder="Common Name (CN) in server certificate"
value={jsonData.serverName || ''}
@@ -232,7 +259,13 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
) : null}
</FieldSet>
<ConnectionLimits labelWidth={shortWidth} options={options} onOptionsChange={onOptionsChange} />
{azureAuthIsSupported && jsonData.authenticationType === MSSQLAuthenticationType.azureAuth && (
<FieldSet label="Azure Authentication Settings">
<azureAuthSettings.azureAuthSettingsUI dataSourceConfig={dsSettings} onChange={onOptionsChange} />
</FieldSet>
)}
<ConnectionLimits labelWidth={SHORT_WIDTH} options={dsSettings} onOptionsChange={onOptionsChange} />
<FieldSet label="MS SQL details">
<InlineField
@@ -243,7 +276,7 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
</span>
}
label="Min time interval"
labelWidth={labelWidthDetails}
labelWidth={LABEL_WIDTH_DETAILS}
>
<Input
placeholder="1m"
@@ -259,7 +292,7 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps<Ms
</span>
}
label="Connection timeout"
labelWidth={labelWidthDetails}
labelWidth={LABEL_WIDTH_DETAILS}
>
<NumberInput
placeholder="60"

View File

@@ -1,8 +1,11 @@
import { DataSourceJsonData } from '@grafana/data';
import { HttpSettingsBaseProps } from '@grafana/ui/src/components/DataSourceSettings/types';
import { SQLOptions } from 'app/features/plugins/sql/types';
export enum MSSQLAuthenticationType {
sqlAuth = 'SQL Server Authentication',
windowsAuth = 'Windows Authentication',
azureAuth = 'Azure AD Authentication',
}
export enum MSSQLEncryptOptions {
@@ -10,10 +13,45 @@ export enum MSSQLEncryptOptions {
false = 'false',
true = 'true',
}
export enum AzureCloud {
Public = 'AzureCloud',
None = '',
}
export type ConcealedSecretType = symbol;
export enum AzureAuthType {
MSI = 'msi',
CLIENT_SECRET = 'clientsecret',
}
export interface AzureCredentialsType {
authType: AzureAuthType;
azureCloud?: string;
tenantId?: string;
clientId?: string;
clientSecret?: string | ConcealedSecretType;
}
export interface MssqlOptions extends SQLOptions {
authenticationType?: MSSQLAuthenticationType;
encrypt?: MSSQLEncryptOptions;
sslRootCertFile?: string;
serverName?: string;
connectionTimeout?: number;
azureCredentials?: AzureCredentialsType;
}
export type AzureAuthJSONDataType = DataSourceJsonData & {
azureCredentials: AzureCredentialsType;
};
export type AzureAuthSecureJSONDataType = {
azureClientSecret: undefined | string | ConcealedSecretType;
};
export type AzureAuthConfigType = {
azureAuthIsSupported: boolean;
azureAuthSettingsUI: (props: HttpSettingsBaseProps) => JSX.Element;
};