frostmoln_appgw_waf_policy (Resource)
Manages a Web Application Firewall policy on a Frostmoln Application Gateway.
The firewall is composed, not singular
A gateway-scoped policy attaches to the gateway and carries the managed ruleset, along with the dials that tune it — paranoia_level, anomaly_score_threshold, managed_ruleset_version. An overlay-scoped policy attaches to one listener or one route, is compiled without the managed ruleset, and carries only your own rules — so none of those dials apply to it. Attach either with frostmoln_appgw_waf_policy_attachment.
An overlay defaults to mode = "inherit": it takes the gateway policy's mode, so it blocks when the gateway policy blocks. Read effective_mode, never mode, to decide whether a policy is refusing requests.
~> These settings are AUTHORED, not enforced. Changing mode (or any other setting) writes the policy immediately, but what the gateway inspects with is the last published version's snapshot. A policy can read block here while the gateway is still only logging, until a frostmoln_appgw_waf_policy_publication publishes and the gateway applies its configuration.
Settings are part of what a dry-run is taken against, so changing one invalidates the current dry-run and the next publish needs a fresh one. That is deliberate: the dry-run must describe the ruleset that is about to go live, settings included.
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: the policy belongs to its gateway; moving it is a different policy.
Create-immutable — name: the policy's name is its identity on the gateway.
Create-immutable — scope: scope decides what the policy is COMPILED FROM — a gateway policy carries the managed ruleset, an overlay does not; the server's update body has no scope field at all.
Observed, not enacted — tags_all: keys set outside Terraform — in the portal, by the fm CLI, or stamped by the platform — appear only here and are kept on every apply, never removed.
Example Usage
# These settings are AUTHORED, not enforced. What the gateway inspects with is
# the last PUBLISHED version — see frostmoln_appgw_waf_policy_publication.
# The GATEWAY policy: it carries the managed ruleset for everything the gateway
# serves, and owns the dials that tune it.
resource "frostmoln_appgw_waf_policy" "main" {
gateway_id = frostmoln_application_gateway.edge.id
name = "default"
scope = "gateway" # the default
# Start in detect: the ruleset runs and records what it WOULD have done, and
# blocks nothing. Move to block once a dry-run has shown you what changes.
mode = "detect"
paranoia_level = 2
# What happens if the inspection engine is unavailable. An explicit choice,
# never an accident.
fail_mode = "open"
# Between 4096 and 1048576 bytes; the default is 40960. A larger body is
# refused 413 rather than served uninspected, so this is the largest request
# your application receives. The gateway's flavor may bound it lower - the
# appliance buffers roughly 1.9x this value per in-flight request.
request_body_limit_bytes = 32768
# The methods this gateway accepts. An OVERRIDE: omit it and you run on the
# platform default, which is kept current for you.
#
# It is a deny-list of four, not a ceiling of seven. TRACE, TRACK, CONNECT and
# DEBUG are refused on every gateway and may never be listed; everything else
# is yours to name -- including the WebDAV and CalDAV verbs, which the default
# does NOT carry and which are therefore refused until you list them. That is
# the usual reason to set this attribute at all.
allowed_methods = ["GET", "HEAD", "POST", "PUT", "DELETE", "PROPFIND", "REPORT"]
# REMOVING EITHER ATTRIBUTE FROM THIS BLOCK CLEARS THE OVERRIDE and returns
# the policy to the platform default. The plan shows it as a change and the
# apply performs it. An empty list is refused rather than accepted as a way to
# say the same thing -- it is not a narrowing to nothing, and it could not be
# kept in state.
# Resource tags: your own metadata for search and filtering. Merged with
# the provider's default_tags; changing them updates in place.
tags = {
env = "prod"
}
}
# An OVERLAY policy: your own rules on one listener or one route. It is compiled
# WITHOUT the managed ruleset, so paranoia_level, anomaly_score_threshold and
# managed_ruleset_version do not belong here.
resource "frostmoln_appgw_waf_policy" "api" {
gateway_id = frostmoln_application_gateway.edge.id
name = "api-rules"
scope = "overlay"
# "inherit" is the overlay default: it takes the GATEWAY policy's mode, so it
# blocks when that policy blocks. It is not the cautious setting — read
# effective_mode, never mode, to see what is actually in force.
mode = "inherit"
}
# What THIS POLICY resolves to, with "inherit" resolved. A check written against
# `mode` would report an inheriting overlay as not blocking while its users'
# requests are being refused.
#
# Not "what is in force for traffic": the platform composes one ruleset per
# gateway, with the managed ruleset applied across the whole gateway and each
# scope's own rules added to it. An overlay adds rules to its listener or route;
# it never exempts that route from the gateway's baseline. So a detecting
# overlay does not mean traffic there is uninspected.
#
# It is null when it cannot be determined, so read it through try().
output "api_overlay_effective_mode" {
value = try(frostmoln_appgw_waf_policy.api.effective_mode, "unknown")
}
# A policy that accepts a vendor media type its API actually serves.
#
# The content-type list may ADD types as well as remove them, and the
# alternative to adding one is turning the whole content-type check off. Write
# each entry lowercase as type/subtype with no parameters: the ruleset matches
# the type alone, so "application/json; charset=utf-8" would never match.
resource "frostmoln_appgw_waf_policy" "vendor_api" {
gateway_id = frostmoln_application_gateway.edge.id
name = "vendor-api"
allowed_request_content_types = [
"application/json",
"application/vnd.acme.v2+json",
"multipart/form-data",
]
}
# WHAT WILL ACTUALLY COMPILE: the override where one is set, the platform
# default otherwise. Render this rather than keeping a copy of the default.
#
# Never feed it back into allowed_request_content_types. The platform default
# has been widened three times, and a configuration that copies today's list
# into the override pins you out of the next widening -- with a plan that looks
# like it changes nothing.
#
# Null on a listener- or route-scoped policy, which does not decide it.
output "gateway_effective_methods" {
value = frostmoln_appgw_waf_policy.main.effective_allowed_methods
}
output "vendor_api_effective_content_types" {
value = frostmoln_appgw_waf_policy.vendor_api.effective_allowed_request_content_types
}Schema
Required
gateway_id(String) The Application Gateway this policy belongs to.name(String) The name of the policy. The API has no rename, so changing this forces a new resource.
Optional
allowed_methods(List of String) The HTTP methods the managed ruleset accepts, as an override of the platform default.Omit it unless you are narrowing: absent means the platform default, which is kept current for you. Removing this attribute from your configuration clears the override and returns the policy to that default — that is the plan you will see (
["GET"]tonull) and what the apply does. An empty list is refused: it is not a narrowing to no methods, and it could not be kept.Entries are uppercase method tokens.
TRACE,TRACK,CONNECTandDEBUGare refused on every gateway and may never be listed — it is a deny-list of four, not a ceiling of seven. Everything else is yours to name, including the WebDAV and CalDAV verbs (PROPFIND,PROPPATCH,MKCOL,COPY,MOVE,LOCK,UNLOCK,REPORT,SEARCH), which the platform default does not carry and which are therefore refused until you list them.~> A method outside the list is refused with 403, independently of
anomaly_score_threshold— at 5, at 100 and at 1000. Raising the threshold to quiet false positives cannot switch this control off. A policy indetectmode still logs and serves: this is a refusal, not a bypass of the mode.Read
effective_allowed_methodsto see the list actually in force.gateway-scoped policies only.allowed_request_content_types(List of String) The requestContent-Typevalues the managed ruleset accepts, as an override of the platform default.Omit it unless you are changing the set. Removing this attribute from your configuration clears the override and returns the policy to the platform default; an empty list is refused, for the same reason as
allowed_methods.Unlike
allowed_methodsthis may add types as well as remove them: a vendor media type such asapplication/vnd.acme.v2+jsonis ordinary, and the alternative is turning the whole content-type check off.Write each entry lowercase as
type/subtypewith no parameters —application/json, neverapplication/json; charset=utf-8. The type is compared without its parameters and without regard to case, and the header is tested with a substring match. A request with noContent-Typeat all is not affected.~> A body whose type is outside the list is refused with 403, independently of
anomaly_score_threshold, exactly as forallowed_methods.The platform default has been widened three times, so read
effective_allowed_request_content_typesrather than keeping a copy of it.gateway-scoped policies only.anomaly_score_threshold(Number) The accumulated anomaly score at which a request is refused.gateway-scoped policies only, for the same reason asparanoia_level.fail_mode(String) What happens when the inspection engine is unavailable:openpasses traffic through uninspected,closedrefuses it. An explicit choice, never an accident.managed_ruleset_version(String) The managed ruleset version in force.The server accepts only the version the running build ships, and migrating a stored pin is the platform's job — so leave this unset unless you have a specific reason, and read it rather than writing it.
gateway-scoped policies only.mode(String)detectrecords what the ruleset WOULD have done and blocks nothing;blockrefuses matching requests;inherittakes the mode from the gateway policy and is valid only on anoverlay-scoped policy, where it is the default.detectis the safe place to start and the safe place to return to. Move toblockafter a dry-run has shown you what would newly be refused.~>
inheritis not the cautious setting. Under a blocking gateway policy an inheriting overlay blocks. Readeffective_modefor what is actually in force.paranoia_level(Number) How aggressively the managed ruleset matches, 1-4. Higher catches more and produces more false positives.gateway-scoped policies only: an overlay is not compiled with the managed ruleset, so this has nothing to act on and the server refuses it.request_body_limit_bytes(Number) The largest request body this gateway ACCEPTS, in bytes. Between 4096 and 1048576; the default is 40960.A larger body is refused with 413 rather than passed uninspected, so lowering this lowers the maximum request size your application receives - it is not only an inspection-cost setting. Lower it only if you know your application never receives bodies above the value you choose; at the 4096 floor the gateway refuses every request with a body over 4 KiB, ordinary JSON API calls included.
The gateway's flavor may bound this below the range above: the appliance buffers roughly 1.9x this value per in-flight request, so a value inside the range can still be refused with 409 naming the flavor's bound.
scope(String)gateway(the default) oroverlay.A
gatewaypolicy carries the managed ruleset and the dials that tune it. Anoverlaypolicy carries only your own rules and attaches to a listener or a route;mode = "inherit"is available to it, and the managed-ruleset dials are not.Changing this forces a new resource: scope decides what the policy is compiled from, and the API has no field for it on an update.
tags(Map of String) Resource tags on the WAF policy: your own key/value metadata for search and filtering, stored by the platform only. Tags are not part of the ruleset: changing them updates the policy in place with nothing to publish or apply. At most 32 keys including the tenant's default tags, which the platform merges in at create; a key is 1-64 bytes matching^[a-zA-Z0-9]([a-zA-Z0-9._:/-]*[a-zA-Z0-9])?$, a value at most 256 bytes; keys beginningfrostmoln_orfrostmoln-(any case) are reserved. Merged with the provider'sdefault_tagson every write (a key set here wins). Holds only the keys this configuration sets; the full set is intags_all.
Read-Only
active_version(Number) The version being ENFORCED. Null until something has been published.created_at(String) The creation timestamp.draft_version(Number) The version being EDITED. Rule and exclusion writes land here.effective_allowed_methods(List of String) The method list that will actually compile:allowed_methodswhere you set one, the platform default otherwise. Server-computed and read-only.Render this if you render only one — it saves you carrying a copy of a default that moves.
~> Never feed this back into
allowed_methods. Copying it turns a default that is maintained for you into a frozen override, and the change is invisible in the plan the day you do it.nullon a listener- or route-scoped policy, which does not decide it.effective_allowed_request_content_types(List of String) The media-type list that will actually compile. Same rule aseffective_allowed_methods, and the same warning with more force: this default has been widened three times, so a configuration that copies it intoallowed_request_content_typespins the tenant out of the next widening.nullon a listener- or route-scoped policy.effective_mode(String)modewithinheritresolved — what the engine actually does. Server-computed and read-only.This is the attribute to key on. An overlay whose
modeisinheritunder a blocking gateway policy haseffective_mode = "block", and a check written againstmodewould report it as not blocking while its users' requests are being refused.It is
nullwhen the mode in force cannot be determined — the policy inherits and the server did not resolve it. It is never the literal stringinherit: resolving that needs the gateway policy's mode, and this provider surfaces an absent value rather than guessing one. Usetry()orcoalesce()if your configuration must tolerate that.id(String) The unique identifier of the WAF policy.tags_all(Map of String) Every tag the platform holds on this resource: the provider'sdefault_tags, this resource's owntags(which win on a shared key), and any key set outside Terraform — in the portal, by the fm CLI, or stamped by the platform. Keys set outside Terraform appear only here and are kept on every apply, never removed.updated_at(String) The last update timestamp.