Lesson 14 / 33

Network ACLs

Use stateless, ordered network ACL rules at the subnet boundary and remember ephemeral ports.

By · 15 years in web development

Stateless, numbered, subnet-wide

A network access control list (NACL) filters traffic at the subnet boundary rather than at each interface. Each subnet is associated with exactly one NACL. Unlike security groups, NACLs support both allow and deny rules, and rules are evaluated in ascending rule-number order: the first rule that matches decides, and the rest are ignored. A final * rule denies anything not matched. Most importantly, NACLs are stateless. They do not remember connections, so the reply to an allowed inbound request is checked against the outbound rules as an independent packet. Replies go to the client's ephemeral port, a high-numbered port chosen by the client's operating system, so outbound rules must allow a range such as 1024-65535. The default NACL allows all traffic in both directions; a custom NACL you create denies everything until you add rules. Many teams leave NACLs permissive and use them only for coarse blocks, such as denying a known bad range.

First match wins

This simulation evaluates rules in number order. Notice that 198.51.100.7 is allowed: rule 100 matches before the deny at 120 is reached.

import ipaddress

# Inbound NACL: evaluated lowest rule number first; first match wins.
rules = [
    (100, "0.0.0.0/0", 443, "ALLOW"),
    (110, "203.0.113.0/24", 22, "ALLOW"),
    (120, "198.51.100.7/32", 443, "DENY"),
    (32767, "0.0.0.0/0", None, "DENY"),  # the implicit '*' rule
]

def evaluate(src, port):
    for num, cidr, rport, action in sorted(rules):
        if ipaddress.ip_address(src) in ipaddress.ip_network(cidr) and rport in (None, port):
            return num, action
    return None

print(evaluate("198.51.100.7", 443))
print(evaluate("203.0.113.9", 22))
print(evaluate("192.0.2.1", 22))

Output:

(100, 'ALLOW')
(110, 'ALLOW')
(32767, 'DENY')

Put denies before broad allows

To block one address, give the deny a lower number (for example 90) than the broad allow (100). Leave gaps between rule numbers, such as 100, 110, 120, so you can insert rules later.

Quick check: A custom NACL allows inbound TCP 443 from anywhere and outbound TCP 443 to anywhere. HTTPS clients time out. What is the most likely cause?

  • Responses to client ephemeral ports are blocked outbound
  • Security groups override NACLs
  • NACLs cannot filter port 443
  • The internet gateway is stateless
Answer

Responses to client ephemeral ports are blocked outbound — NACLs are stateless. The server's replies go to the client's ephemeral port, not 443, so an outbound rule for 1024-65535 is needed.