frostmoln_appgw_waf_policy_publication (Resource)
Runs a dry-run and publishes an Application Gateway WAF policy's draft, making it the version the gateway enforces.
Ordering without depends_on
Feed every rule's and exclusion's revision into rule_revisions — see the example below. Because the revisions are server-assigned and bump on every write, any rule change changes an input here, so Terraform's own graph orders this last and re-runs it exactly when something changed.
The dry-run is a gate, not advice
Applying this resource starts a dry-run, waits for it, and refuses to publish if more than max_newly_blocked requests in the sample would newly be refused — printing the request shapes that would break. The server independently refuses any publish that has no completed dry-run matching the draft exactly, settings included.
Publishing is not applying
A published version reaches the appliance on the gateway's next configuration apply. This resource publishes; it does not dispatch.
~> Declare at most one publication per policy. A second one targeting the same policy_id is not refused, but both then compute the same version and id, and each apply races the other's dry-run. One policy, one publication.
Platform opt-outs reach this resource from outside Terraform
frostmoln_appgw_waf_rule will not manage a platform-owned rule — the tenant may only opt out of one, and only where the platform allows it, using the CLI or the portal. Both of those write the draft, which changes its content hash, which this resource sees as hasUnpublishedChanges. So the next apply publishes that opt-out: a platform protection is switched off by a change that appears in no HCL.
This provider cannot own that decision, so it makes it visible instead. The plan warns and names each rule, and platform_opt_outs records what the published ruleset disables. Read them: an opt-out is normally a false-positive workaround for an emergency virtual patch, and it is the single most consequential edit a tenant can make to a WAF.
~> Destroying this resource does not unpublish anything. A published version stays published — there is no unpublish, by design, because history is never rewritten. Destroy removes it from state and warns; use a rollback to go back to an earlier version.
Authoritative scope — who owns what on this resource, declared in internal/scopedecl and machine-checked against the schema.
Enacted and reconciled — every configurable attribute not listed below: the platform applies it, and a refresh reads the truth back.
Create-immutable — gateway_id, policy_id: a publication belongs to its policy on its gateway.
Example Usage
# Publishing, ordered after every rule WITHOUT depends_on.
#
# Every rule and exclusion exports a server-bumped `revision`. Feeding them in
# here means any rule change changes an input to this resource, so Terraform's
# own graph runs the publish last and re-runs it exactly when something changed.
# Nothing to remember, and a rule you did not list is visibly not listed.
resource "frostmoln_appgw_waf_policy_publication" "main" {
gateway_id = frostmoln_application_gateway.edge.id
policy_id = frostmoln_appgw_waf_policy.main.id
rule_revisions = merge(
{
for r in [
frostmoln_appgw_waf_rule.block_scanners,
frostmoln_appgw_waf_rule.admin_office_only,
frostmoln_appgw_waf_rule.quiet_942100,
] : r.rule_key => r.revision
},
{
for e in [
frostmoln_appgw_waf_exclusion.search_query,
] : e.rule_key => e.revision
},
)
# Refuse to publish anything that would newly break traffic. Raise it
# deliberately when a rule is SUPPOSED to start blocking.
max_newly_blocked = 0
}
# What would newly be refused, recorded for review.
output "waf_newly_blocked" {
value = frostmoln_appgw_waf_policy_publication.main.dry_run_newly_blocked
}
# What the ENFORCED version is actually doing, with "inherit" resolved.
#
# This is the honest answer to "is the firewall blocking right now". A policy's
# own settings are AUTHORED and take effect only once published, and a version's
# mode can be the word "inherit", which answers nothing on its own -- so a check
# written against either can report a gateway as not blocking while requests are
# being refused.
#
# It is null when the mode in force cannot be determined, so read it through
# try().
output "waf_enforced_mode" {
value = try(frostmoln_appgw_waf_policy_publication.main.effective_mode, "unknown")
}Schema
Required
gateway_id(String) The Application Gateway the policy belongs to.policy_id(String) The WAF policy to publish.rule_revisions(Map of Number) A map of rule key to that rule'srevision. This is the ordering input: its only job is to change when a rule changes.
Optional
max_newly_blocked(Number) Refuse to publish if the dry-run reports more than this many requests in its sample would NEWLY be blocked.Defaults to
0— publish nothing that breaks anything in the sample. That is the right setting for a change meant to be invisible, and it is the default because a safety control that is off unless you opt in is one most configurations will not have.Raise it deliberately when a rule is supposed to start blocking. There is no way to disable the check entirely: set a number large enough to cover what you intend, and the dry-run's figures are recorded either way.
timeouts(Block, Optional) (see below for nested schema)
Read-Only
content_hash(String) The canonical hash of the rules, exclusions and settings that were published.dry_run_id(String) The dry-run that gated this publication.dry_run_newly_allowed(Number) How many requests in the dry-run's sample would newly be allowed.dry_run_newly_blocked(Number) How many requests in the dry-run's sample would NEWLY be refused by the published ruleset. The number that says whether this change breaks someone.dry_run_requests_sampled(Number) How many requests the dry-run replayed. A small sample means a weak signal.effective_mode(String) What the enforced version is doing: its mode withinheritresolved against the gateway policy. Server-computed and read-only.This is the honest answer to "is the firewall blocking right now". A policy's own settings are AUTHORED and take effect only once published, and a version's own mode can be the word
inherit, which answers nothing on its own — so a check written against either can report a gateway as not blocking while requests are being refused.It is
nullwhen the mode in force cannot be determined. Usetry()orcoalesce()if your configuration must tolerate that.id(String) The identifier of this publication: the policy id and the version it published.platform_opt_outs(List of String) The keys of platform-owned rules that the published ruleset turns off for this gateway.These are tenant-authored security decisions that live outside Terraform:
frostmoln_appgw_waf_rulewill not manage a platform rule, so an opt-out is made with the CLI or the portal. Both write the draft, so the next apply of this resource publishes it — and without this attribute the plan would say only "version will be known after apply" while disabling a protection that appears in nobody's configuration. Anything listed here is a protection you are choosing not to run.published_at(String) When the version was published.version(Number) The version number this publication produced.
Nested Schema for timeouts
Optional:
create(String) How long the provider waits for the create operation to converge before giving up (e.g. "45m", "2h").delete(String) How long the provider waits for the delete to complete before giving up (e.g. "30m").update(String) How long the provider waits for an update (resize, in-place change) to converge before giving up (e.g. "30m").