Skip to content

Commit 93c95fc

Browse files
authored
feat(VpcV2): add BYOIP IPv6 to VPCv2 (#32927)
### Issue # (if applicable) Closes #<issue number here>. ### Reason for this change This PR adds new fields under VPCv2 construct to add BYOIP using AWS pool id and CIDR range of the IPv6 address. ### Description of changes ### Describe any new or updated permissions being added No update to permissions. ### Description of how you validated changes Added unit test and integration test. To test these changes in future, users will need to modify the pool-id with the one hosted in their account and run integration test. For internal testing, instructions added to [team-internal docs](cdklabs/team-internal#269) with pool onboarding details. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent c86296e commit 93c95fc

17 files changed

+1101
-26
lines changed

packages/@aws-cdk/aws-ec2-alpha/README.md

+32
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,38 @@ new VpcV2(this, 'Vpc', {
105105

106106
Since `VpcV2` does not create subnets automatically, users have full control over IP addresses allocation across subnets.
107107

108+
### Bring your own IPv6 addresses (BYOIP)
109+
110+
If you have your own IP address that you would like to use with EC2, you can set up an IPv6 pool via the AWS CLI, and use that pool ID in your application.
111+
112+
Once you have certified your IP address block with an ROA and have obtained an X-509 certificate, you can run the following command to provision your CIDR block in your AWS account:
113+
114+
```shell
115+
aws ec2 provision-byoip-cidr --region <region> --cidr <your CIDR block> --cidr-authorization-context Message="1|aws|<account>|<your CIDR block>|<expiration date>|SHA256".Signature="<signature>"
116+
```
117+
118+
When your BYOIP CIDR is provisioned, you can run the following command to retrieve your IPv6 pool ID, which will be used in your VPC declaration:
119+
120+
```shell
121+
aws ec2 describe-byoip-cidr --region <region>
122+
```
123+
124+
For more help on setting up your IPv6 address, please review the [EC2 Documentation](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-byoip.html).
125+
126+
Once you have provisioned your address block, you can use the IPv6 in your VPC as follows:
127+
128+
```ts
129+
const myVpc = new VpcV2(this, 'Vpc', {
130+
primaryAddressBlock: IpAddresses.ipv4('10.1.0.0/16'),
131+
secondaryAddressBlocks: [IpAddresses.ipv6ByoipPool({
132+
cidrBlockName: 'MyByoipCidrBlock',
133+
ipv6PoolId: 'ipv6pool-ec2-someHashValue',
134+
ipv6CidrBlock: '2001:db8::/32'
135+
})],
136+
enableDnsHostnames: true,
137+
enableDnsSupport: true,
138+
});
139+
```
108140

109141
## Routing
110142

packages/@aws-cdk/aws-ec2-alpha/lib/subnet-v2.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@ function storeSubnetToVpcByType(vpc: IVpcV2, subnet: SubnetV2, type: SubnetType)
456456
function validateSupportIpv6(vpc: IVpcV2) {
457457
if (vpc.secondaryCidrBlock) {
458458
if (vpc.secondaryCidrBlock.some((secondaryAddress) => secondaryAddress.amazonProvidedIpv6CidrBlock === true ||
459-
secondaryAddress.ipv6IpamPoolId != undefined)) {
459+
secondaryAddress.ipv6IpamPoolId !== undefined || secondaryAddress.ipv6Pool !== undefined)) {
460460
return true;
461461
} else {
462462
throw new Error('To use IPv6, the VPC must enable IPv6 support.');

packages/@aws-cdk/aws-ec2-alpha/lib/util.ts

+54-21
Original file line numberDiff line numberDiff line change
@@ -323,13 +323,54 @@ export class CidrBlockIpv6 {
323323
}
324324

325325
/**
326-
* @returns Maximum IPv6 address for a provided CIDR
326+
* Calculates the maximum IPv6 address in the CIDR block
327+
* @returns The maximum IPv6 address as a string
327328
*/
328329
public maxIp(): string {
330+
/**
331+
* Calculate how many 16-bit blocks are needed for the network portion
332+
* e.g. for /56, networkPartLength = ceil(56/16) = 4 blocks
333+
*/
334+
const networkPartLength = Math.ceil(this.cidrPrefix / 16);
335+
/**
336+
* Calculate remaining bits in last network block
337+
* e.g. for /56, remainingBits = 56 % 16 = 8 bits
338+
*/
339+
const remainingBits = this.cidrPrefix % 16;
340+
/**
341+
* Create copy of network portion of address
342+
* e.g. [2001, db8, 0, 0] for 2001:db8::/56
343+
*/
329344
const endIP = [...this.networkPart];
330-
const hostPart = Array(8 - this.networkPart.length).fill(BigInt(0xffff));
331-
endIP.push(...hostPart);
332345

346+
/**
347+
* If there are remaining bits in last network block,
348+
* create appropriate bitmask and apply to last network block
349+
* e.g. for /56: mask = (1 << (16-8)) - 1 = 0x00FF
350+
*/
351+
if (remainingBits > 0) {
352+
const lastNetworkIndex = networkPartLength - 1;
353+
const mask = (BigInt(1) << BigInt(16 - remainingBits)) - BigInt(1);
354+
/**
355+
* Apply bitmask to last network block using bitwise OR
356+
* e.g. if lastNetworkIndex=3 and mask=0x00FF:
357+
* networkPart[3]=0x0000 | 0x00FF = 0x00FF
358+
*/
359+
endIP[lastNetworkIndex] = this.networkPart[lastNetworkIndex] | mask;
360+
}
361+
362+
/**
363+
* Fill remaining blocks with maximum value 0xFFFF
364+
* e.g. [2001, db8, 0, ff, ffff, ffff, ffff, ffff]
365+
*/
366+
for (let i = networkPartLength; i < 8; i++) {
367+
endIP.push(BigInt('0xffff'));
368+
}
369+
370+
/**
371+
* Convert blocks to hex strings and join with colons
372+
* e.g. 2001:db8:0:ff:ffff:ffff:ffff:ffff
373+
*/
333374
return endIP.map(this.formatIPv6Part).join(':');
334375
}
335376

@@ -342,26 +383,18 @@ export class CidrBlockIpv6 {
342383
* @returns true if two ranges overlap, false otherwise
343384
*/
344385
public rangesOverlap(range1: string, range2: string): boolean {
345-
const [start1, end1] = this.getIPv6Range(range1);
346-
const [start2, end2] = this.getIPv6Range(range2);
386+
// Create new CidrBlockIpv6 instances for both ranges
387+
const cidr1 = new CidrBlockIpv6(range1);
388+
const cidr2 = new CidrBlockIpv6(range2);
347389

348-
return (start1 <= end2) && (start2 <= end1);
349-
}
390+
// Convert min and max IPs to numeric values for comparison
391+
const start1 = this.ipv6ToNumber(cidr1.minIp());
392+
const end1 = this.ipv6ToNumber(cidr1.maxIp());
393+
const start2 = this.ipv6ToNumber(cidr2.minIp());
394+
const end2 = this.ipv6ToNumber(cidr2.maxIp());
350395

351-
/**
352-
*
353-
* @param cidr
354-
* @returns Range in the from of big int number [start, end]
355-
*/
356-
private getIPv6Range(cidr: string): [bigint, bigint] {
357-
const [ipv6Address, prefixLength] = cidr.split('/');
358-
const ipv6Number = this.ipv6ToNumber(ipv6Address);
359-
const mask = (BigInt(1) << BigInt(128 - Number(prefixLength))) - BigInt(1);
360-
const networkPrefix = ipv6Number & ~mask;
361-
const start = networkPrefix;
362-
const end = networkPrefix | mask;
363-
364-
return [start, end];
396+
// Check for overlap
397+
return (start1 <= end2) && (start2 <= end1);
365398
}
366399

367400
/**

packages/@aws-cdk/aws-ec2-alpha/lib/vpc-v2-base.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ export abstract class VpcV2Base extends Resource implements IVpcV2 {
430430
let useIpv6;
431431
if (this.secondaryCidrBlock) {
432432
useIpv6 = (this.secondaryCidrBlock.some((secondaryAddress) => secondaryAddress.amazonProvidedIpv6CidrBlock === true ||
433-
secondaryAddress.ipv6IpamPoolId != undefined));
433+
secondaryAddress.ipv6IpamPoolId !== undefined || secondaryAddress.ipv6CidrBlock !== undefined));
434434
}
435435

436436
if (!useIpv6) {

packages/@aws-cdk/aws-ec2-alpha/lib/vpc-v2.ts

+98-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,27 @@ export interface SecondaryAddressProps {
1717
readonly cidrBlockName: string;
1818
}
1919

20+
/**
21+
* Additional props needed for BYOIP IPv6 address props
22+
*/
23+
export interface Ipv6PoolSecondaryAddressProps extends SecondaryAddressProps {
24+
/**
25+
* ID of the IPv6 address pool from which to allocate the IPv6 CIDR block.
26+
* Note: BYOIP Pool ID is different from the IPAM Pool ID.
27+
* To onboard your IPv6 address range to your AWS account please refer to the below documentation
28+
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/byoip-onboard.html
29+
*/
30+
readonly ipv6PoolId: string;
31+
32+
/**
33+
* A valid IPv6 CIDR block from the IPv6 address pool onboarded to AWS using BYOIP.
34+
* The most specific IPv6 address range that you can bring is /48 for CIDRs that are publicly advertisable
35+
* and /56 for CIDRs that are not publicly advertisable.
36+
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-byoip.html#byoip-definitions
37+
*/
38+
readonly ipv6CidrBlock: string;
39+
}
40+
2041
/**
2142
* IpAddress options to define VPC V2
2243
*/
@@ -49,6 +70,13 @@ export class IpAddresses {
4970
public static amazonProvidedIpv6(props: SecondaryAddressProps) : IIpAddresses {
5071
return new AmazonProvided(props);
5172
}
73+
74+
/**
75+
* A BYOIP IPv6 address pool
76+
*/
77+
public static ipv6ByoipPool(props: Ipv6PoolSecondaryAddressProps): IIpAddresses {
78+
return new Ipv6Pool(props);
79+
}
5280
}
5381

5482
/**
@@ -121,6 +149,20 @@ export interface VpcCidrOptions {
121149
* @default - no IPAM IPv4 CIDR range is provisioned using IPAM
122150
*/
123151
readonly ipv4IpamProvisionedCidrs?: string[];
152+
153+
/**
154+
* IPv6 CIDR block from the BOYIP IPv6 address pool.
155+
*
156+
* @default - None
157+
*/
158+
readonly ipv6CidrBlock?: string;
159+
160+
/**
161+
* ID of the BYOIP IPv6 address pool from which to allocate the IPv6 CIDR block.
162+
*
163+
* @default - None
164+
*/
165+
readonly ipv6PoolId?: string;
124166
}
125167

126168
/**
@@ -499,7 +541,7 @@ export class VpcV2 extends VpcV2Base {
499541
throw new Error('Cidr Block Name is required to create secondary IP address');
500542
}
501543

502-
if (secondaryVpcOptions.amazonProvided || secondaryVpcOptions.ipv6IpamPool) {
544+
if (secondaryVpcOptions.amazonProvided || secondaryVpcOptions.ipv6IpamPool || secondaryVpcOptions.ipv6PoolId) {
503545
this.useIpv6 = true;
504546
}
505547
//validate CIDR ranges per RFC 1918
@@ -520,6 +562,10 @@ export class VpcV2 extends VpcV2Base {
520562
ipv6NetmaskLength: secondaryVpcOptions.ipv6NetmaskLength,
521563
ipv6IpamPoolId: secondaryVpcOptions.ipv6IpamPool?.ipamPoolId,
522564
amazonProvidedIpv6CidrBlock: secondaryVpcOptions.amazonProvided,
565+
//BYOIP IPv6 Address
566+
ipv6CidrBlock: secondaryVpcOptions?.ipv6CidrBlock,
567+
//BYOIP Pool for IPv6 address
568+
ipv6Pool: secondaryVpcOptions?.ipv6PoolId,
523569
});
524570
if (secondaryVpcOptions.dependencies) {
525571
for (const dep of secondaryVpcOptions.dependencies) {
@@ -633,6 +679,22 @@ class IpamIpv4 implements IIpAddresses {
633679
}
634680
}
635681

682+
/**
683+
* Supports assigning IPv6 address to VPC in an address pool
684+
*/
685+
class Ipv6Pool implements IIpAddresses {
686+
687+
constructor(private readonly props: Ipv6PoolSecondaryAddressProps) {
688+
}
689+
allocateVpcCidr(): VpcCidrOptions {
690+
return {
691+
ipv6CidrBlock: this.props.ipv6CidrBlock,
692+
ipv6PoolId: this.props.ipv6PoolId,
693+
cidrBlockName: this.props?.cidrBlockName,
694+
};
695+
}
696+
}
697+
636698
/**
637699
* Interface to create L2 for VPC Cidr Block
638700
*/
@@ -658,6 +720,16 @@ export interface IVPCCidrBlock {
658720
* IPAM pool for IPv4 address type
659721
*/
660722
readonly ipv4IpamPoolId ?: string;
723+
724+
/**
725+
* The IPv6 CIDR block from the specified IPv6 address pool.
726+
*/
727+
readonly ipv6CidrBlock?: string;
728+
729+
/**
730+
* The ID of the IPv6 address pool from which to allocate the IPv6 CIDR block.
731+
*/
732+
readonly ipv6Pool?: string;
661733
}
662734

663735
/**
@@ -721,6 +793,21 @@ export interface VPCCidrBlockattributes {
721793
* @default - no IPAM IPv4 CIDR range is provisioned using IPAM
722794
*/
723795
readonly ipv4IpamProvisionedCidrs?: string[];
796+
797+
/**
798+
* The IPv6 CIDR block from the specified IPv6 address pool.
799+
*
800+
* @default - No IPv6 CIDR block associated with VPC.
801+
*/
802+
readonly ipv6CidrBlock?: string;
803+
804+
/**
805+
* The ID of the IPv6 address pool from which to allocate the IPv6 CIDR block.
806+
* Note: BYOIP Pool ID is different than IPAM Pool ID.
807+
*
808+
* @default - No BYOIP pool associated with VPC.
809+
*/
810+
readonly ipv6Pool?: string;
724811
}
725812

726813
/**
@@ -748,6 +835,9 @@ class VPCCidrBlock extends Resource implements IVPCCidrBlock {
748835
public readonly amazonProvidedIpv6CidrBlock ?: boolean = props.amazonProvidedIpv6CidrBlock;
749836
public readonly ipv6IpamPoolId ?: string = props.ipv6IpamPoolId;
750837
public readonly ipv4IpamPoolId ?: string = props.ipv4IpamPoolId;
838+
//BYOIP Pool Attributes
839+
public readonly ipv6Pool?: string = props.ipv6Pool;
840+
public readonly ipv6CidrBlock?: string = props.ipv6CidrBlock;
751841
}
752842
return new Import(scope, id);
753843
}
@@ -762,6 +852,10 @@ class VPCCidrBlock extends Resource implements IVPCCidrBlock {
762852

763853
public readonly ipv4IpamPoolId?: string;
764854

855+
public readonly ipv6CidrBlock?: string;
856+
857+
public readonly ipv6Pool?: string;
858+
765859
constructor(scope: Construct, id: string, props: VPCCidrBlockProps) {
766860
super(scope, id);
767861
this.resource = new CfnVPCCidrBlock(this, id, props);
@@ -770,6 +864,9 @@ class VPCCidrBlock extends Resource implements IVPCCidrBlock {
770864
this.ipv6IpamPoolId = props.ipv6IpamPoolId;
771865
this.ipv4IpamPoolId = props.ipv4IpamPoolId;
772866
this.amazonProvidedIpv6CidrBlock = props.amazonProvidedIpv6CidrBlock;
867+
//BYOIP Pool and CIDR Block
868+
this.ipv6CidrBlock = props.ipv6CidrBlock;
869+
this.ipv6Pool = props.ipv6Pool;
773870
}
774871
}
775872

packages/@aws-cdk/aws-ec2-alpha/test/integ.byoip-ipv6.js.snapshot/cdk.out

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk/aws-ec2-alpha/test/integ.byoip-ipv6.js.snapshot/integ.json

+12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk/aws-ec2-alpha/test/integ.byoip-ipv6.js.snapshot/integtestmodelDefaultTestDeployAssertCF40BD53.assets.json

+19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)