Skip to content

Commit

Permalink
feat(VpcV2): add BYOIP IPv6 to VPCv2 (#32927)
Browse files Browse the repository at this point in the history
### 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*
  • Loading branch information
shikha372 authored Jan 22, 2025
1 parent c86296e commit 93c95fc
Show file tree
Hide file tree
Showing 17 changed files with 1,101 additions and 26 deletions.
32 changes: 32 additions & 0 deletions packages/@aws-cdk/aws-ec2-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,38 @@ new VpcV2(this, 'Vpc', {

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

### Bring your own IPv6 addresses (BYOIP)

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.

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:

```shell
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>"
```

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:

```shell
aws ec2 describe-byoip-cidr --region <region>
```

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).

Once you have provisioned your address block, you can use the IPv6 in your VPC as follows:

```ts
const myVpc = new VpcV2(this, 'Vpc', {
primaryAddressBlock: IpAddresses.ipv4('10.1.0.0/16'),
secondaryAddressBlocks: [IpAddresses.ipv6ByoipPool({
cidrBlockName: 'MyByoipCidrBlock',
ipv6PoolId: 'ipv6pool-ec2-someHashValue',
ipv6CidrBlock: '2001:db8::/32'
})],
enableDnsHostnames: true,
enableDnsSupport: true,
});
```

## Routing

Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-ec2-alpha/lib/subnet-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ function storeSubnetToVpcByType(vpc: IVpcV2, subnet: SubnetV2, type: SubnetType)
function validateSupportIpv6(vpc: IVpcV2) {
if (vpc.secondaryCidrBlock) {
if (vpc.secondaryCidrBlock.some((secondaryAddress) => secondaryAddress.amazonProvidedIpv6CidrBlock === true ||
secondaryAddress.ipv6IpamPoolId != undefined)) {
secondaryAddress.ipv6IpamPoolId !== undefined || secondaryAddress.ipv6Pool !== undefined)) {
return true;
} else {
throw new Error('To use IPv6, the VPC must enable IPv6 support.');
Expand Down
75 changes: 54 additions & 21 deletions packages/@aws-cdk/aws-ec2-alpha/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,13 +323,54 @@ export class CidrBlockIpv6 {
}

/**
* @returns Maximum IPv6 address for a provided CIDR
* Calculates the maximum IPv6 address in the CIDR block
* @returns The maximum IPv6 address as a string
*/
public maxIp(): string {
/**
* Calculate how many 16-bit blocks are needed for the network portion
* e.g. for /56, networkPartLength = ceil(56/16) = 4 blocks
*/
const networkPartLength = Math.ceil(this.cidrPrefix / 16);
/**
* Calculate remaining bits in last network block
* e.g. for /56, remainingBits = 56 % 16 = 8 bits
*/
const remainingBits = this.cidrPrefix % 16;
/**
* Create copy of network portion of address
* e.g. [2001, db8, 0, 0] for 2001:db8::/56
*/
const endIP = [...this.networkPart];
const hostPart = Array(8 - this.networkPart.length).fill(BigInt(0xffff));
endIP.push(...hostPart);

/**
* If there are remaining bits in last network block,
* create appropriate bitmask and apply to last network block
* e.g. for /56: mask = (1 << (16-8)) - 1 = 0x00FF
*/
if (remainingBits > 0) {
const lastNetworkIndex = networkPartLength - 1;
const mask = (BigInt(1) << BigInt(16 - remainingBits)) - BigInt(1);
/**
* Apply bitmask to last network block using bitwise OR
* e.g. if lastNetworkIndex=3 and mask=0x00FF:
* networkPart[3]=0x0000 | 0x00FF = 0x00FF
*/
endIP[lastNetworkIndex] = this.networkPart[lastNetworkIndex] | mask;
}

/**
* Fill remaining blocks with maximum value 0xFFFF
* e.g. [2001, db8, 0, ff, ffff, ffff, ffff, ffff]
*/
for (let i = networkPartLength; i < 8; i++) {
endIP.push(BigInt('0xffff'));
}

/**
* Convert blocks to hex strings and join with colons
* e.g. 2001:db8:0:ff:ffff:ffff:ffff:ffff
*/
return endIP.map(this.formatIPv6Part).join(':');
}

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

return (start1 <= end2) && (start2 <= end1);
}
// Convert min and max IPs to numeric values for comparison
const start1 = this.ipv6ToNumber(cidr1.minIp());
const end1 = this.ipv6ToNumber(cidr1.maxIp());
const start2 = this.ipv6ToNumber(cidr2.minIp());
const end2 = this.ipv6ToNumber(cidr2.maxIp());

/**
*
* @param cidr
* @returns Range in the from of big int number [start, end]
*/
private getIPv6Range(cidr: string): [bigint, bigint] {
const [ipv6Address, prefixLength] = cidr.split('/');
const ipv6Number = this.ipv6ToNumber(ipv6Address);
const mask = (BigInt(1) << BigInt(128 - Number(prefixLength))) - BigInt(1);
const networkPrefix = ipv6Number & ~mask;
const start = networkPrefix;
const end = networkPrefix | mask;

return [start, end];
// Check for overlap
return (start1 <= end2) && (start2 <= end1);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-ec2-alpha/lib/vpc-v2-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ export abstract class VpcV2Base extends Resource implements IVpcV2 {
let useIpv6;
if (this.secondaryCidrBlock) {
useIpv6 = (this.secondaryCidrBlock.some((secondaryAddress) => secondaryAddress.amazonProvidedIpv6CidrBlock === true ||
secondaryAddress.ipv6IpamPoolId != undefined));
secondaryAddress.ipv6IpamPoolId !== undefined || secondaryAddress.ipv6CidrBlock !== undefined));
}

if (!useIpv6) {
Expand Down
99 changes: 98 additions & 1 deletion packages/@aws-cdk/aws-ec2-alpha/lib/vpc-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,27 @@ export interface SecondaryAddressProps {
readonly cidrBlockName: string;
}

/**
* Additional props needed for BYOIP IPv6 address props
*/
export interface Ipv6PoolSecondaryAddressProps extends SecondaryAddressProps {
/**
* ID of the IPv6 address pool from which to allocate the IPv6 CIDR block.
* Note: BYOIP Pool ID is different from the IPAM Pool ID.
* To onboard your IPv6 address range to your AWS account please refer to the below documentation
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/byoip-onboard.html
*/
readonly ipv6PoolId: string;

/**
* A valid IPv6 CIDR block from the IPv6 address pool onboarded to AWS using BYOIP.
* The most specific IPv6 address range that you can bring is /48 for CIDRs that are publicly advertisable
* and /56 for CIDRs that are not publicly advertisable.
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-byoip.html#byoip-definitions
*/
readonly ipv6CidrBlock: string;
}

/**
* IpAddress options to define VPC V2
*/
Expand Down Expand Up @@ -49,6 +70,13 @@ export class IpAddresses {
public static amazonProvidedIpv6(props: SecondaryAddressProps) : IIpAddresses {
return new AmazonProvided(props);
}

/**
* A BYOIP IPv6 address pool
*/
public static ipv6ByoipPool(props: Ipv6PoolSecondaryAddressProps): IIpAddresses {
return new Ipv6Pool(props);
}
}

/**
Expand Down Expand Up @@ -121,6 +149,20 @@ export interface VpcCidrOptions {
* @default - no IPAM IPv4 CIDR range is provisioned using IPAM
*/
readonly ipv4IpamProvisionedCidrs?: string[];

/**
* IPv6 CIDR block from the BOYIP IPv6 address pool.
*
* @default - None
*/
readonly ipv6CidrBlock?: string;

/**
* ID of the BYOIP IPv6 address pool from which to allocate the IPv6 CIDR block.
*
* @default - None
*/
readonly ipv6PoolId?: string;
}

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

if (secondaryVpcOptions.amazonProvided || secondaryVpcOptions.ipv6IpamPool) {
if (secondaryVpcOptions.amazonProvided || secondaryVpcOptions.ipv6IpamPool || secondaryVpcOptions.ipv6PoolId) {
this.useIpv6 = true;
}
//validate CIDR ranges per RFC 1918
Expand All @@ -520,6 +562,10 @@ export class VpcV2 extends VpcV2Base {
ipv6NetmaskLength: secondaryVpcOptions.ipv6NetmaskLength,
ipv6IpamPoolId: secondaryVpcOptions.ipv6IpamPool?.ipamPoolId,
amazonProvidedIpv6CidrBlock: secondaryVpcOptions.amazonProvided,
//BYOIP IPv6 Address
ipv6CidrBlock: secondaryVpcOptions?.ipv6CidrBlock,
//BYOIP Pool for IPv6 address
ipv6Pool: secondaryVpcOptions?.ipv6PoolId,
});
if (secondaryVpcOptions.dependencies) {
for (const dep of secondaryVpcOptions.dependencies) {
Expand Down Expand Up @@ -633,6 +679,22 @@ class IpamIpv4 implements IIpAddresses {
}
}

/**
* Supports assigning IPv6 address to VPC in an address pool
*/
class Ipv6Pool implements IIpAddresses {

constructor(private readonly props: Ipv6PoolSecondaryAddressProps) {
}
allocateVpcCidr(): VpcCidrOptions {
return {
ipv6CidrBlock: this.props.ipv6CidrBlock,
ipv6PoolId: this.props.ipv6PoolId,
cidrBlockName: this.props?.cidrBlockName,
};
}
}

/**
* Interface to create L2 for VPC Cidr Block
*/
Expand All @@ -658,6 +720,16 @@ export interface IVPCCidrBlock {
* IPAM pool for IPv4 address type
*/
readonly ipv4IpamPoolId ?: string;

/**
* The IPv6 CIDR block from the specified IPv6 address pool.
*/
readonly ipv6CidrBlock?: string;

/**
* The ID of the IPv6 address pool from which to allocate the IPv6 CIDR block.
*/
readonly ipv6Pool?: string;
}

/**
Expand Down Expand Up @@ -721,6 +793,21 @@ export interface VPCCidrBlockattributes {
* @default - no IPAM IPv4 CIDR range is provisioned using IPAM
*/
readonly ipv4IpamProvisionedCidrs?: string[];

/**
* The IPv6 CIDR block from the specified IPv6 address pool.
*
* @default - No IPv6 CIDR block associated with VPC.
*/
readonly ipv6CidrBlock?: string;

/**
* The ID of the IPv6 address pool from which to allocate the IPv6 CIDR block.
* Note: BYOIP Pool ID is different than IPAM Pool ID.
*
* @default - No BYOIP pool associated with VPC.
*/
readonly ipv6Pool?: string;
}

/**
Expand Down Expand Up @@ -748,6 +835,9 @@ class VPCCidrBlock extends Resource implements IVPCCidrBlock {
public readonly amazonProvidedIpv6CidrBlock ?: boolean = props.amazonProvidedIpv6CidrBlock;
public readonly ipv6IpamPoolId ?: string = props.ipv6IpamPoolId;
public readonly ipv4IpamPoolId ?: string = props.ipv4IpamPoolId;
//BYOIP Pool Attributes
public readonly ipv6Pool?: string = props.ipv6Pool;
public readonly ipv6CidrBlock?: string = props.ipv6CidrBlock;
}
return new Import(scope, id);
}
Expand All @@ -762,6 +852,10 @@ class VPCCidrBlock extends Resource implements IVPCCidrBlock {

public readonly ipv4IpamPoolId?: string;

public readonly ipv6CidrBlock?: string;

public readonly ipv6Pool?: string;

constructor(scope: Construct, id: string, props: VPCCidrBlockProps) {
super(scope, id);
this.resource = new CfnVPCCidrBlock(this, id, props);
Expand All @@ -770,6 +864,9 @@ class VPCCidrBlock extends Resource implements IVPCCidrBlock {
this.ipv6IpamPoolId = props.ipv6IpamPoolId;
this.ipv4IpamPoolId = props.ipv4IpamPoolId;
this.amazonProvidedIpv6CidrBlock = props.amazonProvidedIpv6CidrBlock;
//BYOIP Pool and CIDR Block
this.ipv6CidrBlock = props.ipv6CidrBlock;
this.ipv6Pool = props.ipv6Pool;
}
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 93c95fc

Please sign in to comment.