diff --git a/go.mod b/go.mod index e060b8e2d9b..f4d5eed5aca 100644 --- a/go.mod +++ b/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. diff --git a/go.sum b/go.sum index 90f62c2e34e..6c7cedda965 100644 --- a/go.sum +++ b/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= diff --git a/pkg/tsdb/mssql/mssql.go b/pkg/tsdb/mssql/mssql.go index f3d15657804..6200067b292 100644 --- a/pkg/tsdb/mssql/mssql.go +++ b/pkg/tsdb/mssql/mssql.go @@ -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 } diff --git a/pkg/tsdb/mssql/mssql_test.go b/pkg/tsdb/mssql/mssql_test.go index 778864392b1..7c1cd6ca612 100644 --- a/pkg/tsdb/mssql/mssql_test.go +++ b/pkg/tsdb/mssql/mssql_test.go @@ -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) }) diff --git a/pkg/tsdb/mssql/proxy.go b/pkg/tsdb/mssql/proxy.go index 831059452ee..d06c20eccc0 100644 --- a/pkg/tsdb/mssql/proxy.go +++ b/pkg/tsdb/mssql/proxy.go @@ -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 } diff --git a/pkg/tsdb/mssql/proxy_test.go b/pkg/tsdb/mssql/proxy_test.go index de5bf9c8a05..bd214a1ad78 100644 --- a/pkg/tsdb/mssql/proxy_test.go +++ b/pkg/tsdb/mssql/proxy_test.go @@ -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) diff --git a/pkg/tsdb/mssql/utils/utils.go b/pkg/tsdb/mssql/utils/utils.go new file mode 100644 index 00000000000..9e927f28fba --- /dev/null +++ b/pkg/tsdb/mssql/utils/utils.go @@ -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) +} diff --git a/pkg/tsdb/sqleng/sql_engine.go b/pkg/tsdb/sqleng/sql_engine.go index ddf3c8b2332..4dc6cc020b9 100644 --- a/pkg/tsdb/sqleng/sql_engine.go +++ b/pkg/tsdb/sqleng/sql_engine.go @@ -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 { diff --git a/public/app/plugins/datasource/mssql/azureauth/AzureAuth.test.ts b/public/app/plugins/datasource/mssql/azureauth/AzureAuth.test.ts new file mode 100644 index 00000000000..ae8d516966e --- /dev/null +++ b/public/app/plugins/datasource/mssql/azureauth/AzureAuth.test.ts @@ -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 }, + }); + }); + }); + }); +}); diff --git a/public/app/plugins/datasource/mssql/azureauth/AzureAuth.testMocks.ts b/public/app/plugins/datasource/mssql/azureauth/AzureAuth.testMocks.ts new file mode 100644 index 00000000000..ad2372f5cf0 --- /dev/null +++ b/public/app/plugins/datasource/mssql/azureauth/AzureAuth.testMocks.ts @@ -0,0 +1,41 @@ +import { DataSourceSettings } from '@grafana/data'; +import { GrafanaBootConfig } from '@grafana/runtime'; + +import { AzureAuthSecureJSONDataType, AzureAuthJSONDataType, AzureAuthType } from '../types'; + +export const configWithManagedIdentityEnabled: Partial = { + azure: { managedIdentityEnabled: true, userIdentityEnabled: false }, +}; + +export const configWithManagedIdentityDisabled: Partial = { + azure: { managedIdentityEnabled: false, userIdentityEnabled: false, cloud: 'AzureCloud' }, +}; + +export const dataSourceSettingsWithMsiCredentials: Partial< + DataSourceSettings +> = { + 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 +> = { ...basicJSONData, secureJsonFields: { azureClientSecret: true } }; + +// Will return the secret as a string from the secureJsonData +export const dataSourceSettingsWithClientSecretInSecureJSONData: Partial< + DataSourceSettings +> = { + ...basicJSONData, + secureJsonData: { azureClientSecret: 'XXXX-super-secret-secret-XXXX' }, +}; diff --git a/public/app/plugins/datasource/mssql/azureauth/AzureAuthSettings.tsx b/public/app/plugins/datasource/mssql/azureauth/AzureAuthSettings.tsx new file mode 100644 index 00000000000..28b4421a389 --- /dev/null +++ b/public/app/plugins/datasource/mssql/azureauth/AzureAuthSettings.tsx @@ -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 ( + + ); +}; + +export default AzureAuthSettings; diff --git a/public/app/plugins/datasource/mssql/azureauth/AzureCredentials.ts b/public/app/plugins/datasource/mssql/azureauth/AzureCredentials.ts new file mode 100644 index 00000000000..67c8b54d408 --- /dev/null +++ b/public/app/plugins/datasource/mssql/azureauth/AzureCredentials.ts @@ -0,0 +1,19 @@ +import { SelectableValue } from '@grafana/data'; + +import { AzureCredentialsType, AzureAuthType } from '../types'; + +export enum AzureCloud { + Public = 'AzureCloud', + None = '', +} + +export const KnownAzureClouds: Array> = [{ 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); + } +} diff --git a/public/app/plugins/datasource/mssql/azureauth/AzureCredentialsConfig.ts b/public/app/plugins/datasource/mssql/azureauth/AzureCredentialsConfig.ts new file mode 100644 index 00000000000..92c03eceb3b --- /dev/null +++ b/public/app/plugins/datasource/mssql/azureauth/AzureCredentialsConfig.ts @@ -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, + 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, + bootConfig: GrafanaBootConfig, + credentials: AzureCredentialsType +): DataSourceSettings => { + // 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; + } +}; diff --git a/public/app/plugins/datasource/mssql/azureauth/AzureCredentialsForm.tsx b/public/app/plugins/datasource/mssql/azureauth/AzureCredentialsForm.tsx new file mode 100644 index 00000000000..acf68ccca90 --- /dev/null +++ b/public/app/plugins/datasource/mssql/azureauth/AzureCredentialsForm.tsx @@ -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> = [ + { + 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) => { + 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 ( +
+ {managedIdentityEnabled && ( +
+
+ + Authentication + + opt.value === credentials.azureCloud)} + options={azureCloudOptions} + onChange={(selected: SelectableValue) => { + const value = selected.value || ''; + onInputChange({ property: 'azureCloud', value }); + }} + isDisabled={disabled} + /> +
+
+ )} +
+
+ Directory (tenant) ID +
+ ) => { + const value = event.target.value; + onInputChange({ property: 'tenantId', value }); + }} + disabled={disabled} + /> +
+
+
+
+
+ Application (client) ID +
+ ) => { + const value = event.target.value; + onInputChange({ property: 'clientId', value }); + }} + disabled={disabled} + /> +
+
+
+ {typeof credentials.clientSecret === 'symbol' ? ( +
+
+ + Client Secret + + +
+ {!disabled && ( +
+
+ +
+
+ )} +
+ ) : ( +
+
+ Client Secret +
+ ) => { + const value = event.target.value; + onInputChange({ property: 'clientSecret', value }); + }} + disabled={disabled} + /> +
+
+
+ )} + + )} +
+ ); +}; + +export default AzureCredentialsForm; diff --git a/public/app/plugins/datasource/mssql/configuration/ConfigurationEditor.tsx b/public/app/plugins/datasource/mssql/configuration/ConfigurationEditor.tsx index b6001274294..ed6e03b806d 100644 --- a/public/app/plugins/datasource/mssql/configuration/ConfigurationEditor.tsx +++ b/public/app/plugins/datasource/mssql/configuration/ConfigurationEditor.tsx @@ -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) => { - 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) => { - onOptionsChange({ ...options, ...{ [property]: event.currentTarget.value } }); + onOptionsChange({ ...dsSettings, ...{ [property]: event.currentTarget.value } }); }; }; @@ -57,11 +69,11 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps { 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> = [ - { value: MSSQLAuthenticationType.sqlAuth, label: 'SQL Server Authentication' }, - { value: MSSQLAuthenticationType.windowsAuth, label: 'Windows Authentication' }, - ]; + const buildAuthenticationOptions = (): Array> => { + const basicAuthenticationOptions: Array> = [ + { 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> = [ { value: MSSQLEncryptOptions.disable, label: 'disable' }, @@ -82,27 +105,22 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps
- + - + @@ -123,31 +141,40 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorPropsWindows Authentication Windows Integrated Security - single sign on for users who are already logged onto Windows and have enabled this option for MS SQL Server. + {azureAuthIsSupported && ( +
  • + Azure Authentication Securely authenticate and access Azure resources and applications using + Azure AD credentials - Managed Service Identity and Client Secret Credentials are supported. +
  • + )} } >
    - {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) && ( - + - + @@ -157,12 +184,12 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps {config.secureSocksDSProxyEnabled && ( - + )}
    @@ -194,7 +221,7 @@ export const ConfigurationEditor = (props: DataSourcePluginOptionsEditorProps - + 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 - + - + {azureAuthIsSupported && jsonData.authenticationType === MSSQLAuthenticationType.azureAuth && ( +
    + +
    + )} + +
    } label="Min time interval" - labelWidth={labelWidthDetails} + labelWidth={LABEL_WIDTH_DETAILS} > } label="Connection timeout" - labelWidth={labelWidthDetails} + labelWidth={LABEL_WIDTH_DETAILS} > JSX.Element; +};