or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cloudformation-init.mdec2-instances.mdflow-logs.mdindex.mdlaunch-templates.mdmachine-images.mdnetwork-acl.mdsecurity-groups.mdstorage-volumes.mdsubnets-networking.mduser-data.mdvpc-endpoints.mdvpc-management.mdvpn-connectivity.md
tile.json

network-acl.mddocs/

Network Access Control Lists (ACLs)

Network ACLs in AWS CDK EC2 provide subnet-level stateless firewall rules that control traffic entering and leaving subnets, offering an additional layer of security beyond security groups.

NetworkAcl Class

The NetworkAcl class creates and manages custom network ACLs:

class NetworkAcl extends Resource implements INetworkAcl {
  constructor(scope: Construct, id: string, props: NetworkAclProps);
  
  readonly networkAclId: string;
  readonly networkAclVpcId: string;
  
  static fromNetworkAclId(scope: Construct, id: string, networkAclId: string): INetworkAcl;
  
  addEntry(id: string, options: CommonNetworkAclEntryOptions): NetworkAclEntry;
}

interface INetworkAcl extends IResource {
  readonly networkAclId: string;
  
  addEntry(id: string, options: CommonNetworkAclEntryOptions): NetworkAclEntry;
}

interface NetworkAclProps {
  readonly vpc: IVpc;
  readonly networkAclName?: string;
  readonly subnetSelection?: SubnetSelection;
}

NetworkAclEntry Class

Individual ACL rules are managed through the NetworkAclEntry class:

class NetworkAclEntry extends Resource {
  constructor(scope: Construct, id: string, props: NetworkAclEntryProps);
  
  readonly networkAcl: INetworkAcl;
}

interface NetworkAclEntryProps extends CommonNetworkAclEntryOptions {
  readonly networkAcl: INetworkAcl;
}

interface CommonNetworkAclEntryOptions {
  readonly cidr: AclCidr;
  readonly ruleNumber: number;
  readonly traffic: AclTraffic;
  readonly direction?: TrafficDirection;
  readonly ruleAction?: RuleAction;
  readonly description?: string;
}

Traffic and Rule Configuration

Enums and classes for traffic definition and rule actions:

enum TrafficDirection {
  EGRESS = 'egress',
  INGRESS = 'ingress'
}

enum RuleAction {
  ALLOW = 'allow',
  DENY = 'deny'
}

abstract class AclCidr {
  static anyIpv4(): AclCidr;
  static anyIpv6(): AclCidr;
  static ipv4(cidrBlock: string): AclCidr;
  static ipv6(cidrBlock: string): AclCidr;
}

abstract class AclTraffic {
  static allTraffic(): AclTraffic;
  static tcpPort(port: number): AclTraffic;
  static tcpPortRange(startPort: number, endPort: number): AclTraffic;
  static udpPort(port: number): AclTraffic;
  static udpPortRange(startPort: number, endPort: number): AclTraffic;
  static icmp(props: AclIcmp): AclTraffic;
  static icmpv6(props: AclIcmp): AclTraffic;
}

interface AclIcmp {
  readonly code?: number;
  readonly type?: number;
}

Subnet Association

Class for associating subnets with network ACLs:

class SubnetNetworkAclAssociation extends Resource {
  constructor(scope: Construct, id: string, props: SubnetNetworkAclAssociationProps);
  
  readonly subnetNetworkAclAssociationAssociationId: string;
}

interface SubnetNetworkAclAssociationProps {
  readonly subnet: ISubnet;
  readonly networkAcl: INetworkAcl;
}

Usage Examples

Basic Network ACL Creation

import * as ec2 from "@aws-cdk/aws-ec2";

const vpc = new ec2.Vpc(this, "MyVpc", {
  maxAzs: 2
});

// Create a custom network ACL
const webNetworkAcl = new ec2.NetworkAcl(this, "WebNetworkAcl", {
  vpc,
  networkAclName: "web-tier-acl"
});

// Allow HTTP traffic inbound
webNetworkAcl.addEntry("AllowHttpInbound", {
  cidr: ec2.AclCidr.anyIpv4(),
  ruleNumber: 100,
  traffic: ec2.AclTraffic.tcpPort(80),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Allow HTTP inbound traffic"
});

// Allow HTTPS traffic inbound
webNetworkAcl.addEntry("AllowHttpsInbound", {
  cidr: ec2.AclCidr.anyIpv4(),
  ruleNumber: 200,
  traffic: ec2.AclTraffic.tcpPort(443),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Allow HTTPS inbound traffic"
});

// Allow ephemeral ports outbound (for return traffic)
webNetworkAcl.addEntry("AllowEphemeralOutbound", {
  cidr: ec2.AclCidr.anyIpv4(),
  ruleNumber: 100,
  traffic: ec2.AclTraffic.tcpPortRange(32768, 65535),
  direction: ec2.TrafficDirection.EGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Allow ephemeral port outbound traffic"
});

Multi-Tier Network ACL Setup

const vpc = new ec2.Vpc(this, "MultiTierVpc", {
  subnetConfiguration: [
    {
      cidrMask: 24,
      name: "WebTier",
      subnetType: ec2.SubnetType.PUBLIC
    },
    {
      cidrMask: 24,
      name: "AppTier",
      subnetType: ec2.SubnetType.PRIVATE_WITH_NAT
    },
    {
      cidrMask: 28,
      name: "DatabaseTier",
      subnetType: ec2.SubnetType.PRIVATE_ISOLATED
    }
  ]
});

// Web tier ACL
const webAcl = new ec2.NetworkAcl(this, "WebAcl", {
  vpc,
  networkAclName: "web-tier-acl",
  subnetSelection: {
    subnetGroupName: "WebTier"
  }
});

// Application tier ACL
const appAcl = new ec2.NetworkAcl(this, "AppAcl", {
  vpc,
  networkAclName: "app-tier-acl",
  subnetSelection: {
    subnetGroupName: "AppTier"
  }
});

// Database tier ACL
const dbAcl = new ec2.NetworkAcl(this, "DatabaseAcl", {
  vpc,
  networkAclName: "database-tier-acl",
  subnetSelection: {
    subnetGroupName: "DatabaseTier"
  }
});

Comprehensive Web Tier ACL Rules

const webAcl = new ec2.NetworkAcl(this, "WebAcl", {
  vpc,
  networkAclName: "web-tier-acl"
});

// Inbound rules
webAcl.addEntry("AllowHttpInbound", {
  cidr: ec2.AclCidr.anyIpv4(),
  ruleNumber: 100,
  traffic: ec2.AclTraffic.tcpPort(80),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW
});

webAcl.addEntry("AllowHttpsInbound", {
  cidr: ec2.AclCidr.anyIpv4(),
  ruleNumber: 200,
  traffic: ec2.AclTraffic.tcpPort(443),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW
});

webAcl.addEntry("AllowSshInbound", {
  cidr: ec2.AclCidr.ipv4("203.0.113.0/24"), // Office IP range
  ruleNumber: 300,
  traffic: ec2.AclTraffic.tcpPort(22),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW
});

webAcl.addEntry("AllowEphemeralInbound", {
  cidr: ec2.AclCidr.anyIpv4(),
  ruleNumber: 400,
  traffic: ec2.AclTraffic.tcpPortRange(32768, 65535),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW
});

// Outbound rules
webAcl.addEntry("AllowHttpOutbound", {
  cidr: ec2.AclCidr.anyIpv4(),
  ruleNumber: 100,
  traffic: ec2.AclTraffic.tcpPort(80),
  direction: ec2.TrafficDirection.EGRESS,
  ruleAction: ec2.RuleAction.ALLOW
});

webAcl.addEntry("AllowHttpsOutbound", {
  cidr: ec2.AclCidr.anyIpv4(),
  ruleNumber: 200,
  traffic: ec2.AclTraffic.tcpPort(443),
  direction: ec2.TrafficDirection.EGRESS,
  ruleAction: ec2.RuleAction.ALLOW
});

webAcl.addEntry("AllowAppTierOutbound", {
  cidr: ec2.AclCidr.ipv4("10.0.2.0/24"), // App tier subnet
  ruleNumber: 300,
  traffic: ec2.AclTraffic.tcpPort(8080),
  direction: ec2.TrafficDirection.EGRESS,
  ruleAction: ec2.RuleAction.ALLOW
});

webAcl.addEntry("AllowEphemeralOutbound", {
  cidr: ec2.AclCidr.anyIpv4(),
  ruleNumber: 400,
  traffic: ec2.AclTraffic.tcpPortRange(32768, 65535),
  direction: ec2.TrafficDirection.EGRESS,
  ruleAction: ec2.RuleAction.ALLOW
});

Database Tier Security ACL

const dbAcl = new ec2.NetworkAcl(this, "DatabaseAcl", {
  vpc,
  networkAclName: "database-tier-acl"
});

// Allow MySQL from application tier
dbAcl.addEntry("AllowMySQLFromAppTier", {
  cidr: ec2.AclCidr.ipv4("10.0.2.0/24"), // App tier CIDR
  ruleNumber: 100,
  traffic: ec2.AclTraffic.tcpPort(3306),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Allow MySQL from application tier"
});

// Allow PostgreSQL from application tier
dbAcl.addEntry("AllowPostgreSQLFromAppTier", {
  cidr: ec2.AclCidr.ipv4("10.0.2.0/24"),
  ruleNumber: 200,
  traffic: ec2.AclTraffic.tcpPort(5432),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Allow PostgreSQL from application tier"
});

// Allow SSH from bastion host
dbAcl.addEntry("AllowSshFromBastion", {
  cidr: ec2.AclCidr.ipv4("10.0.1.0/24"), // Public subnet with bastion
  ruleNumber: 300,
  traffic: ec2.AclTraffic.tcpPort(22),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Allow SSH from bastion host"
});

// Allow ephemeral port responses
dbAcl.addEntry("AllowEphemeralResponse", {
  cidr: ec2.AclCidr.ipv4("10.0.0.0/16"), // VPC CIDR
  ruleNumber: 100,
  traffic: ec2.AclTraffic.tcpPortRange(32768, 65535),
  direction: ec2.TrafficDirection.EGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Allow ephemeral port responses within VPC"
});

// Deny all other traffic (explicit deny)
dbAcl.addEntry("DenyAllOtherInbound", {
  cidr: ec2.AclCidr.anyIpv4(),
  ruleNumber: 32000,
  traffic: ec2.AclTraffic.allTraffic(),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.DENY,
  description: "Explicitly deny all other inbound traffic"
});

dbAcl.addEntry("DenyAllOtherOutbound", {
  cidr: ec2.AclCidr.anyIpv4(),
  ruleNumber: 32000,
  traffic: ec2.AclTraffic.allTraffic(),
  direction: ec2.TrafficDirection.EGRESS,
  ruleAction: ec2.RuleAction.DENY,
  description: "Explicitly deny all other outbound traffic"
});

ACL with ICMP Rules

const monitoringAcl = new ec2.NetworkAcl(this, "MonitoringAcl", {
  vpc,
  networkAclName: "monitoring-acl"
});

// Allow ping (ICMP echo request)
monitoringAcl.addEntry("AllowPingInbound", {
  cidr: ec2.AclCidr.ipv4("10.0.0.0/16"),
  ruleNumber: 100,
  traffic: ec2.AclTraffic.icmp({
    type: 8, // Echo request
    code: 0
  }),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Allow ping from VPC"
});

// Allow ping responses
monitoringAcl.addEntry("AllowPingResponse", {
  cidr: ec2.AclCidr.anyIpv4(),
  ruleNumber: 100,
  traffic: ec2.AclTraffic.icmp({
    type: 0, // Echo reply
    code: 0
  }),
  direction: ec2.TrafficDirection.EGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Allow ping responses"
});

// Allow traceroute
monitoringAcl.addEntry("AllowTraceroute", {
  cidr: ec2.AclCidr.ipv4("10.0.0.0/16"),
  ruleNumber: 200,
  traffic: ec2.AclTraffic.icmp({
    type: 11 // Time exceeded
  }),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Allow traceroute"
});

IPv6 Network ACL Rules

const ipv6Acl = new ec2.NetworkAcl(this, "IPv6Acl", {
  vpc,
  networkAclName: "ipv6-acl"
});

// Allow HTTP over IPv6
ipv6Acl.addEntry("AllowHttpIPv6Inbound", {
  cidr: ec2.AclCidr.anyIpv6(),
  ruleNumber: 100,
  traffic: ec2.AclTraffic.tcpPort(80),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Allow HTTP over IPv6"
});

// Allow HTTPS over IPv6
ipv6Acl.addEntry("AllowHttpsIPv6Inbound", {
  cidr: ec2.AclCidr.anyIpv6(),
  ruleNumber: 200,
  traffic: ec2.AclTraffic.tcpPort(443),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Allow HTTPS over IPv6"
});

// Allow ICMPv6
ipv6Acl.addEntry("AllowICMPv6", {
  cidr: ec2.AclCidr.anyIpv6(),
  ruleNumber: 300,
  traffic: ec2.AclTraffic.icmpv6({
    type: 128, // Echo request
    code: 0
  }),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Allow ICMPv6 echo request"
});

Manual Subnet Association

const customAcl = new ec2.NetworkAcl(this, "CustomAcl", {
  vpc,
  networkAclName: "custom-acl"
  // No subnetSelection specified
});

// Manually associate with specific subnets
const publicSubnets = vpc.selectSubnets({
  subnetType: ec2.SubnetType.PUBLIC
});

publicSubnets.subnets.forEach((subnet, index) => {
  new ec2.SubnetNetworkAclAssociation(this, `AclAssociation${index}`, {
    subnet,
    networkAcl: customAcl
  });
});

Restrictive ACL with Explicit Denies

const restrictiveAcl = new ec2.NetworkAcl(this, "RestrictiveAcl", {
  vpc,
  networkAclName: "restrictive-acl"
});

// Allow only specific services
restrictiveAcl.addEntry("AllowHttpsOnly", {
  cidr: ec2.AclCidr.anyIpv4(),
  ruleNumber: 100,
  traffic: ec2.AclTraffic.tcpPort(443),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Allow HTTPS only"
});

// Explicitly deny common attack ports
const maliciousPorts = [21, 23, 135, 139, 445, 1433, 1521, 3389];
maliciousPorts.forEach((port, index) => {
  restrictiveAcl.addEntry(`DenyPort${port}`, {
    cidr: ec2.AclCidr.anyIpv4(),
    ruleNumber: 200 + index,
    traffic: ec2.AclTraffic.tcpPort(port),
    direction: ec2.TrafficDirection.INGRESS,
    ruleAction: ec2.RuleAction.DENY,
    description: `Explicitly deny port ${port}`
  });
});

// Allow ephemeral ports for return traffic
restrictiveAcl.addEntry("AllowEphemeralPorts", {
  cidr: ec2.AclCidr.anyIpv4(),
  ruleNumber: 500,
  traffic: ec2.AclTraffic.tcpPortRange(32768, 65535),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Allow ephemeral ports for return traffic"
});

Time-Based Access with Custom Rules

const temporaryAcl = new ec2.NetworkAcl(this, "TemporaryAcl", {
  vpc,
  networkAclName: "temporary-access-acl"
});

// Note: Network ACLs don't support time-based rules directly
// This would require external automation to modify rules

// Allow temporary admin access (to be removed by automation)
temporaryAcl.addEntry("TemporaryAdminAccess", {
  cidr: ec2.AclCidr.ipv4("198.51.100.0/24"), // Admin IP range
  ruleNumber: 50, // High priority
  traffic: ec2.AclTraffic.tcpPort(22),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "Temporary admin SSH access - REMOVE AFTER MAINTENANCE"
});

Importing Existing Network ACL

// Import existing Network ACL
const existingAcl = ec2.NetworkAcl.fromNetworkAclId(
  this,
  "ExistingAcl",
  "acl-12345678"
);

// Add rules to existing ACL
existingAcl.addEntry("NewRule", {
  cidr: ec2.AclCidr.ipv4("10.0.0.0/16"),
  ruleNumber: 150,
  traffic: ec2.AclTraffic.tcpPort(8080),
  direction: ec2.TrafficDirection.INGRESS,
  ruleAction: ec2.RuleAction.ALLOW,
  description: "New rule added via CDK"
});

Best Practices

  1. Rule Numbering: Use rule numbers with gaps (100, 200, 300) to allow for future insertions
  2. Stateless Nature: Remember ACLs are stateless - define both inbound and outbound rules
  3. Ephemeral Ports: Allow ephemeral port ranges for return traffic (32768-65535 for Linux, 1024-65535 for Windows)
  4. Explicit Denies: Use explicit deny rules for security-critical scenarios
  5. CIDR Specificity: Use specific CIDR blocks rather than 0.0.0.0/0 when possible
  6. Defense in Depth: Use ACLs in combination with security groups, not as a replacement
  7. Documentation: Provide clear descriptions for all ACL rules
  8. Regular Audits: Regularly review and audit ACL rules for necessity
  9. Testing: Test ACL changes in non-production environments first
  10. Monitoring: Monitor network traffic to ensure ACL rules are working as expected