From fa9d6be255d2d082e0e6931c57a3500baa089c95 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Fri, 10 Oct 2025 12:46:26 +0200 Subject: [PATCH] Azure: Use SSO settings in plugin context (#112058) * Bump grafana-azure-sdk-go * Set override values * Add Azure settings helper covering SSO cases * Ensure Azure settings are correctly created - Add mock for sso settings service - Add tests - Update wire * Minor improvements * Test updates * Move fake implementation * add interface to limit leakage * rename * work sync * Fix wire * Add fake provider * Update tests * Actually fix the workspace * More go dependency fixes * Update tests * Update workspace, again * Add missing tests * Fix dependencies * These dependencies.. * More dependency things * Okay now dependencies really are fixed * Lint * Update pkg/services/pluginsintegration/pluginconfig/request.go Co-authored-by: Misi --------- Co-authored-by: Will Browne Co-authored-by: Misi --- go.mod | 10 +- go.sum | 20 +- go.work.sum | 5 + pkg/promlib/go.mod | 5 +- pkg/promlib/go.sum | 16 +- pkg/server/wire_gen.go | 19 +- .../pluginconfig/azure_settings.go | 66 ++++++ .../pluginconfig/azure_settings_test.go | 197 ++++++++++++++++++ .../pluginconfig/envvars.go | 32 ++- .../pluginconfig/envvars_test.go | 125 ++++++++++- .../pluginconfig/request.go | 30 ++- .../pluginconfig/request_test.go | 152 ++++++++++++-- .../pluginsintegration/pluginsintegration.go | 3 + .../pluginsintegration/pluginsso/pluginsso.go | 41 ++++ pkg/setting/setting_azure.go | 6 + 15 files changed, 652 insertions(+), 75 deletions(-) create mode 100644 pkg/services/pluginsintegration/pluginconfig/azure_settings.go create mode 100644 pkg/services/pluginsintegration/pluginconfig/azure_settings_test.go create mode 100644 pkg/services/pluginsintegration/pluginsso/pluginsso.go diff --git a/go.mod b/go.mod index 593f25c5698..0cdd794fe95 100644 --- a/go.mod +++ b/go.mod @@ -13,8 +13,8 @@ require ( filippo.io/age v1.2.1 // @grafana/identity-access-team github.com/1NCE-GmbH/grpc-go-pool v0.0.0-20231117122434-2a5bb974daa2 // @grafana/grafana-search-and-storage github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // @grafana/partner-datasources - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 // @grafana/identity-access-team - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 // @grafana/grafana-backend-group + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 // @grafana/identity-access-team + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0 // @grafana/grafana-backend-group github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 // @grafana/grafana-backend-group github.com/Azure/azure-storage-blob-go v0.15.0 // @grafana/grafana-backend-group github.com/Azure/go-autorest/autorest v0.11.29 // @grafana/grafana-backend-group @@ -99,7 +99,7 @@ require ( github.com/grafana/grafana-app-sdk v0.46.0 // @grafana/grafana-app-platform-squad github.com/grafana/grafana-app-sdk/logging v0.45.0 // @grafana/grafana-app-platform-squad github.com/grafana/grafana-aws-sdk v1.2.0 // @grafana/aws-datasources - github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 // @grafana/partner-datasources + github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 // @grafana/partner-datasources github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 // @grafana/grafana-operator-experience-squad github.com/grafana/grafana-google-sdk-go v0.4.2 // @grafana/partner-datasources github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group @@ -296,7 +296,7 @@ require ( cloud.google.com/go/monitoring v1.24.2 // indirect cuelabs.dev/go/oci/ociregistry v0.0.0-20240906074133-82eb438dd565 // indirect github.com/Azure/azure-pipeline-go v0.2.3 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect @@ -306,7 +306,7 @@ require ( github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // indirect github.com/FZambia/eagle v0.2.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect diff --git a/go.sum b/go.sum index ba9545692d5..c7081810411 100644 --- a/go.sum +++ b/go.sum @@ -648,14 +648,14 @@ github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl github.com/Azure/azure-sdk-for-go v23.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0 h1:wL5IEG5zb7BVv1Kv0Xm92orq+5hB5Nipn3B5tn4Rqfk= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= 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.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= @@ -703,8 +703,8 @@ github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 h1:XkkQbfMyuH2jTSjQjSoihryI8GINRcs4xp8lNawg0FI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb h1:ZVN4Iat3runWOFLaBCDVU5a9X/XikSRBosye++6gojw= github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb/go.mod h1:WsAABbY4HQBgd3mGuG4KMNTbHJCPvx9IVBHzysbknss= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -1611,8 +1611,8 @@ github.com/grafana/grafana-app-sdk/logging v0.45.0 h1:0SH6nYZpiLBZRwUq4J6+1vo8xu github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA= github.com/grafana/grafana-aws-sdk v1.2.0 h1:LLR4/g91WBuCRwm2cbWfCREq565+GxIFe08nqqIcIuw= github.com/grafana/grafana-aws-sdk v1.2.0/go.mod h1:bBo7qOmM3f61vO+2JxTolNUph1l2TmtzmWcU9/Im+8A= -github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 h1:0TYrkzAc3u0HX+9GK86cGrLTUAcmQfl3/LEB3tL+SOA= -github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0/go.mod h1:H9sVh9A4yg5egMGZeh0mifxT1Q/uqwKe1LBjBJU6pN8= +github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 h1:FFcEA01tW+SmuJIuDbHOdgUBL+d7DPrZ2N4zwzPhfGk= +github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1/go.mod h1:Oi4anANlCuTCc66jCyqIzfVbgLXFll8Wja+Y4vfANlc= github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 h1:JOzchPgptwJdruYoed7x28lFDwhzs7kssResYsnC0iI= github.com/grafana/grafana-cloud-migration-snapshot v1.9.0/go.mod h1:nOHgq4Oa829qmBKA5KIXw5Ipo3rhLs0d6A8UI9Nw8Zk= github.com/grafana/grafana-google-sdk-go v0.4.2 h1:F44hQF1y6UVJhlJPi+Mz+GCJsioVgezEgPMMEQbUZRo= diff --git a/go.work.sum b/go.work.sum index 72a4ec31fc1..4ad9d238375 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1224,6 +1224,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kevinmbeaulieu/eq-go v1.0.0 h1:AQgYHURDOmnVJ62jnEk0W/7yFKEn+Lv8RHN6t7mB0Zo= github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM= +github.com/keybase/dbus v0.0.0-20220506165403-5aa21ea2c23a h1:K0EAzgzEQHW4Y5lxrmvPMltmlRDzlhLfGmots9EHUTI= +github.com/keybase/dbus v0.0.0-20220506165403-5aa21ea2c23a/go.mod h1:YPNKjjE7Ubp9dTbnWvsP3HT+hYnY6TfXzubYTBeUxc8= github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 h1:veS9QfglfvqAw2e+eeNT/SbGySq8ajECXJ9e4fPoLhY= @@ -1336,6 +1338,7 @@ github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mLPqKctH7Uo//I= @@ -1453,6 +1456,7 @@ github.com/phpdave11/gofpdf v1.4.2 h1:KPKiIbfwbvC/wOncwhrpRdXVj2CZTCFlw4wnoyjtHf github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/sftp v1.13.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs= github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM= @@ -1995,6 +1999,7 @@ golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/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= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index cc2420b2223..7b3aae8a91f 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -18,8 +18,8 @@ require ( ) require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0 // indirect github.com/BurntSushi/toml v1.5.0 // indirect github.com/apache/arrow-go/v18 v18.4.0 // indirect github.com/aws/aws-sdk-go v1.55.7 // indirect @@ -48,7 +48,6 @@ require ( github.com/goccy/go-json v0.10.5 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect github.com/google/gnostic-models v0.7.0 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 3351c0f7097..91592bfd3ba 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -5,14 +5,14 @@ cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIi cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 h1:Wc1ml6QlJs2BHQ/9Bqu1jiyggbsSjramq2oUmp5WeIo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1 h1:B+blDbyVIG3WaikNxPnhPiJ1MThR03b3vKGtER95TP4= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.1/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0 h1:wL5IEG5zb7BVv1Kv0Xm92orq+5hB5Nipn3B5tn4Rqfk= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 h1:XkkQbfMyuH2jTSjQjSoihryI8GINRcs4xp8lNawg0FI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index 7b965f38de3..3de35b9eb01 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -188,6 +188,7 @@ import ( "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginexternal" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugininstaller" service6 "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsso" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins" "github.com/grafana/grafana/pkg/services/pluginsintegration/renderer" @@ -342,7 +343,10 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api hooksService := hooks.ProvideService() ossLicensingService := licensing.ProvideService(cfg, hooksService) licensingService := licensing2.ProvideLicensing(cfg, ossLicensingService) - envVarsProvider := pluginconfig.NewEnvVarsProvider(pluginInstanceCfg, licensingService) + registerer := metrics.ProvideRegisterer() + ssosettingsimplService := ssosettingsimpl.ProvideService(cfg, sqlStore, accessControl, routeRegisterImpl, featureToggles, secretsService, usageStats, registerer, ossImpl, ossLicensingService) + defaultSettingsProvider := pluginsso.ProvideDefaultSettingsProvider(ssosettingsimplService) + envVarsProvider := pluginconfig.NewEnvVarsProvider(pluginInstanceCfg, licensingService, defaultSettingsProvider) inMemory := registry.ProvideService() rendererManager, err := renderer.ProvideService(pluginManagementCfg, envVarsProvider, inMemory, tracingService) if err != nil { @@ -424,7 +428,6 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api } publicDashboardStoreImpl := database3.ProvideStore(sqlStore, cfg, featureToggles) publicDashboardServiceWrapperImpl := service3.ProvideServiceWrapper(publicDashboardStoreImpl) - registerer := metrics.ProvideRegisterer() apikeyService, err := apikeyimpl.ProvideService(sqlStore, cfg, quotaService) if err != nil { return nil, err @@ -559,7 +562,6 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api pluginService := service7.ProvideDashboardPluginService(featureToggles, dashboardServiceImpl) service14 := service8.ProvideService(fileStoreManager, pluginService) orgRoleMapper := connectors.ProvideOrgRoleMapper(cfg, orgService) - ssosettingsimplService := ssosettingsimpl.ProvideService(cfg, sqlStore, accessControl, routeRegisterImpl, featureToggles, secretsService, usageStats, registerer, ossImpl, ossLicensingService) socialService := socialimpl.ProvideService(cfg, featureToggles, usageStats, bundleregistryService, remoteCache, orgRoleMapper, ssosettingsimplService) loginStore := authinfoimpl.ProvideStore(sqlStore, secretsService) authinfoimplService := authinfoimpl.ProvideService(loginStore, remoteCache, secretsService) @@ -599,7 +601,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api return nil, err } datasourcePermissionsService := ossaccesscontrol.ProvideDatasourcePermissionsService(cfg, featureToggles, sqlStore) - requestConfigProvider := pluginconfig.NewRequestConfigProvider(pluginInstanceCfg) + requestConfigProvider := pluginconfig.NewRequestConfigProvider(pluginInstanceCfg, defaultSettingsProvider) baseProvider := plugincontext.ProvideBaseService(cfg, requestConfigProvider) service15, err := service9.ProvideService(sqlStore, secretsService, secretsKVStore, cfg, featureToggles, accessControl, datasourcePermissionsService, quotaService, pluginstoreService, middlewareHandler, baseProvider) if err != nil { @@ -943,7 +945,10 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac hooksService := hooks.ProvideService() ossLicensingService := licensing.ProvideService(cfg, hooksService) licensingService := licensing2.ProvideLicensing(cfg, ossLicensingService) - envVarsProvider := pluginconfig.NewEnvVarsProvider(pluginInstanceCfg, licensingService) + registerer := metrics.ProvideRegistererForTest() + ssosettingsimplService := ssosettingsimpl.ProvideService(cfg, sqlStore, accessControl, routeRegisterImpl, featureToggles, secretsService, usageStats, registerer, ossImpl, ossLicensingService) + defaultSettingsProvider := pluginsso.ProvideDefaultSettingsProvider(ssosettingsimplService) + envVarsProvider := pluginconfig.NewEnvVarsProvider(pluginInstanceCfg, licensingService, defaultSettingsProvider) inMemory := registry.ProvideService() rendererManager, err := renderer.ProvideService(pluginManagementCfg, envVarsProvider, inMemory, tracingService) if err != nil { @@ -1025,7 +1030,6 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac } publicDashboardStoreImpl := database3.ProvideStore(sqlStore, cfg, featureToggles) publicDashboardServiceWrapperImpl := service3.ProvideServiceWrapper(publicDashboardStoreImpl) - registerer := metrics.ProvideRegistererForTest() apikeyService, err := apikeyimpl.ProvideService(sqlStore, cfg, quotaService) if err != nil { return nil, err @@ -1195,7 +1199,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac return nil, err } datasourcePermissionsService := ossaccesscontrol.ProvideDatasourcePermissionsService(cfg, featureToggles, sqlStore) - requestConfigProvider := pluginconfig.NewRequestConfigProvider(pluginInstanceCfg) + requestConfigProvider := pluginconfig.NewRequestConfigProvider(pluginInstanceCfg, defaultSettingsProvider) baseProvider := plugincontext.ProvideBaseService(cfg, requestConfigProvider) service15, err := service9.ProvideService(sqlStore, secretsService, secretsKVStore, cfg, featureToggles, accessControl, datasourcePermissionsService, quotaService, pluginstoreService, middlewareHandler, baseProvider) if err != nil { @@ -1227,7 +1231,6 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac return nil, err } orgRoleMapper := connectors.ProvideOrgRoleMapper(cfg, orgService) - ssosettingsimplService := ssosettingsimpl.ProvideService(cfg, sqlStore, accessControl, routeRegisterImpl, featureToggles, secretsService, usageStats, registerer, ossImpl, ossLicensingService) socialService := socialimpl.ProvideService(cfg, featureToggles, usageStats, bundleregistryService, remoteCache, orgRoleMapper, ssosettingsimplService) loginStore := authinfoimpl.ProvideStore(sqlStore, secretsService) authinfoimplService := authinfoimpl.ProvideService(loginStore, remoteCache, secretsService) diff --git a/pkg/services/pluginsintegration/pluginconfig/azure_settings.go b/pkg/services/pluginsintegration/pluginconfig/azure_settings.go new file mode 100644 index 00000000000..5d0357d2a8f --- /dev/null +++ b/pkg/services/pluginsintegration/pluginconfig/azure_settings.go @@ -0,0 +1,66 @@ +package pluginconfig + +import ( + "github.com/grafana/grafana-azure-sdk-go/v2/azsettings" + + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsso" +) + +// mergeAzureSettings merges the Azure AD settings from the SSO settings DB with the Azure AD settings from the config. +// Azure AD settings can be changed via the UI or SSO settings API +// They can also be overridden in the [azure] config section +// The order of precedence is: +// 1. [azure] config section (if the override flag is set) +// 2. SSO settings from the DB (if they exist) +// 3. [auth.azuread] config section (if enabled) +func mergeAzureSettings(currSettings *azsettings.AzureSettings, azureAdSettings *pluginsso.Settings) *azsettings.AzureSettings { + if azureAdSettings != nil { + settings := azureAdSettings.Values + tokenEndpointSettings := currSettings.UserIdentityTokenEndpoint + if tokenEndpointSettings == nil { + tokenEndpointSettings = &azsettings.TokenEndpointSettings{} + currSettings.UserIdentityTokenEndpoint = tokenEndpointSettings + } + + tokenUrl, ok := settings["token_url"].(string) + if ok { + if !currSettings.UserIdentityTokenEndpoint.TokenUrlOverride { + currSettings.UserIdentityTokenEndpoint.TokenUrl = tokenUrl + } + } + clientAuth, ok := settings["client_authentication"].(string) + if ok { + if !currSettings.UserIdentityTokenEndpoint.ClientAuthenticationOverride { + if clientAuth != "none" { + currSettings.UserIdentityTokenEndpoint.ClientAuthentication = clientAuth + } + } + } + clientId, ok := settings["client_id"].(string) + if ok { + if !currSettings.UserIdentityTokenEndpoint.ClientIdOverride { + currSettings.UserIdentityTokenEndpoint.ClientId = clientId + } + } + clientSecret, ok := settings["client_secret"].(string) + if ok { + if !currSettings.UserIdentityTokenEndpoint.ClientSecretOverride { + currSettings.UserIdentityTokenEndpoint.ClientSecret = clientSecret + } + } + managedIdentityClientId, ok := settings["managed_identity_client_id"].(string) + if ok { + if !currSettings.UserIdentityTokenEndpoint.ManagedIdentityClientIdOverride { + currSettings.UserIdentityTokenEndpoint.ManagedIdentityClientId = managedIdentityClientId + } + } + federatedCredentialAudience, ok := settings["federated_credential_audience"].(string) + if ok { + if !currSettings.UserIdentityTokenEndpoint.FederatedCredentialAudienceOverride { + currSettings.UserIdentityTokenEndpoint.FederatedCredentialAudience = federatedCredentialAudience + } + } + } + + return currSettings +} diff --git a/pkg/services/pluginsintegration/pluginconfig/azure_settings_test.go b/pkg/services/pluginsintegration/pluginconfig/azure_settings_test.go new file mode 100644 index 00000000000..357bb37c491 --- /dev/null +++ b/pkg/services/pluginsintegration/pluginconfig/azure_settings_test.go @@ -0,0 +1,197 @@ +package pluginconfig + +import ( + "testing" + + "github.com/grafana/grafana-azure-sdk-go/v2/azsettings" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsso" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetAzureSettings(t *testing.T) { + t.Run("no Azure settings input", func(t *testing.T) { + result := mergeAzureSettings(nil, nil) + + assert.Nil(t, result) + }) + + t.Run("no SSO settings or override settings", func(t *testing.T) { + currSettings := &azsettings.AzureSettings{ + UserIdentityTokenEndpoint: &azsettings.TokenEndpointSettings{ + TokenUrl: "original-token-url", + ClientAuthentication: "original-auth", + ClientId: "original-client-id", + ClientSecret: "original-client-secret", + }, + } + + azureAdSettings := &pluginsso.Settings{ + Values: map[string]any{}, + } + + result := mergeAzureSettings(currSettings, azureAdSettings) + + assert.Equal(t, "original-token-url", result.UserIdentityTokenEndpoint.TokenUrl) + assert.Equal(t, "original-auth", result.UserIdentityTokenEndpoint.ClientAuthentication) + assert.Equal(t, "original-client-id", result.UserIdentityTokenEndpoint.ClientId) + assert.Equal(t, "original-client-secret", result.UserIdentityTokenEndpoint.ClientSecret) + }) + + t.Run("with SSO settings but no overrides", func(t *testing.T) { + currSettings := &azsettings.AzureSettings{ + UserIdentityTokenEndpoint: &azsettings.TokenEndpointSettings{ + TokenUrl: "original-token-url", + ClientAuthentication: "original-auth", + ClientId: "original-client-id", + ClientSecret: "original-client-secret", + ManagedIdentityClientId: "original-managed-id", + FederatedCredentialAudience: "original-audience", + TokenUrlOverride: false, + ClientAuthenticationOverride: false, + ClientIdOverride: false, + ClientSecretOverride: false, + ManagedIdentityClientIdOverride: false, + FederatedCredentialAudienceOverride: false, + }, + } + + azureAdSettings := &pluginsso.Settings{ + Values: map[string]any{ + "token_url": "sso-token-url", + "client_authentication": "sso-auth", + "client_id": "sso-client-id", + "client_secret": "sso-client-secret", + "managed_identity_client_id": "sso-managed-id", + "federated_credential_audience": "sso-audience", + }, + } + + result := mergeAzureSettings(currSettings, azureAdSettings) + + assert.Equal(t, "sso-token-url", result.UserIdentityTokenEndpoint.TokenUrl) + assert.Equal(t, "sso-auth", result.UserIdentityTokenEndpoint.ClientAuthentication) + assert.Equal(t, "sso-client-id", result.UserIdentityTokenEndpoint.ClientId) + assert.Equal(t, "sso-client-secret", result.UserIdentityTokenEndpoint.ClientSecret) + assert.Equal(t, "sso-managed-id", result.UserIdentityTokenEndpoint.ManagedIdentityClientId) + assert.Equal(t, "sso-audience", result.UserIdentityTokenEndpoint.FederatedCredentialAudience) + }) + + t.Run("with both overrides and SSO settings", func(t *testing.T) { + currSettings := &azsettings.AzureSettings{ + UserIdentityTokenEndpoint: &azsettings.TokenEndpointSettings{ + TokenUrl: "override-token-url", + ClientAuthentication: "override-auth", + ClientId: "override-client-id", + ClientSecret: "override-client-secret", + ManagedIdentityClientId: "override-managed-id", + FederatedCredentialAudience: "override-audience", + TokenUrlOverride: true, + ClientAuthenticationOverride: true, + ClientIdOverride: true, + ClientSecretOverride: true, + ManagedIdentityClientIdOverride: true, + FederatedCredentialAudienceOverride: true, + }, + } + + azureAdSettings := &pluginsso.Settings{ + Values: map[string]any{ + "token_url": "sso-token-url", + "client_authentication": "sso-auth", + "client_id": "sso-client-id", + "client_secret": "sso-client-secret", + "managed_identity_client_id": "sso-managed-id", + "federated_credential_audience": "sso-audience", + }, + } + + result := mergeAzureSettings(currSettings, azureAdSettings) + + // Should keep override values, not SSO values + assert.Equal(t, "override-token-url", result.UserIdentityTokenEndpoint.TokenUrl) + assert.Equal(t, "override-auth", result.UserIdentityTokenEndpoint.ClientAuthentication) + assert.Equal(t, "override-client-id", result.UserIdentityTokenEndpoint.ClientId) + assert.Equal(t, "override-client-secret", result.UserIdentityTokenEndpoint.ClientSecret) + assert.Equal(t, "override-managed-id", result.UserIdentityTokenEndpoint.ManagedIdentityClientId) + assert.Equal(t, "override-audience", result.UserIdentityTokenEndpoint.FederatedCredentialAudience) + }) + + t.Run("client authentication 'none' should be ignored", func(t *testing.T) { + currSettings := &azsettings.AzureSettings{ + UserIdentityTokenEndpoint: &azsettings.TokenEndpointSettings{ + ClientAuthentication: "original-auth", + ClientAuthenticationOverride: false, + }, + } + + azureAdSettings := &pluginsso.Settings{ + Values: map[string]any{ + "client_authentication": "none", + }, + } + + result := mergeAzureSettings(currSettings, azureAdSettings) + + assert.Equal(t, "original-auth", result.UserIdentityTokenEndpoint.ClientAuthentication) + }) + + t.Run("non-string values should be ignored", func(t *testing.T) { + currSettings := &azsettings.AzureSettings{ + UserIdentityTokenEndpoint: &azsettings.TokenEndpointSettings{ + TokenUrl: "original-token-url", + ClientId: "original-client-id", + TokenUrlOverride: false, + ClientIdOverride: false, + }, + } + + azureAdSettings := &pluginsso.Settings{ + Values: map[string]any{ + "token_url": 12345, + "client_id": []string{"array", "value"}, + }, + } + + result := mergeAzureSettings(currSettings, azureAdSettings) + + assert.Equal(t, "original-token-url", result.UserIdentityTokenEndpoint.TokenUrl) + assert.Equal(t, "original-client-id", result.UserIdentityTokenEndpoint.ClientId) + }) + + t.Run("Nil UserIdentityTokenEndpoint should not panic", func(t *testing.T) { + currSettings := &azsettings.AzureSettings{ + UserIdentityTokenEndpoint: nil, + } + + azureAdSettings := &pluginsso.Settings{ + Values: map[string]any{ + "token_url": "sso-token-url", + }, + } + + require.NotPanics(t, func() { + mergeAzureSettings(currSettings, azureAdSettings) + }) + }) + + t.Run("Empty SSO settings map", func(t *testing.T) { + currSettings := &azsettings.AzureSettings{ + UserIdentityTokenEndpoint: &azsettings.TokenEndpointSettings{ + TokenUrl: "original-token-url", + ClientAuthentication: "original-auth", + ClientId: "original-client-id", + }, + } + + azureAdSettings := &pluginsso.Settings{ + Values: map[string]any{}, + } + + result := mergeAzureSettings(currSettings, azureAdSettings) + + assert.Equal(t, "original-token-url", result.UserIdentityTokenEndpoint.TokenUrl) + assert.Equal(t, "original-auth", result.UserIdentityTokenEndpoint.ClientAuthentication) + assert.Equal(t, "original-client-id", result.UserIdentityTokenEndpoint.ClientId) + }) +} diff --git a/pkg/services/pluginsintegration/pluginconfig/envvars.go b/pkg/services/pluginsintegration/pluginconfig/envvars.go index e29a75f6ec7..964115383f4 100644 --- a/pkg/services/pluginsintegration/pluginconfig/envvars.go +++ b/pkg/services/pluginsintegration/pluginconfig/envvars.go @@ -13,23 +13,27 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/proxy" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/envvars" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsso" ) var _ envvars.Provider = (*EnvVarsProvider)(nil) type EnvVarsProvider struct { - cfg *PluginInstanceCfg - license plugins.Licensing - logger log.Logger + cfg *PluginInstanceCfg + license plugins.Licensing + logger log.Logger + ssoSettings pluginsso.SettingsProvider } -func NewEnvVarsProvider(cfg *PluginInstanceCfg, license plugins.Licensing) *EnvVarsProvider { +func NewEnvVarsProvider(cfg *PluginInstanceCfg, license plugins.Licensing, ssoSettings pluginsso.SettingsProvider) *EnvVarsProvider { return &EnvVarsProvider{ - cfg: cfg, - license: license, - logger: log.New("plugins.envvars"), + cfg: cfg, + license: license, + logger: log.New("plugins.envvars"), + ssoSettings: ssoSettings, } } @@ -63,7 +67,7 @@ func (p *EnvVarsProvider) PluginEnvVars(ctx context.Context, plugin *plugins.Plu hostEnv = append(hostEnv, p.featureToggleEnableVars(ctx)...) hostEnv = append(hostEnv, p.awsEnvVars(plugin.PluginID())...) hostEnv = append(hostEnv, p.secureSocksProxyEnvVars()...) - hostEnv = append(hostEnv, azsettings.WriteToEnvStr(p.cfg.Azure)...) + hostEnv = append(hostEnv, azsettings.WriteToEnvStr(p.getAzureSettings())...) hostEnv = append(hostEnv, p.tracingEnvVars(plugin)...) hostEnv = append(hostEnv, p.pluginSettingsEnvVars(plugin.PluginID())...) @@ -189,3 +193,15 @@ func (p *EnvVarsProvider) envVar(key, value string) string { } return fmt.Sprintf("%s=%s", key, value) } + +func (p *EnvVarsProvider) getAzureSettings() *azsettings.AzureSettings { + azureSettings := p.cfg.Azure + if azureSettings == nil { + azureSettings = &azsettings.AzureSettings{} + } + azureAdSettings, err := p.ssoSettings.GetForProvider(context.Background(), social.AzureADProviderName) + if err != nil { + p.logger.Error("Failed to get SSO settings", "error", err) + } + return mergeAzureSettings(azureSettings, azureAdSettings) +} diff --git a/pkg/services/pluginsintegration/pluginconfig/envvars_test.go b/pkg/services/pluginsintegration/pluginconfig/envvars_test.go index e18809d17ad..27e9c419570 100644 --- a/pkg/services/pluginsintegration/pluginconfig/envvars_test.go +++ b/pkg/services/pluginsintegration/pluginconfig/envvars_test.go @@ -46,7 +46,7 @@ func TestPluginEnvVarsProvider_PluginEnvVars(t *testing.T) { Features: featuremgmt.WithFeatures(), } - provider := NewEnvVarsProvider(cfg, licensing) + provider := NewEnvVarsProvider(cfg, licensing, &fakeSSOSettingsProvider{}) envVars := provider.PluginEnvVars(context.Background(), p) assert.Len(t, envVars, 6) assert.Equal(t, "GF_VERSION=", envVars[0]) @@ -77,7 +77,7 @@ func TestPluginEnvVarsProvider_skipHostEnvVars(t *testing.T) { pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - provider := NewEnvVarsProvider(pCfg, nil) + provider := NewEnvVarsProvider(pCfg, nil, &fakeSSOSettingsProvider{}) envVars := provider.PluginEnvVars(context.Background(), p) // We want to test that the envvars.Provider does not add any of the host env vars. @@ -93,7 +93,7 @@ func TestPluginEnvVarsProvider_skipHostEnvVars(t *testing.T) { cfg := setting.NewCfg() pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - provider := NewEnvVarsProvider(pCfg, nil) + provider := NewEnvVarsProvider(pCfg, nil, &fakeSSOSettingsProvider{}) t.Run("should populate allowed host env vars", func(t *testing.T) { // Set all allowed variables @@ -418,7 +418,7 @@ func TestPluginEnvVarsProvider_tracingEnvironmentVariables(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - p := NewEnvVarsProvider(tc.cfg, nil) + p := NewEnvVarsProvider(tc.cfg, nil, &fakeSSOSettingsProvider{}) envVars := p.PluginEnvVars(context.Background(), tc.plugin) tc.exp(t, envVars) }) @@ -473,7 +473,7 @@ func TestPluginEnvVarsProvider_authEnvVars(t *testing.T) { pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - provider := NewEnvVarsProvider(pCfg, nil) + provider := NewEnvVarsProvider(pCfg, nil, &fakeSSOSettingsProvider{}) envVars := provider.PluginEnvVars(context.Background(), p) assert.Equal(t, "GF_VERSION=", envVars[0]) assert.Equal(t, "GF_APP_URL=https://myorg.com/", envVars[1]) @@ -521,7 +521,7 @@ func TestPluginEnvVarsProvider_awsEnvVars(t *testing.T) { Features: featuremgmt.WithFeatures(), } - provider := NewEnvVarsProvider(cfg, nil) + provider := NewEnvVarsProvider(cfg, nil, &fakeSSOSettingsProvider{}) envVars := provider.PluginEnvVars(context.Background(), p) assert.ElementsMatch(t, tc.expected, envVars) } @@ -540,7 +540,7 @@ func TestPluginEnvVarsProvider_featureToggleEnvVar(t *testing.T) { Features: featuremgmt.WithFeatures(expectedFeatures[0], true, expectedFeatures[1], true), } - p := NewEnvVarsProvider(cfg, nil) + p := NewEnvVarsProvider(cfg, nil, &fakeSSOSettingsProvider{}) envVars := p.PluginEnvVars(context.Background(), &plugins.Plugin{}) assert.Equal(t, 2, len(envVars)) @@ -591,7 +591,7 @@ func TestPluginEnvVarsProvider_azureEnvVars(t *testing.T) { pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - provider := NewEnvVarsProvider(pCfg, nil) + provider := NewEnvVarsProvider(pCfg, nil, &fakeSSOSettingsProvider{}) envVars := provider.PluginEnvVars(context.Background(), &plugins.Plugin{}) assert.ElementsMatch(t, []string{"GF_VERSION=", "GFAZPL_AZURE_CLOUD=AzureCloud", "GFAZPL_AZURE_AUTH_ENABLED=true", "GFAZPL_MANAGED_IDENTITY_ENABLED=true", @@ -608,4 +608,113 @@ func TestPluginEnvVarsProvider_azureEnvVars(t *testing.T) { "GFAZPL_USER_IDENTITY_ASSERTION=username", }, envVars) }) + + t.Run("sets user token endpoint settings from SSO settings for an Azure plugin", func(t *testing.T) { + cfg := &setting.Cfg{ + Raw: ini.Empty(), + Azure: &azsettings.AzureSettings{ + AzureAuthEnabled: true, + Cloud: azsettings.AzurePublic, + ManagedIdentityEnabled: true, + ManagedIdentityClientId: "mock_managed_identity_client_id", + WorkloadIdentityEnabled: true, + WorkloadIdentitySettings: &azsettings.WorkloadIdentitySettings{ + TenantId: "mock_workload_identity_tenant_id", + ClientId: "mock_workload_identity_client_id", + TokenFile: "mock_workload_identity_token_file", + }, + UserIdentityEnabled: true, + UserIdentityFallbackCredentialsEnabled: true, + UserIdentityTokenEndpoint: &azsettings.TokenEndpointSettings{ + TokenUrl: "mock_user_identity_token_url", + ClientId: "mock_user_identity_client_id", + ClientSecret: "mock_user_identity_client_secret", + UsernameAssertion: true, + }, + }, + } + + pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) + require.NoError(t, err) + + provider := NewEnvVarsProvider(pCfg, nil, &fakeSSOSettingsProvider{ + GetForProviderFunc: getAzureSSOSettings, + }) + envVars := provider.PluginEnvVars(context.Background(), &plugins.Plugin{}) + assert.ElementsMatch(t, []string{"GF_VERSION=", "GFAZPL_AZURE_CLOUD=AzureCloud", "GFAZPL_AZURE_AUTH_ENABLED=true", + "GFAZPL_MANAGED_IDENTITY_ENABLED=true", + "GFAZPL_MANAGED_IDENTITY_CLIENT_ID=mock_managed_identity_client_id", + "GFAZPL_WORKLOAD_IDENTITY_ENABLED=true", + "GFAZPL_WORKLOAD_IDENTITY_TENANT_ID=mock_workload_identity_tenant_id", + "GFAZPL_WORKLOAD_IDENTITY_CLIENT_ID=mock_workload_identity_client_id", + "GFAZPL_WORKLOAD_IDENTITY_TOKEN_FILE=mock_workload_identity_token_file", + "GFAZPL_USER_IDENTITY_ENABLED=true", + "GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED=true", + "GFAZPL_USER_IDENTITY_TOKEN_URL=sso_user_identity_token_url", + "GFAZPL_USER_IDENTITY_CLIENT_AUTHENTICATION=sso_user_client_authentication", + "GFAZPL_USER_IDENTITY_CLIENT_ID=sso_user_identity_client_id", + "GFAZPL_USER_IDENTITY_CLIENT_SECRET=sso_user_identity_client_secret", + "GFAZPL_USER_IDENTITY_MANAGED_IDENTITY_CLIENT_ID=sso_user_identity_managed_identity_client_id", + "GFAZPL_USER_IDENTITY_FEDERATED_CREDENTIAL_AUDIENCE=sso_user_identity_federated_credential_audience", + "GFAZPL_USER_IDENTITY_ASSERTION=username", + }, envVars) + }) + + t.Run("does not use SSO settings if overrides have been set for an Azure plugin", func(t *testing.T) { + cfg := &setting.Cfg{ + Raw: ini.Empty(), + Azure: &azsettings.AzureSettings{ + AzureAuthEnabled: true, + Cloud: azsettings.AzurePublic, + ManagedIdentityEnabled: true, + ManagedIdentityClientId: "mock_managed_identity_client_id", + WorkloadIdentityEnabled: true, + WorkloadIdentitySettings: &azsettings.WorkloadIdentitySettings{ + TenantId: "mock_workload_identity_tenant_id", + ClientId: "mock_workload_identity_client_id", + TokenFile: "mock_workload_identity_token_file", + }, + UserIdentityEnabled: true, + UserIdentityFallbackCredentialsEnabled: true, + UserIdentityTokenEndpoint: &azsettings.TokenEndpointSettings{ + TokenUrl: "override_user_identity_token_url", + TokenUrlOverride: true, + ClientAuthentication: "override_user_client_authentication", + ClientAuthenticationOverride: true, + ClientId: "override_user_identity_client_id", + ClientIdOverride: true, + ClientSecret: "override_user_identity_client_secret", + ClientSecretOverride: true, + ManagedIdentityClientId: "override_user_identity_managed_identity_client_id", + ManagedIdentityClientIdOverride: true, + FederatedCredentialAudience: "override_user_identity_federated_credential_audience", + FederatedCredentialAudienceOverride: true, + }, + }, + } + + pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) + require.NoError(t, err) + + provider := NewEnvVarsProvider(pCfg, nil, &fakeSSOSettingsProvider{ + GetForProviderFunc: getAzureSSOSettings, + }) + envVars := provider.PluginEnvVars(context.Background(), &plugins.Plugin{}) + assert.ElementsMatch(t, []string{"GF_VERSION=", "GFAZPL_AZURE_CLOUD=AzureCloud", "GFAZPL_AZURE_AUTH_ENABLED=true", + "GFAZPL_MANAGED_IDENTITY_ENABLED=true", + "GFAZPL_MANAGED_IDENTITY_CLIENT_ID=mock_managed_identity_client_id", + "GFAZPL_WORKLOAD_IDENTITY_ENABLED=true", + "GFAZPL_WORKLOAD_IDENTITY_TENANT_ID=mock_workload_identity_tenant_id", + "GFAZPL_WORKLOAD_IDENTITY_CLIENT_ID=mock_workload_identity_client_id", + "GFAZPL_WORKLOAD_IDENTITY_TOKEN_FILE=mock_workload_identity_token_file", + "GFAZPL_USER_IDENTITY_ENABLED=true", + "GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED=true", + "GFAZPL_USER_IDENTITY_TOKEN_URL=override_user_identity_token_url", + "GFAZPL_USER_IDENTITY_CLIENT_AUTHENTICATION=override_user_client_authentication", + "GFAZPL_USER_IDENTITY_CLIENT_ID=override_user_identity_client_id", + "GFAZPL_USER_IDENTITY_CLIENT_SECRET=override_user_identity_client_secret", + "GFAZPL_USER_IDENTITY_MANAGED_IDENTITY_CLIENT_ID=override_user_identity_managed_identity_client_id", + "GFAZPL_USER_IDENTITY_FEDERATED_CREDENTIAL_AUDIENCE=override_user_identity_federated_credential_audience", + }, envVars) + }) } diff --git a/pkg/services/pluginsintegration/pluginconfig/request.go b/pkg/services/pluginsintegration/pluginconfig/request.go index 5065caac36e..a6861c7acec 100644 --- a/pkg/services/pluginsintegration/pluginconfig/request.go +++ b/pkg/services/pluginsintegration/pluginconfig/request.go @@ -9,11 +9,14 @@ import ( "github.com/grafana/grafana-aws-sdk/pkg/awsds" "github.com/grafana/grafana-azure-sdk-go/v2/azsettings" - "github.com/grafana/grafana/pkg/plugins/auth" - "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/proxy" "github.com/grafana/grafana-plugin-sdk-go/experimental/featuretoggles" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/login/social" + "github.com/grafana/grafana/pkg/plugins/auth" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsso" ) var _ PluginRequestConfigProvider = (*RequestConfigProvider)(nil) @@ -23,12 +26,16 @@ type PluginRequestConfigProvider interface { } type RequestConfigProvider struct { - cfg *PluginInstanceCfg + cfg *PluginInstanceCfg + ssoSettings pluginsso.SettingsProvider + logger log.Logger } -func NewRequestConfigProvider(cfg *PluginInstanceCfg) *RequestConfigProvider { +func NewRequestConfigProvider(cfg *PluginInstanceCfg, ssoSettings pluginsso.SettingsProvider) *RequestConfigProvider { return &RequestConfigProvider{ - cfg: cfg, + cfg: cfg, + ssoSettings: ssoSettings, + logger: log.New("pluginrequestconfig"), } } @@ -89,8 +96,19 @@ func (s *RequestConfigProvider) PluginRequestConfig(ctx context.Context, pluginI if s.cfg.AzureAuthEnabled { m[azsettings.AzureAuthEnabled] = strconv.FormatBool(s.cfg.AzureAuthEnabled) } + azureSettings := s.cfg.Azure - if azureSettings != nil && slices.Contains[[]string, string](azureSettings.ForwardSettingsPlugins, pluginID) { + if azureSettings == nil { + azureSettings = &azsettings.AzureSettings{} + } + + if slices.Contains(azureSettings.ForwardSettingsPlugins, pluginID) { + azureAdSettings, err := s.ssoSettings.GetForProvider(ctx, social.AzureADProviderName) + if err != nil { + s.logger.Error("Failed to get SSO settings", "error", err) + } + azureSettings = mergeAzureSettings(azureSettings, azureAdSettings) + if azureSettings.Cloud != "" { m[azsettings.AzureCloud] = azureSettings.Cloud } diff --git a/pkg/services/pluginsintegration/pluginconfig/request_test.go b/pkg/services/pluginsintegration/pluginconfig/request_test.go index 8153c24f10a..d9720045e01 100644 --- a/pkg/services/pluginsintegration/pluginconfig/request_test.go +++ b/pkg/services/pluginsintegration/pluginconfig/request_test.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/auth" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsso" "github.com/grafana/grafana/pkg/setting" ) @@ -19,7 +20,7 @@ func TestRequestConfigProvider_PluginRequestConfig_Defaults(t *testing.T) { pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - p := NewRequestConfigProvider(pCfg) + p := NewRequestConfigProvider(pCfg, &fakeSSOSettingsProvider{}) require.Equal(t, map[string]string{ "GF_SQL_MAX_OPEN_CONNS_DEFAULT": "0", "GF_SQL_MAX_IDLE_CONNS_DEFAULT": "0", @@ -133,7 +134,7 @@ func TestRequestConfigProvider_PluginRequestConfig(t *testing.T) { } for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - p := NewRequestConfigProvider(tc.cfg) + p := NewRequestConfigProvider(tc.cfg, &fakeSSOSettingsProvider{}) require.Subset(t, p.PluginRequestConfig(context.Background(), "", nil), tc.expected) }) } @@ -168,7 +169,7 @@ func TestRequestConfigProvider_PluginRequestConfig_featureToggles(t *testing.T) pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), tc.features) require.NoError(t, err) - p := NewRequestConfigProvider(pCfg) + p := NewRequestConfigProvider(pCfg, &fakeSSOSettingsProvider{}) require.Subset(t, p.PluginRequestConfig(context.Background(), "", nil), tc.expectedConfig) } }) @@ -182,7 +183,7 @@ func TestRequestConfigProvider_PluginRequestConfig_appURL(t *testing.T) { pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - p := NewRequestConfigProvider(pCfg) + p := NewRequestConfigProvider(pCfg, &fakeSSOSettingsProvider{}) require.Subset(t, p.PluginRequestConfig(context.Background(), "", nil), map[string]string{"GF_APP_URL": "https://myorg.com/"}) }) } @@ -198,7 +199,7 @@ func TestRequestConfigProvider_PluginRequestConfig_SQL(t *testing.T) { pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - p := NewRequestConfigProvider(pCfg) + p := NewRequestConfigProvider(pCfg, &fakeSSOSettingsProvider{}) require.Subset(t, p.PluginRequestConfig(context.Background(), "", nil), map[string]string{ "GF_SQL_ROW_LIMIT": "23", "GF_SQL_MAX_OPEN_CONNS_DEFAULT": "24", @@ -216,7 +217,7 @@ func TestRequestConfigProvider_PluginRequestConfig_SQL(t *testing.T) { pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - p := NewRequestConfigProvider(pCfg) + p := NewRequestConfigProvider(pCfg, &fakeSSOSettingsProvider{}) require.Equal(t, map[string]string{ "GF_SQL_MAX_OPEN_CONNS_DEFAULT": "0", "GF_SQL_MAX_IDLE_CONNS_DEFAULT": "0", @@ -233,7 +234,7 @@ func TestRequestConfigProvider_PluginRequestConfig_concurrentQueryCount(t *testi pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - p := NewRequestConfigProvider(pCfg) + p := NewRequestConfigProvider(pCfg, &fakeSSOSettingsProvider{}) require.Subset(t, p.PluginRequestConfig(context.Background(), "", nil), map[string]string{"GF_CONCURRENT_QUERY_COUNT": "42"}) }) @@ -242,7 +243,7 @@ func TestRequestConfigProvider_PluginRequestConfig_concurrentQueryCount(t *testi pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - p := NewRequestConfigProvider(pCfg) + p := NewRequestConfigProvider(pCfg, &fakeSSOSettingsProvider{}) require.NotContains(t, p.PluginRequestConfig(context.Background(), "", nil), "GF_CONCURRENT_QUERY_COUNT") }) @@ -253,7 +254,7 @@ func TestRequestConfigProvider_PluginRequestConfig_concurrentQueryCount(t *testi pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - p := NewRequestConfigProvider(pCfg) + p := NewRequestConfigProvider(pCfg, &fakeSSOSettingsProvider{}) require.NotContains(t, p.PluginRequestConfig(context.Background(), "", nil), "GF_CONCURRENT_QUERY_COUNT") }) } @@ -265,7 +266,7 @@ func TestRequestConfigProvider_PluginRequestConfig_azureAuthEnabled(t *testing.T Features: featuremgmt.WithFeatures(), } - p := NewRequestConfigProvider(cfg) + p := NewRequestConfigProvider(cfg, &fakeSSOSettingsProvider{}) require.Subset(t, p.PluginRequestConfig(context.Background(), "", nil), map[string]string{"GFAZPL_AZURE_AUTH_ENABLED": "true"}) }) @@ -274,7 +275,7 @@ func TestRequestConfigProvider_PluginRequestConfig_azureAuthEnabled(t *testing.T Features: featuremgmt.WithFeatures(), } - p := NewRequestConfigProvider(cfg) + p := NewRequestConfigProvider(cfg, &fakeSSOSettingsProvider{}) require.NotContains(t, p.PluginRequestConfig(context.Background(), "", nil), "GFAZPL_AZURE_AUTH_ENABLED") }) @@ -284,7 +285,7 @@ func TestRequestConfigProvider_PluginRequestConfig_azureAuthEnabled(t *testing.T Features: featuremgmt.WithFeatures(), } - p := NewRequestConfigProvider(cfg) + p := NewRequestConfigProvider(cfg, &fakeSSOSettingsProvider{}) require.NotContains(t, p.PluginRequestConfig(context.Background(), "", nil), "GFAZPL_AZURE_AUTH_ENABLED") }) } @@ -322,7 +323,7 @@ func TestRequestConfigProvider_PluginRequestConfig_azure(t *testing.T) { pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - p := NewRequestConfigProvider(pCfg) + p := NewRequestConfigProvider(pCfg, &fakeSSOSettingsProvider{}) require.Subset(t, p.PluginRequestConfig(context.Background(), "grafana-azure-monitor-datasource", nil), map[string]string{ "GFAZPL_AZURE_CLOUD": "AzureCloud", "GFAZPL_MANAGED_IDENTITY_ENABLED": "true", "GFAZPL_MANAGED_IDENTITY_CLIENT_ID": "mock_managed_identity_client_id", @@ -356,7 +357,7 @@ func TestRequestConfigProvider_PluginRequestConfig_azure(t *testing.T) { pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - p := NewRequestConfigProvider(pCfg) + p := NewRequestConfigProvider(pCfg, &fakeSSOSettingsProvider{}) require.Subset(t, p.PluginRequestConfig(context.Background(), "grafana-azure-monitor-datasource", nil), map[string]string{ "GFAZPL_AZURE_CLOUD": "AzureCloud", "GFAZPL_MANAGED_IDENTITY_ENABLED": "true", "GFAZPL_MANAGED_IDENTITY_CLIENT_ID": "mock_managed_identity_client_id", @@ -384,7 +385,7 @@ func TestRequestConfigProvider_PluginRequestConfig_azure(t *testing.T) { pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - p := NewRequestConfigProvider(pCfg) + p := NewRequestConfigProvider(pCfg, &fakeSSOSettingsProvider{}) m := p.PluginRequestConfig(context.Background(), "", nil) require.NotContains(t, m, "GFAZPL_AZURE_CLOUD") require.NotContains(t, m, "GFAZPL_MANAGED_IDENTITY_ENABLED") @@ -413,7 +414,7 @@ func TestRequestConfigProvider_PluginRequestConfig_azure(t *testing.T) { pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - p := NewRequestConfigProvider(pCfg) + p := NewRequestConfigProvider(pCfg, &fakeSSOSettingsProvider{}) require.Subset(t, p.PluginRequestConfig(context.Background(), "test-datasource", nil), map[string]string{ "GFAZPL_AZURE_CLOUD": "AzureCloud", "GFAZPL_MANAGED_IDENTITY_ENABLED": "true", "GFAZPL_MANAGED_IDENTITY_CLIENT_ID": "mock_managed_identity_client_id", @@ -433,6 +434,108 @@ func TestRequestConfigProvider_PluginRequestConfig_azure(t *testing.T) { "GFAZPL_AZURE_ENTRA_PASSWORD_CREDENTIALS_ENABLED": "true", }) }) + + t.Run("sets user token endpoint settings from SSO settings for an Azure plugin", func(t *testing.T) { + cfg := setting.NewCfg() + cfg.Azure = azSettings + + pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) + require.NoError(t, err) + + p := NewRequestConfigProvider(pCfg, &fakeSSOSettingsProvider{ + GetForProviderFunc: getAzureSSOSettings, + }) + require.Subset(t, p.PluginRequestConfig(context.Background(), "grafana-azure-monitor-datasource", nil), map[string]string{ + "GFAZPL_AZURE_CLOUD": "AzureCloud", "GFAZPL_MANAGED_IDENTITY_ENABLED": "true", + "GFAZPL_MANAGED_IDENTITY_CLIENT_ID": "mock_managed_identity_client_id", + "GFAZPL_WORKLOAD_IDENTITY_ENABLED": "true", + "GFAZPL_WORKLOAD_IDENTITY_TENANT_ID": "mock_workload_identity_tenant_id", + "GFAZPL_WORKLOAD_IDENTITY_CLIENT_ID": "mock_workload_identity_client_id", + "GFAZPL_WORKLOAD_IDENTITY_TOKEN_FILE": "mock_workload_identity_token_file", + "GFAZPL_USER_IDENTITY_ENABLED": "true", + "GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED": "true", + "GFAZPL_USER_IDENTITY_TOKEN_URL": "sso_user_identity_token_url", + "GFAZPL_USER_IDENTITY_CLIENT_AUTHENTICATION": "sso_user_client_authentication", + "GFAZPL_USER_IDENTITY_CLIENT_ID": "sso_user_identity_client_id", + "GFAZPL_USER_IDENTITY_CLIENT_SECRET": "sso_user_identity_client_secret", + "GFAZPL_USER_IDENTITY_MANAGED_IDENTITY_CLIENT_ID": "sso_user_identity_managed_identity_client_id", + "GFAZPL_USER_IDENTITY_FEDERATED_CREDENTIAL_AUDIENCE": "sso_user_identity_federated_credential_audience", + "GFAZPL_USER_IDENTITY_ASSERTION": "username", + "GFAZPL_AZURE_ENTRA_PASSWORD_CREDENTIALS_ENABLED": "true", + }) + }) + + t.Run("does not use SSO settings if overrides have been set for an Azure plugin", func(t *testing.T) { + cfg := setting.NewCfg() + cfg.Azure = &azsettings.AzureSettings{ + Cloud: azsettings.AzurePublic, + ManagedIdentityEnabled: true, + ManagedIdentityClientId: "mock_managed_identity_client_id", + WorkloadIdentityEnabled: true, + WorkloadIdentitySettings: &azsettings.WorkloadIdentitySettings{ + TenantId: "mock_workload_identity_tenant_id", + ClientId: "mock_workload_identity_client_id", + TokenFile: "mock_workload_identity_token_file", + }, + UserIdentityEnabled: true, + UserIdentityTokenEndpoint: &azsettings.TokenEndpointSettings{ + TokenUrl: "override_user_identity_token_url", + TokenUrlOverride: true, + ClientAuthentication: "override_user_client_authentication", + ClientAuthenticationOverride: true, + ClientId: "override_user_identity_client_id", + ClientIdOverride: true, + ClientSecret: "override_user_identity_client_secret", + ClientSecretOverride: true, + ManagedIdentityClientId: "override_user_identity_managed_identity_client_id", + ManagedIdentityClientIdOverride: true, + FederatedCredentialAudience: "override_user_identity_federated_credential_audience", + FederatedCredentialAudienceOverride: true, + UsernameAssertion: true, + }, + UserIdentityFallbackCredentialsEnabled: true, + ForwardSettingsPlugins: []string{"grafana-azure-monitor-datasource", "prometheus", "grafana-azure-data-explorer-datasource", "mssql", "grafana-azureprometheus-datasource"}, + AzureEntraPasswordCredentialsEnabled: true, + } + + pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) + require.NoError(t, err) + + p := NewRequestConfigProvider(pCfg, &fakeSSOSettingsProvider{ + GetForProviderFunc: getAzureSSOSettings, + }) + require.Subset(t, p.PluginRequestConfig(context.Background(), "grafana-azure-monitor-datasource", nil), map[string]string{ + "GFAZPL_AZURE_CLOUD": "AzureCloud", "GFAZPL_MANAGED_IDENTITY_ENABLED": "true", + "GFAZPL_MANAGED_IDENTITY_CLIENT_ID": "mock_managed_identity_client_id", + "GFAZPL_WORKLOAD_IDENTITY_ENABLED": "true", + "GFAZPL_WORKLOAD_IDENTITY_TENANT_ID": "mock_workload_identity_tenant_id", + "GFAZPL_WORKLOAD_IDENTITY_CLIENT_ID": "mock_workload_identity_client_id", + "GFAZPL_WORKLOAD_IDENTITY_TOKEN_FILE": "mock_workload_identity_token_file", + "GFAZPL_USER_IDENTITY_ENABLED": "true", + "GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED": "true", + "GFAZPL_USER_IDENTITY_TOKEN_URL": "override_user_identity_token_url", + "GFAZPL_USER_IDENTITY_CLIENT_AUTHENTICATION": "override_user_client_authentication", + "GFAZPL_USER_IDENTITY_CLIENT_ID": "override_user_identity_client_id", + "GFAZPL_USER_IDENTITY_CLIENT_SECRET": "override_user_identity_client_secret", + "GFAZPL_USER_IDENTITY_MANAGED_IDENTITY_CLIENT_ID": "override_user_identity_managed_identity_client_id", + "GFAZPL_USER_IDENTITY_FEDERATED_CREDENTIAL_AUDIENCE": "override_user_identity_federated_credential_audience", + "GFAZPL_USER_IDENTITY_ASSERTION": "username", + "GFAZPL_AZURE_ENTRA_PASSWORD_CREDENTIALS_ENABLED": "true", + }) + }) +} + +func getAzureSSOSettings(ctx context.Context, provider string) (*pluginsso.Settings, error) { + return &pluginsso.Settings{ + Values: map[string]any{ + "token_url": "sso_user_identity_token_url", + "client_authentication": "sso_user_client_authentication", + "client_id": "sso_user_identity_client_id", + "client_secret": "sso_user_identity_client_secret", + "managed_identity_client_id": "sso_user_identity_managed_identity_client_id", + "federated_credential_audience": "sso_user_identity_federated_credential_audience", + }, + }, nil } func TestRequestConfigProvider_PluginRequestConfig_aws(t *testing.T) { @@ -447,7 +550,7 @@ func TestRequestConfigProvider_PluginRequestConfig_aws(t *testing.T) { cfg.AWSListMetricsPageLimit = "100" cfg.AWSForwardSettingsPlugins = []string{"cloudwatch", "prometheus", "elasticsearch"} - p := NewRequestConfigProvider(cfg) + p := NewRequestConfigProvider(cfg, &fakeSSOSettingsProvider{}) t.Run("uses the aws settings for an AWS plugin", func(t *testing.T) { require.Subset(t, p.PluginRequestConfig(context.Background(), "cloudwatch", nil), map[string]string{ @@ -471,7 +574,7 @@ func TestRequestConfigProvider_PluginRequestConfig_aws(t *testing.T) { t.Run("uses the aws settings for a non-aws user-specified plugin", func(t *testing.T) { cfg.AWSForwardSettingsPlugins = append(cfg.AWSForwardSettingsPlugins, "test-datasource") - p = NewRequestConfigProvider(cfg) + p = NewRequestConfigProvider(cfg, &fakeSSOSettingsProvider{}) require.Subset(t, p.PluginRequestConfig(context.Background(), "test-datasource", nil), map[string]string{ "AWS_AUTH_AssumeRoleEnabled": "false", "AWS_AUTH_AllowedAuthProviders": "grafana_assume_role,keys", @@ -489,9 +592,20 @@ func TestRequestConfigProvider_PluginRequestConfig_appClientSecret(t *testing.T) pCfg, err := ProvidePluginInstanceConfig(cfg, setting.ProvideProvider(cfg), featuremgmt.WithFeatures()) require.NoError(t, err) - p := NewRequestConfigProvider(pCfg) + p := NewRequestConfigProvider(pCfg, &fakeSSOSettingsProvider{}) require.Subset(t, p.PluginRequestConfig(context.Background(), "", &auth.ExternalService{ ClientSecret: "mysecret", }), map[string]string{backend.AppClientSecret: "mysecret"}) }) } + +type fakeSSOSettingsProvider struct { + GetForProviderFunc func(ctx context.Context, provider string) (*pluginsso.Settings, error) +} + +func (m *fakeSSOSettingsProvider) GetForProvider(ctx context.Context, provider string) (*pluginsso.Settings, error) { + if m.GetForProviderFunc != nil { + return m.GetForProviderFunc(ctx, provider) + } + return nil, nil +} diff --git a/pkg/services/pluginsintegration/pluginsintegration.go b/pkg/services/pluginsintegration/pluginsintegration.go index b6d016662be..992eb355a6b 100644 --- a/pkg/services/pluginsintegration/pluginsintegration.go +++ b/pkg/services/pluginsintegration/pluginsintegration.go @@ -54,6 +54,7 @@ import ( "github.com/grafana/grafana/pkg/services/pluginsintegration/plugininstaller" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsso" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins" "github.com/grafana/grafana/pkg/services/pluginsintegration/renderer" @@ -134,6 +135,8 @@ var WireSet = wire.NewSet( wire.Bind(new(advisor.AdvisorStats), new(*advisor.Service)), pluginchecker.ProvideService, wire.Bind(new(pluginchecker.PluginUpdateChecker), new(*pluginchecker.Service)), + pluginsso.ProvideDefaultSettingsProvider, + wire.Bind(new(pluginsso.SettingsProvider), new(*pluginsso.DefaultSettingsProvider)), ) // WireExtensionSet provides a wire.ProviderSet of plugin providers that can be diff --git a/pkg/services/pluginsintegration/pluginsso/pluginsso.go b/pkg/services/pluginsintegration/pluginsso/pluginsso.go new file mode 100644 index 00000000000..0a6eda52c16 --- /dev/null +++ b/pkg/services/pluginsintegration/pluginsso/pluginsso.go @@ -0,0 +1,41 @@ +package pluginsso + +import ( + "context" + + "github.com/grafana/grafana/pkg/services/ssosettings" +) + +// SettingsProvider is used to get the SSO settings for a given provider. +type SettingsProvider interface { + GetForProvider(ctx context.Context, provider string) (*Settings, error) +} + +type Settings struct { + Values map[string]any +} + +// DefaultSettingsProvider is the default implementation of the SettingsProvider interface. +// It uses the SSO settings service to get the settings for a given provider. +type DefaultSettingsProvider struct { + ssoSettings ssosettings.Service +} + +func ProvideDefaultSettingsProvider(ssoSettings ssosettings.Service) *DefaultSettingsProvider { + return &DefaultSettingsProvider{ + ssoSettings: ssoSettings, + } +} + +// GetForProvider returns the SSO settings for a given provider. +// The settings are fetched from the cache if available, otherwise they are fetched from the database. +func (p *DefaultSettingsProvider) GetForProvider(ctx context.Context, provider string) (*Settings, error) { + settings, err := p.ssoSettings.GetForProviderFromCache(ctx, provider) + if err != nil { + return nil, err + } + + return &Settings{ + Values: settings.Settings, + }, nil +} diff --git a/pkg/setting/setting_azure.go b/pkg/setting/setting_azure.go index 61ce973e79a..d489ce8b0e5 100644 --- a/pkg/setting/setting_azure.go +++ b/pkg/setting/setting_azure.go @@ -59,22 +59,28 @@ func (cfg *Cfg) readAzureSettings() { // Override individual settings if val := azureSection.Key("user_identity_token_url").String(); val != "" { tokenEndpointSettings.TokenUrl = val + tokenEndpointSettings.TokenUrlOverride = true } if val := azureSection.Key("user_identity_client_authentication").String(); val != "" { tokenEndpointSettings.ClientAuthentication = val + tokenEndpointSettings.ClientAuthenticationOverride = true } if val := azureSection.Key("user_identity_client_id").String(); val != "" { tokenEndpointSettings.ClientId = val + tokenEndpointSettings.ClientIdOverride = true tokenEndpointSettings.ClientSecret = "" } if val := azureSection.Key("user_identity_client_secret").String(); val != "" { tokenEndpointSettings.ClientSecret = val + tokenEndpointSettings.ClientSecretOverride = true } if val := azureSection.Key("user_identity_managed_identity_client_id").String(); val != "" { tokenEndpointSettings.ManagedIdentityClientId = val + tokenEndpointSettings.ManagedIdentityClientIdOverride = true } if val := azureSection.Key("user_identity_federated_credential_audience").String(); val != "" { tokenEndpointSettings.FederatedCredentialAudience = val + tokenEndpointSettings.FederatedCredentialAudienceOverride = true } if val := azureSection.Key("username_assertion").String(); val != "" && val == "username" { tokenEndpointSettings.UsernameAssertion = true