Azure: Unify credentials in frontend for MSSQL (#96357)

* init

* test fix
pull/97643/head^2
Younjin Song 7 months ago committed by GitHub
parent 663167a16c
commit 85392de2e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 153
      public/app/plugins/datasource/mssql/azureauth/AzureAuth.test.ts
  2. 37
      public/app/plugins/datasource/mssql/azureauth/AzureAuth.testMocks.ts
  3. 15
      public/app/plugins/datasource/mssql/azureauth/AzureAuthSettings.tsx
  4. 21
      public/app/plugins/datasource/mssql/azureauth/AzureCredentials.ts
  5. 173
      public/app/plugins/datasource/mssql/azureauth/AzureCredentialsConfig.ts
  6. 164
      public/app/plugins/datasource/mssql/azureauth/AzureCredentialsForm.tsx
  7. 37
      public/app/plugins/datasource/mssql/types.ts

@ -1,92 +1,115 @@
import { AzureAuthType, AzureCloud, AzureCredentialsType, ConcealedSecretType } from '../types';
import {
AzureCredentials,
AzureCloud,
ConcealedSecret,
AzureClientSecretCredentials,
instanceOfAzureCredential,
updateDatasourceCredentials,
} from '@grafana/azure-sdk';
import { config } from '@grafana/runtime';
import {
configWithManagedIdentityEnabled,
configWithManagedIdentityDisabled,
dataSourceSettingsWithMsiCredentials,
dataSourceSettingsWithClientSecretOnServer,
dataSourceSettingsWithClientSecretInSecureJSONData,
} from './AzureAuth.testMocks';
import { getDefaultCredentials, getSecret, getCredentials, updateCredentials } from './AzureCredentialsConfig';
import { getDefaultCredentials, getCredentials } from './AzureCredentialsConfig';
// NOTE: @ts-ignores are used to ignore the type errors that are thrown when passing in the mocks.
// This is because the mocks are partials of the actual types, so the types are not complete.
export const CLIENT_SECRET_SYMBOL: ConcealedSecretType = Symbol('Concealed client secret');
export const CLIENT_SECRET_SYMBOL: ConcealedSecret = Symbol('Concealed client secret');
export const CLIENT_SECRET_STRING = 'XXXX-super-secret-secret-XXXX';
jest.mock('@grafana/runtime', () => ({
...jest.requireActual('@grafana/runtime'), // Keep the rest of the actual module
}));
describe('AzureAuth', () => {
beforeEach(() => {
jest.resetModules();
});
describe('AzureCredentialsConfig', () => {
it('`getDefaultCredentials()` should return the correct credentials based on whether the managed identity is enabled', () => {
const resultForManagedIdentityEnabled = getDefaultCredentials(true, AzureCloud.Public);
const resultForManagedIdentityDisabled = getDefaultCredentials(false, AzureCloud.Public);
jest.mocked(config).azure.managedIdentityEnabled = true;
const resultForManagedIdentityEnabled = getDefaultCredentials();
jest.mocked(config).azure.managedIdentityEnabled = false;
const resultForManagedIdentityDisabled = getDefaultCredentials();
expect(resultForManagedIdentityEnabled).toEqual({ authType: 'msi' });
expect(resultForManagedIdentityDisabled).toEqual({ authType: 'clientsecret', azureCloud: 'AzureCloud' });
});
it("`getSecret()` should correctly return the client secret if it's not concealed", () => {
const resultFromServerSideSecret = getSecret(false, CLIENT_SECRET_STRING);
expect(resultFromServerSideSecret).toBe(CLIENT_SECRET_STRING);
const resultFromSecureJSONDataSecret = typeof getSecret(true, '');
expect(resultFromSecureJSONDataSecret).toBe('symbol');
});
describe('getCredentials()', () => {
it('should return the correct managed identity credentials', () => {
// If `dataSourceSettings.authType === AzureAuthType.MSI` && `config.azure.managedIdentityEnabled === true`.
// If `dataSourceSettings.authType === 'msi'` && `config.azure.managedIdentityEnabled === true`.
jest.mocked(config).azure.managedIdentityEnabled = true;
const resultForManagedIdentityEnabled = getCredentials(
// @ts-ignore
dataSourceSettingsWithMsiCredentials,
configWithManagedIdentityEnabled
dataSourceSettingsWithMsiCredentials
);
expect(resultForManagedIdentityEnabled).toEqual({ authType: AzureAuthType.MSI });
expect(resultForManagedIdentityEnabled).toEqual({ authType: 'msi' });
// If `dataSourceSettings.authType === AzureAuthType.MSI` but `config.azure.managedIdentityEnabled !== true`.
// If `dataSourceSettings.authType === 'msi'` but `config.azure.managedIdentityEnabled !== true`.
// Default to basic client secret credentials.
jest.mocked(config).azure.managedIdentityEnabled = false;
const resultForManagedIdentityEnabledInJSONButDisabledInConfig = getCredentials(
// @ts-ignore
dataSourceSettingsWithMsiCredentials,
configWithManagedIdentityDisabled
dataSourceSettingsWithMsiCredentials
);
expect(resultForManagedIdentityEnabledInJSONButDisabledInConfig).toEqual({
authType: AzureAuthType.CLIENT_SECRET,
authType: 'clientsecret',
azureCloud: 'AzureCloud',
});
});
it('should return the correct client secret credentials', () => {
const basicExpectedResult = {
authType: AzureAuthType.CLIENT_SECRET,
authType: 'clientsecret',
azureCloud: 'AzureCloud',
tenantId: 'XXXX-tenant-id-XXXX',
clientId: 'XXXX-client-id-XXXX',
};
// If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == true`,
// If `dataSourceSettings.authType === 'clientsecret'` && `secureJsonFields.azureClientSecret == true`,
// i.e. the client secret is stored on the server.
jest.mocked(config).azure.managedIdentityEnabled = false;
const resultForClientSecretCredentialsOnServer = getCredentials(
// @ts-ignore
dataSourceSettingsWithClientSecretOnServer,
configWithManagedIdentityDisabled
dataSourceSettingsWithClientSecretOnServer
);
// Here we test the properties separately because the client secret is a symbol,
// and since JS symobls are unique, we test via the `typeof` operator.
expect(resultForClientSecretCredentialsOnServer.authType).toEqual(AzureAuthType.CLIENT_SECRET);
expect(resultForClientSecretCredentialsOnServer.azureCloud).toEqual('AzureCloud');
expect(resultForClientSecretCredentialsOnServer.tenantId).toEqual('XXXX-tenant-id-XXXX');
expect(resultForClientSecretCredentialsOnServer.clientId).toEqual('XXXX-client-id-XXXX');
expect(typeof resultForClientSecretCredentialsOnServer.clientSecret).toEqual('symbol');
expect(resultForClientSecretCredentialsOnServer.authType).toEqual('clientsecret');
expect(
instanceOfAzureCredential<AzureClientSecretCredentials>(
'clientsecret',
resultForClientSecretCredentialsOnServer
)
).toEqual(true);
expect((resultForClientSecretCredentialsOnServer as AzureClientSecretCredentials).azureCloud).toEqual(
'AzureCloud'
);
expect((resultForClientSecretCredentialsOnServer as AzureClientSecretCredentials).tenantId).toEqual(
'XXXX-tenant-id-XXXX'
);
expect((resultForClientSecretCredentialsOnServer as AzureClientSecretCredentials).clientId).toEqual(
'XXXX-client-id-XXXX'
);
expect(typeof (resultForClientSecretCredentialsOnServer as AzureClientSecretCredentials).clientSecret).toEqual(
'symbol'
);
// If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == false`,
// If `dataSourceSettings.authType === 'clientsecret'` && `secureJsonFields.azureClientSecret == false`,
// i.e. the client secret is stored in the secureJson.
jest.mocked(config).azure.managedIdentityEnabled = false;
const resultForClientSecretCredentialsInSecureJSON = getCredentials(
// @ts-ignore
dataSourceSettingsWithClientSecretInSecureJSONData,
configWithManagedIdentityDisabled
dataSourceSettingsWithClientSecretInSecureJSONData
);
expect(resultForClientSecretCredentialsInSecureJSON).toEqual({
...basicExpectedResult,
@ -97,66 +120,68 @@ describe('AzureAuth', () => {
describe('updateCredentials()', () => {
it('should update the credentials for managed service identity correctly', () => {
// If `dataSourceSettings.authType === AzureAuthType.MSI` && `config.azure.managedIdentityEnabled === true`.
const resultForMsiCredentials = updateCredentials(
// If `dataSourceSettings.authType === 'msi'` && `config.azure.managedIdentityEnabled === true`.
jest.mocked(config).azure.managedIdentityEnabled = true;
const resultForMsiCredentials = updateDatasourceCredentials(
// @ts-ignore
dataSourceSettingsWithMsiCredentials,
configWithManagedIdentityEnabled,
{
authType: AzureAuthType.MSI,
authType: 'msi',
}
);
expect(resultForMsiCredentials).toEqual({ jsonData: { azureCredentials: { authType: 'msi' } } });
// If `dataSourceSettings.authType === AzureAuthType.MSI` but `config.azure.managedIdentityEnabled !== true`.
// If `dataSourceSettings.authType === 'msi'` but `config.azure.managedIdentityEnabled !== true`.
jest.mocked(config).azure.managedIdentityEnabled = false;
expect(() =>
updateCredentials(
updateDatasourceCredentials(
// @ts-ignore
dataSourceSettingsWithMsiCredentials,
configWithManagedIdentityDisabled,
{
authType: AzureAuthType.MSI,
authType: 'msi',
}
)
).toThrow('Managed Identity authentication is not enabled in Grafana config.');
});
it('should update the credentials for client secret correctly', () => {
const basicClientSecretCredentials: AzureCredentialsType = {
authType: AzureAuthType.CLIENT_SECRET,
azureCloud: 'AzureCloud',
const basicClientSecretCredentials: AzureCredentials = {
authType: 'clientsecret',
azureCloud: AzureCloud.Public,
tenantId: 'XXXX-tenant-id-XXXX',
clientId: 'XXXX-client-id-XXXX',
};
// If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == true`.
const resultForClientSecretCredentials1 = updateCredentials(
// If `dataSourceSettings.authType === 'clientsecret'` && `secureJsonFields.azureClientSecret == true`.
jest.mocked(config).azure.managedIdentityEnabled = false;
const resultForClientSecretCredentials1 = updateDatasourceCredentials(
// @ts-ignore
dataSourceSettingsWithClientSecretOnServer,
configWithManagedIdentityDisabled,
basicClientSecretCredentials
);
expect(resultForClientSecretCredentials1).toEqual({
jsonData: {
azureCredentials: { ...basicClientSecretCredentials },
},
secureJsonData: { azureClientSecret: undefined },
secureJsonFields: { azureClientSecret: false },
expect(resultForClientSecretCredentials1.jsonData.azureCredentials).toEqual(basicClientSecretCredentials);
expect(resultForClientSecretCredentials1.secureJsonData).toEqual({ azureClientSecret: undefined });
expect(resultForClientSecretCredentials1.secureJsonFields).toEqual({
azureClientSecret: false,
clientSecret: false,
});
// If `dataSourceSettings.authType === AzureAuthType.CLIENT_SECRET` && `secureJsonFields.azureClientSecret == false`.
const resultForClientSecretCredentials2 = updateCredentials(
// If `dataSourceSettings.authType === 'clientsecret'` && `secureJsonFields.azureClientSecret == false`.
jest.mocked(config).azure.managedIdentityEnabled = false;
const resultForClientSecretCredentials2 = updateDatasourceCredentials(
// @ts-ignore
dataSourceSettingsWithClientSecretInSecureJSONData,
configWithManagedIdentityDisabled,
{ ...basicClientSecretCredentials, clientSecret: 'XXXX-super-secret-secret-XXXX' }
);
expect(resultForClientSecretCredentials2).toEqual({
jsonData: {
azureCredentials: { ...basicClientSecretCredentials },
},
secureJsonData: { azureClientSecret: 'XXXX-super-secret-secret-XXXX' },
secureJsonFields: { azureClientSecret: false },
expect(resultForClientSecretCredentials2.jsonData.azureCredentials).toEqual(basicClientSecretCredentials);
expect(resultForClientSecretCredentials2.secureJsonData).toEqual({
azureClientSecret: 'XXXX-super-secret-secret-XXXX',
});
expect(resultForClientSecretCredentials2.secureJsonFields).toEqual({
azureClientSecret: false,
clientSecret: false,
});
});
});

@ -1,8 +1,6 @@
import { DataSourceSettings } from '@grafana/data';
import { AzureDataSourceSettings } from '@grafana/azure-sdk';
import { GrafanaBootConfig } from '@grafana/runtime';
import { AzureAuthSecureJSONDataType, AzureAuthJSONDataType, AzureAuthType } from '../types';
export const configWithManagedIdentityEnabled: Partial<GrafanaBootConfig> = {
azure: {
managedIdentityEnabled: true,
@ -24,31 +22,22 @@ export const configWithManagedIdentityDisabled: Partial<GrafanaBootConfig> = {
},
};
export const dataSourceSettingsWithMsiCredentials: Partial<
DataSourceSettings<AzureAuthJSONDataType, AzureAuthSecureJSONDataType>
> = {
jsonData: { azureCredentials: { authType: AzureAuthType.MSI } },
export const dataSourceSettingsWithMsiCredentials: Partial<AzureDataSourceSettings> = {
jsonData: { azureCredentials: { authType: 'msi' } },
};
const basicJSONData = {
// Will return symbol as the secret is concealed
export const dataSourceSettingsWithClientSecretOnServer: Partial<AzureDataSourceSettings> = {
jsonData: {
azureCredentials: {
authType: AzureAuthType.CLIENT_SECRET,
tenantId: 'XXXX-tenant-id-XXXX',
clientId: 'XXXX-client-id-XXXX',
},
azureCredentials: { authType: 'clientsecret', clientId: 'XXXX-client-id-XXXX', tenantId: 'XXXX-tenant-id-XXXX' },
},
secureJsonFields: { azureClientSecret: true },
};
// Will return symbol as the secret is concealed
export const dataSourceSettingsWithClientSecretOnServer: Partial<
DataSourceSettings<AzureAuthJSONDataType, AzureAuthSecureJSONDataType>
> = { ...basicJSONData, secureJsonFields: { azureClientSecret: true } };
// Will return the secret as a string from the secureJsonData
export const dataSourceSettingsWithClientSecretInSecureJSONData: Partial<
DataSourceSettings<AzureAuthJSONDataType, AzureAuthSecureJSONDataType>
> = {
...basicJSONData,
secureJsonData: { azureClientSecret: 'XXXX-super-secret-secret-XXXX', password: undefined },
export const dataSourceSettingsWithClientSecretInSecureJSONData: Partial<AzureDataSourceSettings> = {
jsonData: {
azureCredentials: { authType: 'clientsecret', clientId: 'XXXX-client-id-XXXX', tenantId: 'XXXX-tenant-id-XXXX' },
},
secureJsonFields: { azureClientSecret: false },
secureJsonData: { azureClientSecret: 'XXXX-super-secret-secret-XXXX' },
};

@ -1,24 +1,25 @@
import { useMemo } from 'react';
import { useEffectOnce } from 'react-use';
import { AzureCredentials, AzureCloud, updateDatasourceCredentials } from '@grafana/azure-sdk';
import { SelectableValue } from '@grafana/data';
import { config } from '@grafana/runtime';
import { HttpSettingsBaseProps } from '@grafana/ui/src/components/DataSourceSettings/types';
import { AzureCredentialsType } from '../types';
import { KnownAzureClouds } from './AzureCredentials';
import { getCredentials, updateCredentials } from './AzureCredentialsConfig';
import { getCredentials } from './AzureCredentialsConfig';
import { AzureCredentialsForm } from './AzureCredentialsForm';
export const KnownAzureClouds: Array<SelectableValue<AzureCloud>> = [{ value: AzureCloud.Public, label: 'Azure' }];
export const AzureAuthSettings = (props: HttpSettingsBaseProps) => {
const { dataSourceConfig: dsSettings, onChange } = props;
const managedIdentityEnabled = config.azure.managedIdentityEnabled;
const azureEntraPasswordCredentialsEnabled = config.azure.azureEntraPasswordCredentialsEnabled;
const credentials = useMemo(() => getCredentials(dsSettings, config), [dsSettings]);
const credentials = useMemo(() => getCredentials(dsSettings), [dsSettings]);
const onCredentialsChange = (credentials: AzureCredentialsType): void => {
onChange(updateCredentials(dsSettings, config, credentials));
const onCredentialsChange = (credentials: AzureCredentials): void => {
onChange(updateDatasourceCredentials(dsSettings, credentials));
};
// The auth type needs to be set on the first load of the data source

@ -1,21 +0,0 @@
import { SelectableValue } from '@grafana/data';
import { AzureCredentialsType, AzureAuthType } from '../types';
export enum AzureCloud {
Public = 'AzureCloud',
None = '',
}
export const KnownAzureClouds: Array<SelectableValue<AzureCloud>> = [{ value: AzureCloud.Public, label: 'Azure' }];
export function isCredentialsComplete(credentials: AzureCredentialsType): boolean {
switch (credentials.authType) {
case AzureAuthType.MSI:
return true;
case AzureAuthType.CLIENT_SECRET:
return !!(credentials.azureCloud && credentials.tenantId && credentials.clientId && credentials.clientSecret);
case AzureAuthType.AD_PASSWORD:
return !!(credentials.clientId && credentials.password && credentials.userId);
}
}

@ -1,167 +1,26 @@
import { DataSourceSettings } from '@grafana/data';
import { GrafanaBootConfig } from '@grafana/runtime';
import {
AzureCloud,
AzureCredentialsType,
ConcealedSecretType,
AzureAuthSecureJSONDataType,
AzureAuthJSONDataType,
AzureAuthType,
} from '../types';
export const getDefaultCredentials = (managedIdentityEnabled: boolean, cloud: string): AzureCredentialsType => {
if (managedIdentityEnabled) {
return { authType: AzureAuthType.MSI };
AzureCredentials,
AzureDataSourceSettings,
getDatasourceCredentials,
getDefaultAzureCloud,
} from '@grafana/azure-sdk';
import { config } from '@grafana/runtime';
export const getDefaultCredentials = (): AzureCredentials => {
if (config.azure.managedIdentityEnabled) {
return { authType: 'msi' };
} else {
return { authType: AzureAuthType.CLIENT_SECRET, azureCloud: cloud };
return { authType: 'clientsecret', azureCloud: getDefaultAzureCloud() };
}
};
export const getSecret = (
storedServerSide: boolean,
secret: string | symbol | undefined
): undefined | string | ConcealedSecretType => {
const concealedSecret: ConcealedSecretType = Symbol('Concealed client secret');
if (storedServerSide) {
// The secret is concealed server side, so return the symbol
return concealedSecret;
} else {
return typeof secret === 'string' && secret.length > 0 ? secret : undefined;
export const getCredentials = (dsSettings: AzureDataSourceSettings): AzureCredentials => {
const credentials = getDatasourceCredentials(dsSettings);
if (credentials) {
return credentials;
}
};
export const getCredentials = (
dsSettings: DataSourceSettings<AzureAuthJSONDataType, AzureAuthSecureJSONDataType>,
bootConfig: GrafanaBootConfig
): AzureCredentialsType => {
// JSON data
const credentials = dsSettings.jsonData?.azureCredentials;
// Secure JSON data/fields
const clientSecretStoredServerSide = dsSettings.secureJsonFields?.azureClientSecret;
const clientSecret = dsSettings.secureJsonData?.azureClientSecret;
const passwordStoredServerSide = dsSettings.secureJsonFields?.password;
const password = dsSettings.secureJsonData?.password;
// BootConfig data
const managedIdentityEnabled = !!bootConfig.azure?.managedIdentityEnabled;
const cloud = bootConfig.azure?.cloud || AzureCloud.Public;
// If no credentials saved, then return empty credentials
// of type based on whether the managed identity enabled
if (!credentials) {
return getDefaultCredentials(managedIdentityEnabled, cloud);
}
switch (credentials.authType) {
case AzureAuthType.MSI:
if (managedIdentityEnabled) {
return {
authType: AzureAuthType.MSI,
};
} else {
// If authentication type is managed identity but managed identities were disabled in Grafana config,
// then we should fallback to an empty app registration (client secret) configuration
return {
authType: AzureAuthType.CLIENT_SECRET,
azureCloud: cloud,
};
}
case AzureAuthType.CLIENT_SECRET:
return {
authType: AzureAuthType.CLIENT_SECRET,
azureCloud: credentials.azureCloud || cloud,
tenantId: credentials.tenantId,
clientId: credentials.clientId,
clientSecret: getSecret(clientSecretStoredServerSide, clientSecret),
};
case AzureAuthType.AD_PASSWORD:
return {
authType: AzureAuthType.AD_PASSWORD,
userId: credentials.userId,
clientId: credentials.clientId,
password: getSecret(passwordStoredServerSide, password),
};
}
};
export const updateCredentials = (
dsSettings: DataSourceSettings<AzureAuthJSONDataType>,
bootConfig: GrafanaBootConfig,
credentials: AzureCredentialsType
): DataSourceSettings<AzureAuthJSONDataType> => {
// BootConfig data
const managedIdentityEnabled = !!bootConfig.azure?.managedIdentityEnabled;
const cloud = bootConfig.azure?.cloud || AzureCloud.Public;
switch (credentials.authType) {
case AzureAuthType.MSI:
if (!managedIdentityEnabled) {
throw new Error('Managed Identity authentication is not enabled in Grafana config.');
}
dsSettings = {
...dsSettings,
jsonData: {
...dsSettings.jsonData,
azureCredentials: {
authType: AzureAuthType.MSI,
},
},
};
return dsSettings;
case AzureAuthType.CLIENT_SECRET:
dsSettings = {
...dsSettings,
jsonData: {
...dsSettings.jsonData,
azureCredentials: {
authType: AzureAuthType.CLIENT_SECRET,
azureCloud: credentials.azureCloud || cloud,
tenantId: credentials.tenantId,
clientId: credentials.clientId,
},
},
secureJsonData: {
...dsSettings.secureJsonData,
azureClientSecret:
typeof credentials.clientSecret === 'string' && credentials.clientSecret.length > 0
? credentials.clientSecret
: undefined,
},
secureJsonFields: {
...dsSettings.secureJsonFields,
azureClientSecret: typeof credentials.clientSecret === 'symbol',
},
};
return dsSettings;
case AzureAuthType.AD_PASSWORD:
return {
...dsSettings,
jsonData: {
...dsSettings.jsonData,
azureCredentials: {
authType: AzureAuthType.AD_PASSWORD,
userId: credentials.userId,
clientId: credentials.clientId,
},
},
secureJsonData: {
...dsSettings.secureJsonData,
password:
typeof credentials.password === 'string' && credentials.password.length > 0
? credentials.password
: undefined,
},
secureJsonFields: {
...dsSettings.secureJsonFields,
password: typeof credentials.password === 'symbol',
},
};
}
return getDefaultCredentials();
};

@ -1,16 +1,15 @@
import { ChangeEvent } from 'react';
import { AzureCredentials, AzureAuthType } from '@grafana/azure-sdk';
import { SelectableValue } from '@grafana/data';
import { Button, Field, Select, Input } from '@grafana/ui/src/components';
import { AzureCredentialsType, AzureAuthType } from '../types';
export interface Props {
managedIdentityEnabled: boolean;
azureEntraPasswordCredentialsEnabled: boolean;
credentials: AzureCredentialsType;
credentials: AzureCredentials;
azureCloudOptions?: SelectableValue[];
onCredentialsChange: (updatedCredentials: AzureCredentialsType) => void;
onCredentialsChange: (updatedCredentials: AzureCredentials) => void;
disabled?: boolean;
}
@ -26,9 +25,89 @@ export const AzureCredentialsForm = (props: Props) => {
const onAuthTypeChange = (selected: SelectableValue<AzureAuthType>) => {
if (onCredentialsChange) {
const updated: AzureCredentialsType = {
const updated: AzureCredentials = {
...credentials,
authType: selected.value || 'msi',
};
onCredentialsChange(updated);
}
};
const onAzureCloudChange = (selected: SelectableValue<string>) => {
if (credentials.authType === 'clientsecret') {
const updated: AzureCredentials = {
...credentials,
azureCloud: selected.value,
};
onCredentialsChange(updated);
}
};
const onTenantIdChange = (event: ChangeEvent<HTMLInputElement>) => {
if (credentials.authType === 'clientsecret') {
const updated: AzureCredentials = {
...credentials,
tenantId: event.target.value,
};
onCredentialsChange(updated);
}
};
const onClientIdChange = (event: ChangeEvent<HTMLInputElement>) => {
if (credentials.authType === 'clientsecret' || credentials.authType === 'ad-password') {
const updated: AzureCredentials = {
...credentials,
clientId: event.target.value,
};
onCredentialsChange(updated);
}
};
const onClientSecretChange = (event: ChangeEvent<HTMLInputElement>) => {
if (credentials.authType === 'clientsecret') {
const updated: AzureCredentials = {
...credentials,
clientSecret: event.target.value,
};
onCredentialsChange(updated);
}
};
const onClientSecretReset = () => {
if (credentials.authType === 'clientsecret') {
const updated: AzureCredentials = {
...credentials,
clientSecret: '',
};
onCredentialsChange(updated);
}
};
const onUserIdChange = (event: ChangeEvent<HTMLInputElement>) => {
if (credentials.authType === 'ad-password') {
const updated: AzureCredentials = {
...credentials,
userId: event.target.value,
};
onCredentialsChange(updated);
}
};
const onPasswordChange = (event: ChangeEvent<HTMLInputElement>) => {
if (credentials.authType === 'ad-password') {
const updated: AzureCredentials = {
...credentials,
password: event.target.value,
};
onCredentialsChange(updated);
}
};
const onPasswordReset = () => {
if (credentials.authType === 'ad-password') {
const updated: AzureCredentials = {
...credentials,
authType: selected.value || AzureAuthType.MSI,
password: '',
};
onCredentialsChange(updated);
}
@ -36,33 +115,23 @@ export const AzureCredentialsForm = (props: Props) => {
const authTypeOptions: Array<SelectableValue<AzureAuthType>> = [
{
value: AzureAuthType.CLIENT_SECRET,
value: 'clientsecret',
label: 'App Registration',
},
];
if (managedIdentityEnabled) {
authTypeOptions.push({
value: AzureAuthType.MSI,
value: 'msi',
label: 'Managed Identity',
});
}
if (azureEntraPasswordCredentialsEnabled) {
authTypeOptions.push({
value: AzureAuthType.AD_PASSWORD,
value: 'ad-password',
label: 'Azure Entra Password',
});
}
const onInputChange = ({ property, value }: { property: keyof AzureCredentialsType; value: string }) => {
if (onCredentialsChange) {
const updated: AzureCredentialsType = {
...credentials,
[property]: value,
};
onCredentialsChange(updated);
}
};
return (
<div>
<Field
@ -78,17 +147,14 @@ export const AzureCredentialsForm = (props: Props) => {
disabled={disabled}
/>
</Field>
{credentials.authType === AzureAuthType.CLIENT_SECRET && (
{credentials.authType === 'clientsecret' && (
<>
{azureCloudOptions && (
<Field label="Azure Cloud" htmlFor="azure-cloud-type" disabled={disabled}>
<Select
value={azureCloudOptions.find((opt) => opt.value === credentials.azureCloud)}
options={azureCloudOptions}
onChange={(selected: SelectableValue<AzureAuthType>) => {
const value = selected.value || '';
onInputChange({ property: 'azureCloud', value });
}}
onChange={onAzureCloudChange}
isDisabled={disabled}
inputId="azure-cloud-type"
aria-label="Azure Cloud"
@ -107,10 +173,7 @@ export const AzureCredentialsForm = (props: Props) => {
width={45}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value={credentials.tenantId || ''}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
onInputChange({ property: 'tenantId', value });
}}
onChange={onTenantIdChange}
disabled={disabled}
aria-label="Tenant ID"
/>
@ -126,10 +189,7 @@ export const AzureCredentialsForm = (props: Props) => {
width={45}
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value={credentials.clientId || ''}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
onInputChange({ property: 'clientId', value });
}}
onChange={onClientIdChange}
disabled={disabled}
aria-label="Client ID"
/>
@ -145,14 +205,7 @@ export const AzureCredentialsForm = (props: Props) => {
data-testid={'client-secret'}
width={45}
/>
<Button
variant="secondary"
type="button"
onClick={() => {
onInputChange({ property: 'clientSecret', value: '' });
}}
disabled={disabled}
>
<Button variant="secondary" type="button" onClick={onClientSecretReset} disabled={disabled}>
Reset
</Button>
</div>
@ -170,10 +223,7 @@ export const AzureCredentialsForm = (props: Props) => {
aria-label="Client Secret"
placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
value={credentials.clientSecret || ''}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
onInputChange({ property: 'clientSecret', value });
}}
onChange={onClientSecretChange}
id="client-secret"
disabled={disabled}
/>
@ -181,16 +231,13 @@ export const AzureCredentialsForm = (props: Props) => {
))}
</>
)}
{credentials.authType === AzureAuthType.AD_PASSWORD && azureEntraPasswordCredentialsEnabled && (
{credentials.authType === 'ad-password' && azureEntraPasswordCredentialsEnabled && (
<>
<Field label="User Id" required htmlFor="user-id" invalid={!credentials.userId} error={'User ID is required'}>
<Input
width={45}
value={credentials.userId || ''}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
onInputChange({ property: 'userId', value });
}}
onChange={onUserIdChange}
disabled={disabled}
aria-label="User ID"
/>
@ -205,10 +252,7 @@ export const AzureCredentialsForm = (props: Props) => {
<Input
width={45}
value={credentials.clientId || ''}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
onInputChange({ property: 'clientId', value });
}}
onChange={onClientIdChange}
disabled={disabled}
aria-label="Application Client ID"
/>
@ -224,14 +268,7 @@ export const AzureCredentialsForm = (props: Props) => {
data-testid={'password'}
width={45}
/>
<Button
variant="secondary"
type="button"
onClick={() => {
onInputChange({ property: 'password', value: '' });
}}
disabled={disabled}
>
<Button variant="secondary" type="button" onClick={onPasswordReset} disabled={disabled}>
Reset
</Button>
</div>
@ -248,10 +285,7 @@ export const AzureCredentialsForm = (props: Props) => {
width={45}
aria-label="Password"
value={credentials.password || ''}
onChange={(event: ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
onInputChange({ property: 'password', value });
}}
onChange={onPasswordChange}
id="password"
disabled={disabled}
/>

@ -1,4 +1,4 @@
import { DataSourceJsonData } from '@grafana/data';
import { AzureCredentials } from '@grafana/azure-sdk';
import { SQLOptions } from '@grafana/sql';
import { HttpSettingsBaseProps } from '@grafana/ui/src/components/DataSourceSettings/types';
@ -17,37 +17,13 @@ export enum MSSQLEncryptOptions {
false = 'false',
true = 'true',
}
export enum AzureCloud {
Public = 'AzureCloud',
None = '',
}
export type ConcealedSecretType = symbol;
export enum AzureAuthType {
MSI = 'msi',
CLIENT_SECRET = 'clientsecret',
AD_PASSWORD = 'ad-password',
}
export interface AzureCredentialsType {
authType: AzureAuthType;
azureCloud?: string;
tenantId?: string;
clientId?: string;
clientSecret?: string | ConcealedSecretType;
userId?: string;
password?: string | ConcealedSecretType;
}
export interface MssqlOptions extends SQLOptions {
authenticationType?: MSSQLAuthenticationType;
encrypt?: MSSQLEncryptOptions;
sslRootCertFile?: string;
serverName?: string;
connectionTimeout?: number;
azureCredentials?: AzureCredentialsType;
azureCredentials?: AzureCredentials;
keytabFilePath?: string;
credentialCache?: string;
credentialCacheLookupFile?: string;
@ -60,15 +36,6 @@ export interface MssqlSecureOptions {
password?: string;
}
export type AzureAuthJSONDataType = DataSourceJsonData & {
azureCredentials: AzureCredentialsType;
};
export type AzureAuthSecureJSONDataType = {
azureClientSecret: undefined | string | ConcealedSecretType;
password: undefined | string | ConcealedSecretType;
};
export type AzureAuthConfigType = {
azureAuthIsSupported: boolean;
azureAuthSettingsUI: (props: HttpSettingsBaseProps) => JSX.Element;

Loading…
Cancel
Save