diff --git a/conf/defaults.ini b/conf/defaults.ini index 91e7af25442..1ed0bb7a24f 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1013,6 +1013,10 @@ user_identity_fallback_credentials_enabled = true # By default is the same as token URL configured for AAD authentication settings user_identity_token_url = +# Override client authentication method for Azure Active Directory +# By default is the same as client authentication method configured for AAD authentication settings +user_identity_client_authentication = + # Override ADD application ID which would be used to exchange users token to an access token for the datasource # By default is the same as used in AAD authentication or can be set to another application (for OBO flow) user_identity_client_id = @@ -1021,6 +1025,14 @@ user_identity_client_id = # By default is the same as used in AAD authentication or can be set to another application (for OBO flow) user_identity_client_secret = +# Override the AAD managed identity client ID +# By default is the same as used in AAD authentication or can be set to another managed identity (for OBO flow) +user_identity_managed_identity_client_id = + +# Override the AAD federated credential audience +# By default is the same as used in AAD authentication or can be set to another audience (for OBO flow) +user_identity_federated_credential_audience = + # Allows the usage of a custom token request assertion when Grafana is behind an authentication proxy # In most cases this will not need to be used. To enable this set the value to "username" # The default is empty and any other value will not enable this functionality diff --git a/conf/sample.ini b/conf/sample.ini index d1a06d9c331..985fb7034b6 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -998,6 +998,10 @@ # By default is the same as token URL configured for AAD authentication settings ;user_identity_token_url = +# Override client authentication method for Azure Active Directory +# By default is the same as client authentication method configured for AAD authentication settings +;user_identity_client_authentication = + # Override ADD application ID which would be used to exchange users token to an access token for the datasource # By default is the same as used in AAD authentication or can be set to another application (for OBO flow) ;user_identity_client_id = @@ -1006,6 +1010,14 @@ # By default is the same as used in AAD authentication or can be set to another application (for OBO flow) ;user_identity_client_secret = +# Override the AAD managed identity client ID +# By default is the same as used in AAD authentication or can be set to another managed identity (for OBO flow) +;user_identity_managed_identity_client_id = + +# Override the AAD federated credential audience +# By default is the same as used in AAD authentication or can be set to another audience (for OBO flow) +;user_identity_federated_credential_audience = + # Allows the usage of a custom token request assertion when Grafana is behind an authentication proxy # In most cases this will not need to be used. To enable this set the value to "username" # The default is empty and any other value will not enable this functionality diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index 6469a11891b..15247a10908 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -1361,7 +1361,13 @@ Override token URL for Azure Active Directory. By default is the same as token URL configured for AAD authentication settings. -#### `user_identity_client_id` +### `user_identity_client_authentication` + +Override client authentication method for Azure Active Directory. Currently supported values are `client_secret_post` and `managed_identity`. + +By default is the same as client authentication method configured for AAD authentication settings. + +### `user_identity_client_id` Override ADD application ID which would be used to exchange users token to an access token for the data source. @@ -1373,7 +1379,19 @@ Override the AAD application client secret. By default is the same as used in AAD authentication or can be set to another application (for OBO flow). -#### `forward_settings_to_plugins` +### `user_identity_managed_identity_client_id` + +Override the AAD application managed identity client ID of the federated credential configured as a user-assigned managed identity. + +By default is the same as used in AAD authentication or can be set to another managed identity (for OBO flow). + +### `user_identity_federated_credential_audience` + +Override the AAD federated credential audience of the federated credential configured as a user-assigned managed identity. + +By default is the same as used in AAD authentication or can be set to another audience (for OBO flow). + +### `forward_settings_to_plugins` Set plugins to receive Azure settings via plugin context. diff --git a/pkg/services/pluginsintegration/pluginconfig/request.go b/pkg/services/pluginsintegration/pluginconfig/request.go index 347855a8946..5065caac36e 100644 --- a/pkg/services/pluginsintegration/pluginconfig/request.go +++ b/pkg/services/pluginsintegration/pluginconfig/request.go @@ -115,12 +115,21 @@ func (s *RequestConfigProvider) PluginRequestConfig(ctx context.Context, pluginI if azureSettings.UserIdentityTokenEndpoint.TokenUrl != "" { m[azsettings.UserIdentityTokenURL] = azureSettings.UserIdentityTokenEndpoint.TokenUrl } + if azureSettings.UserIdentityTokenEndpoint.ClientAuthentication != "" { + m[azsettings.UserIdentityClientAuthentication] = azureSettings.UserIdentityTokenEndpoint.ClientAuthentication + } if azureSettings.UserIdentityTokenEndpoint.ClientId != "" { m[azsettings.UserIdentityClientID] = azureSettings.UserIdentityTokenEndpoint.ClientId } if azureSettings.UserIdentityTokenEndpoint.ClientSecret != "" { m[azsettings.UserIdentityClientSecret] = azureSettings.UserIdentityTokenEndpoint.ClientSecret } + if azureSettings.UserIdentityTokenEndpoint.ManagedIdentityClientId != "" { + m[azsettings.UserIdentityManagedIdentityClientID] = azureSettings.UserIdentityTokenEndpoint.ManagedIdentityClientId + } + if azureSettings.UserIdentityTokenEndpoint.FederatedCredentialAudience != "" { + m[azsettings.UserIdentityFederatedCredentialAudience] = azureSettings.UserIdentityTokenEndpoint.FederatedCredentialAudience + } if azureSettings.UserIdentityTokenEndpoint.UsernameAssertion { m[azsettings.UserIdentityAssertion] = "username" } diff --git a/pkg/services/pluginsintegration/pluginconfig/request_test.go b/pkg/services/pluginsintegration/pluginconfig/request_test.go index c5eaa7c467c..f8aa0cbc9f2 100644 --- a/pkg/services/pluginsintegration/pluginconfig/request_test.go +++ b/pkg/services/pluginsintegration/pluginconfig/request_test.go @@ -302,10 +302,13 @@ func TestRequestConfigProvider_PluginRequestConfig_azure(t *testing.T) { }, UserIdentityEnabled: true, UserIdentityTokenEndpoint: &azsettings.TokenEndpointSettings{ - TokenUrl: "mock_user_identity_token_url", - ClientId: "mock_user_identity_client_id", - ClientSecret: "mock_user_identity_client_secret", - UsernameAssertion: true, + TokenUrl: "mock_user_identity_token_url", + ClientAuthentication: "mock_user_client_authentication", + ClientId: "mock_user_identity_client_id", + ClientSecret: "mock_user_identity_client_secret", + ManagedIdentityClientId: "mock_user_identity_managed_identity_client_id", + FederatedCredentialAudience: "mock_user_identity_federated_credential_audience", + UsernameAssertion: true, }, UserIdentityFallbackCredentialsEnabled: true, ForwardSettingsPlugins: []string{"grafana-azure-monitor-datasource", "prometheus", "grafana-azure-data-explorer-datasource", "mssql"}, @@ -330,8 +333,11 @@ func TestRequestConfigProvider_PluginRequestConfig_azure(t *testing.T) { "GFAZPL_USER_IDENTITY_ENABLED": "true", "GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED": "true", "GFAZPL_USER_IDENTITY_TOKEN_URL": "mock_user_identity_token_url", + "GFAZPL_USER_IDENTITY_CLIENT_AUTHENTICATION": "mock_user_client_authentication", "GFAZPL_USER_IDENTITY_CLIENT_ID": "mock_user_identity_client_id", "GFAZPL_USER_IDENTITY_CLIENT_SECRET": "mock_user_identity_client_secret", + "GFAZPL_USER_IDENTITY_MANAGED_IDENTITY_CLIENT_ID": "mock_user_identity_managed_identity_client_id", + "GFAZPL_USER_IDENTITY_FEDERATED_CREDENTIAL_AUDIENCE": "mock_user_identity_federated_credential_audience", "GFAZPL_USER_IDENTITY_ASSERTION": "username", }) }) @@ -362,8 +368,11 @@ func TestRequestConfigProvider_PluginRequestConfig_azure(t *testing.T) { "GFAZPL_USER_IDENTITY_ENABLED": "true", "GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED": "true", "GFAZPL_USER_IDENTITY_TOKEN_URL": "mock_user_identity_token_url", + "GFAZPL_USER_IDENTITY_CLIENT_AUTHENTICATION": "mock_user_client_authentication", "GFAZPL_USER_IDENTITY_CLIENT_ID": "mock_user_identity_client_id", "GFAZPL_USER_IDENTITY_CLIENT_SECRET": "mock_user_identity_client_secret", + "GFAZPL_USER_IDENTITY_MANAGED_IDENTITY_CLIENT_ID": "mock_user_identity_managed_identity_client_id", + "GFAZPL_USER_IDENTITY_FEDERATED_CREDENTIAL_AUDIENCE": "mock_user_identity_federated_credential_audience", "GFAZPL_USER_IDENTITY_ASSERTION": "username", }) }) @@ -387,8 +396,11 @@ func TestRequestConfigProvider_PluginRequestConfig_azure(t *testing.T) { require.NotContains(t, m, "GFAZPL_USER_IDENTITY_ENABLED") require.NotContains(t, m, "GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED") require.NotContains(t, m, "GFAZPL_USER_IDENTITY_TOKEN_URL") + require.NotContains(t, m, "GFAZPL_USER_IDENTITY_CLIENT_AUTHENTICATION") require.NotContains(t, m, "GFAZPL_USER_IDENTITY_CLIENT_ID") require.NotContains(t, m, "GFAZPL_USER_IDENTITY_CLIENT_SECRET") + require.NotContains(t, m, "GFAZPL_USER_IDENTITY_MANAGED_IDENTITY_CLIENT_ID") + require.NotContains(t, m, "GFAZPL_USER_IDENTITY_FEDERATED_CREDENTIAL_AUDIENCE") require.NotContains(t, m, "GFAZPL_USER_IDENTITY_ASSERTION") require.NotContains(t, m, "GFAZPL_AZURE_ENTRA_PASSWORD_CREDENTIALS_ENABLED") }) @@ -412,8 +424,11 @@ func TestRequestConfigProvider_PluginRequestConfig_azure(t *testing.T) { "GFAZPL_USER_IDENTITY_ENABLED": "true", "GFAZPL_USER_IDENTITY_FALLBACK_SERVICE_CREDENTIALS_ENABLED": "true", "GFAZPL_USER_IDENTITY_TOKEN_URL": "mock_user_identity_token_url", + "GFAZPL_USER_IDENTITY_CLIENT_AUTHENTICATION": "mock_user_client_authentication", "GFAZPL_USER_IDENTITY_CLIENT_ID": "mock_user_identity_client_id", "GFAZPL_USER_IDENTITY_CLIENT_SECRET": "mock_user_identity_client_secret", + "GFAZPL_USER_IDENTITY_MANAGED_IDENTITY_CLIENT_ID": "mock_user_identity_managed_identity_client_id", + "GFAZPL_USER_IDENTITY_FEDERATED_CREDENTIAL_AUDIENCE": "mock_user_identity_federated_credential_audience", "GFAZPL_USER_IDENTITY_ASSERTION": "username", "GFAZPL_AZURE_ENTRA_PASSWORD_CREDENTIALS_ENABLED": "true", }) diff --git a/pkg/setting/setting_azure.go b/pkg/setting/setting_azure.go index c2531ca4270..61ce973e79a 100644 --- a/pkg/setting/setting_azure.go +++ b/pkg/setting/setting_azure.go @@ -49,14 +49,20 @@ func (cfg *Cfg) readAzureSettings() { azureAdSection := cfg.Raw.Section("auth.azuread") if azureAdSection.Key("enabled").MustBool(false) { tokenEndpointSettings.TokenUrl = azureAdSection.Key("token_url").String() + tokenEndpointSettings.ClientAuthentication = azureAdSection.Key("client_authentication").String() tokenEndpointSettings.ClientId = azureAdSection.Key("client_id").String() tokenEndpointSettings.ClientSecret = azureAdSection.Key("client_secret").String() + tokenEndpointSettings.ManagedIdentityClientId = azureAdSection.Key("managed_identity_client_id").String() + tokenEndpointSettings.FederatedCredentialAudience = azureAdSection.Key("federated_credential_audience").String() } // Override individual settings if val := azureSection.Key("user_identity_token_url").String(); val != "" { tokenEndpointSettings.TokenUrl = val } + if val := azureSection.Key("user_identity_client_authentication").String(); val != "" { + tokenEndpointSettings.ClientAuthentication = val + } if val := azureSection.Key("user_identity_client_id").String(); val != "" { tokenEndpointSettings.ClientId = val tokenEndpointSettings.ClientSecret = "" @@ -64,6 +70,12 @@ func (cfg *Cfg) readAzureSettings() { if val := azureSection.Key("user_identity_client_secret").String(); val != "" { tokenEndpointSettings.ClientSecret = val } + if val := azureSection.Key("user_identity_managed_identity_client_id").String(); val != "" { + tokenEndpointSettings.ManagedIdentityClientId = val + } + if val := azureSection.Key("user_identity_federated_credential_audience").String(); val != "" { + tokenEndpointSettings.FederatedCredentialAudience = val + } if val := azureSection.Key("username_assertion").String(); val != "" && val == "username" { tokenEndpointSettings.UsernameAssertion = true } diff --git a/pkg/setting/setting_azure_test.go b/pkg/setting/setting_azure_test.go index c0bc668b6d0..1738044eb14 100644 --- a/pkg/setting/setting_azure_test.go +++ b/pkg/setting/setting_azure_test.go @@ -145,10 +145,16 @@ func TestAzureSettings(t *testing.T) { require.NoError(t, err) _, err = azureAdSection.NewKey("token_url", "URL_1") require.NoError(t, err) + _, err = azureAdSection.NewKey("client_authentication", "METHOD_1") + require.NoError(t, err) _, err = azureAdSection.NewKey("client_id", "ID_1") require.NoError(t, err) _, err = azureAdSection.NewKey("client_secret", "SECRET_1") require.NoError(t, err) + _, err = azureAdSection.NewKey("managed_identity_client_id", "MANAGED_ID_1") + require.NoError(t, err) + _, err = azureAdSection.NewKey("federated_credential_audience", "AUDIENCE_1") + require.NoError(t, err) azureSection, err := cfg.Raw.NewSection("azure") require.NoError(t, err) @@ -161,8 +167,11 @@ func TestAzureSettings(t *testing.T) { assert.True(t, cfg.Azure.UserIdentityEnabled) assert.Equal(t, "URL_1", cfg.Azure.UserIdentityTokenEndpoint.TokenUrl) + assert.Equal(t, "METHOD_1", cfg.Azure.UserIdentityTokenEndpoint.ClientAuthentication) assert.Equal(t, "ID_1", cfg.Azure.UserIdentityTokenEndpoint.ClientId) assert.Equal(t, "SECRET_1", cfg.Azure.UserIdentityTokenEndpoint.ClientSecret) + assert.Equal(t, "MANAGED_ID_1", cfg.Azure.UserIdentityTokenEndpoint.ManagedIdentityClientId) + assert.Equal(t, "AUDIENCE_1", cfg.Azure.UserIdentityTokenEndpoint.FederatedCredentialAudience) }) t.Run("should not use token endpoint from Azure AD if not enabled", func(t *testing.T) { @@ -174,10 +183,16 @@ func TestAzureSettings(t *testing.T) { require.NoError(t, err) _, err = azureAdSection.NewKey("token_url", "URL_1") require.NoError(t, err) + _, err = azureAdSection.NewKey("client_authentication", "METHOD_1") + require.NoError(t, err) _, err = azureAdSection.NewKey("client_id", "ID_1") require.NoError(t, err) _, err = azureAdSection.NewKey("client_secret", "SECRET_1") require.NoError(t, err) + _, err = azureAdSection.NewKey("managed_identity_client_id", "MANAGED_ID_1") + require.NoError(t, err) + _, err = azureAdSection.NewKey("federated_credential_audience", "AUDIENCE_1") + require.NoError(t, err) azureSection, err := cfg.Raw.NewSection("azure") require.NoError(t, err) @@ -190,8 +205,11 @@ func TestAzureSettings(t *testing.T) { assert.True(t, cfg.Azure.UserIdentityEnabled) assert.Empty(t, cfg.Azure.UserIdentityTokenEndpoint.TokenUrl) + assert.Empty(t, cfg.Azure.UserIdentityTokenEndpoint.ClientAuthentication) assert.Empty(t, cfg.Azure.UserIdentityTokenEndpoint.ClientId) assert.Empty(t, cfg.Azure.UserIdentityTokenEndpoint.ClientSecret) + assert.Empty(t, cfg.Azure.UserIdentityTokenEndpoint.ManagedIdentityClientId) + assert.Empty(t, cfg.Azure.UserIdentityTokenEndpoint.FederatedCredentialAudience) }) t.Run("should override Azure AD settings", func(t *testing.T) { @@ -203,10 +221,16 @@ func TestAzureSettings(t *testing.T) { require.NoError(t, err) _, err = azureAdSection.NewKey("token_url", "URL_1") require.NoError(t, err) + _, err = azureAdSection.NewKey("client_authentication", "METHOD_1") + require.NoError(t, err) _, err = azureAdSection.NewKey("client_id", "ID_1") require.NoError(t, err) _, err = azureAdSection.NewKey("client_secret", "SECRET_1") require.NoError(t, err) + _, err = azureAdSection.NewKey("managed_identity_client_id", "MANAGED_ID_1") + require.NoError(t, err) + _, err = azureAdSection.NewKey("federated_credential_audience", "AUDIENCE_1") + require.NoError(t, err) azureSection, err := cfg.Raw.NewSection("azure") require.NoError(t, err) @@ -214,10 +238,16 @@ func TestAzureSettings(t *testing.T) { require.NoError(t, err) _, err = azureSection.NewKey("user_identity_token_url", "URL_2") require.NoError(t, err) + _, err = azureSection.NewKey("user_identity_client_authentication", "METHOD_2") + require.NoError(t, err) _, err = azureSection.NewKey("user_identity_client_id", "ID_2") require.NoError(t, err) _, err = azureSection.NewKey("user_identity_client_secret", "SECRET_2") require.NoError(t, err) + _, err = azureSection.NewKey("user_identity_managed_identity_client_id", "MANAGED_ID_2") + require.NoError(t, err) + _, err = azureSection.NewKey("user_identity_federated_credential_audience", "AUDIENCE_2") + require.NoError(t, err) cfg.readAzureSettings() require.NotNil(t, cfg.Azure) @@ -225,8 +255,11 @@ func TestAzureSettings(t *testing.T) { assert.True(t, cfg.Azure.UserIdentityEnabled) assert.Equal(t, "URL_2", cfg.Azure.UserIdentityTokenEndpoint.TokenUrl) + assert.Equal(t, "METHOD_2", cfg.Azure.UserIdentityTokenEndpoint.ClientAuthentication) assert.Equal(t, "ID_2", cfg.Azure.UserIdentityTokenEndpoint.ClientId) assert.Equal(t, "SECRET_2", cfg.Azure.UserIdentityTokenEndpoint.ClientSecret) + assert.Equal(t, "MANAGED_ID_2", cfg.Azure.UserIdentityTokenEndpoint.ManagedIdentityClientId) + assert.Equal(t, "AUDIENCE_2", cfg.Azure.UserIdentityTokenEndpoint.FederatedCredentialAudience) }) t.Run("should not use secret from Azure AD if client ID overridden", func(t *testing.T) {