Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 195 additions & 2 deletions internal/services/cdn/cdn_frontdoor_firewall_policy_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,57 @@ func resourceCdnFrontDoorFirewallPolicy() *pluginsdk.Resource {
},
},

"log_scrubbing": {
Type: pluginsdk.TypeList,
MaxItems: 1,
Optional: true,
Elem: &pluginsdk.Resource{
Schema: map[string]*pluginsdk.Schema{
"enabled": {
Type: pluginsdk.TypeBool,
Optional: true,
Default: true,
},

"rule": {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted in the docs, if this doesn't match it will be difficult/frustrating for users to work out the correct block name. Since this is a limitation of the Registry docs, I don't think we have a choice here but to deviate from the convention due to the duplication of the block name in this resource.

Suggested change
"rule": {
"scrubbing_rule": {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Type: pluginsdk.TypeList,
MaxItems: 100,
Required: true,
Elem: &pluginsdk.Resource{
Comment on lines +459 to +469
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a default rule / behaviour here if log_scrubbing is enabled, but no rules are defined? Just wondering, if there's no default behaviour, if these should be Required / MinItems instead to ensure the block does something?

(The test cases suggest that your design is fine, but I wanted to be sure since it's a little confusing to have this without rules?)

Suggested change
"enabled": {
Type: pluginsdk.TypeBool,
Optional: true,
Default: true,
},
"rule": {
Type: pluginsdk.TypeList,
MaxItems: 100,
Optional: true,
Elem: &pluginsdk.Resource{
"enabled": {
Type: pluginsdk.TypeBool,
Required: true,
},
"rule": {
Type: pluginsdk.TypeList,
MaxItems: 100,
Minitems: 1,
Elem: &pluginsdk.Resource{

Copy link
Copy Markdown
Collaborator Author

@WodansSon WodansSon Feb 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, there isn't a default behavior, per the service team:

Correction on the behavior of the LogScrubbing state.

  • If LogScrubbing State is set to Enabled but no scrubbing rules are configured, it will result in a no op (no operation).
  • For log scrubbing to take effect, both the LogScrubbing State (Enabled) and at least one Scrubbing Rule with State Enabled must be configured.

Schema: map[string]*pluginsdk.Schema{
"enabled": {
Type: pluginsdk.TypeBool,
Optional: true,
Default: true,
},

"match_variable": {
Type: pluginsdk.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(waf.PossibleValuesForScrubbingRuleEntryMatchVariable(),
false),
},

"operator": {
Type: pluginsdk.TypeString,
Optional: true,
Default: string(waf.ScrubbingRuleEntryMatchOperatorEquals),
ValidateFunc: validation.StringInSlice(waf.PossibleValuesForScrubbingRuleEntryMatchOperator(),
false),
},

"selector": {
Type: pluginsdk.TypeString,
Optional: true,
ValidateFunc: validation.StringIsNotEmpty,
},
},
},
},
},
},
},

"frontend_endpoint_ids": {
Type: pluginsdk.TypeList,
Computed: true,
Expand Down Expand Up @@ -514,6 +565,17 @@ func resourceCdnFrontDoorFirewallPolicy() *pluginsdk.Resource {

return nil
}),

// Verify that the scrubbing_rule's are valid...
pluginsdk.CustomizeDiffShim(func(ctx context.Context, diff *pluginsdk.ResourceDiff, v interface{}) error {
if v, ok := diff.GetOk("log_scrubbing"); ok {
_, err := expandCdnFrontDoorFirewallLogScrubbingPolicy(v.([]interface{}))
if err != nil {
return err
}
}
return nil
}),
),
}
}
Expand Down Expand Up @@ -558,9 +620,15 @@ func resourceCdnFrontDoorFirewallPolicyCreate(d *pluginsdk.ResourceData, meta in
customBlockResponseStatusCode := d.Get("custom_block_response_status_code").(int)
customBlockResponseBody := d.Get("custom_block_response_body").(string)
customRules := d.Get("custom_rule").([]interface{})

managedRules, err := expandCdnFrontDoorFirewallManagedRules(d.Get("managed_rule").([]interface{}))
if err != nil {
return fmt.Errorf("expanding managed_rule: %+v", err)
return fmt.Errorf("expanding 'managed_rule': %+v", err)
}

logScrubbingRules, err := expandCdnFrontDoorFirewallLogScrubbingPolicy(d.Get("log_scrubbing").([]interface{}))
if err != nil {
return fmt.Errorf("expanding 'log_scrubbing': %+v", err)
}

if sku != string(waf.SkuNamePremiumAzureFrontDoor) && managedRules != nil {
Expand Down Expand Up @@ -613,6 +681,10 @@ func resourceCdnFrontDoorFirewallPolicyCreate(d *pluginsdk.ResourceData, meta in
payload.Properties.PolicySettings.CustomBlockResponseStatusCode = pointer.To(int64(customBlockResponseStatusCode))
}

if logScrubbingRules != nil {
payload.Properties.PolicySettings.LogScrubbing = logScrubbingRules
}

err = client.PoliciesCreateOrUpdateThenPoll(ctx, id, payload)
if err != nil {
return fmt.Errorf("creating %s: %+v", id, err)
Expand Down Expand Up @@ -649,7 +721,7 @@ func resourceCdnFrontDoorFirewallPolicyUpdate(d *pluginsdk.ResourceData, meta in

props := *model.Properties

if d.HasChanges("custom_block_response_body", "custom_block_response_status_code", "enabled", "mode", "redirect_url", "request_body_check_enabled", "js_challenge_cookie_expiration_in_minutes") {
if d.HasChanges("custom_block_response_body", "custom_block_response_status_code", "enabled", "mode", "redirect_url", "request_body_check_enabled", "js_challenge_cookie_expiration_in_minutes", "log_scrubbing") {
enabled := waf.PolicyEnabledStateDisabled
if d.Get("enabled").(bool) {
enabled = waf.PolicyEnabledStateEnabled
Expand Down Expand Up @@ -715,6 +787,17 @@ func resourceCdnFrontDoorFirewallPolicyUpdate(d *pluginsdk.ResourceData, meta in
}
}

if d.HasChange("log_scrubbing") {
logScrubbingPolicy, err := expandCdnFrontDoorFirewallLogScrubbingPolicy(d.Get("log_scrubbing").([]interface{}))
if err != nil {
return err
}

if logScrubbingPolicy != nil {
props.PolicySettings.LogScrubbing = logScrubbingPolicy
}
}

if d.HasChange("tags") {
model.Tags = tags.Expand(d.Get("tags").(map[string]interface{}))
}
Expand Down Expand Up @@ -778,6 +861,10 @@ func resourceCdnFrontDoorFirewallPolicyRead(d *pluginsdk.ResourceData, meta inte
if policy.JavascriptChallengeExpirationInMinutes != nil {
d.Set("js_challenge_cookie_expiration_in_minutes", int(pointer.From(policy.JavascriptChallengeExpirationInMinutes)))
}

if err := d.Set("log_scrubbing", flattenCdnFrontDoorFirewallLogScrubbingPolicy(policy.LogScrubbing)); err != nil {
return fmt.Errorf("flattening 'log_scrubbing': %+v", err)
}
}
}

Expand Down Expand Up @@ -1041,6 +1128,80 @@ func expandCdnFrontDoorFirewallRuleOverride(input []interface{}, versionRaw stri
return &result, nil
}

func expandCdnFrontDoorFirewallLogScrubbingPolicy(input []interface{}) (*waf.PolicySettingsLogScrubbing, error) {
if len(input) == 0 {
return nil, nil
}

inputRaw := input[0].(map[string]interface{})

policyEnabled := waf.WebApplicationFirewallScrubbingStateDisabled
if inputRaw["enabled"].(bool) {
policyEnabled = waf.WebApplicationFirewallScrubbingStateEnabled
}

rules, err := expandCdnFrontDoorFirewallScrubbingRules(inputRaw["rule"].([]interface{}))
if err != nil {
return nil, err
}

return &waf.PolicySettingsLogScrubbing{
State: pointer.To(policyEnabled),
ScrubbingRules: rules,
}, nil
}

func expandCdnFrontDoorFirewallScrubbingRules(input []interface{}) (*[]waf.WebApplicationFirewallScrubbingRules, error) {
if len(input) == 0 {
return nil, nil
}

scrubbingRules := make([]waf.WebApplicationFirewallScrubbingRules, 0)

for _, rule := range input {
v := rule.(map[string]interface{})
var item waf.WebApplicationFirewallScrubbingRules

enalbed := waf.ScrubbingRuleEntryStateDisabled
if value := v["enabled"].(bool); value {
enalbed = waf.ScrubbingRuleEntryStateEnabled
}

item.State = pointer.To(enalbed)
item.MatchVariable = waf.ScrubbingRuleEntryMatchVariable(v["match_variable"].(string))
item.SelectorMatchOperator = waf.ScrubbingRuleEntryMatchOperator(v["operator"].(string))

if selector, ok := v["selector"]; ok {
item.Selector = pointer.To(selector.(string))
}

// NOTE: Validate the rules configuration...
switch {
case item.MatchVariable == waf.ScrubbingRuleEntryMatchVariableRequestIPAddress || item.MatchVariable == waf.ScrubbingRuleEntryMatchVariableRequestUri:
// NOTE: 'RequestIPAddress' and 'RequestUri' 'match_variable's can only use the 'EqualsAny' 'operator'...
if item.SelectorMatchOperator != waf.ScrubbingRuleEntryMatchOperatorEqualsAny {
return nil, fmt.Errorf("the %q 'match_variable' must use the %q 'operator', got %q", item.MatchVariable, waf.ScrubbingRuleEntryMatchOperatorEqualsAny, item.SelectorMatchOperator)
}

case item.SelectorMatchOperator == waf.ScrubbingRuleEntryMatchOperatorEquals:
// NOTE: If the 'operator' is set to 'Equals' the 'selector' cannot be 'nil'...
if pointer.From(item.Selector) == "" {
return nil, fmt.Errorf("the 'selector' field must be set when the %q 'operator' is used, got %q", waf.ScrubbingRuleEntryMatchOperatorEquals, "nil")
}

case item.SelectorMatchOperator == waf.ScrubbingRuleEntryMatchOperatorEqualsAny:
// NOTE: If the 'operator' is set to 'EqualsAny' the 'selector' must be 'nil'...
if pointer.From(item.Selector) != "" {
return nil, fmt.Errorf("the 'selector' field cannot be set when the %q 'operator' is used, got %q", waf.ScrubbingRuleEntryMatchOperatorEqualsAny, *item.Selector)
}
}

scrubbingRules = append(scrubbingRules, item)
}

return pointer.To(scrubbingRules), nil
}

func flattenCdnFrontDoorFirewallCustomRules(input *waf.CustomRuleList) []interface{} {
if input == nil || input.Rules == nil {
return []interface{}{}
Expand Down Expand Up @@ -1204,3 +1365,35 @@ func flattenCdnFrontDoorFirewallRules(input *[]waf.ManagedRuleOverride) []interf

return results
}

func flattenCdnFrontDoorFirewallLogScrubbingPolicy(input *waf.PolicySettingsLogScrubbing) []interface{} {
if input == nil {
return make([]interface{}, 0)
}

result := make(map[string]interface{})
result["enabled"] = pointer.From(input.State) == waf.WebApplicationFirewallScrubbingStateEnabled
result["rule"] = flattenCdnFrontDoorFirewallLogScrubbingRules(input.ScrubbingRules)

return []interface{}{result}
}

func flattenCdnFrontDoorFirewallLogScrubbingRules(rules *[]waf.WebApplicationFirewallScrubbingRules) interface{} {
result := make([]interface{}, 0)

if rules == nil || len(*rules) == 0 {
return result
}

for _, rule := range *rules {
item := map[string]interface{}{}
item["enabled"] = pointer.From(rule.State) == waf.ScrubbingRuleEntryStateEnabled
item["match_variable"] = rule.MatchVariable
item["operator"] = rule.SelectorMatchOperator
item["selector"] = pointer.From(rule.Selector)

result = append(result, item)
}

return result
}
Loading
Loading