Coverage by target cloud
How the targets compare
Each row compares a capability of WAFv2 with its adaptation on each target. A dash means this row is not stated for that target.Infrastructure-only adaptation
On Azure
How it works
On AWS, anaws_wafv2_web_acl holds a tree of rules that AWS WAF evaluates in front of an ALB or a CloudFront distribution: IP-set allow/block lists, rate-based rules, byte- and geo-match conditions, and AWS-managed rule groups. The customer’s Azure has a direct analog, the Application Gateway WAF, whose rules live in an azurerm_web_application_firewall_policy.
At the build, Tensor9 reads that rule tree and emits the equivalent Azure policy as a real resource. Each statement becomes a custom_rules entry with a typed match variable and operator, and the schema-required managed set includes the OWASP Core Rule Set. The policy is a native Azure object the customer attaches to their Application Gateway and inspects in the portal; Azure’s WAF engine evaluates every request. Where a statement has no Azure analog, the build says so rather than dropping it silently. The rest of this document walks each mapping and the closing sections collect every place the two engines diverge.
The rule tree compiles to a native Azure WAF policy that Microsoft’s Application Gateway WAF engine evaluates; nothing of Tensor9 is in the request path.
Architecture
Anaws_wafv2_web_acl maps to exactly one azurerm_web_application_firewall_policy. The rules are read in priority order and split by kind. Every custom statement (IP-set, rate-based, byte-match, geo-match) becomes one custom_rules entry holding a match_conditions block with a typed match variable, an operator, and its match values. AWS-managed rule groups do not become custom rules; they map to the policy’s managed_rules block, which always includes the OWASP Core Rule Set so the policy is schema-valid and covers the injection and cross-site families out of the box.
The policy runs in Prevention mode, so a matched block rule stops the request rather than only logging it. The resource group and region come from the stack’s own placement, threaded in as locals so the policy lands in the same resource group as the Application Gateway it protects. Attaching the policy to that gateway is the one manual step, exactly as attaching a web ACL to an ALB is on AWS.
- One policy per web ACL: the rule tree is read in priority order and emitted into a single
azurerm_web_application_firewall_policy. - Custom vs managed. IP-set, rate, byte- and geo-match statements become
custom_rules; AWS-managed groups map to themanaged_rulesOWASP set. - Prevention by default: the policy enforces (blocks matched requests), it does not run in detection-only mode.
- Placement threaded: resource group and location come from the stack, so the policy sits with the gateway it guards.
One web ACL becomes one WAF policy: the rule tree fans into custom_rules plus the managed OWASP set, and the policy attaches to the Application Gateway.
How the rule statements map
The translation is statement by statement. Anip_set_reference_statement becomes a MatchRule whose match variable is RemoteAddr and whose operator is IPMatch; the referenced ip-set’s CIDRs are written directly into the rule’s match_values, so the block or allow decision reads inline on the Azure policy. A rate_based_statement becomes a RateLimitRule keyed on the client address, with the AWS request limit as its threshold. A byte_match_statement becomes a match_condition whose variable is chosen from the AWS field_to_match (a URI-path match becomes RequestUri, a query-string match becomes QueryString, a header match becomes RequestHeaders), and its positional constraint becomes the Azure operator (Contains, BeginsWith, EndsWith, Equal). A geo_match_statement becomes a geo-match rule on the client address, keeping the two-letter country codes.
AWS-managed rule groups are different in kind. Rather than a per-request condition, they are a curated catalog Amazon maintains. Azure’s equivalent is the OWASP Core Rule Set, so a managed_rule_group_statement maps onto the policy’s OWASP managed_rule_set. The managed-detection posture survives; the specific rule identifiers do not, which the next sections make precise.
Five statement types have a direct Azure construct; the injection- and bot-family managed groups map onto the OWASP Core Rule Set.
IP sets and rate limiting
AWS keeps an ip-set as its own resource that rules reference by ARN. Azure has no reference object for a custom rule, so the ip-set’s CIDRs are inlined into the rule’smatch_values. The block or allow decision therefore reads on the rule itself. If a single ip-set is shared by several web ACLs, each emitted policy holds its own copy of the CIDRs; there is no shared list to update once, which matters when a list changes.
Rate-based rules become RateLimitRule entries keyed on the client address, with the AWS request limit as the threshold. The one thing to know is the window. AWS lets a rate rule name any evaluation window; the Application Gateway WAF policy accepts one of two rate windows, one minute or five minutes. A rule already set to 60 or 300 seconds maps exactly. Any other window is rounded to the nearer of the two (a window of 60 seconds or less to one minute, anything larger to five minutes), and the build flags the rounding so the effective rate is understood rather than assumed.
- CIDRs inline, not referenced: an ip-set’s addresses land in the custom rule’s
match_values; a shared ip-set is copied into each policy that uses it. - Rate key is the client address: a
rate_based_statementbecomes aRateLimitRuleonRemoteAddrwith the AWS limit as its threshold. - Two windows only. 60s and 300s map exactly; any other window rounds to the one-minute or five-minute setting and the build flags it.
The ip-set’s CIDRs are written straight into the custom rule’s match_values; there is no separate reference resource to keep in sync.
Managed rule groups and the OWASP set
AWS-managed rule groups are catalogs Amazon curates and versions: the Common Rule Set, the SQL-injection and known-bad-inputs groups, the bot and IP-reputation lists. Azure’s managed protection is the OWASP Core Rule Set that Microsoft ships with the Application Gateway WAF. Amanaged_rule_group_statement therefore maps onto the policy’s OWASP managed_rule_set, and the injection, cross-site-scripting, and protocol-violation coverage comes with it. What changes is the exact rule identity: a specific AWS-managed rule id does not correspond one-to-one to an OWASP rule id, so an alert or an exclusion keyed on a particular AWS rule name needs re-expressing against the OWASP rule it now corresponds to.
Azure requires every WAF policy to name at least one managed rule set. If the source web ACL declared no managed group at all (an allowlist built only from custom IP and match rules), the emitted policy still includes the OWASP baseline, because a policy with no managed set is invalid. That baseline is a real behavior change: it can block traffic the AWS web ACL would have passed, so it is called out at the build with a note to verify it does not over-block a legitimate request pattern before the gateway takes traffic.
- Posture survives, ids do not. AWS-managed groups map onto the OWASP CRS; injection and XSS coverage comes across, but a rule keyed on a specific AWS rule id needs re-expressing.
- The OWASP baseline is always present. Azure requires a managed set, so a web ACL with none still gets OWASP CRS; verify it does not over-block.
- Managed-rule exclusions are re-authored: per-rule exclusions on AWS map to OWASP exclusions, not the same rule ids.
Scope and the default action
An AWS web ACL is either REGIONAL (in front of an ALB or an API) or CLOUDFRONT (a global edge). Azure’s two analogs split the same way: the Application Gateway WAF is the regional service, and Front Door is the global edge WAF. A regional web ACL maps cleanly to the Application Gateway policy. A CLOUDFRONT-scope web ACL is still emitted as an Application Gateway policy (the rules and match conditions are identical), but the global edge placement is Front Door’s job, so the build flags the scope and names Front Door as the alternative if a global edge is what the workload needs. The default action is the other divergence to understand. On AWS a web ACL sets a default of allow (pass-unless-a-rule-blocks) or block (a deny-by-default allowlist, where only explicitly allowed traffic passes). The Application Gateway WAF has no whole-policy default-deny switch: it evaluates rules and, if none matches, the request passes. A web ACL with a default of allow maps exactly. A web ACL built as a deny-by-default allowlist does not: the allow rules are emitted, but a request matching none of them is not denied. That gap is surfaced at the build, with the fix being an explicit lowest-priority block rule that re-creates the deny-by-default behavior.- Regional maps cleanly: a REGIONAL web ACL becomes an Application Gateway WAF policy one-for-one.
- CLOUDFRONT is a global edge: the rules still emit, but a global edge is Front Door’s role, and the build names it.
- Default allow maps exactly; default block does not: a deny-by-default allowlist needs an explicit lowest-priority block rule, which the build calls out.
Limitations
Five statement types translate to Azure constructs; the rest are named at the build rather than dropped quietly. These are the ceilings to check a web ACL against before cutover. △ Where AWS WAFv2 and the Azure WAF policy diverge- Several statement types are not translated: regex, size-constraint, and standalone SQLi / XSS match statements, label-match, and and / or / not composites are not emitted as
custom_rules; each is named at the build rather than dropped quietly. Injection and cross-site checks are re-expressed through the OWASP managed set; a regex check is re-expressed as a native AzureRegexcustom rule, which the policy does support. - Deny-by-default allowlists are not enforced whole-policy: a web ACL whose default action is block emits its allow rules, but a request matching none of them still passes; add an explicit lowest-priority block rule to restore the deny-by-default posture.
- Rate windows round to two values: a rate rule’s evaluation window is one of two settings the policy accepts, one minute or five minutes; a window that is not 60 or 300 seconds is rounded to the nearer, changing the effective rate.
- Managed-rule-group identity does not port. AWS-managed groups map onto the OWASP Core Rule Set: the protection posture is kept, but a specific AWS rule id has no one-to-one OWASP counterpart, so per-rule alerts and exclusions are re-authored.
- An OWASP baseline is added when none was declared: because Azure requires a managed set, a custom-only web ACL gains the OWASP baseline, which can block traffic the source ACL passed; verify it before taking traffic.
- Byte-match transforms are dropped: a byte-match statement’s text transformations (lowercase, URL-decode, compress-whitespace) are not applied to the emitted match condition; a match that depended on a transform is re-expressed against the raw field.
Other considerations
Beyond the rule-by-rule mapping, a few operational realities are worth stating plainly.- Attach the policy to your gateway: the WAF policy is emitted as a resource; associating it with the Application Gateway that fronts the workload is the one manual step, the same association a web ACL needs on AWS.
- The policy is referenced by name and id: a WAF policy has no ARN analog, so anything that referenced the web ACL by ARN is re-pointed at the policy’s id or name.
- Enforcement is on from the start: the policy runs in Prevention mode, so matched block rules stop requests; switch to detection in the portal first if a soak period is wanted.
- Logging rides the gateway. WAF request logging is a diagnostic setting on the Application Gateway rather than a per-web-ACL logging configuration; enable it on the gateway resource.
- Rule counts and sizes differ: the Application Gateway WAF caps custom-rule count and match-value sizes differently from AWS; a very large ip-set or rule set is validated against the Azure limits at the build.
On OCI
How it works
On AWS, anaws_wafv2_web_acl holds a tree of rules (IP-set lists, rate-based rules, byte- and geo-match conditions, and AWS-managed rule groups) that AWS WAF evaluates in front of an ALB or a CloudFront distribution. The customer’s OCI has a direct analog: the OCI Web Application Firewall, whose rules live in an oci_waf_web_app_firewall_policy.
At the build, Tensor9 reads that rule tree and emits the equivalent OCI policy as a real resource, along with one oci_waf_network_address_list for each ip-set the rules reference. Every request condition is written in the policy’s JMESPATH condition language, and AWS-managed rule groups map onto OCI’s managed protection capabilities, which are drawn from the same OWASP Core Rule Set. The policy is a native OCI object the customer attaches to a load balancer or gateway and inspects in the console; Oracle’s WAF engine evaluates every request. Where a condition has no exact OCI form, the build says so rather than pretending the match is identical. The rest of this document walks each mapping and the closing sections collect every divergence.
The rule tree compiles to a native OCI WAF policy, with each ip-set becoming its own network address list; Oracle’s WAF engine evaluates it, and nothing of Tensor9 is in the request path.
Architecture
Anaws_wafv2_web_acl maps to one oci_waf_web_app_firewall_policy. OCI structures a policy differently from AWS in two ways worth knowing up front. First, actions are named once at the top of the policy (an allow action, a block action that returns an HTTP 403, and a check action that logs and continues), and every rule refers to one of them by name, rather than each rule declaring its own inline action. Second, rules are grouped into modules by what they inspect: access-control rules (IP, byte, and geo conditions), rate-limiting rules, and protection rules (the managed OWASP capabilities).
Each ip-set the rules reference becomes its own oci_waf_network_address_list holding the CIDRs, a separate resource the access-control rules point at. The compartment comes from the stack’s own placement, threaded in as a variable so the policy and its address lists land in the compartment the customer runs in. Attaching the finished policy to the load balancer or gateway that fronts the workload is the one manual step, the same association a web ACL needs on AWS.
- One policy per web ACL: the rule tree is read in priority order and emitted into a single
oci_waf_web_app_firewall_policy. - Actions are named, then referenced: an allow, a block (HTTP 403), and a check action are declared once; every rule refers to one by name.
- Three rule modules: access-control (IP / byte / geo), rate-limiting, and protection (managed OWASP capabilities).
- Ip-sets are their own resource: each becomes an
oci_waf_network_address_listthe access-control rules reference; the compartment is threaded from the stack.
The policy declares three named actions and three rule modules; each ip-set becomes its own network address list, and the compartment comes from the stack.
How the rule statements map
The translation is statement by statement, into whichever rule module fits. Anip_set_reference_statement becomes a network address list holding the CIDRs plus an access-control rule that references it. A rate_based_statement becomes a rate-limiting rule keyed on the client connection, keeping the AWS request limit and its evaluation window unchanged. A byte_match_statement becomes an access-control rule with a JMESPATH condition over the request field, and a geo_match_statement becomes an access-control rule that tests the request’s country code.
OCI’s condition language is JMESPATH, which is expressive but shaped differently from AWS’s field_to_match plus positional-constraint model. The byte- and geo-match conditions therefore land as a best-effort JMESPATH expression: the intent is preserved, but the exact predicate should be checked against the OCI condition before the policy takes traffic. AWS-managed rule groups map onto OCI’s request protection, whose capabilities are drawn from the OWASP Core Rule Set, so the injection and cross-site coverage holds while the specific rule identity does not.
IP-set and rate rules map cleanly; byte- and geo-match conditions become best-effort JMESPATH access rules to verify; managed groups map onto the OWASP protection capabilities.
IP sets and network address lists
OCI keeps a list of source addresses as its own resource, so an ip-set maps to anoci_waf_network_address_list of type ADDRESSES holding the CIDRs. This is closer to the AWS model than Azure’s inline approach: the list is a single resource an access-control rule references, so a shared list lives in one place. The rule that consumes it is an access-control rule whose JMESPATH condition tests the request’s source address against the list.
That source-address condition is the one thing to verify. OCI expresses a source-against-list test through a specific JMESPATH function over the request’s connection address, and the emitted condition is a best-effort rendering of the AWS ip-set match. The address list itself is exact (the CIDRs are copied verbatim), but the condition wiring should be confirmed against OCI’s condition syntax before the policy enforces, which the build calls out so it is not assumed correct.
- An ip-set is its own resource: it becomes an
oci_waf_network_address_listof type ADDRESSES, referenced rather than inlined, so a shared list lives in one place. - The CIDRs are exact: the address list holds the exact prefixes from the ip-set.
- Verify the source-address condition: the access-control rule’s JMESPATH test is best-effort and is flagged at the build to confirm against OCI’s condition syntax.
Unlike Azure, an ip-set stays a standalone resource on OCI: its own network address list, referenced by the access-control rule’s source-address condition.
Rate limiting and protection capabilities
Rate-based rules become request rate-limiting rules with the AWS request limit as therequests_limit and the AWS evaluation window as the period_in_seconds, passed through unchanged. This is a cleaner mapping than Azure’s: OCI takes an arbitrary window in seconds, so a 90-second or 120-second rate rule keeps its exact window rather than rounding to a fixed set. The rate rule references the block action, so exceeding the limit returns the policy’s HTTP 403.
AWS-managed rule groups map onto OCI’s request protection. OCI ships managed protection capabilities drawn from the OWASP Core Rule Set (the same injection, cross-site-scripting, and protocol-violation families), so the managed-detection posture is preserved. What is lost is the exact rule identity: a specific AWS-managed rule id has no one-to-one OCI capability, so an alert or exclusion keyed on a particular AWS rule needs re-expressing against the OWASP capability it now corresponds to. OCI’s protection capabilities are tunable in the console after the build (collaborative thresholds, per-capability exclusions), which is where a false-positive is dialed out.
- Rate windows pass through unrounded: the AWS limit and evaluation window become
requests_limitandperiod_in_seconds; an arbitrary window keeps its exact value. - Exceeding the limit returns HTTP 403: the rate rule references the policy’s block action.
- Managed groups map onto OWASP capabilities: the protection posture is preserved; a rule keyed on a specific AWS id is re-expressed against the OWASP capability.
- Protection is tunable after the build: collaborative thresholds and exclusions are adjusted in the console to dial out a false positive.
Named actions and the default action
OCI declares its actions once at the top of the policy and lets rules reference them by name. Three are emitted: an allow action, a block action that returns an HTTP 403, and a check action that logs the match and lets the request continue. An AWS rule whose action is allow, block, or count maps onto the allow, block, or check action respectively, so the per-rule decision is kept intact. The default action is where OCI is the stronger fit of the two targets. An AWS web ACL sets a default of allow (pass-unless-blocked) or block (a deny-by-default allowlist). OCI’s access-control module has its own required default action, so both map cleanly: a default of allow becomes the allow action, and a deny-by-default web ACL becomes the block action, so a request matching no rule is denied, and the allowlist posture is preserved rather than needing a hand-authored catch-all rule. This is the one place OCI matches a web ACL more closely than the Azure WAF policy, which has no whole-policy default deny.- Three named actions: allow, block (HTTP 403), and check (log and continue); each rule references one by name.
- Per-rule decisions line up. AWS allow / block / count map onto allow / block / check.
- Deny-by-default is preserved: a block-default web ACL maps to the access-control module’s default block action, so unmatched requests are denied, with no catch-all rule needed.
Limitations
Five statement types translate to OCI constructs; the rest are named at the build rather than dropped quietly. These are the ceilings to check a web ACL against before cutover. △ Where AWS WAFv2 and the OCI WAF policy diverge- Several statement types are not translated: regex, size-constraint, and standalone SQLi / XSS match statements, label-match, and and / or / not composites are not emitted as rules; each is named at the build. Injection and cross-site checks are re-expressed through the OWASP protection capabilities, and a bespoke match is re-expressed as a JMESPATH access-control condition.
- Byte- and geo-match conditions are best-effort. OCI’s JMESPATH condition language differs from the AWS field-plus-positional model, so a byte-match or country condition lands as a best-effort expression to verify against OCI’s condition syntax, not a byte-for-byte equivalent.
- The source-address condition on an ip-set rule is verified, not assumed: the address list is exact, but the access-control rule’s source-against-list test is a best-effort JMESPATH rendering flagged at the build.
- Managed-rule-group identity does not port. AWS-managed groups map onto OCI’s OWASP protection capabilities: the protection posture holds, but a specific AWS rule id has no one-to-one OCI capability, so per-rule alerts and exclusions are re-authored.
- There is no dedicated geo operator. OCI matches a country through a JMESPATH comparison on the request’s country code rather than a dedicated geo operator, so a large country list is expressed as a condition rather than a single match block.
Other considerations
Beyond the rule-by-rule mapping, a few operational realities are worth stating plainly.- Attach the policy to an enforcement point: the WAF policy is emitted as a resource; associating it with the load balancer or gateway that fronts the workload is the one manual step, the same association a web ACL needs on AWS.
- The policy is referenced by name and id: an OCI WAF policy has no ARN analog, so anything that referenced the web ACL by ARN is re-pointed at the policy’s id or name.
- The compartment comes from the stack: the policy and its network address lists land in the compartment the stack runs in, threaded in as a variable.
- Enforcement is on from the start: the block action returns an HTTP 403 on a match; switch a rule to the check action first if a soak period is wanted.
- Verify conditions before taking traffic: the best-effort JMESPATH conditions (source address, byte-match, country) are the shape to review in the console once, since OCI’s condition language is where the divergence lives.