From d82477418f117b48948c60386505760bbfb06641 Mon Sep 17 00:00:00 2001 From: Michele Russo Date: Tue, 26 Nov 2024 09:26:16 +0100 Subject: [PATCH 1/6] Add leaked credential check resource --- .../import.sh | 1 + .../resource.tf | 5 + internal/framework/provider/provider.go | 2 + .../service/leaked_credential_check/model.go | 8 + .../leaked_credential_check/resource.go | 142 ++++++++++++++++++ .../leaked_credential_check/resource_test.go | 85 +++++++++++ .../service/leaked_credential_check/schema.go | 25 +++ 7 files changed, 268 insertions(+) create mode 100644 examples/resources/cloudflare_leaked_credential_check/import.sh create mode 100644 examples/resources/cloudflare_leaked_credential_check/resource.tf create mode 100644 internal/framework/service/leaked_credential_check/model.go create mode 100644 internal/framework/service/leaked_credential_check/resource.go create mode 100644 internal/framework/service/leaked_credential_check/resource_test.go create mode 100644 internal/framework/service/leaked_credential_check/schema.go diff --git a/examples/resources/cloudflare_leaked_credential_check/import.sh b/examples/resources/cloudflare_leaked_credential_check/import.sh new file mode 100644 index 00000000000..aa643e4cc9e --- /dev/null +++ b/examples/resources/cloudflare_leaked_credential_check/import.sh @@ -0,0 +1 @@ +terraform import cloudflare_leaked_credential_check.example \ No newline at end of file diff --git a/examples/resources/cloudflare_leaked_credential_check/resource.tf b/examples/resources/cloudflare_leaked_credential_check/resource.tf new file mode 100644 index 00000000000..bd796d49562 --- /dev/null +++ b/examples/resources/cloudflare_leaked_credential_check/resource.tf @@ -0,0 +1,5 @@ +# Enable the Leaked Credentials Check detection +resource "cloudflare_leaked_credential_check" "example" { + zone_id = "399c6f4950c01a5a141b99ff7fbcbd8b" + enabled = true +} \ No newline at end of file diff --git a/internal/framework/provider/provider.go b/internal/framework/provider/provider.go index 7e87a8d657f..33281e5299f 100644 --- a/internal/framework/provider/provider.go +++ b/internal/framework/provider/provider.go @@ -27,6 +27,7 @@ import ( "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/gateway_categories" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/hyperdrive_config" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/infrastructure_access_target_deprecated" + "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/leaked_credential_check" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/list" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/list_item" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/origin_ca_certificate" @@ -387,6 +388,7 @@ func (p *CloudflareProvider) Resources(ctx context.Context) []func() resource.Re zero_trust_risk_score_integration.NewResource, infrastructure_access_target_deprecated.NewResource, zero_trust_infrastructure_access_target.NewResource, + leaked_credential_check.NewResource, } } diff --git a/internal/framework/service/leaked_credential_check/model.go b/internal/framework/service/leaked_credential_check/model.go new file mode 100644 index 00000000000..131d621c0d3 --- /dev/null +++ b/internal/framework/service/leaked_credential_check/model.go @@ -0,0 +1,8 @@ +package leaked_credential_check + +import "github.com/hashicorp/terraform-plugin-framework/types" + +type LeakedCredentialCheckModel struct { + ZoneID types.String `tfsdk:"zone_id"` + Enabled types.Bool `tfsdk:"enabled"` +} diff --git a/internal/framework/service/leaked_credential_check/resource.go b/internal/framework/service/leaked_credential_check/resource.go new file mode 100644 index 00000000000..92cd2fb7e16 --- /dev/null +++ b/internal/framework/service/leaked_credential_check/resource.go @@ -0,0 +1,142 @@ +package leaked_credential_check + +import ( + "context" + "fmt" + + "github.com/cloudflare/cloudflare-go" + "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/muxclient" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ resource.Resource = &LeakedCredentialCheckResource{} +) + +func NewResource() resource.Resource { + return &LeakedCredentialCheckResource{} +} + +type LeakedCredentialCheckResource struct { + client *muxclient.Client +} + +func (r *LeakedCredentialCheckResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_leaked_credential_check" +} + +func (r *LeakedCredentialCheckResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*muxclient.Client) + + if !ok { + resp.Diagnostics.AddError( + "unexpected resource configure type", + fmt.Sprintf("Expected *muxclient.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *LeakedCredentialCheckResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan LeakedCredentialCheckModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + zoneID := plan.ZoneID.ValueString() + createParams := cloudflare.LeakCredentialCheckSetStatusParams{ + Enabled: plan.Enabled.ValueBoolPointer(), + } + + _, err := r.client.V1.LeakedCredentialCheckSetStatus(ctx, cloudflare.ZoneIdentifier(zoneID), createParams) + if err != nil { + resp.Diagnostics.AddError("Error enabling (Create) Leaked Credential Check", err.Error()) + return + } + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +func (r *LeakedCredentialCheckResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state LeakedCredentialCheckModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + zoneID := state.ZoneID.ValueString() + status, err := r.client.V1.LeakedCredentialCheckGetStatus(ctx, cloudflare.ZoneIdentifier(zoneID), cloudflare.LeakedCredentialCheckGetStatusParams{}) + if err != nil { + resp.Diagnostics.AddError("Error reading Leaked Credential Check status", err.Error()) + return + } + state.Enabled = types.BoolPointerValue(status.Enabled) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +func (r *LeakedCredentialCheckResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan LeakedCredentialCheckModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + zoneID := plan.ZoneID.ValueString() + updateParams := cloudflare.LeakCredentialCheckSetStatusParams{ + Enabled: plan.Enabled.ValueBoolPointer(), + } + + _, err := r.client.V1.LeakedCredentialCheckSetStatus(ctx, cloudflare.ZoneIdentifier(zoneID), updateParams) + if err != nil { + resp.Diagnostics.AddError("Error updating status of Leaked Credential Check", err.Error()) + return + } + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +// Delete disables (enabled: false) the Leaked Credential Check functionality +func (r *LeakedCredentialCheckResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state LeakedCredentialCheckModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + zoneID := state.ZoneID.ValueString() + params := cloudflare.LeakCredentialCheckSetStatusParams{ + Enabled: cloudflare.BoolPtr(false), + } + + _, err := r.client.V1.LeakedCredentialCheckSetStatus(ctx, cloudflare.ZoneIdentifier(zoneID), params) + if err != nil { + resp.Diagnostics.AddError("Error updating status of Leaked Credential Check", err.Error()) + return + } +} + +func (r *LeakedCredentialCheckResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + // req.ID is the zoneID for which you want to read the state of the + // Leaked Credential Check feature + cloudflare.ZoneIdentifier("asd") + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("zone_id"), req.ID)...) +} diff --git a/internal/framework/service/leaked_credential_check/resource_test.go b/internal/framework/service/leaked_credential_check/resource_test.go new file mode 100644 index 00000000000..1f0a710a9b2 --- /dev/null +++ b/internal/framework/service/leaked_credential_check/resource_test.go @@ -0,0 +1,85 @@ +package leaked_credential_check_test + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "testing" + + cfv1 "github.com/cloudflare/cloudflare-go" + "github.com/cloudflare/terraform-provider-cloudflare/internal/acctest" + "github.com/cloudflare/terraform-provider-cloudflare/internal/utils" + + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func init() { + resource.AddTestSweepers("cloudflare_leaked_credential_check", &resource.Sweeper{ + Name: "cloudflare_leaked_credential_check", + F: testSweepCloudflareLCC, + }) +} + +func testSweepCloudflareLCC(r string) error { + ctx := context.Background() + client, clientErr := acctest.SharedV1Client() + if clientErr != nil { + tflog.Error(ctx, fmt.Sprintf("Failed to create Cloudflare client: %s", clientErr)) + } + + zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") + if zoneID == "" { + return errors.New("CLOUDFLARE_ZONE_ID must be set") + } + + status, err := client.LeakedCredentialCheckGetStatus(ctx, cfv1.ZoneIdentifier(zoneID), cfv1.LeakedCredentialCheckGetStatusParams{}) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Failed to GET Leaked Credentials status: %s", err)) + } + + if *status.Enabled == false { + log.Print("[DEBUG] LCC already disabled") + return nil + } + _, err = client.LeakedCredentialCheckSetStatus(ctx, cfv1.ZoneIdentifier(zoneID), cfv1.LeakCredentialCheckSetStatusParams{Enabled: cfv1.BoolPtr(false)}) + + return nil +} + +func TestAccCloudflareLeakedCredentialCheck_CRUD(t *testing.T) { + rnd := utils.GenerateRandomResourceName() + name := fmt.Sprintf("cloudflare_leaked_credential_check.%s", rnd) + zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.TestAccPreCheck(t) + }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccLCCSimple(rnd, zoneID, "true"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "enabled", "true"), + ), + }, + { + Config: testAccLCCSimple(rnd, zoneID, "false"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "enabled", "false"), + ), + }, + }, + }) +} + +func testAccLCCSimple(ID, zoneID, enabled string) string { + return fmt.Sprintf(` + resource "cloudflare_leaked_credential_check" "%[1]s" { + zone_id = "%[2]s" + enabled = "%[3]s" + }`, ID, zoneID, enabled) +} diff --git a/internal/framework/service/leaked_credential_check/schema.go b/internal/framework/service/leaked_credential_check/schema.go new file mode 100644 index 00000000000..aed205321ad --- /dev/null +++ b/internal/framework/service/leaked_credential_check/schema.go @@ -0,0 +1,25 @@ +package leaked_credential_check + +import ( + "context" + + "github.com/cloudflare/terraform-provider-cloudflare/internal/consts" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +func (r *LeakedCredentialCheckResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Provides a Cloudflare Leaked Credential Check resource to be used for managing the status of the Cloudflare Leaked Credential detection within a specific zone.", + Attributes: map[string]schema.Attribute{ + consts.ZoneIDSchemaKey: schema.StringAttribute{ + Description: consts.ZoneIDSchemaDescription, + Required: true, + }, + "enabled": schema.BoolAttribute{ + Description: "State of the Leaked Credential Check detection", + Required: true, + }, + }, + } +} From 1ce25d4966f485c5defa9cff4f62d1974e1c7253 Mon Sep 17 00:00:00 2001 From: Michele Russo Date: Tue, 26 Nov 2024 09:42:33 +0100 Subject: [PATCH 2/6] Clean up --- internal/framework/service/leaked_credential_check/resource.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/framework/service/leaked_credential_check/resource.go b/internal/framework/service/leaked_credential_check/resource.go index 92cd2fb7e16..2686f7a7802 100644 --- a/internal/framework/service/leaked_credential_check/resource.go +++ b/internal/framework/service/leaked_credential_check/resource.go @@ -135,8 +135,7 @@ func (r *LeakedCredentialCheckResource) Delete(ctx context.Context, req resource } func (r *LeakedCredentialCheckResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - // req.ID is the zoneID for which you want to read the state of the + // req.ID is the zoneID for which you want to import the state of the // Leaked Credential Check feature - cloudflare.ZoneIdentifier("asd") resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("zone_id"), req.ID)...) } From 8ce9d67843acf377d6e2b08de9b9d75754a59954 Mon Sep 17 00:00:00 2001 From: Michele Russo Date: Thu, 28 Nov 2024 15:21:14 +0100 Subject: [PATCH 3/6] Add error check in sweeper --- .../service/leaked_credential_check/resource_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/framework/service/leaked_credential_check/resource_test.go b/internal/framework/service/leaked_credential_check/resource_test.go index 1f0a710a9b2..c1b4da75e90 100644 --- a/internal/framework/service/leaked_credential_check/resource_test.go +++ b/internal/framework/service/leaked_credential_check/resource_test.go @@ -37,7 +37,7 @@ func testSweepCloudflareLCC(r string) error { status, err := client.LeakedCredentialCheckGetStatus(ctx, cfv1.ZoneIdentifier(zoneID), cfv1.LeakedCredentialCheckGetStatusParams{}) if err != nil { - tflog.Error(ctx, fmt.Sprintf("Failed to GET Leaked Credentials status: %s", err)) + tflog.Error(ctx, fmt.Sprintf("Failed to GET Leaked Credential status: %s", err)) } if *status.Enabled == false { @@ -45,6 +45,10 @@ func testSweepCloudflareLCC(r string) error { return nil } _, err = client.LeakedCredentialCheckSetStatus(ctx, cfv1.ZoneIdentifier(zoneID), cfv1.LeakCredentialCheckSetStatusParams{Enabled: cfv1.BoolPtr(false)}) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Failed to disable Leaked Credential Check: %s", err)) + return err + } return nil } From 4945dfdd49e49c8bd3a6b2bac14c0ee8c0de63f3 Mon Sep 17 00:00:00 2001 From: Michele Russo Date: Thu, 28 Nov 2024 17:30:10 +0100 Subject: [PATCH 4/6] `make docs` --- docs/resources/leaked_credential_check.md | 35 +++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 docs/resources/leaked_credential_check.md diff --git a/docs/resources/leaked_credential_check.md b/docs/resources/leaked_credential_check.md new file mode 100644 index 00000000000..e96149539ef --- /dev/null +++ b/docs/resources/leaked_credential_check.md @@ -0,0 +1,35 @@ +--- +page_title: "cloudflare_leaked_credential_check Resource - Cloudflare" +subcategory: "" +description: |- + Provides a Cloudflare Leaked Credential Check resource to be used for managing the status of the Cloudflare Leaked Credential detection within a specific zone. +--- + +# cloudflare_leaked_credential_check (Resource) + +Provides a Cloudflare Leaked Credential Check resource to be used for managing the status of the Cloudflare Leaked Credential detection within a specific zone. + +## Example Usage + +```terraform +# Enable the Leaked Credentials Check detection +resource "cloudflare_leaked_credential_check" "example" { + zone_id = "399c6f4950c01a5a141b99ff7fbcbd8b" + enabled = true +} +``` + +## Schema + +### Required + +- `enabled` (Boolean) State of the Leaked Credential Check detection +- `zone_id` (String) The zone identifier to target for the resource. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import cloudflare_leaked_credential_check.example +``` From fac77382b54c0d4d6cefab62471aa5630d27ef6e Mon Sep 17 00:00:00 2001 From: Michele Russo Date: Thu, 28 Nov 2024 17:53:23 +0100 Subject: [PATCH 5/6] Add changelog entry --- .changelog/4674.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/4674.txt diff --git a/.changelog/4674.txt b/.changelog/4674.txt new file mode 100644 index 00000000000..ee94251181d --- /dev/null +++ b/.changelog/4674.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +cloudflare_leaked_credential_check +``` \ No newline at end of file From 5227ac7039b1c8f6c60090c92f9d8def52c56794 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Fri, 29 Nov 2024 10:56:32 +1100 Subject: [PATCH 6/6] update test method name --- .../framework/service/leaked_credential_check/resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/framework/service/leaked_credential_check/resource_test.go b/internal/framework/service/leaked_credential_check/resource_test.go index c1b4da75e90..ec642cc8622 100644 --- a/internal/framework/service/leaked_credential_check/resource_test.go +++ b/internal/framework/service/leaked_credential_check/resource_test.go @@ -53,7 +53,7 @@ func testSweepCloudflareLCC(r string) error { return nil } -func TestAccCloudflareLeakedCredentialCheck_CRUD(t *testing.T) { +func TestAccCloudflareLeakedCredentialCheck_Basic(t *testing.T) { rnd := utils.GenerateRandomResourceName() name := fmt.Sprintf("cloudflare_leaked_credential_check.%s", rnd) zoneID := os.Getenv("CLOUDFLARE_ZONE_ID")