diff --git a/docs/resources/alb_waf_configuration.md b/docs/resources/alb_waf_configuration.md index a6dd06b24..b7525ebed 100644 --- a/docs/resources/alb_waf_configuration.md +++ b/docs/resources/alb_waf_configuration.md @@ -16,9 +16,44 @@ ALB WAF Custom Rule Group resource schema. Uses the `default_region` specified i ## Example Usage ```terraform +resource "stackit_alb_waf_managed_rule_set" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-managed-rule-set" + type = "TYPE_OWASP_CRS" +} + +resource "stackit_alb_waf_custom_rule_group" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-custom-rule-group" + rules = [ + { + behavior = { + action = "ACTION_DENY" + } + conditions = [ + { + operator = { + type = "OPERATOR_VALIDATE_UTF8_ENCODING" + } + variable = { + type = "VARIABLE_REQUEST_HEADERS" + } + } + ] + } + ] +} + resource "stackit_alb_waf_configuration" "example" { project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" name = "example-waf-configuration" + + managed_rule_set_name = stackit_alb_waf_managed_rule_set.example.name + custom_rule_group_name = stackit_alb_waf_custom_rule_group.example.name + + labels = { + "key" = "value" + } } ``` diff --git a/examples/resources/stackit_alb_waf_configuration/resource.tf b/examples/resources/stackit_alb_waf_configuration/resource.tf index 027c57e02..53bc6463b 100644 --- a/examples/resources/stackit_alb_waf_configuration/resource.tf +++ b/examples/resources/stackit_alb_waf_configuration/resource.tf @@ -1,5 +1,39 @@ +resource "stackit_alb_waf_managed_rule_set" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-managed-rule-set" + type = "TYPE_OWASP_CRS" +} + +resource "stackit_alb_waf_custom_rule_group" "example" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-custom-rule-group" + rules = [ + { + behavior = { + action = "ACTION_DENY" + } + conditions = [ + { + operator = { + type = "OPERATOR_VALIDATE_UTF8_ENCODING" + } + variable = { + type = "VARIABLE_REQUEST_HEADERS" + } + } + ] + } + ] +} + resource "stackit_alb_waf_configuration" "example" { project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" name = "example-waf-configuration" -} + managed_rule_set_name = stackit_alb_waf_managed_rule_set.example.name + custom_rule_group_name = stackit_alb_waf_custom_rule_group.example.name + + labels = { + "key" = "value" + } +} diff --git a/stackit/internal/services/albwaf/albwaf_acc_test.go b/stackit/internal/services/albwaf/albwaf_acc_test.go index 5029f74f8..3bb2fd52f 100644 --- a/stackit/internal/services/albwaf/albwaf_acc_test.go +++ b/stackit/internal/services/albwaf/albwaf_acc_test.go @@ -664,6 +664,7 @@ func testAccCheckDestroy(s *terraform.State) error { checkFunctions := []func(s *terraform.State) error{ testAlbWafCustomRuleGroupDestroy, testAlbWafManagedRuleSetDestroy, + testAlbWafWafConfigurationDestroy, } var errs []error @@ -743,3 +744,36 @@ func testAlbWafManagedRuleSetDestroy(s *terraform.State) error { } return nil } + +func testAlbWafWafConfigurationDestroy(s *terraform.State) error { + ctx := context.Background() + client, err := createClient() + if err != nil { + return err + } + + wafConfigurationToDestroy := []string{} + for _, rs := range s.RootModule().Resources { + if rs.Type != "stackit_alb_waf_configuration" { + continue + } + // waf configuration transform id: "[projectId],[region],[name]" + name := strings.Split(rs.Primary.ID, core.Separator)[2] + wafConfigurationToDestroy = append(wafConfigurationToDestroy, name) + } + + resp, err := client.DefaultAPI.ListWAF(ctx, testutil.ProjectId, testutil.Region).Execute() + if err != nil { + return fmt.Errorf("getting resp: %w", err) + } + + for _, item := range resp.Items { + if utils.Contains(wafConfigurationToDestroy, item.GetName()) { + _, err := client.DefaultAPI.DeleteWAF(ctx, testutil.ProjectId, testutil.Region, item.GetName()).Execute() + if err != nil { + return fmt.Errorf("deleting policy %s during CheckDestroy: %w", item.GetName(), err) + } + } + } + return nil +} diff --git a/stackit/internal/services/albwaf/custom_rule_group/resource.go b/stackit/internal/services/albwaf/custom_rule_group/resource.go index 540c0411d..5bd14a708 100644 --- a/stackit/internal/services/albwaf/custom_rule_group/resource.go +++ b/stackit/internal/services/albwaf/custom_rule_group/resource.go @@ -350,6 +350,14 @@ func (r *customRuleGroupResource) ModifyPlan(ctx context.Context, req resource.M return } + if !req.State.Raw.IsNull() { + var stateModel Model + resp.Diagnostics.Append(req.State.Get(ctx, &stateModel)...) + if !resp.Diagnostics.HasError() { + utils.WarnIfNameChanges(stateModel.Name, planModel.Name, "Custom Rule Group", &resp.Diagnostics) + } + } + resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...) if resp.Diagnostics.HasError() { return diff --git a/stackit/internal/services/albwaf/managed_rule_set/resource.go b/stackit/internal/services/albwaf/managed_rule_set/resource.go index bf3189e6b..1bd9f4ab2 100644 --- a/stackit/internal/services/albwaf/managed_rule_set/resource.go +++ b/stackit/internal/services/albwaf/managed_rule_set/resource.go @@ -237,24 +237,11 @@ func (r *managedRuleSetResource) ModifyPlan(ctx context.Context, req resource.Mo return } - // Warn the user if the name is changing, as this triggers a replacement. - // Deletion of the old resource will fail if another resource (e.g. stackit_alb_waf_configuration) - // still references this managed rule set. if !req.State.Raw.IsNull() { var stateModel Model resp.Diagnostics.Append(req.State.Get(ctx, &stateModel)...) - if !resp.Diagnostics.HasError() && !stateModel.Name.IsNull() && !stateModel.Name.IsUnknown() { - if !planModel.Name.Equal(stateModel.Name) { - resp.Diagnostics.AddWarning( - "Managed Rule Set name change requires resource replacement", - fmt.Sprintf( - "Changing the \"name\" attribute from %q to %q will destroy and recreate this resource. "+ - "If another resource (e.g. \"stackit_alb_waf_configuration\") references this managed rule set "+ - "by name, the replacement will fail. Remove or update that dependency before applying this change.", - stateModel.Name.ValueString(), planModel.Name.ValueString(), - ), - ) - } + if !resp.Diagnostics.HasError() { + utils.WarnIfNameChanges(stateModel.Name, planModel.Name, "Managed Rule Set", &resp.Diagnostics) } } diff --git a/stackit/internal/services/albwaf/utils/util.go b/stackit/internal/services/albwaf/utils/util.go index fc02f8d89..04ba1e585 100644 --- a/stackit/internal/services/albwaf/utils/util.go +++ b/stackit/internal/services/albwaf/utils/util.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" "github.com/stackitcloud/stackit-sdk-go/core/config" albWaf "github.com/stackitcloud/stackit-sdk-go/services/albwaf/v1api" @@ -29,3 +30,20 @@ func ConfigureClient(ctx context.Context, providerData *core.ProviderData, diags return apiClient } + +func WarnIfNameChanges(stateName, planName types.String, resourceLabel string, diags *diag.Diagnostics) { + if utils.IsUndefined(stateName) { + return + } + if planName.Equal(stateName) { + return + } + diags.AddWarning( + fmt.Sprintf("%s name change requires resource replacement", resourceLabel), + fmt.Sprintf( + "Changing the \"name\" attribute from %q to %q will destroy and recreate this resource. "+ + "If another resource references this %s by name, the replacement will fail. Remove or update that dependency before applying this change.", + stateName.ValueString(), planName.ValueString(), resourceLabel, + ), + ) +} diff --git a/stackit/internal/services/albwaf/waf_configuration/resource.go b/stackit/internal/services/albwaf/waf_configuration/resource.go index 474a65502..481c0d18e 100644 --- a/stackit/internal/services/albwaf/waf_configuration/resource.go +++ b/stackit/internal/services/albwaf/waf_configuration/resource.go @@ -87,6 +87,14 @@ func (r *wafResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanReq return } + if !req.State.Raw.IsNull() { + var stateModel Model + resp.Diagnostics.Append(req.State.Get(ctx, &stateModel)...) + if !resp.Diagnostics.HasError() { + albwafUtils.WarnIfNameChanges(stateModel.Name, planModel.Name, "WAF Configuration", &resp.Diagnostics) + } + } + resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...) if resp.Diagnostics.HasError() { return @@ -407,7 +415,7 @@ func toUpdatePayload(ctx context.Context, model *Model) (*albWaf.UpdateWAFPayloa } var labels *map[string]string - if !(model.Labels.IsNull() || model.Labels.IsUnknown()) { + if !tfutils.IsUndefined(model.Labels) { diags := model.Labels.ElementsAs(ctx, &labels, false) if diags.HasError() { return nil, core.DiagsToError(diags) @@ -426,7 +434,7 @@ func toCreatePayload(ctx context.Context, model *Model) (*albWaf.CreateWAFPayloa } var labels *map[string]string - if !(model.Labels.IsNull() || model.Labels.IsUnknown()) { + if !tfutils.IsUndefined(model.Labels) { diags := model.Labels.ElementsAs(ctx, &labels, false) if diags.HasError() { return nil, core.DiagsToError(diags) diff --git a/stackit/internal/services/albwaf/waf_configuration/resource_test.go b/stackit/internal/services/albwaf/waf_configuration/resource_test.go index e35b06834..b009feffa 100644 --- a/stackit/internal/services/albwaf/waf_configuration/resource_test.go +++ b/stackit/internal/services/albwaf/waf_configuration/resource_test.go @@ -17,7 +17,7 @@ const ( testCustomRuleGroupName = "crg_name" ) -func Test_mapFields(t *testing.T) { +func TestMapFields(t *testing.T) { fixtureModel := func(mods ...func(*Model)) *Model { m := Model{ Id: types.StringValue(fmt.Sprintf("pid,%s,name", testRegion)), @@ -105,7 +105,7 @@ func Test_mapFields(t *testing.T) { } } -func Test_toCreatePayload(t *testing.T) { +func TestToCreatePayload(t *testing.T) { tests := []struct { name string model *Model