mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Azure: Add support for Workload Identity authentication (#75681)
* Update Azure Monitor * Update Prometheus * Update README * Update docs/sources/datasources/azure-monitor/_index.md Co-authored-by: Andrew Hackmann <5140848+bossinc@users.noreply.github.com> * Update docs/sources/datasources/azure-monitor/_index.md Co-authored-by: Beverly <131809838+BeverlyJaneJ@users.noreply.github.com> * Update docs/sources/datasources/azure-monitor/_index.md Co-authored-by: Beverly <131809838+BeverlyJaneJ@users.noreply.github.com> * Update docs/sources/datasources/azure-monitor/_index.md Co-authored-by: Beverly <131809838+BeverlyJaneJ@users.noreply.github.com> * README updates * Fix prettier * memoize options --------- Co-authored-by: Andrew Hackmann <5140848+bossinc@users.noreply.github.com> Co-authored-by: Beverly <131809838+BeverlyJaneJ@users.noreply.github.com>
This commit is contained in:
parent
9d44fefe82
commit
5796836662
@ -63,6 +63,9 @@ For more information, refer to [Azure documentation for role assignments](https:
|
||||
If you host Grafana in Azure, such as in App Service or Azure Virtual Machines, you can configure the Azure Monitor data source to use Managed Identity for secure authentication without entering credentials into Grafana.
|
||||
For details, refer to [Configuring using Managed Identity](#configuring-using-managed-identity).
|
||||
|
||||
You can configure the Azure Monitor data source to use Workload Identity for secure authentication without entering credentials into Grafana if you host Grafana in a Kubernetes environment, such as AKS, and require access to Azure resources.
|
||||
For details, refer to [Configuring using Workload Identity](#configuring-using-workload-identity).
|
||||
|
||||
| Name | Description |
|
||||
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Authentication** | Enables Managed Identity. Selecting Managed Identity hides many of the other fields. For details, see [Configuring using Managed Identity](#configuring-using-managed-identity). |
|
||||
@ -114,6 +117,21 @@ datasources:
|
||||
version: 1
|
||||
```
|
||||
|
||||
**Workload Identity:**
|
||||
|
||||
```yaml
|
||||
apiVersion: 1 # config file version
|
||||
|
||||
datasources:
|
||||
- name: Azure Monitor
|
||||
type: grafana-azure-monitor-datasource
|
||||
access: proxy
|
||||
jsonData:
|
||||
azureAuthType: workloadidentity
|
||||
subscriptionId: <subscription-id> # Optional, default subscription
|
||||
version: 1
|
||||
```
|
||||
|
||||
#### Supported cloud names
|
||||
|
||||
| Azure Cloud | `cloudName` Value |
|
||||
@ -124,8 +142,8 @@ datasources:
|
||||
|
||||
### Configure Managed Identity
|
||||
|
||||
If you host Grafana in Azure, such as an App Service or with Azure Virtual Machines, and have managed identity enabled on your VM, you can use managed identity to configure Azure Monitor in Grafana.
|
||||
This lets you securely authenticate data sources without manually configuring credentials via Azure AD App Registrations for each.
|
||||
You can use managed identity to configure Azure Monitor in Grafana if you host Grafana in Azure (such as an App Service or with Azure Virtual Machines) and have managed identity enabled on your VM.
|
||||
This lets you securely authenticate data sources without manually configuring credentials via Azure AD App Registrations.
|
||||
For details on Azure managed identities, refer to the [Azure documentation](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview).
|
||||
|
||||
**To enable managed identity for Grafana:**
|
||||
@ -141,7 +159,46 @@ For details on Azure managed identities, refer to the [Azure documentation](http
|
||||
|
||||
This hides the directory ID, application ID, and client secret fields, and the data source uses managed identity to authenticate to Azure Monitor Metrics and Logs, and Azure Resource Graph.
|
||||
|
||||
{{< figure src="/media/docs/grafana/data-sources/screenshot-managed-identity.png" max-width="800px" class="docs-image--no-shadow" caption="Azure Monitor Metrics screenshot showing Dimensions" >}}
|
||||
{{< figure src="/media/docs/grafana/data-sources/screenshot-managed-identity-2.png" max-width="800px" class="docs-image--no-shadow" caption="Azure Monitor screenshot showing Managed Identity authentication" >}}
|
||||
|
||||
3. You can set the `managed_identity_client_id` field in the `[azure]` section of the [Grafana server configuration][configure-grafana-azure] to allow a user-assigned managed identity to be used instead of the default system-assigned identity.
|
||||
|
||||
```ini
|
||||
[azure]
|
||||
managed_identity_enabled = true
|
||||
managed_identity_client_id = USER_ASSIGNED_IDENTITY_CLIENT_ID
|
||||
```
|
||||
|
||||
### Configure Workload Identity
|
||||
|
||||
You can use workload identity to configure Azure Monitor in Grafana if you host Grafana in a Kubernetes environment, such as AKS, in conjunction with managed identities.
|
||||
This lets you securely authenticate data sources without manually configuring credentials via Azure AD App Registrations.
|
||||
For details on workload identity, refer to the [Azure workload identity documentation](https://azure.github.io/azure-workload-identity/docs/).
|
||||
|
||||
**To enable workload identity for Grafana:**
|
||||
|
||||
1. Set the `workload_identity_enabled` flag in the `[azure]` section of the [Grafana server configuration][configure-grafana-azure].
|
||||
|
||||
```ini
|
||||
[azure]
|
||||
workload_identity_enabled = true
|
||||
```
|
||||
|
||||
2. In the Azure Monitor data source configuration, set **Authentication** to **Workload Identity**.
|
||||
|
||||
This hides the directory ID, application ID, and client secret fields, and the data source uses workload identity to authenticate to Azure Monitor Metrics and Logs, and Azure Resource Graph.
|
||||
|
||||
{{< figure src="/media/docs/grafana/data-sources/screenshot-workload-identity.png" max-width="800px" class="docs-image--no-shadow" caption="Azure Monitor screenshot showing Workload Identity authentication" >}}
|
||||
|
||||
3. There are additional configuration variables that can control the authentication method.`workload_identity_tenant_id` represents the Azure AD tenant that contains the managed identity, `workload_identity_client_id` represents the client ID of the managed identity if it differs from the default client ID, `workload_identity_token_file` represents the path to the token file. Refer to the [documentation](https://azure.github.io/azure-workload-identity/docs/) for more information on what values these variables should use, if any.
|
||||
|
||||
```ini
|
||||
[azure]
|
||||
workload_identity_enabled = true
|
||||
workload_identity_tenant_id = IDENTITY_TENANT_ID
|
||||
workload_identity_client_id = IDENTITY_CLIENT_ID
|
||||
workload_identity_token_file = TOKEN_FILE_PATH
|
||||
```
|
||||
|
||||
## Query the data source
|
||||
|
||||
|
@ -31,10 +31,14 @@ func getAuthType(cfg *setting.Cfg, jsonData *types.AzureClientSettings) string {
|
||||
return azcredentials.AzureAuthClientSecret
|
||||
}
|
||||
|
||||
// For newly created datasource with no configuration, managed identity is the default authentication type
|
||||
// if they are enabled in Grafana config
|
||||
// For newly created datasource with no configuration the order is as follows:
|
||||
// Managed identity is the default if enabled
|
||||
// Workload identity is the next option if enabled
|
||||
// Client secret is the final fallback
|
||||
if cfg.Azure.ManagedIdentityEnabled {
|
||||
return azcredentials.AzureAuthManagedIdentity
|
||||
} else if cfg.Azure.WorkloadIdentityEnabled {
|
||||
return azcredentials.AzureAuthWorkloadIdentity
|
||||
} else {
|
||||
return azcredentials.AzureAuthClientSecret
|
||||
}
|
||||
@ -84,8 +88,8 @@ func normalizeAzureCloud(cloudName string) (string, error) {
|
||||
func getAzureCloud(cfg *setting.Cfg, jsonData *types.AzureClientSettings) (string, error) {
|
||||
authType := getAuthType(cfg, jsonData)
|
||||
switch authType {
|
||||
case azcredentials.AzureAuthManagedIdentity:
|
||||
// In case of managed identity, the cloud is always same as where Grafana is hosted
|
||||
case azcredentials.AzureAuthManagedIdentity, azcredentials.AzureAuthWorkloadIdentity:
|
||||
// In case of managed identity and workload identity, the cloud is always same as where Grafana is hosted
|
||||
return getDefaultAzureCloud(cfg)
|
||||
case azcredentials.AzureAuthClientSecret:
|
||||
if cloud := jsonData.CloudName; cloud != "" {
|
||||
@ -106,7 +110,9 @@ func getAzureCredentials(cfg *setting.Cfg, jsonData *types.AzureClientSettings,
|
||||
case azcredentials.AzureAuthManagedIdentity:
|
||||
credentials := &azcredentials.AzureManagedIdentityCredentials{}
|
||||
return credentials, nil
|
||||
|
||||
case azcredentials.AzureAuthWorkloadIdentity:
|
||||
credentials := &azcredentials.AzureWorkloadIdentityCredentials{}
|
||||
return credentials, nil
|
||||
case azcredentials.AzureAuthClientSecret:
|
||||
cloud, err := getAzureCloud(cfg, jsonData)
|
||||
if err != nil {
|
||||
|
@ -77,6 +77,66 @@ func TestCredentials_getAuthType(t *testing.T) {
|
||||
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when workload identities enabled", func(t *testing.T) {
|
||||
cfg.Azure.WorkloadIdentityEnabled = true
|
||||
|
||||
t.Run("should be client secret if auth type is set to client secret", func(t *testing.T) {
|
||||
jsonData := &types.AzureClientSettings{
|
||||
AzureAuthType: azcredentials.AzureAuthClientSecret,
|
||||
}
|
||||
|
||||
authType := getAuthType(cfg, jsonData)
|
||||
|
||||
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
|
||||
})
|
||||
|
||||
t.Run("should be workload identity if datasource not configured and managed identity is disabled", func(t *testing.T) {
|
||||
jsonData := &types.AzureClientSettings{
|
||||
AzureAuthType: "",
|
||||
}
|
||||
|
||||
authType := getAuthType(cfg, jsonData)
|
||||
|
||||
assert.Equal(t, azcredentials.AzureAuthWorkloadIdentity, authType)
|
||||
})
|
||||
|
||||
t.Run("should be client secret if auth type not specified but credentials configured", func(t *testing.T) {
|
||||
jsonData := &types.AzureClientSettings{
|
||||
AzureAuthType: "",
|
||||
TenantId: "9b9d90ee-a5cc-49c2-b97e-0d1b0f086b5c",
|
||||
ClientId: "849ccbb0-92eb-4226-b228-ef391abd8fe6",
|
||||
}
|
||||
|
||||
authType := getAuthType(cfg, jsonData)
|
||||
|
||||
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when workload identities disabled", func(t *testing.T) {
|
||||
cfg.Azure.WorkloadIdentityEnabled = false
|
||||
|
||||
t.Run("should be workload identity if auth type is set to workload identity", func(t *testing.T) {
|
||||
jsonData := &types.AzureClientSettings{
|
||||
AzureAuthType: azcredentials.AzureAuthWorkloadIdentity,
|
||||
}
|
||||
|
||||
authType := getAuthType(cfg, jsonData)
|
||||
|
||||
assert.Equal(t, azcredentials.AzureAuthWorkloadIdentity, authType)
|
||||
})
|
||||
|
||||
t.Run("should be client secret if datasource not configured", func(t *testing.T) {
|
||||
jsonData := &types.AzureClientSettings{
|
||||
AzureAuthType: "",
|
||||
}
|
||||
|
||||
authType := getAuthType(cfg, jsonData)
|
||||
|
||||
assert.Equal(t, azcredentials.AzureAuthClientSecret, authType)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestCredentials_getAzureCloud(t *testing.T) {
|
||||
|
@ -6,6 +6,7 @@ import AzureCredentialsForm, { Props } from './AzureCredentialsForm';
|
||||
const setup = (propsFunc?: (props: Props) => Props) => {
|
||||
let props: Props = {
|
||||
managedIdentityEnabled: false,
|
||||
workloadIdentityEnabled: false,
|
||||
credentials: {
|
||||
authType: 'clientsecret',
|
||||
azureCloud: 'azuremonitor',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import React, { ChangeEvent, useMemo } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { ConfigSection } from '@grafana/experimental';
|
||||
@ -9,39 +9,64 @@ import { AzureAuthType, AzureCredentials } from '../types';
|
||||
|
||||
export interface Props {
|
||||
managedIdentityEnabled: boolean;
|
||||
workloadIdentityEnabled: boolean;
|
||||
credentials: AzureCredentials;
|
||||
azureCloudOptions?: SelectableValue[];
|
||||
onCredentialsChange?: (updatedCredentials: AzureCredentials) => void;
|
||||
onCredentialsChange: (updatedCredentials: AzureCredentials) => void;
|
||||
disabled?: boolean;
|
||||
children?: JSX.Element;
|
||||
}
|
||||
|
||||
const authTypeOptions: Array<SelectableValue<AzureAuthType>> = [
|
||||
{
|
||||
value: 'msi',
|
||||
label: 'Managed Identity',
|
||||
},
|
||||
{
|
||||
value: 'clientsecret',
|
||||
label: 'App Registration',
|
||||
},
|
||||
];
|
||||
|
||||
export const AzureCredentialsForm = (props: Props) => {
|
||||
const { credentials, azureCloudOptions, onCredentialsChange, disabled, managedIdentityEnabled } = props;
|
||||
const {
|
||||
credentials,
|
||||
azureCloudOptions,
|
||||
onCredentialsChange,
|
||||
disabled,
|
||||
managedIdentityEnabled,
|
||||
workloadIdentityEnabled,
|
||||
} = props;
|
||||
|
||||
const authTypeOptions = useMemo(() => {
|
||||
let opts: Array<SelectableValue<AzureAuthType>> = [
|
||||
{
|
||||
value: 'clientsecret',
|
||||
label: 'App Registration',
|
||||
},
|
||||
];
|
||||
|
||||
if (managedIdentityEnabled) {
|
||||
opts.push({
|
||||
value: 'msi',
|
||||
label: 'Managed Identity',
|
||||
});
|
||||
}
|
||||
|
||||
if (workloadIdentityEnabled) {
|
||||
opts.push({
|
||||
value: 'workloadidentity',
|
||||
label: 'Workload Identity',
|
||||
});
|
||||
}
|
||||
|
||||
return opts;
|
||||
}, [managedIdentityEnabled, workloadIdentityEnabled]);
|
||||
|
||||
const onAuthTypeChange = (selected: SelectableValue<AzureAuthType>) => {
|
||||
if (onCredentialsChange) {
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
authType: selected.value || 'msi',
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
const defaultAuthType = managedIdentityEnabled
|
||||
? 'msi'
|
||||
: workloadIdentityEnabled
|
||||
? 'workloadidentity'
|
||||
: 'clientsecret';
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
authType: selected.value || defaultAuthType,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
};
|
||||
|
||||
const onAzureCloudChange = (selected: SelectableValue<string>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
if (credentials.authType === 'clientsecret') {
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
azureCloud: selected.value,
|
||||
@ -51,7 +76,7 @@ export const AzureCredentialsForm = (props: Props) => {
|
||||
};
|
||||
|
||||
const onTenantIdChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
if (credentials.authType === 'clientsecret') {
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
tenantId: event.target.value,
|
||||
@ -61,7 +86,7 @@ export const AzureCredentialsForm = (props: Props) => {
|
||||
};
|
||||
|
||||
const onClientIdChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
if (credentials.authType === 'clientsecret') {
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
clientId: event.target.value,
|
||||
@ -71,7 +96,7 @@ export const AzureCredentialsForm = (props: Props) => {
|
||||
};
|
||||
|
||||
const onClientSecretChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
if (credentials.authType === 'clientsecret') {
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
clientSecret: event.target.value,
|
||||
@ -81,7 +106,7 @@ export const AzureCredentialsForm = (props: Props) => {
|
||||
};
|
||||
|
||||
const onClientSecretReset = () => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
if (credentials.authType === 'clientsecret') {
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
clientSecret: '',
|
||||
@ -92,7 +117,7 @@ export const AzureCredentialsForm = (props: Props) => {
|
||||
|
||||
return (
|
||||
<ConfigSection title="Authentication">
|
||||
{managedIdentityEnabled && (
|
||||
{authTypeOptions.length > 1 && (
|
||||
<Field
|
||||
label="Authentication"
|
||||
description="Choose the type of authentication to Azure services"
|
||||
|
@ -45,6 +45,7 @@ export const MonitorConfig = (props: Props) => {
|
||||
<>
|
||||
<AzureCredentialsForm
|
||||
managedIdentityEnabled={config.azure.managedIdentityEnabled}
|
||||
workloadIdentityEnabled={config.azure.workloadIdentityEnabled}
|
||||
credentials={credentials}
|
||||
azureCloudOptions={azureClouds}
|
||||
onCredentialsChange={onCredentialsChange}
|
||||
|
@ -59,7 +59,8 @@ export function getAzureCloud(options: AzureDataSourceSettings | AzureDataSource
|
||||
const authType = getAuthType(options);
|
||||
switch (authType) {
|
||||
case 'msi':
|
||||
// In case of managed identity, the cloud is always same as where Grafana is hosted
|
||||
case 'workloadidentity':
|
||||
// In case of managed identity and workload identity, the cloud is always same as where Grafana is hosted
|
||||
return getDefaultAzureCloud();
|
||||
case 'clientsecret':
|
||||
return options.jsonData.cloudName || getDefaultAzureCloud();
|
||||
@ -79,6 +80,7 @@ function getSecret(options: AzureDataSourceSettings): undefined | string | Conce
|
||||
export function isCredentialsComplete(credentials: AzureCredentials): boolean {
|
||||
switch (credentials.authType) {
|
||||
case 'msi':
|
||||
case 'workloadidentity':
|
||||
return true;
|
||||
case 'clientsecret':
|
||||
return !!(credentials.azureCloud && credentials.tenantId && credentials.clientId && credentials.clientSecret);
|
||||
@ -89,12 +91,16 @@ export function getCredentials(options: AzureDataSourceSettings): AzureCredentia
|
||||
const authType = getAuthType(options);
|
||||
switch (authType) {
|
||||
case 'msi':
|
||||
if (config.azure.managedIdentityEnabled) {
|
||||
case 'workloadidentity':
|
||||
if (
|
||||
(authType === 'msi' && config.azure.managedIdentityEnabled) ||
|
||||
(authType === 'workloadidentity' && config.azure.workloadIdentityEnabled)
|
||||
) {
|
||||
return {
|
||||
authType: 'msi',
|
||||
authType,
|
||||
};
|
||||
} else {
|
||||
// If authentication type is managed identity but managed identities were disabled in Grafana config,
|
||||
// If authentication type is managed identity or workload identity but either method is disabled in Grafana config,
|
||||
// then we should fallback to an empty app registration (client secret) configuration
|
||||
return {
|
||||
authType: 'clientsecret',
|
||||
@ -118,15 +124,19 @@ export function updateCredentials(
|
||||
): AzureDataSourceSettings {
|
||||
switch (credentials.authType) {
|
||||
case 'msi':
|
||||
if (!config.azure.managedIdentityEnabled) {
|
||||
case 'workloadidentity':
|
||||
if (credentials.authType === 'msi' && !config.azure.managedIdentityEnabled) {
|
||||
throw new Error('Managed Identity authentication is not enabled in Grafana config.');
|
||||
}
|
||||
if (credentials.authType === 'workloadidentity' && !config.azure.workloadIdentityEnabled) {
|
||||
throw new Error('Workload Identity authentication is not enabled in Grafana config.');
|
||||
}
|
||||
|
||||
options = {
|
||||
...options,
|
||||
jsonData: {
|
||||
...options.jsonData,
|
||||
azureAuthType: 'msi',
|
||||
azureAuthType: credentials.authType,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -32,7 +32,7 @@ export enum AzureCloud {
|
||||
None = '',
|
||||
}
|
||||
|
||||
export type AzureAuthType = 'msi' | 'clientsecret';
|
||||
export type AzureAuthType = 'msi' | 'clientsecret' | 'workloadidentity';
|
||||
|
||||
export type ConcealedSecret = symbol;
|
||||
|
||||
@ -44,6 +44,10 @@ export interface AzureManagedIdentityCredentials extends AzureCredentialsBase {
|
||||
authType: 'msi';
|
||||
}
|
||||
|
||||
export interface AzureWorkloadIdentityCredentials extends AzureCredentialsBase {
|
||||
authType: 'workloadidentity';
|
||||
}
|
||||
|
||||
export interface AzureClientSecretCredentials extends AzureCredentialsBase {
|
||||
authType: 'clientsecret';
|
||||
azureCloud?: string;
|
||||
@ -52,7 +56,10 @@ export interface AzureClientSecretCredentials extends AzureCredentialsBase {
|
||||
clientSecret?: string | ConcealedSecret;
|
||||
}
|
||||
|
||||
export type AzureCredentials = AzureManagedIdentityCredentials | AzureClientSecretCredentials;
|
||||
export type AzureCredentials =
|
||||
| AzureManagedIdentityCredentials
|
||||
| AzureClientSecretCredentials
|
||||
| AzureWorkloadIdentityCredentials;
|
||||
|
||||
export interface AzureDataSourceJsonData extends DataSourceJsonData {
|
||||
cloudName: string;
|
||||
|
@ -50,6 +50,7 @@ export const AzureAuthSettings = (props: HttpSettingsBaseProps) => {
|
||||
<h6>Azure authentication</h6>
|
||||
<AzureCredentialsForm
|
||||
managedIdentityEnabled={config.azure.managedIdentityEnabled}
|
||||
workloadIdentityEnabled={config.azure.workloadIdentityEnabled}
|
||||
credentials={credentials}
|
||||
azureCloudOptions={KnownAzureClouds}
|
||||
onCredentialsChange={onCredentialsChange}
|
||||
|
@ -13,7 +13,7 @@ export const KnownAzureClouds: Array<SelectableValue<AzureCloud>> = [
|
||||
{ value: AzureCloud.USGovernment, label: 'Azure US Government' },
|
||||
];
|
||||
|
||||
export type AzureAuthType = 'msi' | 'clientsecret';
|
||||
export type AzureAuthType = 'msi' | 'clientsecret' | 'workloadidentity';
|
||||
|
||||
export type ConcealedSecret = symbol;
|
||||
|
||||
@ -26,6 +26,10 @@ export interface AzureManagedIdentityCredentials extends AzureCredentialsBase {
|
||||
authType: 'msi';
|
||||
}
|
||||
|
||||
export interface AzureWorkloadIdentityCredentials extends AzureCredentialsBase {
|
||||
authType: 'workloadidentity';
|
||||
}
|
||||
|
||||
export interface AzureClientSecretCredentials extends AzureCredentialsBase {
|
||||
authType: 'clientsecret';
|
||||
azureCloud?: string;
|
||||
@ -34,11 +38,15 @@ export interface AzureClientSecretCredentials extends AzureCredentialsBase {
|
||||
clientSecret?: string | ConcealedSecret;
|
||||
}
|
||||
|
||||
export type AzureCredentials = AzureManagedIdentityCredentials | AzureClientSecretCredentials;
|
||||
export type AzureCredentials =
|
||||
| AzureManagedIdentityCredentials
|
||||
| AzureClientSecretCredentials
|
||||
| AzureWorkloadIdentityCredentials;
|
||||
|
||||
export function isCredentialsComplete(credentials: AzureCredentials): boolean {
|
||||
switch (credentials.authType) {
|
||||
case 'msi':
|
||||
case 'workloadidentity':
|
||||
return true;
|
||||
case 'clientsecret':
|
||||
return !!(credentials.azureCloud && credentials.tenantId && credentials.clientId && credentials.clientSecret);
|
||||
|
@ -42,12 +42,16 @@ export function getCredentials(options: DataSourceSettings<any, any>): AzureCred
|
||||
|
||||
switch (credentials.authType) {
|
||||
case 'msi':
|
||||
if (config.azure.managedIdentityEnabled) {
|
||||
case 'workloadidentity':
|
||||
if (
|
||||
(credentials.authType === 'msi' && config.azure.managedIdentityEnabled) ||
|
||||
(credentials.authType === 'workloadidentity' && config.azure.workloadIdentityEnabled)
|
||||
) {
|
||||
return {
|
||||
authType: 'msi',
|
||||
authType: credentials.authType,
|
||||
};
|
||||
} else {
|
||||
// If authentication type is managed identity but managed identities were disabled in Grafana config,
|
||||
// If authentication type is managed identity or workload identity but either method is disabled in Grafana config,
|
||||
// then we should fallback to an empty app registration (client secret) configuration
|
||||
return {
|
||||
authType: 'clientsecret',
|
||||
@ -71,16 +75,21 @@ export function updateCredentials(
|
||||
): DataSourceSettings<any, any> {
|
||||
switch (credentials.authType) {
|
||||
case 'msi':
|
||||
if (!config.azure.managedIdentityEnabled) {
|
||||
case 'workloadidentity':
|
||||
if (credentials.authType === 'msi' && !config.azure.managedIdentityEnabled) {
|
||||
throw new Error('Managed Identity authentication is not enabled in Grafana config.');
|
||||
}
|
||||
if (credentials.authType === 'workloadidentity' && !config.azure.workloadIdentityEnabled) {
|
||||
throw new Error('Workload Identity authentication is not enabled in Grafana config.');
|
||||
}
|
||||
|
||||
options = {
|
||||
...options,
|
||||
jsonData: {
|
||||
...options.jsonData,
|
||||
azureAuthType: credentials.authType,
|
||||
azureCredentials: {
|
||||
authType: 'msi',
|
||||
authType: credentials.authType,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -6,6 +6,7 @@ import AzureCredentialsForm, { Props } from './AzureCredentialsForm';
|
||||
const setup = (propsFunc?: (props: Props) => Props) => {
|
||||
let props: Props = {
|
||||
managedIdentityEnabled: false,
|
||||
workloadIdentityEnabled: false,
|
||||
credentials: {
|
||||
authType: 'clientsecret',
|
||||
azureCloud: 'azuremonitor',
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { cx } from '@emotion/css';
|
||||
import React, { ChangeEvent, useEffect, useReducer, useState } from 'react';
|
||||
import React, { ChangeEvent, useEffect, useMemo, useReducer, useState } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
@ -11,6 +11,7 @@ import { AzureAuthType, AzureCredentials, isCredentialsComplete } from './AzureC
|
||||
|
||||
export interface Props {
|
||||
managedIdentityEnabled: boolean;
|
||||
workloadIdentityEnabled: boolean;
|
||||
credentials: AzureCredentials;
|
||||
azureCloudOptions?: SelectableValue[];
|
||||
onCredentialsChange: (updatedCredentials: AzureCredentials) => void;
|
||||
@ -18,23 +19,45 @@ export interface Props {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const authTypeOptions: Array<SelectableValue<AzureAuthType>> = [
|
||||
{
|
||||
value: 'msi',
|
||||
label: 'Managed Identity',
|
||||
},
|
||||
{
|
||||
value: 'clientsecret',
|
||||
label: 'App Registration',
|
||||
},
|
||||
];
|
||||
|
||||
export const AzureCredentialsForm = (props: Props) => {
|
||||
const { credentials, azureCloudOptions, onCredentialsChange, getSubscriptions, disabled } = props;
|
||||
const {
|
||||
credentials,
|
||||
azureCloudOptions,
|
||||
onCredentialsChange,
|
||||
getSubscriptions,
|
||||
disabled,
|
||||
managedIdentityEnabled,
|
||||
workloadIdentityEnabled,
|
||||
} = props;
|
||||
const hasRequiredFields = isCredentialsComplete(credentials);
|
||||
|
||||
const [subscriptions, setSubscriptions] = useState<Array<SelectableValue<string>>>([]);
|
||||
const [loadSubscriptionsClicked, onLoadSubscriptions] = useReducer((val) => val + 1, 0);
|
||||
|
||||
const authTypeOptions = useMemo(() => {
|
||||
let opts: Array<SelectableValue<AzureAuthType>> = [
|
||||
{
|
||||
value: 'clientsecret',
|
||||
label: 'App Registration',
|
||||
},
|
||||
];
|
||||
|
||||
if (managedIdentityEnabled) {
|
||||
opts.push({
|
||||
value: 'msi',
|
||||
label: 'Managed Identity',
|
||||
});
|
||||
}
|
||||
|
||||
if (workloadIdentityEnabled) {
|
||||
opts.push({
|
||||
value: 'workloadidentity',
|
||||
label: 'Workload Identity',
|
||||
});
|
||||
}
|
||||
return opts;
|
||||
}, [managedIdentityEnabled, workloadIdentityEnabled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!getSubscriptions || !hasRequiredFields) {
|
||||
updateSubscriptions([]);
|
||||
@ -70,19 +93,22 @@ export const AzureCredentialsForm = (props: Props) => {
|
||||
};
|
||||
|
||||
const onAuthTypeChange = (selected: SelectableValue<AzureAuthType>) => {
|
||||
if (onCredentialsChange) {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
authType: selected.value || 'msi',
|
||||
defaultSubscriptionId: undefined,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
setSubscriptions([]);
|
||||
const defaultAuthType = managedIdentityEnabled
|
||||
? 'msi'
|
||||
: workloadIdentityEnabled
|
||||
? 'workloadidentity'
|
||||
: 'clientsecret';
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
authType: selected.value || defaultAuthType,
|
||||
defaultSubscriptionId: undefined,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
};
|
||||
|
||||
const onAzureCloudChange = (selected: SelectableValue<string>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
if (credentials.authType === 'clientsecret') {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
@ -94,7 +120,7 @@ export const AzureCredentialsForm = (props: Props) => {
|
||||
};
|
||||
|
||||
const onTenantIdChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
if (credentials.authType === 'clientsecret') {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
@ -106,7 +132,7 @@ export const AzureCredentialsForm = (props: Props) => {
|
||||
};
|
||||
|
||||
const onClientIdChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
if (credentials.authType === 'clientsecret') {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
@ -118,7 +144,7 @@ export const AzureCredentialsForm = (props: Props) => {
|
||||
};
|
||||
|
||||
const onClientSecretChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
if (credentials.authType === 'clientsecret') {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
@ -130,7 +156,7 @@ export const AzureCredentialsForm = (props: Props) => {
|
||||
};
|
||||
|
||||
const onClientSecretReset = () => {
|
||||
if (onCredentialsChange && credentials.authType === 'clientsecret') {
|
||||
if (credentials.authType === 'clientsecret') {
|
||||
setSubscriptions([]);
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
@ -142,19 +168,17 @@ export const AzureCredentialsForm = (props: Props) => {
|
||||
};
|
||||
|
||||
const onSubscriptionChange = (selected: SelectableValue<string> | undefined) => {
|
||||
if (onCredentialsChange) {
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
defaultSubscriptionId: selected?.value,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
}
|
||||
const updated: AzureCredentials = {
|
||||
...credentials,
|
||||
defaultSubscriptionId: selected?.value,
|
||||
};
|
||||
onCredentialsChange(updated);
|
||||
};
|
||||
const prometheusConfigOverhaulAuth = config.featureToggles.prometheusConfigOverhaulAuth;
|
||||
|
||||
return (
|
||||
<div className="gf-form-group">
|
||||
{props.managedIdentityEnabled && (
|
||||
{authTypeOptions.length > 1 && (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel className="width-12" tooltip="Choose the type of authentication to Azure services">
|
||||
|
Loading…
Reference in New Issue
Block a user