Skip to content

Commit 631b1bd

Browse files
authored
SSO: add saml support for the sso settings resource (#1474)
* fix new settings from ReadSSOSettings function * add saml support for sso settings resource * add conflictsWith param for settings
1 parent d6d9a19 commit 631b1bd

File tree

3 files changed

+339
-33
lines changed

3 files changed

+339
-33
lines changed

docs/resources/sso_settings.md

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
page_title: "grafana_sso_settings Resource - terraform-provider-grafana"
44
subcategory: "Grafana OSS"
55
description: |-
6-
Manages Grafana SSO Settings for OAuth2. SAML support will be added soon.
6+
Manages Grafana SSO Settings for OAuth2 and SAML.
77
Official documentation https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/HTTP API https://grafana.com/docs/grafana/latest/developers/http_api/sso-settings/
88
---
99

1010
# grafana_sso_settings (Resource)
1111

12-
Manages Grafana SSO Settings for OAuth2. SAML support will be added soon.
12+
Manages Grafana SSO Settings for OAuth2 and SAML.
1313

1414
* [Official documentation](https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/)
1515
* [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/sso-settings/)
@@ -33,8 +33,12 @@ resource "grafana_sso_settings" "github_sso_settings" {
3333

3434
### Required
3535

36-
- `oauth2_settings` (Block Set, Min: 1, Max: 1) The SSO settings set. (see [below for nested schema](#nestedblock--oauth2_settings))
37-
- `provider_name` (String) The name of the SSO provider. Supported values: github, gitlab, google, azuread, okta, generic_oauth.
36+
- `provider_name` (String) The name of the SSO provider. Supported values: github, gitlab, google, azuread, okta, generic_oauth, saml.
37+
38+
### Optional
39+
40+
- `oauth2_settings` (Block Set, Max: 1) The OAuth2 settings set. Required for github, gitlab, google, azuread, okta, generic_oauth providers. (see [below for nested schema](#nestedblock--oauth2_settings))
41+
- `saml_settings` (Block Set, Max: 1) The SAML settings set. Required for the saml provider. (see [below for nested schema](#nestedblock--saml_settings))
3842

3943
### Read-Only
4044

@@ -87,6 +91,43 @@ Optional:
8791
- `use_pkce` (Boolean) If enabled, Grafana will use Proof Key for Code Exchange (PKCE) with the OAuth2 Authorization Code Grant.
8892
- `use_refresh_token` (Boolean) If enabled, Grafana will fetch a new access token using the refresh token provided by the OAuth2 provider.
8993

94+
95+
<a id="nestedblock--saml_settings"></a>
96+
### Nested Schema for `saml_settings`
97+
98+
Optional:
99+
100+
- `allow_idp_initiated` (Boolean) Whether SAML IdP-initiated login is allowed.
101+
- `allow_sign_up` (Boolean) Whether to allow new Grafana user creation through SAML login. If set to false, then only existing Grafana users can log in with SAML.
102+
- `allowed_organizations` (String) List of comma- or space-separated organizations. User should be a member of at least one organization to log in.
103+
- `assertion_attribute_email` (String) Friendly name or name of the attribute within the SAML assertion to use as the user email.
104+
- `assertion_attribute_groups` (String) Friendly name or name of the attribute within the SAML assertion to use as the user groups.
105+
- `assertion_attribute_login` (String) Friendly name or name of the attribute within the SAML assertion to use as the user login handle.
106+
- `assertion_attribute_name` (String) Friendly name or name of the attribute within the SAML assertion to use as the user name. Alternatively, this can be a template with variables that match the names of attributes within the SAML assertion.
107+
- `assertion_attribute_org` (String) Friendly name or name of the attribute within the SAML assertion to use as the user organization.
108+
- `assertion_attribute_role` (String) Friendly name or name of the attribute within the SAML assertion to use as the user roles.
109+
- `auto_login` (Boolean) Whether SAML auto login is enabled.
110+
- `certificate` (String, Sensitive) Base64-encoded string for the SP X.509 certificate.
111+
- `certificate_path` (String) Path for the SP X.509 certificate.
112+
- `enabled` (Boolean) Define whether this configuration is enabled for SAML. Defaults to `true`.
113+
- `idp_metadata` (String) Base64-encoded string for the IdP SAML metadata XML.
114+
- `idp_metadata_path` (String) Path for the IdP SAML metadata XML.
115+
- `idp_metadata_url` (String) URL for the IdP SAML metadata XML.
116+
- `max_issue_delay` (String) Duration, since the IdP issued a response and the SP is allowed to process it. For example: 90s, 1h.
117+
- `metadata_valid_duration` (String) Duration, for how long the SP metadata is valid. For example: 48h, 5d.
118+
- `name` (String) Name used to refer to the SAML authentication.
119+
- `name_id_format` (String) The Name ID Format to request within the SAML assertion. Defaults to urn:oasis:names:tc:SAML:2.0:nameid-format:transient
120+
- `org_mapping` (String) List of comma- or space-separated Organization:OrgId:Role mappings. Organization can be * meaning “All users”. Role is optional and can have the following values: Viewer, Editor or Admin.
121+
- `private_key` (String, Sensitive) Base64-encoded string for the SP private key.
122+
- `private_key_path` (String) Path for the SP private key.
123+
- `relay_state` (String) Relay state for IdP-initiated login. Should match relay state configured in IdP.
124+
- `role_values_admin` (String) List of comma- or space-separated roles which will be mapped into the Admin role.
125+
- `role_values_editor` (String) List of comma- or space-separated roles which will be mapped into the Editor role.
126+
- `role_values_grafana_admin` (String) List of comma- or space-separated roles which will be mapped into the Grafana Admin (Super Admin) role.
127+
- `role_values_none` (String) List of comma- or space-separated roles which will be mapped into the None role.
128+
- `signature_algorithm` (String) Signature algorithm used for signing requests to the IdP. Supported values are rsa-sha1, rsa-sha256, rsa-sha512.
129+
- `single_logout` (Boolean) Whether SAML Single Logout is enabled.
130+
90131
## Import
91132

92133
Import is supported using the following syntax:

internal/resources/grafana/resource_sso_settings.go

Lines changed: 193 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@ import (
1717
const (
1818
providerKey = "provider_name"
1919
oauth2SettingsKey = "oauth2_settings"
20+
samlSettingsKey = "saml_settings"
2021
customFieldsKey = "custom"
2122
)
2223

2324
func resourceSSOSettings() *common.Resource {
2425
schema := &schema.Resource{
2526

2627
Description: `
27-
Manages Grafana SSO Settings for OAuth2. SAML support will be added soon.
28+
Manages Grafana SSO Settings for OAuth2 and SAML.
2829
2930
* [Official documentation](https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/)
3031
* [HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/sso-settings/)
@@ -42,16 +43,26 @@ Manages Grafana SSO Settings for OAuth2. SAML support will be added soon.
4243
providerKey: {
4344
Type: schema.TypeString,
4445
Required: true,
45-
Description: "The name of the SSO provider. Supported values: github, gitlab, google, azuread, okta, generic_oauth.",
46-
ValidateFunc: validation.StringInSlice([]string{"github", "gitlab", "google", "azuread", "okta", "generic_oauth"}, false),
46+
Description: "The name of the SSO provider. Supported values: github, gitlab, google, azuread, okta, generic_oauth, saml.",
47+
ValidateFunc: validation.StringInSlice([]string{"github", "gitlab", "google", "azuread", "okta", "generic_oauth", "saml"}, false),
4748
},
4849
oauth2SettingsKey: {
49-
Type: schema.TypeSet,
50-
Required: true,
51-
MaxItems: 1,
52-
MinItems: 1,
53-
Description: "The SSO settings set.",
54-
Elem: oauth2SettingsSchema,
50+
Type: schema.TypeSet,
51+
Optional: true,
52+
MaxItems: 1,
53+
MinItems: 0,
54+
Description: "The OAuth2 settings set. Required for github, gitlab, google, azuread, okta, generic_oauth providers.",
55+
Elem: oauth2SettingsSchema,
56+
ConflictsWith: []string{samlSettingsKey},
57+
},
58+
samlSettingsKey: {
59+
Type: schema.TypeSet,
60+
Optional: true,
61+
MaxItems: 1,
62+
MinItems: 0,
63+
Description: "The SAML settings set. Required for the saml provider.",
64+
Elem: samlSettingsSchema,
65+
ConflictsWith: []string{oauth2SettingsKey},
5566
},
5667
},
5768
}
@@ -263,6 +274,164 @@ var oauth2SettingsSchema = &schema.Resource{
263274
},
264275
}
265276

277+
var samlSettingsSchema = &schema.Resource{
278+
Schema: map[string]*schema.Schema{
279+
"enabled": {
280+
Type: schema.TypeBool,
281+
Optional: true,
282+
Default: true,
283+
Description: "Define whether this configuration is enabled for SAML.",
284+
},
285+
"name": {
286+
Type: schema.TypeString,
287+
Optional: true,
288+
Description: "Name used to refer to the SAML authentication.",
289+
},
290+
"single_logout": {
291+
Type: schema.TypeBool,
292+
Optional: true,
293+
Description: "Whether SAML Single Logout is enabled.",
294+
},
295+
"allow_sign_up": {
296+
Type: schema.TypeBool,
297+
Optional: true,
298+
Description: "Whether to allow new Grafana user creation through SAML login. If set to false, then only existing Grafana users can log in with SAML.",
299+
},
300+
"auto_login": {
301+
Type: schema.TypeBool,
302+
Optional: true,
303+
Description: "Whether SAML auto login is enabled.",
304+
},
305+
"allow_idp_initiated": {
306+
Type: schema.TypeBool,
307+
Optional: true,
308+
Description: "Whether SAML IdP-initiated login is allowed.",
309+
},
310+
"certificate": {
311+
Type: schema.TypeString,
312+
Optional: true,
313+
Sensitive: true,
314+
Description: "Base64-encoded string for the SP X.509 certificate.",
315+
},
316+
"certificate_path": {
317+
Type: schema.TypeString,
318+
Optional: true,
319+
Description: "Path for the SP X.509 certificate.",
320+
},
321+
"private_key": {
322+
Type: schema.TypeString,
323+
Optional: true,
324+
Sensitive: true,
325+
Description: "Base64-encoded string for the SP private key.",
326+
},
327+
"private_key_path": {
328+
Type: schema.TypeString,
329+
Optional: true,
330+
Description: "Path for the SP private key.",
331+
},
332+
"signature_algorithm": {
333+
Type: schema.TypeString,
334+
Optional: true,
335+
Description: "Signature algorithm used for signing requests to the IdP. Supported values are rsa-sha1, rsa-sha256, rsa-sha512.",
336+
},
337+
"idp_metadata": {
338+
Type: schema.TypeString,
339+
Optional: true,
340+
Description: "Base64-encoded string for the IdP SAML metadata XML.",
341+
},
342+
"idp_metadata_path": {
343+
Type: schema.TypeString,
344+
Optional: true,
345+
Description: "Path for the IdP SAML metadata XML.",
346+
},
347+
"idp_metadata_url": {
348+
Type: schema.TypeString,
349+
Optional: true,
350+
Description: "URL for the IdP SAML metadata XML.",
351+
},
352+
"max_issue_delay": {
353+
Type: schema.TypeString,
354+
Optional: true,
355+
Description: "Duration, since the IdP issued a response and the SP is allowed to process it. For example: 90s, 1h.",
356+
},
357+
"metadata_valid_duration": {
358+
Type: schema.TypeString,
359+
Optional: true,
360+
Description: "Duration, for how long the SP metadata is valid. For example: 48h, 5d.",
361+
},
362+
"relay_state": {
363+
Type: schema.TypeString,
364+
Optional: true,
365+
Description: "Relay state for IdP-initiated login. Should match relay state configured in IdP.",
366+
},
367+
"assertion_attribute_name": {
368+
Type: schema.TypeString,
369+
Optional: true,
370+
Description: "Friendly name or name of the attribute within the SAML assertion to use as the user name. Alternatively, this can be a template with variables that match the names of attributes within the SAML assertion.",
371+
},
372+
"assertion_attribute_login": {
373+
Type: schema.TypeString,
374+
Optional: true,
375+
Description: "Friendly name or name of the attribute within the SAML assertion to use as the user login handle.",
376+
},
377+
"assertion_attribute_email": {
378+
Type: schema.TypeString,
379+
Optional: true,
380+
Description: "Friendly name or name of the attribute within the SAML assertion to use as the user email.",
381+
},
382+
"assertion_attribute_groups": {
383+
Type: schema.TypeString,
384+
Optional: true,
385+
Description: "Friendly name or name of the attribute within the SAML assertion to use as the user groups.",
386+
},
387+
"assertion_attribute_role": {
388+
Type: schema.TypeString,
389+
Optional: true,
390+
Description: "Friendly name or name of the attribute within the SAML assertion to use as the user roles.",
391+
},
392+
"assertion_attribute_org": {
393+
Type: schema.TypeString,
394+
Optional: true,
395+
Description: "Friendly name or name of the attribute within the SAML assertion to use as the user organization.",
396+
},
397+
"allowed_organizations": {
398+
Type: schema.TypeString,
399+
Optional: true,
400+
Description: "List of comma- or space-separated organizations. User should be a member of at least one organization to log in.",
401+
},
402+
"org_mapping": {
403+
Type: schema.TypeString,
404+
Optional: true,
405+
Description: "List of comma- or space-separated Organization:OrgId:Role mappings. Organization can be * meaning “All users”. Role is optional and can have the following values: Viewer, Editor or Admin.",
406+
},
407+
"role_values_none": {
408+
Type: schema.TypeString,
409+
Optional: true,
410+
Description: "List of comma- or space-separated roles which will be mapped into the None role.",
411+
},
412+
"role_values_editor": {
413+
Type: schema.TypeString,
414+
Optional: true,
415+
Description: "List of comma- or space-separated roles which will be mapped into the Editor role.",
416+
},
417+
"role_values_admin": {
418+
Type: schema.TypeString,
419+
Optional: true,
420+
Description: "List of comma- or space-separated roles which will be mapped into the Admin role.",
421+
},
422+
"role_values_grafana_admin": {
423+
Type: schema.TypeString,
424+
Optional: true,
425+
Description: "List of comma- or space-separated roles which will be mapped into the Grafana Admin (Super Admin) role.",
426+
},
427+
"name_id_format": {
428+
Type: schema.TypeString,
429+
Optional: true,
430+
Description: "The Name ID Format to request within the SAML assertion. Defaults to urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
431+
},
432+
},
433+
}
434+
266435
func ReadSSOSettings(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
267436
client, _ := OAPIGlobalClient(meta) // TODO: Check error. This resource works with a token. Is it org-scoped?
268437

@@ -404,10 +573,17 @@ func isOAuth2Provider(provider string) bool {
404573
return false
405574
}
406575

576+
func isSamlProvider(provider string) bool {
577+
return provider == "saml"
578+
}
579+
407580
func getSettingsKey(provider string) (string, error) {
408581
if isOAuth2Provider(provider) {
409582
return oauth2SettingsKey, nil
410583
}
584+
if isSamlProvider(provider) {
585+
return samlSettingsKey, nil
586+
}
411587

412588
return "", fmt.Errorf("no settings key found for provider %s", provider)
413589
}
@@ -416,6 +592,9 @@ func getSettingsSchema(provider string) (*schema.Resource, error) {
416592
if isOAuth2Provider(provider) {
417593
return oauth2SettingsSchema, nil
418594
}
595+
if isSamlProvider(provider) {
596+
return samlSettingsSchema, nil
597+
}
419598

420599
return nil, fmt.Errorf("no settings schema found for provider %s", provider)
421600
}
@@ -443,10 +622,12 @@ func getSettingsFromResourceData(d *schema.ResourceData, settingsKey string) (ma
443622

444623
// TODO investigate why we need this
445624
// sometimes the settings set contains some empty items that we want to ignore
446-
// we are only interested in the settings that have the client_id set because the client_id is a required field
625+
// we are only interested in the settings that have one of the following:
626+
// - the client_id set because the client_id is a required field for OAuth2 providers
627+
// - the private_key or private_key_path set because those are required fields for SAML
447628
for _, item := range settingsList {
448629
settings := item.(map[string]any)
449-
if settings["client_id"] != "" {
630+
if settings["client_id"] != "" || settings["private_key"] != "" || settings["private_key_path"] != "" {
450631
return settings, nil
451632
}
452633
}
@@ -560,7 +741,7 @@ func toSnake(s string) string {
560741
}
561742

562743
func isSecret(fieldName string) bool {
563-
secretFieldPatterns := []string{"secret"}
744+
secretFieldPatterns := []string{"secret", "certificate", "private"}
564745

565746
for _, v := range secretFieldPatterns {
566747
if strings.Contains(strings.ToLower(fieldName), strings.ToLower(v)) {

0 commit comments

Comments
 (0)