From c97e7f4fde1418b4e003c5bd90051045a2152648 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Wed, 12 Feb 2020 08:00:31 +1100 Subject: [PATCH 01/12] basic setup for access_identity_provider resource --- cloudflare/provider.go | 59 +++---- ...rce_cloudflare_access_identity_provider.go | 152 ++++++++++++++++++ 2 files changed, 182 insertions(+), 29 deletions(-) create mode 100644 cloudflare/resource_cloudflare_access_identity_provider.go diff --git a/cloudflare/provider.go b/cloudflare/provider.go index 1a9dcd3693..b6eea72d89 100644 --- a/cloudflare/provider.go +++ b/cloudflare/provider.go @@ -98,35 +98,36 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "cloudflare_access_application": resourceCloudflareAccessApplication(), - "cloudflare_access_policy": resourceCloudflareAccessPolicy(), - "cloudflare_access_group": resourceCloudflareAccessGroup(), - "cloudflare_access_rule": resourceCloudflareAccessRule(), - "cloudflare_access_service_token": resourceCloudflareAccessServiceToken(), - "cloudflare_account_member": resourceCloudflareAccountMember(), - "cloudflare_argo": resourceCloudflareArgo(), - "cloudflare_custom_pages": resourceCloudflareCustomPages(), - "cloudflare_custom_ssl": resourceCloudflareCustomSsl(), - "cloudflare_filter": resourceCloudflareFilter(), - "cloudflare_firewall_rule": resourceCloudflareFirewallRule(), - "cloudflare_load_balancer_monitor": resourceCloudflareLoadBalancerMonitor(), - "cloudflare_load_balancer_pool": resourceCloudflareLoadBalancerPool(), - "cloudflare_load_balancer": resourceCloudflareLoadBalancer(), - "cloudflare_logpush_job": resourceCloudflareLogpushJob(), - "cloudflare_origin_ca_certificate": resourceCloudflareOriginCACertificate(), - "cloudflare_page_rule": resourceCloudflarePageRule(), - "cloudflare_rate_limit": resourceCloudflareRateLimit(), - "cloudflare_record": resourceCloudflareRecord(), - "cloudflare_spectrum_application": resourceCloudflareSpectrumApplication(), - "cloudflare_waf_group": resourceCloudflareWAFGroup(), - "cloudflare_waf_package": resourceCloudflareWAFPackage(), - "cloudflare_waf_rule": resourceCloudflareWAFRule(), - "cloudflare_worker_route": resourceCloudflareWorkerRoute(), - "cloudflare_worker_script": resourceCloudflareWorkerScript(), - "cloudflare_workers_kv_namespace": resourceCloudflareWorkersKVNamespace(), - "cloudflare_zone_lockdown": resourceCloudflareZoneLockdown(), - "cloudflare_zone_settings_override": resourceCloudflareZoneSettingsOverride(), - "cloudflare_zone": resourceCloudflareZone(), + "cloudflare_access_application": resourceCloudflareAccessApplication(), + "cloudflare_access_policy": resourceCloudflareAccessPolicy(), + "cloudflare_access_group": resourceCloudflareAccessGroup(), + "cloudflare_access_rule": resourceCloudflareAccessRule(), + "cloudflare_access_service_token": resourceCloudflareAccessServiceToken(), + "cloudflare_access_identity_provider": resourceCloudflareAccessIdentityProvider(), + "cloudflare_account_member": resourceCloudflareAccountMember(), + "cloudflare_argo": resourceCloudflareArgo(), + "cloudflare_custom_pages": resourceCloudflareCustomPages(), + "cloudflare_custom_ssl": resourceCloudflareCustomSsl(), + "cloudflare_filter": resourceCloudflareFilter(), + "cloudflare_firewall_rule": resourceCloudflareFirewallRule(), + "cloudflare_load_balancer_monitor": resourceCloudflareLoadBalancerMonitor(), + "cloudflare_load_balancer_pool": resourceCloudflareLoadBalancerPool(), + "cloudflare_load_balancer": resourceCloudflareLoadBalancer(), + "cloudflare_logpush_job": resourceCloudflareLogpushJob(), + "cloudflare_origin_ca_certificate": resourceCloudflareOriginCACertificate(), + "cloudflare_page_rule": resourceCloudflarePageRule(), + "cloudflare_rate_limit": resourceCloudflareRateLimit(), + "cloudflare_record": resourceCloudflareRecord(), + "cloudflare_spectrum_application": resourceCloudflareSpectrumApplication(), + "cloudflare_waf_group": resourceCloudflareWAFGroup(), + "cloudflare_waf_package": resourceCloudflareWAFPackage(), + "cloudflare_waf_rule": resourceCloudflareWAFRule(), + "cloudflare_worker_route": resourceCloudflareWorkerRoute(), + "cloudflare_worker_script": resourceCloudflareWorkerScript(), + "cloudflare_workers_kv_namespace": resourceCloudflareWorkersKVNamespace(), + "cloudflare_zone_lockdown": resourceCloudflareZoneLockdown(), + "cloudflare_zone_settings_override": resourceCloudflareZoneSettingsOverride(), + "cloudflare_zone": resourceCloudflareZone(), }, } diff --git a/cloudflare/resource_cloudflare_access_identity_provider.go b/cloudflare/resource_cloudflare_access_identity_provider.go new file mode 100644 index 0000000000..4b7c47a338 --- /dev/null +++ b/cloudflare/resource_cloudflare_access_identity_provider.go @@ -0,0 +1,152 @@ +package cloudflare + +import ( + "fmt" + "log" + "strings" + + cloudflare "github.com/cloudflare/cloudflare-go" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceCloudflareAccessIdentityProvider() *schema.Resource { + return &schema.Resource{ + Create: resourceCloudflareAccessIdentityProviderCreate, + Read: resourceCloudflareAccessIdentityProviderRead, + Update: resourceCloudflareAccessIdentityProviderUpdate, + Delete: resourceCloudflareAccessIdentityProviderDelete, + Importer: &schema.ResourceImporter{ + State: resourceCloudflareAccessIdentityProviderImport, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"centrify", "citrix_adc_saml", "facebook", "google-apps", "oidc", "github", "google", "saml", "linkedin", "azureAD", "okta", "onetimepin", "onelogin", "authn", "yandex"}, false), + }, + "config": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceCloudflareAccessIdentityProviderRead(d *schema.ResourceData, meta interface{}) error { + // client := meta.(*cloudflare.API) + // zoneID := d.Get("zone_id").(string) + // appID := d.Get("application_id").(string) + + // accessPolicy, err := client.AccessPolicy(zoneID, appID, d.Id()) + // if err != nil { + // if strings.Contains(err.Error(), "HTTP status 404") { + // log.Printf("[INFO] Access Policy %s no longer exists", d.Id()) + // d.SetId("") + // return nil + // } + // return fmt.Errorf("Error finding Access Policy %q: %s", d.Id(), err) + // } + + // d.Set("name", accessPolicy.Name) + // d.Set("decision", accessPolicy.Decision) + // d.Set("precedence", accessPolicy.Precedence) + // d.Set("require", accessPolicy.Require) + // d.Set("exclude", accessPolicy.Exclude) + // d.Set("include", accessPolicy.Include) + + return nil +} + +func resourceCloudflareAccessIdentityProviderCreate(d *schema.ResourceData, meta interface{}) error { + // client := meta.(*cloudflare.API) + // appID := d.Get("application_id").(string) + // zoneID := d.Get("zone_id").(string) + // newAccessPolicy := cloudflare.AccessPolicy{ + // Name: d.Get("name").(string), + // Precedence: d.Get("precedence").(int), + // Decision: d.Get("decision").(string), + // } + + // newAccessPolicy = appendConditionalAccessPolicyFields(newAccessPolicy, d) + + // log.Printf("[DEBUG] Creating Cloudflare Access Policy from struct: %+v", newAccessPolicy) + + // accessPolicy, err := client.CreateAccessPolicy(zoneID, appID, newAccessPolicy) + // if err != nil { + // return fmt.Errorf("error creating Access Policy for ID %q: %s", accessPolicy.ID, err) + // } + + // d.SetId(accessPolicy.ID) + + return nil +} + +func resourceCloudflareAccessIdentityProviderUpdate(d *schema.ResourceData, meta interface{}) error { + // client := meta.(*cloudflare.API) + // zoneID := d.Get("zone_id").(string) + // appID := d.Get("application_id").(string) + // updatedAccessPolicy := cloudflare.AccessPolicy{ + // Name: d.Get("name").(string), + // Precedence: d.Get("precedence").(int), + // Decision: d.Get("decision").(string), + // ID: d.Id(), + // } + + // updatedAccessPolicy = appendConditionalAccessPolicyFields(updatedAccessPolicy, d) + + // log.Printf("[DEBUG] Updating Cloudflare Access Policy from struct: %+v", updatedAccessPolicy) + + // accessPolicy, err := client.UpdateAccessPolicy(zoneID, appID, updatedAccessPolicy) + // if err != nil { + // return fmt.Errorf("error updating Access Policy for ID %q: %s", d.Id(), err) + // } + + // if accessPolicy.ID == "" { + // return fmt.Errorf("failed to find Access Policy ID in update response; resource was empty") + // } + + return resourceCloudflareAccessIdentityProviderRead(d, meta) +} + +func resourceCloudflareAccessIdentityProviderDelete(d *schema.ResourceData, meta interface{}) error { + // client := meta.(*cloudflare.API) + // zoneID := d.Get("zone_id").(string) + // appID := d.Get("application_id").(string) + + // log.Printf("[DEBUG] Deleting Cloudflare Access Policy using ID: %s", d.Id()) + + // err := client.DeleteAccessPolicy(zoneID, appID, d.Id()) + // if err != nil { + // return fmt.Errorf("error deleting Access Policy for ID %q: %s", d.Id(), err) + // } + + // resourceCloudflareAccessIdentityProviderRead(d, meta) + + return nil +} + +func resourceCloudflareAccessIdentityProviderImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + // attributes := strings.SplitN(d.Id(), "/", 3) + + // if len(attributes) != 3 { + // return nil, fmt.Errorf("invalid id (\"%s\") specified, should be in format \"zoneID/accessApplicationID/accessPolicyID\"", d.Id()) + // } + + // zoneID, accessAppID, accessPolicyID := attributes[0], attributes[1], attributes[2] + + // log.Printf("[DEBUG] Importing Cloudflare Access Policy: zoneID %q, appID %q, accessPolicyID %q", zoneID, accessAppID, accessPolicyID) + + // d.Set("zone_id", zoneID) + // d.Set("application_id", accessAppID) + // d.SetId(accessPolicyID) + + // resourceCloudflareAccessIdentityProviderRead(d, meta) + + return []*schema.ResourceData{d}, nil +} From d79b5aedda93c94ebd4b4dde64990c656eae4815 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Wed, 12 Feb 2020 08:05:42 +1100 Subject: [PATCH 02/12] add config options to schema --- ...rce_cloudflare_access_identity_provider.go | 83 ++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/cloudflare/resource_cloudflare_access_identity_provider.go b/cloudflare/resource_cloudflare_access_identity_provider.go index 4b7c47a338..78c9a7b0c7 100644 --- a/cloudflare/resource_cloudflare_access_identity_provider.go +++ b/cloudflare/resource_cloudflare_access_identity_provider.go @@ -31,8 +31,87 @@ func resourceCloudflareAccessIdentityProvider() *schema.Resource { ValidateFunc: validation.StringInSlice([]string{"centrify", "citrix_adc_saml", "facebook", "google-apps", "oidc", "github", "google", "saml", "linkedin", "azureAD", "okta", "onetimepin", "onelogin", "authn", "yandex"}, false), }, "config": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "apps_domain": { + Type: schema.TypeString, + Optional: true, + }, + "attributes": { + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "auth_url": { + Type: schema.TypeString, + Optional: true, + }, + "centrify_account": { + Type: schema.TypeString, + Optional: true, + }, + "centrify_app_id": { + Type: schema.TypeString, + Optional: true, + }, + "certs_url": { + Type: schema.TypeString, + Optional: true, + }, + "client_id": { + Type: schema.TypeString, + Optional: true, + }, + "client_secret": { + Type: schema.TypeString, + Optional: true, + }, + "directory_id": { + Type: schema.TypeString, + Optional: true, + }, + "email_attribute_name": { + Type: schema.TypeString, + Optional: true, + }, + "idp_public_cert": { + Type: schema.TypeString, + Optional: true, + }, + "issuer_url": { + Type: schema.TypeString, + Optional: true, + }, + "okta_account": { + Type: schema.TypeString, + Optional: true, + }, + "onelogin_account": { + Type: schema.TypeString, + Optional: true, + }, + "sign_request": { + Type: schema.TypeBool, + Optional: true, + }, + "sso_target_url": { + Type: schema.TypeString, + Optional: true, + }, + "support_groups": { + Type: schema.TypeBool, + Optional: true, + }, + "token_url": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, }, }, } From 9c99fcb6691e7e2c23a080d4712949e3337efb14 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Wed, 12 Feb 2020 09:24:46 +1100 Subject: [PATCH 03/12] add account_id as a schema param --- cloudflare/resource_cloudflare_access_identity_provider.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cloudflare/resource_cloudflare_access_identity_provider.go b/cloudflare/resource_cloudflare_access_identity_provider.go index 78c9a7b0c7..9ebe1fd878 100644 --- a/cloudflare/resource_cloudflare_access_identity_provider.go +++ b/cloudflare/resource_cloudflare_access_identity_provider.go @@ -21,6 +21,10 @@ func resourceCloudflareAccessIdentityProvider() *schema.Resource { }, Schema: map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeString, + Required: true, + }, "name": { Type: schema.TypeString, Required: true, From 62aa92c41cc65fcdb3e972679041944432bd5140 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Wed, 12 Feb 2020 09:24:59 +1100 Subject: [PATCH 04/12] define all the available types in schema --- cloudflare/resource_cloudflare_access_identity_provider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudflare/resource_cloudflare_access_identity_provider.go b/cloudflare/resource_cloudflare_access_identity_provider.go index 9ebe1fd878..0b6cb6b71a 100644 --- a/cloudflare/resource_cloudflare_access_identity_provider.go +++ b/cloudflare/resource_cloudflare_access_identity_provider.go @@ -32,7 +32,7 @@ func resourceCloudflareAccessIdentityProvider() *schema.Resource { "type": { Type: schema.TypeString, Required: true, - ValidateFunc: validation.StringInSlice([]string{"centrify", "citrix_adc_saml", "facebook", "google-apps", "oidc", "github", "google", "saml", "linkedin", "azureAD", "okta", "onetimepin", "onelogin", "authn", "yandex"}, false), + ValidateFunc: validation.StringInSlice([]string{"centrify", "facebook", "google-apps", "oidc", "github", "google", "saml", "linkedin", "azureAD", "okta", "onetimepin", "onelogin", "authn", "yandex"}, false), }, "config": { Type: schema.TypeList, From 0487ccbfb53d48bc1744e003f8118136b6a62261 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Wed, 12 Feb 2020 09:25:53 +1100 Subject: [PATCH 05/12] get basic expand/flatten functionality working --- ...rce_cloudflare_access_identity_provider.go | 111 +++++++++++------- 1 file changed, 69 insertions(+), 42 deletions(-) diff --git a/cloudflare/resource_cloudflare_access_identity_provider.go b/cloudflare/resource_cloudflare_access_identity_provider.go index 0b6cb6b71a..face7fe78e 100644 --- a/cloudflare/resource_cloudflare_access_identity_provider.go +++ b/cloudflare/resource_cloudflare_access_identity_provider.go @@ -122,50 +122,48 @@ func resourceCloudflareAccessIdentityProvider() *schema.Resource { } func resourceCloudflareAccessIdentityProviderRead(d *schema.ResourceData, meta interface{}) error { - // client := meta.(*cloudflare.API) - // zoneID := d.Get("zone_id").(string) - // appID := d.Get("application_id").(string) + client := meta.(*cloudflare.API) + accountID := d.Get("account_id").(string) + + accessIdentityProvider, err := client.AccessIdentityProviderDetails(accountID, d.Id()) + if err != nil { + if strings.Contains(err.Error(), "HTTP status 404") { + log.Printf("[INFO] Access Identity Provider %s no longer exists", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("unable to find Access Identity Provider %q: %s", d.Id(), err) + } - // accessPolicy, err := client.AccessPolicy(zoneID, appID, d.Id()) - // if err != nil { - // if strings.Contains(err.Error(), "HTTP status 404") { - // log.Printf("[INFO] Access Policy %s no longer exists", d.Id()) - // d.SetId("") - // return nil - // } - // return fmt.Errorf("Error finding Access Policy %q: %s", d.Id(), err) - // } + d.SetId(accessIdentityProvider.ID) + d.Set("name", accessIdentityProvider.Name) + d.Set("type", accessIdentityProvider.Type) - // d.Set("name", accessPolicy.Name) - // d.Set("decision", accessPolicy.Decision) - // d.Set("precedence", accessPolicy.Precedence) - // d.Set("require", accessPolicy.Require) - // d.Set("exclude", accessPolicy.Exclude) - // d.Set("include", accessPolicy.Include) + // d.Set("config", flattenIDPConfigOptions(accessIdentityProvider.Config)) return nil } func resourceCloudflareAccessIdentityProviderCreate(d *schema.ResourceData, meta interface{}) error { - // client := meta.(*cloudflare.API) - // appID := d.Get("application_id").(string) - // zoneID := d.Get("zone_id").(string) - // newAccessPolicy := cloudflare.AccessPolicy{ - // Name: d.Get("name").(string), - // Precedence: d.Get("precedence").(int), - // Decision: d.Get("decision").(string), - // } + client := meta.(*cloudflare.API) + accountID := d.Get("account_id").(string) - // newAccessPolicy = appendConditionalAccessPolicyFields(newAccessPolicy, d) + IDPConfiguration, err := expandIDPOptions(d) - // log.Printf("[DEBUG] Creating Cloudflare Access Policy from struct: %+v", newAccessPolicy) + identityProvider := cloudflare.AccessIdentityProvider{ + Name: d.Get("name").(string), + Type: d.Get("type").(string), + Config: IDPConfiguration, + } - // accessPolicy, err := client.CreateAccessPolicy(zoneID, appID, newAccessPolicy) - // if err != nil { - // return fmt.Errorf("error creating Access Policy for ID %q: %s", accessPolicy.ID, err) - // } + log.Printf("[DEBUG] Creating Cloudflare Access Identity Provider from struct: %+v", identityProvider) - // d.SetId(accessPolicy.ID) + accessPolicy, err := client.CreateAccessIdentityProvider(accountID, identityProvider) + if err != nil { + return fmt.Errorf("error creating Access Identity Provider for ID %q: %s", d.Id(), err) + } + + d.SetId(accessPolicy.ID) return nil } @@ -198,18 +196,17 @@ func resourceCloudflareAccessIdentityProviderUpdate(d *schema.ResourceData, meta } func resourceCloudflareAccessIdentityProviderDelete(d *schema.ResourceData, meta interface{}) error { - // client := meta.(*cloudflare.API) - // zoneID := d.Get("zone_id").(string) - // appID := d.Get("application_id").(string) + client := meta.(*cloudflare.API) + accountID := d.Get("account_id").(string) - // log.Printf("[DEBUG] Deleting Cloudflare Access Policy using ID: %s", d.Id()) + log.Printf("[DEBUG] Deleting Cloudflare Access Identity Provider using ID: %s", d.Id()) - // err := client.DeleteAccessPolicy(zoneID, appID, d.Id()) - // if err != nil { - // return fmt.Errorf("error deleting Access Policy for ID %q: %s", d.Id(), err) - // } + _, err := client.DeleteAccessIdentityProvider(accountID, d.Id()) + if err != nil { + return fmt.Errorf("error deleting Access Policy for ID %q: %s", d.Id(), err) + } - // resourceCloudflareAccessIdentityProviderRead(d, meta) + resourceCloudflareAccessIdentityProviderRead(d, meta) return nil } @@ -233,3 +230,33 @@ func resourceCloudflareAccessIdentityProviderImport(d *schema.ResourceData, meta return []*schema.ResourceData{d}, nil } + +func expandIDPOptions(d *schema.ResourceData) (cloudflare.AccessIdentityProviderConfiguration, error) { + tfAction := d.Get("config").([]interface{})[0].(map[string]interface{}) + IDPConfig := cloudflare.AccessIdentityProviderConfiguration{} + + client_id := tfAction["client_id"].(string) + client_secret := tfAction["client_secret"].(string) + + IDPConfig.ClientID = client_id + IDPConfig.ClientSecret = client_secret + + return IDPConfig, nil +} + +// func flattenIDPConfigOptions(options cloudflare.AccessIdentityProviderConfiguration) interface{} { +// action := map[string]interface{}{ +// "mode": cfg.Mode, +// "timeout": cfg.Timeout, +// } + +// if cfg.Response != nil { +// cfgResponse := *cfg.Response +// actionResponse := map[string]interface{}{ +// "content_type": cfgResponse.ContentType, +// "body": cfgResponse.Body, +// } +// action["response"] = []map[string]interface{}{actionResponse} +// } +// return []map[string]interface{}{action} +// } From 44e5d1932357d1cf1ef462b9bb95955e1a820516 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Wed, 12 Feb 2020 09:26:11 +1100 Subject: [PATCH 06/12] add tests for onetimepin and github --- ...loudflare_access_identity_provider_test.go | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 cloudflare/resource_cloudflare_access_identity_provider_test.go diff --git a/cloudflare/resource_cloudflare_access_identity_provider_test.go b/cloudflare/resource_cloudflare_access_identity_provider_test.go new file mode 100644 index 0000000000..064612b240 --- /dev/null +++ b/cloudflare/resource_cloudflare_access_identity_provider_test.go @@ -0,0 +1,75 @@ +package cloudflare + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccCloudflareAccessIdentityProviderOneTimePin(t *testing.T) { + t.Parallel() + accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + rnd := generateRandomResourceName() + resourceName := "cloudflare_access_policy_identity_provider." + rnd + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckCloudflareAccessIdentityProviderOneTimePin(accountID, rnd), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_id", accountID), + resource.TestCheckResourceAttr(resourceName, "name", "test"), + resource.TestCheckResourceAttr(resourceName, "type", "onetimepin"), + ), + }, + }, + }) +} + +func TestAccCloudflareAccessIdentityProviderOAuth(t *testing.T) { + t.Parallel() + accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + rnd := generateRandomResourceName() + resourceName := "cloudflare_access_policy_identity_provider." + rnd + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckCloudflareAccessIdentityProviderOPAuth(accountID, rnd), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_id", accountID), + resource.TestCheckResourceAttr(resourceName, "name", "test"), + resource.TestCheckResourceAttr(resourceName, "type", "github"), + resource.TestCheckResourceAttr(resourceName, "config.0.client_id", "test"), + resource.TestCheckResourceAttr(resourceName, "config.0.client_secret", "secret"), + ), + }, + }, + }) +} + +func testAccCheckCloudflareAccessIdentityProviderOneTimePin(accountID, name string) string { + return fmt.Sprintf(` +resource "cloudflare_access_identity_provider" "%[2]s" { + account_id = "%[1]s" + name = "test" + type = "onetimepin" +}`, accountID, name) +} + +func testAccCheckCloudflareAccessIdentityProviderOPAuth(accountID, name string) string { + return fmt.Sprintf(` +resource "cloudflare_access_identity_provider" "%[2]s" { + account_id = "%[1]s" + name = "test" + type = "github" + config { + client_id = "test" + client_secret = "secret" + } +}`, accountID, name) +} From 36c8e219b4960a2f1e819625d380fca844cf5d24 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 13 Feb 2020 07:36:28 +1100 Subject: [PATCH 07/12] force state value to always be the hardcoded concealed values --- cloudflare/resource_cloudflare_access_identity_provider.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cloudflare/resource_cloudflare_access_identity_provider.go b/cloudflare/resource_cloudflare_access_identity_provider.go index face7fe78e..d06c2723e6 100644 --- a/cloudflare/resource_cloudflare_access_identity_provider.go +++ b/cloudflare/resource_cloudflare_access_identity_provider.go @@ -73,6 +73,13 @@ func resourceCloudflareAccessIdentityProvider() *schema.Resource { "client_secret": { Type: schema.TypeString, Optional: true, + ForceNew: true, + // client_secret is a write only operation from the Cloudflare API + // and once it's set, it is no longer accessible. To avoid storing + // it and messing up the state, hardcode in the concealed version. + StateFunc: func(val interface{}) string { + return "**********************************" + }, }, "directory_id": { Type: schema.TypeString, From c55bbf84322ba148983c98506d808e7b6c89a75a Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 13 Feb 2020 07:36:55 +1100 Subject: [PATCH 08/12] working state for all tests --- ...rce_cloudflare_access_identity_provider.go | 108 ++++++++++-------- ...loudflare_access_identity_provider_test.go | 24 ++-- 2 files changed, 75 insertions(+), 57 deletions(-) diff --git a/cloudflare/resource_cloudflare_access_identity_provider.go b/cloudflare/resource_cloudflare_access_identity_provider.go index d06c2723e6..d373f80b80 100644 --- a/cloudflare/resource_cloudflare_access_identity_provider.go +++ b/cloudflare/resource_cloudflare_access_identity_provider.go @@ -146,7 +146,12 @@ func resourceCloudflareAccessIdentityProviderRead(d *schema.ResourceData, meta i d.Set("name", accessIdentityProvider.Name) d.Set("type", accessIdentityProvider.Type) - // d.Set("config", flattenIDPConfigOptions(accessIdentityProvider.Config)) + fmt.Printf("concertStructToSchema: %+v", convertStructToSchema(d, accessIdentityProvider.Config)) + + config := convertStructToSchema(d, accessIdentityProvider.Config) + if configErr := d.Set("config", config); configErr != nil { + return fmt.Errorf("error setting Access Identity Provider configuration: %s", configErr) + } return nil } @@ -154,13 +159,17 @@ func resourceCloudflareAccessIdentityProviderRead(d *schema.ResourceData, meta i func resourceCloudflareAccessIdentityProviderCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*cloudflare.API) accountID := d.Get("account_id").(string) + IDPConfig := cloudflare.AccessIdentityProviderConfiguration{} - IDPConfiguration, err := expandIDPOptions(d) + if _, ok := d.GetOk("config"); ok { + IDPConfig.ClientID = d.Get("config.0.client_id").(string) + IDPConfig.ClientSecret = d.Get("config.0.client_secret").(string) + } identityProvider := cloudflare.AccessIdentityProvider{ Name: d.Get("name").(string), Type: d.Get("type").(string), - Config: IDPConfiguration, + Config: IDPConfig, } log.Printf("[DEBUG] Creating Cloudflare Access Identity Provider from struct: %+v", identityProvider) @@ -172,32 +181,44 @@ func resourceCloudflareAccessIdentityProviderCreate(d *schema.ResourceData, meta d.SetId(accessPolicy.ID) - return nil + return resourceCloudflareAccessIdentityProviderRead(d, meta) } func resourceCloudflareAccessIdentityProviderUpdate(d *schema.ResourceData, meta interface{}) error { - // client := meta.(*cloudflare.API) - // zoneID := d.Get("zone_id").(string) - // appID := d.Get("application_id").(string) - // updatedAccessPolicy := cloudflare.AccessPolicy{ - // Name: d.Get("name").(string), - // Precedence: d.Get("precedence").(int), - // Decision: d.Get("decision").(string), - // ID: d.Id(), - // } + client := meta.(*cloudflare.API) + accountID := d.Get("account_id").(string) - // updatedAccessPolicy = appendConditionalAccessPolicyFields(updatedAccessPolicy, d) + IDPConfig := cloudflare.AccessIdentityProviderConfiguration{} - // log.Printf("[DEBUG] Updating Cloudflare Access Policy from struct: %+v", updatedAccessPolicy) + if _, ok := d.GetOk("config"); ok { + tfAction := d.Get("config").([]interface{})[0].(map[string]interface{}) - // accessPolicy, err := client.UpdateAccessPolicy(zoneID, appID, updatedAccessPolicy) - // if err != nil { - // return fmt.Errorf("error updating Access Policy for ID %q: %s", d.Id(), err) - // } + fmt.Printf("tfAction: %+v", tfAction) - // if accessPolicy.ID == "" { - // return fmt.Errorf("failed to find Access Policy ID in update response; resource was empty") - // } + clientID := tfAction["client_id"].(string) + clientSecret := tfAction["client_secret"].(string) + + IDPConfig.ClientID = clientID + IDPConfig.ClientSecret = clientSecret + } + + log.Printf("[DEBUG] updatedConfig: %+v", IDPConfig) + updatedAccessIdentityProvider := cloudflare.AccessIdentityProvider{ + Name: d.Get("name").(string), + Type: d.Get("type").(string), + Config: IDPConfig, + } + + log.Printf("[DEBUG] Updating Cloudflare Access Identity Provider from struct: %+v", updatedAccessIdentityProvider) + + accessIdentityProvider, err := client.UpdateAccessIdentityProvider(accountID, d.Id(), updatedAccessIdentityProvider) + if err != nil { + return fmt.Errorf("error updating Access Identity Provider for ID %q: %s", d.Id(), err) + } + + if accessIdentityProvider.ID == "" { + return fmt.Errorf("failed to find Access Identity Provider ID in update response; resource was empty") + } return resourceCloudflareAccessIdentityProviderRead(d, meta) } @@ -213,7 +234,7 @@ func resourceCloudflareAccessIdentityProviderDelete(d *schema.ResourceData, meta return fmt.Errorf("error deleting Access Policy for ID %q: %s", d.Id(), err) } - resourceCloudflareAccessIdentityProviderRead(d, meta) + d.SetId("") return nil } @@ -238,32 +259,23 @@ func resourceCloudflareAccessIdentityProviderImport(d *schema.ResourceData, meta return []*schema.ResourceData{d}, nil } -func expandIDPOptions(d *schema.ResourceData) (cloudflare.AccessIdentityProviderConfiguration, error) { - tfAction := d.Get("config").([]interface{})[0].(map[string]interface{}) - IDPConfig := cloudflare.AccessIdentityProviderConfiguration{} +// func convertSchemaToStruct(d *schema.ResourceData) (cloudflare.AccessIdentityProviderConfiguration, error) { - client_id := tfAction["client_id"].(string) - client_secret := tfAction["client_secret"].(string) +// fmt.Printf("IDPConfig: %+v", IDPConfig) - IDPConfig.ClientID = client_id - IDPConfig.ClientSecret = client_secret +// return IDPConfig, nil +// } - return IDPConfig, nil -} +func convertStructToSchema(d *schema.ResourceData, options cloudflare.AccessIdentityProviderConfiguration) []interface{} { + // todo: find a better way of confirming we have options + if options.ClientID == "" { + return []interface{}{} + } -// func flattenIDPConfigOptions(options cloudflare.AccessIdentityProviderConfiguration) interface{} { -// action := map[string]interface{}{ -// "mode": cfg.Mode, -// "timeout": cfg.Timeout, -// } - -// if cfg.Response != nil { -// cfgResponse := *cfg.Response -// actionResponse := map[string]interface{}{ -// "content_type": cfgResponse.ContentType, -// "body": cfgResponse.Body, -// } -// action["response"] = []map[string]interface{}{actionResponse} -// } -// return []map[string]interface{}{action} -// } + m := map[string]interface{}{ + "client_id": options.ClientID, + "client_secret": options.ClientSecret, + } + + return []interface{}{m} +} diff --git a/cloudflare/resource_cloudflare_access_identity_provider_test.go b/cloudflare/resource_cloudflare_access_identity_provider_test.go index 064612b240..f458435a47 100644 --- a/cloudflare/resource_cloudflare_access_identity_provider_test.go +++ b/cloudflare/resource_cloudflare_access_identity_provider_test.go @@ -12,16 +12,19 @@ func TestAccCloudflareAccessIdentityProviderOneTimePin(t *testing.T) { t.Parallel() accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") rnd := generateRandomResourceName() - resourceName := "cloudflare_access_policy_identity_provider." + rnd + resourceName := "cloudflare_access_identity_provider." + rnd resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAccount(t) + }, Providers: testAccProviders, Steps: []resource.TestStep{ { Config: testAccCheckCloudflareAccessIdentityProviderOneTimePin(accountID, rnd), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "account_id", accountID), - resource.TestCheckResourceAttr(resourceName, "name", "test"), + resource.TestCheckResourceAttr(resourceName, "name", rnd), resource.TestCheckResourceAttr(resourceName, "type", "onetimepin"), ), }, @@ -33,19 +36,22 @@ func TestAccCloudflareAccessIdentityProviderOAuth(t *testing.T) { t.Parallel() accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") rnd := generateRandomResourceName() - resourceName := "cloudflare_access_policy_identity_provider." + rnd + resourceName := "cloudflare_access_identity_provider." + rnd resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAccount(t) + }, Providers: testAccProviders, Steps: []resource.TestStep{ { Config: testAccCheckCloudflareAccessIdentityProviderOPAuth(accountID, rnd), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "account_id", accountID), - resource.TestCheckResourceAttr(resourceName, "name", "test"), + resource.TestCheckResourceAttr(resourceName, "name", rnd), resource.TestCheckResourceAttr(resourceName, "type", "github"), resource.TestCheckResourceAttr(resourceName, "config.0.client_id", "test"), - resource.TestCheckResourceAttr(resourceName, "config.0.client_secret", "secret"), + resource.TestCheckResourceAttrSet(resourceName, "config.0.client_secret"), ), }, }, @@ -56,7 +62,7 @@ func testAccCheckCloudflareAccessIdentityProviderOneTimePin(accountID, name stri return fmt.Sprintf(` resource "cloudflare_access_identity_provider" "%[2]s" { account_id = "%[1]s" - name = "test" + name = "%[2]s" type = "onetimepin" }`, accountID, name) } @@ -65,7 +71,7 @@ func testAccCheckCloudflareAccessIdentityProviderOPAuth(accountID, name string) return fmt.Sprintf(` resource "cloudflare_access_identity_provider" "%[2]s" { account_id = "%[1]s" - name = "test" + name = "%[2]s" type = "github" config { client_id = "test" From 964404b9e34ca806036c5f68417c2ce31635a52b Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 13 Feb 2020 10:06:57 +1100 Subject: [PATCH 09/12] fix resource create/update state --- ...rce_cloudflare_access_identity_provider.go | 130 ++++++++++++------ 1 file changed, 89 insertions(+), 41 deletions(-) diff --git a/cloudflare/resource_cloudflare_access_identity_provider.go b/cloudflare/resource_cloudflare_access_identity_provider.go index d373f80b80..6aec7f8340 100644 --- a/cloudflare/resource_cloudflare_access_identity_provider.go +++ b/cloudflare/resource_cloudflare_access_identity_provider.go @@ -10,6 +10,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/validation" ) +const CONCEALED_STRING = "**********************************" + func resourceCloudflareAccessIdentityProvider() *schema.Resource { return &schema.Resource{ Create: resourceCloudflareAccessIdentityProviderCreate, @@ -24,6 +26,7 @@ func resourceCloudflareAccessIdentityProvider() *schema.Resource { "account_id": { Type: schema.TypeString, Required: true, + ForceNew: true, }, "name": { Type: schema.TypeString, @@ -32,7 +35,7 @@ func resourceCloudflareAccessIdentityProvider() *schema.Resource { "type": { Type: schema.TypeString, Required: true, - ValidateFunc: validation.StringInSlice([]string{"centrify", "facebook", "google-apps", "oidc", "github", "google", "saml", "linkedin", "azureAD", "okta", "onetimepin", "onelogin", "authn", "yandex"}, false), + ValidateFunc: validation.StringInSlice([]string{"centrify", "facebook", "google-apps", "oidc", "github", "google", "saml", "linkedin", "azureAD", "okta", "onetimepin", "onelogin", "yandex"}, false), }, "config": { Type: schema.TypeList, @@ -44,7 +47,7 @@ func resourceCloudflareAccessIdentityProvider() *schema.Resource { Optional: true, }, "attributes": { - Type: schema.TypeMap, + Type: schema.TypeList, Optional: true, Elem: &schema.Schema{ Type: schema.TypeString, @@ -73,12 +76,11 @@ func resourceCloudflareAccessIdentityProvider() *schema.Resource { "client_secret": { Type: schema.TypeString, Optional: true, - ForceNew: true, // client_secret is a write only operation from the Cloudflare API // and once it's set, it is no longer accessible. To avoid storing // it and messing up the state, hardcode in the concealed version. StateFunc: func(val interface{}) string { - return "**********************************" + return CONCEALED_STRING }, }, "directory_id": { @@ -92,6 +94,13 @@ func resourceCloudflareAccessIdentityProvider() *schema.Resource { "idp_public_cert": { Type: schema.TypeString, Optional: true, + // idp_public_cert is a write only operation from the Cloudflare + // API and once it's set, it is no longer accessible. To avoid + // storing it and messing up the state, hardcode in the concealed + // version. + StateFunc: func(val interface{}) string { + return CONCEALED_STRING + }, }, "issuer_url": { Type: schema.TypeString, @@ -105,6 +114,11 @@ func resourceCloudflareAccessIdentityProvider() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "redirect_url": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, "sign_request": { Type: schema.TypeBool, Optional: true, @@ -143,11 +157,10 @@ func resourceCloudflareAccessIdentityProviderRead(d *schema.ResourceData, meta i } d.SetId(accessIdentityProvider.ID) + d.Set("account_id", accountID) d.Set("name", accessIdentityProvider.Name) d.Set("type", accessIdentityProvider.Type) - fmt.Printf("concertStructToSchema: %+v", convertStructToSchema(d, accessIdentityProvider.Config)) - config := convertStructToSchema(d, accessIdentityProvider.Config) if configErr := d.Set("config", config); configErr != nil { return fmt.Errorf("error setting Access Identity Provider configuration: %s", configErr) @@ -159,12 +172,8 @@ func resourceCloudflareAccessIdentityProviderRead(d *schema.ResourceData, meta i func resourceCloudflareAccessIdentityProviderCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*cloudflare.API) accountID := d.Get("account_id").(string) - IDPConfig := cloudflare.AccessIdentityProviderConfiguration{} - if _, ok := d.GetOk("config"); ok { - IDPConfig.ClientID = d.Get("config.0.client_id").(string) - IDPConfig.ClientSecret = d.Get("config.0.client_secret").(string) - } + IDPConfig, _ := convertSchemaToStruct(d) identityProvider := cloudflare.AccessIdentityProvider{ Name: d.Get("name").(string), @@ -188,18 +197,9 @@ func resourceCloudflareAccessIdentityProviderUpdate(d *schema.ResourceData, meta client := meta.(*cloudflare.API) accountID := d.Get("account_id").(string) - IDPConfig := cloudflare.AccessIdentityProviderConfiguration{} - - if _, ok := d.GetOk("config"); ok { - tfAction := d.Get("config").([]interface{})[0].(map[string]interface{}) - - fmt.Printf("tfAction: %+v", tfAction) - - clientID := tfAction["client_id"].(string) - clientSecret := tfAction["client_secret"].(string) - - IDPConfig.ClientID = clientID - IDPConfig.ClientSecret = clientSecret + IDPConfig, conversionErr := convertSchemaToStruct(d) + if conversionErr != nil { + return fmt.Errorf("failed to convert schema into struct: %s", conversionErr) } log.Printf("[DEBUG] updatedConfig: %+v", IDPConfig) @@ -240,41 +240,89 @@ func resourceCloudflareAccessIdentityProviderDelete(d *schema.ResourceData, meta } func resourceCloudflareAccessIdentityProviderImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - // attributes := strings.SplitN(d.Id(), "/", 3) + attributes := strings.SplitN(d.Id(), "/", 2) - // if len(attributes) != 3 { - // return nil, fmt.Errorf("invalid id (\"%s\") specified, should be in format \"zoneID/accessApplicationID/accessPolicyID\"", d.Id()) - // } + if len(attributes) != 2 { + return nil, fmt.Errorf("invalid id (\"%s\") specified, should be in format \"accountID/accessIdentityProviderID\"", d.Id()) + } - // zoneID, accessAppID, accessPolicyID := attributes[0], attributes[1], attributes[2] + accountID, accessIdentityProviderID := attributes[0], attributes[1] - // log.Printf("[DEBUG] Importing Cloudflare Access Policy: zoneID %q, appID %q, accessPolicyID %q", zoneID, accessAppID, accessPolicyID) + log.Printf("[DEBUG] Importing Cloudflare Access Identity Provider: accountID=%s accessIdentityProviderID=%s", accountID, accessIdentityProviderID) - // d.Set("zone_id", zoneID) - // d.Set("application_id", accessAppID) - // d.SetId(accessPolicyID) + d.Set("account_id", accountID) + d.SetId(accessIdentityProviderID) - // resourceCloudflareAccessIdentityProviderRead(d, meta) + resourceCloudflareAccessIdentityProviderRead(d, meta) return []*schema.ResourceData{d}, nil } -// func convertSchemaToStruct(d *schema.ResourceData) (cloudflare.AccessIdentityProviderConfiguration, error) { +func convertSchemaToStruct(d *schema.ResourceData) (cloudflare.AccessIdentityProviderConfiguration, error) { + IDPConfig := cloudflare.AccessIdentityProviderConfiguration{} + + if _, ok := d.GetOk("config"); ok { + if _, ok := d.GetOk("config.0.attributes"); ok { + attrData := make([]string, d.Get("config.0.attributes.#").(int)) + for id := range attrData { + attrData[id] = d.Get(fmt.Sprintf("config.0.attributes.%d", id)).(string) + } + IDPConfig.Attributes = attrData + } -// fmt.Printf("IDPConfig: %+v", IDPConfig) + IDPConfig.AppsDomain = d.Get("config.0.apps_domain").(string) + IDPConfig.AuthURL = d.Get("config.0.auth_url").(string) + IDPConfig.CentrifyAccount = d.Get("config.0.centrify_account").(string) + IDPConfig.CentrifyAppID = d.Get("config.0.centrify_app_id").(string) + IDPConfig.CertsURL = d.Get("config.0.certs_url").(string) + IDPConfig.ClientID = d.Get("config.0.client_id").(string) + IDPConfig.ClientSecret = d.Get("config.0.client_secret").(string) + IDPConfig.DirectoryID = d.Get("config.0.directory_id").(string) + IDPConfig.EmailAttributeName = d.Get("config.0.email_attribute_name").(string) + IDPConfig.IdpPublicCert = d.Get("config.0.idp_public_cert").(string) + IDPConfig.IssuerURL = d.Get("config.0.issuer_url").(string) + IDPConfig.OktaAccount = d.Get("config.0.okta_account").(string) + IDPConfig.OneloginAccount = d.Get("config.0.onelogin_account").(string) + IDPConfig.RedirectURL = d.Get("config.0.redirect_url").(string) + IDPConfig.SignRequest = d.Get("config.0.sign_request").(bool) + IDPConfig.SsoTargetURL = d.Get("config.0.sso_target_url").(string) + IDPConfig.SupportGroups = d.Get("config.0.support_groups").(bool) + IDPConfig.TokenURL = d.Get("config.0.token_url").(string) + } -// return IDPConfig, nil -// } + return IDPConfig, nil +} func convertStructToSchema(d *schema.ResourceData, options cloudflare.AccessIdentityProviderConfiguration) []interface{} { - // todo: find a better way of confirming we have options - if options.ClientID == "" { + if _, ok := d.GetOk("config"); !ok { return []interface{}{} } + attributes := make([]string, 0) + for _, value := range options.Attributes { + attributes = append(attributes, value) + } + m := map[string]interface{}{ - "client_id": options.ClientID, - "client_secret": options.ClientSecret, + "apps_domain": options.AppsDomain, + "attributes": attributes, + "auth_url": options.AuthURL, + "centrify_account": options.CentrifyAccount, + "centrify_app_id": options.CentrifyAppID, + "certs_url": options.CertsURL, + "client_id": options.ClientID, + "client_secret": options.ClientSecret, + "directory_id": options.DirectoryID, + "email_attribute_name": options.EmailAttributeName, + "idp_public_cert": options.IdpPublicCert, + "issuer_url": options.IssuerURL, + "okta_account": options.OktaAccount, + "onelogin_account": options.OneloginAccount, + "redirect_url": options.RedirectURL, + "sign_request": options.SignRequest, + "sso_target_url": options.SsoTargetURL, + "support_groups": options.SupportGroups, + "token_url": options.TokenURL, } return []interface{}{m} From cd42b3a300f64ea44b3df26d317b9fb067e25225 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 13 Feb 2020 10:07:11 +1100 Subject: [PATCH 10/12] add test sweeper --- ...loudflare_access_identity_provider_test.go | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/cloudflare/resource_cloudflare_access_identity_provider_test.go b/cloudflare/resource_cloudflare_access_identity_provider_test.go index f458435a47..0e905ea741 100644 --- a/cloudflare/resource_cloudflare_access_identity_provider_test.go +++ b/cloudflare/resource_cloudflare_access_identity_provider_test.go @@ -8,6 +8,42 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/resource" ) +func init() { + resource.AddTestSweepers("cloudflare_access_identity_provider", &resource.Sweeper{ + Name: "cloudflare_access_identity_provider", + F: testSweepCloudflareAccessIdentityProviders, + }) +} + +func testSweepCloudflareAccessIdentityProviders(r string) error { + accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + client, clientErr := sharedClient() + if clientErr != nil { + log.Printf("[ERROR] Failed to create Cloudflare client: %s", clientErr) + } + + accessIDPs, accessIDPsErr := client.ListZones() + if accessIDPsErr != nil { + log.Printf("[ERROR] Failed to fetch Access Identity Providers: %s", accessIDPsErr) + } + + if len(accessIDPs) == 0 { + log.Print("[DEBUG] No Access Identity Providers to sweep") + return nil + } + + for _, idp := range accessIDPs { + log.Printf("[INFO] Deleting Access Identity Provider ID: %s", idp.ID) + _, err := client.DeleteAccessIdentityProvider(accountID, idp.ID) + + if err != nil { + log.Printf("[ERROR] Failed to delete Access Identity Provider (%s): %s", idp.ID, err) + } + } + + return nil +} + func TestAccCloudflareAccessIdentityProviderOneTimePin(t *testing.T) { t.Parallel() accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") From 60490177f5ef84dd4d0712f4d7cd2ee955df30dd Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 13 Feb 2020 10:07:30 +1100 Subject: [PATCH 11/12] add test cases for the new resource configurations --- ...loudflare_access_identity_provider_test.go | 101 +++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/cloudflare/resource_cloudflare_access_identity_provider_test.go b/cloudflare/resource_cloudflare_access_identity_provider_test.go index 0e905ea741..469a0e6e42 100644 --- a/cloudflare/resource_cloudflare_access_identity_provider_test.go +++ b/cloudflare/resource_cloudflare_access_identity_provider_test.go @@ -2,6 +2,7 @@ package cloudflare import ( "fmt" + "log" "os" "testing" @@ -81,7 +82,7 @@ func TestAccCloudflareAccessIdentityProviderOAuth(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: testAccCheckCloudflareAccessIdentityProviderOPAuth(accountID, rnd), + Config: testAccCheckCloudflareAccessIdentityProviderOAuth(accountID, rnd), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "account_id", accountID), resource.TestCheckResourceAttr(resourceName, "name", rnd), @@ -94,6 +95,73 @@ func TestAccCloudflareAccessIdentityProviderOAuth(t *testing.T) { }) } +func TestAccCloudflareAccessIdentityProviderOAuthWithUpdate(t *testing.T) { + t.Parallel() + accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + rnd := generateRandomResourceName() + resourceName := "cloudflare_access_identity_provider." + rnd + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAccount(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckCloudflareAccessIdentityProviderOAuth(accountID, rnd), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_id", accountID), + resource.TestCheckResourceAttr(resourceName, "name", rnd), + resource.TestCheckResourceAttr(resourceName, "type", "github"), + resource.TestCheckResourceAttr(resourceName, "config.0.client_id", "test"), + resource.TestCheckResourceAttrSet(resourceName, "config.0.client_secret"), + ), + }, + { + Config: testAccCheckCloudflareAccessIdentityProviderOAuthUpdatedName(accountID, rnd), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_id", accountID), + resource.TestCheckResourceAttr(resourceName, "name", rnd+"-updated"), + resource.TestCheckResourceAttr(resourceName, "type", "github"), + resource.TestCheckResourceAttr(resourceName, "config.0.client_id", "test"), + resource.TestCheckResourceAttrSet(resourceName, "config.0.client_secret"), + ), + }, + }, + }) +} + +func TestAccCloudflareAccessIdentityProviderSAML(t *testing.T) { + t.Parallel() + accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID") + rnd := generateRandomResourceName() + resourceName := "cloudflare_access_identity_provider." + rnd + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + testAccPreCheckAccount(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckCloudflareAccessIdentityProviderSAML(accountID, rnd), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_id", accountID), + resource.TestCheckResourceAttr(resourceName, "name", rnd), + resource.TestCheckResourceAttr(resourceName, "type", "saml"), + resource.TestCheckResourceAttr(resourceName, "config.0.issuer_url", "jumpcloud"), + resource.TestCheckResourceAttr(resourceName, "config.0.sso_target_url", "https://sso.myexample.jumpcloud.com/saml2/cloudflareaccess"), + resource.TestCheckResourceAttr(resourceName, "config.0.sign_request", "false"), + resource.TestCheckResourceAttr(resourceName, "config.0.attributes.#", "2"), + resource.TestCheckResourceAttr(resourceName, "config.0.attributes.0", "email"), + resource.TestCheckResourceAttr(resourceName, "config.0.attributes.1", "username"), + resource.TestCheckResourceAttrSet(resourceName, "config.0.idp_public_cert"), + ), + }, + }, + }) +} + func testAccCheckCloudflareAccessIdentityProviderOneTimePin(accountID, name string) string { return fmt.Sprintf(` resource "cloudflare_access_identity_provider" "%[2]s" { @@ -103,7 +171,7 @@ resource "cloudflare_access_identity_provider" "%[2]s" { }`, accountID, name) } -func testAccCheckCloudflareAccessIdentityProviderOPAuth(accountID, name string) string { +func testAccCheckCloudflareAccessIdentityProviderOAuth(accountID, name string) string { return fmt.Sprintf(` resource "cloudflare_access_identity_provider" "%[2]s" { account_id = "%[1]s" @@ -115,3 +183,32 @@ resource "cloudflare_access_identity_provider" "%[2]s" { } }`, accountID, name) } + +func testAccCheckCloudflareAccessIdentityProviderOAuthUpdatedName(accountID, name string) string { + return fmt.Sprintf(` +resource "cloudflare_access_identity_provider" "%[2]s" { + account_id = "%[1]s" + name = "%[2]s-updated" + type = "github" + config { + client_id = "test" + client_secret = "secret" + } +}`, accountID, name) +} + +func testAccCheckCloudflareAccessIdentityProviderSAML(accountID, name string) string { + return fmt.Sprintf(` +resource "cloudflare_access_identity_provider" "%[2]s" { + account_id = "%[1]s" + name = "%[2]s" + type = "saml" + config { + issuer_url = "jumpcloud" + sso_target_url = "https://sso.myexample.jumpcloud.com/saml2/cloudflareaccess" + attributes = [ "email", "username" ] + sign_request = false + idp_public_cert = "MIIDpDCCAoygAwIBAgIGAV2ka+55MA0GCSqGSIb3DQEBCwUAMIGSMQswCQYDVQQGEwJVUzETMBEG\nA1UEC…..GF/Q2/MHadws97cZg\nuTnQyuOqPuHbnN83d/2l1NSYKCbHt24o" + } +}`, accountID, name) +} From 72ca69998349cb8ac15ce27b6a0143772fec7127 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 13 Feb 2020 10:09:16 +1100 Subject: [PATCH 12/12] add website documentation --- website/cloudflare.erb | 3 + .../r/access_identity_provider.html.markdown | 82 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 website/docs/r/access_identity_provider.html.markdown diff --git a/website/cloudflare.erb b/website/cloudflare.erb index 5704bfa098..6e09e64d97 100644 --- a/website/cloudflare.erb +++ b/website/cloudflare.erb @@ -46,6 +46,9 @@ > cloudflare_access_application + > + cloudflare_access_identity_provider + > cloudflare_access_policy diff --git a/website/docs/r/access_identity_provider.html.markdown b/website/docs/r/access_identity_provider.html.markdown new file mode 100644 index 0000000000..45a6316072 --- /dev/null +++ b/website/docs/r/access_identity_provider.html.markdown @@ -0,0 +1,82 @@ +--- +layout: "cloudflare" +page_title: "Cloudflare: cloudflare_access_identity_provider" +sidebar_current: "docs-cloudflare-resource-access-identity-provider" +description: |- + Provides a Cloudflare Access Identity Provider resource. +--- + +# cloudflare_access_identity_provider + +Provides a Cloudflare Access Identity Provider resource. Identity Providers are +used as an authentication or authorisation source within Access. + +## Example Usage + +```hcl +# one time pin +resource "cloudflare_access_identity_provider" "pin_login" { + account_id = "1d5fdc9e88c8a8c4518b068cd94331fe" + name = "PIN login" + type = "onetimepin" +} + +# oauth +resource "cloudflare_access_identity_provider" "github_oauth" { + account_id = "1d5fdc9e88c8a8c4518b068cd94331fe" + name = "GitHub OAuth" + type = "github" + config { + client_id = "example" + client_secret = "secret_key" + } +} + +# saml +resource "cloudflare_access_identity_provider" "jumpcloud_saml" { + account_id = "1d5fdc9e88c8a8c4518b068cd94331fe" + name = "JumpCloud SAML" + type = "saml" + config { + issuer_url = "jumpcloud" + sso_target_url = "https://sso.myexample.jumpcloud.com/saml2/cloudflareaccess" + attributes = [ "email", "username" ] + sign_request = false + idp_public_cert = "MIIDpDCCAoygAwIBAgIGAV2ka+55MA0GCSqGSIb3DQEBCwUAMIGSMQswCQ...GF/Q2/MHadws97cZg\nuTnQyuOqPuHbnN83d/2l1NSYKCbHt24o" + } +} +``` + +Please refer to the [developers.cloudflare.com Access documentation][access_identity_provider_guide] +for full reference on what is available and how to configure your provider. + +## Argument Reference + +The following arguments are supported: + +* `account` - (Required) The account ID the provider should be associated with. +* `name` - (Required) Friendly name of the Access Identity Provider configuration. +* `type` - (Required) The provider type to use. Must be one of: `"centrify"`, + `"facebook"`, `"google-apps"`, `"oidc"`, `"github"`, `"google"`, `"saml"`, + `"linkedin"`, `"azureAD"`, `"okta"`, `"onetimepin"`, `"onelogin"`, `"yandex"`. +* `config` - (Optional) Provider configuration from the [developer documentation][access_identity_provider_guide]. + +## Attributes Reference + +The following additional attributes are exported: + +* `id` - ID of the Access Identity Provider +* `name` - Friendly name of the Access Identity Provider configuration. +* `type` - The provider type to use. +* `config` - Access Identity Provider configuration. + +## Import + +Access Identity Providers can be imported using a composite ID formed of account +ID and Access Identity Provider ID. + +``` +$ terraform import cloudflare_access_identity_provider.my_idp cb029e245cfdd66dc8d2e570d5dd3322/e00e1c13-e350-44fe-96c5-fb75c954871c +``` + +[access_identity_provider_guide]: https://developers.cloudflare.com/access/configuring-identity-providers/