WAF protection
The Static files scope can attach an existing AWS WAF WebACL to its CloudFront distribution, adding an extra security layer in front of your assets. The feature is opt-in and adds no IAM overhead unless enabled.
When to use it
Attach a WebACL when you need:
- Managed AWS rules (common rule set, IP reputation lists, bot control)
- Rate-based rules per IP
- Custom request filtering or geo restrictions
- WAF logging to S3, CloudWatch, or Firehose
If you only need basic HTTPS and edge caching, leave the WAF off.
Limits
The integration is intentionally narrow to keep the configuration simple. The following are out of scope today:
| Limit | Detail |
|---|---|
| WAF version | Only AWS WAFv2 is supported. WAF Classic (aws_waf_*) is not. |
| Account | Same-account only. The WebACL must live in the same AWS account that the agent authenticates against. |
| Scope | The WebACL must have scope=CLOUDFRONT. AWS confines those WebACLs to us-east-1, so the WebACL must live in us-east-1. |
| Lifecycle | The scope attaches an existing WebACL by name. It does not create, update, or delete WebACL resources. |
If you need any of the above, manage the WebACL outside the scope and reference it by name once it exists.
Configuration
Add a security block to the attributes of your scope-configurations provider:
attributes = {
cloud_provider = "aws"
provider = { ... }
network = { ... }
distribution = { aws_distribution = "cloudfront" }
security = {
aws_security = "waf"
aws_web_acl_name = "my-web-acl"
}
}
| Field | Values | Description |
|---|---|---|
security.aws_security | "none" (default) or "waf" | Choose whether to attach a WebACL. "none" skips the WAF integration entirely. |
security.aws_web_acl_name | string | Name of an existing WAFv2 WebACL with scope=CLOUDFRONT in us-east-1. Required when aws_security="waf". |
The same fields are also available in the nullplatform UI under the Security tab of the scope configuration.
IAM permissions
When aws_security="waf", the agent needs two additional permissions on top of the ones documented in AWS setup:
{
"Sid": "WAFv2WebACLLookup",
"Effect": "Allow",
"Action": [
"wafv2:ListWebACLs",
"wafv2:GetWebACL"
],
"Resource": "arn:aws:wafv2:us-east-1:YOUR_ACCOUNT_ID:global/webacl/*/*"
}
ListWebACLs is called by the scope's setup script to validate the WebACL name before running OpenTofu. GetWebACL is called by the OpenTofu data source that resolves the name to an ARN.
When aws_security="none", neither action is invoked, so the policy above is only needed in clusters that actually use the WAF feature.
Operational behavior
Attaching or detaching a WebACL is always an in-place update of the CloudFront distribution. The distribution ID stays stable across the change, no DNS or edge propagation reset is required, and there is no downtime.
- Attach: setting
aws_security="waf"with a valid name on an existing scope triggers a distribution update that setsWebACLIdto the WebACL ARN. The scope is redeployed normally. - Detach: setting
aws_security="none"(or clearing the name) on the next deployment setsWebACLIdback to empty. The distribution keeps the same ID. - Swap: changing
aws_web_acl_nameto a different existing WebACL re-pointsWebACLIdto the new ARN, also in-place.
The configuration change in the provider does not by itself update CloudFront. You need to trigger a deployment of the scope (initial, blue-green, or any redeploy) for the agent to apply the new value.
Validation and errors
Before running OpenTofu, the scope's setup script validates that the WebACL exists by calling wafv2:ListWebACLs with scope=CLOUDFRONT in us-east-1. If the name is misspelled, lives in a different account, or has the wrong scope, the deployment fails fast with a message that points to the most common causes:
- Name misspelled
- WebACL exists but with
scope=REGIONALinstead ofCLOUDFRONT - WebACL lives in a different AWS account
- Agent role lacks
wafv2:ListWebACLs
Fix the underlying issue and redeploy. No partial state is left behind because the failure happens before the OpenTofu apply.