diff --git a/ovh/data_me_identity_provider.go b/ovh/data_me_identity_provider.go new file mode 100644 index 000000000..776a5915e --- /dev/null +++ b/ovh/data_me_identity_provider.go @@ -0,0 +1,105 @@ +package ovh + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceMeIdentityProvider() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceMeIdentityProviderRead, + + Schema: map[string]*schema.Schema{ + "group_attribute_name": { + Type: schema.TypeString, + Computed: true, + }, + "requested_attributes": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "is_required": { + Type: schema.TypeBool, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "name_format": { + Type: schema.TypeString, + Computed: true, + }, + "values": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + }, + }, + }, + }, + "idp_signing_certificates": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "expiration": { + Type: schema.TypeString, + Computed: true, + }, + "subject": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "sso_service_url": { + Type: schema.TypeString, + Computed: true, + }, + "user_attribute_name": { + Type: schema.TypeString, + Computed: true, + }, + "disable_users": { + Type: schema.TypeBool, + Computed: true, + }, + "creation": { + Type: schema.TypeString, + Computed: true, + }, + "last_update": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceMeIdentityProviderRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + config := meta.(*Config) + + providerConfDetails := &MeIdentityProviderResponse{} + if err := config.OVHClient.GetWithContext(ctx, "/me/identity/provider", providerConfDetails); err != nil { + return diag.FromErr(err) + } + + d.SetId("ovh_sso") + d.Set("group_attribute_name", providerConfDetails.GroupAttributeName) + d.Set("disable_users", providerConfDetails.DisableUsers) + d.Set("requested_attributes", requestedAttributesToMapList(providerConfDetails.Extensions.RequestedAttributes)) + d.Set("idp_signing_certificates", idpSigningCertificatesToMapList(providerConfDetails.IdpSigningCertificates)) + d.Set("sso_service_url", providerConfDetails.SsoServiceUrl) + d.Set("user_attribute_name", providerConfDetails.UserAttributeName) + d.Set("creation", providerConfDetails.Creation) + d.Set("last_update", providerConfDetails.LastUpdate) + + return nil +} diff --git a/ovh/data_me_identity_provider_test.go b/ovh/data_me_identity_provider_test.go new file mode 100644 index 000000000..7319d3ef0 --- /dev/null +++ b/ovh/data_me_identity_provider_test.go @@ -0,0 +1,125 @@ +package ovh + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccMeIdentityProviderDataSource_basic(t *testing.T) { + groupAttributeName := "http://schemas.xmlsoap.org/claims/Group" + disableUsers := "false" + reqAttributeRequired := "false" + reqAttributeName := "identity" + reqAttributeNameFormat := "urn:oasis:names:tc:SAML:2.0:attrname-format:basic" + reqAttributeValue := "foobar" + + preSetup := fmt.Sprintf( + testAccMeIdentityProviderDataSourceConfig_preSetup, + groupAttributeName, + samlIDPMetadata, + disableUsers, + reqAttributeRequired, + reqAttributeName, + reqAttributeNameFormat, + reqAttributeValue, + ) + config := fmt.Sprintf( + testAccMeIdentityProviderDataSourceConfig_keys, + groupAttributeName, + samlIDPMetadata, + disableUsers, + reqAttributeRequired, + reqAttributeName, + reqAttributeNameFormat, + reqAttributeValue, + ) + + userAttributeName := "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" + ssoServiceUrl := "https://ovhcloud.com/" + certificateExpiration := "2033-11-06T10:06:24Z" + certificateSubject := "CN=ovhcloud.com,O=OVHcloud,L=RBX,ST=Some-State,C=FR" + + requestedAttributes := map[string]string{ + "is_required": reqAttributeRequired, + "name": reqAttributeName, + "name_format": reqAttributeNameFormat, + "values": reqAttributeValue, + } + + checks := checkIdentityProviderResourceAttr("ovh_me_identity_provider.sso", groupAttributeName, disableUsers, samlIDPMetadata, requestedAttributes) + dataSourceChecks := checkIdentityProviderDataSourceAttr("data.ovh_me_identity_provider.sso", groupAttributeName, userAttributeName, ssoServiceUrl, disableUsers, certificateExpiration, certificateSubject, requestedAttributes) + dataSourceChecks = append(dataSourceChecks, resource.TestCheckOutput("keys_present", "true")) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheckCredentials(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: preSetup, + Check: resource.ComposeTestCheckFunc(checks...), + }, { + Config: config, + Check: resource.ComposeTestCheckFunc(dataSourceChecks...), + }, + }, + }) +} + +func checkIdentityProviderDataSourceAttr(name, group_attribute, user_attribute, sso_service_url, disable_users, certificateExpiration, certificateSubject string, requestedAttributes map[string]string) []resource.TestCheckFunc { + checks := []resource.TestCheckFunc{} + checks = append(checks, resource.TestCheckResourceAttr(name, "group_attribute_name", group_attribute)) + checks = append(checks, resource.TestCheckResourceAttr(name, "user_attribute_name", user_attribute)) + checks = append(checks, resource.TestCheckResourceAttr(name, "sso_service_url", sso_service_url)) + checks = append(checks, resource.TestCheckResourceAttr(name, "disable_users", disable_users)) + checks = append(checks, resource.TestCheckResourceAttr(name, "idp_signing_certificates.0.expiration", certificateExpiration)) + checks = append(checks, resource.TestCheckResourceAttr(name, "idp_signing_certificates.0.subject", certificateSubject)) + if requestedAttributes != nil { + checks = append(checks, resource.TestCheckResourceAttr(name, "requested_attributes.0.is_required", requestedAttributes["is_required"])) + checks = append(checks, resource.TestCheckResourceAttr(name, "requested_attributes.0.name", requestedAttributes["name"])) + checks = append(checks, resource.TestCheckResourceAttr(name, "requested_attributes.0.name_format", requestedAttributes["name_format"])) + checks = append(checks, resource.TestCheckResourceAttr(name, "requested_attributes.0.values.0", requestedAttributes["values"])) + } + return checks +} + +const testAccMeIdentityProviderDataSourceConfig_preSetup = ` +resource "ovh_me_identity_provider" "sso" { + group_attribute_name = "%s" + metadata = < + + + + + + MIIFlTCCA32gAwIBAgIUP8WQwHQwrvTa00RU9JROZAJj9ccwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCRlIxEzARBgNVBAgMClNvbWUtU3RhdGUxDDAKBgNVBAcMA1JCWDERMA8GA1UECgwIT1ZIY2xvdWQxFTATBgNVBAMMDG92aGNsb3VkLmNvbTAeFw0yMzExMDkxMDA2MjRaFw0zMzExMDYxMDA2MjRaMFoxCzAJBgNVBAYTAkZSMRMwEQYDVQQIDApTb21lLVN0YXRlMQwwCgYDVQQHDANSQlgxETAPBgNVBAoMCE9WSGNsb3VkMRUwEwYDVQQDDAxvdmhjbG91ZC5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC4V3HulFBksxpgkgR6KgDaSSIKkKRgyDGCF06oQN/WPxGDSHTQQHTMN7jnsbr2uJieNKh+iasGvE9JFmd6nutloL1UHoO/ecrE8P2PYgpgezl7WfyoscBDZAjWM8E9FdENnonhvlga2DgV2DGIB4+D7aN6TIPfWukOB2MjfQloA9Iw71+peO9R55S7x7zixgpLO9NovbmaAyClbz06Tsm/7ezM+Vte7BfFqGUnwuNzqgOYfQm88EqXTpCT3QfR8i2IydGgAFLMFs9YvMnCaNLw9PCN7U6VPkY6M6cFQhO/moRb3H/euJnLNRMsXp99K8ruUnQ6902NXpOOnQu5Ewzfahmx0WWvlpFGdJK34oXjaWeTodGuvHtDxCY4tiHr8jCHf9h4cmC20xAyd/V7XBtu1Pc5UAg4I0w5ehWvHtVdxCsuPEh7c4qtuLyN9Qh15r+eRbiqnWTH/xJTwfo6q6iafXXcFOlTn7WoWmmeq0R8whg6XjcxMIzBXjtynTDbQa4LVq3T8iJiGfuDgwv5OwDPRN1CsawxefETsCUQ+jf/Iw/4nZpD/YqCI5xvYtDgPSt3v2TsoOnwOSjOqKmEOoHxGTN3mhbcD+I1QKJW79zqu6GVXVwMkgWdP4pkIWGccB0FqhIVzY19xQ40DbfnCkMTv2XN4t53c/q7CYhtvyN3XwIDAQABo1MwUTAdBgNVHQ4EFgQUC8yuX4Ub/Od5jSaz7NdwHUSlq5wwHwYDVR0jBBgwFoAUC8yuX4Ub/Od5jSaz7NdwHUSlq5wwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAR4MzroH8kEwqcZeB94hetY/NQGZI+kZ26iKnvLaZa8r56UeiIrEGdEeys5JdQh/XJDWsEU6piJ0dwrIkkpgZELmUmToylcxndzjjcHbiKLlqkL+kBu9QeO/r6JTHaNyWs0An2VvCUfo+Frt8hvrJCINlCDylOaWIxHH3P0TG7ThFGWSy8nW+VMMXDS8vQIGRM66HqgYlu6HBryecf0SsCkVYbUb1zYJ+lEhYK0pj4RORainJX+PU+mIMUwQtfBByuI7RP0a2Vny0gffrtPuNfhRJb8Pwt2UYw2niWUDOfXuk9RYgqX/1wLVqk72KJJlD3c7+abZ6BcNEJax5e/icilUrxcs4MymDPjk63kQURRVzcC4hCXYqJVQmRfVT4fdLLKPmeg3ysl+U4eJZ8odmaqoVGqZryncdAC+nT5lnLRm6m2lv3v+YhConctLxzCwV/xA8jU2w9VVRw2gkY8bdkvOb7c2OpXU6J3TYtaltG7foQiuXbRd37GWzzzEspxiAI9y8uIEJTsASaufsEdpR+a1sPy3rYJom/Li3dH9p9Ch+tp51pMYhSRGEiNu9g5918zMbrKvwkl6h/PQlTOlb65qUUoNKC5Baxhz3VkGxSKMUwS4Lj/WHvCGU5OteGFHglDgDm125FDakOYU1dnMm/P55yNhnSUH2sXngybxnw/w= + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + +` + +func init() { + resource.AddTestSweepers("ovh_me_identity_provider", &resource.Sweeper{ + Name: "ovh_me_identity_provider", + F: testSweepMeIdentityProvider, + }) +} + +func testSweepMeIdentityProvider(region string) error { + client, err := sharedClientForRegion(region) + if err != nil { + return fmt.Errorf("error getting client: %s", err) + } + + err = resource.Retry(5*time.Minute, func() *resource.RetryError { + log.Printf("[INFO] Deleting identity provider") + if err := client.Delete("/me/identity/provider", nil); err != nil { + return resource.RetryableError(err) + } + + // Successful delete + return nil + }) + + return err +} + +func TestAccMeIdentityProvider_basic(t *testing.T) { + groupeAttribute := acctest.RandomWithPrefix(test_prefix) + disableUsers := "false" + config := fmt.Sprintf(testAccMeIdentityProviderConfig_basic, groupeAttribute, disableUsers, samlIDPMetadata) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheckCredentials(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + checkIdentityProviderResourceAttr("ovh_me_identity_provider.my_provider", groupeAttribute, disableUsers, samlIDPMetadata, nil)..., + ), + }, + }, + }) +} + +func TestAccMeIdentityProvider_requestedAttributes(t *testing.T) { + groupeAttribute := acctest.RandomWithPrefix(test_prefix) + disableUsers := "false" + requestedAttribute := map[string]string{ + "is_required": "false", + "name": "test1", + "name_format": "test2", + "values": "test3", + } + config := fmt.Sprintf(testAccMeIdentityProviderConfig_requestedAttribute, groupeAttribute, disableUsers, samlIDPMetadata, requestedAttribute["is_required"], requestedAttribute["name"], requestedAttribute["name_format"], requestedAttribute["values"]) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheckCredentials(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + checkIdentityProviderResourceAttr("ovh_me_identity_provider.my_provider", groupeAttribute, disableUsers, samlIDPMetadata, requestedAttribute)..., + ), + }, + }, + }) +} + +func checkIdentityProviderResourceAttr(name, group_attribute, disable_users, metadata string, requestedAttributes map[string]string) []resource.TestCheckFunc { + checks := []resource.TestCheckFunc{} + checks = append(checks, resource.TestCheckResourceAttr(name, "group_attribute_name", group_attribute)) + checks = append(checks, resource.TestCheckResourceAttr(name, "disable_users", disable_users)) + checks = append(checks, resource.TestCheckResourceAttr(name, "metadata", metadata+"\n")) + if requestedAttributes != nil { + checks = append(checks, resource.TestCheckResourceAttr(name, "requested_attributes.0.is_required", requestedAttributes["is_required"])) + checks = append(checks, resource.TestCheckResourceAttr(name, "requested_attributes.0.name", requestedAttributes["name"])) + checks = append(checks, resource.TestCheckResourceAttr(name, "requested_attributes.0.name_format", requestedAttributes["name_format"])) + checks = append(checks, resource.TestCheckResourceAttr(name, "requested_attributes.0.values.0", requestedAttributes["values"])) + } + return checks +} + +const testAccMeIdentityProviderConfig_basic = ` +resource "ovh_me_identity_provider" "my_provider" { + group_attribute_name = "%s" + disable_users = %s + metadata = < + + + + + + MIIFlTCCA32gAwIBAgIUP8WQwHQwrvTa00RU9JROZAJj9ccwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCRlIxEzARBgNVBAgMClNvbWUtU3RhdGUxDDAKBgNVBAcMA1JCWDERMA8GA1UECgwIT1ZIY2xvdWQxFTATBgNVBAMMDG92aGNsb3VkLmNvbTAeFw0yMzExMDkxMDA2[...]xA8jU2w9VVRw2gkY8bdkvOb7c2OpXU6J3TYtaltG7foQiuXbRd37GWzzzEspxiAI9y8uIEJTsASaufsEdpR+a1sPy3rYJom/Li3dH9p9Ch+tp51pMYhSRGEiNu9g5918zMbrKvwkl6h/PQlTOlb65qUUoNKC5Baxhz3VkGxSKMUwS4Lj/WHvCGU5OteGFHglDgDm125FDakOYU1dnMm/P55yNhnSUH2sXngybxnw/w= + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:persistent + + + + +EOT + + # Local users will still be able to login if set to false. + # Owner of the OVHcloud account can always login using the nichandle ID and the nichandle password, regardless of this value. + disable_users = false + + # The assertion must contain the attribute "https://example.org/attributes/role" + # with the allowed values being "user" or "administrator" + requested_attributes { + is_required = true + name = "https://example.org/attributes/role" + name_format = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" + values = ["user", administrator] + } + # If the attribute "https://example.org/attributes/group" is available, + # we want the IdP to provide it + requested_attributes { + is_required = false + name = "https://example.org/attributes/group" + name_format = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" + values = [] + } +} +``` + +## Argument Reference + +* `group_attribute_name` - The name of the attribute containing the information of which group the connecting users belong to. +* `metadata` - The SAML xml metadata of the Identity Provider to federate to. +* `disable_users` - Whether local users should still be usable as a login method or not (optional, defaults to true). Owner of the OVHcloud account can always login using the nichandle ID and the nichandle password, regardless of this value. +* `requested_attributes` A SAML 2.0 requested attribute as defined in [SAML-ReqAttrExt-v1.0](http://docs.oasis-open.org/security/saml-protoc-req-attr-req/v1.0/cs01/saml-protoc-req-attr-req-v1.0-cs01.pdf). A RequestedAttribute object will indicate that the Identity Provider should add the described attribute to the SAML assertions that will be given to the Service Provider (OVHcloud). + * `is_required` Expresses that this Attribute is mandatory. If the requested attribute is not present in the assertion, the user won't be allowed to log in. + * `name` Name of the SAML Attribute that is requested. + * `name_format` NameFormat of the SAML RequestedAttribute. + * `values` List of AttributeValues allowed for this RequestedAttribute. + +## Attributes Reference + +* `creation` - Creation date of the SAML Federation. +* `last_update` - Date of the last update of the SAML Federation.