Auth: Add Azure/Entra workload identity support (#104807)

* fixes/adds azure workload identity authentication. Issue #78249

* Updates default values. Adds `workload_identity_token_file` defaults

* Updates example config. Adds `workload_identity_token_file`

* Updates docummentation: adds Federated credentials for Workload Identity

* Update docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md

Co-authored-by: Misi <mgyongyosi@users.noreply.github.com>

* Update docs/sources/setup-grafana/configure-security/configure-authentication/azuread/index.md

Co-authored-by: Misi <mgyongyosi@users.noreply.github.com>

* Docs: add link to official documentation. Clarifies example.

* 1. Add workload_identity_enabled and workload_identity_token_file settings to [auth.azuread] for workload identity support.

2. Extend OAuthInfo struct to include workload identity fields.
3. Update OAuth authentication logic to handle Azure AD workload identity using federated token as client assertion.
4. Update sample configuration and documentation for new settings.

* ensure environment variable overrides are respected for OAuth SSO settings

- Ensure that settings loaded in pkg/services/ssosettings/strategies/oauth_strategy.go correctly reflect environment variable overrides, matching Grafana's config behavior.
- Align config loading logic with main config loader to prevent issues where INI values would override environment variables.

* updates documentation

* test: add workload identity configuration tests for Azure AD OAuth strategy. Add test coverage for workload_identity_enabled and workload_identity_token_file settings

* feat: add workload identity support to Azure AD SSO configuration UI

* updates documentation

* Simplify OAuth flow by removing unnecessary switch-case structure

* Small changes

* Lint + i18n gen

* refactor: remove redundant workload_identity_enabled setting as auth method gets defined by client_authentication

* update documentation

* refactor: remove redundant workload_identity_enabled setting as auth method gets defined by client_authentication

* updates documentation - configuration options table: adds `client_authentication`, `workload_identity_token_file`, and `federated_credential_audience`

* Small changes, lint, i18n

---------

Co-authored-by: Misi <mgyongyosi@users.noreply.github.com>
This commit is contained in:
Mykhailo Zahlada
2025-05-29 14:09:18 +02:00
committed by GitHub
co-authored by Misi
parent 601676b98e
commit fc988c8771
15 changed files with 163 additions and 57 deletions
+2 -1
View File
@@ -812,6 +812,7 @@ client_id = some_client_id
client_secret =
managed_identity_client_id =
federated_credential_audience =
workload_identity_token_file = /var/run/secrets/azure/tokens/azure-identity-token
scopes = openid email profile
auth_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize
token_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token
@@ -1841,7 +1842,7 @@ forward_host_env_vars =
preinstall =
# Comma separated list of plugin ids to install before the startup process
# These will be installed before starting Grafana. Useful when used with provisioning.
preinstall_sync =
preinstall_sync =
# Disables preinstall feature. It has the same effect as setting preinstall to an empty list.
preinstall_disabled = false
# Update strategy for plugins.
+2 -1
View File
@@ -787,6 +787,7 @@
;client_secret = some_client_secret
;managed_identity_client_id =
;federated_credential_audience =
;workload_identity_token_file =
;scopes = openid email profile
;auth_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize
;token_url = https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token
@@ -1787,7 +1788,7 @@ default_datasource_uid =
; preinstall =
# Comma separated list of plugin ids to install before the startup process
# These will be installed before starting Grafana. Useful when used with provisioning.
; preinstall_sync =
; preinstall_sync =
# Disables preinstall feature. It has the same effect as setting preinstall to an empty list.
; preinstall_disabled = false
@@ -65,19 +65,53 @@ To enable the Azure AD/Entra ID OAuth, register your application with Entra ID.
- **Federated credentials**
1. Refer to [Configure an application to trust a managed identity (preview)](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-config-app-trust-managed-identity?tabs=microsoft-entra-admin-center) for a complete guide on setting up a managed identity as a federated credential.
Add a new entry under Federated credentials with the following configuration.
- **_Managed Identity_**
- Federated credential scenario: Select **Other issuer**.
- Issuer: The OAuth 2.0 / OIDC issuer URL of the Microsoft Entra ID authority. For example: `https://login.microsoftonline.com/{tenantID}/v2.0`.
- Subject identifier: The Object (Principal) ID GUID of the Managed Identity.
- Name: A unique descriptive name for the credential.
- Description: Grafana OAuth.
- Audience: The audience value that must appear in the external token. For Public cloud, it would be `api://AzureADTokenExchange`. See mentioned documentation for the full list of available audiences.
1. Refer to [Configure an application to trust a managed identity (preview)](https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation-config-app-trust-managed-identity?tabs=microsoft-entra-admin-center) for a complete guide on setting up a managed identity as a federated credential.
Add a new entry under Federated credentials with the following configuration.
1. Click **Add**, and then copy the Managed Identity Client ID and the federated credential Audience values. This is your OAuth 2.0 federated credential.
- Federated credential scenario: Select **Other issuer**.
- Issuer: The OAuth 2.0 / OIDC issuer URL of the Microsoft Entra ID authority. For example: `https://login.microsoftonline.com/{tenantID}/v2.0`.
- Subject identifier: The Object (Principal) ID GUID of the Managed Identity.
- Name: A unique descriptive name for the credential.
- Description: Grafana OAuth.
- Audience: The audience value that must appear in the external token. For Public cloud, it would be `api://AzureADTokenExchange`. See mentioned documentation for the full list of available audiences.
1. You must have set `client_authentication` under `[auth.azuread]` to `managed_identity` in the Grafana server configuration for this to work.
1. Click **Add**, and then copy the Managed Identity Client ID and the federated credential Audience values. This is your OAuth 2.0 federated credential.
1. You must have set `client_authentication` under `[auth.azuread]` to `managed_identity` in the Grafana server configuration for this to work.
{{< admonition type="note" >}}
Managed identities as federated credentials are only applicable to workloads hosted in Azure.
You can only add user-assigned managed identities as federated credentials on Entra ID applications.
{{< /admonition >}}
- **_Workload Identity (K8s/AKS)_**
1. Refer to [Federated identity credential for an Azure AD application](https://azure.github.io/azure-workload-identity/docs/topics/federated-identity-credential.html#azure-portal-ui) for a complete guide on setting up a federated credential for workload identity.
Add a new entry under Federated credentials with the following configuration.
- Federated credential scenario: Select **Kubernetes accessing Azure resources**.
- [Cluster issuer URL](https://learn.microsoft.com/en-us/azure/aks/use-oidc-issuer#get-the-oidc-issuer-url): The OIDC issuer URL that your cluster is integrated with. For example: `https://{region}.oic.prod-aks.azure.com/{tenant_id}/{uuid}`.
- Namespace: Namespace of your Grafana deployment. For example: `grafana`.
- Service account name: Service account name of your Grafana deployment. For example: `grafana`.
- Subject identifier: The expected identity (subject claim) from the OIDC token, which Azure uses to validate and authorize token issuance to the requesting workload. For example: `system:serviceaccount:grafana:grafana`.
- Name: A unique descriptive name for the credential.
- Description: Grafana OAuth.
- Audience: The audience value that must appear in the external token. For Public cloud, it would be `api://AzureADTokenExchange`. See mentioned documentation for the full list of available audiences.
1. You must have set `client_authentication` (env var `GF_AUTH_AZUREAD_CLIENT_AUTHENTICATION`) under `[auth.azuread]` to `workload_identity` in the Grafana server configuration for this to work.
1. You may optionally set `workload_identity_token_file` (env var `GF_AUTH_AZUREAD_WORKLOAD_IDENTITY_TOKEN_FILE`) under `[auth.azuread]` to `/var/run/secrets/azure/tokens/azure-identity-token` in the Grafana server configuration for this to work. (Optional, defaults to `/var/run/secrets/azure/tokens/azure-identity-token`)
1. You must have set `client_id` (env var `GF_AUTH_AZUREAD_CLIENT_ID`) under `[auth.azuread]` in the Grafana server configuration for this to work. This must match the Entra ID/Azure AD App Registration Application (client) ID.
1. You must have set `token_url` (env var `GF_AUTH_AZUREAD_TOKEN_URL`) under `[auth.azuread]` to `https://login.microsoftonline.com/{tenantID}/oauth2/v2.0/token` in the Grafana server configuration for this to work.
1. You must have set `auth_url` (env var `GF_AUTH_AZUREAD_AUTH_URL`) under `[auth.azuread]` to `https://login.microsoftonline.com/{tenantID}/oauth2/v2.0/authorize` in the Grafana server configuration for this to work.
1. You must have set `federated_credential_audience` (env var `GF_AUTH_AZUREAD_FEDERATED_CREDENTIAL_AUDIENCE`) under `[auth.azuread]` to `api://AzureADTokenExchange` in the Grafana server configuration for this to work.
{{< admonition type="note" >}}
Managed identities as federated credentials are only applicable to workloads hosted in Azure.
@@ -498,32 +532,35 @@ skip_org_role_sync = true
The following table outlines the various Azure AD/Entra ID configuration options. You can apply these options as environment variables, similar to any other configuration within Grafana. For more information, refer to [Override configuration with environment variables](../../../configure-grafana/#override-configuration-with-environment-variables).
| Setting | Required | Supported on Cloud | Description | Default |
| ---------------------------- | -------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| `enabled` | No | Yes | Enables Azure AD/Entra ID authentication. | `false` |
| `name` | No | Yes | Name that refers to the Azure AD/Entra ID authentication from the Grafana user interface. | `OAuth` |
| `icon` | No | Yes | Icon used for the Azure AD/Entra ID authentication in the Grafana user interface. | `signin` |
| `client_id` | Yes | Yes | Client ID of the App (`Application (client) ID` on the **App registration** dashboard). | |
| `client_secret` | Yes | Yes | Client secret of the App. | |
| `auth_url` | Yes | Yes | Authorization endpoint of the Azure AD/Entra ID OAuth2 provider. | |
| `token_url` | Yes | Yes | Endpoint used to obtain the OAuth2 access token. | |
| `auth_style` | No | Yes | Name of the [OAuth2 AuthStyle](https://pkg.go.dev/golang.org/x/oauth2#AuthStyle) to be used when ID token is requested from OAuth2 provider. It determines how `client_id` and `client_secret` are sent to Oauth2 provider. Available values are `AutoDetect`, `InParams` and `InHeader`. | `AutoDetect` |
| `scopes` | No | Yes | List of comma- or space-separated OAuth2 scopes. | `openid email profile` |
| `allow_sign_up` | No | Yes | Controls Grafana user creation through the Azure AD/Entra ID login. Only existing Grafana users can log in with Azure AD/Entra ID if set to `false`. | `true` |
| `auto_login` | No | Yes | Set to `true` to enable users to bypass the login screen and automatically log in. This setting is ignored if you configure multiple auth providers to use auto-login. | `false` |
| `role_attribute_strict` | No | Yes | Set to `true` to deny user login if the Grafana org role cannot be extracted using `role_attribute_path` or `org_mapping`. For more information on user role mapping, refer to [Map roles](#map-roles). | `false` |
| `org_attribute_path` | No | No | [JMESPath](http://jmespath.org/examples.html) expression to use for Grafana org to role lookup. Grafana will first evaluate the expression using the OAuth2 ID token. If no value is returned, the expression will be evaluated using the user information obtained from the UserInfo endpoint. The result of the evaluation will be mapped to org roles based on `org_mapping`. For more information on org to role mapping, refer to [Org roles mapping example](#org-roles-mapping-example). | |
| `org_mapping` | No | No | List of comma- or space-separated `<ExternalOrgName>:<OrgIdOrName>:<Role>` mappings. Value can be `*` meaning "All users". Role is optional and can have the following values: `None`, `Viewer`, `Editor` or `Admin`. For more information on external organization to role mapping, refer to [Org roles mapping example](#org-roles-mapping-example). | |
| `allow_assign_grafana_admin` | No | No | Set to `true` to automatically sync the Grafana server administrator role. When enabled, if the Azure AD/Entra ID user's App role is `GrafanaAdmin`, Grafana grants the user server administrator privileges and the organization administrator role. If disabled, the user will only receive the organization administrator role. For more details on user role mapping, refer to [Map roles](#map-roles). | `false` |
| `skip_org_role_sync` | No | Yes | Set to `true` to stop automatically syncing user roles. This will allow you to set organization roles for your users from within Grafana manually. | `false` |
| `allowed_groups` | No | Yes | List of comma- or space-separated groups. The user should be a member of at least one group to log in. If you configure `allowed_groups`, you must also configure Azure AD/Entra ID to include the `groups` claim following [Configure group membership claims on the Azure Portal](#configure-group-membership-claims-on-the-azure-portal). | |
| `allowed_organizations` | No | Yes | List of comma- or space-separated Azure tenant identifiers. The user should be a member of at least one tenant to log in. | |
| `allowed_domains` | No | Yes | List of comma- or space-separated domains. The user should belong to at least one domain to log in. | |
| `tls_skip_verify_insecure` | No | No | If set to `true`, the client accepts any certificate presented by the server and any host name in that certificate. _You should only use this for testing_, because this mode leaves SSL/TLS susceptible to man-in-the-middle attacks. | `false` |
| `tls_client_cert` | No | No | The path to the certificate. | |
| `tls_client_key` | No | No | The path to the key. | |
| `tls_client_ca` | No | No | The path to the trusted certificate authority list. | |
| `use_pkce` | No | Yes | Set to `true` to use [Proof Key for Code Exchange (PKCE)](https://datatracker.ietf.org/doc/html/rfc7636). Grafana uses the SHA256 based `S256` challenge method and a 128 bytes (base64url encoded) code verifier. | `true` |
| `use_refresh_token` | No | Yes | Enables the use of refresh tokens and checks for access token expiration. When enabled, Grafana automatically adds the `offline_access` scope to the list of scopes. | `true` |
| `force_use_graph_api` | No | Yes | Set to `true` to always fetch groups from the Microsoft Graph API instead of the `id_token`. If a user belongs to more than 200 groups, the Microsoft Graph API will be used to retrieve the groups regardless of this setting. | `false` |
| `signout_redirect_url` | No | Yes | URL to redirect to after the user logs out. | |
| Setting | Required | Supported on Cloud | Description | Default |
| ------------------------------- | -------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `enabled` | No | Yes | Enables Azure AD/Entra ID authentication. | `false` |
| `name` | No | Yes | Name that refers to the Azure AD/Entra ID authentication from the Grafana user interface. | `OAuth` |
| `icon` | No | Yes | Icon used for the Azure AD/Entra ID authentication in the Grafana user interface. | `signin` |
| `client_authentication` | Yes | Yes | Defines the client authentication method used to authenticate to the token endpoint. Supported values: `none`, `client_secret_post`, `managed_identity`, or `workload_identity`. | |
| `workload_identity_token_file` | No | Yes | The path to the token file used to authenticate to the OAuth2 provider. This is only required when `client_authentication` is set to `workload_identity`. The token file contains the service account token projected by Kubernetes. | `/var/run/secrets/azure/tokens/azure-identity-token` |
| `federated_credential_audience` | No | Yes | The audience of the federated identity credential of your OAuth2 app. Required when `client_authentication` is set to `managed_identity` or `workload_identity`. For public cloud, this is typically `api://AzureADTokenExchange`. | `api://AzureADTokenExchange` |
| `client_id` | Yes | Yes | Client ID of the App (`Application (client) ID` on the **App registration** dashboard). | |
| `client_secret` | Yes | Yes | Client secret of the App. | |
| `auth_url` | Yes | Yes | Authorization endpoint of the Azure AD/Entra ID OAuth2 provider. | |
| `token_url` | Yes | Yes | Endpoint used to obtain the OAuth2 access token. | |
| `auth_style` | No | Yes | Name of the [OAuth2 AuthStyle](https://pkg.go.dev/golang.org/x/oauth2#AuthStyle) to be used when ID token is requested from OAuth2 provider. It determines how `client_id` and `client_secret` are sent to Oauth2 provider. Available values are `AutoDetect`, `InParams` and `InHeader`. | `AutoDetect` |
| `scopes` | No | Yes | List of comma- or space-separated OAuth2 scopes. | `openid email profile` |
| `allow_sign_up` | No | Yes | Controls Grafana user creation through the Azure AD/Entra ID login. Only existing Grafana users can log in with Azure AD/Entra ID if set to `false`. | `true` |
| `auto_login` | No | Yes | Set to `true` to enable users to bypass the login screen and automatically log in. This setting is ignored if you configure multiple auth providers to use auto-login. | `false` |
| `role_attribute_strict` | No | Yes | Set to `true` to deny user login if the Grafana org role cannot be extracted using `role_attribute_path` or `org_mapping`. For more information on user role mapping, refer to [Map roles](#map-roles). | `false` |
| `org_attribute_path` | No | No | [JMESPath](http://jmespath.org/examples.html) expression to use for Grafana org to role lookup. Grafana will first evaluate the expression using the OAuth2 ID token. If no value is returned, the expression will be evaluated using the user information obtained from the UserInfo endpoint. The result of the evaluation will be mapped to org roles based on `org_mapping`. For more information on org to role mapping, refer to [Org roles mapping example](#org-roles-mapping-example). | |
| `org_mapping` | No | No | List of comma- or space-separated `<ExternalOrgName>:<OrgIdOrName>:<Role>` mappings. Value can be `*` meaning "All users". Role is optional and can have the following values: `None`, `Viewer`, `Editor` or `Admin`. For more information on external organization to role mapping, refer to [Org roles mapping example](#org-roles-mapping-example). | |
| `allow_assign_grafana_admin` | No | No | Set to `true` to automatically sync the Grafana server administrator role. When enabled, if the Azure AD/Entra ID user's App role is `GrafanaAdmin`, Grafana grants the user server administrator privileges and the organization administrator role. If disabled, the user will only receive the organization administrator role. For more details on user role mapping, refer to [Map roles](#map-roles). | `false` |
| `skip_org_role_sync` | No | Yes | Set to `true` to stop automatically syncing user roles. This will allow you to set organization roles for your users from within Grafana manually. | `false` |
| `allowed_groups` | No | Yes | List of comma- or space-separated groups. The user should be a member of at least one group to log in. If you configure `allowed_groups`, you must also configure Azure AD/Entra ID to include the `groups` claim following [Configure group membership claims on the Azure Portal](#configure-group-membership-claims-on-the-azure-portal). | |
| `allowed_organizations` | No | Yes | List of comma- or space-separated Azure tenant identifiers. The user should be a member of at least one tenant to log in. | |
| `allowed_domains` | No | Yes | List of comma- or space-separated domains. The user should belong to at least one domain to log in. | |
| `tls_skip_verify_insecure` | No | No | If set to `true`, the client accepts any certificate presented by the server and any host name in that certificate. _You should only use this for testing_, because this mode leaves SSL/TLS susceptible to man-in-the-middle attacks. | `false` |
| `tls_client_cert` | No | No | The path to the certificate. | |
| `tls_client_key` | No | No | The path to the key. | |
| `tls_client_ca` | No | No | The path to the trusted certificate authority list. | |
| `use_pkce` | No | Yes | Set to `true` to use [Proof Key for Code Exchange (PKCE)](https://datatracker.ietf.org/doc/html/rfc7636). Grafana uses the SHA256 based `S256` challenge method and a 128 bytes (base64url encoded) code verifier. | `true` |
| `use_refresh_token` | No | Yes | Enables the use of refresh tokens and checks for access token expiration. When enabled, Grafana automatically adds the `offline_access` scope to the list of scopes. | `true` |
| `force_use_graph_api` | No | Yes | Set to `true` to always fetch groups from the Microsoft Graph API instead of the `id_token`. If a user belongs to more than 200 groups, the Microsoft Graph API will be used to retrieve the groups regardless of this setting. | `false` |
| `signout_redirect_url` | No | Yes | URL to redirect to after the user logs out. | |
@@ -385,6 +385,12 @@ func validateClientAuthentication(info *social.OAuthInfo, requester identity.Req
}
return nil
case social.WorkloadIdentity:
if info.WorkloadIdentityTokenFile == "" {
return ssosettings.ErrInvalidOAuthConfig("Workload identity token file is required for Workload identity authentication.")
}
return nil
case social.ClientSecretPost, "":
if info.ClientSecret == "" {
return ssosettings.ErrInvalidOAuthConfig("Client secret is required for Client secret authentication.")
@@ -124,6 +124,7 @@ func (s *SocialBase) getBaseSupportBundleContent(bf *bytes.Buffer) error {
fmt.Fprintf(bf, "client_secret = %v ; issue if empty\n", strings.Repeat("*", len(s.ClientSecret)))
fmt.Fprintf(bf, "managed_identity_client_id = %v\n", s.info.ManagedIdentityClientID)
fmt.Fprintf(bf, "federated_credential_audience = %v\n", s.info.FederatedCredentialAudience)
fmt.Fprintf(bf, "workload_identity_token_file = %v\n", s.info.WorkloadIdentityTokenFile)
fmt.Fprintf(bf, "auth_url = %v\n", s.Endpoint.AuthURL)
fmt.Fprintf(bf, "token_url = %v\n", s.Endpoint.TokenURL)
fmt.Fprintf(bf, "auth_style = %v\n", s.Endpoint.AuthStyle)
+3 -1
View File
@@ -18,7 +18,8 @@ const (
ClientSecretPost = "client_secret_post"
None = "none"
// Azure AD
ManagedIdentity = "managed_identity"
ManagedIdentity = "managed_identity"
WorkloadIdentity = "workload_identity"
// Other providers...
AzureADProviderName = "azuread"
@@ -72,6 +73,7 @@ type OAuthInfo struct {
ClientId string `mapstructure:"client_id" toml:"client_id"`
ClientSecret string `mapstructure:"client_secret" toml:"-"`
ManagedIdentityClientID string `mapstructure:"managed_identity_client_id" toml:"managed_identity_client_id"`
WorkloadIdentityTokenFile string `mapstructure:"workload_identity_token_file" toml:"workload_identity_token_file"`
FederatedCredentialAudience string `mapstructure:"federated_credential_audience" toml:"federated_credential_audience"`
EmailAttributeName string `mapstructure:"email_attribute_name" toml:"email_attribute_name"`
EmailAttributePath string `mapstructure:"email_attribute_path" toml:"email_attribute_path"`
+15
View File
@@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"net/url"
"os"
"strings"
"golang.org/x/oauth2"
@@ -137,7 +138,21 @@ func (c *OAuth) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden
}
clientCtx := context.WithValue(ctx, oauth2.HTTPClient, httpClient)
// exchange auth code to a valid token
if oauthCfg.ClientAuthentication == social.WorkloadIdentity {
federatedToken, err := os.ReadFile(oauthCfg.WorkloadIdentityTokenFile)
if err != nil {
return nil, fmt.Errorf("failed to read workload identity token file: %w", err)
}
opts = append(opts,
oauth2.SetAuthURLParam("client_id", oauthCfg.ClientId),
oauth2.SetAuthURLParam("client_assertion", strings.TrimSpace(string(federatedToken))),
oauth2.SetAuthURLParam("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"),
)
}
token, err := connector.Exchange(clientCtx, r.HTTPRequest.URL.Query().Get("code"), opts...)
if err != nil {
return nil, errOAuthTokenExchange.Errorf("failed to exchange code to token: %w", err)
@@ -75,6 +75,7 @@ func (s *OAuthStrategy) loadSettingsForProvider(provider string) map[string]any
"client_secret": section.Key("client_secret").Value(),
"managed_identity_client_id": section.Key("managed_identity_client_id").Value(),
"federated_credential_audience": section.Key("federated_credential_audience").Value(),
"workload_identity_token_file": section.Key("workload_identity_token_file").Value(),
"scopes": section.Key("scopes").Value(),
"empty_scopes": section.Key("empty_scopes").MustBool(false),
"auth_style": section.Key("auth_style").Value(),
@@ -24,6 +24,7 @@ var (
client_secret = test_client_secret
managed_identity_client_id = test_managed_identity_client_id
federated_credential_audience = test_federated_credential_audience
workload_identity_token_file = test_workload_identity_token_file
scopes = openid, profile, email
empty_scopes = false
email_attribute_name = email:primary
@@ -70,6 +71,7 @@ var (
"client_secret": "test_client_secret",
"managed_identity_client_id": "test_managed_identity_client_id",
"federated_credential_audience": "test_federated_credential_audience",
"workload_identity_token_file": "test_workload_identity_token_file",
"scopes": "openid, profile, email",
"empty_scopes": false,
"email_attribute_name": "email:primary",
@@ -125,6 +127,7 @@ func TestGetProviderConfig_ExtraFields(t *testing.T) {
[auth.azuread]
force_use_graph_api = true
allowed_organizations = org1, org2
workload_identity_token_file = azuread_token_file
[auth.github]
team_ids = first, second
@@ -159,6 +162,7 @@ func TestGetProviderConfig_ExtraFields(t *testing.T) {
require.Equal(t, true, result["force_use_graph_api"])
require.Equal(t, "org1, org2", result["allowed_organizations"])
require.Equal(t, "azuread_token_file", result["workload_identity_token_file"])
})
t.Run(social.GitHubProviderName, func(t *testing.T) {
@@ -210,7 +214,7 @@ func TestGetProviderConfig_GrafanaComGrafanaNet(t *testing.T) {
[auth.grafana_com]
enabled = true
client_id = grafanaComClientId
[auth.grafananet]
enabled = false
client_id = grafanaNetClientId`,
@@ -225,7 +229,7 @@ func TestGetProviderConfig_GrafanaComGrafanaNet(t *testing.T) {
[auth.grafana_com]
enabled = false
client_id = grafanaComClientId
[auth.grafananet]
enabled = true
client_id = grafanaNetClientId`,
@@ -240,7 +244,7 @@ func TestGetProviderConfig_GrafanaComGrafanaNet(t *testing.T) {
[auth.grafana_com]
enabled = true
client_id = grafanaComClientId
[auth.grafananet]
enabled = true
client_id = grafanaNetClientId`,
@@ -255,7 +259,7 @@ func TestGetProviderConfig_GrafanaComGrafanaNet(t *testing.T) {
[auth.grafana_com]
enabled = false
client_id = grafanaComClientId
[auth.grafananet]
enabled = false
client_id = grafanaNetClientId`,
@@ -7,7 +7,7 @@ import { Checkbox, Field, Input, SecretInput, Select, Switch, useTheme2 } from '
import { fieldMap } from './fields';
import { SSOProviderDTO, SSOSettingsField } from './types';
import { isSelectableValue } from './utils/guards';
import { isSelectableValueArray } from './utils/guards';
interface FieldRendererProps
extends Pick<
@@ -123,7 +123,7 @@ export const FieldRenderer = ({
const watchOptions = watch(name);
let options = fieldData.options;
if (!fieldData.options?.length) {
options = isSelectableValue(watchOptions) ? watchOptions : [];
options = isSelectableValueArray(watchOptions) ? watchOptions : [];
}
return (
<Field key={name} {...fieldProps} htmlFor={name}>
+33 -4
View File
@@ -9,7 +9,7 @@ import { contextSrv } from 'app/core/core';
import { ServerDiscoveryField } from './components/ServerDiscoveryField';
import { FieldData, SSOProvider, SSOSettingsField } from './types';
import { isSelectableValue } from './utils/guards';
import { isSelectableValue, isSelectableValueArray } from './utils/guards';
import { isUrlValid } from './utils/url';
type Section = Record<
@@ -53,7 +53,7 @@ export const getSectionFields = (): Section => {
fields: ['roleAttributeStrict', 'orgMapping', 'allowAssignGrafanaAdmin', 'skipOrgRoleSync'],
},
{
name: 'Extra security measures',
name: extraSecurityLabel,
id: 'extra',
fields: [
'allowedOrganizations',
@@ -66,6 +66,7 @@ export const getSectionFields = (): Section => {
'tlsClientCert',
'tlsClientKey',
'tlsClientCa',
'workloadIdentityTokenFile',
],
},
],
@@ -284,6 +285,7 @@ const authURLLabel = 'Auth URL';
const tokenURLLabel = 'Token URL';
const apiURLLabel = 'API URL';
const jmesPathLabel = 'JMESPath';
const workloadIdentityLabel = 'Workload identity';
/**
* List all the fields that can be used in the form
@@ -357,6 +359,32 @@ export function fieldMap(provider: string): Record<string, FieldData> {
'The audience of the federated identity credential of your OAuth2 app.'
),
},
workloadIdentityTokenFile: {
label: t('auth-config.fields.workload-identity-token-file-label', '{{ workloadIdentityLabel }} token file', {
workloadIdentityLabel,
}),
type: 'text',
description: t(
'auth-config.fields.workload-identity-token-file-description',
'The file path to the token file used to authenticate to the OAuth2 provider. This is only required when client authentication is set to "workload_identity". Defaults to /var/run/secrets/azure/tokens/azure-identity-token.'
),
validation: {
validate: (value, formValues) => {
let clientAuth = formValues.clientAuthentication;
if (isSelectableValue<string>(clientAuth)) {
clientAuth = clientAuth.value;
}
if (clientAuth === 'workload_identity') {
return !!value;
}
return true;
},
message: t(
'auth-config.fields.workload-identity-token-file-required',
'This field must be set when client authentication is set to "Workload identity".'
),
},
},
allowedOrganizations: {
label: t('auth-config.fields.allowed-organizations-label', 'Allowed organizations'),
type: 'select',
@@ -465,7 +493,7 @@ export function fieldMap(provider: string): Record<string, FieldData> {
if (typeof value === 'string') {
return uuidValidate(value);
}
if (isSelectableValue(value)) {
if (isSelectableValueArray(value)) {
return value.every((v) => v?.value && uuidValidate(v.value));
}
return true;
@@ -809,7 +837,7 @@ export function fieldMap(provider: string): Record<string, FieldData> {
if (typeof value === 'string') {
return isNumeric(value);
}
if (isSelectableValue(value)) {
if (isSelectableValueArray(value)) {
return value.every((v) => v?.value && isNumeric(v.value));
}
return true;
@@ -893,6 +921,7 @@ function clientAuthenticationOptions(provider: string): Array<SelectableValue<st
{ value: 'none', label: t('auth-config.fields.client-authentication-none', 'None') },
{ value: 'client_secret_post', label: 'Client secret' },
{ value: 'managed_identity', label: 'Managed identity' },
{ value: 'workload_identity', label: 'Workload identity' },
];
// Other providers ...
default:
+1
View File
@@ -26,6 +26,7 @@ export type SSOProviderSettingsBase = {
clientSecret: string;
managedIdentityClientId?: string;
federatedCredentialAudience?: string;
workloadIdentityTokenFile?: string;
emailAttributeName?: string;
emailAttributePath?: string;
emptyScopes?: boolean;
@@ -3,7 +3,7 @@ import { SelectableValue } from '@grafana/data';
import { fieldMap, getSectionFields } from '../fields';
import { FieldData, SSOProvider, SSOProviderDTO } from '../types';
import { isSelectableValue } from './guards';
import { isSelectableValueArray } from './guards';
export const emptySettings: SSOProviderDTO = {
allowAssignGrafanaAdmin: false,
@@ -20,6 +20,7 @@ export const emptySettings: SSOProviderDTO = {
clientSecret: '',
managedIdentityClientId: '',
federatedCredentialAudience: '',
workloadIdentityTokenFile: '',
emailAttributeName: '',
emailAttributePath: '',
emptyScopes: false,
@@ -118,10 +119,10 @@ export function dtoToData(dto: SSOProviderDTO, provider: string) {
for (const field of arrayFields) {
const value = current[field];
if (value) {
if (isSelectableValue(value)) {
if (isSelectableValueArray(value)) {
//@ts-expect-error
settings[field] = valuesToString(value);
} else if (isSelectableValue([value])) {
} else if (isSelectableValueArray([value])) {
//@ts-expect-error
settings[field] = value.value;
}
@@ -1,5 +1,9 @@
import { SelectableValue } from '@grafana/data';
export function isSelectableValue(value: unknown): value is SelectableValue[] {
export function isSelectableValueArray(value: unknown): value is SelectableValue[] {
return Array.isArray(value) && value.every((v) => typeof v === 'object' && v !== null && 'value' in v);
}
export function isSelectableValue<T>(value: unknown): value is SelectableValue<T> {
return typeof value === 'object' && value !== null && 'value' in value;
}
+4 -1
View File
@@ -3094,7 +3094,10 @@
"use-refresh-token-description": "If enabled, Grafana will fetch a new access token using the refresh token provided by the OAuth2 provider.",
"use-refresh-token-label": "Use refresh token",
"validate-hosted-domain-description": "If enabled, Grafana will match the Hosted Domain retrieved from the Google ID Token against the \"{{ allowedDomainsLabel }}\" list specified by the user.",
"validate-hosted-domain-label": "Validate hosted domain"
"validate-hosted-domain-label": "Validate hosted domain",
"workload-identity-token-file-description": "The file path to the token file used to authenticate to the OAuth2 provider. This is only required when client authentication is set to \"workload_identity\". Defaults to /var/run/secrets/azure/tokens/azure-identity-token.",
"workload-identity-token-file-label": "{{ workloadIdentityLabel }} token file",
"workload-identity-token-file-required": "This field must be set when client authentication is set to \"Workload identity\"."
},
"get-page-nav": {
"subTitle": {