Skip to content

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 ​

terraform
# 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"] to null) 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, CONNECT and DEBUG are 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 in detect mode still logs and serves: this is a refusal, not a bypass of the mode.

    Read effective_allowed_methods to see the list actually in force. gateway-scoped policies only.

  • allowed_request_content_types (List of String) The request Content-Type values 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_methods this may add types as well as remove them: a vendor media type such as application/vnd.acme.v2+json is ordinary, and the alternative is turning the whole content-type check off.

    Write each entry lowercase as type/subtype with no parameters — application/json, never application/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 no Content-Type at all is not affected.

    ~> A body whose type is outside the list is refused with 403, independently of anomaly_score_threshold, exactly as for allowed_methods.

    The platform default has been widened three times, so read effective_allowed_request_content_types rather 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 as paranoia_level.

  • fail_mode (String) What happens when the inspection engine is unavailable: open passes traffic through uninspected, closed refuses 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) detect records what the ruleset WOULD have done and blocks nothing; block refuses matching requests; inherit takes the mode from the gateway policy and is valid only on an overlay-scoped policy, where it is the default.

    detect is the safe place to start and the safe place to return to. Move to block after a dry-run has shown you what would newly be refused.

    ~> inherit is not the cautious setting. Under a blocking gateway policy an inheriting overlay blocks. Read effective_mode for 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) or overlay.

    A gateway policy carries the managed ruleset and the dials that tune it. An overlay policy 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 beginning frostmoln_ or frostmoln- (any case) are reserved. Merged with the provider's default_tags on every write (a key set here wins). Holds only the keys this configuration sets; the full set is in tags_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_methods where 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.

    null on 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 as effective_allowed_methods, and the same warning with more force: this default has been widened three times, so a configuration that copies it into allowed_request_content_types pins the tenant out of the next widening.

    null on a listener- or route-scoped policy.

  • effective_mode (String) mode with inherit resolved — what the engine actually does. Server-computed and read-only.

    This is the attribute to key on. An overlay whose mode is inherit under a blocking gateway policy has effective_mode = "block", and a check written against mode would report it as not blocking while its users' requests are being refused.

    It is null when the mode in force cannot be determined — the policy inherits and the server did not resolve it. It is never the literal string inherit: resolving that needs the gateway policy's mode, and this provider surfaces an absent value rather than guessing one. Use try() or coalesce() 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's default_tags, this resource's own tags (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.