From 196168ec803196c8de5e2c3308115368505ee18d Mon Sep 17 00:00:00 2001 From: Michele Russo Date: Thu, 28 Nov 2024 18:31:27 +0100 Subject: [PATCH 01/11] Add leaked credential check rules resource --- .../leaked_credential_check_rules/model.go | 14 + .../leaked_credential_check_rules/resource.go | 230 +++++++++++++++ .../resource_test.go | 262 ++++++++++++++++++ .../leaked_credential_check_rules/schema.go | 42 +++ 4 files changed, 548 insertions(+) create mode 100644 internal/framework/service/leaked_credential_check_rules/model.go create mode 100644 internal/framework/service/leaked_credential_check_rules/resource.go create mode 100644 internal/framework/service/leaked_credential_check_rules/resource_test.go create mode 100644 internal/framework/service/leaked_credential_check_rules/schema.go diff --git a/internal/framework/service/leaked_credential_check_rules/model.go b/internal/framework/service/leaked_credential_check_rules/model.go new file mode 100644 index 0000000000..316b8ad51c --- /dev/null +++ b/internal/framework/service/leaked_credential_check_rules/model.go @@ -0,0 +1,14 @@ +package leaked_credential_check_rules + +import "github.com/hashicorp/terraform-plugin-framework/types" + +type LeakedCredentialCheckRulesModel struct { + ZoneID types.String `tfsdk:"zone_id"` + Rules []LCCRuleValueModel `tfsdk:"rule"` +} + +type LCCRuleValueModel struct { + ID types.String `tfsdk:"id"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` +} diff --git a/internal/framework/service/leaked_credential_check_rules/resource.go b/internal/framework/service/leaked_credential_check_rules/resource.go new file mode 100644 index 0000000000..ac34bc7362 --- /dev/null +++ b/internal/framework/service/leaked_credential_check_rules/resource.go @@ -0,0 +1,230 @@ +package leaked_credential_check_rules + +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 = &LeakedCredentialCheckRulesResource{} + _ resource.ResourceWithImportState = &LeakedCredentialCheckRulesResource{} +) + +func NewResource() resource.Resource { + return &LeakedCredentialCheckRulesResource{} +} + +type LeakedCredentialCheckRulesResource struct { + client *muxclient.Client +} + +func (r *LeakedCredentialCheckRulesResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_leaked_credential_check_rules" +} + +func (r *LeakedCredentialCheckRulesResource) 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 *LeakedCredentialCheckRulesResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan LeakedCredentialCheckRulesModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + zoneID := plan.ZoneID.ValueString() + var createdRules []LCCRuleValueModel + if len(plan.Rules) > 0 { + for _, rule := range plan.Rules { + createParam := cloudflare.LeakedCredentialCheckCreateDetectionParams{ + Username: rule.Username.ValueString(), + Password: rule.Password.ValueString(), + } + detection, err := r.client.V1.LeakedCredentialCheckCreateDetection(ctx, cloudflare.ZoneIdentifier(zoneID), createParam) + if err != nil { + resp.Diagnostics.AddError("Error creating a user-defined detection patter for Leaked Credential Check", err.Error()) + return + } + createdRules = append(createdRules, LCCRuleValueModel{ID: types.StringValue(detection.ID), Username: rule.Username, Password: rule.Password}) + } + + } + plan.Rules = createdRules + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +func (r *LeakedCredentialCheckRulesResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state LeakedCredentialCheckRulesModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + zoneID := state.ZoneID.ValueString() + rules, err := r.client.V1.LeakedCredentialCheckListDetections(ctx, cloudflare.ZoneIdentifier(zoneID), cloudflare.LeakedCredentialCheckListDetectionsParams{}) + if err != nil { + resp.Diagnostics.AddError("Error listing Leaked Credential Check user-defined detection patterns", err.Error()) + return + } + var readRules []LCCRuleValueModel + for _, rule := range rules { + readRules = append(readRules, buildLCCRuleValueModel(rule)) + } + state.Rules = readRules + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +func (r *LeakedCredentialCheckRulesResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan LeakedCredentialCheckRulesModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + zoneID := cloudflare.ZoneIdentifier(plan.ZoneID.ValueString()) + // fetch existing rules from API + existing_rules, err := r.client.V1.LeakedCredentialCheckListDetections(ctx, zoneID, cloudflare.LeakedCredentialCheckListDetectionsParams{}) + if err != nil { + resp.Diagnostics.AddError("Error fetching Leaked Credential Check user-defined detection patterns", err.Error()) + return + } + // compare and create/delete accordingly + var createdRules []LCCRuleValueModel + toAdd, toRemove, toKeep := diffRules(plan.Rules, existing_rules) + // create + for _, createParam := range toAdd { + detection, err := r.client.V1.LeakedCredentialCheckCreateDetection(ctx, zoneID, createParam) + if err != nil { + resp.Diagnostics.AddError("Error creating a user-defined detection patter for Leaked Credential Check", err.Error()) + return + } + createdRules = append(createdRules, buildLCCRuleValueModel(detection)) + } + plan.Rules = createdRules // update plan rules with the newly created rules + // delete + for _, deleteParam := range toRemove { + _, err := r.client.V1.LeakedCredentialCheckDeleteDetection(ctx, zoneID, deleteParam) + if err != nil { + resp.Diagnostics.AddError("Error deleting a user-defined detection patter for Leaked Credential Check", err.Error()) + return + } + } + // add the existing rules we kept to the plan, if any + for _, kr := range toKeep { + plan.Rules = append(plan.Rules, buildLCCRuleValueModel(kr)) + } + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) +} + +// Delete all user-defined detection rules +func (r *LeakedCredentialCheckRulesResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state LeakedCredentialCheckRulesModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + zoneID := cloudflare.ZoneIdentifier(state.ZoneID.ValueString()) + // fetch existing rules from API + rules, err := r.client.V1.LeakedCredentialCheckListDetections(ctx, zoneID, cloudflare.LeakedCredentialCheckListDetectionsParams{}) + if err != nil { + resp.Diagnostics.AddError("Error fetching Leaked Credential Check user-defined detection patterns", err.Error()) + return + } + for _, rule := range rules { + deleteParam := cloudflare.LeakedCredentialCheckDeleteDetectionParams{DetectionID: rule.ID} + _, err := r.client.V1.LeakedCredentialCheckDeleteDetection(ctx, zoneID, deleteParam) + if err != nil { + resp.Diagnostics.AddError("Error deleting a user-defined detection patter for Leaked Credential Check", err.Error()) + return + } + + } + +} + +func (r *LeakedCredentialCheckRulesResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + // req.ID is the zoneID for which you want to import the state of the + // Leaked Credential Check Rules + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("zone_id"), req.ID)...) + return +} + +func diffRules(desired []LCCRuleValueModel, current []cloudflare.LeakedCredentialCheckDetectionEntry) (toAdd []cloudflare.LeakedCredentialCheckCreateDetectionParams, toRemove []cloudflare.LeakedCredentialCheckDeleteDetectionParams, toKeep []cloudflare.LeakedCredentialCheckDetectionEntry) { + // Create a map for the desired rules + desiredMap := make(map[string]struct{}) + for _, d := range desired { + key := d.Username.ValueString() + ":" + d.Password.ValueString() // Use a unique key for comparison + desiredMap[key] = struct{}{} + } + + // Create a map for the current rules + currentMap := make(map[string]struct{}) + for _, c := range current { + key := c.Username + ":" + c.Password + currentMap[key] = struct{}{} + + // If a rule exists in current but not in desired, mark it for removal + if _, exists := desiredMap[key]; !exists { + toRemove = append(toRemove, cloudflare.LeakedCredentialCheckDeleteDetectionParams{DetectionID: c.ID}) + } else { + // If a rule exists both in current and desired, mark it as to keep + toKeep = append( + toKeep, + cloudflare.LeakedCredentialCheckDetectionEntry{ID: c.ID, Username: c.Username, Password: c.Password}, + ) + } + } + + // Find rules that exist in desired but not in current + for _, d := range desired { + key := d.Username.ValueString() + ":" + d.Password.ValueString() + if _, exists := currentMap[key]; !exists { + toAdd = append(toAdd, cloudflare.LeakedCredentialCheckCreateDetectionParams{ + Username: d.Username.ValueString(), + Password: d.Password.ValueString(), + }) + } + } + + return +} + +func buildLCCRuleValueModel(lccEntry cloudflare.LeakedCredentialCheckDetectionEntry) LCCRuleValueModel { + rule := LCCRuleValueModel{ + ID: types.StringValue(lccEntry.ID), + Username: types.StringValue(lccEntry.Username), + Password: types.StringValue(lccEntry.Password), + } + return rule +} diff --git a/internal/framework/service/leaked_credential_check_rules/resource_test.go b/internal/framework/service/leaked_credential_check_rules/resource_test.go new file mode 100644 index 0000000000..781b04ce10 --- /dev/null +++ b/internal/framework/service/leaked_credential_check_rules/resource_test.go @@ -0,0 +1,262 @@ +package leaked_credential_check_rules_test + +import ( + "context" + "errors" + "fmt" + "os" + "regexp" + "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" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func init() { + resource.AddTestSweepers("cloudflare_leaked_credential_check_rules", &resource.Sweeper{ + Name: "cloudflare_leaked_credential_check_rules", + F: testSweepCloudflareLCCRules, + }) +} + +func testSweepCloudflareLCCRules(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") + } + // fetch existing rules from API + rules, err := client.LeakedCredentialCheckListDetections(ctx, cfv1.ZoneIdentifier(zoneID), cfv1.LeakedCredentialCheckListDetectionsParams{}) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Error fetching Leaked Credential Check user-defined detection patterns: %s", err)) + return err + } + for _, rule := range rules { + deleteParam := cfv1.LeakedCredentialCheckDeleteDetectionParams{DetectionID: rule.ID} + _, err := client.LeakedCredentialCheckDeleteDetection(ctx, cfv1.ZoneIdentifier(zoneID), deleteParam) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Error deleting a user-defined detection patter for Leaked Credential Check: %s", err)) + } + + } + return nil +} + +func TestAccCloudflareLeakedCredentialCheckRules_CRUD(t *testing.T) { + rnd := utils.GenerateRandomResourceName() + name := fmt.Sprintf("cloudflare_leaked_credential_check_rules.%s", rnd) + zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") + + var detectionRules []cfv1.LeakedCredentialCheckDetectionEntry + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.TestAccPreCheck(t) + }, + ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccConfigAddHeader(rnd, zoneID, testAccLCCTwoSimpleRules(rnd)), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "zone_id", zoneID), + resource.TestCheckResourceAttr(name, "rule.#", "2"), + resource.TestCheckResourceAttr(name, "rule.0.%", "3"), + resource.TestCheckResourceAttr(name, "rule.1.%", "3"), + resource.TestMatchTypeSetElemNestedAttrs( + name, + "rule.*", + map[string]*regexp.Regexp{ + "id": regexp.MustCompile(`^[a-z0-9]+$`), + "username": regexp.MustCompile(`"id"`), + "password": regexp.MustCompile(`"secret"`), + }, + ), + resource.TestMatchTypeSetElemNestedAttrs( + name, + "rule.*", + map[string]*regexp.Regexp{ + "id": regexp.MustCompile(`^[a-z0-9]+$`), + "username": regexp.MustCompile(`"user"`), + "password": regexp.MustCompile(`"pass"`), + }, + ), + testAccCheckLCCNumRules(name, 2, &detectionRules), + ), + }, + { // remove one rule, keep one, and add a new one + Config: testAccConfigAddHeader(rnd, zoneID, testAccLCCSimpleChangeOneRule(rnd)), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "zone_id", zoneID), + resource.TestCheckResourceAttr(name, "rule.#", "2"), + resource.TestCheckResourceAttr(name, "rule.0.%", "3"), + resource.TestCheckResourceAttr(name, "rule.1.%", "3"), + resource.TestMatchTypeSetElemNestedAttrs( + name, + "rule.*", + map[string]*regexp.Regexp{ + "id": regexp.MustCompile(`^[a-z0-9]+$`), + "username": regexp.MustCompile(`"name"`), + "password": regexp.MustCompile(`"key"`), + }, + ), + testAccCheckLCCOneRuleChange(name, 2, &detectionRules), + ), + }, + { // clear rules + Config: testAccConfigAddHeader(rnd, zoneID, testAccLCCNoRules(rnd)), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "zone_id", zoneID), + resource.TestCheckResourceAttr(name, "rule.#", "0"), + testAccCheckLCCNumRules(name, 0, &detectionRules), + ), + }, + { // add a single rule + Config: testAccConfigAddHeader(rnd, zoneID, testAccLCCOneSimpleRule(rnd)), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name, "zone_id", zoneID), + resource.TestCheckResourceAttr(name, "rule.#", "1"), + resource.TestCheckResourceAttr(name, "rule.0.%", "3"), + resource.TestCheckResourceAttr(name, "rule.0.username", "lookup_json_string(http.request.body.raw, \"username\")"), + resource.TestCheckResourceAttr(name, "rule.0.password", "lookup_json_string(http.request.body.raw, \"password\")"), + testAccCheckLCCNumRules(name, 1, &detectionRules), + ), + }, + }, + }) +} + +func testAccConfigAddHeader(name, zoneID, config string) string { + header := fmt.Sprintf(` + resource "cloudflare_leaked_credential_check" "%[1]s" { + zone_id = "%[2]s" + enabled = true + }`, name, zoneID) + return header + "\n" + config +} + +func testAccLCCTwoSimpleRules(name string) string { + return fmt.Sprintf(` + resource "cloudflare_leaked_credential_check_rules" "%[1]s" { + zone_id = cloudflare_leaked_credential_check.%[1]s.zone_id + rule { + username = "lookup_json_string(http.request.body.raw, \"user\")" + password = "lookup_json_string(http.request.body.raw, \"pass\")" + } + rule { + username = "lookup_json_string(http.request.body.raw, \"id\")" + password = "lookup_json_string(http.request.body.raw, \"secret\")" + } + }`, name) +} + +func testAccLCCSimpleChangeOneRule(name string) string { + return fmt.Sprintf(` + resource "cloudflare_leaked_credential_check_rules" "%[1]s" { + zone_id = cloudflare_leaked_credential_check.%[1]s.zone_id + rule { + username = "lookup_json_string(http.request.body.raw, \"name\")" + password = "lookup_json_string(http.request.body.raw, \"key\")" + } + rule { + username = "lookup_json_string(http.request.body.raw, \"id\")" + password = "lookup_json_string(http.request.body.raw, \"secret\")" + } + }`, name) +} + +func testAccLCCNoRules(name string) string { + return fmt.Sprintf(` + resource "cloudflare_leaked_credential_check_rules" "%[1]s" { + zone_id = cloudflare_leaked_credential_check.%[1]s.zone_id + }`, name) +} + +func testAccLCCOneSimpleRule(name string) string { + return fmt.Sprintf(` + resource "cloudflare_leaked_credential_check_rules" "%[1]s" { + zone_id = cloudflare_leaked_credential_check.%[1]s.zone_id + rule { + username = "lookup_json_string(http.request.body.raw, \"username\")" + password = "lookup_json_string(http.request.body.raw, \"password\")" + } + }`, name) +} + +func testAccCheckLCCNumRules(name string, length int, rules *[]cfv1.LeakedCredentialCheckDetectionEntry) resource.TestCheckFunc { + return func(s *terraform.State) error { + zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") + _, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("not found: %s", name) + } + client, _ := acctest.SharedV1Client() + detections, err := client.LeakedCredentialCheckListDetections( + context.Background(), + cfv1.ZoneIdentifier(zoneID), + cfv1.LeakedCredentialCheckListDetectionsParams{}, + ) + if err != nil { + return err + } + + if length != len(detections) { + return fmt.Errorf("Expected num of rules (%d) does not match the actual num (%d)", length, len(detections)) + } + *rules = detections + + return nil + } +} + +func testAccCheckLCCOneRuleChange(name string, length int, rules *[]cfv1.LeakedCredentialCheckDetectionEntry) resource.TestCheckFunc { + return func(s *terraform.State) error { + zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") + _, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("not found: %s", name) + } + client, _ := acctest.SharedV1Client() + detections, err := client.LeakedCredentialCheckListDetections( + context.Background(), + cfv1.ZoneIdentifier(zoneID), + cfv1.LeakedCredentialCheckListDetectionsParams{}, + ) + if err != nil { + return err + } + + if length != len(detections) { + return fmt.Errorf("Expected num of rules (%d) does not match the actual num (%d)", length, len(detections)) + } + previousRulesMap := make(map[string]string) + for _, prul := range *rules { + mapkey := prul.Password + "|" + prul.Username + previousRulesMap[mapkey] = prul.ID + } + + found := false + for _, det := range detections { + mapkey := det.Password + "|" + det.Username + if _, exists := previousRulesMap[mapkey]; exists { + if previousRulesMap[mapkey] != det.ID { + return errors.New("Found the unchanged rule but the ID is different") + } + found = true + } + } + if !found { + return fmt.Errorf("Could not find the rule that was not changed!") + } + return nil + } +} diff --git a/internal/framework/service/leaked_credential_check_rules/schema.go b/internal/framework/service/leaked_credential_check_rules/schema.go new file mode 100644 index 0000000000..41e19ddba9 --- /dev/null +++ b/internal/framework/service/leaked_credential_check_rules/schema.go @@ -0,0 +1,42 @@ +package leaked_credential_check_rules + +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 *LeakedCredentialCheckRulesResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Provides a Cloudflare Leaked Credential Check Rules resource for managing user-defined Leaked Credential detection patterns within a specific zone.", + Attributes: map[string]schema.Attribute{ + consts.ZoneIDSchemaKey: schema.StringAttribute{ + Description: consts.ZoneIDSchemaDescription, + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "rule": schema.SetNestedBlock{ + Description: "List of user-defined patterns for Leaked Credential Check", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + consts.IDSchemaKey: schema.StringAttribute{ + Description: consts.IDSchemaDescription, + Computed: true, + }, + "username": schema.StringAttribute{ + Description: "The ruleset expression to use in matching the username in a request.", + Required: true, + }, + "password": schema.StringAttribute{ + Description: "The ruleset expression to use in matching the password in a request", + Required: true, + }, + }, + }, + }, + }, + } +} From 5ea4dd5a6bea7296a8acebd9472731d2398e8aaa Mon Sep 17 00:00:00 2001 From: Michele Russo Date: Thu, 28 Nov 2024 18:31:56 +0100 Subject: [PATCH 02/11] Add resource documentation material --- .../import.sh | 1 + .../resource.tf | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 examples/resources/cloudflare_leaked_credential_check_rules/import.sh create mode 100644 examples/resources/cloudflare_leaked_credential_check_rules/resource.tf diff --git a/examples/resources/cloudflare_leaked_credential_check_rules/import.sh b/examples/resources/cloudflare_leaked_credential_check_rules/import.sh new file mode 100644 index 0000000000..9b2836a260 --- /dev/null +++ b/examples/resources/cloudflare_leaked_credential_check_rules/import.sh @@ -0,0 +1 @@ +terraform import cloudflare_leaked_credential_check_rules.example \ No newline at end of file diff --git a/examples/resources/cloudflare_leaked_credential_check_rules/resource.tf b/examples/resources/cloudflare_leaked_credential_check_rules/resource.tf new file mode 100644 index 0000000000..07b8386414 --- /dev/null +++ b/examples/resources/cloudflare_leaked_credential_check_rules/resource.tf @@ -0,0 +1,23 @@ +# Enable the Leaked Credentials Check detection +resource "cloudflare_leaked_credential_check" "example" { + zone_id = "399c6f4950c01a5a141b99ff7fbcbd8b" + enabled = true +} + +# Create user-defined detection patterns for Leaked Credentials Check +resource "cloudflare_leaked_credential_check_rules" "example" { + # The following line ensures that rules are created only for a zone + # where the Leaked Credentials Check module is enabled, and that the + # module is activated before user-defined detection patterns are created. + zone_id = cloudflare_leaked_credential_check.example.zone_id + + rule { + username = "lookup_json_string(http.request.body.raw, \"user\")" + password = "lookup_json_string(http.request.body.raw, \"pass\")" + } + + rule { + username = "lookup_json_string(http.request.body.raw, \"id\")" + password = "lookup_json_string(http.request.body.raw, \"secret\")" + } +} \ No newline at end of file From 66e07d8e0ec9c1b98f3de1e88c981a866f10361d Mon Sep 17 00:00:00 2001 From: Michele Russo Date: Thu, 28 Nov 2024 18:32:17 +0100 Subject: [PATCH 03/11] Add new resource to provider --- internal/framework/provider/provider.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/framework/provider/provider.go b/internal/framework/provider/provider.go index 33281e5299..aa7c167031 100644 --- a/internal/framework/provider/provider.go +++ b/internal/framework/provider/provider.go @@ -28,6 +28,7 @@ import ( "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/leaked_credential_check_rules" "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" @@ -388,7 +389,11 @@ 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, +<<<<<<< HEAD leaked_credential_check.NewResource, +======= + leaked_credential_check_rules.NewResource, +>>>>>>> 9769fdf5b (Add new resource to provider) } } From 064a25834951562b68449b66f58fa137162d19d5 Mon Sep 17 00:00:00 2001 From: Michele Russo Date: Thu, 28 Nov 2024 18:32:29 +0100 Subject: [PATCH 04/11] `make docs` --- .../leaked_credential_check_rules.md | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 docs/resources/leaked_credential_check_rules.md diff --git a/docs/resources/leaked_credential_check_rules.md b/docs/resources/leaked_credential_check_rules.md new file mode 100644 index 0000000000..eae1dc3adb --- /dev/null +++ b/docs/resources/leaked_credential_check_rules.md @@ -0,0 +1,68 @@ +--- +page_title: "cloudflare_leaked_credential_check_rules Resource - Cloudflare" +subcategory: "" +description: |- + Provides a Cloudflare Leaked Credential Check Rules resource for managing user-defined Leaked Credential detection patterns within a specific zone. +--- + +# cloudflare_leaked_credential_check_rules (Resource) + +Provides a Cloudflare Leaked Credential Check Rules resource for managing user-defined Leaked Credential detection patterns within a specific zone. + +## Example Usage + +```terraform +# Enable the Leaked Credentials Check detection +resource "cloudflare_leaked_credential_check" "example" { + zone_id = "399c6f4950c01a5a141b99ff7fbcbd8b" + enabled = true +} + +# Create user-defined detection patterns for Leaked Credentials Check +resource "cloudflare_leaked_credential_check_rules" "example" { + # The following line ensures that rules are created only for a zone + # where the Leaked Credentials Check module is enabled, and that the + # module is activated before user-defined detection patterns are created. + zone_id = cloudflare_leaked_credential_check.example.zone_id + + rule { + username = "lookup_json_string(http.request.body.raw, \"user\")" + password = "lookup_json_string(http.request.body.raw, \"pass\")" + } + + rule { + username = "lookup_json_string(http.request.body.raw, \"id\")" + password = "lookup_json_string(http.request.body.raw, \"secret\")" + } +} +``` + +## Schema + +### Required + +- `zone_id` (String) The zone identifier to target for the resource. + +### Optional + +- `rule` (Block Set) List of user-defined patterns for Leaked Credential Check (see [below for nested schema](#nestedblock--rule)) + + +### Nested Schema for `rule` + +Required: + +- `password` (String) The ruleset expression to use in matching the password in a request +- `username` (String) The ruleset expression to use in matching the username in a request. + +Read-Only: + +- `id` (String) The identifier of this resource. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import cloudflare_leaked_credential_check_rules.example +``` From 35b9619066e6bcf5eead969b7b6bdb03be905192 Mon Sep 17 00:00:00 2001 From: Michele Russo Date: Thu, 28 Nov 2024 18:37:03 +0100 Subject: [PATCH 05/11] Add changelog entry --- .changelog/4676.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/4676.txt diff --git a/.changelog/4676.txt b/.changelog/4676.txt new file mode 100644 index 0000000000..dcb60c13b7 --- /dev/null +++ b/.changelog/4676.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +cloudflare_leaked_credential_check_rules +``` \ No newline at end of file From 4167c6f18ac3a133c9485989efa535f5a927999b Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 5 Dec 2024 11:12:12 +1100 Subject: [PATCH 06/11] fix conflict markers --- internal/framework/provider/provider.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/internal/framework/provider/provider.go b/internal/framework/provider/provider.go index aa7c167031..afe4903201 100644 --- a/internal/framework/provider/provider.go +++ b/internal/framework/provider/provider.go @@ -389,11 +389,8 @@ 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, -<<<<<<< HEAD leaked_credential_check.NewResource, -======= leaked_credential_check_rules.NewResource, ->>>>>>> 9769fdf5b (Add new resource to provider) } } From 8a4727ea4536c249f5a650b1e651f1b2bdb673cb Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 5 Dec 2024 11:24:10 +1100 Subject: [PATCH 07/11] simplify resource to be single detection based --- internal/framework/provider/provider.go | 4 +- .../model.go | 8 +- .../leaked_credential_check_rule/resource.go | 151 ++++++++++ .../resource_test.go | 102 +++++++ .../leaked_credential_check_rule/schema.go | 33 +++ .../leaked_credential_check_rules/resource.go | 230 --------------- .../resource_test.go | 262 ------------------ .../leaked_credential_check_rules/schema.go | 42 --- 8 files changed, 290 insertions(+), 542 deletions(-) rename internal/framework/service/{leaked_credential_check_rules => leaked_credential_check_rule}/model.go (58%) create mode 100644 internal/framework/service/leaked_credential_check_rule/resource.go create mode 100644 internal/framework/service/leaked_credential_check_rule/resource_test.go create mode 100644 internal/framework/service/leaked_credential_check_rule/schema.go delete mode 100644 internal/framework/service/leaked_credential_check_rules/resource.go delete mode 100644 internal/framework/service/leaked_credential_check_rules/resource_test.go delete mode 100644 internal/framework/service/leaked_credential_check_rules/schema.go diff --git a/internal/framework/provider/provider.go b/internal/framework/provider/provider.go index afe4903201..57a7e62fcd 100644 --- a/internal/framework/provider/provider.go +++ b/internal/framework/provider/provider.go @@ -28,7 +28,7 @@ import ( "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/leaked_credential_check_rules" + "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/service/leaked_credential_check_rule" "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" @@ -390,7 +390,7 @@ func (p *CloudflareProvider) Resources(ctx context.Context) []func() resource.Re infrastructure_access_target_deprecated.NewResource, zero_trust_infrastructure_access_target.NewResource, leaked_credential_check.NewResource, - leaked_credential_check_rules.NewResource, + leaked_credential_check_rule.NewResource, } } diff --git a/internal/framework/service/leaked_credential_check_rules/model.go b/internal/framework/service/leaked_credential_check_rule/model.go similarity index 58% rename from internal/framework/service/leaked_credential_check_rules/model.go rename to internal/framework/service/leaked_credential_check_rule/model.go index 316b8ad51c..f9f8e6192e 100644 --- a/internal/framework/service/leaked_credential_check_rules/model.go +++ b/internal/framework/service/leaked_credential_check_rule/model.go @@ -1,13 +1,9 @@ -package leaked_credential_check_rules +package leaked_credential_check_rule import "github.com/hashicorp/terraform-plugin-framework/types" type LeakedCredentialCheckRulesModel struct { - ZoneID types.String `tfsdk:"zone_id"` - Rules []LCCRuleValueModel `tfsdk:"rule"` -} - -type LCCRuleValueModel struct { + ZoneID types.String `tfsdk:"zone_id"` ID types.String `tfsdk:"id"` Username types.String `tfsdk:"username"` Password types.String `tfsdk:"password"` diff --git a/internal/framework/service/leaked_credential_check_rule/resource.go b/internal/framework/service/leaked_credential_check_rule/resource.go new file mode 100644 index 0000000000..6f4917d0e1 --- /dev/null +++ b/internal/framework/service/leaked_credential_check_rule/resource.go @@ -0,0 +1,151 @@ +package leaked_credential_check_rule + +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 = &LeakedCredentialCheckRuleResource{} + _ resource.ResourceWithImportState = &LeakedCredentialCheckRuleResource{} +) + +func NewResource() resource.Resource { + return &LeakedCredentialCheckRuleResource{} +} + +type LeakedCredentialCheckRuleResource struct { + client *muxclient.Client +} + +func (r *LeakedCredentialCheckRuleResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_leaked_credential_check_rule" +} + +func (r *LeakedCredentialCheckRuleResource) 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 *LeakedCredentialCheckRuleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data LeakedCredentialCheckRulesModel + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + detection, err := r.client.V1.LeakedCredentialCheckCreateDetection(ctx, cloudflare.ZoneIdentifier(data.ZoneID.ValueString()), cloudflare.LeakedCredentialCheckCreateDetectionParams{ + Username: data.Username.ValueString(), + Password: data.Password.ValueString(), + }) + if err != nil { + resp.Diagnostics.AddError("Error creating a user-defined detection patter for Leaked Credential Check", err.Error()) + return + } + + data.ID = types.StringValue(detection.ID) + + diags = resp.State.Set(ctx, &data) + resp.Diagnostics.Append(diags...) +} + +func (r *LeakedCredentialCheckRuleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state LeakedCredentialCheckRulesModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + zoneID := state.ZoneID.ValueString() + var foundRule cloudflare.LeakedCredentialCheckDetectionEntry + rules, err := r.client.V1.LeakedCredentialCheckListDetections(ctx, cloudflare.ZoneIdentifier(zoneID), cloudflare.LeakedCredentialCheckListDetectionsParams{}) + if err != nil { + resp.Diagnostics.AddError("Error listing Leaked Credential Check user-defined detection patterns", err.Error()) + return + } + + // leaked credentials doens't offer a single get operation so + // loop until we find the matching ID. + for _, rule := range rules { + if rule.ID == state.ID.ValueString() { + foundRule = rule + break + } + } + + state.Password = types.StringValue(foundRule.Password) + state.Username = types.StringValue(foundRule.Username) + state.ID = types.StringValue(foundRule.ID) + state.ZoneID = types.StringValue(zoneID) + + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) +} + +func (r *LeakedCredentialCheckRuleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data LeakedCredentialCheckRulesModel + diags := req.Plan.Get(ctx, &data) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + zoneID := cloudflare.ZoneIdentifier(data.ZoneID.ValueString()) + _, err := r.client.V1.LeakedCredentialCheckUpdateDetection(ctx, zoneID, cloudflare.LeakedCredentialCheckUpdateDetectionParams{ + LeakedCredentialCheckDetectionEntry: cloudflare.LeakedCredentialCheckDetectionEntry{ + ID: data.ID.ValueString(), + Username: data.Username.ValueString(), + Password: data.Password.ValueString(), + }, + }) + if err != nil { + resp.Diagnostics.AddError("Error fetching Leaked Credential Check user-defined detection patterns", err.Error()) + return + } + + diags = resp.State.Set(ctx, &data) + resp.Diagnostics.Append(diags...) +} + +func (r *LeakedCredentialCheckRuleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state LeakedCredentialCheckRulesModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + zoneID := cloudflare.ZoneIdentifier(state.ZoneID.ValueString()) + deleteParam := cloudflare.LeakedCredentialCheckDeleteDetectionParams{DetectionID: state.ID.ValueString()} + _, err := r.client.V1.LeakedCredentialCheckDeleteDetection(ctx, zoneID, deleteParam) + if err != nil { + resp.Diagnostics.AddError("Error deleting a user-defined detection patter for Leaked Credential Check", err.Error()) + return + } +} + +func (r *LeakedCredentialCheckRuleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), req.ID)...) + return +} diff --git a/internal/framework/service/leaked_credential_check_rule/resource_test.go b/internal/framework/service/leaked_credential_check_rule/resource_test.go new file mode 100644 index 0000000000..88cce88b09 --- /dev/null +++ b/internal/framework/service/leaked_credential_check_rule/resource_test.go @@ -0,0 +1,102 @@ +package leaked_credential_check_rule_test + +import ( + "context" + "errors" + "fmt" + "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_rule", &resource.Sweeper{ + Name: "cloudflare_leaked_credential_check_rule", + F: testSweepCloudflareLCCRules, + }) +} + +func testSweepCloudflareLCCRules(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") + } + // fetch existing rules from API + rules, err := client.LeakedCredentialCheckListDetections(ctx, cfv1.ZoneIdentifier(zoneID), cfv1.LeakedCredentialCheckListDetectionsParams{}) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Error fetching Leaked Credential Check user-defined detection patterns: %s", err)) + return err + } + for _, rule := range rules { + deleteParam := cfv1.LeakedCredentialCheckDeleteDetectionParams{DetectionID: rule.ID} + _, err := client.LeakedCredentialCheckDeleteDetection(ctx, cfv1.ZoneIdentifier(zoneID), deleteParam) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Error deleting a user-defined detection patter for Leaked Credential Check: %s", err)) + } + + } + return nil +} + +func TestAccCloudflareLeakedCredentialCheckRule_Basic(t *testing.T) { + rnd := utils.GenerateRandomResourceName() + name := fmt.Sprintf("cloudflare_leaked_credential_check_rule.%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: testAccConfigAddHeader(rnd, zoneID, testAccLCCTwoSimpleRules(rnd)), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(name+"_first", "zone_id", zoneID), + resource.TestCheckResourceAttr(name+"_first", "username", "lookup_json_string(http.request.body.raw, \"user\")"), + resource.TestCheckResourceAttr(name+"_first", "password", "lookup_json_string(http.request.body.raw, \"pass\")"), + + resource.TestCheckResourceAttr(name+"_second", "zone_id", zoneID), + resource.TestCheckResourceAttr(name+"_second", "username", "lookup_json_string(http.request.body.raw, \"id\")"), + resource.TestCheckResourceAttr(name+"_second", "password", "lookup_json_string(http.request.body.raw, \"secret\")"), + ), + }, + }, + }) +} + +func testAccConfigAddHeader(name, zoneID, config string) string { + header := fmt.Sprintf(` + resource "cloudflare_leaked_credential_check" "%[1]s" { + zone_id = "%[2]s" + enabled = true + }`, name, zoneID) + return header + "\n" + config +} + +func testAccLCCTwoSimpleRules(name string) string { + return fmt.Sprintf(` + resource "cloudflare_leaked_credential_check_rule" "%[1]s_first" { + zone_id = cloudflare_leaked_credential_check.%[1]s.zone_id + username = "lookup_json_string(http.request.body.raw, \"user\")" + password = "lookup_json_string(http.request.body.raw, \"pass\")" + } + + resource "cloudflare_leaked_credential_check_rule" "%[1]s_second" { + zone_id = cloudflare_leaked_credential_check.%[1]s.zone_id + username = "lookup_json_string(http.request.body.raw, \"id\")" + password = "lookup_json_string(http.request.body.raw, \"secret\")" + }`, name) +} diff --git a/internal/framework/service/leaked_credential_check_rule/schema.go b/internal/framework/service/leaked_credential_check_rule/schema.go new file mode 100644 index 0000000000..6f580e96a3 --- /dev/null +++ b/internal/framework/service/leaked_credential_check_rule/schema.go @@ -0,0 +1,33 @@ +package leaked_credential_check_rule + +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 *LeakedCredentialCheckRuleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Provides a Cloudflare Leaked Credential Check Rules resource for managing user-defined Leaked Credential detection patterns within a specific zone.", + Attributes: map[string]schema.Attribute{ + consts.IDSchemaKey: schema.StringAttribute{ + Description: consts.IDSchemaDescription, + Computed: true, + }, + consts.ZoneIDSchemaKey: schema.StringAttribute{ + Description: consts.ZoneIDSchemaDescription, + Required: true, + }, + "username": schema.StringAttribute{ + Description: "The ruleset expression to use in matching the username in a request.", + Required: true, + }, + "password": schema.StringAttribute{ + Description: "The ruleset expression to use in matching the password in a request", + Required: true, + }, + }, + } +} diff --git a/internal/framework/service/leaked_credential_check_rules/resource.go b/internal/framework/service/leaked_credential_check_rules/resource.go deleted file mode 100644 index ac34bc7362..0000000000 --- a/internal/framework/service/leaked_credential_check_rules/resource.go +++ /dev/null @@ -1,230 +0,0 @@ -package leaked_credential_check_rules - -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 = &LeakedCredentialCheckRulesResource{} - _ resource.ResourceWithImportState = &LeakedCredentialCheckRulesResource{} -) - -func NewResource() resource.Resource { - return &LeakedCredentialCheckRulesResource{} -} - -type LeakedCredentialCheckRulesResource struct { - client *muxclient.Client -} - -func (r *LeakedCredentialCheckRulesResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_leaked_credential_check_rules" -} - -func (r *LeakedCredentialCheckRulesResource) 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 *LeakedCredentialCheckRulesResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var plan LeakedCredentialCheckRulesModel - diags := req.Plan.Get(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - zoneID := plan.ZoneID.ValueString() - var createdRules []LCCRuleValueModel - if len(plan.Rules) > 0 { - for _, rule := range plan.Rules { - createParam := cloudflare.LeakedCredentialCheckCreateDetectionParams{ - Username: rule.Username.ValueString(), - Password: rule.Password.ValueString(), - } - detection, err := r.client.V1.LeakedCredentialCheckCreateDetection(ctx, cloudflare.ZoneIdentifier(zoneID), createParam) - if err != nil { - resp.Diagnostics.AddError("Error creating a user-defined detection patter for Leaked Credential Check", err.Error()) - return - } - createdRules = append(createdRules, LCCRuleValueModel{ID: types.StringValue(detection.ID), Username: rule.Username, Password: rule.Password}) - } - - } - plan.Rules = createdRules - - diags = resp.State.Set(ctx, &plan) - resp.Diagnostics.Append(diags...) -} - -func (r *LeakedCredentialCheckRulesResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var state LeakedCredentialCheckRulesModel - diags := req.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - zoneID := state.ZoneID.ValueString() - rules, err := r.client.V1.LeakedCredentialCheckListDetections(ctx, cloudflare.ZoneIdentifier(zoneID), cloudflare.LeakedCredentialCheckListDetectionsParams{}) - if err != nil { - resp.Diagnostics.AddError("Error listing Leaked Credential Check user-defined detection patterns", err.Error()) - return - } - var readRules []LCCRuleValueModel - for _, rule := range rules { - readRules = append(readRules, buildLCCRuleValueModel(rule)) - } - state.Rules = readRules - diags = resp.State.Set(ctx, &state) - resp.Diagnostics.Append(diags...) -} - -func (r *LeakedCredentialCheckRulesResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var plan LeakedCredentialCheckRulesModel - diags := req.Plan.Get(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - zoneID := cloudflare.ZoneIdentifier(plan.ZoneID.ValueString()) - // fetch existing rules from API - existing_rules, err := r.client.V1.LeakedCredentialCheckListDetections(ctx, zoneID, cloudflare.LeakedCredentialCheckListDetectionsParams{}) - if err != nil { - resp.Diagnostics.AddError("Error fetching Leaked Credential Check user-defined detection patterns", err.Error()) - return - } - // compare and create/delete accordingly - var createdRules []LCCRuleValueModel - toAdd, toRemove, toKeep := diffRules(plan.Rules, existing_rules) - // create - for _, createParam := range toAdd { - detection, err := r.client.V1.LeakedCredentialCheckCreateDetection(ctx, zoneID, createParam) - if err != nil { - resp.Diagnostics.AddError("Error creating a user-defined detection patter for Leaked Credential Check", err.Error()) - return - } - createdRules = append(createdRules, buildLCCRuleValueModel(detection)) - } - plan.Rules = createdRules // update plan rules with the newly created rules - // delete - for _, deleteParam := range toRemove { - _, err := r.client.V1.LeakedCredentialCheckDeleteDetection(ctx, zoneID, deleteParam) - if err != nil { - resp.Diagnostics.AddError("Error deleting a user-defined detection patter for Leaked Credential Check", err.Error()) - return - } - } - // add the existing rules we kept to the plan, if any - for _, kr := range toKeep { - plan.Rules = append(plan.Rules, buildLCCRuleValueModel(kr)) - } - - diags = resp.State.Set(ctx, &plan) - resp.Diagnostics.Append(diags...) -} - -// Delete all user-defined detection rules -func (r *LeakedCredentialCheckRulesResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var state LeakedCredentialCheckRulesModel - diags := req.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - zoneID := cloudflare.ZoneIdentifier(state.ZoneID.ValueString()) - // fetch existing rules from API - rules, err := r.client.V1.LeakedCredentialCheckListDetections(ctx, zoneID, cloudflare.LeakedCredentialCheckListDetectionsParams{}) - if err != nil { - resp.Diagnostics.AddError("Error fetching Leaked Credential Check user-defined detection patterns", err.Error()) - return - } - for _, rule := range rules { - deleteParam := cloudflare.LeakedCredentialCheckDeleteDetectionParams{DetectionID: rule.ID} - _, err := r.client.V1.LeakedCredentialCheckDeleteDetection(ctx, zoneID, deleteParam) - if err != nil { - resp.Diagnostics.AddError("Error deleting a user-defined detection patter for Leaked Credential Check", err.Error()) - return - } - - } - -} - -func (r *LeakedCredentialCheckRulesResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - // req.ID is the zoneID for which you want to import the state of the - // Leaked Credential Check Rules - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("zone_id"), req.ID)...) - return -} - -func diffRules(desired []LCCRuleValueModel, current []cloudflare.LeakedCredentialCheckDetectionEntry) (toAdd []cloudflare.LeakedCredentialCheckCreateDetectionParams, toRemove []cloudflare.LeakedCredentialCheckDeleteDetectionParams, toKeep []cloudflare.LeakedCredentialCheckDetectionEntry) { - // Create a map for the desired rules - desiredMap := make(map[string]struct{}) - for _, d := range desired { - key := d.Username.ValueString() + ":" + d.Password.ValueString() // Use a unique key for comparison - desiredMap[key] = struct{}{} - } - - // Create a map for the current rules - currentMap := make(map[string]struct{}) - for _, c := range current { - key := c.Username + ":" + c.Password - currentMap[key] = struct{}{} - - // If a rule exists in current but not in desired, mark it for removal - if _, exists := desiredMap[key]; !exists { - toRemove = append(toRemove, cloudflare.LeakedCredentialCheckDeleteDetectionParams{DetectionID: c.ID}) - } else { - // If a rule exists both in current and desired, mark it as to keep - toKeep = append( - toKeep, - cloudflare.LeakedCredentialCheckDetectionEntry{ID: c.ID, Username: c.Username, Password: c.Password}, - ) - } - } - - // Find rules that exist in desired but not in current - for _, d := range desired { - key := d.Username.ValueString() + ":" + d.Password.ValueString() - if _, exists := currentMap[key]; !exists { - toAdd = append(toAdd, cloudflare.LeakedCredentialCheckCreateDetectionParams{ - Username: d.Username.ValueString(), - Password: d.Password.ValueString(), - }) - } - } - - return -} - -func buildLCCRuleValueModel(lccEntry cloudflare.LeakedCredentialCheckDetectionEntry) LCCRuleValueModel { - rule := LCCRuleValueModel{ - ID: types.StringValue(lccEntry.ID), - Username: types.StringValue(lccEntry.Username), - Password: types.StringValue(lccEntry.Password), - } - return rule -} diff --git a/internal/framework/service/leaked_credential_check_rules/resource_test.go b/internal/framework/service/leaked_credential_check_rules/resource_test.go deleted file mode 100644 index 781b04ce10..0000000000 --- a/internal/framework/service/leaked_credential_check_rules/resource_test.go +++ /dev/null @@ -1,262 +0,0 @@ -package leaked_credential_check_rules_test - -import ( - "context" - "errors" - "fmt" - "os" - "regexp" - "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" - "github.com/hashicorp/terraform-plugin-testing/terraform" -) - -func init() { - resource.AddTestSweepers("cloudflare_leaked_credential_check_rules", &resource.Sweeper{ - Name: "cloudflare_leaked_credential_check_rules", - F: testSweepCloudflareLCCRules, - }) -} - -func testSweepCloudflareLCCRules(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") - } - // fetch existing rules from API - rules, err := client.LeakedCredentialCheckListDetections(ctx, cfv1.ZoneIdentifier(zoneID), cfv1.LeakedCredentialCheckListDetectionsParams{}) - if err != nil { - tflog.Error(ctx, fmt.Sprintf("Error fetching Leaked Credential Check user-defined detection patterns: %s", err)) - return err - } - for _, rule := range rules { - deleteParam := cfv1.LeakedCredentialCheckDeleteDetectionParams{DetectionID: rule.ID} - _, err := client.LeakedCredentialCheckDeleteDetection(ctx, cfv1.ZoneIdentifier(zoneID), deleteParam) - if err != nil { - tflog.Error(ctx, fmt.Sprintf("Error deleting a user-defined detection patter for Leaked Credential Check: %s", err)) - } - - } - return nil -} - -func TestAccCloudflareLeakedCredentialCheckRules_CRUD(t *testing.T) { - rnd := utils.GenerateRandomResourceName() - name := fmt.Sprintf("cloudflare_leaked_credential_check_rules.%s", rnd) - zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") - - var detectionRules []cfv1.LeakedCredentialCheckDetectionEntry - - resource.Test(t, resource.TestCase{ - PreCheck: func() { - acctest.TestAccPreCheck(t) - }, - ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - { - Config: testAccConfigAddHeader(rnd, zoneID, testAccLCCTwoSimpleRules(rnd)), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(name, "zone_id", zoneID), - resource.TestCheckResourceAttr(name, "rule.#", "2"), - resource.TestCheckResourceAttr(name, "rule.0.%", "3"), - resource.TestCheckResourceAttr(name, "rule.1.%", "3"), - resource.TestMatchTypeSetElemNestedAttrs( - name, - "rule.*", - map[string]*regexp.Regexp{ - "id": regexp.MustCompile(`^[a-z0-9]+$`), - "username": regexp.MustCompile(`"id"`), - "password": regexp.MustCompile(`"secret"`), - }, - ), - resource.TestMatchTypeSetElemNestedAttrs( - name, - "rule.*", - map[string]*regexp.Regexp{ - "id": regexp.MustCompile(`^[a-z0-9]+$`), - "username": regexp.MustCompile(`"user"`), - "password": regexp.MustCompile(`"pass"`), - }, - ), - testAccCheckLCCNumRules(name, 2, &detectionRules), - ), - }, - { // remove one rule, keep one, and add a new one - Config: testAccConfigAddHeader(rnd, zoneID, testAccLCCSimpleChangeOneRule(rnd)), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(name, "zone_id", zoneID), - resource.TestCheckResourceAttr(name, "rule.#", "2"), - resource.TestCheckResourceAttr(name, "rule.0.%", "3"), - resource.TestCheckResourceAttr(name, "rule.1.%", "3"), - resource.TestMatchTypeSetElemNestedAttrs( - name, - "rule.*", - map[string]*regexp.Regexp{ - "id": regexp.MustCompile(`^[a-z0-9]+$`), - "username": regexp.MustCompile(`"name"`), - "password": regexp.MustCompile(`"key"`), - }, - ), - testAccCheckLCCOneRuleChange(name, 2, &detectionRules), - ), - }, - { // clear rules - Config: testAccConfigAddHeader(rnd, zoneID, testAccLCCNoRules(rnd)), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(name, "zone_id", zoneID), - resource.TestCheckResourceAttr(name, "rule.#", "0"), - testAccCheckLCCNumRules(name, 0, &detectionRules), - ), - }, - { // add a single rule - Config: testAccConfigAddHeader(rnd, zoneID, testAccLCCOneSimpleRule(rnd)), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(name, "zone_id", zoneID), - resource.TestCheckResourceAttr(name, "rule.#", "1"), - resource.TestCheckResourceAttr(name, "rule.0.%", "3"), - resource.TestCheckResourceAttr(name, "rule.0.username", "lookup_json_string(http.request.body.raw, \"username\")"), - resource.TestCheckResourceAttr(name, "rule.0.password", "lookup_json_string(http.request.body.raw, \"password\")"), - testAccCheckLCCNumRules(name, 1, &detectionRules), - ), - }, - }, - }) -} - -func testAccConfigAddHeader(name, zoneID, config string) string { - header := fmt.Sprintf(` - resource "cloudflare_leaked_credential_check" "%[1]s" { - zone_id = "%[2]s" - enabled = true - }`, name, zoneID) - return header + "\n" + config -} - -func testAccLCCTwoSimpleRules(name string) string { - return fmt.Sprintf(` - resource "cloudflare_leaked_credential_check_rules" "%[1]s" { - zone_id = cloudflare_leaked_credential_check.%[1]s.zone_id - rule { - username = "lookup_json_string(http.request.body.raw, \"user\")" - password = "lookup_json_string(http.request.body.raw, \"pass\")" - } - rule { - username = "lookup_json_string(http.request.body.raw, \"id\")" - password = "lookup_json_string(http.request.body.raw, \"secret\")" - } - }`, name) -} - -func testAccLCCSimpleChangeOneRule(name string) string { - return fmt.Sprintf(` - resource "cloudflare_leaked_credential_check_rules" "%[1]s" { - zone_id = cloudflare_leaked_credential_check.%[1]s.zone_id - rule { - username = "lookup_json_string(http.request.body.raw, \"name\")" - password = "lookup_json_string(http.request.body.raw, \"key\")" - } - rule { - username = "lookup_json_string(http.request.body.raw, \"id\")" - password = "lookup_json_string(http.request.body.raw, \"secret\")" - } - }`, name) -} - -func testAccLCCNoRules(name string) string { - return fmt.Sprintf(` - resource "cloudflare_leaked_credential_check_rules" "%[1]s" { - zone_id = cloudflare_leaked_credential_check.%[1]s.zone_id - }`, name) -} - -func testAccLCCOneSimpleRule(name string) string { - return fmt.Sprintf(` - resource "cloudflare_leaked_credential_check_rules" "%[1]s" { - zone_id = cloudflare_leaked_credential_check.%[1]s.zone_id - rule { - username = "lookup_json_string(http.request.body.raw, \"username\")" - password = "lookup_json_string(http.request.body.raw, \"password\")" - } - }`, name) -} - -func testAccCheckLCCNumRules(name string, length int, rules *[]cfv1.LeakedCredentialCheckDetectionEntry) resource.TestCheckFunc { - return func(s *terraform.State) error { - zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") - _, ok := s.RootModule().Resources[name] - if !ok { - return fmt.Errorf("not found: %s", name) - } - client, _ := acctest.SharedV1Client() - detections, err := client.LeakedCredentialCheckListDetections( - context.Background(), - cfv1.ZoneIdentifier(zoneID), - cfv1.LeakedCredentialCheckListDetectionsParams{}, - ) - if err != nil { - return err - } - - if length != len(detections) { - return fmt.Errorf("Expected num of rules (%d) does not match the actual num (%d)", length, len(detections)) - } - *rules = detections - - return nil - } -} - -func testAccCheckLCCOneRuleChange(name string, length int, rules *[]cfv1.LeakedCredentialCheckDetectionEntry) resource.TestCheckFunc { - return func(s *terraform.State) error { - zoneID := os.Getenv("CLOUDFLARE_ZONE_ID") - _, ok := s.RootModule().Resources[name] - if !ok { - return fmt.Errorf("not found: %s", name) - } - client, _ := acctest.SharedV1Client() - detections, err := client.LeakedCredentialCheckListDetections( - context.Background(), - cfv1.ZoneIdentifier(zoneID), - cfv1.LeakedCredentialCheckListDetectionsParams{}, - ) - if err != nil { - return err - } - - if length != len(detections) { - return fmt.Errorf("Expected num of rules (%d) does not match the actual num (%d)", length, len(detections)) - } - previousRulesMap := make(map[string]string) - for _, prul := range *rules { - mapkey := prul.Password + "|" + prul.Username - previousRulesMap[mapkey] = prul.ID - } - - found := false - for _, det := range detections { - mapkey := det.Password + "|" + det.Username - if _, exists := previousRulesMap[mapkey]; exists { - if previousRulesMap[mapkey] != det.ID { - return errors.New("Found the unchanged rule but the ID is different") - } - found = true - } - } - if !found { - return fmt.Errorf("Could not find the rule that was not changed!") - } - return nil - } -} diff --git a/internal/framework/service/leaked_credential_check_rules/schema.go b/internal/framework/service/leaked_credential_check_rules/schema.go deleted file mode 100644 index 41e19ddba9..0000000000 --- a/internal/framework/service/leaked_credential_check_rules/schema.go +++ /dev/null @@ -1,42 +0,0 @@ -package leaked_credential_check_rules - -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 *LeakedCredentialCheckRulesResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Description: "Provides a Cloudflare Leaked Credential Check Rules resource for managing user-defined Leaked Credential detection patterns within a specific zone.", - Attributes: map[string]schema.Attribute{ - consts.ZoneIDSchemaKey: schema.StringAttribute{ - Description: consts.ZoneIDSchemaDescription, - Required: true, - }, - }, - Blocks: map[string]schema.Block{ - "rule": schema.SetNestedBlock{ - Description: "List of user-defined patterns for Leaked Credential Check", - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - consts.IDSchemaKey: schema.StringAttribute{ - Description: consts.IDSchemaDescription, - Computed: true, - }, - "username": schema.StringAttribute{ - Description: "The ruleset expression to use in matching the username in a request.", - Required: true, - }, - "password": schema.StringAttribute{ - Description: "The ruleset expression to use in matching the password in a request", - Required: true, - }, - }, - }, - }, - }, - } -} From 3d703de29c9644142ba11c139ac8d400f2176607 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 5 Dec 2024 11:27:39 +1100 Subject: [PATCH 08/11] fix import handling --- .../import.sh | 1 + .../resource.tf | 12 ++++++++++ .../import.sh | 1 - .../resource.tf | 23 ------------------- .../leaked_credential_check_rule/resource.go | 14 +++++++++-- 5 files changed, 25 insertions(+), 26 deletions(-) create mode 100644 examples/resources/cloudflare_leaked_credential_check_rule/import.sh create mode 100644 examples/resources/cloudflare_leaked_credential_check_rule/resource.tf delete mode 100644 examples/resources/cloudflare_leaked_credential_check_rules/import.sh delete mode 100644 examples/resources/cloudflare_leaked_credential_check_rules/resource.tf diff --git a/examples/resources/cloudflare_leaked_credential_check_rule/import.sh b/examples/resources/cloudflare_leaked_credential_check_rule/import.sh new file mode 100644 index 0000000000..bccbf57cd9 --- /dev/null +++ b/examples/resources/cloudflare_leaked_credential_check_rule/import.sh @@ -0,0 +1 @@ +terraform import cloudflare_leaked_credential_check_rule.example / diff --git a/examples/resources/cloudflare_leaked_credential_check_rule/resource.tf b/examples/resources/cloudflare_leaked_credential_check_rule/resource.tf new file mode 100644 index 0000000000..b18147d1a8 --- /dev/null +++ b/examples/resources/cloudflare_leaked_credential_check_rule/resource.tf @@ -0,0 +1,12 @@ +# Enable the Leaked Credentials Check detection before trying +# to add detections. +resource "cloudflare_leaked_credential_check" "example" { + zone_id = "399c6f4950c01a5a141b99ff7fbcbd8b" + enabled = true +} + +resource "cloudflare_leaked_credential_check_rules" "example" { + zone_id = cloudflare_leaked_credential_check.example.zone_id + username = "lookup_json_string(http.request.body.raw, \"user\")" + password = "lookup_json_string(http.request.body.raw, \"pass\")" +} diff --git a/examples/resources/cloudflare_leaked_credential_check_rules/import.sh b/examples/resources/cloudflare_leaked_credential_check_rules/import.sh deleted file mode 100644 index 9b2836a260..0000000000 --- a/examples/resources/cloudflare_leaked_credential_check_rules/import.sh +++ /dev/null @@ -1 +0,0 @@ -terraform import cloudflare_leaked_credential_check_rules.example \ No newline at end of file diff --git a/examples/resources/cloudflare_leaked_credential_check_rules/resource.tf b/examples/resources/cloudflare_leaked_credential_check_rules/resource.tf deleted file mode 100644 index 07b8386414..0000000000 --- a/examples/resources/cloudflare_leaked_credential_check_rules/resource.tf +++ /dev/null @@ -1,23 +0,0 @@ -# Enable the Leaked Credentials Check detection -resource "cloudflare_leaked_credential_check" "example" { - zone_id = "399c6f4950c01a5a141b99ff7fbcbd8b" - enabled = true -} - -# Create user-defined detection patterns for Leaked Credentials Check -resource "cloudflare_leaked_credential_check_rules" "example" { - # The following line ensures that rules are created only for a zone - # where the Leaked Credentials Check module is enabled, and that the - # module is activated before user-defined detection patterns are created. - zone_id = cloudflare_leaked_credential_check.example.zone_id - - rule { - username = "lookup_json_string(http.request.body.raw, \"user\")" - password = "lookup_json_string(http.request.body.raw, \"pass\")" - } - - rule { - username = "lookup_json_string(http.request.body.raw, \"id\")" - password = "lookup_json_string(http.request.body.raw, \"secret\")" - } -} \ No newline at end of file diff --git a/internal/framework/service/leaked_credential_check_rule/resource.go b/internal/framework/service/leaked_credential_check_rule/resource.go index 6f4917d0e1..454fc00701 100644 --- a/internal/framework/service/leaked_credential_check_rule/resource.go +++ b/internal/framework/service/leaked_credential_check_rule/resource.go @@ -3,6 +3,7 @@ package leaked_credential_check_rule import ( "context" "fmt" + "strings" "github.com/cloudflare/cloudflare-go" "github.com/cloudflare/terraform-provider-cloudflare/internal/framework/muxclient" @@ -146,6 +147,15 @@ func (r *LeakedCredentialCheckRuleResource) Delete(ctx context.Context, req reso } func (r *LeakedCredentialCheckRuleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), req.ID)...) - return + idparts := strings.Split(req.ID, "/") + if len(idparts) != 2 { + resp.Diagnostics.AddError("error importing leaked credential detection", "invalid ID specified. Please specify the ID as \"zone_id/resource_id\"") + return + } + resp.Diagnostics.Append(resp.State.SetAttribute( + ctx, path.Root("zone_id"), idparts[0], + )...) + resp.Diagnostics.Append(resp.State.SetAttribute( + ctx, path.Root("id"), idparts[1], + )...) } From 526b324aba3579ea14717b36451f9faff3466718 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 5 Dec 2024 11:32:22 +1100 Subject: [PATCH 09/11] `make docs` --- .../resources/leaked_credential_check_rule.md | 47 +++++++++++++ .../leaked_credential_check_rules.md | 68 ------------------- docs/resources/notification_policy.md | 2 +- .../resource.tf | 2 +- .../leaked_credential_check_rule/schema.go | 2 +- 5 files changed, 50 insertions(+), 71 deletions(-) create mode 100644 docs/resources/leaked_credential_check_rule.md delete mode 100644 docs/resources/leaked_credential_check_rules.md diff --git a/docs/resources/leaked_credential_check_rule.md b/docs/resources/leaked_credential_check_rule.md new file mode 100644 index 0000000000..f832180d7e --- /dev/null +++ b/docs/resources/leaked_credential_check_rule.md @@ -0,0 +1,47 @@ +--- +page_title: "cloudflare_leaked_credential_check_rule Resource - Cloudflare" +subcategory: "" +description: |- + Provides a Cloudflare Leaked Credential Check Rule resource for managing user-defined Leaked Credential detection patterns within a specific zone. +--- + +# cloudflare_leaked_credential_check_rule (Resource) + +Provides a Cloudflare Leaked Credential Check Rule resource for managing user-defined Leaked Credential detection patterns within a specific zone. + +## Example Usage + +```terraform +# Enable the Leaked Credentials Check detection before trying +# to add detections. +resource "cloudflare_leaked_credential_check" "example" { + zone_id = "399c6f4950c01a5a141b99ff7fbcbd8b" + enabled = true +} + +resource "cloudflare_leaked_credential_check_rule" "example" { + zone_id = cloudflare_leaked_credential_check.example.zone_id + username = "lookup_json_string(http.request.body.raw, \"user\")" + password = "lookup_json_string(http.request.body.raw, \"pass\")" +} +``` + +## Schema + +### Required + +- `password` (String) The ruleset expression to use in matching the password in a request +- `username` (String) The ruleset expression to use in matching the username in a request. +- `zone_id` (String) The zone identifier to target for the resource. + +### Read-Only + +- `id` (String) The identifier of this resource. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import cloudflare_leaked_credential_check_rule.example / +``` diff --git a/docs/resources/leaked_credential_check_rules.md b/docs/resources/leaked_credential_check_rules.md deleted file mode 100644 index eae1dc3adb..0000000000 --- a/docs/resources/leaked_credential_check_rules.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -page_title: "cloudflare_leaked_credential_check_rules Resource - Cloudflare" -subcategory: "" -description: |- - Provides a Cloudflare Leaked Credential Check Rules resource for managing user-defined Leaked Credential detection patterns within a specific zone. ---- - -# cloudflare_leaked_credential_check_rules (Resource) - -Provides a Cloudflare Leaked Credential Check Rules resource for managing user-defined Leaked Credential detection patterns within a specific zone. - -## Example Usage - -```terraform -# Enable the Leaked Credentials Check detection -resource "cloudflare_leaked_credential_check" "example" { - zone_id = "399c6f4950c01a5a141b99ff7fbcbd8b" - enabled = true -} - -# Create user-defined detection patterns for Leaked Credentials Check -resource "cloudflare_leaked_credential_check_rules" "example" { - # The following line ensures that rules are created only for a zone - # where the Leaked Credentials Check module is enabled, and that the - # module is activated before user-defined detection patterns are created. - zone_id = cloudflare_leaked_credential_check.example.zone_id - - rule { - username = "lookup_json_string(http.request.body.raw, \"user\")" - password = "lookup_json_string(http.request.body.raw, \"pass\")" - } - - rule { - username = "lookup_json_string(http.request.body.raw, \"id\")" - password = "lookup_json_string(http.request.body.raw, \"secret\")" - } -} -``` - -## Schema - -### Required - -- `zone_id` (String) The zone identifier to target for the resource. - -### Optional - -- `rule` (Block Set) List of user-defined patterns for Leaked Credential Check (see [below for nested schema](#nestedblock--rule)) - - -### Nested Schema for `rule` - -Required: - -- `password` (String) The ruleset expression to use in matching the password in a request -- `username` (String) The ruleset expression to use in matching the username in a request. - -Read-Only: - -- `id` (String) The identifier of this resource. - -## Import - -Import is supported using the following syntax: - -```shell -terraform import cloudflare_leaked_credential_check_rules.example -``` diff --git a/docs/resources/notification_policy.md b/docs/resources/notification_policy.md index 77fda4f5c3..d44ab56cec 100644 --- a/docs/resources/notification_policy.md +++ b/docs/resources/notification_policy.md @@ -104,7 +104,7 @@ Read-Only: Optional: - `actions` (Set of String) Targeted actions for alert. -- `affected_components` (Set of String) Affected components for alert. Available values: `API`, `API Shield`, `Access`, `Always Online`, `Analytics`, `Apps Marketplace`, `Argo Smart Routing`, `Audit Logs`, `Authoritative DNS`, `Billing`, `Bot Management`, `Bring Your Own IP (BYOIP)`, `Browser Isolation`, `CDN Cache Purge`, `CDN/Cache`, `Cache Reserve`, `Challenge Platform`, `Cloud Access Security Broker (CASB)`, `Community Site`, `DNS Root Servers`, `DNS Updates`, `Dashboard`, `Data Loss Prevention (DLP)`, `Developer's Site`, `Digital Experience Monitoring (DEX)`, `Distributed Web Gateway`, `Durable Objects`, `Email Routing`, `Ethereum Gateway`, `Firewall`, `Gateway`, `Geo-Key Manager`, `Image Resizing`, `Images`, `Infrastructure`, `Lists`, `Load Balancing and Monitoring`, `Logs`, `Magic Firewall`, `Magic Transit`, `Magic WAN`, `Magic WAN Connector`, `Marketing Site`, `Mirage`, `Network`, `Notifications`, `Observatory`, `Page Shield`, `Pages`, `R2`, `Radar`, `Randomness Beacon`, `Recursive DNS`, `Registrar`, `Registration Data Access Protocol (RDAP)`, `SSL Certificate Provisioning`, `SSL for SaaS Provisioning`, `Security Center`, `Snippets`, `Spectrum`, `Speed Optimizations`, `Stream`, `Support Site`, `Time Services`, `Trace`, `Tunnel`, `Turnstile`, `WARP`, `Waiting Room`, `Web Analytics`, `Workers`, `Workers KV`, `Workers Preview`, `Zaraz`, `Zero Trust`, `Zero Trust Dashboard`, `Zone Versioning`. +- `affected_components` (Set of String) Affected components for alert. Available values: `API`, `API Shield`, `Access`, `Always Online`, `Analytics`, `Apps Marketplace`, `Argo Smart Routing`, `Audit Logs`, `Authoritative DNS`, `Billing`, `Bot Management`, `Bring Your Own IP (BYOIP)`, `Browser Isolation`, `CDN Cache Purge`, `CDN/Cache`, `Cache Reserve`, `Challenge Platform`, `Cloud Access Security Broker (CASB)`, `Community Site`, `D1`, `DNS Root Servers`, `DNS Updates`, `Dashboard`, `Data Loss Prevention (DLP)`, `Developer's Site`, `Digital Experience Monitoring (DEX)`, `Distributed Web Gateway`, `Durable Objects`, `Email Routing`, `Ethereum Gateway`, `Firewall`, `Gateway`, `Geo-Key Manager`, `Image Resizing`, `Images`, `Infrastructure`, `Lists`, `Load Balancing and Monitoring`, `Logs`, `Magic Firewall`, `Magic Transit`, `Magic WAN`, `Magic WAN Connector`, `Marketing Site`, `Mirage`, `Network`, `Notifications`, `Observatory`, `Page Shield`, `Pages`, `R2`, `Radar`, `Randomness Beacon`, `Recursive DNS`, `Registrar`, `Registration Data Access Protocol (RDAP)`, `SSL Certificate Provisioning`, `SSL for SaaS Provisioning`, `Security Center`, `Snippets`, `Spectrum`, `Speed Optimizations`, `Stream`, `Support Site`, `Time Services`, `Trace`, `Tunnel`, `Turnstile`, `WARP`, `Waiting Room`, `Web Analytics`, `Workers`, `Workers KV`, `Workers Preview`, `Zaraz`, `Zero Trust`, `Zero Trust Dashboard`, `Zone Versioning`. - `airport_code` (Set of String) Filter on Points of Presence. - `alert_trigger_preferences` (Set of String) Alert trigger preferences. Example: `slo`. - `enabled` (Set of String) State of the pool to alert on. diff --git a/examples/resources/cloudflare_leaked_credential_check_rule/resource.tf b/examples/resources/cloudflare_leaked_credential_check_rule/resource.tf index b18147d1a8..879fce32e6 100644 --- a/examples/resources/cloudflare_leaked_credential_check_rule/resource.tf +++ b/examples/resources/cloudflare_leaked_credential_check_rule/resource.tf @@ -5,7 +5,7 @@ resource "cloudflare_leaked_credential_check" "example" { enabled = true } -resource "cloudflare_leaked_credential_check_rules" "example" { +resource "cloudflare_leaked_credential_check_rule" "example" { zone_id = cloudflare_leaked_credential_check.example.zone_id username = "lookup_json_string(http.request.body.raw, \"user\")" password = "lookup_json_string(http.request.body.raw, \"pass\")" diff --git a/internal/framework/service/leaked_credential_check_rule/schema.go b/internal/framework/service/leaked_credential_check_rule/schema.go index 6f580e96a3..4da8843159 100644 --- a/internal/framework/service/leaked_credential_check_rule/schema.go +++ b/internal/framework/service/leaked_credential_check_rule/schema.go @@ -10,7 +10,7 @@ import ( func (r *LeakedCredentialCheckRuleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - Description: "Provides a Cloudflare Leaked Credential Check Rules resource for managing user-defined Leaked Credential detection patterns within a specific zone.", + Description: "Provides a Cloudflare Leaked Credential Check Rule resource for managing user-defined Leaked Credential detection patterns within a specific zone.", Attributes: map[string]schema.Attribute{ consts.IDSchemaKey: schema.StringAttribute{ Description: consts.IDSchemaDescription, From 3d446df05a5a350d846fbb5d8b7736f461d70f7d Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 5 Dec 2024 11:35:31 +1100 Subject: [PATCH 10/11] Update 4676.txt --- .changelog/4676.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changelog/4676.txt b/.changelog/4676.txt index dcb60c13b7..d36c31856b 100644 --- a/.changelog/4676.txt +++ b/.changelog/4676.txt @@ -1,3 +1,3 @@ ```release-note:new-resource -cloudflare_leaked_credential_check_rules -``` \ No newline at end of file +cloudflare_leaked_credential_check_rule +``` From 078b57dbb21b98deb0e70ef804b19bf0e2b6e275 Mon Sep 17 00:00:00 2001 From: Jacob Bednarz Date: Thu, 5 Dec 2024 15:48:51 +1100 Subject: [PATCH 11/11] fix trailing whitespace --- .../service/leaked_credential_check_rule/resource_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/framework/service/leaked_credential_check_rule/resource_test.go b/internal/framework/service/leaked_credential_check_rule/resource_test.go index 88cce88b09..62714a892d 100644 --- a/internal/framework/service/leaked_credential_check_rule/resource_test.go +++ b/internal/framework/service/leaked_credential_check_rule/resource_test.go @@ -45,8 +45,8 @@ func testSweepCloudflareLCCRules(r string) error { if err != nil { tflog.Error(ctx, fmt.Sprintf("Error deleting a user-defined detection patter for Leaked Credential Check: %s", err)) } - } + return nil }