From 7b84398979518f9342787f260b7e5efe4d2baf23 Mon Sep 17 00:00:00 2001 From: "uros.simovic" Date: Thu, 5 Nov 2020 07:41:45 +0100 Subject: [PATCH 01/17] add reasource 'api_token' and data source 'permission_groups' --- ...data_source_api_token_permission_groups.go | 46 +++ ...source_api_token_permission_groups_test.go | 54 ++++ cloudflare/provider.go | 12 +- cloudflare/resource_cloudflare_api_token.go | 264 ++++++++++++++++++ .../resource_cloudflare_api_token_test.go | 66 +++++ go.mod | 4 +- go.sum | 7 +- 7 files changed, 443 insertions(+), 10 deletions(-) create mode 100644 cloudflare/data_source_api_token_permission_groups.go create mode 100644 cloudflare/data_source_api_token_permission_groups_test.go create mode 100644 cloudflare/resource_cloudflare_api_token.go create mode 100644 cloudflare/resource_cloudflare_api_token_test.go diff --git a/cloudflare/data_source_api_token_permission_groups.go b/cloudflare/data_source_api_token_permission_groups.go new file mode 100644 index 0000000000..5a0e43fe1e --- /dev/null +++ b/cloudflare/data_source_api_token_permission_groups.go @@ -0,0 +1,46 @@ +package cloudflare + +import ( + "fmt" + "log" + "time" + + "github.com/cloudflare/cloudflare-go" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func dataSourceCloudflareApiTokenPermissionGroups() *schema.Resource { + return &schema.Resource{ + Read: dataSourceCloudflareApiTokenPermissionGroupsRead, + + Schema: map[string]*schema.Schema{ + "permissions": { + Computed: true, + Type: schema.TypeMap, + }, + }, + } +} + +func dataSourceCloudflareApiTokenPermissionGroupsRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("[DEBUG] Reading User Token Permission Groups") + client := meta.(*cloudflare.API) + + permissions, err := client.ListAPITokensPermissionGroups() + if err != nil { + return fmt.Errorf("error listing User Token Permission Groups: %s", err) + } + + permissionDetails := make(map[string]interface{}, 0) + for _, v := range permissions { + permissionDetails[v.Name] = v.ID + } + + err = d.Set("permissions", permissionDetails) + if err != nil { + return fmt.Errorf("Error setting User Token Permission Groups: %s", err) + } + + d.SetId(time.Now().UTC().String()) + return nil +} diff --git a/cloudflare/data_source_api_token_permission_groups_test.go b/cloudflare/data_source_api_token_permission_groups_test.go new file mode 100644 index 0000000000..36a054196d --- /dev/null +++ b/cloudflare/data_source_api_token_permission_groups_test.go @@ -0,0 +1,54 @@ +package cloudflare + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccCloudflareApiTokenPermissionGroups(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCloudflareApiTokenPermissionGroupsConfig, + Check: resource.ComposeTestCheckFunc( + testAccCloudflareApiTokenPermissionGroups("data.cloudflare_api_token_permission_groups.some"), + ), + }, + }, + }) +} + +func testAccCloudflareApiTokenPermissionGroups(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + r := s.RootModule().Resources[n] + a := r.Primary.Attributes + + apiTokenReadId, ok := a["permissions.API Tokens Read"] + if !ok { + return fmt.Errorf("couldn't get 'API Tokens Read' permission ID") + } + + // PermissionGroupsIDs can be found at + // https://developers.cloudflare.com/api/tokens/create/permissions + apiTokenReadIdShouldBe := "0cc3a61731504c89b99ec1be78b77aa" + + if apiTokenReadId != apiTokenReadIdShouldBe { + return fmt.Errorf("ApiTokenPermissionGroups 'API Tokens Read' is '%s', but should be '%s'", + apiTokenReadId, + apiTokenReadIdShouldBe, + ) + } + + return nil + } +} + +const testAccCloudflareApiTokenPermissionGroupsConfig = ` +data "cloudflare_api_token_permission_groups" "some" {} +` diff --git a/cloudflare/provider.go b/cloudflare/provider.go index 0d7711a0ad..44201372ad 100644 --- a/cloudflare/provider.go +++ b/cloudflare/provider.go @@ -94,11 +94,12 @@ func Provider() terraform.ResourceProvider { }, DataSourcesMap: map[string]*schema.Resource{ - "cloudflare_ip_ranges": dataSourceCloudflareIPRanges(), - "cloudflare_waf_groups": dataSourceCloudflareWAFGroups(), - "cloudflare_waf_packages": dataSourceCloudflareWAFPackages(), - "cloudflare_waf_rules": dataSourceCloudflareWAFRules(), - "cloudflare_zones": dataSourceCloudflareZones(), + "cloudflare_api_token_permission_groups": dataSourceCloudflareApiTokenPermissionGroups(), + "cloudflare_ip_ranges": dataSourceCloudflareIPRanges(), + "cloudflare_waf_groups": dataSourceCloudflareWAFGroups(), + "cloudflare_waf_packages": dataSourceCloudflareWAFPackages(), + "cloudflare_waf_rules": dataSourceCloudflareWAFRules(), + "cloudflare_zones": dataSourceCloudflareZones(), }, ResourcesMap: map[string]*schema.Resource{ @@ -109,6 +110,7 @@ func Provider() terraform.ResourceProvider { "cloudflare_access_service_token": resourceCloudflareAccessServiceToken(), "cloudflare_access_identity_provider": resourceCloudflareAccessIdentityProvider(), "cloudflare_account_member": resourceCloudflareAccountMember(), + "cloudflare_api_token": resourceCloudflareApiToken(), "cloudflare_argo": resourceCloudflareArgo(), "cloudflare_authenticated_origin_pulls": resourceCloudflareAuthenticatedOriginPulls(), "cloudflare_authenticated_origin_pulls_certificate": resourceCloudflareAuthenticatedOriginPullsCertificate(), diff --git a/cloudflare/resource_cloudflare_api_token.go b/cloudflare/resource_cloudflare_api_token.go new file mode 100644 index 0000000000..a0292d9890 --- /dev/null +++ b/cloudflare/resource_cloudflare_api_token.go @@ -0,0 +1,264 @@ +package cloudflare + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/cloudflare/cloudflare-go" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceCloudflareApiToken() *schema.Resource { + p := schema.Resource{ + Schema: map[string]*schema.Schema{ + "resources": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "permission_groups": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } + + return &schema.Resource{ + Create: resourceCloudflareApiTokenCreate, + Read: resourceCloudflareApiTokenRead, + Update: resourceCloudflareApiTokenUpdate, + Delete: resourceCloudflareApiTokenDelete, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "policy": { + Type: schema.TypeSet, + Required: true, + Set: schema.HashResource(&p), + Elem: &p, + }, + "request_ip_in": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + "request_ip_not_in": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + "value": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "issued_on": { + Type: schema.TypeString, + Computed: true, + }, + "modified_on": { + Type: schema.TypeString, + Computed: true, + }, + //"not_before": { + // Type: schema.TypeString, + // Computed: true, + //}, + //"expires_on": { + // Type: schema.TypeString, + // Computed: true, + //}, + }, + } +} + +func resourceCloudflareApiTokenCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cloudflare.API) + + //accountID := client.AccountID + name := d.Get("name").(string) + + log.Printf("[INFO] Creating Cloudflare ApiToken: name %s", name) + + t := cloudflare.APIToken{ + Name: name, + Policies: resourceDataToApiTokenPolices(d), + Condition: resourceDataToApiTokenCondition(d), + } + + t, err := client.CreateAPIToken(t) + if err != nil { + return fmt.Errorf("Error creating Cloudflare ApiToken %q: %s", name, err) + } + + d.SetId(t.ID) + d.Set("status", t.Status) + d.Set("issued_on", t.IssuedOn.Format(time.RFC3339Nano)) + d.Set("modified_on", t.ModifiedOn.Format(time.RFC3339Nano)) + //if t.NotBefore != nil { + // d.Set("not_before", t.NotBefore.Format(time.RFC3339Nano)) + //} + //if t.ExpiresOn != nil { + // d.Set("expires_on", t.ExpiresOn.Format(time.RFC3339Nano)) + //} + d.Set("value", t.Value) + + return nil +} + +func resourceDataToApiTokenCondition(d *schema.ResourceData) *cloudflare.APITokenCondition { + ipIn := []string{} + ipNotIn := []string{} + if ips, ok := d.GetOk("request_ip_in"); ok { + ipIn = expandInterfaceToStringList(ips) + } + if ips, ok := d.GetOk("request_ip_not_in"); ok { + ipNotIn = expandInterfaceToStringList(ips) + } + + //if len(ipIn) == 0 && len(ipNotIn) == 0 { + // return nil + //} + + return &cloudflare.APITokenCondition{ + RequestIP: &cloudflare.APITokenRequestIPCondition{ + In: ipIn, + NotIn: ipNotIn, + }, + } +} + +func resourceDataToApiTokenPolices(d *schema.ResourceData) []cloudflare.APITokenPolicies { + policies := d.Get("policy").(*schema.Set).List() + var cfPolicies []cloudflare.APITokenPolicies + + for _, p := range policies { + policy := p.(map[string]interface{}) + + resources := expandInterfaceToStringList(policy["resources"]) + cfResources := map[string]interface{}{} + for _, r := range resources { + cfResources[r] = "*" + } + + permissionGroups := expandInterfaceToStringList(policy["permission_groups"]) + var cfPermissionGroups []cloudflare.APITokenPermissionGroups + for _, pg := range permissionGroups { + cfPermissionGroups = append(cfPermissionGroups, cloudflare.APITokenPermissionGroups{ + ID: pg, + }) + } + + cfPolicies = append(cfPolicies, cloudflare.APITokenPolicies{ + Effect: "allow", + Resources: cfResources, + PermissionGroups: cfPermissionGroups, + }) + } + + return cfPolicies +} + +func resourceCloudflareApiTokenRead(d *schema.ResourceData, meta interface{}) error { + + client := meta.(*cloudflare.API) + tokenID := d.Id() + + t, err := client.GetAPIToken(tokenID) + + log.Printf("[DEBUG] Cloudflare APIToken: %+v", t) + log.Printf("[DEBUG] Cloudflare APIToken error: %#v", err) + + if err != nil { + if strings.Contains(err.Error(), "HTTP status 404") { + log.Printf("[INFO] Cloudflare ApiToken %s no longer exists", d.Id()) + d.SetId("") + return nil + } + return fmt.Errorf("Error finding Cloudflare ApiToken %q: %s", d.Id(), err) + } + + policies := []map[string]interface{}{} + + for _, p := range t.Policies { + resources := []string{} + for k, _ := range p.Resources { + resources = append(resources, k) + } + + permissionGroups := []string{} + for _, v := range p.PermissionGroups { + permissionGroups = append(permissionGroups, v.ID) + } + + policies = append(policies, map[string]interface{}{ + "resources": resources, + "permission_groups": permissionGroups, + }) + } + + d.Set("name", t.Name) + d.Set("policies", policies) + d.Set("status", t.Status) + d.Set("issued_on", t.IssuedOn.Format(time.RFC3339Nano)) + d.Set("modified_on", t.ModifiedOn.Format(time.RFC3339Nano)) + //if t.NotBefore != nil { + // d.Set("not_before", t.NotBefore.Format(time.RFC3339Nano)) + //} + //if t.ExpiresOn != nil { + // d.Set("expires_on", t.ExpiresOn.Format(time.RFC3339Nano)) + //} + + d.Set("request_ip_in", t.Condition.RequestIP.In) + d.Set("request_ip_not_in", t.Condition.RequestIP.NotIn) + + return nil +} + +func resourceCloudflareApiTokenUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cloudflare.API) + + //accountID := client.AccountID + name := d.Get("name").(string) + tokenID := d.Id() + + t := cloudflare.APIToken{ + Name: d.Get("name").(string), + Policies: resourceDataToApiTokenPolices(d), + Condition: resourceDataToApiTokenCondition(d), + } + + log.Printf("[INFO] Updating Cloudflare ApiToken: name %s", name) + + t, err := client.UpdateAPIToken(tokenID, t) + if err != nil { + return fmt.Errorf("Error updating Cloudflare ApiToken %q: %s", name, err) + } + + d.Set("modified_on", t.ModifiedOn.Format(time.RFC3339Nano)) + + return nil +} + +func resourceCloudflareApiTokenDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*cloudflare.API) + tokenID := d.Id() + + log.Printf("[INFO] Deleting Cloudflare APIToken: id %s", tokenID) + + err := client.DeleteAPIToken(tokenID) + if err != nil { + return fmt.Errorf("Error deleting Cloudflare ApiToken: %s", err) + } + + return nil +} diff --git a/cloudflare/resource_cloudflare_api_token_test.go b/cloudflare/resource_cloudflare_api_token_test.go new file mode 100644 index 0000000000..3e206f3e52 --- /dev/null +++ b/cloudflare/resource_cloudflare_api_token_test.go @@ -0,0 +1,66 @@ +package cloudflare + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccAPIToken(t *testing.T) { + rnd := generateRandomResourceName() + resourceID := "cloudflare_api_token." + rnd + zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") + permissionID := "82e64a83756745bbbb1c9c2701bf816b" // DNS read + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAPITokenConfig(rnd, rnd, permissionID, zoneID, true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceID, "name", rnd), + resource.TestCheckResourceAttr(resourceID, "request_ip_in.0", "10.0.0.0/8"), + resource.TestCheckResourceAttr(resourceID, "request_ip_not_in.0", "10.0.255.0/24"), + ), + }, + { + Config: testAPITokenConfig(rnd, rnd, permissionID, zoneID, false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceID, "name", rnd), + resource.TestCheckNoResourceAttr(resourceID, "request_ip_in.0"), + resource.TestCheckNoResourceAttr(resourceID, "request_ip_not_in.0"), + ), + }, + }, + }) +} + +func testAPITokenConfig(resourceID, name, permissionID, zoneID string, ips bool) string { + var ipIn, ipNotIn string + + if ips { + ipIn = `request_ip_in = ["10.0.0.0/8"]` + ipNotIn = `request_ip_not_in = ["10.0.255.0/24"]` + } + + return fmt.Sprintf(` + resource "cloudflare_api_token" "%[1]s" { + name = "%[2]s" + + %[5]s + %[6]s + + policy { + permission_groups = [ + "%[3]s", + ] + resources = [ + "com.cloudflare.api.account.zone.%[4]s", + ] + } + } + `, resourceID, name, permissionID, zoneID, ipIn, ipNotIn) +} diff --git a/go.mod b/go.mod index 5269149978..0b9abeff78 100644 --- a/go.mod +++ b/go.mod @@ -7,5 +7,7 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.1 github.com/hashicorp/terraform-plugin-sdk v1.16.0 github.com/pkg/errors v0.9.1 - golang.org/x/net v0.0.0-20201010224723-4f7140c49acb + golang.org/x/net v0.0.0-20201026091529-146b70c837a4 ) + +replace github.com/cloudflare/cloudflare-go v0.13.4 => github.com/UrosSimovic/cloudflare-go v0.11.2-0.20201105063726-a3eaafdd1160 diff --git a/go.sum b/go.sum index b4614d7485..a936fb6edd 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,7 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/UrosSimovic/cloudflare-go v0.11.2-0.20201105063726-a3eaafdd1160/go.mod h1:tnzgLnTgEGpiXl0yUxklqLVFrXk+X4wxkJKP0sYD8Zc= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= @@ -69,8 +70,6 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.13.4 h1:3Dm3p31K/BxKZhy+Ll2Pf7yStprJtgBKi52ee0NPcAU= -github.com/cloudflare/cloudflare-go v0.13.4/go.mod h1:jGTn0jEGfm8MVoTjBdbVDPHDkLmHdvcVIbYWTklYTvs= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -407,8 +406,8 @@ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb h1:mUVeFHoDKis5nxCAzoAi7E8Ghb86EXh/RK6wtvJIqRY= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201026091529-146b70c837a4 h1:awiuzyrRjJDb+OXi9ceHO3SDxVoN3JER57mhtqkdQBs= +golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= From 8b0c56d0f9ab42aa47872d1903c9c680a5f243b1 Mon Sep 17 00:00:00 2001 From: "uros.simovic" Date: Sun, 15 Nov 2020 09:59:21 +0100 Subject: [PATCH 02/17] remove comments --- cloudflare/resource_cloudflare_api_token.go | 27 --------------------- 1 file changed, 27 deletions(-) diff --git a/cloudflare/resource_cloudflare_api_token.go b/cloudflare/resource_cloudflare_api_token.go index a0292d9890..afdaf14ffe 100644 --- a/cloudflare/resource_cloudflare_api_token.go +++ b/cloudflare/resource_cloudflare_api_token.go @@ -69,14 +69,6 @@ func resourceCloudflareApiToken() *schema.Resource { Type: schema.TypeString, Computed: true, }, - //"not_before": { - // Type: schema.TypeString, - // Computed: true, - //}, - //"expires_on": { - // Type: schema.TypeString, - // Computed: true, - //}, }, } } @@ -84,7 +76,6 @@ func resourceCloudflareApiToken() *schema.Resource { func resourceCloudflareApiTokenCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*cloudflare.API) - //accountID := client.AccountID name := d.Get("name").(string) log.Printf("[INFO] Creating Cloudflare ApiToken: name %s", name) @@ -104,12 +95,6 @@ func resourceCloudflareApiTokenCreate(d *schema.ResourceData, meta interface{}) d.Set("status", t.Status) d.Set("issued_on", t.IssuedOn.Format(time.RFC3339Nano)) d.Set("modified_on", t.ModifiedOn.Format(time.RFC3339Nano)) - //if t.NotBefore != nil { - // d.Set("not_before", t.NotBefore.Format(time.RFC3339Nano)) - //} - //if t.ExpiresOn != nil { - // d.Set("expires_on", t.ExpiresOn.Format(time.RFC3339Nano)) - //} d.Set("value", t.Value) return nil @@ -125,10 +110,6 @@ func resourceDataToApiTokenCondition(d *schema.ResourceData) *cloudflare.APIToke ipNotIn = expandInterfaceToStringList(ips) } - //if len(ipIn) == 0 && len(ipNotIn) == 0 { - // return nil - //} - return &cloudflare.APITokenCondition{ RequestIP: &cloudflare.APITokenRequestIPCondition{ In: ipIn, @@ -211,13 +192,6 @@ func resourceCloudflareApiTokenRead(d *schema.ResourceData, meta interface{}) er d.Set("status", t.Status) d.Set("issued_on", t.IssuedOn.Format(time.RFC3339Nano)) d.Set("modified_on", t.ModifiedOn.Format(time.RFC3339Nano)) - //if t.NotBefore != nil { - // d.Set("not_before", t.NotBefore.Format(time.RFC3339Nano)) - //} - //if t.ExpiresOn != nil { - // d.Set("expires_on", t.ExpiresOn.Format(time.RFC3339Nano)) - //} - d.Set("request_ip_in", t.Condition.RequestIP.In) d.Set("request_ip_not_in", t.Condition.RequestIP.NotIn) @@ -227,7 +201,6 @@ func resourceCloudflareApiTokenRead(d *schema.ResourceData, meta interface{}) er func resourceCloudflareApiTokenUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*cloudflare.API) - //accountID := client.AccountID name := d.Get("name").(string) tokenID := d.Id() From 681beba1755dbca7730bd6b6712c91e02e99f6c8 Mon Sep 17 00:00:00 2001 From: "uros.simovic" Date: Tue, 17 Nov 2020 15:46:14 +0100 Subject: [PATCH 03/17] refactor permission resources --- cloudflare/resource_cloudflare_api_token.go | 47 ++++++++------ .../resource_cloudflare_api_token_test.go | 62 +++++++++++++++++-- 2 files changed, 87 insertions(+), 22 deletions(-) diff --git a/cloudflare/resource_cloudflare_api_token.go b/cloudflare/resource_cloudflare_api_token.go index afdaf14ffe..2d19969fda 100644 --- a/cloudflare/resource_cloudflare_api_token.go +++ b/cloudflare/resource_cloudflare_api_token.go @@ -1,7 +1,9 @@ package cloudflare import ( + "encoding/json" "fmt" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" "log" "strings" "time" @@ -14,7 +16,7 @@ func resourceCloudflareApiToken() *schema.Resource { p := schema.Resource{ Schema: map[string]*schema.Schema{ "resources": { - Type: schema.TypeList, + Type: schema.TypeMap, Required: true, Elem: &schema.Schema{Type: schema.TypeString}, }, @@ -23,6 +25,12 @@ func resourceCloudflareApiToken() *schema.Resource { Required: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "effect": { + Type: schema.TypeString, + Optional: true, + Default: "allow", + ValidateFunc: validation.StringInSlice([]string{"allow", "deny"}, false), + }, }, } @@ -31,15 +39,17 @@ func resourceCloudflareApiToken() *schema.Resource { Read: resourceCloudflareApiTokenRead, Update: resourceCloudflareApiTokenUpdate, Delete: resourceCloudflareApiTokenDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, Required: true, }, "policy": { - Type: schema.TypeSet, - Required: true, - Set: schema.HashResource(&p), + Type: schema.TypeList, + Optional: true, Elem: &p, }, "request_ip_in": { @@ -119,18 +129,12 @@ func resourceDataToApiTokenCondition(d *schema.ResourceData) *cloudflare.APIToke } func resourceDataToApiTokenPolices(d *schema.ResourceData) []cloudflare.APITokenPolicies { - policies := d.Get("policy").(*schema.Set).List() + policies := d.Get("policy").([]interface{}) var cfPolicies []cloudflare.APITokenPolicies for _, p := range policies { policy := p.(map[string]interface{}) - resources := expandInterfaceToStringList(policy["resources"]) - cfResources := map[string]interface{}{} - for _, r := range resources { - cfResources[r] = "*" - } - permissionGroups := expandInterfaceToStringList(policy["permission_groups"]) var cfPermissionGroups []cloudflare.APITokenPermissionGroups for _, pg := range permissionGroups { @@ -139,8 +143,19 @@ func resourceDataToApiTokenPolices(d *schema.ResourceData) []cloudflare.APIToken }) } + cfResources := map[string]interface{}{} + for k, v := range policy["resources"].(map[string]interface{}) { + // value can be object or just a string ("*"), try to convert it to map + obj := map[string]string{} + if err := json.Unmarshal([]byte(v.(string)), &obj); err == nil { + cfResources[k] = obj + } else { + cfResources[k] = v + } + } + cfPolicies = append(cfPolicies, cloudflare.APITokenPolicies{ - Effect: "allow", + Effect: policy["effect"].(string), Resources: cfResources, PermissionGroups: cfPermissionGroups, }) @@ -171,19 +186,15 @@ func resourceCloudflareApiTokenRead(d *schema.ResourceData, meta interface{}) er policies := []map[string]interface{}{} for _, p := range t.Policies { - resources := []string{} - for k, _ := range p.Resources { - resources = append(resources, k) - } - permissionGroups := []string{} for _, v := range p.PermissionGroups { permissionGroups = append(permissionGroups, v.ID) } policies = append(policies, map[string]interface{}{ - "resources": resources, + "resources": p.Resources, "permission_groups": permissionGroups, + "effect": p.Effect, }) } diff --git a/cloudflare/resource_cloudflare_api_token_test.go b/cloudflare/resource_cloudflare_api_token_test.go index 3e206f3e52..6e70b3b3c1 100644 --- a/cloudflare/resource_cloudflare_api_token_test.go +++ b/cloudflare/resource_cloudflare_api_token_test.go @@ -38,6 +38,25 @@ func TestAccAPIToken(t *testing.T) { }) } +func TestAccAPITokenAllowDeny(t *testing.T) { + rnd := generateRandomResourceName() + zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") + permissionID := "82e64a83756745bbbb1c9c2701bf816b" // DNS read + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAPITokenConfigAllowDeny(rnd, rnd, permissionID, zoneID, false), + }, + { + Config: testAPITokenConfigAllowDeny(rnd, rnd, permissionID, zoneID, true), + }, + }, + }) +} + func testAPITokenConfig(resourceID, name, permissionID, zoneID string, ips bool) string { var ipIn, ipNotIn string @@ -54,13 +73,48 @@ func testAPITokenConfig(resourceID, name, permissionID, zoneID string, ips bool) %[6]s policy { + effect = "deny" permission_groups = [ - "%[3]s", - ] - resources = [ - "com.cloudflare.api.account.zone.%[4]s", + "%[3]s", ] + resources = { + "com.cloudflare.api.account.zone.%[4]s" = "*" + } } } `, resourceID, name, permissionID, zoneID, ipIn, ipNotIn) } + +func testAPITokenConfigAllowDeny(resourceID, name, permissionID, zoneID string, allowAllZonesExceptOne bool) string { + var add string + if allowAllZonesExceptOne { + add = fmt.Sprintf(` + policy { + effect = "deny" + permission_groups = [ + "%[1]s", + ] + resources = { + "com.cloudflare.api.account.zone.*" = "*" + } + } + `, permissionID) + } + + return fmt.Sprintf(` + resource "cloudflare_api_token" "%[1]s" { + name = "%[2]s" + + policy { + effect = "allow" + permission_groups = [ + "%[3]s", + ] + resources = { + "com.cloudflare.api.account.zone.%[4]s" = "*" + } + } + %[5]s + } + `, resourceID, name, permissionID, zoneID, add) +} From 126001f1b444b547983ac89f77b2fd49caac8709 Mon Sep 17 00:00:00 2001 From: "uros.simovic" Date: Wed, 18 Nov 2020 10:59:51 +0100 Subject: [PATCH 04/17] go fmt --- cloudflare/resource_cloudflare_api_token.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudflare/resource_cloudflare_api_token.go b/cloudflare/resource_cloudflare_api_token.go index 2d19969fda..a60716c444 100644 --- a/cloudflare/resource_cloudflare_api_token.go +++ b/cloudflare/resource_cloudflare_api_token.go @@ -27,7 +27,7 @@ func resourceCloudflareApiToken() *schema.Resource { }, "effect": { Type: schema.TypeString, - Optional: true, + Optional: true, Default: "allow", ValidateFunc: validation.StringInSlice([]string{"allow", "deny"}, false), }, From 5b71cbf373ad381ec4a8dc025a53a7f3f5516847 Mon Sep 17 00:00:00 2001 From: "uros.simovic" Date: Thu, 19 Nov 2020 11:26:37 +0100 Subject: [PATCH 05/17] add docs --- website/cloudflare.erb | 6 + .../d/api_token_permission_groups.html.md | 29 +++ website/docs/r/api_token.html.markdown | 167 ++++++++++++++++++ 3 files changed, 202 insertions(+) create mode 100644 website/docs/d/api_token_permission_groups.html.md create mode 100644 website/docs/r/api_token.html.markdown diff --git a/website/cloudflare.erb b/website/cloudflare.erb index 77da4d859f..b61b66d43a 100644 --- a/website/cloudflare.erb +++ b/website/cloudflare.erb @@ -22,6 +22,9 @@ > Data Sources