Skip to content

Commit

Permalink
[Enhancement:] cdn_frontdoor_firewall_policy* - add support for `js…
Browse files Browse the repository at this point in the history
…_challenge_cookie_expiration_in_minutes` policy (#28284)

* Initial Check-in...

* Added js Challenge to policy and policy datasources...
  • Loading branch information
WodansSon authored Jan 31, 2025
1 parent 3c5cace commit d834600
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ func dataSourceCdnFrontDoorFirewallPolicy() *pluginsdk.Resource {
Computed: true,
},

"js_challenge_cookie_expiration_in_minutes": {
Type: pluginsdk.TypeInt,
Computed: true,
},

"redirect_url": {
Type: pluginsdk.TypeString,
Computed: true,
Expand Down Expand Up @@ -78,7 +83,7 @@ func dataSourceCdnFrontDoorFirewallPolicyRead(d *pluginsdk.ResourceData, meta in

result, err := client.PoliciesGet(ctx, id)
if err != nil {
if !response.WasNotFound(result.HttpResponse) {
if response.WasNotFound(result.HttpResponse) {
return fmt.Errorf("%s was not found", id)
}

Expand All @@ -103,6 +108,7 @@ func dataSourceCdnFrontDoorFirewallPolicyRead(d *pluginsdk.ResourceData, meta in
d.Set("enabled", pointer.From(policy.EnabledState) == waf.PolicyEnabledStateEnabled)
d.Set("mode", pointer.From(policy.Mode))
d.Set("redirect_url", policy.RedirectURL)
d.Set("js_challenge_cookie_expiration_in_minutes", int(pointer.From(policy.JavascriptChallengeExpirationInMinutes)))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ func TestAccCdnFrontDoorFirewallPolicyDataSource_basic(t *testing.T) {
Config: d.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).Key("redirect_url").MatchesOtherKey(check.That("azurerm_cdn_frontdoor_firewall_policy.test").Key("redirect_url")),
check.That(data.ResourceName).Key("js_challenge_cookie_expiration_in_minutes").HasValue("30"),
),
},
})
}

func TestAccCdnFrontDoorFirewallPolicyDataSourceJsChallenge_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "data.azurerm_cdn_frontdoor_firewall_policy", "test")
d := CdnFrontDoorFirewallPolicyDataSource{}

data.DataSourceTest(t, []acceptance.TestStep{
{
Config: d.basicJsChallenge(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).Key("redirect_url").MatchesOtherKey(check.That("azurerm_cdn_frontdoor_firewall_policy.test").Key("redirect_url")),
check.That(data.ResourceName).Key("js_challenge_cookie_expiration_in_minutes").HasValue("45"),
),
},
})
Expand All @@ -33,7 +49,18 @@ func (CdnFrontDoorFirewallPolicyDataSource) basic(data acceptance.TestData) stri
data "azurerm_cdn_frontdoor_firewall_policy" "test" {
name = azurerm_cdn_frontdoor_firewall_policy.test.name
resource_group_name = azurerm_cdn_frontdoor_profile.test.resource_group_name
resource_group_name = azurerm_resource_group.test.name
}
`, CdnFrontDoorFirewallPolicyResource{}.basic(data))
}

func (CdnFrontDoorFirewallPolicyDataSource) basicJsChallenge(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
data "azurerm_cdn_frontdoor_firewall_policy" "test" {
name = azurerm_cdn_frontdoor_firewall_policy.test.name
resource_group_name = azurerm_resource_group.test.name
}
`, CdnFrontDoorFirewallPolicyResource{}.complete(data))
`, CdnFrontDoorFirewallPolicyResource{}.basicJsChallenge(data))
}
28 changes: 21 additions & 7 deletions internal/services/cdn/cdn_frontdoor_firewall_policy_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ func resourceCdnFrontDoorFirewallPolicy() *pluginsdk.Resource {
Default: true,
},

// NOTE: Through local testing, the new API js challenge expiration is always
// enabled no matter what and cannot be disabled...
"js_challenge_cookie_expiration_in_minutes": {
Type: pluginsdk.TypeInt,
Optional: true,
Default: 30,
ValidateFunc: validation.IntBetween(5, 1440),
},

"redirect_url": {
Type: pluginsdk.TypeString,
Optional: true,
Expand Down Expand Up @@ -490,6 +499,7 @@ func resourceCdnFrontDoorFirewallPolicyCreate(d *pluginsdk.ResourceData, meta in
sku := d.Get("sku_name").(string)
mode := waf.PolicyMode(d.Get("mode").(string))
redirectUrl := d.Get("redirect_url").(string)
jsChallengeExpirationInMinutes := int64(d.Get("js_challenge_cookie_expiration_in_minutes").(int))
customBlockResponseStatusCode := d.Get("custom_block_response_status_code").(int)
customBlockResponseBody := d.Get("custom_block_response_body").(string)
customRules := d.Get("custom_rule").([]interface{})
Expand All @@ -509,9 +519,10 @@ func resourceCdnFrontDoorFirewallPolicyCreate(d *pluginsdk.ResourceData, meta in
},
Properties: &waf.WebApplicationFirewallPolicyProperties{
PolicySettings: &waf.PolicySettings{
EnabledState: pointer.To(enabled),
Mode: pointer.To(mode),
RequestBodyCheck: pointer.To(requestBodyCheck),
EnabledState: pointer.To(enabled),
Mode: pointer.To(mode),
RequestBodyCheck: pointer.To(requestBodyCheck),
JavascriptChallengeExpirationInMinutes: pointer.To(jsChallengeExpirationInMinutes),
},
CustomRules: expandCdnFrontDoorFirewallCustomRules(customRules),
},
Expand Down Expand Up @@ -570,7 +581,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") {
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") {
enabled := waf.PolicyEnabledStateDisabled
if d.Get("enabled").(bool) {
enabled = waf.PolicyEnabledStateEnabled
Expand All @@ -580,10 +591,12 @@ func resourceCdnFrontDoorFirewallPolicyUpdate(d *pluginsdk.ResourceData, meta in
if d.Get("request_body_check_enabled").(bool) {
requestBodyCheck = waf.PolicyRequestBodyCheckEnabled
}

props.PolicySettings = &waf.PolicySettings{
EnabledState: pointer.To(enabled),
Mode: pointer.To(waf.PolicyMode(d.Get("mode").(string))),
RequestBodyCheck: pointer.To(requestBodyCheck),
EnabledState: pointer.To(enabled),
Mode: pointer.To(waf.PolicyMode(d.Get("mode").(string))),
RequestBodyCheck: pointer.To(requestBodyCheck),
JavascriptChallengeExpirationInMinutes: pointer.To(int64(d.Get("js_challenge_cookie_expiration_in_minutes").(int))),
}

if redirectUrl := d.Get("redirect_url").(string); redirectUrl != "" {
Expand Down Expand Up @@ -679,6 +692,7 @@ func resourceCdnFrontDoorFirewallPolicyRead(d *pluginsdk.ResourceData, meta inte
d.Set("redirect_url", policy.RedirectURL)
d.Set("custom_block_response_status_code", int(pointer.From(policy.CustomBlockResponseStatusCode)))
d.Set("custom_block_response_body", policy.CustomBlockResponseBody)
d.Set("js_challenge_cookie_expiration_in_minutes", int(pointer.From(policy.JavascriptChallengeExpirationInMinutes)))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import (
"regexp"
"testing"

"github.com/hashicorp/go-azure-helpers/lang/response"
waf "github.com/hashicorp/go-azure-sdk/resource-manager/frontdoor/2024-02-01/webapplicationfirewallpolicies"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance"
"github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check"
"github.com/hashicorp/terraform-provider-azurerm/internal/clients"
"github.com/hashicorp/terraform-provider-azurerm/internal/services/cdn/parse"
"github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk"
"github.com/hashicorp/terraform-provider-azurerm/utils"
)
Expand All @@ -27,6 +28,22 @@ func TestAccCdnFrontDoorFirewallPolicy_basic(t *testing.T) {
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("js_challenge_cookie_expiration_in_minutes").HasValue("30"),
),
},
data.ImportStep(),
})
}

func TestAccCdnFrontDoorFirewallPolicyJsChallenge_basic(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_cdn_frontdoor_firewall_policy", "test")
r := CdnFrontDoorFirewallPolicyResource{}
data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basicJsChallenge(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("js_challenge_cookie_expiration_in_minutes").HasValue("45"),
),
},
data.ImportStep(),
Expand Down Expand Up @@ -75,6 +92,45 @@ func TestAccCdnFrontDoorFirewallPolicy_update(t *testing.T) {
})
}

func TestAccCdnFrontDoorFirewallPolicyJsChallenge_update(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_cdn_frontdoor_firewall_policy", "test")
r := CdnFrontDoorFirewallPolicyResource{}
data.ResourceTest(t, r, []acceptance.TestStep{
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("js_challenge_cookie_expiration_in_minutes").HasValue("30"),
),
},
data.ImportStep(),
{
Config: r.basicJsChallenge(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("js_challenge_cookie_expiration_in_minutes").HasValue("45"),
),
},
data.ImportStep(),
{
Config: r.basicJsChallengeUpdate(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("js_challenge_cookie_expiration_in_minutes").HasValue("1440"),
),
},
data.ImportStep(),
{
Config: r.basic(data),
Check: acceptance.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("js_challenge_cookie_expiration_in_minutes").HasValue("30"),
),
},
data.ImportStep(),
})
}

func TestAccCdnFrontDoorFirewallPolicy_complete(t *testing.T) {
// NOTE: Regression test case for issue #19088
data := acceptance.BuildTestData(t, "azurerm_cdn_frontdoor_firewall_policy", "test")
Expand Down Expand Up @@ -254,14 +310,14 @@ func TestAccCdnFrontDoorFirewallPolicy_DRSTwoPointOneActionError(t *testing.T) {
}

func (CdnFrontDoorFirewallPolicyResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) {
id, err := parse.FrontDoorFirewallPolicyID(state.ID)
id, err := waf.ParseFrontDoorWebApplicationFirewallPolicyID(state.ID)
if err != nil {
return nil, err
}

resp, err := clients.Cdn.FrontDoorLegacyFirewallPoliciesClient.Get(ctx, id.ResourceGroup, id.FrontDoorWebApplicationFirewallPolicyName)
result, err := clients.Cdn.FrontDoorFirewallPoliciesClient.PoliciesGet(ctx, *id)
if err != nil {
if utils.ResponseWasNotFound(resp.Response) {
if response.WasNotFound(result.HttpResponse) {
return utils.Bool(false), nil
}
return nil, fmt.Errorf("retrieving %s: %+v", id, err)
Expand Down Expand Up @@ -303,6 +359,38 @@ resource "azurerm_cdn_frontdoor_firewall_policy" "test" {
`, tmp, data.RandomInteger)
}

func (r CdnFrontDoorFirewallPolicyResource) basicJsChallenge(data acceptance.TestData) string {
tmp := r.template(data)
return fmt.Sprintf(`
%s
resource "azurerm_cdn_frontdoor_firewall_policy" "test" {
name = "accTestWAF%d"
resource_group_name = azurerm_resource_group.test.name
sku_name = azurerm_cdn_frontdoor_profile.test.sku_name
mode = "Prevention"
js_challenge_cookie_expiration_in_minutes = 45
}
`, tmp, data.RandomInteger)
}

func (r CdnFrontDoorFirewallPolicyResource) basicJsChallengeUpdate(data acceptance.TestData) string {
tmp := r.template(data)
return fmt.Sprintf(`
%s
resource "azurerm_cdn_frontdoor_firewall_policy" "test" {
name = "accTestWAF%d"
resource_group_name = azurerm_resource_group.test.name
sku_name = azurerm_cdn_frontdoor_profile.test.sku_name
mode = "Prevention"
js_challenge_cookie_expiration_in_minutes = 1440
}
`, tmp, data.RandomInteger)
}

func (r CdnFrontDoorFirewallPolicyResource) requiresImport(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
Expand Down Expand Up @@ -389,6 +477,8 @@ resource "azurerm_cdn_frontdoor_firewall_policy" "test" {
custom_block_response_status_code = 403
custom_block_response_body = "PGh0bWw+CjxoZWFkZXI+PHRpdGxlPkhlbGxvPC90aXRsZT48L2hlYWRlcj4KPGJvZHk+CkhlbGxvIHdvcmxkCjwvYm9keT4KPC9odG1sPg=="
js_challenge_cookie_expiration_in_minutes = 30
custom_rule {
name = "Rule1"
enabled = true
Expand Down
2 changes: 2 additions & 0 deletions website/docs/d/cdn_frontdoor_firewall_policy.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ The following attributes are exported:

* `frontend_endpoint_ids` - The Front Door Profiles frontend endpoints associated with this Front Door Firewall Policy.

* `js_challenge_cookie_expiration_in_minutes` - The Front Door Firewall Policy JavaScript challenge cookie lifetime in minutes.

* `mode` - The Front Door Firewall Policy mode.

* `redirect_url` - The redirect URL for the client.
Expand Down
14 changes: 9 additions & 5 deletions website/docs/r/cdn_frontdoor_firewall_policy.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,19 @@ The following arguments are supported:

* `sku_name` - (Required) The sku's pricing tier for this Front Door Firewall Policy. Possible values include `Standard_AzureFrontDoor` or `Premium_AzureFrontDoor`. Changing this forces a new resource to be created.

-> **NOTE:** The `Standard_AzureFrontDoor` Front Door Firewall Policy sku may contain `custom` rules only. The `Premium_AzureFrontDoor` Front Door Firewall Policy skus may contain both `custom` and `managed` rules.
-> **Note:** The `Standard_AzureFrontDoor` Front Door Firewall Policy sku may contain `custom` rules only. The `Premium_AzureFrontDoor` Front Door Firewall Policy skus may contain both `custom` and `managed` rules.

* `enabled` - (Optional) Is the Front Door Firewall Policy enabled? Defaults to `true`.

* `js_challenge_cookie_expiration_in_minutes` - (Optional) Specifies the JavaScript challenge cookie lifetime in minutes, after which the user will be revalidated. Possible values are between `5` to `1440` minutes. Defaults to `30` minutes.

!> **Important:** Azure Web Application Firewall JavaScript challenge is currently in **PREVIEW**. See the [Supplemental Terms of Use for Microsoft Azure Previews](https://azure.microsoft.com/support/legal/preview-supplemental-terms/) for legal terms that apply to Azure features that are in beta, preview, or otherwise not yet released into general availability.

* `mode` - (Required) The Front Door Firewall Policy mode. Possible values are `Detection`, `Prevention`.

* `request_body_check_enabled` - (Optional) Should policy managed rules inspect the request body content? Defaults to `true`.

-> **NOTE:** When run in `Detection` mode, the Front Door Firewall Policy doesn't take any other actions other than monitoring and logging the request and its matched Front Door Rule to the Web Application Firewall logs.
-> **Note:** When run in `Detection` mode, the Front Door Firewall Policy doesn't take any other actions other than monitoring and logging the request and its matched Front Door Rule to the Web Application Firewall logs.

* `redirect_url` - (Optional) If action type is redirect, this field represents redirect URL for the client.

Expand Down Expand Up @@ -227,7 +231,7 @@ A `rule` block supports the following:

* `action` - (Required) The action to be applied when the managed rule matches or when the anomaly score is 5 or greater. Possible values for DRS `1.1` and below are `Allow`, `Log`, `Block`, and `Redirect`. For DRS `2.0` and above the possible values are `Log` or `AnomalyScoring`.

->**NOTE:** Please see the DRS [product documentation](https://learn.microsoft.com/azure/web-application-firewall/afds/waf-front-door-drs?tabs=drs20#anomaly-scoring-mode) for more information.
->**Note:** Please see the DRS [product documentation](https://learn.microsoft.com/azure/web-application-firewall/afds/waf-front-door-drs?tabs=drs20#anomaly-scoring-mode) for more information.

* `enabled` - (Optional) Is the managed rule override enabled or disabled. Defaults to `false`

Expand All @@ -239,13 +243,13 @@ An `exclusion` block supports the following:

* `match_variable` - (Required) The variable type to be excluded. Possible values are `QueryStringArgNames`, `RequestBodyPostArgNames`, `RequestCookieNames`, `RequestHeaderNames`, `RequestBodyJsonArgNames`

-> **NOTE:** `RequestBodyJsonArgNames` is only available on Default Rule Set (DRS) 2.0 or later
-> **Note:** `RequestBodyJsonArgNames` is only available on Default Rule Set (DRS) 2.0 or later

* `operator` - (Required) Comparison operator to apply to the selector when specifying which elements in the collection this exclusion applies to. Possible values are: `Equals`, `Contains`, `StartsWith`, `EndsWith`, `EqualsAny`.

* `selector` - (Required) Selector for the value in the `match_variable` attribute this exclusion applies to.

-> **NOTE:** `selector` must be set to `*` if `operator` is set to `EqualsAny`.
-> **Note:** `selector` must be set to `*` if `operator` is set to `EqualsAny`.

## Attributes Reference

Expand Down

0 comments on commit d834600

Please sign in to comment.