From 3ee40d3a5a168a850cb5d03cc1f50b5cdae32849 Mon Sep 17 00:00:00 2001 From: Sergey Kostrukov Date: Thu, 28 Sep 2023 04:05:16 -0700 Subject: [PATCH] Azure: Settings for Azure AD Workload Identity (#75283) * Settings for Azure AD Workload Identity * Update dependency on Grafana Azure SDK * Documentation * Fix JS code * Cleanup Prometheus backend code * Making prettier happy --- conf/defaults.ini | 18 +++++++++++++ conf/sample.ini | 18 +++++++++++++ .../setup-grafana/configure-grafana/_index.md | 26 +++++++++++++++++++ go.mod | 2 +- go.sum | 4 +-- packages/grafana-runtime/src/config.ts | 2 ++ pkg/api/dtos/frontend_settings.go | 7 ++--- pkg/api/frontendsettings.go | 7 ++--- pkg/setting/setting_azure.go | 18 +++++++++++++ pkg/tsdb/prometheus/azureauth/azure.go | 25 +----------------- .../mssql/azureauth/AzureAuth.testMocks.ts | 9 +++++-- 11 files changed, 101 insertions(+), 35 deletions(-) diff --git a/conf/defaults.ini b/conf/defaults.ini index c5f35c46864..1e3feb44292 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -846,6 +846,24 @@ managed_identity_enabled = false # Should be set for user-assigned identity and should be empty for system-assigned identity managed_identity_client_id = +# Specifies whether Azure AD Workload Identity authentication should be enabled in datasources that support it +# For more documentation on Azure AD Workload Identity, review this documentation: +# https://azure.github.io/azure-workload-identity/docs/ +# Disabled by default, needs to be explicitly enabled +workload_identity_enabled = false + +# Tenant ID of the Azure AD Workload Identity +# Allows to override default tenant ID of the Azure AD identity associated with the Kubernetes service account +workload_identity_tenant_id = + +# Client ID of the Azure AD Workload Identity +# Allows to override default client ID of the Azure AD identity associated with the Kubernetes service account +workload_identity_client_id = + +# Custom path to token file for the Azure AD Workload Identity +# Allows to set a custom path to the projected service account token file +workload_identity_token_file = + # Specifies whether user identity authentication (on behalf of currently signed-in user) should be enabled in datasources # that support it (requires AAD authentication) # Disabled by default, needs to be explicitly enabled diff --git a/conf/sample.ini b/conf/sample.ini index ac3a9568e2e..e58ab27a662 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -799,6 +799,24 @@ # Should be set for user-assigned identity and should be empty for system-assigned identity ;managed_identity_client_id = +# Specifies whether Azure AD Workload Identity authentication should be enabled in datasources that support it +# For more documentation on Azure AD Workload Identity, review this documentation: +# https://azure.github.io/azure-workload-identity/docs/ +# Disabled by default, needs to be explicitly enabled +;workload_identity_enabled = false + +# Tenant ID of the Azure AD Workload Identity +# Allows to override default tenant ID of the Azure AD identity associated with the Kubernetes service account +;workload_identity_tenant_id = + +# Client ID of the Azure AD Workload Identity +# Allows to override default client ID of the Azure AD identity associated with the Kubernetes service account +;workload_identity_client_id = + +# Custom path to token file for the Azure AD Workload Identity +# Allows to set a custom path to the projected service account token file +;workload_identity_token_file = + # Specifies whether user identity authentication (on behalf of currently signed-in user) should be enabled in datasources # that support it (requires AAD authentication) # Disabled by default, needs to be explicitly enabled diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index 3b288238367..1eb6aaa1222 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -1158,6 +1158,32 @@ The client ID to use for user-assigned managed identity. Should be set for user-assigned identity and should be empty for system-assigned identity. +### workload_identity_enabled + +Specifies whether Azure AD Workload Identity authentication should be enabled in datasources that support it. + +For more documentation on Azure AD Workload Identity, review [Azure AD Workload Identity](https://azure.github.io/azure-workload-identity/docs/) documentation. + +Disabled by default, needs to be explicitly enabled. + +### workload_identity_tenant_id + +Tenant ID of the Azure AD Workload Identity. + +Allows to override default tenant ID of the Azure AD identity associated with the Kubernetes service account. + +### workload_identity_client_id + +Client ID of the Azure AD Workload Identity. + +Allows to override default client ID of the Azure AD identity associated with the Kubernetes service account. + +### workload_identity_token_file + +Custom path to token file for the Azure AD Workload Identity. + +Allows to set a custom path to the projected service account token file. + ### user_identity_enabled Specifies whether user identity authentication (on behalf of currently signed-in user) should be enabled in datasources that support it (requires AAD authentication). diff --git a/go.mod b/go.mod index 02358d3291d..2b1faa92e15 100644 --- a/go.mod +++ b/go.mod @@ -62,7 +62,7 @@ require ( github.com/grafana/alerting v0.0.0-20230918125844-e60d564786c9 // @grafana/alerting-squad-backend github.com/grafana/cuetsy v0.1.10 // @grafana/grafana-as-code github.com/grafana/grafana-aws-sdk v0.19.1 // @grafana/aws-datasources - github.com/grafana/grafana-azure-sdk-go v1.8.1 // @grafana/backend-platform + github.com/grafana/grafana-azure-sdk-go v1.9.0 // @grafana/backend-platform github.com/grafana/grafana-plugin-sdk-go v0.177.0 // @grafana/plugins-platform-backend github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // @grafana/backend-platform github.com/hashicorp/go-hclog v1.5.0 // @grafana/plugins-platform-backend diff --git a/go.sum b/go.sum index 89f72015bbf..ffe0ffd0919 100644 --- a/go.sum +++ b/go.sum @@ -1799,8 +1799,8 @@ github.com/grafana/grafana-apiserver v0.0.0-20230713001719-88a9ed41992d h1:fjc6v github.com/grafana/grafana-apiserver v0.0.0-20230713001719-88a9ed41992d/go.mod h1:2g9qGdCeU6x/69QAs82WM52bwBUT5/CBaBD0I94+txU= github.com/grafana/grafana-aws-sdk v0.19.1 h1:5GBiOv2AgdyjwlgAX+dtgPtXU4FgMTD9rfQUPQseEpQ= github.com/grafana/grafana-aws-sdk v0.19.1/go.mod h1:ntq2NDH12Y2Fkbc6fozpF8kYsJM9k6KNr+Xfo5w3/iM= -github.com/grafana/grafana-azure-sdk-go v1.8.1 h1:UgzOc7Qv1P0EJhijn71g9eT1xqujI3nwbhw+OIarBYE= -github.com/grafana/grafana-azure-sdk-go v1.8.1/go.mod h1:1vBa0KOl+/Kcm7V888OyMXDSFncmek14q7XhEkrcSaA= +github.com/grafana/grafana-azure-sdk-go v1.9.0 h1:4JRwlqgUtPRAQSoiV4DFZDQ3lbNsauHqj9kC6SMR9Ak= +github.com/grafana/grafana-azure-sdk-go v1.9.0/go.mod h1:1vBa0KOl+/Kcm7V888OyMXDSFncmek14q7XhEkrcSaA= github.com/grafana/grafana-google-sdk-go v0.1.0 h1:LKGY8z2DSxKjYfr2flZsWgTRTZ6HGQbTqewE3JvRaNA= github.com/grafana/grafana-google-sdk-go v0.1.0/go.mod h1:Vo2TKWfDVmNTELBUM+3lkrZvFtBws0qSZdXhQxRdJrE= github.com/grafana/grafana-plugin-sdk-go v0.94.0/go.mod h1:3VXz4nCv6wH5SfgB3mlW39s+c+LetqSCjFj7xxPC5+M= diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index 431d4ddc3be..991704aa19c 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -21,6 +21,7 @@ import { export interface AzureSettings { cloud?: string; managedIdentityEnabled: boolean; + workloadIdentityEnabled: boolean; userIdentityEnabled: boolean; } @@ -124,6 +125,7 @@ export class GrafanaBootConfig implements GrafanaConfig { awsAssumeRoleEnabled = false; azure: AzureSettings = { managedIdentityEnabled: false, + workloadIdentityEnabled: false, userIdentityEnabled: false, }; caching = { diff --git a/pkg/api/dtos/frontend_settings.go b/pkg/api/dtos/frontend_settings.go index b62420ad1ba..529ddd08cef 100644 --- a/pkg/api/dtos/frontend_settings.go +++ b/pkg/api/dtos/frontend_settings.go @@ -44,9 +44,10 @@ type FrontendSettingsLicenseInfoDTO struct { } type FrontendSettingsAzureDTO struct { - Cloud string `json:"cloud"` - ManagedIdentityEnabled bool `json:"managedIdentityEnabled"` - UserIdentityEnabled bool `json:"userIdentityEnabled"` + Cloud string `json:"cloud"` + ManagedIdentityEnabled bool `json:"managedIdentityEnabled"` + WorkloadIdentityEnabled bool `json:"workloadIdentityEnabled"` + UserIdentityEnabled bool `json:"userIdentityEnabled"` } type FrontendSettingsCachingDTO struct { diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 1fffa2a20b1..974d5a11e0a 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -206,9 +206,10 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro SupportBundlesEnabled: isSupportBundlesEnabled(hs), Azure: dtos.FrontendSettingsAzureDTO{ - Cloud: hs.Cfg.Azure.Cloud, - ManagedIdentityEnabled: hs.Cfg.Azure.ManagedIdentityEnabled, - UserIdentityEnabled: hs.Cfg.Azure.UserIdentityEnabled, + Cloud: hs.Cfg.Azure.Cloud, + ManagedIdentityEnabled: hs.Cfg.Azure.ManagedIdentityEnabled, + WorkloadIdentityEnabled: hs.Cfg.Azure.WorkloadIdentityEnabled, + UserIdentityEnabled: hs.Cfg.Azure.UserIdentityEnabled, }, Caching: dtos.FrontendSettingsCachingDTO{ diff --git a/pkg/setting/setting_azure.go b/pkg/setting/setting_azure.go index 05fb415ad9f..ec1c079ba53 100644 --- a/pkg/setting/setting_azure.go +++ b/pkg/setting/setting_azure.go @@ -17,6 +17,24 @@ func (cfg *Cfg) readAzureSettings() { azureSettings.ManagedIdentityEnabled = azureSection.Key("managed_identity_enabled").MustBool(false) azureSettings.ManagedIdentityClientId = azureSection.Key("managed_identity_client_id").String() + // Workload Identity authentication + if azureSection.Key("workload_identity_enabled").MustBool(false) { + azureSettings.WorkloadIdentityEnabled = true + workloadIdentitySettings := &azsettings.WorkloadIdentitySettings{} + + if val := azureSection.Key("workload_identity_tenant_id").String(); val != "" { + workloadIdentitySettings.TenantId = val + } + if val := azureSection.Key("workload_identity_client_id").String(); val != "" { + workloadIdentitySettings.ClientId = val + } + if val := azureSection.Key("workload_identity_token_file").String(); val != "" { + workloadIdentitySettings.TokenFile = val + } + + azureSettings.WorkloadIdentitySettings = workloadIdentitySettings + } + // User Identity authentication if azureSection.Key("user_identity_enabled").MustBool(false) { azureSettings.UserIdentityEnabled = true diff --git a/pkg/tsdb/prometheus/azureauth/azure.go b/pkg/tsdb/prometheus/azureauth/azure.go index f46df732c8d..5b0f9a98a64 100644 --- a/pkg/tsdb/prometheus/azureauth/azure.go +++ b/pkg/tsdb/prometheus/azureauth/azure.go @@ -77,7 +77,7 @@ func getOverriddenScopes(jsonData map[string]any) ([]string, error) { func getPrometheusScopes(settings *azsettings.AzureSettings, credentials azcredentials.AzureCredentials) ([]string, error) { // Extract cloud from credentials - azureCloud, err := getAzureCloudFromCredentials(settings, credentials) + azureCloud, err := azcredentials.GetAzureCloud(settings, credentials) if err != nil { return nil, err } @@ -90,26 +90,3 @@ func getPrometheusScopes(settings *azsettings.AzureSettings, credentials azcrede return scopes, nil } } - -// To be part of grafana-azure-sdk-go -func getAzureCloudFromCredentials(settings *azsettings.AzureSettings, credentials azcredentials.AzureCredentials) (string, error) { - switch c := credentials.(type) { - case *azcredentials.AzureManagedIdentityCredentials: - // In case of managed identity, the cloud is always same as where Grafana is hosted - return getDefaultAzureCloud(settings), nil - case *azcredentials.AzureClientSecretCredentials: - return c.AzureCloud, nil - default: - err := fmt.Errorf("the Azure credentials of type '%s' not supported by Prometheus datasource", c.AzureAuthType()) - return "", err - } -} - -// To be part of grafana-azure-sdk-go -func getDefaultAzureCloud(settings *azsettings.AzureSettings) string { - cloudName := settings.Cloud - if cloudName == "" { - return azsettings.AzurePublic - } - return cloudName -} diff --git a/public/app/plugins/datasource/mssql/azureauth/AzureAuth.testMocks.ts b/public/app/plugins/datasource/mssql/azureauth/AzureAuth.testMocks.ts index ad2372f5cf0..bf3b00063ff 100644 --- a/public/app/plugins/datasource/mssql/azureauth/AzureAuth.testMocks.ts +++ b/public/app/plugins/datasource/mssql/azureauth/AzureAuth.testMocks.ts @@ -4,11 +4,16 @@ import { GrafanaBootConfig } from '@grafana/runtime'; import { AzureAuthSecureJSONDataType, AzureAuthJSONDataType, AzureAuthType } from '../types'; export const configWithManagedIdentityEnabled: Partial = { - azure: { managedIdentityEnabled: true, userIdentityEnabled: false }, + azure: { managedIdentityEnabled: true, workloadIdentityEnabled: false, userIdentityEnabled: false }, }; export const configWithManagedIdentityDisabled: Partial = { - azure: { managedIdentityEnabled: false, userIdentityEnabled: false, cloud: 'AzureCloud' }, + azure: { + managedIdentityEnabled: false, + workloadIdentityEnabled: false, + userIdentityEnabled: false, + cloud: 'AzureCloud', + }, }; export const dataSourceSettingsWithMsiCredentials: Partial<