← Back to Blog

AWS Network Firewall: VPC Traffic Inspection and Threat Prevention

6 min read

AWS Network Firewall: VPC Traffic Inspection and Threat Prevention

AWS Network Firewall provides stateful inspection, intrusion prevention, and web filtering for VPC traffic. Unlike security groups and NACLs that operate at layers 3 and 4, Network Firewall performs deep packet inspection at layer 7, including TLS decryption. This guide covers practical deployment patterns from single-VPC setups to centralized Transit Gateway architectures, with real Suricata rule examples and domain filtering configurations.

Architecture Patterns

Centralized Inspection with Transit Gateway

The most common production pattern routes all inter-VPC and egress traffic through a dedicated inspection VPC. This centralizes firewall management and reduces costs compared to deploying firewall endpoints in every VPC.

{
  "Architecture": {
    "InspectionVPC": {
      "Subnets": {
        "FirewallSubnets": ["10.0.1.0/28", "10.0.1.16/28"],
        "TransitGatewaySubnets": ["10.0.2.0/28", "10.0.2.16/28"]
      },
      "RoutingFlow": [
        "Spoke VPC -> TGW Attachment -> TGW Route Table -> Inspection VPC",
        "TGW Subnet -> Route to Firewall Endpoint -> Firewall Subnet",
        "Firewall Subnet -> NAT Gateway -> Internet (egress)",
        "Return traffic follows reverse path for symmetric routing"
      ]
    },
    "TransitGateway": {
      "ApplianceModeSupport": "enable",
      "DefaultRouteTableAssociation": "disable",
      "RouteTables": {
        "SpokeRouteTable": "0.0.0.0/0 -> Inspection VPC attachment",
        "FirewallRouteTable": "10.0.0.0/8 -> Spoke VPC attachments"
      }
    }
  }
}

Enabling appliance mode on the Transit Gateway attachment is critical. Without it, return traffic may route through a different availability zone than the original request, breaking stateful inspection. This is the single most common misconfiguration in centralized firewall deployments.

Stateless and Stateful Rule Groups

Stateless Rules for High-Volume Filtering

Stateless rules evaluate each packet independently without tracking connections. Use them for simple, high-throughput decisions like blocking known-bad IP ranges or allowing established protocols before packets reach the more expensive stateful engine.

#!/bin/bash
# Create a stateless rule group that pre-filters traffic

aws network-firewall create-rule-group \
  --rule-group-name "baseline-stateless-filter" \
  --type STATELESS \
  --capacity 100 \
  --rule-group '{
    "RulesSource": {
      "StatelessRulesAndCustomActions": {
        "StatelessRules": [
          {
            "RuleDefinition": {
              "MatchAttributes": {
                "Sources": [{"AddressDefinition": "0.0.0.0/0"}],
                "Destinations": [{"AddressDefinition": "0.0.0.0/0"}],
                "SourcePorts": [{"FromPort": 0, "ToPort": 65535}],
                "DestinationPorts": [{"FromPort": 443, "ToPort": 443}],
                "Protocols": [6]
              },
              "Actions": ["aws:forward_to_sfe"]
            },
            "Priority": 1
          },
          {
            "RuleDefinition": {
              "MatchAttributes": {
                "Sources": [{"AddressDefinition": "198.51.100.0/24"}],
                "Destinations": [{"AddressDefinition": "0.0.0.0/0"}],
                "Protocols": [6, 17]
              },
              "Actions": ["aws:drop"]
            },
            "Priority": 2
          }
        ],
        "CustomActions": []
      }
    }
  }' \
  --description "Forward HTTPS to stateful engine, drop known-bad ranges"

echo "Stateless rule group created"

Priority 1 forwards all HTTPS traffic to the stateful engine for deep inspection. Priority 2 drops traffic from a known-malicious CIDR block before it reaches stateful processing. The default action for unmatched traffic should be aws:forward_to_sfe so the stateful engine can evaluate everything else.

Suricata-Compatible IPS Rules

The stateful engine supports Suricata-compatible rules, giving you access to decades of community and commercial IPS signatures. Write custom rules to detect organization-specific threats.

# suricata-rules.txt - Custom IPS rules for AWS Network Firewall

# Detect AWS credential exfiltration attempts
alert http any any -> $EXTERNAL_NET any (
  msg:"Possible AWS credential exfiltration - AccessKeyId in HTTP body";
  flow:to_server,established;
  content:"AKIA";
  content:"SecretAccessKey";
  within:500;
  classtype:policy-violation;
  sid:1000001;
  rev:1;
)

# Block unauthorized DNS-over-HTTPS to prevent DNS tunnel exfiltration
drop tls any any -> $EXTERNAL_NET 443 (
  msg:"Block DNS-over-HTTPS to non-approved resolvers";
  tls.sni;
  content:"dns.google";
  nocase;
  classtype:policy-violation;
  sid:1000002;
  rev:1;
)

# Detect potential reverse shell over HTTPS
alert tls $HOME_NET any -> $EXTERNAL_NET 443 (
  msg:"Suspicious long-lived outbound TLS session";
  flow:to_server,established;
  flow:established;
  classtype:trojan-activity;
  sid:1000003;
  rev:1;
)

# Block access to known cryptocurrency mining pools
drop tls any any -> $EXTERNAL_NET any (
  msg:"Block cryptocurrency mining pool connection";
  tls.sni;
  content:"pool.";
  nocase;
  classtype:policy-violation;
  sid:1000004;
  rev:1;
)

Upload these rules as a Suricata-compatible rule group. Network Firewall validates Suricata syntax at creation time and rejects invalid rules, so test your rules locally with Suricata's --engine-analysis flag before deploying.

Domain Filtering for Egress Control

HTTP and TLS Domain Lists

Domain filtering controls which external domains your workloads can reach. This is one of the most effective controls against data exfiltration and command-and-control traffic.

import boto3

def create_domain_filter_rules(allowed_domains, blocked_domains):
    """Create stateful domain filtering rule groups."""
    nfw = boto3.client('network-firewall')

    # Allow-list for approved SaaS and AWS endpoints
    allow_rules = "pass http $HOME_NET any -> $EXTERNAL_NET any (\n"
    allow_rules += "  msg:\"Allow approved domains\";\n"
    for domain in allowed_domains:
        allow_rules += f"  http.host; content:\"{domain}\"; endswith; nocase;\n"
    allow_rules += "  sid:2000001; rev:1;\n)\n\n"

    # TLS SNI-based allow list
    for i, domain in enumerate(allowed_domains):
        allow_rules += f"pass tls $HOME_NET any -> $EXTERNAL_NET 443 (\n"
        allow_rules += f"  msg:\"Allow TLS to {domain}\";\n"
        allow_rules += f"  tls.sni; content:\"{domain}\"; endswith; nocase;\n"
        allow_rules += f"  sid:{2000010 + i}; rev:1;\n)\n\n"

    # Default deny for unmatched domains
    allow_rules += "drop http $HOME_NET any -> $EXTERNAL_NET any (\n"
    allow_rules += "  msg:\"Block unapproved HTTP domains\";\n"
    allow_rules += "  sid:2999999; rev:1;\n)\n"
    allow_rules += "drop tls $HOME_NET any -> $EXTERNAL_NET 443 (\n"
    allow_rules += "  msg:\"Block unapproved TLS domains\";\n"
    allow_rules += "  sid:2999998; rev:1;\n)\n"

    nfw.create_rule_group(
        RuleGroupName='egress-domain-filter',
        Type='STATEFUL',
        Capacity=500,
        RuleGroup={
            'RulesSource': {
                'RulesString': allow_rules
            },
            'StatefulRuleOptions': {
                'RuleOrder': 'STRICT_ORDER'
            }
        },
        Description='Egress domain allow-list with default deny'
    )

    print(f"Domain filter created: {len(allowed_domains)} allowed domains")

# Usage
create_domain_filter_rules(
    allowed_domains=[
        '.amazonaws.com',
        '.aws.amazon.com',
        'api.github.com',
        'registry.npmjs.org',
        'pypi.org'
    ],
    blocked_domains=[]
)

Use STRICT_ORDER rule ordering so that pass rules are evaluated before the default drop. With default ordering, Suricata's pass/drop priority can produce unexpected results.

TLS Inspection

TLS inspection decrypts outbound HTTPS traffic, inspects it with stateful rules, then re-encrypts it. This is required to detect threats hidden in encrypted payloads, but it has significant operational implications. You need an ACM certificate that your workloads trust, and you must exclude domains like banking, healthcare, and certificate pinning endpoints from decryption. AWS recommends starting with TLS inspection in alert-only mode before switching to blocking.

Logging and Monitoring

Enable both alert logs (IPS signature matches) and flow logs (all connections). Send alert logs to CloudWatch Logs for real-time alarming and flow logs to S3 for long-term analysis. Create CloudWatch alarms on the DroppedPackets metric to detect when legitimate traffic is being blocked by overly aggressive rules, and on Packets to monitor throughput against your firewall capacity.

Securing Network Traffic with AccessLens

AWS Network Firewall controls what traffic flows through your VPCs, but the IAM policies governing who can modify firewall rules, update route tables, and change VPC configurations are equally critical. A misconfigured IAM policy that grants network-firewall:UpdateRuleGroup to the wrong principal can silently disable your entire inspection infrastructure.

AccessLens strengthens your network security posture by providing:

  • IAM permission analysis that identifies which principals can modify Network Firewall rules and VPC routing
  • Cross-account trust mapping that reveals whether external accounts could alter your firewall configurations
  • Change detection that alerts when IAM policies affecting network security resources are modified
  • Risk scoring that highlights overly permissive policies on network infrastructure management roles
  • Compliance reporting that verifies network security IAM controls meet regulatory requirements

Your firewall rules are only as strong as the IAM policies protecting them.

Protect your network security controls with AccessLens and ensure the IAM layer securing your AWS Network Firewall deployment is as robust as the firewall itself.

Ready to secure your AWS environment?

Get comprehensive IAM visibility across all your AWS accounts in minutes.