mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
18
go.mod
18
go.mod
@@ -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
51
go.sum
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
28
pkg/tsdb/mssql/utils/utils.go
Normal file
28
pkg/tsdb/mssql/utils/utils.go
Normal 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)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
164
public/app/plugins/datasource/mssql/azureauth/AzureAuth.test.ts
Normal file
164
public/app/plugins/datasource/mssql/azureauth/AzureAuth.test.ts
Normal 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 },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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' },
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user