I Tried Out Container Association in AWS Network Firewall

I Tried Out Container Association in AWS Network Firewall

A feature that lets Network Firewall rules reference the IP addresses of containers running on EKS or ECS. I selected EKS Pods by namespace and label, configured per-application domain restriction rules, and verified how they work.
2026.10.30

This page has been translated by machine translation. View original

Hello, this is Hayashi.

In June 2026, Container Association was added to AWS Network Firewall.
It is a feature that lets you select EKS Pods by namespace or label and reference the list of their IP addresses from Network Firewall rules.

https://aws.amazon.com/jp/about-aws/whats-new/2026/06/aws-network-firewall-container-attributes-referencing/

To see what kind of feature it is, I configured rules that restrict the destination domains for each EKS application and tried them out.

What is Container Association?

Network Firewall stateful rules specify the source and destination by IP address, such as "allow traffic from this IP address to this destination."

In EKS, applications run in units called Pods. Each Pod is assigned one VPC IP address, but every time a Pod is recreated, it gets a different IP address.
Because of this, it was not possible to define a rule like "allow only traffic from this application" using IP addresses.

Container Association is a resource where Network Firewall automatically maintains the list of IP addresses of Pods that match a condition, so that it can be referenced as the source or destination of a rule. You create it by specifying an EKS or ECS cluster.

There is no additional charge; it is included in the Network Firewall usage fee.

https://docs.aws.amazon.com/network-firewall/latest/developerguide/container-associations.html

Selecting Pods

Pod conditions use namespaces and labels.
As an example, take an environment where, in the apps namespace, a generative AI app with the label app: ai-agent and a web app with the label app: web-app are running.

# Pod for the generative AI app
apiVersion: v1
kind: Pod
metadata:
  name: ai-agent
  namespace: apps      # namespace used for the condition
  labels:
    app: ai-agent      # label used for the condition (key: app, value: ai-agent)
---
# Pod for the web app
apiVersion: v1
kind: Pod
metadata:
  name: web-app
  namespace: apps      # same namespace
  labels:
    app: web-app       # only the label value differs

If you set the Container Association condition to "namespace is apps and label is app=ai-agent," only the generative AI app's Pod is included in the list.
If you set the condition to only "namespace is apps," both Pods are included in the list.

If you want to change the allowed destinations for each application, create a Container Association for each application.

How to use it in rules

In Network Firewall, you define rules that allow or deny traffic by grouping them into rule groups.
In addition to specifying IP addresses directly as the source or destination of a rule, you can use a feature called IP set references to reference a collection of IP addresses, such as a prefix list, by variable name.
Container Association can now be specified as a target of this IP set reference.

If you register the list of ai-agent Pods under the variable name AI_AGENT, you can define a rule that specifies @AI_AGENT as the source.

This is a rule that allows TLS traffic from the ai-agent Pods to api.openai.com.

pass tls @AI_AGENT any -> any 443 (tls.sni; content:"api.openai.com"; startswith; endswith; nocase; flow:to_server; sid:1;)

The destination domain is determined by SNI. SNI is the destination hostname that the client sends when it starts a TLS connection.

Updating the list of Pod IP addresses

Network Firewall automatically updates the list of Pod IP addresses.
When a Pod is recreated and its IP address changes, the new IP address is added to the list and the old IP address is removed from it. The same applies when the number of Pods increases.

The only things the user configures are the cluster and the condition specified when creating the Container Association.
There is no additional configuration needed on the cluster side, and you don't need to modify the rules every time a Pod's IP address changes.

Traffic subject to control

Only traffic that passes through Network Firewall can be controlled.
You configure route tables so that traffic from Pods to external destinations goes through the Firewall endpoint. Traffic that stays within the VPC, such as Pod-to-Pod traffic, is out of scope.

Also, the source of traffic reaching the Firewall must be the Pod's IP address. The official documentation's network requirements contain the following note.

Disable SNAT (Amazon EKS) – For Amazon EKS pods, disable source NAT (SNAT) so that the firewall sees the pod's original IP address. If SNAT is enabled, traffic arrives at the firewall with the node IP, not the pod IP, and rules referencing the container association do not match.

The VPC CNI, which assigns IP addresses to Pods in EKS, by default translates the source of traffic going outside the VPC to the node's IP address. You disable this translation so that traffic reaches the Firewall with the Pod's IP address unchanged.

Verification setup

What to verify

In the apps namespace, I place ai-agent, a generative AI app, and web-app, a web app.
I define rules so that ai-agent can connect only to api.openai.com and web-app can connect only to api.github.com.

From each Pod, I connect to both APIs with curl and confirm that it can connect only to the allowed API.

Pod Label api.openai.com api.github.com
ai-agent app=ai-agent Can connect Cannot connect
web-app app=web-app Cannot connect Can connect

Environment

構成図

There is one EKS node in a private subnet, and both Pods run on this node.
Traffic from the Pods to external destinations passes through the Firewall endpoint, the NAT gateway, and the internet gateway, in that order.

Item Details
Kubernetes version 1.37
Node Managed node group, t3.medium, AL2023, 1 node
VPC CNI add-on v1.22.4-eksbuild.8
Container image public.ecr.aws/amazonlinux/amazonlinux:2023
Region Tokyo (ap-northeast-1)

Steps

1. Create Container Associations

I create two Container Associations for the individual applications and one for the whole apps namespace.

Name Attribute filter Pods included in the list
ai-agent-pods namespace=apps, app=ai-agent ai-agent
web-app-pods namespace=apps, app=web-app web-app
app-pods namespace=apps ai-agent, web-app

At the time of writing (2026/10/30), CloudFormation's AWS::NetworkFirewall::ContainerAssociation was available in N. Virginia and Oregon, but not in Tokyo.
Therefore, create them from the console or the AWS CLI. This time I created them with the AWS CLI.

aws network-firewall create-container-association \
  --container-association-name ai-agent-pods \
  --type EKS \
  --container-monitoring-configurations '[{
    "ClusterArn": "arn:aws:eks:ap-northeast-1:123456789012:cluster/sample-cluster",
    "AttributeFilters": [
      {"Key": "namespace", "Value": "apps"},
      {"Key": "app", "Value": "ai-agent"}
    ]
  }]'

After creation, Status becomes ACTIVE within a few seconds.
ResolvedCidrCount in describe-container-association is the number of IP addresses included in the list.
About 20 seconds after creating the Pods, it was 1 for ai-agent-pods and web-app-pods, and 2 for app-pods.

{
    "ContainerAssociationName": "ai-agent-pods",
    "Type": "EKS",
    "Status": "ACTIVE",
    "ResolvedCidrCount": 1
}

2. Reference the Container Associations in the rule group

If you register the ARN of a Container Association under a variable name in the rule group's IPSetReferences, you can specify it as the rule source with @variable-name. I used the variable names AI_AGENT, WEB_APP, and APPS.

This is what I set in the rule group.

{
  "RulesSource": {
    "RulesString": "pass tls @AI_AGENT any -> any 443 (msg:\"ai-agent: allow api.openai.com\"; tls.sni; content:\"api.openai.com\"; startswith; endswith; nocase; flow:to_server; sid:1; rev:1;)\npass tls @WEB_APP any -> any 443 (msg:\"web-app: allow api.github.com\"; tls.sni; content:\"api.github.com\"; startswith; endswith; nocase; flow:to_server; sid:2; rev:1;)\ndrop tcp @APPS any -> any 443 (msg:\"apps: drop TLS to other domains\"; flow:established,to_server; sid:3; rev:1;)\ndrop tcp @APPS any -> any !443 (msg:\"apps: drop non-443 TCP\"; sid:4; rev:1;)\ndrop udp @APPS any -> any any (msg:\"apps: drop UDP\"; sid:5; rev:1;)\npass ip any any -> any any (msg:\"others: pass established\"; flow:established,to_server; sid:6; rev:1;)"
  },
  "ReferenceSets": {
    "IPSetReferences": {
      "AI_AGENT": { "ReferenceArn": "arn:aws:network-firewall:ap-northeast-1:123456789012:container-association/ai-agent-pods" },
      "WEB_APP": { "ReferenceArn": "arn:aws:network-firewall:ap-northeast-1:123456789012:container-association/web-app-pods" },
      "APPS": { "ReferenceArn": "arn:aws:network-firewall:ap-northeast-1:123456789012:container-association/app-pods" }
    }
  },
  "StatefulRuleOptions": { "RuleOrder": "STRICT_ORDER" }
}

These are the rules written in RulesString.

sid Source Traffic Action
1 @AI_AGENT (ai-agent) TLS with SNI api.openai.com Allow
2 @WEB_APP (web-app) TLS with SNI api.github.com Allow
3 @APPS (all Pods in the apps namespace) TCP to port 443 Deny
4 @APPS TCP to ports other than 443 Deny
5 @APPS UDP Deny
6 Any (nodes, Pods outside apps) Any Allow

The evaluation order is STRICT_ORDER, which evaluates rules from top to bottom and applies the first rule that matches.
Traffic from ai-agent to api.openai.com is allowed by sid 1, while traffic to api.github.com matches neither sid 1 nor 2 and is denied by sid 3.

Get the update token with describe-rule-group, and apply rulegroup.json with update-rule-group.
In the console, you can make the same setting under "IP set references" on the rule group edit screen.

aws network-firewall describe-rule-group \
  --rule-group-arn arn:aws:network-firewall:ap-northeast-1:123456789012:stateful-rulegroup/pod-egress \
  --query UpdateToken --output text
aws network-firewall update-rule-group \
  --rule-group-arn arn:aws:network-firewall:ap-northeast-1:123456789012:stateful-rulegroup/pod-egress \
  --update-token 0a1b2c3d-4e5f-6789-abcd-ef0123456789 \
  --rule-group file://rulegroup.json

3. Create the firewall policy

Associate the rule group from step 2 with the firewall policy.

I set the default actions for stateful rules to aws:drop_established and aws:alert_established. Traffic that matches no rule is denied and logged after the TCP connection is established. Since SNI is sent after the TCP connection is established, denying before establishment would prevent sid 1 and 2 from making their determination.

{
  "StatelessDefaultActions": ["aws:forward_to_sfe"],
  "StatelessFragmentDefaultActions": ["aws:forward_to_sfe"],
  "StatefulEngineOptions": { "RuleOrder": "STRICT_ORDER" },
  "StatefulDefaultActions": ["aws:drop_established", "aws:alert_established"],
  "StatefulRuleGroupReferences": [
    { "ResourceArn": "arn:aws:network-firewall:ap-northeast-1:123456789012:stateful-rulegroup/pod-egress", "Priority": 10 }
  ]
}

4. Enable external SNAT in the VPC CNI

I set the VPC CNI's AWS_VPC_K8S_CNI_EXTERNALSNAT to true. The node does not translate the source and sends traffic to the NAT gateway with the Pod's IP address unchanged.

The setting is specified in the add-on's configuration values.

aws eks update-addon --cluster-name sample-cluster --addon-name vpc-cni \
  --resolve-conflicts OVERWRITE \
  --configuration-values '{"env":{"AWS_VPC_K8S_CNI_EXTERNALSNAT":"true"}}'

https://docs.aws.amazon.com/eks/latest/userguide/external-snat.html

5. Create the Pods

Using the manifest from "Selecting Pods," I created ai-agent and web-app in the apps namespace.

kubectl apply -f pods.yaml

Results

From each Pod, I connected to https://api.openai.com/v1/models and https://api.github.com/ with curl. The timeout is 10 seconds.

kubectl exec -n apps ai-agent -- curl -sS -m 10 -o /dev/null -w '%{http_code}' https://api.openai.com/v1/models
Pod api.openai.com api.github.com
ai-agent 401 Timeout
web-app Timeout 200

ai-agent and web-app could each connect only to the API they were allowed to.
The 401 from api.openai.com is the response when connecting without an API key; the TLS connection was established and reached the API server.

Denied traffic is recorded in the alert log.
From the log of the denied connection from web-app to api.openai.com, I extracted the source, destination, matched rule, and SNI fields.

{
  "event": {
    "src_ip": "10.40.10.95",
    "dest_ip": "162.159.140.245",
    "dest_port": 443,
    "alert": {
      "signature_id": 3,
      "signature": "apps: drop TLS to other domains",
      "action": "blocked",
      "metadata": {
        "container_association": ["APPS"]
      }
    },
    "tls": {
      "sni": "api.openai.com",
      "version": "TLS 1.3"
    },
    "verdict": { "action": "drop" }
  }
}

The variable name of the matched rule is recorded in alert.metadata.container_association.
You can tell which Container Association's traffic was denied without looking up the Pod from the source IP address.
tls.sni is the destination domain.

When external SNAT is returned to the default

I set AWS_VPC_K8S_CNI_EXTERNALSNAT back to false and tried the same connections.

Pod api.openai.com api.github.com
ai-agent 401 200
web-app 401 200

Both Pods could connect to both APIs. Nothing was recorded in the alert log.

Using the Firewall flow logs, I checked the source IP addresses of traffic that reached the Firewall.

External SNAT Source IP address
Enabled (true) 10.40.10.142 (ai-agent), 10.40.10.95 (web-app), 10.40.10.195 (node)
Default (false) 10.40.10.195 (node)

By default, the source is the node's IP address, which matches no Container Association.
All traffic was allowed by sid 6.

Notes

It took a few minutes after updating the rule group for the change to be reflected in the Firewall. If you connect before it is reflected, the traffic is evaluated with the pre-update rules. You can check whether it has been reflected by seeing if ConfigurationSyncStateSummary in describe-firewall is IN_SYNC.

There are also some constraints on referencing Container Associations.

  • Within the same rule group, references to Container Associations and IP set references such as prefix lists cannot be mixed
  • A single rule group can reference up to 30 Container Associations

Summary

I tried out Network Firewall Container Association.

  • The list of IP addresses of Pods selected by namespace and label can be referenced from rules with @variable-name
  • The list is updated automatically as Pods are added or removed, so no rule modifications are needed
  • The variable name of the matched rule is recorded in the alert log
  • You need to enable external SNAT in the VPC CNI so that traffic reaches the Firewall with the Pod's IP address unchanged

I hope this article is helpful to someone. Thank you for reading to the end!

References

https://docs.aws.amazon.com/network-firewall/latest/developerguide/container-associations.html

https://docs.aws.amazon.com/eks/latest/userguide/external-snat.html

https://aws.amazon.com/blogs/security/secure-amazon-container-workloads-using-container-attribute-based-rules-in-aws-network-firewall/

https://aws.amazon.com/jp/about-aws/whats-new/2026/06/aws-network-firewall-container-attributes-referencing/

Share this article

AWSのお困り事はクラスメソッドへ