Hi,
I'm just starting out to play with the SDK, please forgive if I a missing something. First off, I'd like to probably point out how great this product is and I see a bright future for it. It's in the early stages and I understand things will be missing. This is not a complaint, just something I noticed while attempting to create an IPv6 enabled app.
It seems like the ability to map an amazonProvidedIpv6CidrBlock
is missing within the @aws-cdk/aws-ec2 VpcNetwork
construct as well as within the SubnetConfiguration
interface.
Something along the lines of the below would be helpful.
import * as ec2 from "@aws-cdk/aws-ec2";
new ec2.VpcNetwork(this, name, {
// other attributes
amazonProvidedIpv6CidrBlock: true,
// other attributes
}
Other values left out on purpose just to keep this concise.
From the subnetConfiguration[] it would be helpful to have.
import * as ec2 from "@aws-cdk/aws-ec2";
new ec2.VpcNetwork(this, name, {
// other attributes
subnetConfiguration: [
{
name: "pub",
cidrMask: 22,
ipv6Cidr: true,
mapIPv6OnLaunch: true,
subnetType: ec2.SubnetType.Public
}
]
// other attributes
}
The ipv6Cidr
could be a boolean
since all subnet cidrs must be /64
and then the obvious addition to map IPv6 address to instances within those subnet .
To get the amazonProvidedIpv6CidrBlock
within the created VpcNetwork I had to do something like this, and in testing, it seems to work. Though going through the documentation, it's advised to use native constructs, which I'd agree completely.
this.vpc = new ec2.VpcNetwork(this, `${name}Vpc`, args);
new ec2.cloudformation.VPCCidrBlockResource(
this,
`CidrRes`,
{
vpcId: this.vpc.vpcId,
amazonProvidedIpv6CidrBlock: true
}
);
We'll definitely take this under consideration. Thanks for the suggestion!
Well, it is really complicated to have real IPv6 support for now with CDK. Even if you add the CfnVPCCidrBlock
, you cannot configure Subnets properly. And even if you create an aspect to alter the Subnets somehow. you still need to write the logic for assigning cidrs to subnets. And even if you do that, it might not be compatible with how it will be implemented in CDK in the future so when it will be implemented all your code is gonna regenerate the ipv6 vpc stuff...
Are there any plans to implement this?
Is there any plans to support this? As far as I can tell this means it's more or less impossible to have IPv6 support using the AWS CDK today.
After a bit of fiddling, I only needed two "hacks" to get IPv6 working via CDK. If those alone could be address, that would be very helpful. What I did:
CfnVPCCidrBlock
with amazon_provided_ipv6_cidr_block
setnode.children
of the VPC, to find the CfnInternetGateway
CfnInternetGateway
node.children
of the subnet to find CfnSubnet
, and assign a Ipv6CidrBlock
to itThat alone enables IPv6 on the VPC. For the ALB, as that was my goal, I also had to add a connection for IPv6 on the ports the ALB is listening on.
Assigning Ipv6CidrBlock
per subnet I managed to do with the Fn::Cidr
.
I did not add IPv6 to the private subnets, as I have no need for that. But doing this is now trivial; it just requires an extra EgressOnly gateway and a similar solution as to the public subnets.
In general, it feels that adding IPv6 support is not that far away; I wish I was a bit more into TypeScripting to give it a go, but alas, I am not. But hopefully this gives a bit of inspiration to someone who is :)
Subnet
doesn't allow setting Ipv6CidrBlock
at any point. self._vpc = Vpc(self, "Vpc",
max_azs=2,
nat_gateway_provider=nat_provider,
)
# IPv6 is currently not supported by CDK.
# This is done manually now, based on:
# https://gist.github.com/milesjordan/d86942718f8d4dc20f9f331913e7367a
ipv6_block = CfnVPCCidrBlock(self, "Ipv6",
vpc_id=self._vpc.vpc_id,
amazon_provided_ipv6_cidr_block=True,
)
# We need to sniff out the InternetGateway the VPC is using, as we
# need to assign this for IPv6 routing too.
for child in self._vpc.node.children:
if isinstance(child, CfnInternetGateway):
internet_gateway = child
break
else:
raise Exception("Couldn't find the InternetGateway of the VPC")
for index, subnet in enumerate(self._vpc.public_subnets):
subnet.add_route("DefaultIpv6Route",
router_id=internet_gateway.ref,
router_type=RouterType.GATEWAY,
destination_ipv6_cidr_block="::/0",
)
# This is of course not the best way to do this, but it seems CDK
# currently allows no other way to set the IPv6 CIDR on subnets.
assert isinstance(subnet.node.children[0], CfnSubnet)
# As IPv6 are allocated on provisioning, we need to use "Fn::Cidr"
# to get a subnet out of it.
subnet.node.children[0].ipv6_cidr_block = Fn.select(
index,
Fn.cidr(
Fn.select(
0,
self._vpc.vpc_ipv6_cidr_blocks
),
len(self._vpc.public_subnets),
"64"
)
)
# Make sure the dependencies are correct, otherwise we might be
# creating a subnet before IPv6 is added.
subnet.node.add_dependency(ipv6_block)
And for the ALB:
http_listener = ApplicationListener(self, "Listener-Http",
load_balancer=alb,
port=80,
protocol=ApplicationProtocol.HTTP,
)
http_listener.connections.allow_default_port_from(
other=Peer.any_ipv6(),
description="Allow from anyone on port 80",
)
I followed @TrueBrain's example and got it to work in Typescript for myself. Here's my code:
// Create a VPC
const vpc = new ec2.Vpc(this, "Vpc", {
cidr: "10.0.0.0/16",
subnetConfiguration: [
{
cidrMask: 24,
name: "public",
subnetType: ec2.SubnetType.PUBLIC,
},
],
});
// Add an Ipv6 workaround
new Ipv6Workaround(this, "Ipv6Workaround", {
vpc: vpc,
});
// ipv6-workaround.ts
import * as cdk from "@aws-cdk/core";
import * as ec2 from "@aws-cdk/aws-ec2";
/**
* Gets a value or throws an exception.
*
* @param value A value, possibly undefined
* @param err The error to throw if `value` is undefined.
*/
const valueOrDie = <T, C extends T = T>(
value: T | undefined,
err: Error,
): C => {
if (value === undefined) throw err;
return value as C;
};
export interface Ipv6WorkaroundProps {
vpc: ec2.Vpc;
}
/**
* Adds IPv6 support to a VPC by modifying the CfnSubnets.
*
* For example:
* ```
* const vpc = new Vpc(this, "MyVpc", { ... });
* new Ipv6Workaround(this, "Ipv6Workaround", {
* vpc: vpc,
* });
* ```
*/
export class Ipv6Workaround extends cdk.Construct {
constructor(scope: cdk.Construct, id: string, props: Ipv6WorkaroundProps) {
super(scope, id);
const { vpc } = props;
// Associate an IPv6 block with the VPC.
// Note: You're may get an error like, "The network 'your vpc id' has met
// its maximum number of allowed CIDRs" if you cause this
// `AWS::EC2::VPCCidrBlock` ever to be recreated.
const ipv6Cidr = new ec2.CfnVPCCidrBlock(this, "Ipv6Cidr", {
vpcId: vpc.vpcId,
amazonProvidedIpv6CidrBlock: true,
});
// Get the vpc's internet gateway so we can create default routes for the
// public subnets.
const internetGateway = valueOrDie<cdk.IConstruct, ec2.CfnInternetGateway>(
vpc.node.children.find(c => c instanceof ec2.CfnInternetGateway),
new Error("Couldn't find an internet gateway"),
);
// Modify each public subnet so that it has both a public route and an ipv6
// CIDR.
vpc.publicSubnets.forEach((subnet, idx) => {
// Add a default ipv6 route to the subnet's route table.
const unboxedSubnet = subnet as ec2.Subnet;
unboxedSubnet.addRoute("IPv6Default", {
routerId: internetGateway.ref,
routerType: ec2.RouterType.GATEWAY,
destinationIpv6CidrBlock: "::/0",
});
// Find a CfnSubnet (raw cloudformation resources) child to the public
// subnet nodes.
const cfnSubnet = valueOrDie<cdk.IConstruct, ec2.CfnSubnet>(
subnet.node.children.find(c => c instanceof ec2.CfnSubnet),
new Error("Couldn't find a CfnSubnet"),
);
// Use the intrinsic Fn::Cidr CloudFormation function on the VPC's
// first IPv6 block to determine ipv6 /64 cidrs for each subnet as
// a function of the public subnet's index.
const vpcCidrBlock = cdk.Fn.select(0, vpc.vpcIpv6CidrBlocks);
const ipv6Cidrs = cdk.Fn.cidr(
vpcCidrBlock,
vpc.publicSubnets.length,
"64",
);
cfnSubnet.ipv6CidrBlock = cdk.Fn.select(idx, ipv6Cidrs);
// The subnet depends on the ipv6 cidr being allocated.
cfnSubnet.addDependsOn(ipv6Cidr);
});
}
}
Following the hacks of @TrueBrain and @misterjoshua I got the vpc and subnets to have ipv6 setup. However, ec2s launched into these subnets still don't get ipv6 addresses. It looks like https://github.com/aws/aws-cdk/blob/master/packages/%40aws-cdk/aws-ec2/lib/vpc.ts#L1234-L1237 doesn't have any attribute that could be used to fix this. It looks like this could be fixed with a custom resource, as per https://stackoverflow.com/questions/42047071/auto-assign-ipv6-address-via-aws-and-cloudformation . I'm wondering if I'm missing something? What's the value of a subnet with ipv6 cidrs if ec2 launched into that subnet don't get ipv6 addresses? Auto-assignment should be included as part of the proposal?
In the hope of helping others with the same conundrum, here's some python cdk to implement the auto-assign-ipv6-address to all public subnets
from itertools import starmap
lambda_role = iam.Role(self, "ipv6-auto-lambda-role",
assumed_by = iam.ServicePrincipal("lambda.amazonaws.com"),
inline_policies = {
"ipv6-fix-logs" : iam.PolicyDocument(
statements = [iam.PolicyStatement(
effect = iam.Effect.ALLOW,
actions = [ "logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
resources = ["arn:aws:logs:*:*:*"]
)]
),
"ipv6-fix-modify" : iam.PolicyDocument(
statements = [iam.PolicyStatement(
effect = iam.Effect.ALLOW,
actions = [ "ec2:ModifySubnetAttribute"],
resources = ["*"]
)]
)
}
)
lambda_function = aws_lambda.Function(self, "ipv6-auto-true-lambda",
handler = "index.handler",
code = aws_lambda.Code.from_inline(
f"""
import cfnresponse
import boto3
import json
def handler(event, context):
print(json.dumps(event))
if event['RequestType'] is 'Delete':
cfnresponse.send(event, context, cfnresponse.SUCCESS)
else:
try:
responseValue = event['ResourceProperties']['SubnetId']
ec2 = boto3.client('ec2', region_name='{core.Stack.of(self).region}')
ec2.modify_subnet_attribute(AssignIpv6AddressOnCreation={{
'Value': True
}},
SubnetId=responseValue)
responseData = {{}}
responseData['SubnetId'] = responseValue
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")
except:
cfnresponse.send(event, context, cfnresponse.FAILED, responseData = "An error occured")
"""
),
runtime = aws_lambda.Runtime.PYTHON_3_7,
role = lambda_role
)
def auto_assign_ipv6(index, subnet):
subnet_id = subnet.subnet_id
return cfn.CustomResource( self, f"ipv6-auto-truer-{index}",
provider = cfn.CustomResourceProvider.from_lambda(lambda_function),
properties = {"SubnetId" : subnet_id}
)
list(starmap(auto_assign_ipv6, enumerate(vpc.public_subnets)))
Should probably be tackled as part of https://github.com/aws/aws-cdk/issues/5927
Out of pure curiosity, I've got both the associated IPv6 addresses and enabling auto-assign IPv6 on each public subnet working with typescript thanks to the solutions above.
This is 100% "Your scientists were so preoccupied with whether or not they could, they didn鈥檛 stop to think if they should." But none the less, an awesome exercise working with escape hatches, tokens, new typescript operators, and custom resources...
export class UtpoiaStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Create our 2-Tier Network Stack with IPv4
const vpc = new ec2.Vpc(this, 'DualStackVPC', {
cidr: '10.0.0.0/16',
maxAzs: 2,
enableDnsHostnames: true,
enableDnsSupport: true,
// subnetConfiguration: [], // interface ec2.SubnetConfiguration is limited (Great for Ipv4 though)
})
// Associate an IPv6 CIDR block to our VPC
const ipv6Block = new ec2.CfnVPCCidrBlock(this, 'IPv6Block', {
amazonProvidedIpv6CidrBlock: true,
vpcId: vpc.vpcId
})
// Using escape hatches to assign an Ipv6 address to every subnet as well as a custom resource that enables auto-assigned Ipv6 addresses
vpc.publicSubnets.forEach((subnet: ec2.ISubnet, idx: number) => {
const unboxedSubnet = subnet as ec2.Subnet
unboxedSubnet.addRoute("IPv6Default", {
routerId: (vpc.node.children.find(c => c instanceof ec2.CfnInternetGateway) as ec2.CfnInternetGateway)?.ref,
routerType: ec2.RouterType.GATEWAY,
destinationIpv6CidrBlock: "::/0"
})
const vpcCidrBlock = cdk.Fn.select(0, vpc.vpcIpv6CidrBlocks);
const ipv6Cidrs = cdk.Fn.cidr(
vpcCidrBlock,
vpc.publicSubnets.length,
"64"
)
let cfnSubnet = subnet.node.children.find(c => c instanceof ec2.CfnSubnet) as ec2.CfnSubnet ?? new Error("Why am I still doing this?");
cfnSubnet.ipv6CidrBlock = cdk.Fn.select(idx, ipv6Cidrs)
cfnSubnet.addDependsOn(ipv6Block)
// Define a custom resource to auto-assign IPv6 addresses to all of our subnets
const autoAssignCR = new cr.AwsCustomResource(this, `AutoAssignIPv6CustomResource${Math.random()*100}`, {
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE }),
onCreate: {
physicalResourceId: cr.PhysicalResourceId.of(`AutoAssignIPv6Create${Math.random()*100}`),
service: 'EC2',
action: 'modifySubnetAttribute',
parameters: {
AssignIpv6AddressOnCreation: { Value: true },
SubnetId: subnet.subnetId
}
}
})
})
Again, this is more of a fun hack than a solution. If you need IPv6 addressing, I would use L1 constructs until this issue and #5927 are resolved.
Just a small modification in @misterjoshua approach to include private subnets for IPv6 -
```import * as cdk from "@aws-cdk/core";
import * as ec2 from "@aws-cdk/aws-ec2";
/**
value
is undefined.export interface Ipv6WorkaroundProps {
vpc: ec2.Vpc;
}
/**
const { vpc } = props;
// Associate an IPv6 block with the VPC.
// Note: You're may get an error like, "The network 'your vpc id' has met
// its maximum number of allowed CIDRs" if you cause this
// `AWS::EC2::VPCCidrBlock` ever to be recreated.
const ipv6Cidr = new ec2.CfnVPCCidrBlock(this, "Ipv6Cidr", {
vpcId: vpc.vpcId,
amazonProvidedIpv6CidrBlock: true,
});
// Get the vpc's internet gateway so we can create default routes for the
// public subnets.
const internetGateway = valueOrDie<cdk.IConstruct, ec2.CfnInternetGateway>(
vpc.node.children.find(c => c instanceof ec2.CfnInternetGateway),
new Error("Couldn't find an internet gateway"),
);
// Modify each public subnet so that it has both a public route and an ipv6
// CIDR.
vpc.publicSubnets.forEach((subnet, idx) => {
// Add a default ipv6 route to the subnet's route table.
const unboxedSubnet = subnet as ec2.Subnet;
unboxedSubnet.addRoute("IPv6Default", {
routerId: internetGateway.ref,
routerType: ec2.RouterType.GATEWAY,
destinationIpv6CidrBlock: "::/0",
});
// Find a CfnSubnet (raw cloudformation resources) child to the public
// subnet nodes.
const cfnSubnet = valueOrDie<cdk.IConstruct, ec2.CfnSubnet>(
subnet.node.children.find(c => c instanceof ec2.CfnSubnet),
new Error("Couldn't find a CfnSubnet"),
);
// Use the intrinsic Fn::Cidr CloudFormation function on the VPC's
// first IPv6 block to determine ipv6 /64 cidrs for each subnet as
// a function of the public subnet's index.
const vpcCidrBlock = cdk.Fn.select(0, vpc.vpcIpv6CidrBlocks);
const ipv6Cidrs = cdk.Fn.cidr(
vpcCidrBlock,
256,
"64",
);
cfnSubnet.ipv6CidrBlock = cdk.Fn.select(idx, ipv6Cidrs);
// The subnet depends on the ipv6 cidr being allocated.
cfnSubnet.addDependsOn(ipv6Cidr);
});
// Modify each private subnet so that it has both a public route and an ipv6
// CIDR.
vpc.privateSubnets.forEach((subnet, idx) => {
// Add a default ipv6 route to the subnet's route table.
const unboxedSubnet = subnet as ec2.Subnet;
unboxedSubnet.addRoute("IPv6Default", {
routerId: internetGateway.ref,
routerType: ec2.RouterType.GATEWAY,
destinationIpv6CidrBlock: "::/0",
});
// Find a CfnSubnet (raw cloudformation resources) child to the public
// subnet nodes.
const cfnSubnet = valueOrDie<cdk.IConstruct, ec2.CfnSubnet>(
subnet.node.children.find(c => c instanceof ec2.CfnSubnet),
new Error("Couldn't find a CfnSubnet"),
);
// Use the intrinsic Fn::Cidr CloudFormation function on the VPC's
// first IPv6 block to determine ipv6 /64 cidrs for each subnet as
// a function of the private subnet's index.
const vpcCidrBlock = cdk.Fn.select(0, vpc.vpcIpv6CidrBlocks);
const ipv6Cidrs = cdk.Fn.cidr(
vpcCidrBlock,
256,
"64",
);
cfnSubnet.ipv6CidrBlock = cdk.Fn.select(idx+3, ipv6Cidrs);
// The subnet depends on the ipv6 cidr being allocated.
cfnSubnet.addDependsOn(ipv6Cidr);
});
}
}```
The same problem here. I will be great to have simple options in ec2.SubnetConfiguration to indicate if a subnet should have IPv6 prefix, and in ec2.Vpc to assign IPv6 prefix.
Our team is also interested in IPv6 support, specifically for use with an ApplicationLoadBalancer.
To provide scope, Apple requires all new apps to support IPv6-only networks.
Starting June 1, 2016 all apps submitted to the App Store must support IPv6-only networking
This is pretty unexpected that IPv6 is just not supported in the CDKs core networking construct.
Most helpful comment
I followed @TrueBrain's example and got it to work in Typescript for myself. Here's my code: