Skip to content

Instantly share code, notes, and snippets.

@milesjordan
Last active April 4, 2024 02:37
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save milesjordan/d86942718f8d4dc20f9f331913e7367a to your computer and use it in GitHub Desktop.
Save milesjordan/d86942718f8d4dc20f9f331913e7367a to your computer and use it in GitHub Desktop.
Cloudformation template for a VPC with ipv6, with public and private subnets, calculating the subnet ipv6 CIDR blocks on the fly.
---
AWSTemplateFormatVersion: '2010-09-09'
Description: Public VPC and subnets
Resources:
#
# Public VPC
#
PublicVpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 172.31.0.0/16
InstanceTenancy: default
Tags:
- Key: Name
Value: Public VPC
IPv6CidrBlock:
Type: AWS::EC2::VPCCidrBlock
Properties:
VpcId: !Ref PublicVpc
AmazonProvidedIpv6CidrBlock: true
#
# Internet gateways (ipv4, and egress for ipv6)
#
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: Public VPC Internet Access
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref PublicVpc
InternetGatewayId: !Ref InternetGateway
EgressOnlyInternetGateway:
Type: AWS::EC2::EgressOnlyInternetGateway
Properties:
VpcId: !Ref PublicVpc
#
# Routing - public subnets
#
PublicSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref PublicVpc
Tags:
- Key: Name
Value: Route Table for Public Subnets in Public VPC
PublicSubnetDefaultRoute:
DependsOn: InternetGatewayAttachment
Type: AWS::EC2::Route
Properties:
DestinationCidrBlock: 0.0.0.0/0
RouteTableId: !Ref PublicSubnetRouteTable
GatewayId: !Ref InternetGateway
PublicSubnetDefaultIpv6Route:
Type: AWS::EC2::Route
Properties:
DestinationIpv6CidrBlock: ::/0
RouteTableId: !Ref PublicSubnetRouteTable
EgressOnlyInternetGatewayId: !Ref EgressOnlyInternetGateway
#
# Routing - private subnets
#
PrivateSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref PublicVpc
Tags:
- Key: Name
Value: Route Table for Private Subnets in Public VPC
PrivateSubnetDefaultIpv6Route:
Type: AWS::EC2::Route
Properties:
DestinationIpv6CidrBlock: ::/0
RouteTableId: !Ref PrivateSubnetRouteTable
EgressOnlyInternetGatewayId: !Ref EgressOnlyInternetGateway
#
# Access control
#
PrivateSubnetsNetworkAcl:
Type: AWS::EC2::NetworkAcl
Properties:
VpcId: !Ref PublicVpc
Tags:
- Key: Name
Value: Private Subnet on Public VPC ACL
PrivateSubnetsNetworkAclInboundEntry:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PrivateSubnetsNetworkAcl
RuleNumber: 1
PortRange:
From: 22 # SSH
To: 22
Protocol: 6 # TCP
RuleAction: allow
Egress: false
CidrBlock: 172.31.0.0/16
PrivateSubnetsNetworkAclIpv6OutboundEntry:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PrivateSubnetsNetworkAcl
RuleNumber: 1
Protocol: -1
RuleAction: allow
Egress: true
Ipv6CidrBlock: ::/0
PrivateSubnetsNetworkAclOutboundEntry:
Type: AWS::EC2::NetworkAclEntry
Properties:
NetworkAclId: !Ref PrivateSubnetsNetworkAcl
RuleNumber: 2
Protocol: -1
RuleAction: allow
Egress: true
CidrBlock: 0.0.0.0/0
#
# Public subnet A
#
PublicSubnetA:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 172.31.0.0/20
Ipv6CidrBlock:
Fn::Sub:
- "${VpcPart}${SubnetPart}"
- SubnetPart: '01::/64'
VpcPart: !Select [ 0, !Split [ '00::/56', !Select [ 0, !GetAtt PublicVpc.Ipv6CidrBlocks ]]]
AvailabilityZone: !Select [ 0, !GetAZs '' ]
VpcId: !Ref PublicVpc
MapPublicIpOnLaunch: 'true'
Tags:
- Key: Name
Value: Public Subnet A
PublicSubnetARouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicSubnetRouteTable
SubnetId: !Ref PublicSubnetA
#
# Private subnet A
#
PrivateSubnetA:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 172.31.48.0/20
Ipv6CidrBlock:
Fn::Sub:
- "${VpcPart}${SubnetPart}"
- SubnetPart: 'a1::/64'
VpcPart: !Select [ 0, !Split [ '00::/56', !Select [ 0, !GetAtt PublicVpc.Ipv6CidrBlocks ]]]
AssignIpv6AddressOnCreation: true
AvailabilityZone: !Select [ 0, !GetAZs '' ]
VpcId: !Ref PublicVpc
Tags:
- Key: Name
Value: Private Subnet A in Public VPC
PrivateSubnetAAclAssociation:
Type: AWS::EC2::SubnetNetworkAclAssociation
Properties:
NetworkAclId: !Ref PrivateSubnetsNetworkAcl
SubnetId: !Ref PrivateSubnetA
PrivateSubnetARouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateSubnetRouteTable
SubnetId: !Ref PrivateSubnetA
@milesjordan
Copy link
Author

This doesn't currently work due to an issue in cloudformation that prevents Egress Only Internet Gateways from routing properly. Let's hope it's fixed soon!

@jghaines
Copy link

The routing issue looks to fixed now.

@jghaines
Copy link

I suggest adding DependsOn: IPv6CidrBlock to the AWS::EC2::Subnet resources.

Otherwise you can get a race condition with the Subnet failing on the GetAtt VPC.Ipv6CidrBlocks operation.

@drobern
Copy link

drobern commented Aug 28, 2018

Thanks for this post!

@thyme-87
Copy link

Thanks for your work! I based my own template on it and found !Cidr ( https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-cidr.html) to make the subnet calculation more simple for a scenario with multiple subnets.

@sciarrilli
Copy link

thanks for the ipv6 subnet piece.

@hsulipe
Copy link

hsulipe commented Jan 9, 2020

Thanks for this post it really helped me !!!

@groverlalit
Copy link

I suggest adding DependsOn: IPv6CidrBlock to the AWS::EC2::Subnet resources.

Otherwise you can get a race condition with the Subnet failing on the GetAtt VPC.Ipv6CidrBlocks operation.

+1

@groverlalit
Copy link

I suggest using intrinsic function Fn::Cidr to break the /48 VPC Cidr block into /64 rather than hard coding the SubnetPart.

@tobiasmcnulty
Copy link

I suggest using intrinsic function Fn::Cidr to break the /48 VPC Cidr block into /64 rather than hard coding the SubnetPart.

Indeed, it looks like the docs for this function have an example of doing exactly this (search for "Creating an IPv6 enabled VPC"): https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-cidr.html

@milesjordan
Copy link
Author

Thanks for the suggestions! Indeed, using the newer Fn::Cidr function and adding DependsOn would be a nice addition. I'm glad this helped some of you out!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment