Skip to content

Commit 319a874

Browse files
bossincaangelisc
andauthored
MSSQL: Password auth for Azure AD (grafana#89746)
* Password auth for Azure AD * rename auth fields * add azure flag for client password cred enabled * prettier * rename flag * Update go.mod * Update public/app/plugins/datasource/mssql/azureauth/AzureCredentialsForm.tsx Co-authored-by: Andreas Christou <[email protected]> * Apply suggestions from code review Co-authored-by: Andreas Christou <[email protected]> * update package * go mod * prettier * remove password * gowork * remove unused env test * linter --------- Co-authored-by: Andreas Christou <[email protected]>
1 parent ac21fa8 commit 319a874

File tree

19 files changed

+215
-42
lines changed

19 files changed

+215
-42
lines changed

conf/defaults.ini

+4
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,10 @@ username_assertion =
997997
# By default this will include all Grafana Labs owned Azure plugins, or those that make use of Azure settings (Azure Monitor, Azure Data Explorer, Prometheus, MSSQL).
998998
forward_settings_to_plugins = grafana-azure-monitor-datasource, prometheus, grafana-azure-data-explorer-datasource, mssql
999999

1000+
# Specifies whether Entra password auth can be used for the MSSQL data source
1001+
# Disabled by default, needs to be explicitly enabled
1002+
azure_entra_password_credentials_enabled = false
1003+
10001004
#################################### Role-based Access Control ###########
10011005
[rbac]
10021006
# If enabled, cache permissions in a in memory cache

conf/sample.ini

+4
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,10 @@
984984
# By default this will include all Grafana Labs owned Azure plugins, or those that make use of Azure settings (Azure Monitor, Azure Data Explorer, Prometheus, MSSQL).
985985
;forward_settings_to_plugins = grafana-azure-monitor-datasource, prometheus, grafana-azure-data-explorer-datasource, mssql
986986

987+
# Specifies whether Entra password auth can be used for the MSSQL data source
988+
# Disabled by default, needs to be explicitly enabled
989+
;azure_entra_password_credentials_enabled = false
990+
987991
#################################### Role-based Access Control ###########
988992
[rbac]
989993
;permission_cache = true

docs/sources/setup-grafana/configure-grafana/_index.md

+6
Original file line numberDiff line numberDiff line change
@@ -1275,6 +1275,12 @@ Set plugins that will receive Azure settings via plugin context.
12751275

12761276
By default, this will include all Grafana Labs owned Azure plugins or those that use Azure settings (Azure Monitor, Azure Data Explorer, Prometheus, MSSQL).
12771277

1278+
### azure_entra_password_credentials_enabled
1279+
1280+
Specifies whether Entra password auth can be used for the MSSQL data source. This authentication is not recommended and consideration should be taken before enabling this.
1281+
1282+
Disabled by default, needs to be explicitly enabled.
1283+
12781284
## [auth.jwt]
12791285

12801286
Refer to [JWT authentication]({{< relref "../configure-security/configure-authentication/jwt" >}}) for more information.

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ require (
8888
github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 // @grafana/sharing-squad
8989
github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56 // @grafana/grafana-operator-experience-squad
9090
github.com/grafana/grafana-aws-sdk v0.28.0 // @grafana/aws-datasources
91-
github.com/grafana/grafana-azure-sdk-go/v2 v2.0.4 // @grafana/partner-datasources
91+
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.0 // @grafana/partner-datasources
9292
github.com/grafana/grafana-cloud-migration-snapshot v1.1.0 // @grafana/grafana-operator-experience-squad
9393
github.com/grafana/grafana-google-sdk-go v0.1.0 // @grafana/partner-datasources
9494
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group

go.work.sum

+2
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,8 @@ github.com/grafana/e2e v0.1.1-0.20221018202458-cffd2bb71c7b h1:Ha+kSIoTutf4ytlVw
416416
github.com/grafana/e2e v0.1.1-0.20221018202458-cffd2bb71c7b/go.mod h1:3UsooRp7yW5/NJQBlXcTsAHOoykEhNUYXkQ3r6ehEEY=
417417
github.com/grafana/e2e v0.1.1 h1:/b6xcv5BtoBnx8cZnCiey9DbjEc8z7gXHO5edoeRYxc=
418418
github.com/grafana/e2e v0.1.1/go.mod h1:RpNLgae5VT+BUHvPE+/zSypmOXKwEu4t+tnEMS1ATaE=
419+
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.0 h1:lajVqTWaE96MpbjZToj7EshvqgRWOfYNkD4MbIZizaY=
420+
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.0/go.mod h1:aKlFPE36IDa8qccRg3KbgZX3MQ5xymS3RelT4j6kkVU=
419421
github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b h1:HCbWyVL6vi7gxyO76gQksSPH203oBJ1MJ3JcG1OQlsg=
420422
github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b/go.mod h1:01sXtHoRwI8W324IPAzuxDFOmALqYLCOhvSC2fUHWXc=
421423
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=

packages/grafana-runtime/src/config.ts

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface AzureSettings {
2626
workloadIdentityEnabled: boolean;
2727
userIdentityEnabled: boolean;
2828
userIdentityFallbackCredentialsEnabled: boolean;
29+
azureEntraPasswordCredentialsEnabled: boolean;
2930
}
3031

3132
export interface AzureCloudInfo {
@@ -131,6 +132,7 @@ export class GrafanaBootConfig implements GrafanaConfig {
131132
workloadIdentityEnabled: false,
132133
userIdentityEnabled: false,
133134
userIdentityFallbackCredentialsEnabled: false,
135+
azureEntraPasswordCredentialsEnabled: false,
134136
};
135137
caching = {
136138
enabled: false,

pkg/api/dtos/frontend_settings.go

+7-6
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,13 @@ type FrontendSettingsLicenseInfoDTO struct {
6666
}
6767

6868
type FrontendSettingsAzureDTO struct {
69-
Cloud string `json:"cloud"`
70-
Clouds []azsettings.AzureCloudInfo `json:"clouds"`
71-
ManagedIdentityEnabled bool `json:"managedIdentityEnabled"`
72-
WorkloadIdentityEnabled bool `json:"workloadIdentityEnabled"`
73-
UserIdentityEnabled bool `json:"userIdentityEnabled"`
74-
UserIdentityFallbackCredentialsEnabled bool `json:"userIdentityFallbackCredentialsEnabled"`
69+
Cloud string `json:"cloud,omitempty"`
70+
Clouds []azsettings.AzureCloudInfo `json:"clouds,omitempty"`
71+
ManagedIdentityEnabled bool `json:"managedIdentityEnabled,omitempty"`
72+
WorkloadIdentityEnabled bool `json:"workloadIdentityEnabled,omitempty"`
73+
UserIdentityEnabled bool `json:"userIdentityEnabled,omitempty"`
74+
UserIdentityFallbackCredentialsEnabled bool `json:"userIdentityFallbackCredentialsEnabled,omitempty"`
75+
AzureEntraPasswordCredentialsEnabled bool `json:"azureEntraPasswordCredentialsEnabled,omitempty"`
7576
}
7677

7778
type FrontendSettingsCachingDTO struct {

pkg/api/frontendsettings.go

+1
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
276276
WorkloadIdentityEnabled: hs.Cfg.Azure.WorkloadIdentityEnabled,
277277
UserIdentityEnabled: hs.Cfg.Azure.UserIdentityEnabled,
278278
UserIdentityFallbackCredentialsEnabled: hs.Cfg.Azure.UserIdentityFallbackCredentialsEnabled,
279+
AzureEntraPasswordCredentialsEnabled: hs.Cfg.Azure.AzureEntraPasswordCredentialsEnabled,
279280
},
280281

281282
Caching: dtos.FrontendSettingsCachingDTO{

pkg/services/pluginsintegration/pluginconfig/request.go

+2
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ func (s *RequestConfigProvider) PluginRequestConfig(ctx context.Context, pluginI
142142
}
143143
}
144144
}
145+
146+
m[azsettings.AzureEntraPasswordCredentialsEnabled] = strconv.FormatBool(azureSettings.AzureEntraPasswordCredentialsEnabled)
145147
}
146148

147149
if s.cfg.UserFacingDefaultError != "" {

pkg/services/pluginsintegration/pluginconfig/request_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ func TestRequestConfigProvider_PluginRequestConfig_azure(t *testing.T) {
309309
},
310310
UserIdentityFallbackCredentialsEnabled: true,
311311
ForwardSettingsPlugins: []string{"grafana-azure-monitor-datasource", "prometheus", "grafana-azure-data-explorer-datasource", "mssql"},
312+
AzureEntraPasswordCredentialsEnabled: true,
312313
}
313314

314315
t.Run("uses the azure settings for an Azure plugin", func(t *testing.T) {
@@ -389,6 +390,7 @@ func TestRequestConfigProvider_PluginRequestConfig_azure(t *testing.T) {
389390
require.NotContains(t, m, "GFAZPL_USER_IDENTITY_CLIENT_ID")
390391
require.NotContains(t, m, "GFAZPL_USER_IDENTITY_CLIENT_SECRET")
391392
require.NotContains(t, m, "GFAZPL_USER_IDENTITY_ASSERTION")
393+
require.NotContains(t, m, "GFAZPL_AZURE_ENTRA_PASSWORD_CREDENTIALS_ENABLED")
392394
})
393395

394396
t.Run("uses the azure settings for a non-Azure user-specified plugin", func(t *testing.T) {
@@ -413,6 +415,7 @@ func TestRequestConfigProvider_PluginRequestConfig_azure(t *testing.T) {
413415
"GFAZPL_USER_IDENTITY_CLIENT_ID": "mock_user_identity_client_id",
414416
"GFAZPL_USER_IDENTITY_CLIENT_SECRET": "mock_user_identity_client_secret",
415417
"GFAZPL_USER_IDENTITY_ASSERTION": "username",
418+
"GFAZPL_AZURE_ENTRA_PASSWORD_CREDENTIALS_ENABLED": "true",
416419
})
417420
})
418421
}

pkg/setting/setting_azure.go

+2
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,7 @@ func (cfg *Cfg) readAzureSettings() {
8080

8181
azureSettings.ForwardSettingsPlugins = util.SplitString(azureSection.Key("forward_settings_to_plugins").String())
8282

83+
azureSettings.AzureEntraPasswordCredentialsEnabled = azureSection.Key("azure_entra_password_credentials_enabled").MustBool(false)
84+
8385
cfg.Azure = azureSettings
8486
}

pkg/tsdb/mssql/mssql.go

+11
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,17 @@ func getAzureCredentialDSNFragment(azureCredentials azcredentials.AzureCredentia
321321
c.ClientSecret,
322322
"ActiveDirectoryApplication",
323323
)
324+
case *azcredentials.AzureEntraPasswordCredentials:
325+
if cfg.Azure.AzureEntraPasswordCredentialsEnabled {
326+
connStr += fmt.Sprintf("user id=%s;password=%s;applicationclientid=%s;fedauth=%s;",
327+
c.UserId,
328+
c.Password,
329+
c.ClientId,
330+
"ActiveDirectoryPassword",
331+
)
332+
} else {
333+
return "", fmt.Errorf("azure entra password authentication is not enabled")
334+
}
324335
default:
325336
return "", fmt.Errorf("unsupported azure authentication type")
326337
}

public/app/plugins/datasource/mssql/azureauth/AzureAuth.testMocks.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const configWithManagedIdentityEnabled: Partial<GrafanaBootConfig> = {
99
workloadIdentityEnabled: false,
1010
userIdentityEnabled: false,
1111
userIdentityFallbackCredentialsEnabled: false,
12+
azureEntraPasswordCredentialsEnabled: false,
1213
},
1314
};
1415

@@ -19,6 +20,7 @@ export const configWithManagedIdentityDisabled: Partial<GrafanaBootConfig> = {
1920
userIdentityEnabled: false,
2021
cloud: 'AzureCloud',
2122
userIdentityFallbackCredentialsEnabled: false,
23+
azureEntraPasswordCredentialsEnabled: false,
2224
},
2325
};
2426

@@ -48,5 +50,5 @@ export const dataSourceSettingsWithClientSecretInSecureJSONData: Partial<
4850
DataSourceSettings<AzureAuthJSONDataType, AzureAuthSecureJSONDataType>
4951
> = {
5052
...basicJSONData,
51-
secureJsonData: { azureClientSecret: 'XXXX-super-secret-secret-XXXX' },
53+
secureJsonData: { azureClientSecret: 'XXXX-super-secret-secret-XXXX', password: undefined },
5254
};

public/app/plugins/datasource/mssql/azureauth/AzureAuthSettings.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { AzureCredentialsForm } from './AzureCredentialsForm';
1313
export const AzureAuthSettings = (props: HttpSettingsBaseProps) => {
1414
const { dataSourceConfig: dsSettings, onChange } = props;
1515
const managedIdentityEnabled = config.azure.managedIdentityEnabled;
16+
const azureEntraPasswordCredentialsEnabled = config.azure.azureEntraPasswordCredentialsEnabled;
1617

1718
const credentials = useMemo(() => getCredentials(dsSettings, config), [dsSettings]);
1819

@@ -30,6 +31,7 @@ export const AzureAuthSettings = (props: HttpSettingsBaseProps) => {
3031
return (
3132
<AzureCredentialsForm
3233
managedIdentityEnabled={managedIdentityEnabled}
34+
azureEntraPasswordCredentialsEnabled={azureEntraPasswordCredentialsEnabled}
3335
credentials={credentials}
3436
azureCloudOptions={KnownAzureClouds}
3537
onCredentialsChange={onCredentialsChange}

public/app/plugins/datasource/mssql/azureauth/AzureCredentials.ts

+2
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,7 @@ export function isCredentialsComplete(credentials: AzureCredentialsType): boolea
1515
return true;
1616
case AzureAuthType.CLIENT_SECRET:
1717
return !!(credentials.azureCloud && credentials.tenantId && credentials.clientId && credentials.clientSecret);
18+
case AzureAuthType.AD_PASSWORD:
19+
return !!(credentials.clientId && credentials.password && credentials.userId);
1820
}
1921
}

public/app/plugins/datasource/mssql/azureauth/AzureCredentialsConfig.ts

+37-4
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ export const getDefaultCredentials = (managedIdentityEnabled: boolean, cloud: st
1919
};
2020

2121
export const getSecret = (
22-
clientSecretStoredServerSide: boolean,
23-
clientSecret: string | symbol | undefined
22+
storedServerSide: boolean,
23+
secret: string | symbol | undefined
2424
): undefined | string | ConcealedSecretType => {
2525
const concealedSecret: ConcealedSecretType = Symbol('Concealed client secret');
26-
if (clientSecretStoredServerSide) {
26+
if (storedServerSide) {
2727
// The secret is concealed server side, so return the symbol
2828
return concealedSecret;
2929
} else {
30-
return typeof clientSecret === 'string' && clientSecret.length > 0 ? clientSecret : undefined;
30+
return typeof secret === 'string' && secret.length > 0 ? secret : undefined;
3131
}
3232
};
3333

@@ -41,6 +41,8 @@ export const getCredentials = (
4141
// Secure JSON data/fields
4242
const clientSecretStoredServerSide = dsSettings.secureJsonFields?.azureClientSecret;
4343
const clientSecret = dsSettings.secureJsonData?.azureClientSecret;
44+
const passwordStoredServerSide = dsSettings.secureJsonFields?.password;
45+
const password = dsSettings.secureJsonData?.password;
4446

4547
// BootConfig data
4648
const managedIdentityEnabled = !!bootConfig.azure?.managedIdentityEnabled;
@@ -74,6 +76,13 @@ export const getCredentials = (
7476
clientId: credentials.clientId,
7577
clientSecret: getSecret(clientSecretStoredServerSide, clientSecret),
7678
};
79+
case AzureAuthType.AD_PASSWORD:
80+
return {
81+
authType: AzureAuthType.AD_PASSWORD,
82+
userId: credentials.userId,
83+
clientId: credentials.clientId,
84+
password: getSecret(passwordStoredServerSide, password),
85+
};
7786
}
7887
};
7988

@@ -130,5 +139,29 @@ export const updateCredentials = (
130139
};
131140

132141
return dsSettings;
142+
143+
case AzureAuthType.AD_PASSWORD:
144+
return {
145+
...dsSettings,
146+
jsonData: {
147+
...dsSettings.jsonData,
148+
azureCredentials: {
149+
authType: AzureAuthType.AD_PASSWORD,
150+
userId: credentials.userId,
151+
clientId: credentials.clientId,
152+
},
153+
},
154+
secureJsonData: {
155+
...dsSettings.secureJsonData,
156+
password:
157+
typeof credentials.password === 'string' && credentials.password.length > 0
158+
? credentials.password
159+
: undefined,
160+
},
161+
secureJsonFields: {
162+
...dsSettings.secureJsonFields,
163+
password: typeof credentials.password === 'symbol',
164+
},
165+
};
133166
}
134167
};

0 commit comments

Comments
 (0)