From 3492bf4b766b4b5890325da9af464193f5fe5470 Mon Sep 17 00:00:00 2001 From: Sagar Barai Date: Wed, 24 Sep 2025 10:06:28 +0530 Subject: [PATCH 1/6] add managed_rule_group_configs and visibility_config feature to wafv2_web_acl_rule_group_association --- .../wafv2/web_acl_rule_group_association.go | 924 ++++++++++++++++- .../web_acl_rule_group_association_test.go | 948 +++++++++++++++++- ...b_acl_rule_group_association.html.markdown | 181 ++++ 3 files changed, 2002 insertions(+), 51 deletions(-) diff --git a/internal/service/wafv2/web_acl_rule_group_association.go b/internal/service/wafv2/web_acl_rule_group_association.go index a21361204a9c..325f1ad1ea74 100644 --- a/internal/service/wafv2/web_acl_rule_group_association.go +++ b/internal/service/wafv2/web_acl_rule_group_association.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws/arn" "github.com/aws/aws-sdk-go-v2/service/wafv2" @@ -17,10 +18,14 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" @@ -64,6 +69,431 @@ type resourceWebACLRuleGroupAssociation struct { } func (r *resourceWebACLRuleGroupAssociation) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + usernameFieldLNB := schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[usernameFieldModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + names.AttrIdentifier: schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.All( + stringvalidator.LengthBetween(1, 512), + stringvalidator.RegexMatches( + regexache.MustCompile(`\S`), + "can not be empty string", + ), + ), + }, + Description: "Identifier of the username field", + }, + }, + }, + } + + passwordFieldLNB := schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[passwordFieldModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + names.AttrIdentifier: schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.All( + stringvalidator.LengthBetween(1, 512), + stringvalidator.RegexMatches( + regexache.MustCompile(`\S`), + "can not be empty string", + ), + ), + }, + Description: "Identifier of the password field", + }, + }, + }, + } + + managedRulegroupConfigACFPRequestInspectionLNB := schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[requestInspectionACFPModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "payload_type": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.PayloadType](), + Required: true, + Description: "Payload type for inspection, either JSON or FORM_ENCODED.", + }, + }, + Blocks: map[string]schema.Block{ + "username_field": usernameFieldLNB, + "address_fields": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[addressFieldModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + listvalidator.SizeAtLeast(0), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "identifiers": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + Description: "Identifiers of the address fields", + }, + }, + }, + }, + "email_field": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[emailFieldModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + listvalidator.SizeAtLeast(0), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + names.AttrIdentifier: schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.All( + stringvalidator.LengthBetween(1, 512), + stringvalidator.RegexMatches( + regexache.MustCompile(`\S`), + "can not be empty string", + ), + ), + }, + Description: "Identifier of the email field", + }, + }, + }, + }, + "password_field": passwordFieldLNB, + "phone_number_fields": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[phoneNumberFieldModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + listvalidator.SizeAtLeast(0), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "identifiers": schema.ListAttribute{ + ElementType: types.StringType, + Required: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + Description: "Identifiers of the phone number fields", + }, + }, + }, + }, + }, + }, + } + managedRulegroupConfigATPRequestInspectionLNB := schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[requestInspectionModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "payload_type": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.PayloadType](), + Required: true, + Description: "Payload type for inspection, either JSON or FORM_ENCODED.", + }, + }, + Blocks: map[string]schema.Block{ + "password_field": passwordFieldLNB, + "username_field": usernameFieldLNB, + }, + }, + } + managedRulegroupConfigResponseInspectionLNB := schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[responseInspectionModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "body_contains": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[responseInspectionBodyContainsModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "failure_strings": schema.SetAttribute{ + Required: true, + ElementType: types.StringType, + Description: "Strings that indicate a failed login or account creation attempt", + }, + "success_strings": schema.SetAttribute{ + Required: true, + ElementType: types.StringType, + Description: "Strings that indicate a successful login or account creation attempt", + }, + }, + }, + }, + names.AttrHeader: schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[responseInspectionHeaderModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + names.AttrName: schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 256), + }, + Description: "Name of the HTTP header to inspect", + }, + "failure_values": schema.SetAttribute{ + Required: true, + ElementType: types.StringType, + Description: "Strings that indicate a failed login or account creation attempt", + }, + "success_values": schema.SetAttribute{ + Required: true, + ElementType: types.StringType, + Description: "Strings that indicate a successful login or account creation attempt", + }, + }, + }, + }, + names.AttrJSON: schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[responseInspectionJsonModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + names.AttrIdentifier: schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 256), + }, + Description: "Identifier of the JSON field to inspect", + }, + "failure_values": schema.SetAttribute{ + Required: true, + ElementType: types.StringType, + Description: "Strings that indicate a failed login or account creation attempt", + }, + "success_values": schema.SetAttribute{ + Required: true, + ElementType: types.StringType, + Description: "Strings that indicate a successful login or account creation attempt", + }, + }, + }, + }, + names.AttrStatusCode: schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[responseInspectionStatusCodeModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "failure_codes": schema.SetAttribute{ + Required: true, + ElementType: types.Int32Type, + Description: "Status codes that indicate a failed login or account creation attempt", + }, + "success_codes": schema.SetAttribute{ + Required: true, + ElementType: types.Int32Type, + Description: "Status codes that indicate a successful login or account creation attempt", + }, + }, + }, + }, + }, + }, + } + managedRulegroupConfigLNB := schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[managedRuleGroupConfigModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "aws_managed_rules_acfp_rule_set": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[awsManagedRulesACFPRuleSetModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "creation_path": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.All( + stringvalidator.LengthBetween(1, 256), + stringvalidator.RegexMatches( + regexache.MustCompile(`\S`), + "can not be empty string", + ), + ), + }, + Description: "Path to the account creation endpoint on the protected website", + }, + "enable_regex_in_path": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "registration_page_path": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.All( + stringvalidator.LengthBetween(1, 256), + stringvalidator.RegexMatches( + regexache.MustCompile(`\S`), + "can not be empty string", + ), + ), + }, + }, + }, + Blocks: map[string]schema.Block{ + "request_inspection": managedRulegroupConfigACFPRequestInspectionLNB, + "response_inspection": managedRulegroupConfigResponseInspectionLNB, + }, + }, + }, + "aws_managed_rules_anti_ddos_rule_set": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[awsManagedRulesAntiDDoSRuleSetModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "sensitivity_to_block": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.SensitivityToAct](), + Optional: true, + Computed: true, + Default: stringdefault.StaticString(string(awstypes.SensitivityToActLow)), + }, + }, + Blocks: map[string]schema.Block{ + "client_side_action_config": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[clientSideActionConfigModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + listvalidator.SizeAtLeast(1), + }, + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "challenge": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[clientSideActionModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + listvalidator.SizeAtLeast(1), + }, + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "exempt_uri_regular_expression": schema.ListNestedBlock{ + //CustomType: fwtypes.NewListNestedObjectTypeOf[exemptUriRegularExpressionModel](ctx), + CustomType: fwtypes.NewListNestedObjectTypeOf[regexModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(5), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "regex_string": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 512), + }, + }, + }, + }, + }, + }, + Attributes: map[string]schema.Attribute{ + "sensitivity": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.SensitivityToAct](), + Optional: true, + Computed: true, + Default: stringdefault.StaticString(string(awstypes.SensitivityToActHigh)), + }, + "usage_of_action": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.UsageOfAction](), + Required: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "aws_managed_rules_atp_rule_set": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[awsManagedRulesATPRuleSetModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "enable_regex_in_path": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "login_path": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.All( + stringvalidator.LengthBetween(1, 256), + stringvalidator.RegexMatches( + regexache.MustCompile(`\S`), + "can not be empty string", + ), + ), + }, + }, + }, + Blocks: map[string]schema.Block{ + "request_inspection": managedRulegroupConfigATPRequestInspectionLNB, + "response_inspection": managedRulegroupConfigResponseInspectionLNB, + }, + }, + }, + "aws_managed_rules_bot_control_rule_set": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[awsManagedRulesBotControlRuleSetModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "enable_machine_learning": schema.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(false), + Computed: true, + }, + "inspection_level": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[awstypes.InspectionLevel](), + Required: true, + }, + }, + }, + }, + }, + }, + } ruleActionOverrideLNB := schema.ListNestedBlock{ CustomType: fwtypes.NewListNestedObjectTypeOf[ruleActionOverrideModel](ctx), Validators: []validator.List{ @@ -426,11 +856,44 @@ func (r *resourceWebACLRuleGroupAssociation) Schema(ctx context.Context, req res }, }, Blocks: map[string]schema.Block{ - "rule_action_override": ruleActionOverrideLNB, + "rule_action_override": ruleActionOverrideLNB, + "managed_rule_group_configs": managedRulegroupConfigLNB, }, }, Description: "Managed rule group configuration.", }, + "visibility_config": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[visibilityConfigModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "sampled_requests_enabled": schema.BoolAttribute{ + Required: true, + Description: "Indicates whether to store a sampling of the web requests that match the rule.", + }, + "cloudwatch_metrics_enabled": schema.BoolAttribute{ + Required: true, + Description: "Indicates whether the rule is available for use in the metrics for the web ACL.", + }, + names.AttrMetricName: schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.All( + stringvalidator.LengthBetween(1, 128), + stringvalidator.RegexMatches( + regexache.MustCompile(`^[0-9A-Za-z_-]+$`), + "can only contain alphanumeric characters, hyphens, underscores, and colons", + ), + ), + }, + Description: "A name for the metrics for this rule.", + }, + }, + }, + Description: "Visibility configuration for the rule.", + }, names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{ Create: true, Update: true, @@ -551,6 +1014,45 @@ func (r *resourceWebACLRuleGroupAssociation) Create(ctx context.Context, req res managedRuleGroupStatement.Version = aws.String(ruleGroupVersion) } + // Add managed rule group configurations if specified + if !managedRuleGroupRef.ManagedRuleGroupConfig.IsNull() && !managedRuleGroupRef.ManagedRuleGroupConfig.IsUnknown() { + var tfManagedRuleGroupConfigModel managedRuleGroupConfigModel + var managedRuleGroupConfigs []awstypes.ManagedRuleGroupConfig + + resp.Diagnostics.Append(managedRuleGroupRef.ManagedRuleGroupConfig.Elements()[0].(fwtypes.ObjectValueOf[managedRuleGroupConfigModel]).As(ctx, &tfManagedRuleGroupConfigModel, basetypes.ObjectAsOptions{})...) + if resp.Diagnostics.HasError() { + return + } + + if !tfManagedRuleGroupConfigModel.AWSManagedRulesACFPRuleSet.IsNull() && !tfManagedRuleGroupConfigModel.AWSManagedRulesACFPRuleSet.IsUnknown() { + // custom expand for ACFP Managed Rule Group Config + var tfACFPConfig awsManagedRulesACFPRuleSetModel + resp.Diagnostics.Append(tfManagedRuleGroupConfigModel.AWSManagedRulesACFPRuleSet.Elements()[0].(fwtypes.ObjectValueOf[awsManagedRulesACFPRuleSetModel]).As(ctx, &tfACFPConfig, basetypes.ObjectAsOptions{})...) + if resp.Diagnostics.HasError() { + return + } + + acfpConfig, diags := expandACFPManagedRuleConfig(ctx, tfACFPConfig) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + managedRuleGroupConfigs = append(managedRuleGroupConfigs, awstypes.ManagedRuleGroupConfig{ + AWSManagedRulesACFPRuleSet: &acfpConfig, + }) + + } else { + // let auto flex handle expand + resp.Diagnostics.Append(fwflex.Expand(ctx, managedRuleGroupRef.ManagedRuleGroupConfig, &managedRuleGroupConfigs)...) + if resp.Diagnostics.HasError() { + return + } + } + + managedRuleGroupStatement.ManagedRuleGroupConfigs = managedRuleGroupConfigs + } + // Add rule action overrides if specified if !managedRuleGroupRef.RuleActionOverride.IsNull() && !managedRuleGroupRef.RuleActionOverride.IsUnknown() { resp.Diagnostics.Append(fwflex.Expand(ctx, managedRuleGroupRef.RuleActionOverride, &ruleActionOverrides)...) @@ -574,16 +1076,29 @@ func (r *resourceWebACLRuleGroupAssociation) Create(ctx context.Context, req res return } - // Create new rule with the appropriate statement type - newRule := awstypes.Rule{ - Name: plan.RuleName.ValueStringPointer(), - Priority: plan.Priority.ValueInt32(), - Statement: ruleStatement, - VisibilityConfig: &awstypes.VisibilityConfig{ + // Build visibility config for the new rule + var visibilityConfig awstypes.VisibilityConfig + if !plan.VisibilityConfig.IsNull() && !plan.VisibilityConfig.IsUnknown() { + // Expand user-provided visibility_config into AWS SDK type + resp.Diagnostics.Append(fwflex.Expand(ctx, plan.VisibilityConfig, &visibilityConfig)...) + if resp.Diagnostics.HasError() { + return + } + } else { + // Use defaults when not provided in plan, for backward compability of this provider + visibilityConfig = awstypes.VisibilityConfig{ SampledRequestsEnabled: true, CloudWatchMetricsEnabled: true, MetricName: plan.RuleName.ValueStringPointer(), - }, + } + } + + // Create new rule with the appropriate statement type + newRule := awstypes.Rule{ + Name: plan.RuleName.ValueStringPointer(), + Priority: plan.Priority.ValueInt32(), + Statement: ruleStatement, + VisibilityConfig: &visibilityConfig, } // Set override action @@ -758,8 +1273,43 @@ func (r *resourceWebACLRuleGroupAssociation) Read(ctx context.Context, req resou ruleActionOverrides = fwtypes.NewListNestedObjectValueOfNull[ruleActionOverrideModel](ctx) } + var managedRuleGroupConfigs fwtypes.ListNestedObjectValueOf[managedRuleGroupConfigModel] + if managedStmt.ManagedRuleGroupConfigs != nil && len(managedStmt.ManagedRuleGroupConfigs) > 0 { + + sdkManagedRuleGroupConfigs := managedStmt.ManagedRuleGroupConfigs[0] + + if acfp := sdkManagedRuleGroupConfigs.AWSManagedRulesACFPRuleSet; acfp != nil { + var diags diag.Diagnostics + managedRuleGroupConfigs, diags = flattenACFPRuleConfig(ctx, acfp, managedStmt.ManagedRuleGroupConfigs) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + } else { + resp.Diagnostics.Append(fwflex.Flatten(ctx, managedStmt.ManagedRuleGroupConfigs, &managedRuleGroupConfigs)...) + if resp.Diagnostics.HasError() { + return + } + + if botControl := sdkManagedRuleGroupConfigs.AWSManagedRulesBotControlRuleSet; botControl != nil { + var tfManagedRuleGroupConfigModel managedRuleGroupConfigModel + resp.Diagnostics.Append(managedRuleGroupConfigs.Elements()[0].(fwtypes.ObjectValueOf[managedRuleGroupConfigModel]).As(ctx, &tfManagedRuleGroupConfigModel, basetypes.ObjectAsOptions{})...) + if resp.Diagnostics.HasError() { + return + } + tfManagedRuleGroupConfigModel = patchBotControlFlatten(ctx, botControl, tfManagedRuleGroupConfigModel) + managedRuleGroupConfigs = fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*managedRuleGroupConfigModel{&tfManagedRuleGroupConfigModel}) + } + } + + } else { + managedRuleGroupConfigs = fwtypes.NewListNestedObjectValueOfNull[managedRuleGroupConfigModel](ctx) + } + // Update the managed rule group nested structure managedRuleGroupRef.RuleActionOverride = ruleActionOverrides + managedRuleGroupRef.ManagedRuleGroupConfig = managedRuleGroupConfigs listValue, diags := fwtypes.NewListNestedObjectValueOfSlice(ctx, []*managedRuleGroupModel{&managedRuleGroupRef}, nil) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -776,6 +1326,22 @@ func (r *resourceWebACLRuleGroupAssociation) Read(ctx context.Context, req resou found = true state.Priority = types.Int32Value(rule.Priority) + var visibilityConfig fwtypes.ListNestedObjectValueOf[visibilityConfigModel] + // for backward compability, visibility_config to be read only if it's not set to default + if rule.VisibilityConfig != nil { + isDefault := rule.VisibilityConfig.SampledRequestsEnabled && + rule.VisibilityConfig.CloudWatchMetricsEnabled && + aws.ToString(rule.VisibilityConfig.MetricName) == aws.ToString(rule.Name) + + if !isDefault { + resp.Diagnostics.Append(fwflex.Flatten(ctx, rule.VisibilityConfig, &visibilityConfig)...) + if resp.Diagnostics.HasError() { + return + } + state.VisibilityConfig = visibilityConfig + } + } + // Determine override action overrideAction := overrideActionNone if rule.OverrideAction != nil { @@ -862,8 +1428,27 @@ func (r *resourceWebACLRuleGroupAssociation) Update(ctx context.Context, req res } } + // update visibility config if changed + var visibilityConfig awstypes.VisibilityConfig + if !plan.VisibilityConfig.IsNull() && !plan.VisibilityConfig.IsUnknown() { + + resp.Diagnostics.Append(fwflex.Expand(ctx, plan.VisibilityConfig, &visibilityConfig)...) + if resp.Diagnostics.HasError() { + return + } + webACL.WebACL.Rules[i].VisibilityConfig = &visibilityConfig + } else { + visibilityConfig = awstypes.VisibilityConfig{ + SampledRequestsEnabled: true, + CloudWatchMetricsEnabled: true, + MetricName: plan.RuleName.ValueStringPointer(), + } + } + // Update rule action overrides from nested structure (both custom and managed) var overrides []awstypes.RuleActionOverride + var managedRuleGroupConfigs []awstypes.ManagedRuleGroupConfig + if !plan.RuleGroupReference.IsNull() && !plan.RuleGroupReference.IsUnknown() { ruleGroupRefs := plan.RuleGroupReference.Elements() if len(ruleGroupRefs) > 0 { @@ -895,6 +1480,41 @@ func (r *resourceWebACLRuleGroupAssociation) Update(ctx context.Context, req res return } } + + if !managedRuleGroupRef.ManagedRuleGroupConfig.IsNull() && !managedRuleGroupRef.ManagedRuleGroupConfig.IsUnknown() { + var tfManagedRuleGroupConfigModel managedRuleGroupConfigModel + + resp.Diagnostics.Append(managedRuleGroupRef.ManagedRuleGroupConfig.Elements()[0].(fwtypes.ObjectValueOf[managedRuleGroupConfigModel]).As(ctx, &tfManagedRuleGroupConfigModel, basetypes.ObjectAsOptions{})...) + if resp.Diagnostics.HasError() { + return + } + + if !tfManagedRuleGroupConfigModel.AWSManagedRulesACFPRuleSet.IsNull() && !tfManagedRuleGroupConfigModel.AWSManagedRulesACFPRuleSet.IsUnknown() { + // custom expand for ACFP Managed Rule Group Config + var tfACFPConfig awsManagedRulesACFPRuleSetModel + resp.Diagnostics.Append(tfManagedRuleGroupConfigModel.AWSManagedRulesACFPRuleSet.Elements()[0].(fwtypes.ObjectValueOf[awsManagedRulesACFPRuleSetModel]).As(ctx, &tfACFPConfig, basetypes.ObjectAsOptions{})...) + if resp.Diagnostics.HasError() { + return + } + + acfpConfig, diags := expandACFPManagedRuleConfig(ctx, tfACFPConfig) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + managedRuleGroupConfigs = append(managedRuleGroupConfigs, awstypes.ManagedRuleGroupConfig{ + AWSManagedRulesACFPRuleSet: &acfpConfig, + }) + + } else { + // let auto flex handle expand + resp.Diagnostics.Append(fwflex.Expand(ctx, managedRuleGroupRef.ManagedRuleGroupConfig, &managedRuleGroupConfigs)...) + if resp.Diagnostics.HasError() { + return + } + } + } } } @@ -904,6 +1524,7 @@ func (r *resourceWebACLRuleGroupAssociation) Update(ctx context.Context, req res webACL.WebACL.Rules[i].Statement.RuleGroupReferenceStatement.RuleActionOverrides = overrides } else if webACL.WebACL.Rules[i].Statement.ManagedRuleGroupStatement != nil { webACL.WebACL.Rules[i].Statement.ManagedRuleGroupStatement.RuleActionOverrides = overrides + webACL.WebACL.Rules[i].Statement.ManagedRuleGroupStatement.ManagedRuleGroupConfigs = managedRuleGroupConfigs } } @@ -1131,6 +1752,8 @@ func (r *resourceWebACLRuleGroupAssociation) ImportState(ctx context.Context, re Name: types.StringValue(ruleGroupName), VendorName: types.StringValue(vendorName), RuleActionOverride: fwtypes.NewListNestedObjectValueOfNull[ruleActionOverrideModel](ctx), + + ManagedRuleGroupConfig: fwtypes.NewListNestedObjectValueOfNull[managedRuleGroupConfigModel](ctx), } if version != "" { managedRuleGroupRef.Version = types.StringValue(version) @@ -1186,6 +1809,172 @@ func parseWebACLARN(arn string) (id, name, scope string, err error) { return resourceParts[idIndex], resourceParts[nameIndex], scopeValue, nil } +func flattenACFPRuleConfig( + ctx context.Context, + acfp *awstypes.AWSManagedRulesACFPRuleSet, + managedRuleGroupConfigs []awstypes.ManagedRuleGroupConfig, +) (fwtypes.ListNestedObjectValueOf[managedRuleGroupConfigModel], diag.Diagnostics) { + var diags diag.Diagnostics + + if acfp.RequestInspection == nil { + // autoflex is possible + var tfManagedRuleGroupConfigModel managedRuleGroupConfigModel + diags.Append(fwflex.Flatten(ctx, managedRuleGroupConfigs, &tfManagedRuleGroupConfigModel)...) + return fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*managedRuleGroupConfigModel{&tfManagedRuleGroupConfigModel}), diags + } + + requestInspection := acfp.RequestInspection + var emailField emailFieldModel + var usernameField usernameFieldModel + var passwordField passwordFieldModel + + fwflex.Flatten(ctx, requestInspection.EmailField, &emailField) + fwflex.Flatten(ctx, requestInspection.UsernameField, &usernameField) + fwflex.Flatten(ctx, requestInspection.PasswordField, &passwordField) + + var addrList []attr.Value + for _, af := range requestInspection.AddressFields { + addrList = append(addrList, types.StringValue(aws.ToString(af.Identifier))) + } + ids := fwtypes.NewListValueOfMust[types.String](ctx, addrList) + afModel := &addressFieldModel{ + Identifiers: ids, + } + + var phoneList []attr.Value + for _, pf := range requestInspection.PhoneNumberFields { + phoneList = append(phoneList, types.StringValue(aws.ToString(pf.Identifier))) + } + pfModel := &phoneNumberFieldModel{ + Identifiers: fwtypes.NewListValueOfMust[types.String](ctx, phoneList), + } + + tfRequestInspection := &requestInspectionACFPModel{ + PayloadType: types.StringValue(string(requestInspection.PayloadType)), + EmailField: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*emailFieldModel{&emailField}), + UsernameField: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*usernameFieldModel{&usernameField}), + PasswordField: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*passwordFieldModel{&passwordField}), + AddressFields: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*addressFieldModel{afModel}), + PhoneNumberFields: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*phoneNumberFieldModel{pfModel}), + } + + tfACFPModel := &awsManagedRulesACFPRuleSetModel{ + CreationPath: types.StringValue(aws.ToString(acfp.CreationPath)), + RegistrationPagePath: types.StringValue(aws.ToString(acfp.RegistrationPagePath)), + EnableRegexInPath: types.BoolValue(acfp.EnableRegexInPath), + ResponseInspection: fwtypes.NewListNestedObjectValueOfNull[responseInspectionModel](ctx), + RequestInspection: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*requestInspectionACFPModel{tfRequestInspection}), + } + + patchedRuleGroupConfig := make([]*managedRuleGroupConfigModel, 0, 1) + + tfManagedRuleGroupConfigModel := &managedRuleGroupConfigModel{ + AWSManagedRulesACFPRuleSet: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*awsManagedRulesACFPRuleSetModel{tfACFPModel}), + AWSManagedRulesATPRuleSet: fwtypes.NewListNestedObjectValueOfNull[awsManagedRulesATPRuleSetModel](ctx), + AWSManagedRulesBotControlRuleSet: fwtypes.NewListNestedObjectValueOfNull[awsManagedRulesBotControlRuleSetModel](ctx), + AWSManagedRulesAntiDDoSRuleSet: fwtypes.NewListNestedObjectValueOfNull[awsManagedRulesAntiDDoSRuleSetModel](ctx), + } + patchedRuleGroupConfig = append(patchedRuleGroupConfig, tfManagedRuleGroupConfigModel) + + return fwtypes.NewListNestedObjectValueOfSliceMust(ctx, patchedRuleGroupConfig), diags +} + +func patchBotControlFlatten( + ctx context.Context, + botControl *awstypes.AWSManagedRulesBotControlRuleSet, + tfManagedRuleGroupConfigModel managedRuleGroupConfigModel, +) managedRuleGroupConfigModel { + + if botControl.InspectionLevel == awstypes.InspectionLevelCommon { + tfBotControlModelConfig := awsManagedRulesBotControlRuleSetModel{ + InspectionLevel: types.StringValue(string(awstypes.InspectionLevelCommon)), + EnableMachineLearning: types.BoolValue(false), + } + tfManagedRuleGroupConfigModel.AWSManagedRulesBotControlRuleSet = fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*awsManagedRulesBotControlRuleSetModel{&tfBotControlModelConfig}) + } + + return tfManagedRuleGroupConfigModel +} + +func expandACFPManagedRuleConfig( + ctx context.Context, + tfACFPRuleConfigModel awsManagedRulesACFPRuleSetModel, +) (awstypes.AWSManagedRulesACFPRuleSet, diag.Diagnostics) { + var diags diag.Diagnostics + var acfpRuleSetConfig awstypes.AWSManagedRulesACFPRuleSet + + if tfACFPRuleConfigModel.RequestInspection.IsNull() && tfACFPRuleConfigModel.RequestInspection.IsUnknown() { + // auto flex works, no need to manually expand + diags.Append(fwflex.Expand(ctx, tfACFPRuleConfigModel, &acfpRuleSetConfig)...) + return acfpRuleSetConfig, diags + } + + acfpRuleSetConfig = awstypes.AWSManagedRulesACFPRuleSet{ + CreationPath: tfACFPRuleConfigModel.CreationPath.ValueStringPointer(), + RegistrationPagePath: tfACFPRuleConfigModel.RegistrationPagePath.ValueStringPointer(), + EnableRegexInPath: tfACFPRuleConfigModel.EnableRegexInPath.ValueBool(), + } + + var tfReqInspection requestInspectionACFPModel + diags.Append(tfACFPRuleConfigModel.RequestInspection.Elements()[0].(fwtypes.ObjectValueOf[requestInspectionACFPModel]).As(ctx, &tfReqInspection, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return acfpRuleSetConfig, diags + } + + addressFields := make([]awstypes.AddressField, 0) + + for _, af := range tfReqInspection.AddressFields.Elements() { + var tfAddressField addressFieldModel + diags.Append(af.(fwtypes.ObjectValueOf[addressFieldModel]).As(ctx, &tfAddressField, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return acfpRuleSetConfig, diags + } + for _, id := range tfAddressField.Identifiers.Elements() { + addressFields = append(addressFields, awstypes.AddressField{ + Identifier: aws.String(id.(types.String).ValueString()), + }) + } + } + + phoneNumberFields := make([]awstypes.PhoneNumberField, 0) + for _, pf := range tfReqInspection.PhoneNumberFields.Elements() { + var tfPhoneNumberField phoneNumberFieldModel + diags.Append(pf.(fwtypes.ObjectValueOf[phoneNumberFieldModel]).As(ctx, &tfPhoneNumberField, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return acfpRuleSetConfig, diags + } + for _, id := range tfPhoneNumberField.Identifiers.Elements() { + phoneNumberFields = append(phoneNumberFields, awstypes.PhoneNumberField{ + Identifier: aws.String(id.(types.String).ValueString()), + }) + } + } + + acfpRuleSetConfig.RequestInspection = &awstypes.RequestInspectionACFP{ + AddressFields: addressFields, + PhoneNumberFields: phoneNumberFields, + PayloadType: awstypes.PayloadType(tfReqInspection.PayloadType.ValueString()), + } + + diags.Append(fwflex.Expand(ctx, tfReqInspection.EmailField, &acfpRuleSetConfig.RequestInspection.EmailField)...) + if diags.HasError() { + return acfpRuleSetConfig, diags + } + + diags.Append(fwflex.Expand(ctx, tfReqInspection.UsernameField, &acfpRuleSetConfig.RequestInspection.UsernameField)...) + if diags.HasError() { + return acfpRuleSetConfig, diags + } + + diags.Append(fwflex.Expand(ctx, tfReqInspection.PasswordField, &acfpRuleSetConfig.RequestInspection.PasswordField)...) + if diags.HasError() { + return acfpRuleSetConfig, diags + } + + return acfpRuleSetConfig, diags + +} + type resourceWebACLRuleGroupAssociationModel struct { framework.WithRegionModel RuleName types.String `tfsdk:"rule_name"` @@ -1194,9 +1983,16 @@ type resourceWebACLRuleGroupAssociationModel struct { ManagedRuleGroup fwtypes.ListNestedObjectValueOf[managedRuleGroupModel] `tfsdk:"managed_rule_group"` WebACLARN types.String `tfsdk:"web_acl_arn"` OverrideAction types.String `tfsdk:"override_action"` + VisibilityConfig fwtypes.ListNestedObjectValueOf[visibilityConfigModel] `tfsdk:"visibility_config"` Timeouts timeouts.Value `tfsdk:"timeouts"` } +type visibilityConfigModel struct { + CloudWatchMetricsEnabled types.Bool `tfsdk:"cloudwatch_metrics_enabled"` + MetricName types.String `tfsdk:"metric_name"` + SampledRequestsEnabled types.Bool `tfsdk:"sampled_requests_enabled"` +} + type ruleGroupReferenceModel struct { ARN types.String `tfsdk:"arn"` RuleActionOverride fwtypes.ListNestedObjectValueOf[ruleActionOverrideModel] `tfsdk:"rule_action_override"` @@ -1207,6 +2003,118 @@ type managedRuleGroupModel struct { VendorName types.String `tfsdk:"vendor_name"` Version types.String `tfsdk:"version"` RuleActionOverride fwtypes.ListNestedObjectValueOf[ruleActionOverrideModel] `tfsdk:"rule_action_override"` + + ManagedRuleGroupConfig fwtypes.ListNestedObjectValueOf[managedRuleGroupConfigModel] `tfsdk:"managed_rule_group_configs"` +} + +type managedRuleGroupConfigModel struct { + AWSManagedRulesACFPRuleSet fwtypes.ListNestedObjectValueOf[awsManagedRulesACFPRuleSetModel] `tfsdk:"aws_managed_rules_acfp_rule_set"` + AWSManagedRulesATPRuleSet fwtypes.ListNestedObjectValueOf[awsManagedRulesATPRuleSetModel] `tfsdk:"aws_managed_rules_atp_rule_set"` + AWSManagedRulesAntiDDoSRuleSet fwtypes.ListNestedObjectValueOf[awsManagedRulesAntiDDoSRuleSetModel] `tfsdk:"aws_managed_rules_anti_ddos_rule_set"` + AWSManagedRulesBotControlRuleSet fwtypes.ListNestedObjectValueOf[awsManagedRulesBotControlRuleSetModel] `tfsdk:"aws_managed_rules_bot_control_rule_set"` +} + +type awsManagedRulesBotControlRuleSetModel struct { + InspectionLevel types.String `tfsdk:"inspection_level"` + EnableMachineLearning types.Bool `tfsdk:"enable_machine_learning"` +} + +type awsManagedRulesAntiDDoSRuleSetModel struct { + ClientSideActionConfig fwtypes.ListNestedObjectValueOf[clientSideActionConfigModel] `tfsdk:"client_side_action_config"` + SensitivityToBlock types.String `tfsdk:"sensitivity_to_block"` +} + +type clientSideActionConfigModel struct { + Challenge fwtypes.ListNestedObjectValueOf[clientSideActionModel] `tfsdk:"challenge"` +} + +type clientSideActionModel struct { + UsageOfAction types.String `tfsdk:"usage_of_action"` + ExemptUriRegularExpressions fwtypes.ListNestedObjectValueOf[regexModel] `tfsdk:"exempt_uri_regular_expression"` + Sensitivity types.String `tfsdk:"sensitivity"` +} + +type regexModel struct { + RegexString types.String `tfsdk:"regex_string"` +} + +type awsManagedRulesATPRuleSetModel struct { + LoginPath types.String `tfsdk:"login_path"` + EnableRegexInPath types.Bool `tfsdk:"enable_regex_in_path"` + RequestInspection fwtypes.ListNestedObjectValueOf[requestInspectionModel] `tfsdk:"request_inspection"` + ResponseInspection fwtypes.ListNestedObjectValueOf[responseInspectionModel] `tfsdk:"response_inspection"` +} + +type requestInspectionModel struct { + PasswordField fwtypes.ListNestedObjectValueOf[passwordFieldModel] `tfsdk:"password_field"` + PayloadType types.String `tfsdk:"payload_type"` + UsernameField fwtypes.ListNestedObjectValueOf[usernameFieldModel] `tfsdk:"username_field"` +} + +type usernameFieldModel struct { + Identifier types.String `tfsdk:"identifier"` +} + +type passwordFieldModel struct { + Identifier types.String `tfsdk:"identifier"` +} + +type awsManagedRulesACFPRuleSetModel struct { + CreationPath types.String `tfsdk:"creation_path"` + RegistrationPagePath types.String `tfsdk:"registration_page_path"` + RequestInspection fwtypes.ListNestedObjectValueOf[requestInspectionACFPModel] `tfsdk:"request_inspection"` + EnableRegexInPath types.Bool `tfsdk:"enable_regex_in_path"` + ResponseInspection fwtypes.ListNestedObjectValueOf[responseInspectionModel] `tfsdk:"response_inspection"` +} + +type requestInspectionACFPModel struct { + PayloadType types.String `tfsdk:"payload_type"` + AddressFields fwtypes.ListNestedObjectValueOf[addressFieldModel] `tfsdk:"address_fields"` + EmailField fwtypes.ListNestedObjectValueOf[emailFieldModel] `tfsdk:"email_field"` + PasswordField fwtypes.ListNestedObjectValueOf[passwordFieldModel] `tfsdk:"password_field"` + UsernameField fwtypes.ListNestedObjectValueOf[usernameFieldModel] `tfsdk:"username_field"` + PhoneNumberFields fwtypes.ListNestedObjectValueOf[phoneNumberFieldModel] `tfsdk:"phone_number_fields"` +} + +type phoneNumberFieldModel struct { + Identifiers fwtypes.ListValueOf[types.String] `tfsdk:"identifiers"` +} + +type emailFieldModel struct { + Identifier types.String `tfsdk:"identifier"` +} + +type addressFieldModel struct { + Identifiers fwtypes.ListValueOf[types.String] `tfsdk:"identifiers"` +} + +type responseInspectionModel struct { + BodyContains fwtypes.ListNestedObjectValueOf[responseInspectionBodyContainsModel] `tfsdk:"body_contains"` + Header fwtypes.ListNestedObjectValueOf[responseInspectionHeaderModel] `tfsdk:"header"` + Json fwtypes.ListNestedObjectValueOf[responseInspectionJsonModel] `tfsdk:"json"` + StatusCode fwtypes.ListNestedObjectValueOf[responseInspectionStatusCodeModel] `tfsdk:"status_code"` +} + +type responseInspectionBodyContainsModel struct { + FailureStrings fwtypes.SetValueOf[types.String] `tfsdk:"failure_strings"` + SuccessStrings fwtypes.SetValueOf[types.String] `tfsdk:"success_strings"` +} + +type responseInspectionHeaderModel struct { + FailureValues fwtypes.SetValueOf[types.String] `tfsdk:"failure_values"` + Name types.String `tfsdk:"name"` + SuccessValues fwtypes.SetValueOf[types.String] `tfsdk:"success_values"` +} + +type responseInspectionJsonModel struct { + FailureValues fwtypes.SetValueOf[types.String] `tfsdk:"failure_values"` + Identifier types.String `tfsdk:"identifier"` + SuccessValues fwtypes.SetValueOf[types.String] `tfsdk:"success_values"` +} + +type responseInspectionStatusCodeModel struct { + FailureCodes fwtypes.SetValueOf[types.Int32] `tfsdk:"failure_codes"` + SuccessCodes fwtypes.SetValueOf[types.Int32] `tfsdk:"success_codes"` } type ruleActionOverrideModel struct { diff --git a/internal/service/wafv2/web_acl_rule_group_association_test.go b/internal/service/wafv2/web_acl_rule_group_association_test.go index b87471a9da8e..4ae6cbf4034b 100644 --- a/internal/service/wafv2/web_acl_rule_group_association_test.go +++ b/internal/service/wafv2/web_acl_rule_group_association_test.go @@ -171,6 +171,46 @@ func TestAccWAFV2WebACLRuleGroupAssociation_basic(t *testing.T) { }) } +func TestAccWAFV2WebACLRuleGroupAssociation_withVisibilityConfig(t *testing.T) { + ctx := acctest.Context(t) + var v wafv2.GetWebACLOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_wafv2_web_acl_rule_group_association.test" + webACLResourceName := "aws_wafv2_web_acl.test" + ruleGroupResourceName := "aws_wafv2_rule_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.WAFV2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWebACLRuleGroupAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccWebACLRuleGroupAssociationConfig_RuleGroupReference_withVisibilityConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "rule_name", fmt.Sprintf("%s-association", rName)), + resource.TestCheckResourceAttr(resourceName, names.AttrPriority, "10"), + resource.TestCheckResourceAttr(resourceName, "override_action", "none"), + resource.TestCheckResourceAttrPair(resourceName, "web_acl_arn", webACLResourceName, names.AttrARN), + resource.TestCheckResourceAttrPair(resourceName, "rule_group_reference.0.arn", ruleGroupResourceName, names.AttrARN), + resource.TestCheckResourceAttr(resourceName, "visibility_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "visibility_config.0.cloudwatch_metrics_enabled", acctest.CtFalse), + resource.TestCheckResourceAttr(resourceName, "visibility_config.0.metric_name", "friendly-metric-name"), + resource.TestCheckResourceAttr(resourceName, "visibility_config.0.sampled_requests_enabled", acctest.CtFalse), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccWebACLRuleGroupAssociationImportStateIDFunc(resourceName), + ImportStateVerifyIdentifierAttribute: "web_acl_arn", + }, + }, + }) +} + func TestAccWAFV2WebACLRuleGroupAssociation_disappears(t *testing.T) { ctx := acctest.Context(t) var v wafv2.GetWebACLOutput @@ -562,6 +602,289 @@ func TestAccWAFV2WebACLRuleGroupAssociation_ManagedRuleGroup_ruleActionOverride( }) } +func TestAccWAFV2WebACLRuleGroupAssociation_ManagedRuleGroup_ManagedRuleGroupConfig_basic(t *testing.T) { + ctx := acctest.Context(t) + var webACL wafv2.GetWebACLOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_wafv2_web_acl_rule_group_association.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.WAFV2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWebACLRuleGroupAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.managed_rule_group_configs.0.aws_managed_rules_bot_control_rule_set.0.inspection_level", "COMMON"), + ), + }, + { + Config: testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfigUpdate(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.managed_rule_group_configs.0.aws_managed_rules_bot_control_rule_set.0.inspection_level", "TARGETED"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccWebACLRuleGroupAssociationManagedRuleGroupImportStateIDFunc(resourceName), + ImportStateVerifyIdentifierAttribute: "web_acl_arn", + }, + }, + }) +} + +func TestAccWAFV2WebACLRuleGroupAssociation_ManagedRuleGroup_ManagedRuleGroupConfig_ACFPRuleSet(t *testing.T) { + ctx := acctest.Context(t) + var webACL wafv2.GetWebACLOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_wafv2_web_acl_rule_group_association.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.WAFV2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWebACLRuleGroupAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig_acfpRuleSet(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), + // Check top-level attributes + resource.TestCheckResourceAttr(resourceName, "rule_name", "test-rule"), + resource.TestCheckResourceAttr(resourceName, "priority", "1"), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.name", "AWSManagedRulesACFPRuleSet"), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.vendor_name", "AWS"), + + // Verify Managed Rule Group Config + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "managed_rule_group.0.managed_rule_group_configs.*", map[string]string{ + "aws_managed_rules_acfp_rule_set.0.creation_path": "/creation", + "aws_managed_rules_acfp_rule_set.0.registration_page_path": "/registration", + "aws_managed_rules_acfp_rule_set.0.request_inspection.0.email_field.0.identifier": "/email", + "aws_managed_rules_acfp_rule_set.0.request_inspection.0.password_field.0.identifier": "/password", + "aws_managed_rules_acfp_rule_set.0.request_inspection.0.phone_number_fields.0.identifiers.0": "/phone1", + "aws_managed_rules_acfp_rule_set.0.request_inspection.0.phone_number_fields.0.identifiers.1": "/phone2", + "aws_managed_rules_acfp_rule_set.0.request_inspection.0.address_fields.0.identifiers.0": "home", + "aws_managed_rules_acfp_rule_set.0.request_inspection.0.address_fields.0.identifiers.1": "work", + "aws_managed_rules_acfp_rule_set.0.request_inspection.0.payload_type": "JSON", + "aws_managed_rules_acfp_rule_set.0.request_inspection.0.username_field.0.identifier": "/username", + }), + ), + }, + { + Config: testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig_acfpRuleSetUpdate(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), + // Check top-level attributes + resource.TestCheckResourceAttr(resourceName, "rule_name", "test-rule"), + resource.TestCheckResourceAttr(resourceName, "priority", "1"), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.name", "AWSManagedRulesACFPRuleSet"), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.vendor_name", "AWS"), + + // Verify Managed Rule Group Config + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "managed_rule_group.0.managed_rule_group_configs.*", map[string]string{ + "aws_managed_rules_acfp_rule_set.0.enable_regex_in_path": acctest.CtTrue, + "aws_managed_rules_acfp_rule_set.0.creation_path": "/creation", + "aws_managed_rules_acfp_rule_set.0.registration_page_path": "/registration", + "aws_managed_rules_acfp_rule_set.0.request_inspection.0.email_field.0.identifier": "/email", + "aws_managed_rules_acfp_rule_set.0.request_inspection.0.password_field.0.identifier": "/pass", + "aws_managed_rules_acfp_rule_set.0.request_inspection.0.phone_number_fields.0.identifiers.0": "/phone3", + "aws_managed_rules_acfp_rule_set.0.request_inspection.0.address_fields.0.identifiers.0": "mobile", + "aws_managed_rules_acfp_rule_set.0.request_inspection.0.payload_type": "JSON", + "aws_managed_rules_acfp_rule_set.0.request_inspection.0.username_field.0.identifier": "/user", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccWebACLRuleGroupAssociationManagedRuleGroupImportStateIDFunc(resourceName), + ImportStateVerifyIdentifierAttribute: "web_acl_arn", + }, + }, + }) +} + +func TestAccWAFV2WebACLRuleGroupAssociation_ManagedRuleGroup_ManagedRuleGroupConfig_AntiDDoSRuleSet(t *testing.T) { + ctx := acctest.Context(t) + var webACL wafv2.GetWebACLOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_wafv2_web_acl_rule_group_association.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.WAFV2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWebACLRuleGroupAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig_antiDDoSRuleSet(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), + // Check top-level attributes + resource.TestCheckResourceAttr(resourceName, "rule_name", "test-rule"), + resource.TestCheckResourceAttr(resourceName, "priority", "1"), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.name", "AWSManagedRulesAntiDDoSRuleSet"), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.vendor_name", "AWS"), + + // Verify Managed Rule Group Config + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "managed_rule_group.0.managed_rule_group_configs.*", map[string]string{ + "aws_managed_rules_anti_ddos_rule_set.0.client_side_action_config.0.challenge.0.usage_of_action": "ENABLED", + "aws_managed_rules_anti_ddos_rule_set.0.client_side_action_config.0.challenge.0.exempt_uri_regular_expression.#": "2", + "aws_managed_rules_anti_ddos_rule_set.0.client_side_action_config.0.challenge.0.exempt_uri_regular_expression.0.regex_string": "\\/api\\/", + "aws_managed_rules_anti_ddos_rule_set.0.client_side_action_config.0.challenge.0.exempt_uri_regular_expression.1.regex_string": "jpg", + "aws_managed_rules_anti_ddos_rule_set.0.client_side_action_config.0.challenge.0.sensitivity": "MEDIUM", + "aws_managed_rules_anti_ddos_rule_set.0.sensitivity_to_block": "HIGH", + }), + ), + }, + { + Config: testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig_antiDDoSRuleSetWithDefaultSensitivity(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), + // Check top-level attributes + resource.TestCheckResourceAttr(resourceName, "rule_name", "test-rule"), + resource.TestCheckResourceAttr(resourceName, "priority", "1"), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.name", "AWSManagedRulesAntiDDoSRuleSet"), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.vendor_name", "AWS"), + + // Verify Managed Rule Group Config + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "managed_rule_group.0.managed_rule_group_configs.*", map[string]string{ + "aws_managed_rules_anti_ddos_rule_set.0.client_side_action_config.0.challenge.0.usage_of_action": "ENABLED", + "aws_managed_rules_anti_ddos_rule_set.0.client_side_action_config.0.challenge.0.exempt_uri_regular_expression.#": "2", + "aws_managed_rules_anti_ddos_rule_set.0.client_side_action_config.0.challenge.0.exempt_uri_regular_expression.0.regex_string": "\\/api\\/", + "aws_managed_rules_anti_ddos_rule_set.0.client_side_action_config.0.challenge.0.exempt_uri_regular_expression.1.regex_string": "jpg", + "aws_managed_rules_anti_ddos_rule_set.0.client_side_action_config.0.challenge.0.sensitivity": "HIGH", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccWebACLRuleGroupAssociationManagedRuleGroupImportStateIDFunc(resourceName), + ImportStateVerifyIdentifierAttribute: "web_acl_arn", + }, + }, + }) +} + +func TestAccWAFV2WebACLRuleGroupAssociation_ManagedRuleGroup_ManagedRuleGroupConfig_ATPRuleSet(t *testing.T) { + ctx := acctest.Context(t) + var webACL wafv2.GetWebACLOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_wafv2_web_acl_rule_group_association.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.WAFV2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWebACLRuleGroupAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig_atpRuleSet(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), + // Check top-level attributes + resource.TestCheckResourceAttr(resourceName, "rule_name", "test-rule"), + resource.TestCheckResourceAttr(resourceName, "priority", "1"), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.name", "AWSManagedRulesATPRuleSet"), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.vendor_name", "AWS"), + + // Verify Managed Rule Group Config + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "managed_rule_group.0.managed_rule_group_configs.*", map[string]string{ + "aws_managed_rules_atp_rule_set.0.login_path": "/api/1/signin", + "aws_managed_rules_atp_rule_set.0.request_inspection.#": "1", + "aws_managed_rules_atp_rule_set.0.request_inspection.0.password_field.#": "1", + "aws_managed_rules_atp_rule_set.0.request_inspection.0.password_field.0.identifier": "/password", + "aws_managed_rules_atp_rule_set.0.request_inspection.0.username_field.#": "1", + "aws_managed_rules_atp_rule_set.0.request_inspection.0.username_field.0.identifier": "/username", + "aws_managed_rules_atp_rule_set.0.request_inspection.0.payload_type": "JSON", + "aws_managed_rules_atp_rule_set.0.response_inspection.#": "0", + }), + ), + }, + { + Config: testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig_atpRuleSetUpdate(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), + // Check top-level attributes + resource.TestCheckResourceAttr(resourceName, "rule_name", "test-rule"), + resource.TestCheckResourceAttr(resourceName, "priority", "1"), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.name", "AWSManagedRulesATPRuleSet"), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.vendor_name", "AWS"), + + // Verify Managed Rule Group Config + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "managed_rule_group.0.managed_rule_group_configs.*", map[string]string{ + "aws_managed_rules_atp_rule_set.0.enable_regex_in_path": acctest.CtTrue, + "aws_managed_rules_atp_rule_set.0.login_path": "/api/2/signin", + "aws_managed_rules_atp_rule_set.0.request_inspection.#": "1", + "aws_managed_rules_atp_rule_set.0.request_inspection.0.password_field.#": "1", + "aws_managed_rules_atp_rule_set.0.request_inspection.0.password_field.0.identifier": "/pass", + "aws_managed_rules_atp_rule_set.0.request_inspection.0.username_field.#": "1", + "aws_managed_rules_atp_rule_set.0.request_inspection.0.username_field.0.identifier": "/user", + "aws_managed_rules_atp_rule_set.0.request_inspection.0.payload_type": "JSON", + "aws_managed_rules_atp_rule_set.0.response_inspection.#": "0", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccWebACLRuleGroupAssociationManagedRuleGroupImportStateIDFunc(resourceName), + ImportStateVerifyIdentifierAttribute: "web_acl_arn", + }, + }, + }) +} + +func TestAccWAFV2WebACLRuleGroupAssociation_ManagedRuleGroup_ManagedRuleGroupConfig_BotControl(t *testing.T) { + ctx := acctest.Context(t) + var webACL wafv2.GetWebACLOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_wafv2_web_acl_rule_group_association.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.WAFV2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckWebACLRuleGroupAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig_botControl(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), + // Check top-level attributes + resource.TestCheckResourceAttr(resourceName, "rule_name", "test-rule"), + resource.TestCheckResourceAttr(resourceName, "priority", "1"), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.name", "AWSManagedRulesBotControlRuleSet"), + resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.vendor_name", "AWS"), + + // Verify Managed Rule Group Config + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "managed_rule_group.0.managed_rule_group_configs.*", map[string]string{ + "aws_managed_rules_bot_control_rule_set.0.inspection_level": "TARGETED", + "aws_managed_rules_bot_control_rule_set.0.enable_machine_learning": acctest.CtTrue, + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccWebACLRuleGroupAssociationManagedRuleGroupImportStateIDFunc(resourceName), + ImportStateVerifyIdentifierAttribute: "web_acl_arn", + }, + }, + }) +} + func testAccCheckWebACLRuleGroupAssociationDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).WAFV2Client(ctx) @@ -839,7 +1162,7 @@ resource "aws_wafv2_web_acl_rule_group_association" "test" { `, rName) } -func testAccWebACLRuleGroupAssociationConfig_RuleGroupReference_overrideAction(rName, overrideAction string) string { +func testAccWebACLRuleGroupAssociationConfig_RuleGroupReference_withVisibilityConfig(rName string) string { return fmt.Sprintf(` resource "aws_wafv2_rule_group" "test" { name = %[1]q @@ -851,7 +1174,7 @@ resource "aws_wafv2_rule_group" "test" { priority = 1 action { - block {} + count {} } statement { @@ -894,19 +1217,24 @@ resource "aws_wafv2_web_acl" "test" { } resource "aws_wafv2_web_acl_rule_group_association" "test" { - rule_name = "%[1]s-association" - priority = 10 - web_acl_arn = aws_wafv2_web_acl.test.arn - override_action = %[2]q + rule_name = "%[1]s-association" + priority = 10 + web_acl_arn = aws_wafv2_web_acl.test.arn rule_group_reference { arn = aws_wafv2_rule_group.test.arn } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = "friendly-metric-name" + sampled_requests_enabled = false + } } -`, rName, overrideAction) +`, rName) } -func testAccWebACLRuleGroupAssociationConfig_RuleGroupReference_ruleActionOverride(rName string) string { +func testAccWebACLRuleGroupAssociationConfig_RuleGroupReference_overrideAction(rName, overrideAction string) string { return fmt.Sprintf(` resource "aws_wafv2_rule_group" "test" { name = %[1]q @@ -934,27 +1262,6 @@ resource "aws_wafv2_rule_group" "test" { } } - rule { - name = "rule-2" - priority = 2 - - action { - allow {} - } - - statement { - ip_set_reference_statement { - arn = aws_wafv2_ip_set.test.arn - } - } - - visibility_config { - cloudwatch_metrics_enabled = false - metric_name = "rule-2" - sampled_requests_enabled = false - } - } - visibility_config { cloudwatch_metrics_enabled = false metric_name = %[1]q @@ -962,14 +1269,6 @@ resource "aws_wafv2_rule_group" "test" { } } -resource "aws_wafv2_ip_set" "test" { - name = %[1]q - scope = "REGIONAL" - - ip_address_version = "IPV4" - addresses = ["192.0.2.0/24"] -} - resource "aws_wafv2_web_acl" "test" { name = %[1]q scope = "REGIONAL" @@ -990,12 +1289,108 @@ resource "aws_wafv2_web_acl" "test" { } resource "aws_wafv2_web_acl_rule_group_association" "test" { - rule_name = "%[1]s-association" - priority = 10 - web_acl_arn = aws_wafv2_web_acl.test.arn - - rule_group_reference { - arn = aws_wafv2_rule_group.test.arn + rule_name = "%[1]s-association" + priority = 10 + web_acl_arn = aws_wafv2_web_acl.test.arn + override_action = %[2]q + + rule_group_reference { + arn = aws_wafv2_rule_group.test.arn + } +} +`, rName, overrideAction) +} + +func testAccWebACLRuleGroupAssociationConfig_RuleGroupReference_ruleActionOverride(rName string) string { + return fmt.Sprintf(` +resource "aws_wafv2_rule_group" "test" { + name = %[1]q + scope = "REGIONAL" + capacity = 10 + + rule { + name = "rule-1" + priority = 1 + + action { + block {} + } + + statement { + geo_match_statement { + country_codes = ["US", "CA"] + } + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = "rule-1" + sampled_requests_enabled = false + } + } + + rule { + name = "rule-2" + priority = 2 + + action { + allow {} + } + + statement { + ip_set_reference_statement { + arn = aws_wafv2_ip_set.test.arn + } + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = "rule-2" + sampled_requests_enabled = false + } + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = %[1]q + sampled_requests_enabled = false + } +} + +resource "aws_wafv2_ip_set" "test" { + name = %[1]q + scope = "REGIONAL" + + ip_address_version = "IPV4" + addresses = ["192.0.2.0/24"] +} + +resource "aws_wafv2_web_acl" "test" { + name = %[1]q + scope = "REGIONAL" + + default_action { + allow {} + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = %[1]q + sampled_requests_enabled = false + } + + lifecycle { + ignore_changes = [rule] + } +} + +resource "aws_wafv2_web_acl_rule_group_association" "test" { + rule_name = "%[1]s-association" + priority = 10 + web_acl_arn = aws_wafv2_web_acl.test.arn + + rule_group_reference { + arn = aws_wafv2_rule_group.test.arn rule_action_override { name = "rule-1" @@ -1502,3 +1897,470 @@ resource "aws_wafv2_web_acl_rule_group_association" "test" { } `, rName) } + +func testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl" "test" { + name = %[1]q + scope = "REGIONAL" + + default_action { + allow {} + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = %[1]q + sampled_requests_enabled = false + } + + lifecycle { + ignore_changes = [rule] + } +} + +resource "aws_wafv2_web_acl_rule_group_association" "test" { + rule_name = "test-rule" + priority = 1 + web_acl_arn = aws_wafv2_web_acl.test.arn + + managed_rule_group { + name = "AWSManagedRulesBotControlRuleSet" + vendor_name = "AWS" + + managed_rule_group_configs { + aws_managed_rules_bot_control_rule_set { + inspection_level = "COMMON" + } + } + } + + override_action = "none" +} +`, rName) + +} + +func testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfigUpdate(rName string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl" "test" { + name = %[1]q + scope = "REGIONAL" + + default_action { + allow {} + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = %[1]q + sampled_requests_enabled = false + } + + lifecycle { + ignore_changes = [rule] + } +} + +resource "aws_wafv2_web_acl_rule_group_association" "test" { + rule_name = "test-rule" + priority = 1 + web_acl_arn = aws_wafv2_web_acl.test.arn + + managed_rule_group { + name = "AWSManagedRulesBotControlRuleSet" + vendor_name = "AWS" + + managed_rule_group_configs { + aws_managed_rules_bot_control_rule_set { + inspection_level = "TARGETED" + } + } + } + + override_action = "none" +} +`, rName) + +} + +func testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig_acfpRuleSet(rName string) string { + return fmt.Sprintf(` + +resource "aws_wafv2_web_acl" "test" { + name = %[1]q + scope = "REGIONAL" + + default_action { + allow {} + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = %[1]q + sampled_requests_enabled = false + } + + lifecycle { + ignore_changes = [rule] + } +} + +resource "aws_wafv2_web_acl_rule_group_association" "test" { + rule_name = "test-rule" + priority = 1 + web_acl_arn = aws_wafv2_web_acl.test.arn + + managed_rule_group { + + name = "AWSManagedRulesACFPRuleSet" + vendor_name = "AWS" + + managed_rule_group_configs { + aws_managed_rules_acfp_rule_set { + creation_path = "/creation" + registration_page_path = "/registration" + request_inspection { + email_field { + identifier = "/email" + } + password_field { + identifier = "/password" + } + phone_number_fields { + identifiers = ["/phone1", "/phone2"] + } + address_fields { + identifiers = ["home", "work"] + } + payload_type = "JSON" + username_field { + identifier = "/username" + } + } + } + } + } +} +`, rName) +} + +func testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig_acfpRuleSetUpdate(rName string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl" "test" { + name = %[1]q + scope = "REGIONAL" + + default_action { + allow {} + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = %[1]q + sampled_requests_enabled = false + } + + lifecycle { + ignore_changes = [rule] + } +} + +resource "aws_wafv2_web_acl_rule_group_association" "test" { + rule_name = "test-rule" + priority = 1 + web_acl_arn = aws_wafv2_web_acl.test.arn + + managed_rule_group { + name = "AWSManagedRulesACFPRuleSet" + vendor_name = "AWS" + + managed_rule_group_configs { + aws_managed_rules_acfp_rule_set { + enable_regex_in_path = true + creation_path = "/creation" + registration_page_path = "/registration" + + request_inspection { + email_field { + identifier = "/email" + } + password_field { + identifier = "/pass" + } + phone_number_fields { + identifiers = ["/phone3"] + } + address_fields { + identifiers = ["mobile"] + } + payload_type = "JSON" + username_field { + identifier = "/user" + } + } + } + } + } + + override_action = "none" +} + +`, rName) +} + +func testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig_antiDDoSRuleSet(rName string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl" "test" { + name = %[1]q + scope = "REGIONAL" + + default_action { + allow {} + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = %[1]q + sampled_requests_enabled = false + } + + lifecycle { + ignore_changes = [rule] + } +} + +resource "aws_wafv2_web_acl_rule_group_association" "test" { + rule_name = "test-rule" + priority = 1 + web_acl_arn = aws_wafv2_web_acl.test.arn + + managed_rule_group { + + name = "AWSManagedRulesAntiDDoSRuleSet" + vendor_name = "AWS" + + managed_rule_group_configs { + aws_managed_rules_anti_ddos_rule_set { + client_side_action_config { + challenge { + usage_of_action = "ENABLED" + exempt_uri_regular_expression { + regex_string = "\\/api\\/" + } + exempt_uri_regular_expression { + regex_string = "jpg" + } + sensitivity = "MEDIUM" + } + } + sensitivity_to_block = "HIGH" + } + } + + } + +} +`, rName) +} + +func testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig_antiDDoSRuleSetWithDefaultSensitivity(rName string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl" "test" { + name = %[1]q + scope = "REGIONAL" + + default_action { + allow {} + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = %[1]q + sampled_requests_enabled = false + } + + lifecycle { + ignore_changes = [rule] + } +} + +resource "aws_wafv2_web_acl_rule_group_association" "test" { + rule_name = "test-rule" + priority = 1 + web_acl_arn = aws_wafv2_web_acl.test.arn + + managed_rule_group { + name = "AWSManagedRulesAntiDDoSRuleSet" + vendor_name = "AWS" + + managed_rule_group_configs { + aws_managed_rules_anti_ddos_rule_set { + client_side_action_config { + challenge { + usage_of_action = "ENABLED" + exempt_uri_regular_expression { + regex_string = "\\/api\\/" + } + exempt_uri_regular_expression { + regex_string = "jpg" + } + } + } + } + } + } +} +`, rName) +} + +func testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig_atpRuleSet(rName string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl" "test" { + name = %[1]q + scope = "REGIONAL" + + default_action { + allow {} + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = %[1]q + sampled_requests_enabled = false + } + + lifecycle { + ignore_changes = [rule] + } +} + +resource "aws_wafv2_web_acl_rule_group_association" "test" { + rule_name = "test-rule" + priority = 1 + web_acl_arn = aws_wafv2_web_acl.test.arn + + managed_rule_group { + name = "AWSManagedRulesATPRuleSet" + vendor_name = "AWS" + + managed_rule_group_configs { + aws_managed_rules_atp_rule_set { + login_path = "/api/1/signin" + + request_inspection { + password_field { + identifier = "/password" + } + + payload_type = "JSON" + + username_field { + identifier = "/username" + } + } + } + } + } + + override_action = "none" +} +`, rName) +} + +func testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig_atpRuleSetUpdate(rName string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl" "test" { + name = %[1]q + scope = "REGIONAL" + + default_action { + allow {} + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = %[1]q + sampled_requests_enabled = false + } + + lifecycle { + ignore_changes = [rule] + } +} + +resource "aws_wafv2_web_acl_rule_group_association" "test" { + rule_name = "test-rule" + priority = 1 + web_acl_arn = aws_wafv2_web_acl.test.arn + + managed_rule_group { + name = "AWSManagedRulesATPRuleSet" + vendor_name = "AWS" + + managed_rule_group_configs { + aws_managed_rules_atp_rule_set { + enable_regex_in_path = true + login_path = "/api/2/signin" + + request_inspection { + password_field { + identifier = "/pass" + } + payload_type = "JSON" + username_field { + identifier = "/user" + } + } + } + } + } + + override_action = "none" +} + +`, rName) +} + +func testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig_botControl(rName string) string { + return fmt.Sprintf(` +resource "aws_wafv2_web_acl" "test" { + name = %[1]q + scope = "REGIONAL" + + default_action { + allow {} + } + + visibility_config { + cloudwatch_metrics_enabled = false + metric_name = %[1]q + sampled_requests_enabled = false + } + + lifecycle { + ignore_changes = [rule] + } +} + +resource "aws_wafv2_web_acl_rule_group_association" "test" { + rule_name = "test-rule" + priority = 1 + web_acl_arn = aws_wafv2_web_acl.test.arn + + managed_rule_group { + name = "AWSManagedRulesBotControlRuleSet" + vendor_name = "AWS" + + managed_rule_group_configs { + aws_managed_rules_bot_control_rule_set { + inspection_level = "TARGETED" + enable_machine_learning = true + } + } + } + + override_action = "none" +} +`, rName) +} diff --git a/website/docs/r/wafv2_web_acl_rule_group_association.html.markdown b/website/docs/r/wafv2_web_acl_rule_group_association.html.markdown index 81e2a614ede9..ee28fb0e7059 100644 --- a/website/docs/r/wafv2_web_acl_rule_group_association.html.markdown +++ b/website/docs/r/wafv2_web_acl_rule_group_association.html.markdown @@ -176,6 +176,71 @@ resource "aws_wafv2_web_acl_rule_group_association" "managed_with_overrides" { } ``` +### Managed Rule Group - With Managed Rule Group Configs + +```terraform +resource "aws_wafv2_web_acl" "example" { + name = "example" + scope = "REGIONAL" + + default_action { + allow {} + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "example-regional-waf" + sampled_requests_enabled = true + } + + lifecycle { + ignore_changes = [rule] + } +} + +resource "aws_wafv2_web_acl_rule_group_association" "managed_rule_group_configs" { + rule_name = "acfp-ruleset-with-rule-config" + priority = 70 + web_acl_arn = aws_wafv2_web_acl.example.arn + + managed_rule_group { + name = "AWSManagedRulesACFPRuleSet" + vendor_name = "AWS" + + managed_rule_group_configs { + aws_managed_rules_acfp_rule_set { + creation_path = "/creation" + registration_page_path = "/registration" + request_inspection { + email_field { + identifier = "/email" + } + password_field { + identifier = "/password" + } + phone_number_fields { + identifiers = ["/phone1", "/phone2"] + } + address_fields { + identifiers = ["home", "work"] + } + payload_type = "JSON" + username_field { + identifier = "/username" + } + } + } + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "friendly-metric-name" + sampled_requests_enabled = true + } +} +``` + ### Custom Rule Group - With Override Action ```terraform @@ -389,6 +454,15 @@ The following arguments are optional: * `override_action` - (Optional) Override action for the rule group. Valid values are `none` and `count`. Defaults to `none`. When set to `count`, the actions defined in the rule group rules are overridden to count matches instead of blocking or allowing requests. * `region` - (Optional) Region where this resource will be [managed](https://docs.aws.amazon.com/general/latest/gr/rande.html#regional-endpoints). Defaults to the Region set in the [provider configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#aws-configuration-reference). * `rule_group_reference` - (Optional) Custom Rule Group reference configuration. One of `rule_group_reference` or `managed_rule_group` is required. Conflicts with `managed_rule_group`. [See below](#rule_group_reference). +* `visibility_config` - (Optional) Defines and enables Amazon CloudWatch metrics and web request sample collection. See [`visibility_config`](#visibility_config-block) below for details. + +### `visibility_config` Block + +The `visibility_config` block supports the following arguments: + +* `cloudwatch_metrics_enabled` - (Required) Whether the associated resource sends metrics to CloudWatch. For the list of available metrics, see [AWS WAF Metrics](https://docs.aws.amazon.com/waf/latest/developerguide/monitoring-cloudwatch.html#waf-metrics). +* `metric_name` - (Required) A friendly name of the CloudWatch metric. The name can contain only alphanumeric characters (A-Z, a-z, 0-9) hyphen(-) and underscore (\_), with length from one to 128 characters. It can't contain whitespace or metric names reserved for AWS WAF, for example `All` and `Default_Action`. +* `sampled_requests_enabled` - (Required) Whether AWS WAF should store a sampling of the web requests that match the rules. You can view the sampled requests through the AWS WAF console. ### rule_group_reference @@ -401,6 +475,7 @@ The following arguments are optional: * `vendor_name` - (Required) Name of the managed rule group vendor. For AWS managed rule groups, this is `AWS`. * `version` - (Optional) Version of the managed rule group. If not specified, the default version is used. * `rule_action_override` - (Optional) Override actions for specific rules within the rule group. [See below](#rule_action_override). +* `managed_rule_group_configs`- (Optional) Additional information that's used by a managed rule group. Only one rule attribute is allowed in each config. See [`managed_rule_group_configs`](#managed_rule_group_configs-block) for more details ### rule_action_override @@ -457,6 +532,112 @@ Exactly one of the following action blocks must be specified: * `name` - (Required) Name of the response header. * `value` - (Required) Value of the response header. +### `managed_rule_group_configs` Block + +The `managed_rule_group_configs` block support the following arguments: + +* `aws_managed_rules_bot_control_rule_set` - (Optional) Additional configuration for using the Bot Control managed rule group. Use this to specify the inspection level that you want to use. See [`aws_managed_rules_bot_control_rule_set`](#aws_managed_rules_bot_control_rule_set-block) for more details +* `aws_managed_rules_acfp_rule_set` - (Optional) Additional configuration for using the Account Creation Fraud Prevention managed rule group. Use this to specify information such as the registration page of your application and the type of content to accept or reject from the client. +* `aws_managed_rules_anti_ddos_rule_set` - (Optional) Configuration for using the anti-DDoS managed rule group. See [`aws_managed_rules_anti_ddos_rule_set`](#aws_managed_rules_anti_ddos_rule_set-block) for more details. +* `aws_managed_rules_atp_rule_set` - (Optional) Additional configuration for using the Account Takeover Protection managed rule group. Use this to specify information such as the sign-in page of your application and the type of content to accept or reject from the client. + +### `aws_managed_rules_bot_control_rule_set` Block + +* `enable_machine_learning` - (Optional) Applies only to the targeted inspection level. Determines whether to use machine learning (ML) to analyze your web traffic for bot-related activity. Defaults to `true`. +* `inspection_level` - (Optional) The inspection level to use for the Bot Control rule group. + +### `aws_managed_rules_acfp_rule_set` Block + +* `creation_path` - (Required) The path of the account creation endpoint for your application. This is the page on your website that accepts the completed registration form for a new user. This page must accept POST requests. +* `enable_regex_in_path` - (Optional) Whether or not to allow the use of regular expressions in the login page path. +* `registration_page_path` - (Required) The path of the account registration endpoint for your application. This is the page on your website that presents the registration form to new users. This page must accept GET text/html requests. +* `request_inspection` - (Optional) The criteria for inspecting login requests, used by the ATP rule group to validate credentials usage. See [`request_inspection`](#request_inspection-block-acfp) for more details. +* `response_inspection` - (Optional) The criteria for inspecting responses to login requests, used by the ATP rule group to track login failure rates. Note that Response Inspection is available only on web ACLs that protect CloudFront distributions. See [`response_inspection`](#response_inspection-block) for more details. + +### `request_inspection` Block (ACFP) + +* `addressFields` (Optional) The names of the fields in the request payload that contain your customer's primary physical address. See [`addressFields`](#address_fields-block) for more details. +* `emailField` (Optional) The name of the field in the request payload that contains your customer's email. See [`emailField`](#email_field-block) for more details. +* `passwordField` (Optional) Details about your login page password field. See [`passwordField`](#password_field-block) for more details. +* `payloadType` (Required) The payload type for your login endpoint, either JSON or form encoded. +* `phoneNumberFields` (Optional) The names of the fields in the request payload that contain your customer's primary phone number. See [`phoneNumberFields`](#phone_number_fields-block) for more details. +* `usernameField` (Optional) Details about your login page username field. See [`usernameField`](#username_field-block) for more details. + +### `aws_managed_rules_anti_ddos_rule_set` Block + +* `client_side_action_config` - (Required) Configuration for the request handling that's applied by the managed rule group rules `ChallengeAllDuringEvent` and `ChallengeDDoSRequests` during a distributed denial of service (DDoS) attack. See [`client_side_action_config`](#client_side_action_config-block) for more details. +* `sensitivity_to_block` - (Optional) Sensitivity that the rule group rule DDoSRequests uses when matching against the DDoS suspicion labeling on a request. Valid values are `LOW` (Default), `MEDIUM`, and `HIGH`. + +### `client_side_action_config` Block + +* `challenge` - (Required) Configuration for the use of the `AWSManagedRulesAntiDDoSRuleSet` rules `ChallengeAllDuringEvent` and `ChallengeDDoSRequests`. + * `exempt_uri_regular_expression` - (Optional) Block for the list of the regular expressions to match against the web request URI, used to identify requests that can't handle a silent browser challenge. + * `regex_string` - (Optional) Regular expression string. + * `sensitivity` - (Optional) Sensitivity that the rule group rule ChallengeDDoSRequests uses when matching against the DDoS suspicion labeling on a request. Valid values are `LOW`, `MEDIUM` and `HIGH` (Default). + * `usage_of_action` - (Required) Configuration whether to use the `AWSManagedRulesAntiDDoSRuleSet` rules `ChallengeAllDuringEvent` and `ChallengeDDoSRequests` in the rule group evaluation. Valid values are `ENABLED` and `DISABLED`. + +### `aws_managed_rules_atp_rule_set` Block + +* `enable_regex_in_path` - (Optional) Whether or not to allow the use of regular expressions in the login page path. +* `login_path` - (Required) The path of the login endpoint for your application. +* `request_inspection` - (Optional) The criteria for inspecting login requests, used by the ATP rule group to validate credentials usage. See [`request_inspection`](#request_inspection-block) for more details. +* `response_inspection` - (Optional) The criteria for inspecting responses to login requests, used by the ATP rule group to track login failure rates. Note that Response Inspection is available only on web ACLs that protect CloudFront distributions. See [`response_inspection`](#response_inspection-block) for more details. + +### `request_inspection` Block + +* `password_field` (Optional) Details about your login page password field. See [`password_field`](#password_field-block) for more details. +* `payload_type` (Required) The payload type for your login endpoint, either JSON or form encoded. +* `username_field` (Optional) Details about your login page username field. See [`username_field`](#username_field-block) for more details. + +### `address_fields` Block + +* `identifiers` - (Required) The names of the address fields. + +### `email_field` Block + +* `identifier` - (Required) The name of the field in the request payload that contains your customer's email. + +### `password_field` Block + +* `identifier` - (Required) The name of the password field. + +### `phone_number_fields` Block + +* `identifiers` - (Required) The names of the phone number fields. + +### `username_field` Block + +* `identifier` - (Required) The name of the username field. + +### `response_inspection` Block + +* `body_contains` (Optional) Configures inspection of the response body. See [`body_contains`](#body_contains-block) for more details. +* `header` (Optional) Configures inspection of the response header.See [`header`](#header-block) for more details. +* `json` (Optional) Configures inspection of the response JSON. See [`json`](#json-block) for more details. +* `status_code` (Optional) Configures inspection of the response status code.See [`status_code`](#status_code-block) for more details. + +### `body_contains` Block + +* `success_strings` (Required) Strings in the body of the response that indicate a successful login attempt. +* `failure_strings` (Required) Strings in the body of the response that indicate a failed login attempt. + +### `header` Block + +* `name` (Required) The name of the header to match against. The name must be an exact match, including case. +* `success_values` (Required) Values in the response header with the specified name that indicate a successful login attempt. +* `failure_values` (Required) Values in the response header with the specified name that indicate a failed login attempt. + +### `json` Block + +* `identifier` (Required) The identifier for the value to match against in the JSON. +* `success_strings` (Required) Strings in the body of the response that indicate a successful login attempt. +* `failure_strings` (Required) Strings in the body of the response that indicate a failed login attempt. + +### `status_code` Block + +* `success_codes` (Required) Status codes in the response that indicate a successful login attempt. +* `failure_codes` (Required) Status codes in the response that indicate a failed login attempt. + ## Attribute Reference This resource exports the following attributes in addition to the arguments above: From 1daff171adfcde84d42fd7f1f79def47c0bef0b4 Mon Sep 17 00:00:00 2001 From: Sagar Barai Date: Wed, 24 Sep 2025 10:26:57 +0530 Subject: [PATCH 2/6] update doc --- .../docs/r/wafv2_web_acl_rule_group_association.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/wafv2_web_acl_rule_group_association.html.markdown b/website/docs/r/wafv2_web_acl_rule_group_association.html.markdown index ee28fb0e7059..267cba813416 100644 --- a/website/docs/r/wafv2_web_acl_rule_group_association.html.markdown +++ b/website/docs/r/wafv2_web_acl_rule_group_association.html.markdown @@ -543,7 +543,7 @@ The `managed_rule_group_configs` block support the following arguments: ### `aws_managed_rules_bot_control_rule_set` Block -* `enable_machine_learning` - (Optional) Applies only to the targeted inspection level. Determines whether to use machine learning (ML) to analyze your web traffic for bot-related activity. Defaults to `true`. +* `enable_machine_learning` - (Optional) Applies only to the targeted inspection level. Determines whether to use machine learning (ML) to analyze your web traffic for bot-related activity. Defaults to `false`. * `inspection_level` - (Optional) The inspection level to use for the Bot Control rule group. ### `aws_managed_rules_acfp_rule_set` Block From df07e50ee4da690af29913a8aece0ad7b3bfe303 Mon Sep 17 00:00:00 2001 From: Sagar Barai Date: Wed, 24 Sep 2025 13:21:51 +0530 Subject: [PATCH 3/6] refactor and fix linting --- .../web_acl_rule_group_association_test.go | 143 +++++++++--------- 1 file changed, 71 insertions(+), 72 deletions(-) diff --git a/internal/service/wafv2/web_acl_rule_group_association_test.go b/internal/service/wafv2/web_acl_rule_group_association_test.go index 4ae6cbf4034b..f29aa1b30368 100644 --- a/internal/service/wafv2/web_acl_rule_group_association_test.go +++ b/internal/service/wafv2/web_acl_rule_group_association_test.go @@ -657,7 +657,7 @@ func TestAccWAFV2WebACLRuleGroupAssociation_ManagedRuleGroup_ManagedRuleGroupCon testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), // Check top-level attributes resource.TestCheckResourceAttr(resourceName, "rule_name", "test-rule"), - resource.TestCheckResourceAttr(resourceName, "priority", "1"), + resource.TestCheckResourceAttr(resourceName, names.AttrPriority, "1"), resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.name", "AWSManagedRulesACFPRuleSet"), resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.vendor_name", "AWS"), @@ -682,7 +682,7 @@ func TestAccWAFV2WebACLRuleGroupAssociation_ManagedRuleGroup_ManagedRuleGroupCon testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), // Check top-level attributes resource.TestCheckResourceAttr(resourceName, "rule_name", "test-rule"), - resource.TestCheckResourceAttr(resourceName, "priority", "1"), + resource.TestCheckResourceAttr(resourceName, names.AttrPriority, "1"), resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.name", "AWSManagedRulesACFPRuleSet"), resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.vendor_name", "AWS"), @@ -729,7 +729,7 @@ func TestAccWAFV2WebACLRuleGroupAssociation_ManagedRuleGroup_ManagedRuleGroupCon testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), // Check top-level attributes resource.TestCheckResourceAttr(resourceName, "rule_name", "test-rule"), - resource.TestCheckResourceAttr(resourceName, "priority", "1"), + resource.TestCheckResourceAttr(resourceName, names.AttrPriority, "1"), resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.name", "AWSManagedRulesAntiDDoSRuleSet"), resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.vendor_name", "AWS"), @@ -750,7 +750,7 @@ func TestAccWAFV2WebACLRuleGroupAssociation_ManagedRuleGroup_ManagedRuleGroupCon testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), // Check top-level attributes resource.TestCheckResourceAttr(resourceName, "rule_name", "test-rule"), - resource.TestCheckResourceAttr(resourceName, "priority", "1"), + resource.TestCheckResourceAttr(resourceName, names.AttrPriority, "1"), resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.name", "AWSManagedRulesAntiDDoSRuleSet"), resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.vendor_name", "AWS"), @@ -793,7 +793,7 @@ func TestAccWAFV2WebACLRuleGroupAssociation_ManagedRuleGroup_ManagedRuleGroupCon testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), // Check top-level attributes resource.TestCheckResourceAttr(resourceName, "rule_name", "test-rule"), - resource.TestCheckResourceAttr(resourceName, "priority", "1"), + resource.TestCheckResourceAttr(resourceName, names.AttrPriority, "1"), resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.name", "AWSManagedRulesATPRuleSet"), resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.vendor_name", "AWS"), @@ -816,7 +816,7 @@ func TestAccWAFV2WebACLRuleGroupAssociation_ManagedRuleGroup_ManagedRuleGroupCon testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), // Check top-level attributes resource.TestCheckResourceAttr(resourceName, "rule_name", "test-rule"), - resource.TestCheckResourceAttr(resourceName, "priority", "1"), + resource.TestCheckResourceAttr(resourceName, names.AttrPriority, "1"), resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.name", "AWSManagedRulesATPRuleSet"), resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.vendor_name", "AWS"), @@ -863,7 +863,7 @@ func TestAccWAFV2WebACLRuleGroupAssociation_ManagedRuleGroup_ManagedRuleGroupCon testAccCheckWebACLRuleGroupAssociationExists(ctx, resourceName, &webACL), // Check top-level attributes resource.TestCheckResourceAttr(resourceName, "rule_name", "test-rule"), - resource.TestCheckResourceAttr(resourceName, "priority", "1"), + resource.TestCheckResourceAttr(resourceName, names.AttrPriority, "1"), resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.name", "AWSManagedRulesBotControlRuleSet"), resource.TestCheckResourceAttr(resourceName, "managed_rule_group.0.vendor_name", "AWS"), @@ -1224,7 +1224,7 @@ resource "aws_wafv2_web_acl_rule_group_association" "test" { rule_group_reference { arn = aws_wafv2_rule_group.test.arn } - + visibility_config { cloudwatch_metrics_enabled = false metric_name = "friendly-metric-name" @@ -1929,10 +1929,10 @@ resource "aws_wafv2_web_acl_rule_group_association" "test" { vendor_name = "AWS" managed_rule_group_configs { - aws_managed_rules_bot_control_rule_set { - inspection_level = "COMMON" - } - } + aws_managed_rules_bot_control_rule_set { + inspection_level = "COMMON" + } + } } override_action = "none" @@ -1972,10 +1972,10 @@ resource "aws_wafv2_web_acl_rule_group_association" "test" { vendor_name = "AWS" managed_rule_group_configs { - aws_managed_rules_bot_control_rule_set { - inspection_level = "TARGETED" - } - } + aws_managed_rules_bot_control_rule_set { + inspection_level = "TARGETED" + } + } } override_action = "none" @@ -2073,39 +2073,38 @@ resource "aws_wafv2_web_acl_rule_group_association" "test" { managed_rule_group { name = "AWSManagedRulesACFPRuleSet" - vendor_name = "AWS" + vendor_name = "AWS" - managed_rule_group_configs { - aws_managed_rules_acfp_rule_set { - enable_regex_in_path = true - creation_path = "/creation" - registration_page_path = "/registration" + managed_rule_group_configs { + aws_managed_rules_acfp_rule_set { + enable_regex_in_path = true + creation_path = "/creation" + registration_page_path = "/registration" - request_inspection { - email_field { - identifier = "/email" - } - password_field { - identifier = "/pass" - } - phone_number_fields { - identifiers = ["/phone3"] - } - address_fields { - identifiers = ["mobile"] - } - payload_type = "JSON" - username_field { - identifier = "/user" - } - } - } - } + request_inspection { + email_field { + identifier = "/email" + } + password_field { + identifier = "/pass" + } + phone_number_fields { + identifiers = ["/phone3"] + } + address_fields { + identifiers = ["mobile"] + } + payload_type = "JSON" + username_field { + identifier = "/user" + } + } + } + } } override_action = "none" } - `, rName) } @@ -2242,18 +2241,18 @@ resource "aws_wafv2_web_acl_rule_group_association" "test" { managed_rule_group { name = "AWSManagedRulesATPRuleSet" - vendor_name = "AWS" + vendor_name = "AWS" - managed_rule_group_configs { + managed_rule_group_configs { aws_managed_rules_atp_rule_set { login_path = "/api/1/signin" - - request_inspection { + + request_inspection { password_field { identifier = "/password" } - - payload_type = "JSON" + + payload_type = "JSON" username_field { identifier = "/username" @@ -2296,24 +2295,24 @@ resource "aws_wafv2_web_acl_rule_group_association" "test" { managed_rule_group { name = "AWSManagedRulesATPRuleSet" - vendor_name = "AWS" + vendor_name = "AWS" - managed_rule_group_configs { - aws_managed_rules_atp_rule_set { - enable_regex_in_path = true - login_path = "/api/2/signin" + managed_rule_group_configs { + aws_managed_rules_atp_rule_set { + enable_regex_in_path = true + login_path = "/api/2/signin" - request_inspection { - password_field { - identifier = "/pass" - } - payload_type = "JSON" - username_field { - identifier = "/user" - } - } - } - } + request_inspection { + password_field { + identifier = "/pass" + } + payload_type = "JSON" + username_field { + identifier = "/user" + } + } + } + } } override_action = "none" @@ -2350,14 +2349,14 @@ resource "aws_wafv2_web_acl_rule_group_association" "test" { managed_rule_group { name = "AWSManagedRulesBotControlRuleSet" - vendor_name = "AWS" + vendor_name = "AWS" - managed_rule_group_configs { - aws_managed_rules_bot_control_rule_set { - inspection_level = "TARGETED" - enable_machine_learning = true - } - } + managed_rule_group_configs { + aws_managed_rules_bot_control_rule_set { + inspection_level = "TARGETED" + enable_machine_learning = true + } + } } override_action = "none" From 9165f28925b976431dc558baf8119f09f5a46d20 Mon Sep 17 00:00:00 2001 From: Sagar Barai Date: Wed, 24 Sep 2025 13:22:31 +0530 Subject: [PATCH 4/6] refactor, fix linting, fix code quality --- .../wafv2/web_acl_rule_group_association.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/internal/service/wafv2/web_acl_rule_group_association.go b/internal/service/wafv2/web_acl_rule_group_association.go index 325f1ad1ea74..70f89ba90076 100644 --- a/internal/service/wafv2/web_acl_rule_group_association.go +++ b/internal/service/wafv2/web_acl_rule_group_association.go @@ -1041,7 +1041,6 @@ func (r *resourceWebACLRuleGroupAssociation) Create(ctx context.Context, req res managedRuleGroupConfigs = append(managedRuleGroupConfigs, awstypes.ManagedRuleGroupConfig{ AWSManagedRulesACFPRuleSet: &acfpConfig, }) - } else { // let auto flex handle expand resp.Diagnostics.Append(fwflex.Expand(ctx, managedRuleGroupRef.ManagedRuleGroupConfig, &managedRuleGroupConfigs)...) @@ -1085,7 +1084,7 @@ func (r *resourceWebACLRuleGroupAssociation) Create(ctx context.Context, req res return } } else { - // Use defaults when not provided in plan, for backward compability of this provider + // Use defaults when not provided in plan, for backward compatibility of this provider visibilityConfig = awstypes.VisibilityConfig{ SampledRequestsEnabled: true, CloudWatchMetricsEnabled: true, @@ -1274,8 +1273,7 @@ func (r *resourceWebACLRuleGroupAssociation) Read(ctx context.Context, req resou } var managedRuleGroupConfigs fwtypes.ListNestedObjectValueOf[managedRuleGroupConfigModel] - if managedStmt.ManagedRuleGroupConfigs != nil && len(managedStmt.ManagedRuleGroupConfigs) > 0 { - + if len(managedStmt.ManagedRuleGroupConfigs) > 0 { sdkManagedRuleGroupConfigs := managedStmt.ManagedRuleGroupConfigs[0] if acfp := sdkManagedRuleGroupConfigs.AWSManagedRulesACFPRuleSet; acfp != nil { @@ -1285,7 +1283,6 @@ func (r *resourceWebACLRuleGroupAssociation) Read(ctx context.Context, req resou if resp.Diagnostics.HasError() { return } - } else { resp.Diagnostics.Append(fwflex.Flatten(ctx, managedStmt.ManagedRuleGroupConfigs, &managedRuleGroupConfigs)...) if resp.Diagnostics.HasError() { @@ -1302,11 +1299,9 @@ func (r *resourceWebACLRuleGroupAssociation) Read(ctx context.Context, req resou managedRuleGroupConfigs = fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*managedRuleGroupConfigModel{&tfManagedRuleGroupConfigModel}) } } - } else { managedRuleGroupConfigs = fwtypes.NewListNestedObjectValueOfNull[managedRuleGroupConfigModel](ctx) } - // Update the managed rule group nested structure managedRuleGroupRef.RuleActionOverride = ruleActionOverrides managedRuleGroupRef.ManagedRuleGroupConfig = managedRuleGroupConfigs @@ -1327,7 +1322,7 @@ func (r *resourceWebACLRuleGroupAssociation) Read(ctx context.Context, req resou state.Priority = types.Int32Value(rule.Priority) var visibilityConfig fwtypes.ListNestedObjectValueOf[visibilityConfigModel] - // for backward compability, visibility_config to be read only if it's not set to default + // for backward compatibility, visibility_config to be read only if it's not set to default if rule.VisibilityConfig != nil { isDefault := rule.VisibilityConfig.SampledRequestsEnabled && rule.VisibilityConfig.CloudWatchMetricsEnabled && @@ -1431,7 +1426,6 @@ func (r *resourceWebACLRuleGroupAssociation) Update(ctx context.Context, req res // update visibility config if changed var visibilityConfig awstypes.VisibilityConfig if !plan.VisibilityConfig.IsNull() && !plan.VisibilityConfig.IsUnknown() { - resp.Diagnostics.Append(fwflex.Expand(ctx, plan.VisibilityConfig, &visibilityConfig)...) if resp.Diagnostics.HasError() { return @@ -1884,7 +1878,6 @@ func patchBotControlFlatten( botControl *awstypes.AWSManagedRulesBotControlRuleSet, tfManagedRuleGroupConfigModel managedRuleGroupConfigModel, ) managedRuleGroupConfigModel { - if botControl.InspectionLevel == awstypes.InspectionLevelCommon { tfBotControlModelConfig := awsManagedRulesBotControlRuleSetModel{ InspectionLevel: types.StringValue(string(awstypes.InspectionLevelCommon)), @@ -1931,7 +1924,7 @@ func expandACFPManagedRuleConfig( } for _, id := range tfAddressField.Identifiers.Elements() { addressFields = append(addressFields, awstypes.AddressField{ - Identifier: aws.String(id.(types.String).ValueString()), + Identifier: id.(types.String).ValueStringPointer(), }) } } @@ -1945,7 +1938,7 @@ func expandACFPManagedRuleConfig( } for _, id := range tfPhoneNumberField.Identifiers.Elements() { phoneNumberFields = append(phoneNumberFields, awstypes.PhoneNumberField{ - Identifier: aws.String(id.(types.String).ValueString()), + Identifier: id.(types.String).ValueStringPointer(), }) } } From 0476b5ccbada3dbe69bb7aea0de1696ef5db5f1f Mon Sep 17 00:00:00 2001 From: Sagar Barai Date: Wed, 24 Sep 2025 13:22:51 +0530 Subject: [PATCH 5/6] add changelog file --- .changelog/44426.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/44426.txt diff --git a/.changelog/44426.txt b/.changelog/44426.txt new file mode 100644 index 000000000000..e426b2f1a6be --- /dev/null +++ b/.changelog/44426.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +resource/aws_wafv2_web_acl_rule_group_association: Added support for `managed_rule_group_configs` within `managed_rule_group` and root-level `visibility_config` block for CloudWatch metrics configuration +``` From 3203ce117a4cc3f941395f4d3f8154a7f4ef0748 Mon Sep 17 00:00:00 2001 From: Sagar Barai Date: Wed, 24 Sep 2025 14:49:42 +0530 Subject: [PATCH 6/6] fix golinting issue move custom flatten and expand for acfp rule set to flex.go file --- internal/service/wafv2/flex.go | 155 ++++++++++++++++++ .../wafv2/web_acl_rule_group_association.go | 151 ----------------- .../web_acl_rule_group_association_test.go | 3 - 3 files changed, 155 insertions(+), 154 deletions(-) diff --git a/internal/service/wafv2/flex.go b/internal/service/wafv2/flex.go index c0809dbf0af3..3d0acdeb2cf9 100644 --- a/internal/service/wafv2/flex.go +++ b/internal/service/wafv2/flex.go @@ -4,6 +4,7 @@ package wafv2 import ( + "context" "errors" "fmt" "reflect" @@ -11,8 +12,14 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" awstypes "github.com/aws/aws-sdk-go-v2/service/wafv2/types" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/flex" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" tfjson "github.com/hashicorp/terraform-provider-aws/internal/json" itypes "github.com/hashicorp/terraform-provider-aws/internal/types" "github.com/hashicorp/terraform-provider-aws/names" @@ -3635,3 +3642,151 @@ func flattenFieldToProtect(apiObject *awstypes.FieldToProtect) any { return []any{tfMap} } + +func expandACFPManagedRuleConfig( + ctx context.Context, + tfACFPRuleConfigModel awsManagedRulesACFPRuleSetModel, +) (awstypes.AWSManagedRulesACFPRuleSet, diag.Diagnostics) { + var diags diag.Diagnostics + var acfpRuleSetConfig awstypes.AWSManagedRulesACFPRuleSet + + if tfACFPRuleConfigModel.RequestInspection.IsNull() && tfACFPRuleConfigModel.RequestInspection.IsUnknown() { + // auto flex works, no need to manually expand + diags.Append(fwflex.Expand(ctx, tfACFPRuleConfigModel, &acfpRuleSetConfig)...) + return acfpRuleSetConfig, diags + } + + acfpRuleSetConfig = awstypes.AWSManagedRulesACFPRuleSet{ + CreationPath: tfACFPRuleConfigModel.CreationPath.ValueStringPointer(), + RegistrationPagePath: tfACFPRuleConfigModel.RegistrationPagePath.ValueStringPointer(), + EnableRegexInPath: tfACFPRuleConfigModel.EnableRegexInPath.ValueBool(), + } + + var tfReqInspection requestInspectionACFPModel + diags.Append(tfACFPRuleConfigModel.RequestInspection.Elements()[0].(fwtypes.ObjectValueOf[requestInspectionACFPModel]).As(ctx, &tfReqInspection, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return acfpRuleSetConfig, diags + } + + addressFields := make([]awstypes.AddressField, 0) + + for _, af := range tfReqInspection.AddressFields.Elements() { + var tfAddressField addressFieldModel + diags.Append(af.(fwtypes.ObjectValueOf[addressFieldModel]).As(ctx, &tfAddressField, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return acfpRuleSetConfig, diags + } + for _, id := range tfAddressField.Identifiers.Elements() { + addressFields = append(addressFields, awstypes.AddressField{ + Identifier: id.(types.String).ValueStringPointer(), + }) + } + } + + phoneNumberFields := make([]awstypes.PhoneNumberField, 0) + for _, pf := range tfReqInspection.PhoneNumberFields.Elements() { + var tfPhoneNumberField phoneNumberFieldModel + diags.Append(pf.(fwtypes.ObjectValueOf[phoneNumberFieldModel]).As(ctx, &tfPhoneNumberField, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return acfpRuleSetConfig, diags + } + for _, id := range tfPhoneNumberField.Identifiers.Elements() { + phoneNumberFields = append(phoneNumberFields, awstypes.PhoneNumberField{ + Identifier: id.(types.String).ValueStringPointer(), + }) + } + } + + acfpRuleSetConfig.RequestInspection = &awstypes.RequestInspectionACFP{ + AddressFields: addressFields, + PhoneNumberFields: phoneNumberFields, + PayloadType: awstypes.PayloadType(tfReqInspection.PayloadType.ValueString()), + } + + diags.Append(fwflex.Expand(ctx, tfReqInspection.EmailField, &acfpRuleSetConfig.RequestInspection.EmailField)...) + if diags.HasError() { + return acfpRuleSetConfig, diags + } + + diags.Append(fwflex.Expand(ctx, tfReqInspection.UsernameField, &acfpRuleSetConfig.RequestInspection.UsernameField)...) + if diags.HasError() { + return acfpRuleSetConfig, diags + } + + diags.Append(fwflex.Expand(ctx, tfReqInspection.PasswordField, &acfpRuleSetConfig.RequestInspection.PasswordField)...) + if diags.HasError() { + return acfpRuleSetConfig, diags + } + + return acfpRuleSetConfig, diags +} + +func flattenACFPRuleConfig( + ctx context.Context, + acfp *awstypes.AWSManagedRulesACFPRuleSet, + managedRuleGroupConfigs []awstypes.ManagedRuleGroupConfig, +) (fwtypes.ListNestedObjectValueOf[managedRuleGroupConfigModel], diag.Diagnostics) { + var diags diag.Diagnostics + + if acfp.RequestInspection == nil { + // autoflex is possible + var tfManagedRuleGroupConfigModel managedRuleGroupConfigModel + diags.Append(fwflex.Flatten(ctx, managedRuleGroupConfigs, &tfManagedRuleGroupConfigModel)...) + return fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*managedRuleGroupConfigModel{&tfManagedRuleGroupConfigModel}), diags + } + + requestInspection := acfp.RequestInspection + var emailField emailFieldModel + var usernameField usernameFieldModel + var passwordField passwordFieldModel + + fwflex.Flatten(ctx, requestInspection.EmailField, &emailField) + fwflex.Flatten(ctx, requestInspection.UsernameField, &usernameField) + fwflex.Flatten(ctx, requestInspection.PasswordField, &passwordField) + + var addrList []attr.Value + for _, af := range requestInspection.AddressFields { + addrList = append(addrList, types.StringValue(aws.ToString(af.Identifier))) + } + ids := fwtypes.NewListValueOfMust[types.String](ctx, addrList) + afModel := &addressFieldModel{ + Identifiers: ids, + } + + var phoneList []attr.Value + for _, pf := range requestInspection.PhoneNumberFields { + phoneList = append(phoneList, types.StringValue(aws.ToString(pf.Identifier))) + } + pfModel := &phoneNumberFieldModel{ + Identifiers: fwtypes.NewListValueOfMust[types.String](ctx, phoneList), + } + + tfRequestInspection := &requestInspectionACFPModel{ + PayloadType: types.StringValue(string(requestInspection.PayloadType)), + EmailField: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*emailFieldModel{&emailField}), + UsernameField: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*usernameFieldModel{&usernameField}), + PasswordField: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*passwordFieldModel{&passwordField}), + AddressFields: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*addressFieldModel{afModel}), + PhoneNumberFields: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*phoneNumberFieldModel{pfModel}), + } + + tfACFPModel := &awsManagedRulesACFPRuleSetModel{ + CreationPath: types.StringValue(aws.ToString(acfp.CreationPath)), + RegistrationPagePath: types.StringValue(aws.ToString(acfp.RegistrationPagePath)), + EnableRegexInPath: types.BoolValue(acfp.EnableRegexInPath), + ResponseInspection: fwtypes.NewListNestedObjectValueOfNull[responseInspectionModel](ctx), + RequestInspection: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*requestInspectionACFPModel{tfRequestInspection}), + } + + patchedRuleGroupConfig := make([]*managedRuleGroupConfigModel, 0, 1) + + tfManagedRuleGroupConfigModel := &managedRuleGroupConfigModel{ + AWSManagedRulesACFPRuleSet: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*awsManagedRulesACFPRuleSetModel{tfACFPModel}), + AWSManagedRulesATPRuleSet: fwtypes.NewListNestedObjectValueOfNull[awsManagedRulesATPRuleSetModel](ctx), + AWSManagedRulesBotControlRuleSet: fwtypes.NewListNestedObjectValueOfNull[awsManagedRulesBotControlRuleSetModel](ctx), + AWSManagedRulesAntiDDoSRuleSet: fwtypes.NewListNestedObjectValueOfNull[awsManagedRulesAntiDDoSRuleSetModel](ctx), + } + patchedRuleGroupConfig = append(patchedRuleGroupConfig, tfManagedRuleGroupConfigModel) + + return fwtypes.NewListNestedObjectValueOfSliceMust(ctx, patchedRuleGroupConfig), diags +} diff --git a/internal/service/wafv2/web_acl_rule_group_association.go b/internal/service/wafv2/web_acl_rule_group_association.go index 70f89ba90076..0671b39758eb 100644 --- a/internal/service/wafv2/web_acl_rule_group_association.go +++ b/internal/service/wafv2/web_acl_rule_group_association.go @@ -18,7 +18,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" - "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -1500,7 +1499,6 @@ func (r *resourceWebACLRuleGroupAssociation) Update(ctx context.Context, req res managedRuleGroupConfigs = append(managedRuleGroupConfigs, awstypes.ManagedRuleGroupConfig{ AWSManagedRulesACFPRuleSet: &acfpConfig, }) - } else { // let auto flex handle expand resp.Diagnostics.Append(fwflex.Expand(ctx, managedRuleGroupRef.ManagedRuleGroupConfig, &managedRuleGroupConfigs)...) @@ -1803,76 +1801,6 @@ func parseWebACLARN(arn string) (id, name, scope string, err error) { return resourceParts[idIndex], resourceParts[nameIndex], scopeValue, nil } -func flattenACFPRuleConfig( - ctx context.Context, - acfp *awstypes.AWSManagedRulesACFPRuleSet, - managedRuleGroupConfigs []awstypes.ManagedRuleGroupConfig, -) (fwtypes.ListNestedObjectValueOf[managedRuleGroupConfigModel], diag.Diagnostics) { - var diags diag.Diagnostics - - if acfp.RequestInspection == nil { - // autoflex is possible - var tfManagedRuleGroupConfigModel managedRuleGroupConfigModel - diags.Append(fwflex.Flatten(ctx, managedRuleGroupConfigs, &tfManagedRuleGroupConfigModel)...) - return fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*managedRuleGroupConfigModel{&tfManagedRuleGroupConfigModel}), diags - } - - requestInspection := acfp.RequestInspection - var emailField emailFieldModel - var usernameField usernameFieldModel - var passwordField passwordFieldModel - - fwflex.Flatten(ctx, requestInspection.EmailField, &emailField) - fwflex.Flatten(ctx, requestInspection.UsernameField, &usernameField) - fwflex.Flatten(ctx, requestInspection.PasswordField, &passwordField) - - var addrList []attr.Value - for _, af := range requestInspection.AddressFields { - addrList = append(addrList, types.StringValue(aws.ToString(af.Identifier))) - } - ids := fwtypes.NewListValueOfMust[types.String](ctx, addrList) - afModel := &addressFieldModel{ - Identifiers: ids, - } - - var phoneList []attr.Value - for _, pf := range requestInspection.PhoneNumberFields { - phoneList = append(phoneList, types.StringValue(aws.ToString(pf.Identifier))) - } - pfModel := &phoneNumberFieldModel{ - Identifiers: fwtypes.NewListValueOfMust[types.String](ctx, phoneList), - } - - tfRequestInspection := &requestInspectionACFPModel{ - PayloadType: types.StringValue(string(requestInspection.PayloadType)), - EmailField: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*emailFieldModel{&emailField}), - UsernameField: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*usernameFieldModel{&usernameField}), - PasswordField: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*passwordFieldModel{&passwordField}), - AddressFields: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*addressFieldModel{afModel}), - PhoneNumberFields: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*phoneNumberFieldModel{pfModel}), - } - - tfACFPModel := &awsManagedRulesACFPRuleSetModel{ - CreationPath: types.StringValue(aws.ToString(acfp.CreationPath)), - RegistrationPagePath: types.StringValue(aws.ToString(acfp.RegistrationPagePath)), - EnableRegexInPath: types.BoolValue(acfp.EnableRegexInPath), - ResponseInspection: fwtypes.NewListNestedObjectValueOfNull[responseInspectionModel](ctx), - RequestInspection: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*requestInspectionACFPModel{tfRequestInspection}), - } - - patchedRuleGroupConfig := make([]*managedRuleGroupConfigModel, 0, 1) - - tfManagedRuleGroupConfigModel := &managedRuleGroupConfigModel{ - AWSManagedRulesACFPRuleSet: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*awsManagedRulesACFPRuleSetModel{tfACFPModel}), - AWSManagedRulesATPRuleSet: fwtypes.NewListNestedObjectValueOfNull[awsManagedRulesATPRuleSetModel](ctx), - AWSManagedRulesBotControlRuleSet: fwtypes.NewListNestedObjectValueOfNull[awsManagedRulesBotControlRuleSetModel](ctx), - AWSManagedRulesAntiDDoSRuleSet: fwtypes.NewListNestedObjectValueOfNull[awsManagedRulesAntiDDoSRuleSetModel](ctx), - } - patchedRuleGroupConfig = append(patchedRuleGroupConfig, tfManagedRuleGroupConfigModel) - - return fwtypes.NewListNestedObjectValueOfSliceMust(ctx, patchedRuleGroupConfig), diags -} - func patchBotControlFlatten( ctx context.Context, botControl *awstypes.AWSManagedRulesBotControlRuleSet, @@ -1889,85 +1817,6 @@ func patchBotControlFlatten( return tfManagedRuleGroupConfigModel } -func expandACFPManagedRuleConfig( - ctx context.Context, - tfACFPRuleConfigModel awsManagedRulesACFPRuleSetModel, -) (awstypes.AWSManagedRulesACFPRuleSet, diag.Diagnostics) { - var diags diag.Diagnostics - var acfpRuleSetConfig awstypes.AWSManagedRulesACFPRuleSet - - if tfACFPRuleConfigModel.RequestInspection.IsNull() && tfACFPRuleConfigModel.RequestInspection.IsUnknown() { - // auto flex works, no need to manually expand - diags.Append(fwflex.Expand(ctx, tfACFPRuleConfigModel, &acfpRuleSetConfig)...) - return acfpRuleSetConfig, diags - } - - acfpRuleSetConfig = awstypes.AWSManagedRulesACFPRuleSet{ - CreationPath: tfACFPRuleConfigModel.CreationPath.ValueStringPointer(), - RegistrationPagePath: tfACFPRuleConfigModel.RegistrationPagePath.ValueStringPointer(), - EnableRegexInPath: tfACFPRuleConfigModel.EnableRegexInPath.ValueBool(), - } - - var tfReqInspection requestInspectionACFPModel - diags.Append(tfACFPRuleConfigModel.RequestInspection.Elements()[0].(fwtypes.ObjectValueOf[requestInspectionACFPModel]).As(ctx, &tfReqInspection, basetypes.ObjectAsOptions{})...) - if diags.HasError() { - return acfpRuleSetConfig, diags - } - - addressFields := make([]awstypes.AddressField, 0) - - for _, af := range tfReqInspection.AddressFields.Elements() { - var tfAddressField addressFieldModel - diags.Append(af.(fwtypes.ObjectValueOf[addressFieldModel]).As(ctx, &tfAddressField, basetypes.ObjectAsOptions{})...) - if diags.HasError() { - return acfpRuleSetConfig, diags - } - for _, id := range tfAddressField.Identifiers.Elements() { - addressFields = append(addressFields, awstypes.AddressField{ - Identifier: id.(types.String).ValueStringPointer(), - }) - } - } - - phoneNumberFields := make([]awstypes.PhoneNumberField, 0) - for _, pf := range tfReqInspection.PhoneNumberFields.Elements() { - var tfPhoneNumberField phoneNumberFieldModel - diags.Append(pf.(fwtypes.ObjectValueOf[phoneNumberFieldModel]).As(ctx, &tfPhoneNumberField, basetypes.ObjectAsOptions{})...) - if diags.HasError() { - return acfpRuleSetConfig, diags - } - for _, id := range tfPhoneNumberField.Identifiers.Elements() { - phoneNumberFields = append(phoneNumberFields, awstypes.PhoneNumberField{ - Identifier: id.(types.String).ValueStringPointer(), - }) - } - } - - acfpRuleSetConfig.RequestInspection = &awstypes.RequestInspectionACFP{ - AddressFields: addressFields, - PhoneNumberFields: phoneNumberFields, - PayloadType: awstypes.PayloadType(tfReqInspection.PayloadType.ValueString()), - } - - diags.Append(fwflex.Expand(ctx, tfReqInspection.EmailField, &acfpRuleSetConfig.RequestInspection.EmailField)...) - if diags.HasError() { - return acfpRuleSetConfig, diags - } - - diags.Append(fwflex.Expand(ctx, tfReqInspection.UsernameField, &acfpRuleSetConfig.RequestInspection.UsernameField)...) - if diags.HasError() { - return acfpRuleSetConfig, diags - } - - diags.Append(fwflex.Expand(ctx, tfReqInspection.PasswordField, &acfpRuleSetConfig.RequestInspection.PasswordField)...) - if diags.HasError() { - return acfpRuleSetConfig, diags - } - - return acfpRuleSetConfig, diags - -} - type resourceWebACLRuleGroupAssociationModel struct { framework.WithRegionModel RuleName types.String `tfsdk:"rule_name"` diff --git a/internal/service/wafv2/web_acl_rule_group_association_test.go b/internal/service/wafv2/web_acl_rule_group_association_test.go index f29aa1b30368..6a7fa9f16898 100644 --- a/internal/service/wafv2/web_acl_rule_group_association_test.go +++ b/internal/service/wafv2/web_acl_rule_group_association_test.go @@ -1938,7 +1938,6 @@ resource "aws_wafv2_web_acl_rule_group_association" "test" { override_action = "none" } `, rName) - } func testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfigUpdate(rName string) string { @@ -1981,7 +1980,6 @@ resource "aws_wafv2_web_acl_rule_group_association" "test" { override_action = "none" } `, rName) - } func testAccWebACLRuleGroupAssociationConfig_ManagedRuleGroupConfig_acfpRuleSet(rName string) string { @@ -2317,7 +2315,6 @@ resource "aws_wafv2_web_acl_rule_group_association" "test" { override_action = "none" } - `, rName) }