Skip to content

Commit

Permalink
simplify resource to be single detection based
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobbednarz committed Dec 5, 2024
1 parent 4167c6f commit 8a4727e
Show file tree
Hide file tree
Showing 8 changed files with 290 additions and 542 deletions.
4 changes: 2 additions & 2 deletions internal/framework/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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"`
Expand Down
151 changes: 151 additions & 0 deletions internal/framework/service/leaked_credential_check_rule/resource.go
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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)
}
33 changes: 33 additions & 0 deletions internal/framework/service/leaked_credential_check_rule/schema.go
Original file line number Diff line number Diff line change
@@ -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,
},
},
}
}
Loading

0 comments on commit 8a4727e

Please sign in to comment.