AWS Cloud
AWS Firewall Manager is a control plane that isn't a firewall
It doesn't inspect a single packet. It's the service that stamps WAF, Shield, security groups, and Network Firewall across every account in your organization — and keeps them there.
You open the "Firewall Manager" console expecting a firewall. No rules to write, no packets to inspect, no traffic graph — just a list of policies, a compliance dashboard, and a nagging prerequisite: turn on AWS Config in every account and every Region first. That's the whole point, and the whole trap. Firewall Manager (FMS) configures other firewalls org-wide, and both its power and its cost fall out of that one design decision.
TL;DR
- FMS is a policy manager, not a firewall. It centrally deploys and enforces AWS WAF, Shield Advanced, security groups, network ACLs, Network Firewall, Route 53 DNS Firewall, and third-party firewalls (Palo Alto Cloud NGFW, Fortigate CNF) — it never inspects traffic itself; the managed services do that.
- It scopes in two layers, and wires resources itself. A policy targets accounts / OUs, then narrows to resources by
resource_type+resource_tags; FMS creates the managed resource and associates it automatically — no manual attach, and new matching resources are picked up as they appear. - It depends on AWS Organizations and AWS Config. Organizations owns membership, Config owns compliance detection, FMS owns policy intent — miss either prerequisite and FMS silently can't protect an account.
- The management account can't be the policy admin. The Organizations management account designates a delegated administrator member account; that account is where you author policies.
- Config-everywhere is the real bill. FMS itself is about $100 per policy, per Region, per month — but it forces AWS Config on in every account and Region and creates two Config rules per policy per account, a cost that usually dwarfs the FMS fee.
- Auto-remediation is disruptive by design, so it ships off. New policies default to "detect but don't remediate." You review what would change, scope tightly, then enable it.
- It earns its keep at organization scale. Below a handful of accounts, per-account config or Terraform is cheaper and simpler. FMS wins when you need "every new account is protected automatically" as an invariant.
What it actually is — a manager, not a firewall
Start with the misconception everyone brings: Firewall Manager does not sit in your data path — no listeners, no rules engine, no packet counters. It's a control plane: you declare a protection once; FMS applies it across every account and resource in your AWS Organization, and keeps applying it as new ones appear.
The docs are blunt about what it wraps: it manages "AWS WAF, AWS Shield Advanced, Amazon VPC security groups and network ACLs, AWS Network Firewall, and Amazon Route 53 Resolver DNS Firewall." Those services do the filtering; FMS just ensures they exist everywhere, configured the way you said. The pricing page says the quiet part out loud: "Charges incurred by AWS Firewall Manager are for the underlying services."
Why not Control Tower or Security Hub?
They solve different problems. Control Tower builds and governs a landing zone — the accounts and guardrails FMS operates within. Security Hub aggregates and scores findings; it flags a missing WAF but won't create one. FMS is the only one of the three that will deploy and continuously enforce a firewall configuration across accounts. So they stack: Control Tower lays down the org, FMS enforces protections, Security Hub reports (and FMS can feed it findings).
With vs without Firewall Manager
What actually changes when you adopt it: on the left, every account is secured by hand — baselines drift apart, edits go unnoticed, each new account slips through unprotected. On the right, one policy set secures the whole organization and keeps it that way.
- Without FMS, every account is configured by hand — baselines drift apart and gaps open silently.
- A manual security-group or rule-set edit slips in and goes unnoticed until an incident or audit.
- A newly-created account has no protection until someone remembers to wire it up.
- With FMS, one
aws_fms_policyper protection type is authored once and auto-deployed to every in-scope account. - AWS Config detects any drift and FMS re-applies the intended configuration automatically — no ticket.
- New accounts are enrolled and protected the moment they join the OU.
How FMS manages resources in each account
FMS never signs into your accounts as you: it assumes a service-linked role in each member account and creates, associates, and remediates the managed resources through it. That’s how a policy authored in one delegated account becomes a live firewall in all of them.
- FMS scopes the policy to an OU (or tags) and assumes its service-linked role (AWSServiceRoleForFMS) inside each in-scope member account.
- Through that role, FMS creates and configures the managed resource — a WAF web ACL, a baseline security group, a Network Firewall endpoint (same mechanism for DNS Firewall, network ACLs, Shield).
- FMS associates each managed resource to the in-scope targets — the web ACL to ALBs/CloudFront, the security group to EC2 ENIs, the firewall endpoint to VPC subnets.
- AWS Config records the resource state; FMS compares it to policy intent and re-applies automatically on drift — no per-account action.
Where a policy applies — accounts, then resources
Scoping happens in two layers, and the second is the one people miss. First FMS selects accounts and OUs (include_map / exclude_map); then inside each, it narrows to resources matching resource_type (only ALBs, only VPCs…) and resource_tags (an include or exclude set, never both). So a policy blankets a whole OU or lands on just the handful of tagged load balancers you point it at.
include_map / exclude_map), then, inside them, resources by resource_type and resource_tags. Only matching resources get the managed protection; a new account in an in-scope OU is captured automatically.resource "aws_fms_policy" "waf_edge" {
name = "org-waf-edge-albs"
# -- Layer 1: account scope -- which accounts / OUs the policy covers
include_map { orgunit = ["ou-abcd-1example"] } # the OU(s) in scope
exclude_map { account = ["111122223333"] } # ...minus the FMS admin account
# -- Layer 2: resource scope -- which resources INSIDE those accounts
resource_type = "AWS::ElasticLoadBalancingV2::LoadBalancer" # only ALBs
resource_tags = { protect = "true" } # ...and only if tagged protect=true
exclude_resource_tags = false # tags are an INCLUDE set (true flips to exclude)
security_service_policy_data { type = "WAFV2" /* managed_service_data = ... */ }
}
resource_type / resource_type_list (required) plus resource_tags (optional) — omit the tags and the policy covers every resource of that type in scope; tags only narrow it. And you never attach anything by hand: FMS creates and associates the managed resource automatically, re-attaching on drift and catching new resources as they appear. That's the difference from AWS RAM, which shares one resource for you to wire up: FMS builds, wires, and keeps a managed one wired in every account. (FMS does use RAM under the hood to distribute reusable rule groups for Network Firewall and DNS Firewall.)What it can deploy — every policy type
Everything in FMS is a security policy (the Terraform aws_fms_policy), and each carries a single type field — WAFV2, SHIELD_ADVANCED, SECURITY_GROUPS_COMMON, NETWORK_ACL_COMMON, NETWORK_FIREWALL, DNS_FIREWALL, or THIRD_PARTY_FIREWALL — that decides which managed service deploys and what it lands on. The map below shows all seven types (numbered, colour-coded) stamping protection into member accounts A, B and C, over the AWS Config substrate that detects drift.
- WAFV2 policy → web ACL on the ALB. FMS creates a managed web ACL and associates it with the account's Application Load Balancers (the policy's target resource type) — and equally with CloudFront distributions or API Gateway stages. The web ACL isn't standalone; it attaches to the load balancer.
- Shield Advanced policy → Shield protection. Subscribes the account to Shield Advanced and protects CloudFront, ALB/ELB, and Elastic IPs against DDoS.
- Security group policy → Baseline SG. Replicates a common security group onto tagged ENIs in every in-scope account (A, B, and C).
- Network ACL policy → Network ACL. Enforces first/last inbound and outbound NACL rules on the account's subnets.
- Network Firewall policy → NFW endpoint. Deploys AWS Network Firewall endpoints into the account's VPCs (distributed or centralized).
- DNS Firewall policy → DNS Firewall. Associates Route 53 Resolver DNS Firewall rule groups with the account's VPCs.
- Third-party policy → NGFW. Deploys Palo Alto Cloud NGFW / Fortigate CNF resources across the account.
Each policy names a single protection, a Region (except global CloudFront/WAF), a scope, and a remediation setting — here is every type, what it deploys, and its deployment model.
| Policy type | What it deploys | Where it lands | Model / note |
|---|---|---|---|
| AWS WAF (v2) | A managed web ACL per account, associated to resources | CloudFront, ALB, API Gateway, AppSync, Cognito | First/last rule groups; replace or retrofit |
| Shield Advanced | Subscribes accounts, applies DDoS protection | CloudFront, ALB/ELB, EIP, Route 53, Global Accelerator | Policy fee waived for Shield Advanced customers |
| Security group | Common baseline SG, or a rule/usage audit | Tagged ENIs, VPCs, security groups | Three modes: common / content-audit / usage-audit |
| Network Firewall | Firewall endpoints (VPC or inspection VPC) | In-scope VPCs, or one inspection VPC | Distributed vs centralized; RAM-shared rule groups |
| Route 53 DNS Firewall | Rule-group associations to VPCs | In-scope VPCs | First/last priorities; needs RAM |
| Network ACL | First/last inbound & outbound NACL rules | In-scope subnets | Slower to apply (EC2 API limits) |
| Third-party | Vendor firewall resources and rule sets | Across accounts, via Marketplace | Palo Alto Cloud NGFW; Fortigate CNF |
How it guards your web apps — AWS WAF policies
A WAF policy names a set of AWS WAF (v2) rule groups; FMS creates a managed web ACL in each in-scope account (named FMManagedWebACLV2-<policy>-<timestamp>) and associates it with in-scope CloudFront distributions, ALBs, API Gateway stages, AppSync, and Cognito user pools. If a resource already carries its own web ACL, overrideCustomerWebACLAssociation decides whether FMS leaves it in place (the safe default) or replaces it. You set first and last rule groups (PREFMManaged- / POSTFMManaged-), owners adding their own between; two modes, replace any existing web ACL or retrofit (keep existing ones, create new ACLs only where none exists). For CloudFront, set the policy Region to Global.
Building blocks — and how the FMS policy wraps them
- Rule groups — AWS-managed + your custom rules.
- Web ACL — composes rule groups + a default action.
- FMS WAF policy — wraps the web ACL; adds org scope, associations, remediation.
- Managed web ACL — created & associated in every in-scope account.
In Terraform — the same aws_fms_policy wrapper, this section’s type:
resource "aws_fms_policy" "waf" {
name = "org-waf-edge-albs"
remediation_enabled = false # ship report-only, then enable
# Layer 1 — account scope: which accounts / OUs
include_map { orgunit = ["ou-abcd-1example"] }
exclude_map { account = ["111122223333"] } # never the FMS admin account
# Layer 2 — resource scope: which resources inside them
# WHY: only internet-facing ALBs need a web ACL — tagging keeps WAF (and its bill) off internal load balancers
resource_type = "AWS::ElasticLoadBalancingV2::LoadBalancer"
resource_tags = { edge = "true" }
security_service_policy_data {
type = "WAFV2"
managed_service_data = jsonencode({
type = "WAFV2"
defaultAction = { type = "ALLOW" }
preProcessRuleGroups = [{
managedRuleGroupIdentifier = { vendorName = "AWS", managedRuleGroupName = "AWSManagedRulesCommonRuleSet" }
overrideAction = { type = "NONE" }, ruleGroupType = "ManagedRuleGroup"
}]
})
}
}
include_map/exclude_map) then resource (resource_type + resource_tags). The WHY comment is the point of layer 2 — narrow to the resources that need it, so you don’t protect (and pay for) everything.How it blunts DDoS at scale — Shield Advanced policies
A Shield Advanced policy subscribes in-scope member accounts to AWS Shield Advanced and applies DDoS protection to eligible resources — per AWS's Shield documentation, CloudFront, ALB/ELB, EC2 Elastic IPs, Route 53, and Global Accelerator — auto-enrolling new in-scope accounts as they join. It's the one policy type at no extra FMS charge for Shield Advanced customers, though the Config rules it spins up are still billed separately — and it's FMS's centralized-DDoS-monitoring story: one dashboard for attacks across the org.
Building blocks
- Eligible resources — CloudFront, ALB/ELB, EIP, Route 53, Global Accelerator.
- Shield Advanced protection — + optional protection groups.
- FMS Shield policy — org scope + auto-subscribe + remediation.
- Subscription + protections — in every in-scope account.
Same aws_fms_policy wrapper in Terraform — only the type changes:
resource "aws_fms_policy" "shield" {
name = "org-shield-critical"
remediation_enabled = true
# Layer 1 — account scope
include_map { orgunit = ["ou-abcd-1example"] }
# Layer 2 — resource scope
# WHY: Shield Advanced bills per protected resource — protect only business-critical, internet-facing endpoints
resource_type_list = ["AWS::ElasticLoadBalancingV2::LoadBalancer", "AWS::CloudFront::Distribution"]
resource_tags = { tier = "critical" }
security_service_policy_data { type = "SHIELD_ADVANCED" }
}
How it governs security groups — the three audit modes
aws_fms_policy wrapper, a different type.Security-group policies come in three flavours, and mixing them up is a classic mistake:
- Common security group replicates a primary security group onto every tagged ENI in scope, so all get the same baseline rules — the "one SG to rule them all" mode.
- Content audit checks the rules inside security groups against an allowed/denied template, flagging (and optionally remediating) non-compliant rules. This is how you ban
0.0.0.0/0on port 22 org-wide. - Usage audit finds unused and redundant security groups so you can clean them up.
The docs are explicit about two guardrails: exclude the FMS admin account from scope (the default for usage-audit policies), and start with automatic remediation disabled for content and usage audit policies until you've reviewed what would change. Watch out for one more failure mode: FMS and an outside SG tool each "own" the same security group and fight in an endless remediation loop — keep their scopes mutually exclusive with tags.
Building blocks
- Primary security group — the baseline rules.
- Security-group policy — mode: COMMON / CONTENT_AUDIT / USAGE_AUDIT.
- FMS wrapper — org scope + replicate/audit + remediation.
- Baseline SG — on every tagged ENI, across accounts.
In Terraform — the same aws_fms_policy wrapper, this section’s type:
# Mode 1 — COMMON: replicate one baseline SG to matching ENIs
resource "aws_fms_policy" "sg_common" {
name = "org-sg-baseline"
# Layer 1 — account scope
include_map { account = ["*"] } # every account in the org
# Layer 2 — resource scope
# WHY: apply the baseline only to your workload ENIs — leave appliance / third-party ENIs alone
resource_type = "AWS::EC2::NetworkInterface"
resource_tags = { managed = "true" }
security_service_policy_data {
type = "SECURITY_GROUPS_COMMON"
managed_service_data = jsonencode({
type = "SECURITY_GROUPS_COMMON"
securityGroups = [{ id = "sg-0123456789abcdef0" }]
applyToAllEC2InstanceENIs = false # false = honour the resource_tags filter above
})
}
}
# Mode 2 — CONTENT_AUDIT: check rules against an allow/deny template
resource "aws_fms_policy" "sg_content" {
name = "org-sg-content-audit"
# Layer 1 — account scope
include_map { orgunit = ["ou-abcd-1example"] }
# Layer 2 — resource scope
# WHY: audit only the SGs for a given app/team, not every SG in the account
resource_type = "AWS::EC2::SecurityGroup"
resource_tags = { app = "payments" }
security_service_policy_data {
type = "SECURITY_GROUPS_CONTENT_AUDIT"
managed_service_data = jsonencode({
type = "SECURITY_GROUPS_CONTENT_AUDIT"
securityGroups = [{ id = "sg-0allowed00000000" }]
securityGroupAction = { type = "ALLOW" }
})
}
}
# Mode 3 — USAGE_AUDIT: find unused / redundant SGs
resource "aws_fms_policy" "sg_usage" {
name = "org-sg-usage-audit"
# Layer 1 — account scope
include_map { orgunit = ["ou-abcd-1example"] }
exclude_map { account = ["111122223333"] } # exclude the FMS admin account (default for usage audit)
# Layer 2 — resource scope
# WHY: only clean up prod SGs here — keep sandbox / experiment SGs out of scope
resource_type = "AWS::EC2::SecurityGroup"
resource_tags = { env = "prod" }
security_service_policy_data {
type = "SECURITY_GROUPS_USAGE_AUDIT"
managed_service_data = jsonencode({
type = "SECURITY_GROUPS_USAGE_AUDIT"
deleteUnusedSecurityGroups = true
coalesceRedundantSecurityGroups = true
})
}
}
How it drops in network firewalls — distributed vs centralized
Network Firewall is where FMS's deployment model forks, and it’s a real architectural decision, not a toggle. Both models share rule groups through AWS RAM and leave most route-table wiring to you.
- Distributed — FMS deploys firewall endpoints into each in-scope VPC (one or more per AZ), identified by VPC tags; placement is automatic (AZs with public subnets) or a custom AZ list. Good for many small, independent VPCs; more endpoints to pay for.
- Centralized — FMS creates endpoints in a single inspection VPC per account behind a Transit Gateway, inspecting east-west (VPC-to-VPC) and north-south (internet / on-prem) traffic in one place. Fewer endpoints, one choke point. The constraint, per AWS's networking blog: FMS supports only one inspection VPC per account in this model.
Import existing firewalls is a third deployment model. Instead of having FMS create endpoints, you point the policy at Network Firewalls you have already deployed — distributed across your VPCs, or centralized in an inspection VPC — and FMS adopts them, taking over their firewall policy and compliance and still auto-remediating drift. It's the brownfield path: keep the firewalls you built, gain org-wide, Config-backed enforcement. In every model FMS offers a "monitoring" mode to confirm routing before you commit — get route tables wrong here and traffic bypasses the firewall entirely.
- Distributed: each spoke VPC's own firewall endpoint inspects, then egresses locally.
- Centralized: every spoke routes to the Transit Gateway.
- The TGW sends traffic to the single inspection VPC's firewall.
- Inspected once, then out — one choke point, fewer endpoints to run.
Building blocks
- IP sets & variables — reusable named CIDR lists and port variables the rules reference.
- Rule groups — the reusable matching unit: stateless (5-tuple) + stateful (Suricata).
- Firewall policy — the object that holds the rule-group references plus the stateless and stateful default actions (shown nested).
- FMS policy — the Firewall Manager wrapper that contains that firewall policy and adds org scope, deployment model, and remediation. Its
typefield (hereNETWORK_FIREWALL) is what makes it cover Network Firewall — sibling types cover WAF, Shield, security groups, DNS Firewall, and third-party. - Firewall endpoints — the data-plane resource the FMS policy deploys, per VPC and AZ.
- IP sets & variables — reusable CIDR lists + port vars.
- Rule groups — stateless (5-tuple) + stateful (Suricata).
- Firewall policy — composes rule groups + default actions.
- FMS policy — wraps the firewall policy; adds org scope, deployment model, remediation.
- Firewall endpoints — deployed per VPC & AZ.
In Terraform — the same aws_fms_policy wrapper, this section’s type:
# Distributed — a firewall endpoint in every in-scope VPC
resource "aws_fms_policy" "nfw_dist" {
name = "org-nfw-distributed"
# Layer 1 — account scope
include_map { orgunit = ["ou-abcd-1example"] }
# Layer 2 — resource scope
# WHY: firewall endpoints cost per AZ — tag only the VPCs that actually carry sensitive / egress traffic
resource_type = "AWS::EC2::VPC"
resource_tags = { inspect = "true" }
security_service_policy_data {
type = "NETWORK_FIREWALL"
managed_service_data = jsonencode({
type = "NETWORK_FIREWALL"
networkFirewallStatelessDefaultActions = ["aws:forward_to_sfe"]
networkFirewallStatelessFragmentDefaultActions = ["aws:forward_to_sfe"]
networkFirewallStatefulRuleGroupReferences = [{ resourceARN = aws_networkfirewall_rule_group.stateful.arn }]
networkFirewallOrchestrationConfig = { singleFirewallEndpointPerVPC = false }
})
}
}
# Centralized — one inspection VPC per account behind a Transit Gateway
resource "aws_fms_policy" "nfw_central" {
name = "org-nfw-centralized"
# Layer 1 — account scope
include_map { orgunit = ["ou-abcd-1example"] }
# Layer 2 — resource scope
# WHY: only route the tagged VPCs through inspection — dev / isolated VPCs skip the choke point
resource_type = "AWS::EC2::VPC"
resource_tags = { inspect = "true" }
security_service_policy_data {
type = "NETWORK_FIREWALL"
managed_service_data = jsonencode({
type = "NETWORK_FIREWALL"
networkFirewallStatefulRuleGroupReferences = [{ resourceARN = aws_networkfirewall_rule_group.stateful.arn }]
networkFirewallOrchestrationConfig = {
singleFirewallEndpointPerVPC = true
allowedIPV4CidrList = ["100.64.0.0/16"]
}
firewallDeploymentModel = "CENTRALIZED"
})
}
}
How it filters outbound DNS — Resolver DNS Firewall policies
A DNS Firewall policy manages the associations between Route 53 Resolver DNS Firewall rule groups (which must already exist in the FMS admin account) and in-scope VPCs, and needs RAM sharing on. You set first rule groups (priority 1–99) and last rule groups (priority 9,901–10,000); account owners slot their own into the range between. FMS names its associations FMManaged_<policyId>; if an owner overrides one, the policy goes non-compliant and FMS proposes remediation — the same first/last-plus-owner-middle sandwich as WAF.
Building blocks
- Domain lists — allow / block.
- Resolver DNS Firewall rule groups — reference domain lists + actions.
- FMS DNS policy — org scope + RAM-share + associate.
- Rule-group associations — on in-scope VPCs.
In Terraform, again the aws_fms_policy wrapper — here the type is:
resource "aws_fms_policy" "dns" {
name = "org-dns-firewall"
remediation_enabled = true
# Layer 1 — account scope
include_map { orgunit = ["ou-abcd-1example"] }
# Layer 2 — resource scope
# WHY: only associate rule groups to VPCs that resolve external names — isolated VPCs don't need it
resource_type = "AWS::EC2::VPC"
resource_tags = { dns_filter = "on" }
security_service_policy_data {
type = "DNS_FIREWALL"
managed_service_data = jsonencode({
type = "DNS_FIREWALL"
preProcessRuleGroups = [{ ruleGroupId = "rslvr-frg-0123456789abcdef0", priority = 100 }]
postProcessRuleGroups = []
})
}
}
How it fences off subnets — network ACL policies
A newer type: FMS manages VPC network ACLs org-wide. You define first and last inbound and outbound rules; FMS enforces their presence and ordering on in-scope subnets, then tags the managed NACLs FMManaged=true. Account owners keep custom rules in the reserved range in between (5,000–32,000). Two caveats: NACL updates are slower than other types (EC2 NACL API rate limits), and if a subnet lands in several NACL policies at once, the oldest wins.
Building blocks
- First + last rule entries — inbound & outbound, ordered.
- Network-ACL policy — enforce presence + ordering.
- FMS wrapper — org scope + force-remediate.
- Managed NACL — on in-scope VPC subnets.
In Terraform — the same aws_fms_policy wrapper, this section’s type:
resource "aws_fms_policy" "nacl" {
name = "org-nacl-baseline"
remediation_enabled = true
# Layer 1 — account scope
include_map { orgunit = ["ou-abcd-1example"] }
# Layer 2 — resource scope
# WHY: pin the deny rules to internet-facing subnets — private / data subnets keep their own NACLs
resource_type = "AWS::EC2::Subnet"
resource_tags = { tier = "public" }
security_service_policy_data {
type = "NETWORK_ACL_COMMON"
managed_service_data = jsonencode({
type = "NETWORK_ACL_COMMON"
networkAclCommonPolicy = { networkAclEntrySet = {
forceRemediateForFirstEntries = true
firstEntries = [{ egress = false, protocol = "6", ruleAction = "deny", cidrBlock = "0.0.0.0/0",
portRange = { from = 22, to = 22 } }]
forceRemediateForLastEntries = false, lastEntries = []
}}
})
}
}
How it deploys someone else's firewall — Palo Alto and Fortigate
FMS can orchestrate two third-party firewalls as native policy types once you subscribe in AWS Marketplace and configure the vendor side:
- Palo Alto Networks Cloud NGFW — subscribe to "Palo Alto Networks Cloud NGFW Pay-As-You-Go," configure in the Cloud NGFW service, then an FMS policy centrally deploys NGFW resources and rulestacks across accounts.
- Fortigate Cloud Native Firewall (CNF) as a Service — subscribe in Marketplace, define global policy sets in Fortigate CNF, then an FMS policy deploys CNF resources across accounts.
Same pattern as the AWS-native types: FMS handles org-wide placement and lifecycle, the vendor console owns the rule content. Only the default administrator can manage third-party firewalls, and vendor support and pricing shift — check current Marketplace listings before committing.
Building blocks
- Vendor policy / rulestack — Palo Alto Cloud NGFW or Fortigate CNF.
- Third-party-firewall policy — references the vendor policy.
- FMS wrapper — org scope + deployment model + lifecycle.
- NGFW endpoints — across in-scope accounts.
Same wrapper in Terraform; the type is what makes it third-party:
# Palo Alto Cloud NGFW — subscribe in Marketplace + configure Cloud NGFW first
resource "aws_fms_policy" "tp_palo" {
name = "org-palo-alto-ngfw"
# Layer 1 — account scope
include_map { orgunit = ["ou-abcd-1example"] }
# Layer 2 — resource scope
# WHY: NGFW endpoints are billed per VPC — only route production VPCs through the vendor firewall
resource_type = "AWS::EC2::VPC"
resource_tags = { ngfw = "true" }
security_service_policy_data {
type = "THIRD_PARTY_FIREWALL"
managed_service_data = jsonencode({
type = "THIRD_PARTY_FIREWALL"
thirdPartyFirewall = "PALO_ALTO_NETWORKS_CLOUD_NGFW"
thirdPartyFirewallConfig = { thirdPartyFirewallPolicyList = ["global-1"] # a Cloud NGFW firewall-policy NAME in your tenant, not an ARN }
firewallDeploymentModel = { distributedFirewallDeploymentModel = {
distributedFirewallOrchestrationConfig = { firewallCreationConfig = { endpointLocation = { availabilityZoneConfigList = [] } } }
}}
})
}
}
# Fortigate CNF — subscribe in Marketplace + define CNF policy sets first
resource "aws_fms_policy" "tp_forti" {
name = "org-fortigate-cnf"
# Layer 1 — account scope
include_map { orgunit = ["ou-abcd-1example"] }
# Layer 2 — resource scope
# WHY: same as above — keep the vendor firewall (and its per-VPC cost) on the VPCs that need it
resource_type = "AWS::EC2::VPC"
resource_tags = { ngfw = "true" }
security_service_policy_data {
type = "THIRD_PARTY_FIREWALL"
managed_service_data = jsonencode({
type = "THIRD_PARTY_FIREWALL"
thirdPartyFirewall = "FORTIGATE_CLOUD_NATIVE_FIREWALL"
firewallDeploymentModel = { centralizedFirewallDeploymentModel = {
centralizedFirewallOrchestrationConfig = {
inspectionVpcIds = [{ resourceId = "vpc-0inspection00000", accountId = "111122223333" }]
allowedIPV4CidrList = ["100.64.0.0/16"]
}
}}
})
}
}
What you stand up first — Organizations, a delegated admin, Config everywhere
FMS has more prerequisites than most AWS services, and skipping one produces silent gaps, not errors. The docs prescribe a strict order — follow it:
- AWS Organizations with all features enabled. Not "consolidated billing only" — all features. No Organizations, no FMS; there is no standalone mode.
- Designate a Firewall Manager administrator. From the management account, onboard the org and create a default administrator account (a member account). More on this below — it's the part people get wrong.
- Enable AWS Config in every member account and every Region that holds resources you want to protect, including the FMS admin account itself. Recording frequency must be Continuous (the default).
- Enable AWS RAM resource sharing if you'll use Network Firewall or DNS Firewall policies — those share rule groups and firewalls across accounts through Resource Access Manager.
- Subscribe in AWS Marketplace if you'll deploy a third-party firewall (Palo Alto or Fortigate).
You needn't record all resource types in Config, but you must record the ones each policy needs. A WAF policy needs Config recording CloudFront distributions, ALBs (as ElasticLoadBalancingV2), API Gateway, and the WebACL types; a common security-group policy needs EC2 SecurityGroup, EC2 VPC, plus the instance and ENI types; a Network Firewall policy needs the firewall policy, rule group, VPC, internet gateway, route table, and subnet types. Get the recording set wrong and FMS can't see the resource — and, per the docs, "Firewall Manager doesn't have visibility into these permission misconfigurations." It just quietly under-protects.
Who gets to write the rules — the delegated-admin model
This is the most misunderstood piece of FMS, so be precise. There are two roles:
- The Organizations management account onboards the org to FMS and designates administrators. It is the only account that can create, update, or revoke FMS admins — but not where you live day to day.
- The Firewall Manager default administrator is a member account the management account nominates — where you actually author policies. It has full administrative scope and is the only account that can manage third-party firewalls.
Under the covers, when the management account creates an FMS administrator, FMS checks whether that account is already an Organizations delegated administrator for the service and registers it if not. So the "FMS admin account" is an Organizations delegated administrator — the standard multi-account pattern of pushing a security function out of the management account into a dedicated security account, exactly what AWS recommends: don't run FMS from the management account, delegate it.
Since a 2023 update, FMS supports multiple administrators with scoped permissions: the management account grants each admin full or restricted scope along three axes — which accounts and OUs they can touch, which Regions they can act in, and which policy types they can manage. That's how a networking team owns Network Firewall policies while an app-sec team owns WAF, without either seeing the other's blast radius.
- The management account designates the delegated admin(s) — the only account that can.
- Each admin authors policies in its own account, with scope optionally restricted by account/OU, Region, and policy type.
How it finds and fixes drift — scope, detect, remediate
Every policy runs the same loop, and it explains most of FMS's behaviour.
- Scope selects targets: accounts and OUs (all, or explicit include/exclude), resource types, and tags — tags being include OR exclude, never both. New accounts joining an in-scope OU are picked up automatically; that automatic capture is the whole reason FMS exists.
- Detect: FMS reads AWS Config's recorded state for in-scope resources and compares it to the policy. A missing web ACL, a forbidden SG rule, an owner-overridden DNS association — all surface as non-compliant on the dashboard. Detection isn't instant; minutes, and up to hours for SG/ENI changes.
- Remediate: if automatic remediation is on, FMS creates or associates the missing protection (builds and attaches the web ACL, replicates the SG, stands up the firewall endpoint). If off — the default — it only reports, showing what it would do.
Drift is a first-class concept. When a local owner changes something FMS manages — deletes the web ACL, re-associates a different DNS rule group — Config records it, FMS marks the policy non-compliant and (if enabled) re-asserts intent. It reconciles continuously, not once: powerful and occasionally infuriating, since you cannot locally "fix" what FMS owns without FMS undoing you.
- Policy intent lives in the FMS admin account.
- AWS Config records each member resource's state.
- FMS compares to intent and remediates — or only reports, if remediation is off.
- An owner's change re-enters the loop as drift and gets corrected.
When it earns its keep — and what it costs
When FMS beats the alternatives
FMS is not a free lunch: a fixed monthly tax plus a real Config bill, in exchange for one guarantee — every current and future account is protected without anyone remembering to do it. Weigh it like this:
| Situation | Better tool | Why |
|---|---|---|
| A few accounts, stable | Per-account config / Terraform | No Config-everywhere tax, no per-policy fee; you own drift yourself. |
| Building the org from scratch | Control Tower / landing zone (LZA) | Sets up accounts and guardrails; FMS then enforces network protections on top. |
| Many accounts, new ones appearing, must-not-drift | Firewall Manager | Auto-enrols new accounts, continuously reconciles, one compliance view. |
| Org-wide DDoS with Shield Advanced | Firewall Manager | Subscribes accounts and centralizes attack monitoring; policy fee waived. |
The cost model — the sticker price and the Config tax
Three layers, and only the first is the "FMS bill":
- FMS policy fee — roughly $100 per policy, per Region, per month (it varies slightly by Region; some are higher). Shield Advanced policies are included for Shield Advanced customers. This multiplies fast: 5 policy types × 4 Regions is 20 policies, about $2,000/month before anything is protected.
- The Config tax — FMS requires AWS Config on in every account and Region, and creates two AWS Config rules per policy, per account. Config bills per configuration item recorded and per rule evaluation; for a large org this is typically the dominant cost, and it is not covered by a Shield Advanced subscription.
- The underlying services — the WAF web ACLs and rules, Network Firewall endpoints plus data processing, DNS Firewall queries, third-party Marketplace charges. FMS deploys them; you pay their normal rates.
Practitioners flag the same thing: the $100/policy fee is visible and predictable; the Config bill is the one that surprises you. Model the Config cost first when scoping a big rollout.
Standing one up end to end
Here’s the concrete path from nothing to a first compliant resource — a common AWS WAF web ACL across an OU. Use the console for the one-time org setup, then CLI or Terraform for the policy itself.
Step 1 — Org, admin, and Config (one-time)
From the management account (both calls must originate here), enable AWS Organizations all-features, then designate the delegated FMS admin:
# CLI — from the Organizations MANAGEMENT account
aws organizations enable-all-features
# Designate a member account as the Firewall Manager admin
aws fms associate-admin-account \
--admin-account 111122223333 # your dedicated security account
# Terraform — run with management-account credentials
resource "aws_fms_admin_account" "this" {
account_id = "111122223333" # the delegated security account
}
Step 2 — Create and scope the policy
Now switch to the delegated FMS admin account and create the WAF policy. In Terraform the policy body is a JSON blob (ManagedServiceData) that mirrors the console's rule-group choices:
resource "aws_fms_policy" "waf_baseline" {
name = "org-waf-baseline"
resource_type = "AWS::ElasticLoadBalancingV2::LoadBalancer" # the TARGET the web ACL attaches to — not a policy type
remediation_enabled = false # start OFF — review first
exclude_resource_tags = false # tags below are an INCLUDE set
security_service_policy_data {
type = "WAFV2"
managed_service_data = jsonencode({
type = "WAFV2"
defaultAction = { type = "ALLOW" }
overrideCustomerWebACLAssociation = false
preProcessRuleGroups = [{
managedRuleGroupIdentifier = {
vendorName = "AWS"
managedRuleGroupName = "AWSManagedRulesCommonRuleSet"
}
overrideAction = { type = "NONE" }
ruleGroupType = "ManagedRuleGroup"
}]
postProcessRuleGroups = []
})
}
# Scope: an OU, minus the shared/admin account
include_map { orgunit = ["ou-abcd-1example"] }
exclude_map { account = ["111122223333"] } # exclude the FMS admin account
resource_tags = { protect = "true" } # only tagged ALBs
}
# CLI equivalent — the policy definition lives in policy.json
aws fms put-policy --cli-input-json file://policy.json
remediation_enabled = false ships the policy in report-only mode so you can inspect the compliance dashboard before FMS touches anything, and the scope excludes the FMS admin account and narrows to tagged resources — both are the documented best practice for a first rollout.Step 3 — Verify, then turn on remediation
Open the policy in the FMS console. In-scope accounts start as Pending, then resolve to compliant/non-compliant counts (allow several minutes). The non-compliant list is exactly what remediation would change; when it matches your intent, flip remediation_enabled to true and re-apply. FMS creates the FMManagedWebACLV2-… web ACL in each account and associates it with the tagged ALBs, and the dashboard flips to compliant. That's the whole loop, live.
The things that are always true
- Three stores of truth, always. Organizations = membership, Config = compliance, FMS = intent. FMS is only ever as good as the other two — a missing Config recorder is an invisible protection gap, not an error.
- The management account designates; the delegated admin authors. Policy scope lives in the member admin account, never the management account.
- Policies are per-Region. Except CloudFront/WAF (Global), you create one policy per Region — the fee and the config multiply accordingly.
- FMS-managed resources are tagged and named for it (
FMManaged,FMManagedWebACLV2-,FMManaged_). If it carries the mark, don't hand-edit it — FMS will reconcile you away. - First/last is the sandwich pattern. WAF, DNS Firewall, and NACL policies all reserve a first block and a last block for FMS, leaving the middle for local account owners.
Where it bites
| Pitfall | Fix |
|---|---|
| The Config bill you didn't model — Config-everywhere plus two rules per policy per account is usually the largest cost, and it's separate from Shield Advanced. | Price the Config cost before enabling; model configuration-item and rule-evaluation volume first. |
| Auto-remediation as a production incident — enabling it on a broad scope can rip out or replace live web ACLs and security groups. | Ship policies report-only, review the non-compliant list, then enable remediation. |
| Trying to run FMS from the management account — it can't be the scoped policy admin. | Delegate to a dedicated security account first. |
| Forgetting to exclude the admin/shared accounts — protections land where you least want surprises. | Exclude the FMS admin and shared-services accounts in every policy scope. |
| Overlapping or duelling policies — two SG policies (or FMS plus an outside SG tool) claiming the same resource loop forever. | Keep scopes mutually exclusive with tags. |
| Assuming per-Region is per-org — a policy protects one Region only. | Create one policy per Region; budget the multiplied fees and Config recording. |
| CloudFront flat-rate WAF — an FMS-managed web ACL on a flat-rate plan is permanently, unremediably non-compliant. | Don't scope flat-rate CloudFront distributions into a WAF policy. |
Conclusion
Firewall Manager isn’t a firewall — it’s the control plane that keeps every account’s firewalls consistent without anyone logging in. You author one aws_fms_policy in a delegated admin account, scope it to accounts and then to resources, and FMS creates the managed protection in each account, associates it, and re-applies it when it drifts — new accounts included, automatically.
That leverage has a price: it rides on AWS Organizations and AWS Config, and the Config-everywhere bill usually dwarfs the per-policy fee. So reach for it when you actually have the problem it solves — many accounts, new ones appearing, a baseline that must not drift; below that, per-account Terraform is simpler and cheaper. When you do adopt it, ship every policy in report-only mode, scope tightly with tags, and turn on remediation once you’ve seen the blast radius.
References
- AWS — AWS Firewall Manager (chapter overview), AWS WAF Developer Guide. docs.aws.amazon.com/waf/latest/developerguide/fms-chapter.html
- AWS — AWS Firewall Manager prerequisites. docs.aws.amazon.com/waf/latest/developerguide/fms-prereq.html
- AWS — Using AWS Firewall Manager administrators (default vs. delegated admin, admin scope). docs.aws.amazon.com/waf/latest/developerguide/fms-administrators.html
- AWS — Enabling AWS Config for using Firewall Manager (required resource types, Continuous recording, cost note). docs.aws.amazon.com/waf/latest/developerguide/enable-config.html
- AWS — Using AWS WAF policies with Firewall Manager and Setting up AWS Firewall Manager AWS WAF policies. docs.aws.amazon.com/waf/latest/developerguide/waf-policies.html
- AWS — Using security group policies (common / content audit / usage audit, best practices, caveats). docs.aws.amazon.com/waf/latest/developerguide/security-group-policies.html
- AWS — Using network ACL policies with Firewall Manager. docs.aws.amazon.com/waf/latest/developerguide/network-acl-policies.html
- AWS — Using Route 53 Resolver DNS Firewall policies (first/last priorities, RAM). docs.aws.amazon.com/waf/latest/developerguide/dns-firewall-policies.html
- AWS — Using Palo Alto Networks Cloud NGFW policies and Using Fortigate CNF policies. docs.aws.amazon.com/waf/latest/developerguide/cloud-ngfw-policies.html
- AWS — AWS Network Firewall Developer Guide (rule groups: stateless vs. stateful/Suricata, firewall policy and default actions, firewall endpoints, IP set variables). docs.aws.amazon.com/network-firewall/latest/developerguide/firewalls.html
- AWS Security Blog — Gaddamanugu & Liu, How to deploy AWS Network Firewall by using AWS Firewall Manager (2022-08-26; distributed vs centralized). aws.amazon.com/blogs/security/how-to-deploy-aws-network-firewall-by-using-aws-firewall-manager/
- AWS Networking Blog — Deployment models for AWS Network Firewall. aws.amazon.com/blogs/networking-and-content-delivery/deployment-models-for-aws-network-firewall/
- AWS — AWS Firewall Manager pricing. aws.amazon.com/firewall-manager/pricing/
- AWS — Firewall Manager and AWS Organizations (service integration). docs.aws.amazon.com/organizations/latest/userguide/services-that-can-integrate-fms.html
- AWS CLI — fms put-policy; AWS API — PutAdminAccount. docs.aws.amazon.com/cli/latest/reference/fms/put-policy.html
- HashiCorp — Terraform AWS provider, aws_fms_policy and aws_fms_admin_account. registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/fms_policy
- AWS re:Post — AWS FW manager policy cost to manage common SG (community cost reality-check). repost.aws/questions/QUYo_whKsASj2OJv0g5-7yeQ/
- Shine Solutions — AWS Firewall Manager Operational Insights (2024-10-23; Config-cost and admin gotchas, practitioner). shinesolutions.com/2024/10/23/aws-firewall-manager-operational-insights/