Skip to content

SAML: Add Azure AD Graph API integration #1669

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/resources/sso_settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ Optional:
- `auto_login` (Boolean) Whether SAML auto login is enabled.
- `certificate` (String, Sensitive) Base64-encoded string for the SP X.509 certificate.
- `certificate_path` (String) Path for the SP X.509 certificate.
- `client_id` (String) The client Id of your OAuth2 app.
- `client_secret` (String) The client secret of your OAuth2 app.
- `enabled` (Boolean) Define whether this configuration is enabled for SAML. Defaults to `true`.
- `force_use_graph_api` (Boolean) If enabled, Grafana will fetch groups from Microsoft Graph API instead of using the groups claim from the ID token.
- `idp_metadata` (String) Base64-encoded string for the IdP SAML metadata XML.
- `idp_metadata_path` (String) Path for the IdP SAML metadata XML.
- `idp_metadata_url` (String) URL for the IdP SAML metadata XML.
Expand All @@ -168,6 +171,7 @@ Optional:
- `signature_algorithm` (String) Signature algorithm used for signing requests to the IdP. Supported values are rsa-sha1, rsa-sha256, rsa-sha512.
- `single_logout` (Boolean) Whether SAML Single Logout is enabled.
- `skip_org_role_sync` (Boolean) Prevent synchronizing users’ organization roles from your IdP.
- `token_url` (String) The token endpoint of your OAuth2 provider. Required for Azure AD providers.

## Import

Expand Down
45 changes: 45 additions & 0 deletions internal/resources/grafana/resource_sso_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,27 @@ var samlSettingsSchema = &schema.Resource{
Optional: true,
Description: "Prevent synchronizing users’ organization roles from your IdP.",
},
"client_id": {
Type: schema.TypeString,
Optional: true,
Description: "The client Id of your OAuth2 app.",
},
"client_secret": {
Type: schema.TypeString,
Optional: true,
// Sensitive: true,
Description: "The client secret of your OAuth2 app.",
},
"token_url": {
Type: schema.TypeString,
Optional: true,
Description: "The token endpoint of your OAuth2 provider. Required for Azure AD providers.",
},
"force_use_graph_api": {
Type: schema.TypeBool,
Optional: true,
Description: "If enabled, Grafana will fetch groups from Microsoft Graph API instead of using the groups claim from the ID token.",
},
},
}

Expand Down Expand Up @@ -703,6 +724,8 @@ var validationsByProvider = map[string][]validateFunc{
ssoValidateOnlyOneOf("private_key", "private_key_path"),
ssoValidateOnlyOneOf("idp_metadata", "idp_metadata_path", "idp_metadata_url"),
ssoValidateURL("idp_metadata_url"),
ssoValidateInterdependencyXOR("client_id", "client_secret", "token_url"),
ssoValidateURL("token_url"),
},
}

Expand Down Expand Up @@ -870,3 +893,25 @@ func ssoValidateOnlyOneOf(keys ...string) validateFunc {
return nil
}
}

// XOR validation of variables
func ssoValidateInterdependencyXOR(keys ...string) validateFunc {
return func(settingsMap map[string]any, provider string) error {
configuredKeys := 0
nonConfiguredKeys := 0

for _, key := range keys {
if settingsMap[key].(string) != "" {
configuredKeys++
} else {
nonConfiguredKeys++
}
}

if configuredKeys != len(keys) && nonConfiguredKeys != len(keys) {
return fmt.Errorf("all varialbes in %v must be configured or empty for provider %s", keys, provider)
}

return nil
}
}
47 changes: 46 additions & 1 deletion internal/resources/grafana/resource_sso_settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,28 @@ func TestSSOSettings_basic_saml(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "saml_settings.0.allow_sign_up", "true"),
),
},
{
Config: testConfigForSAMLProviderWithAzureAD,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "provider_name", provider),
resource.TestCheckResourceAttr(resourceName, "saml_settings.#", "1"),
resource.TestCheckResourceAttr(resourceName, "saml_settings.0.certificate_path", "devenv/docker/blocks/auth/saml-enterprise/cert.crt"),
resource.TestCheckResourceAttr(resourceName, "saml_settings.0.private_key_path", "devenv/docker/blocks/auth/saml-enterprise/key.pem"),
resource.TestCheckResourceAttr(resourceName, "saml_settings.0.idp_metadata_url", "https://nexus.microsoftonline-p.com/federationmetadata/saml20/federationmetadata.xml"),
resource.TestCheckResourceAttr(resourceName, "saml_settings.0.signature_algorithm", "rsa-sha256"),
resource.TestCheckResourceAttr(resourceName, "saml_settings.0.metadata_valid_duration", "24h"),
resource.TestCheckResourceAttr(resourceName, "saml_settings.0.assertion_attribute_email", "email"),
resource.TestCheckResourceAttr(resourceName, "saml_settings.0.client_id", "client_id"),
resource.TestCheckResourceAttr(resourceName, "saml_settings.0.client_secret", "client_secret"),
resource.TestCheckResourceAttr(resourceName, "saml_settings.0.token_url", "https://myidp.com/oauth/token"),
resource.TestCheckResourceAttr(resourceName, "saml_settings.0.force_use_graph_api", "true"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"saml_settings.0.private_key_path", "saml_settings.0.certificate_path"},
ImportStateVerifyIgnore: []string{"saml_settings.0.private_key_path", "saml_settings.0.certificate_path", "saml_settings.0.client_secret", "saml_settings.0.token_url"},
},
},
})
Expand Down Expand Up @@ -381,6 +398,22 @@ const testConfigForSamlProviderUpdated = `resource "grafana_sso_settings" "saml_
}
}`

const testConfigForSAMLProviderWithAzureAD = `resource "grafana_sso_settings" "saml_sso_settings" {
provider_name = "saml"
saml_settings {
certificate_path = "devenv/docker/blocks/auth/saml-enterprise/cert.crt"
private_key_path = "devenv/docker/blocks/auth/saml-enterprise/key.pem"
idp_metadata_url = "https://nexus.microsoftonline-p.com/federationmetadata/saml20/federationmetadata.xml"
signature_algorithm = "rsa-sha256"
metadata_valid_duration = "24h"
assertion_attribute_email = "email"
client_id = "client_id"
client_secret = "client_secret"
token_url = "https://myidp.com/oauth/token"
force_use_graph_api = true
}
}`

const testConfigWithCustomFields = `resource "grafana_sso_settings" "sso_settings" {
provider_name = "github"
oauth2_settings {
Expand Down Expand Up @@ -550,5 +583,17 @@ var testConfigsWithValidationErrors = []string{
certificate = "this-is-a-valid-certificate"
private_key = "this-is-a-valid-private-key"
}
}`,
// missing value value for client_secret
`resource "grafana_sso_settings" "saml_sso_settings" {
provider_name = "saml"
saml_settings {
certificate = "this-is-a-valid-certificate"
private_key = "this-is-a-valid-private-key"
idp_metadata_path = "/path/to/metadata"
client_id = "client_id"
client_secret = ""
token_url = "https://myidp.com/oauth/token"
}
}`,
}
Loading