mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Azure: Unify credentials in frontend (#95354)
* init * fix lint * fix lint * lint * update version * fix
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import Datasource from '../datasource';
|
||||
import { AzureDataSourceJsonData } from '../types';
|
||||
import { AzureMonitorDataSourceInstanceSettings } from '../types';
|
||||
|
||||
import { createMockInstanceSetttings } from './instanceSettings';
|
||||
import { DeepPartial } from './utils';
|
||||
|
||||
export interface Context {
|
||||
instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>;
|
||||
instanceSettings: AzureMonitorDataSourceInstanceSettings;
|
||||
templateSrv: TemplateSrv;
|
||||
datasource: Datasource;
|
||||
getResource: jest.Mock;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { KeyValue } from '@grafana/data';
|
||||
|
||||
import { AzureDataSourceSettings } from '../types';
|
||||
import { AzureMonitorDataSourceSettings } from '../types';
|
||||
|
||||
import { DeepPartial } from './utils';
|
||||
|
||||
export const createMockDatasourceSettings = (
|
||||
overrides?: DeepPartial<AzureDataSourceSettings>,
|
||||
overrides?: DeepPartial<AzureMonitorDataSourceSettings>,
|
||||
secureJsonFieldsOverrides?: KeyValue<boolean>
|
||||
): AzureDataSourceSettings => {
|
||||
): AzureMonitorDataSourceSettings => {
|
||||
return {
|
||||
id: 1,
|
||||
uid: 'uid',
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { DataSourceInstanceSettings, PluginType } from '@grafana/data';
|
||||
|
||||
import { AzureDataSourceInstanceSettings } from '../types';
|
||||
import { AzureMonitorDataSourceInstanceSettings } from '../types';
|
||||
|
||||
import { DeepPartial, mapPartialArrayObject } from './utils';
|
||||
|
||||
export const createMockInstanceSetttings = (
|
||||
overrides?: DeepPartial<DataSourceInstanceSettings>
|
||||
): AzureDataSourceInstanceSettings => {
|
||||
): AzureMonitorDataSourceInstanceSettings => {
|
||||
const metaOverrides = overrides?.meta;
|
||||
return {
|
||||
url: '/ds/1',
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { map } from 'lodash';
|
||||
|
||||
import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
|
||||
import { AzureCredentials } from '@grafana/azure-sdk';
|
||||
import { ScopedVars } from '@grafana/data';
|
||||
import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import ResponseParser from '../azure_monitor/response_parser';
|
||||
import { getAuthType } from '../credentials';
|
||||
import { getCredentials } from '../credentials';
|
||||
import {
|
||||
AzureAPIResponse,
|
||||
AzureDataSourceJsonData,
|
||||
AzureMonitorDataSourceInstanceSettings,
|
||||
AzureMonitorDataSourceJsonData,
|
||||
AzureLogsVariable,
|
||||
AzureMonitorQuery,
|
||||
AzureQueryType,
|
||||
@@ -21,8 +23,9 @@ import { transformMetadataToKustoSchema } from './utils';
|
||||
|
||||
export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
|
||||
AzureMonitorQuery,
|
||||
AzureDataSourceJsonData
|
||||
AzureMonitorDataSourceJsonData
|
||||
> {
|
||||
readonly credentials: AzureCredentials;
|
||||
resourcePath: string;
|
||||
declare applicationId: string;
|
||||
|
||||
@@ -32,10 +35,11 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
|
||||
firstWorkspace?: string;
|
||||
|
||||
constructor(
|
||||
private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
||||
private instanceSettings: AzureMonitorDataSourceInstanceSettings,
|
||||
private readonly templateSrv: TemplateSrv = getTemplateSrv()
|
||||
) {
|
||||
super(instanceSettings);
|
||||
this.credentials = getCredentials(instanceSettings);
|
||||
|
||||
this.resourcePath = `${routeNames.logAnalytics}`;
|
||||
this.azureMonitorPath = `${routeNames.azureMonitor}/subscriptions`;
|
||||
@@ -222,17 +226,15 @@ export default class AzureLogAnalyticsDatasource extends DataSourceWithBackend<
|
||||
}
|
||||
|
||||
private validateDatasource(): DatasourceValidationResult | undefined {
|
||||
const authType = getAuthType(this.instanceSettings);
|
||||
|
||||
if (authType === 'clientsecret') {
|
||||
if (!this.isValidConfigField(this.instanceSettings.jsonData.tenantId)) {
|
||||
if (this.credentials.authType === 'clientsecret') {
|
||||
if (!this.isValidConfigField(this.credentials.tenantId)) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: 'The Tenant Id field is required.',
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.isValidConfigField(this.instanceSettings.jsonData.clientId)) {
|
||||
if (!this.isValidConfigField(this.credentials.clientId)) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: 'The Client Id field is required.',
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { get, set } from 'lodash';
|
||||
|
||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||
|
||||
import createMockQuery from '../__mocks__/query';
|
||||
import { createTemplateVariables } from '../__mocks__/utils';
|
||||
import { multiVariable } from '../__mocks__/variables';
|
||||
import AzureMonitorDatasource from '../datasource';
|
||||
import { AzureAPIResponse, AzureDataSourceJsonData, Location } from '../types';
|
||||
import { AzureAPIResponse, AzureMonitorDataSourceInstanceSettings, Location } from '../types';
|
||||
|
||||
let replace = () => '';
|
||||
|
||||
@@ -24,7 +22,7 @@ jest.mock('@grafana/runtime', () => {
|
||||
});
|
||||
|
||||
interface TestContext {
|
||||
instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>;
|
||||
instanceSettings: AzureMonitorDataSourceInstanceSettings;
|
||||
ds: AzureMonitorDatasource;
|
||||
}
|
||||
|
||||
@@ -37,7 +35,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
name: 'test',
|
||||
url: 'http://azuremonitor.com',
|
||||
jsonData: { subscriptionId: 'mock-subscription-id', cloudName: 'azuremonitor' },
|
||||
} as unknown as DataSourceInstanceSettings<AzureDataSourceJsonData>;
|
||||
} as unknown as AzureMonitorDataSourceInstanceSettings;
|
||||
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings);
|
||||
});
|
||||
|
||||
@@ -664,6 +662,7 @@ describe('AzureMonitorDatasource', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.instanceSettings.jsonData.azureAuthType = 'msi';
|
||||
ctx.ds = new AzureMonitorDatasource(ctx.instanceSettings);
|
||||
ctx.ds.azureMonitorDatasource.getResource = jest.fn().mockResolvedValue(response);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { Namespace } from 'i18next';
|
||||
import { find, startsWith } from 'lodash';
|
||||
|
||||
import { DataSourceInstanceSettings, ScopedVars } from '@grafana/data';
|
||||
import { AzureCredentials } from '@grafana/azure-sdk';
|
||||
import { ScopedVars } from '@grafana/data';
|
||||
import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import { getAuthType } from '../credentials';
|
||||
import { getCredentials } from '../credentials';
|
||||
import TimegrainConverter from '../time_grain_converter';
|
||||
import {
|
||||
AzureDataSourceJsonData,
|
||||
AzureMonitorDataSourceInstanceSettings,
|
||||
AzureMonitorDataSourceJsonData,
|
||||
AzureMonitorMetricsMetadataResponse,
|
||||
AzureMonitorQuery,
|
||||
AzureQueryType,
|
||||
@@ -37,7 +39,11 @@ function hasValue(item?: string) {
|
||||
return !!(item && item !== defaultDropdownValue);
|
||||
}
|
||||
|
||||
export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureMonitorQuery, AzureDataSourceJsonData> {
|
||||
export default class AzureMonitorDatasource extends DataSourceWithBackend<
|
||||
AzureMonitorQuery,
|
||||
AzureMonitorDataSourceJsonData
|
||||
> {
|
||||
private readonly credentials: AzureCredentials;
|
||||
apiVersion = '2018-01-01';
|
||||
apiPreviewVersion = '2017-12-01-preview';
|
||||
listByResourceGroupApiVersion = '2021-04-01';
|
||||
@@ -50,10 +56,11 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
||||
declare resourceName: string;
|
||||
|
||||
constructor(
|
||||
private instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
||||
instanceSettings: AzureMonitorDataSourceInstanceSettings,
|
||||
private readonly templateSrv: TemplateSrv = getTemplateSrv()
|
||||
) {
|
||||
super(instanceSettings);
|
||||
this.credentials = getCredentials(instanceSettings);
|
||||
|
||||
this.defaultSubscriptionId = instanceSettings.jsonData.subscriptionId;
|
||||
this.basicLogsEnabled = instanceSettings.jsonData.basicLogsEnabled;
|
||||
@@ -306,17 +313,15 @@ export default class AzureMonitorDatasource extends DataSourceWithBackend<AzureM
|
||||
}
|
||||
|
||||
private validateDatasource(): DatasourceValidationResult | undefined {
|
||||
const authType = getAuthType(this.instanceSettings);
|
||||
|
||||
if (authType === 'clientsecret') {
|
||||
if (!this.isValidConfigField(this.instanceSettings.jsonData.tenantId)) {
|
||||
if (this.credentials.authType === 'clientsecret') {
|
||||
if (!this.isValidConfigField(this.credentials.tenantId)) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: 'The Tenant Id field is required.',
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.isValidConfigField(this.instanceSettings.jsonData.clientId)) {
|
||||
if (!this.isValidConfigField(this.credentials.clientId)) {
|
||||
return {
|
||||
status: 'error',
|
||||
message: 'The Client Id field is required.',
|
||||
|
||||
@@ -4,12 +4,12 @@ import _ from 'lodash';
|
||||
import { ScopedVars } from '@grafana/data';
|
||||
import { getTemplateSrv, DataSourceWithBackend } from '@grafana/runtime';
|
||||
|
||||
import { AzureMonitorQuery, AzureDataSourceJsonData, AzureQueryType } from '../types';
|
||||
import { AzureMonitorQuery, AzureMonitorDataSourceJsonData, AzureQueryType } from '../types';
|
||||
import { interpolateVariable } from '../utils/common';
|
||||
|
||||
export default class AzureResourceGraphDatasource extends DataSourceWithBackend<
|
||||
AzureMonitorQuery,
|
||||
AzureDataSourceJsonData
|
||||
AzureMonitorDataSourceJsonData
|
||||
> {
|
||||
filterQuery(item: AzureMonitorQuery): boolean {
|
||||
return !!item.azureResourceGraph?.query && !!item.subscriptions && item.subscriptions.length > 0;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { ChangeEvent } from 'react';
|
||||
|
||||
import { AzureClientSecretCredentials, AzureCredentials } from '@grafana/azure-sdk';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Field, Select, Input, Button } from '@grafana/ui';
|
||||
|
||||
import { selectors } from '../../e2e/selectors';
|
||||
import { AzureClientSecretCredentials, AzureCredentials } from '../../types';
|
||||
|
||||
export interface AppRegistrationCredentialsProps {
|
||||
credentials: AzureClientSecretCredentials;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { getAzureClouds } from '@grafana/azure-sdk';
|
||||
import { AzureAuthType, AzureCredentials, getAzureClouds } from '@grafana/azure-sdk';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { ConfigSection } from '@grafana/experimental';
|
||||
import { Select, Field } from '@grafana/ui';
|
||||
|
||||
import { selectors } from '../../e2e/selectors';
|
||||
import { AzureAuthType, AzureCredentials } from '../../types';
|
||||
|
||||
import { AppRegistrationCredentials } from './AppRegistrationCredentials';
|
||||
import CurrentUserFallbackCredentials from './CurrentUserFallbackCredentials';
|
||||
|
||||
@@ -3,10 +3,10 @@ import * as React from 'react';
|
||||
|
||||
import { Field, Switch, useTheme2 } from '@grafana/ui';
|
||||
|
||||
import { AzureDataSourceJsonData } from '../../types';
|
||||
import { AzureMonitorDataSourceJsonData } from '../../types';
|
||||
|
||||
export interface Props {
|
||||
options: AzureDataSourceJsonData;
|
||||
options: AzureMonitorDataSourceJsonData;
|
||||
onBasicLogsEnabledChange: (basicLogsEnabled: boolean) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,16 +8,19 @@ import { Alert, Divider, SecureSocksProxySettings } from '@grafana/ui';
|
||||
import ResponseParser from '../../azure_monitor/response_parser';
|
||||
import {
|
||||
AzureAPIResponse,
|
||||
AzureDataSourceJsonData,
|
||||
AzureDataSourceSecureJsonData,
|
||||
AzureDataSourceSettings,
|
||||
AzureMonitorDataSourceJsonData,
|
||||
AzureMonitorDataSourceSecureJsonData,
|
||||
AzureMonitorDataSourceSettings,
|
||||
Subscription,
|
||||
} from '../../types';
|
||||
import { routeNames } from '../../utils/common';
|
||||
|
||||
import { MonitorConfig } from './MonitorConfig';
|
||||
|
||||
export type Props = DataSourcePluginOptionsEditorProps<AzureDataSourceJsonData, AzureDataSourceSecureJsonData>;
|
||||
export type Props = DataSourcePluginOptionsEditorProps<
|
||||
AzureMonitorDataSourceJsonData,
|
||||
AzureMonitorDataSourceSecureJsonData
|
||||
>;
|
||||
|
||||
interface ErrorMessage {
|
||||
title: string;
|
||||
@@ -43,7 +46,9 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
this.baseURL = `/api/datasources/${this.props.options.id}/resources/${routeNames.azureMonitor}/subscriptions`;
|
||||
}
|
||||
|
||||
private updateOptions = (optionsFunc: (options: AzureDataSourceSettings) => AzureDataSourceSettings): void => {
|
||||
private updateOptions = (
|
||||
optionsFunc: (options: AzureMonitorDataSourceSettings) => AzureMonitorDataSourceSettings
|
||||
): void => {
|
||||
const updated = optionsFunc(this.props.options);
|
||||
this.props.onOptionsChange(updated);
|
||||
|
||||
@@ -54,7 +59,7 @@ export class ConfigEditor extends PureComponent<Props, State> {
|
||||
if (this.state.unsaved) {
|
||||
await getBackendSrv()
|
||||
.put(`/api/datasources/${this.props.options.id}`, this.props.options)
|
||||
.then((result: { datasource: AzureDataSourceSettings }) => {
|
||||
.then((result: { datasource: AzureMonitorDataSourceSettings }) => {
|
||||
updateDatasourcePluginOption(this.props, 'version', result.datasource.version);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { AadCurrentUserCredentials, AzureCredentials, instanceOfAzureCredential } from '@grafana/azure-sdk';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { ConfigSection } from '@grafana/experimental';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Select, Field, RadioButtonGroup, Alert, Stack } from '@grafana/ui';
|
||||
|
||||
import { instanceOfAzureCredential } from '../../credentials';
|
||||
import { selectors } from '../../e2e/selectors';
|
||||
import { AadCurrentUserCredentials, AzureAuthType, AzureCredentials } from '../../types';
|
||||
|
||||
import { AppRegistrationCredentials } from './AppRegistrationCredentials';
|
||||
|
||||
@@ -32,8 +31,9 @@ export const CurrentUserFallbackCredentials = (props: Props) => {
|
||||
workloadIdentityEnabled,
|
||||
} = props;
|
||||
|
||||
type FallbackCredentialAuthTypeOptions = 'clientsecret' | 'msi' | 'workloadidentity';
|
||||
const authTypeOptions = useMemo(() => {
|
||||
let opts: Array<SelectableValue<Exclude<AzureAuthType, 'currentuser'>>> = [
|
||||
let opts: Array<SelectableValue<FallbackCredentialAuthTypeOptions>> = [
|
||||
{
|
||||
value: 'clientsecret',
|
||||
label: 'App Registration',
|
||||
@@ -57,7 +57,7 @@ export const CurrentUserFallbackCredentials = (props: Props) => {
|
||||
return opts;
|
||||
}, [managedIdentityEnabled, workloadIdentityEnabled]);
|
||||
|
||||
const onAuthTypeChange = (selected: SelectableValue<Exclude<AzureAuthType, 'currentuser'>>) => {
|
||||
const onAuthTypeChange = (selected: SelectableValue<FallbackCredentialAuthTypeOptions>) => {
|
||||
const defaultAuthType = managedIdentityEnabled
|
||||
? 'msi'
|
||||
: workloadIdentityEnabled
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { useEffect, useReducer } from 'react';
|
||||
|
||||
import { AzureCredentials, isCredentialsComplete } from '@grafana/azure-sdk';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Select, Button, Field } from '@grafana/ui';
|
||||
|
||||
import { isCredentialsComplete } from '../../credentials';
|
||||
import { selectors } from '../../e2e/selectors';
|
||||
import { AzureCredentials, AzureDataSourceJsonData } from '../../types';
|
||||
import { AzureMonitorDataSourceJsonData } from '../../types';
|
||||
|
||||
export interface Props {
|
||||
options: AzureDataSourceJsonData;
|
||||
options: AzureMonitorDataSourceJsonData;
|
||||
credentials: AzureCredentials;
|
||||
getSubscriptions?: () => Promise<SelectableValue[]>;
|
||||
subscriptions: Array<SelectableValue<string>>;
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useEffectOnce } from 'react-use';
|
||||
|
||||
import { AzureCredentials } from '@grafana/azure-sdk';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import { getCredentials, updateCredentials } from '../../credentials';
|
||||
import { AzureDataSourceSettings, AzureCredentials } from '../../types';
|
||||
import { AzureMonitorDataSourceSettings } from '../../types';
|
||||
|
||||
import { AzureCredentialsForm, getAzureCloudOptions } from './AzureCredentialsForm';
|
||||
import { BasicLogsToggle } from './BasicLogsToggle';
|
||||
import { DefaultSubscription } from './DefaultSubscription';
|
||||
|
||||
export interface Props {
|
||||
options: AzureDataSourceSettings;
|
||||
updateOptions: (optionsFunc: (options: AzureDataSourceSettings) => AzureDataSourceSettings) => void;
|
||||
options: AzureMonitorDataSourceSettings;
|
||||
updateOptions: (optionsFunc: (options: AzureMonitorDataSourceSettings) => AzureMonitorDataSourceSettings) => void;
|
||||
getSubscriptions: () => Promise<Array<SelectableValue<string>>>;
|
||||
}
|
||||
|
||||
@@ -26,7 +27,7 @@ export const MonitorConfig = (props: Props) => {
|
||||
if (!subscriptionId) {
|
||||
setSubscriptions([]);
|
||||
}
|
||||
updateOptions((options) =>
|
||||
updateOptions((options: AzureMonitorDataSourceSettings) =>
|
||||
updateCredentials({ ...options, jsonData: { ...options.jsonData, subscriptionId } }, credentials)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Alert, Button, CodeEditor, Space } from '@grafana/ui';
|
||||
import AzureMonitorDatasource from '../../datasource';
|
||||
import { selectors } from '../../e2e/selectors';
|
||||
import {
|
||||
AzureDataSourceJsonData,
|
||||
AzureMonitorDataSourceJsonData,
|
||||
AzureMonitorErrorish,
|
||||
AzureMonitorOption,
|
||||
AzureMonitorQuery,
|
||||
@@ -28,7 +28,7 @@ import usePreparedQuery from './usePreparedQuery';
|
||||
export type AzureMonitorQueryEditorProps = QueryEditorProps<
|
||||
AzureMonitorDatasource,
|
||||
AzureMonitorQuery,
|
||||
AzureDataSourceJsonData
|
||||
AzureMonitorDataSourceJsonData
|
||||
>;
|
||||
|
||||
const QueryEditor = ({
|
||||
|
||||
@@ -1,259 +1,74 @@
|
||||
import { getAzureClouds } from '@grafana/azure-sdk';
|
||||
import {
|
||||
AzureCredentials,
|
||||
getDatasourceCredentials,
|
||||
getDefaultAzureCloud,
|
||||
getClientSecret,
|
||||
resolveLegacyCloudName,
|
||||
updateDatasourceCredentials,
|
||||
} from '@grafana/azure-sdk';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import {
|
||||
AadCurrentUserCredentials,
|
||||
AzureAuthType,
|
||||
AzureClientSecretCredentials,
|
||||
AzureCloud,
|
||||
AzureCredentials,
|
||||
AzureDataSourceInstanceSettings,
|
||||
AzureDataSourceSettings,
|
||||
ConcealedSecret,
|
||||
} from './types';
|
||||
import { AzureMonitorDataSourceInstanceSettings, AzureMonitorDataSourceSettings } from './types';
|
||||
|
||||
const concealed: ConcealedSecret = Symbol('Concealed client secret');
|
||||
|
||||
export function getAuthType(options: AzureDataSourceSettings | AzureDataSourceInstanceSettings): AzureAuthType {
|
||||
if (!options.jsonData.azureAuthType) {
|
||||
// If authentication type isn't explicitly specified and datasource has client credentials,
|
||||
// then this is existing datasource which is configured for app registration (client secret)
|
||||
if (options.jsonData.tenantId && options.jsonData.clientId) {
|
||||
return 'clientsecret';
|
||||
}
|
||||
|
||||
// For newly created datasource with no configuration, managed identity is the default authentication type
|
||||
// if they are enabled in Grafana config
|
||||
return config.azure.managedIdentityEnabled ? 'msi' : 'clientsecret';
|
||||
export function getCredentials(
|
||||
options: AzureMonitorDataSourceSettings | AzureMonitorDataSourceInstanceSettings
|
||||
): AzureCredentials {
|
||||
// Try to get the credentials from the datasource settings,
|
||||
// If not found, return the legacy azure monitor credentials if they exist or fallback to default credentials
|
||||
const creds = getDatasourceCredentials(options);
|
||||
if (creds) {
|
||||
return creds;
|
||||
}
|
||||
|
||||
return options.jsonData.azureAuthType;
|
||||
}
|
||||
|
||||
function resolveLegacyCloudName(cloudName: string | undefined): string | undefined {
|
||||
if (!cloudName) {
|
||||
// if undefined, allow the code to fallback to calling getDefaultAzureCloud() since that has the complete logic for handling an empty cloud name
|
||||
return undefined;
|
||||
}
|
||||
switch (cloudName) {
|
||||
case 'azuremonitor':
|
||||
return AzureCloud.Public;
|
||||
case 'chinaazuremonitor':
|
||||
return AzureCloud.China;
|
||||
case 'govazuremonitor':
|
||||
return AzureCloud.USGovernment;
|
||||
default:
|
||||
return cloudName;
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultAzureCloud(): string {
|
||||
const cloudName = resolveLegacyCloudName(config.azure.cloud);
|
||||
|
||||
switch (cloudName) {
|
||||
case AzureCloud.Public:
|
||||
case AzureCloud.None:
|
||||
return AzureCloud.Public;
|
||||
case AzureCloud.China:
|
||||
return AzureCloud.China;
|
||||
case AzureCloud.USGovernment:
|
||||
return AzureCloud.USGovernment;
|
||||
default:
|
||||
const cloudInfo = getAzureClouds();
|
||||
|
||||
for (const cloud of cloudInfo) {
|
||||
if (cloud.name === config.azure.cloud) {
|
||||
return cloud.name;
|
||||
}
|
||||
}
|
||||
throw new Error(`The cloud '${config.azure.cloud}' is unsupported.`);
|
||||
}
|
||||
}
|
||||
|
||||
export function getAzureCloud(options: AzureDataSourceSettings | AzureDataSourceInstanceSettings): string {
|
||||
const authType = getAuthType(options);
|
||||
switch (authType) {
|
||||
case 'msi':
|
||||
case 'workloadidentity':
|
||||
// In case of managed identity and workload identity, the cloud is always same as where Grafana is hosted
|
||||
return getDefaultAzureCloud();
|
||||
case 'clientsecret':
|
||||
case 'currentuser':
|
||||
return resolveLegacyCloudName(options.jsonData.cloudName) || getDefaultAzureCloud();
|
||||
}
|
||||
}
|
||||
|
||||
function getSecret(options: AzureDataSourceSettings): undefined | string | ConcealedSecret {
|
||||
if (options.secureJsonFields.clientSecret) {
|
||||
// The secret is concealed on server
|
||||
return concealed;
|
||||
} else {
|
||||
const secret = options.secureJsonData?.clientSecret;
|
||||
return typeof secret === 'string' && secret.length > 0 ? secret : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function isCredentialsComplete(credentials: AzureCredentials, ignoreSecret = false): boolean {
|
||||
switch (credentials.authType) {
|
||||
case 'msi':
|
||||
case 'workloadidentity':
|
||||
case 'currentuser':
|
||||
return true;
|
||||
case 'clientsecret':
|
||||
return !!(
|
||||
credentials.azureCloud &&
|
||||
credentials.tenantId &&
|
||||
credentials.clientId &&
|
||||
// When ignoreSecret is set we consider the credentials complete without checking the secret
|
||||
!!(ignoreSecret || credentials.clientSecret)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function instanceOfAzureCredential<T extends AzureCredentials>(
|
||||
authType: AzureAuthType,
|
||||
object?: AzureCredentials
|
||||
): object is T {
|
||||
if (!object) {
|
||||
return false;
|
||||
}
|
||||
return object.authType === authType;
|
||||
}
|
||||
|
||||
export function getCredentials(options: AzureDataSourceSettings): AzureCredentials {
|
||||
const authType = getAuthType(options);
|
||||
const credentials = options.jsonData.azureCredentials;
|
||||
switch (authType) {
|
||||
case 'msi':
|
||||
case 'workloadidentity':
|
||||
if (
|
||||
(authType === 'msi' && config.azure.managedIdentityEnabled) ||
|
||||
(authType === 'workloadidentity' && config.azure.workloadIdentityEnabled)
|
||||
) {
|
||||
return {
|
||||
authType,
|
||||
};
|
||||
} else {
|
||||
// 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',
|
||||
azureCloud: getDefaultAzureCloud(),
|
||||
};
|
||||
}
|
||||
case 'clientsecret':
|
||||
return {
|
||||
authType,
|
||||
azureCloud: resolveLegacyCloudName(options.jsonData.cloudName) || getDefaultAzureCloud(),
|
||||
tenantId: options.jsonData.tenantId,
|
||||
clientId: options.jsonData.clientId,
|
||||
clientSecret: getSecret(options),
|
||||
};
|
||||
}
|
||||
if (instanceOfAzureCredential<AadCurrentUserCredentials>(authType, credentials)) {
|
||||
if (instanceOfAzureCredential<AzureClientSecretCredentials>('clientsecret', credentials.serviceCredentials)) {
|
||||
const serviceCredentials = { ...credentials.serviceCredentials, clientSecret: getSecret(options) };
|
||||
return {
|
||||
authType,
|
||||
serviceCredentialsEnabled: credentials.serviceCredentialsEnabled,
|
||||
serviceCredentials,
|
||||
};
|
||||
}
|
||||
return {
|
||||
authType,
|
||||
serviceCredentialsEnabled: credentials.serviceCredentialsEnabled,
|
||||
serviceCredentials: credentials.serviceCredentials,
|
||||
};
|
||||
}
|
||||
return {
|
||||
authType: 'clientsecret',
|
||||
azureCloud: getDefaultAzureCloud(),
|
||||
};
|
||||
return getLegacyCredentials(options) || getDefaultCredentials();
|
||||
}
|
||||
|
||||
export function updateCredentials(
|
||||
options: AzureDataSourceSettings,
|
||||
options: AzureMonitorDataSourceSettings,
|
||||
credentials: AzureCredentials
|
||||
): AzureDataSourceSettings {
|
||||
switch (credentials.authType) {
|
||||
case 'msi':
|
||||
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: credentials.authType,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return options;
|
||||
|
||||
case 'clientsecret':
|
||||
options = {
|
||||
...options,
|
||||
jsonData: {
|
||||
...options.jsonData,
|
||||
azureAuthType: credentials.authType,
|
||||
cloudName: resolveLegacyCloudName(credentials.azureCloud) || getDefaultAzureCloud(),
|
||||
tenantId: credentials.tenantId,
|
||||
clientId: credentials.clientId,
|
||||
azureCredentials: {
|
||||
authType: credentials.authType,
|
||||
azureCloud: resolveLegacyCloudName(credentials.azureCloud) || getDefaultAzureCloud(),
|
||||
tenantId: credentials.tenantId,
|
||||
clientId: credentials.clientId,
|
||||
},
|
||||
},
|
||||
secureJsonData: {
|
||||
...options.secureJsonData,
|
||||
clientSecret: typeof credentials.clientSecret === 'string' ? credentials.clientSecret : undefined,
|
||||
},
|
||||
secureJsonFields: {
|
||||
...options.secureJsonFields,
|
||||
clientSecret: typeof credentials.clientSecret === 'symbol',
|
||||
},
|
||||
};
|
||||
}
|
||||
if (instanceOfAzureCredential<AadCurrentUserCredentials>('currentuser', credentials)) {
|
||||
const serviceCredentials = credentials.serviceCredentials;
|
||||
let clientSecret: string | symbol | undefined;
|
||||
if (instanceOfAzureCredential<AzureClientSecretCredentials>('clientsecret', serviceCredentials)) {
|
||||
clientSecret = serviceCredentials.clientSecret;
|
||||
// Do this to not expose the secret in unencrypted JSON data
|
||||
delete serviceCredentials.clientSecret;
|
||||
}
|
||||
options = {
|
||||
...options,
|
||||
jsonData: {
|
||||
...options.jsonData,
|
||||
azureAuthType: credentials.authType,
|
||||
azureCredentials: {
|
||||
authType: 'currentuser',
|
||||
serviceCredentialsEnabled: credentials.serviceCredentialsEnabled,
|
||||
serviceCredentials,
|
||||
},
|
||||
oauthPassThru: true,
|
||||
disableGrafanaCache: true,
|
||||
},
|
||||
secureJsonData: {
|
||||
...options.secureJsonData,
|
||||
clientSecret: typeof clientSecret === 'string' ? clientSecret : undefined,
|
||||
},
|
||||
secureJsonFields: {
|
||||
...options.secureJsonFields,
|
||||
clientSecret: typeof clientSecret === 'symbol',
|
||||
},
|
||||
};
|
||||
}
|
||||
return options;
|
||||
): AzureMonitorDataSourceSettings {
|
||||
return updateDatasourceCredentials(options, credentials);
|
||||
}
|
||||
|
||||
function getLegacyCredentials(
|
||||
options: AzureMonitorDataSourceSettings | AzureMonitorDataSourceInstanceSettings
|
||||
): AzureCredentials | undefined {
|
||||
try {
|
||||
// If authentication type isn't explicitly specified and datasource has client credentials,
|
||||
// then this is existing datasource which is configured for app registration (client secret)
|
||||
if (
|
||||
options.jsonData.azureAuthType === 'clientsecret' ||
|
||||
(!options.jsonData.azureAuthType && options.jsonData.tenantId && options.jsonData.clientId)
|
||||
) {
|
||||
return {
|
||||
authType: 'clientsecret',
|
||||
tenantId: options.jsonData.tenantId,
|
||||
clientId: options.jsonData.clientId,
|
||||
azureCloud: resolveLegacyCloudName(options.jsonData.cloudName) || getDefaultAzureCloud(),
|
||||
clientSecret: getClientSecret(options),
|
||||
};
|
||||
}
|
||||
|
||||
// If the authentication type is not set, then no legacy credentials exist so return undefined
|
||||
if (!options.jsonData.azureAuthType) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return { authType: options.jsonData.azureAuthType };
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
console.error('Unable to restore legacy credentials: %s', e.message);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultCredentials(): AzureCredentials {
|
||||
if (config.azure.managedIdentityEnabled) {
|
||||
return { authType: 'msi' };
|
||||
} else if (config.azure.workloadIdentityEnabled) {
|
||||
return { authType: 'workloadidentity' };
|
||||
} else {
|
||||
return { authType: 'clientsecret', azureCloud: getDefaultAzureCloud() };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { cloneDeep } from 'lodash';
|
||||
import { forkJoin, Observable, of } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { AadCurrentUserCredentials, instanceOfAzureCredential, isCredentialsComplete } from '@grafana/azure-sdk';
|
||||
import {
|
||||
DataFrame,
|
||||
DataQueryRequest,
|
||||
@@ -16,14 +17,13 @@ import { DataSourceWithBackend, getTemplateSrv, TemplateSrv } from '@grafana/run
|
||||
import AzureLogAnalyticsDatasource from './azure_log_analytics/azure_log_analytics_datasource';
|
||||
import AzureMonitorDatasource from './azure_monitor/azure_monitor_datasource';
|
||||
import AzureResourceGraphDatasource from './azure_resource_graph/azure_resource_graph_datasource';
|
||||
import { instanceOfAzureCredential, isCredentialsComplete } from './credentials';
|
||||
import ResourcePickerData from './resourcePicker/resourcePickerData';
|
||||
import { AadCurrentUserCredentials, AzureDataSourceJsonData, AzureMonitorQuery, AzureQueryType } from './types';
|
||||
import { AzureMonitorDataSourceJsonData, AzureMonitorQuery, AzureQueryType } from './types';
|
||||
import migrateAnnotation from './utils/migrateAnnotation';
|
||||
import migrateQuery from './utils/migrateQuery';
|
||||
import { VariableSupport } from './variables';
|
||||
|
||||
export default class Datasource extends DataSourceWithBackend<AzureMonitorQuery, AzureDataSourceJsonData> {
|
||||
export default class Datasource extends DataSourceWithBackend<AzureMonitorQuery, AzureMonitorDataSourceJsonData> {
|
||||
annotations = {
|
||||
prepareAnnotation: migrateAnnotation,
|
||||
};
|
||||
@@ -42,7 +42,7 @@ export default class Datasource extends DataSourceWithBackend<AzureMonitorQuery,
|
||||
declare optionsKey: Record<AzureQueryType, string>;
|
||||
|
||||
constructor(
|
||||
instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
||||
instanceSettings: DataSourceInstanceSettings<AzureMonitorDataSourceJsonData>,
|
||||
private readonly templateSrv: TemplateSrv = getTemplateSrv()
|
||||
) {
|
||||
super(instanceSettings);
|
||||
|
||||
@@ -6,9 +6,9 @@ import AzureMonitorQueryEditor from './components/QueryEditor';
|
||||
import Datasource from './datasource';
|
||||
import pluginJson from './plugin.json';
|
||||
import { trackAzureMonitorDashboardLoaded } from './tracking';
|
||||
import { AzureMonitorQuery, AzureDataSourceJsonData, AzureQueryType, ResultFormat } from './types';
|
||||
import { AzureMonitorQuery, AzureMonitorDataSourceJsonData, AzureQueryType, ResultFormat } from './types';
|
||||
|
||||
export const plugin = new DataSourcePlugin<Datasource, AzureMonitorQuery, AzureDataSourceJsonData>(Datasource)
|
||||
export const plugin = new DataSourcePlugin<Datasource, AzureMonitorQuery, AzureMonitorDataSourceJsonData>(Datasource)
|
||||
.setConfigEditor(ConfigEditor)
|
||||
.setQueryEditor(AzureMonitorQueryEditor);
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { DataSourceInstanceSettings } from '@grafana/data';
|
||||
import { DataSourceWithBackend, reportInteraction } from '@grafana/runtime';
|
||||
|
||||
import { logsResourceTypes, resourceTypeDisplayNames, resourceTypes } from '../azureMetadata';
|
||||
@@ -13,7 +12,8 @@ import {
|
||||
resourceToString,
|
||||
} from '../components/ResourcePicker/utils';
|
||||
import {
|
||||
AzureDataSourceJsonData,
|
||||
AzureMonitorDataSourceInstanceSettings,
|
||||
AzureMonitorDataSourceJsonData,
|
||||
AzureGraphResponse,
|
||||
AzureMonitorResource,
|
||||
AzureMonitorQuery,
|
||||
@@ -31,14 +31,17 @@ const logsSupportedResourceTypesKusto = logsResourceTypes.map((v) => `"${v}"`).j
|
||||
|
||||
export type ResourcePickerQueryType = 'logs' | 'metrics' | 'traces';
|
||||
|
||||
export default class ResourcePickerData extends DataSourceWithBackend<AzureMonitorQuery, AzureDataSourceJsonData> {
|
||||
export default class ResourcePickerData extends DataSourceWithBackend<
|
||||
AzureMonitorQuery,
|
||||
AzureMonitorDataSourceJsonData
|
||||
> {
|
||||
private resourcePath: string;
|
||||
resultLimit = 200;
|
||||
azureMonitorDatasource;
|
||||
supportedMetricNamespaces = '';
|
||||
|
||||
constructor(
|
||||
instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
||||
instanceSettings: AzureMonitorDataSourceInstanceSettings,
|
||||
azureMonitorDatasource: AzureMonitorDatasource
|
||||
) {
|
||||
super(instanceSettings);
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import { ScalarParameter, TabularParameter, Function, EntityGroup } from '@kusto/monaco-kusto';
|
||||
|
||||
import {
|
||||
DataSourceInstanceSettings,
|
||||
DataSourceJsonData,
|
||||
DataSourceSettings,
|
||||
PanelData,
|
||||
SelectableValue,
|
||||
TimeRange,
|
||||
} from '@grafana/data';
|
||||
import { AzureDataSourceSecureJsonData, AzureDataSourceJsonData } from '@grafana/azure-sdk';
|
||||
import { DataSourceInstanceSettings, DataSourceSettings, PanelData, SelectableValue, TimeRange } from '@grafana/data';
|
||||
|
||||
import Datasource from '../datasource';
|
||||
|
||||
import { AzureLogAnalyticsMetadataTable } from './logAnalyticsMetadata';
|
||||
import { AzureMonitorQuery, ResultFormat } from './query';
|
||||
|
||||
export type AzureDataSourceSettings = DataSourceSettings<AzureDataSourceJsonData, AzureDataSourceSecureJsonData>;
|
||||
export type AzureDataSourceInstanceSettings = DataSourceInstanceSettings<AzureDataSourceJsonData>;
|
||||
export type AzureMonitorDataSourceSettings = DataSourceSettings<
|
||||
AzureMonitorDataSourceJsonData,
|
||||
AzureMonitorDataSourceSecureJsonData
|
||||
>;
|
||||
export type AzureMonitorDataSourceInstanceSettings = DataSourceInstanceSettings<AzureMonitorDataSourceJsonData>;
|
||||
|
||||
export interface DatasourceValidationResult {
|
||||
status: 'success' | 'error';
|
||||
@@ -23,64 +20,9 @@ export interface DatasourceValidationResult {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Azure clouds known to Azure Monitor.
|
||||
*/
|
||||
export enum AzureCloud {
|
||||
Public = 'AzureCloud',
|
||||
China = 'AzureChinaCloud',
|
||||
USGovernment = 'AzureUSGovernment',
|
||||
None = '',
|
||||
}
|
||||
|
||||
export type AzureAuthType = 'msi' | 'clientsecret' | 'workloadidentity' | 'currentuser';
|
||||
|
||||
export type ConcealedSecret = symbol;
|
||||
|
||||
interface AzureCredentialsBase {
|
||||
authType: AzureAuthType;
|
||||
}
|
||||
|
||||
export interface AzureManagedIdentityCredentials extends AzureCredentialsBase {
|
||||
authType: 'msi';
|
||||
}
|
||||
|
||||
export interface AzureWorkloadIdentityCredentials extends AzureCredentialsBase {
|
||||
authType: 'workloadidentity';
|
||||
}
|
||||
|
||||
export interface AzureClientSecretCredentials extends AzureCredentialsBase {
|
||||
authType: 'clientsecret';
|
||||
azureCloud?: string;
|
||||
tenantId?: string;
|
||||
clientId?: string;
|
||||
clientSecret?: string | ConcealedSecret;
|
||||
}
|
||||
export interface AadCurrentUserCredentials extends AzureCredentialsBase {
|
||||
authType: 'currentuser';
|
||||
serviceCredentials?:
|
||||
| AzureClientSecretCredentials
|
||||
| AzureManagedIdentityCredentials
|
||||
| AzureWorkloadIdentityCredentials;
|
||||
serviceCredentialsEnabled?: boolean;
|
||||
}
|
||||
|
||||
export type AzureCredentials =
|
||||
| AadCurrentUserCredentials
|
||||
| AzureManagedIdentityCredentials
|
||||
| AzureClientSecretCredentials
|
||||
| AzureWorkloadIdentityCredentials;
|
||||
|
||||
export interface AzureDataSourceJsonData extends DataSourceJsonData {
|
||||
cloudName: string;
|
||||
azureAuthType?: AzureAuthType;
|
||||
|
||||
export interface AzureMonitorDataSourceJsonData extends AzureDataSourceJsonData {
|
||||
// monitor
|
||||
tenantId?: string;
|
||||
clientId?: string;
|
||||
subscriptionId?: string;
|
||||
oauthPassThru?: boolean;
|
||||
azureCredentials?: AzureCredentials;
|
||||
basicLogsEnabled?: boolean;
|
||||
|
||||
// logs
|
||||
@@ -101,8 +43,7 @@ export interface AzureDataSourceJsonData extends DataSourceJsonData {
|
||||
enableSecureSocksProxy?: boolean;
|
||||
}
|
||||
|
||||
export interface AzureDataSourceSecureJsonData {
|
||||
clientSecret?: string;
|
||||
export interface AzureMonitorDataSourceSecureJsonData extends AzureDataSourceSecureJsonData {
|
||||
appInsightsApiKey?: string;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user