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.
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;
}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;
}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;
}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;
}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"
});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"
}
});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
});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"
});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"
});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"
});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
});
});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"
});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"
});// 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"
});