|
1 |
| -import { CfnEIP, CfnEgressOnlyInternetGateway, CfnInternetGateway, CfnNatGateway, CfnRoute, CfnRouteTable, CfnVPCGatewayAttachment, CfnVPNGateway, CfnVPNGatewayRoutePropagation, GatewayVpcEndpoint, IRouteTable, IVpcEndpoint, RouterType } from 'aws-cdk-lib/aws-ec2'; |
| 1 | +import { CfnEIP, CfnEgressOnlyInternetGateway, CfnInternetGateway, CfnNatGateway, CfnVPCPeeringConnection, CfnRoute, CfnRouteTable, CfnVPCGatewayAttachment, CfnVPNGateway, CfnVPNGatewayRoutePropagation, GatewayVpcEndpoint, IRouteTable, IVpcEndpoint, RouterType } from 'aws-cdk-lib/aws-ec2'; |
2 | 2 | import { Construct, IDependable } from 'constructs';
|
3 | 3 | import { Annotations, Duration, IResource, Resource } from 'aws-cdk-lib/core';
|
4 | 4 | import { IVpcV2, VPNGatewayV2Options } from './vpc-v2-base';
|
5 |
| -import { NetworkUtils, allRouteTableIds } from './util'; |
| 5 | +import { NetworkUtils, allRouteTableIds, CidrBlock } from './util'; |
6 | 6 | import { ISubnetV2 } from './subnet-v2';
|
7 | 7 |
|
8 | 8 | /**
|
@@ -175,6 +175,40 @@ export interface NatGatewayProps extends NatGatewayOptions {
|
175 | 175 | readonly vpc?: IVpcV2;
|
176 | 176 | }
|
177 | 177 |
|
| 178 | +/** |
| 179 | + * Options to define a VPC peering connection. |
| 180 | + */ |
| 181 | +export interface VPCPeeringConnectionOptions { |
| 182 | + /** |
| 183 | + * The VPC that is accepting the peering connection. |
| 184 | + */ |
| 185 | + readonly acceptorVpc: IVpcV2; |
| 186 | + |
| 187 | + /** |
| 188 | + * The role arn created in the acceptor account. |
| 189 | + * |
| 190 | + * @default - no peerRoleArn needed if not cross account connection |
| 191 | + */ |
| 192 | + readonly peerRoleArn?: string; |
| 193 | + |
| 194 | + /** |
| 195 | + * The resource name of the peering connection. |
| 196 | + * |
| 197 | + * @default - peering connection provisioned without any name |
| 198 | + */ |
| 199 | + readonly vpcPeeringConnectionName?: string; |
| 200 | +} |
| 201 | + |
| 202 | +/** |
| 203 | + * Properties to define a VPC peering connection. |
| 204 | + */ |
| 205 | +export interface VPCPeeringConnectionProps extends VPCPeeringConnectionOptions { |
| 206 | + /** |
| 207 | + * The VPC that is requesting the peering connection. |
| 208 | + */ |
| 209 | + readonly requestorVpc: IVpcV2; |
| 210 | +} |
| 211 | + |
178 | 212 | /**
|
179 | 213 | * Creates an egress-only internet gateway
|
180 | 214 | * @resource AWS::EC2::EgressOnlyInternetGateway
|
@@ -312,7 +346,7 @@ export class VPNGatewayV2 extends Resource implements IRouteTarget {
|
312 | 346 | const routeTableIds = allRouteTableIds(subnets);
|
313 | 347 |
|
314 | 348 | if (routeTableIds.length === 0) {
|
315 |
| - Annotations.of(this).addWarningV2('@aws-cdk:aws-ec2-elpha:enableVpnGatewayV2', `No subnets matching selection: '${JSON.stringify(vpnRoutePropagation)}'. Select other subnets to add routes to.`); |
| 349 | + Annotations.of(scope).addWarningV2('@aws-cdk:aws-ec2-elpha:enableVpnGatewayV2', `No subnets matching selection: '${JSON.stringify(vpnRoutePropagation)}'. Select other subnets to add routes to.`); |
316 | 350 | }
|
317 | 351 |
|
318 | 352 | this._routePropagation = new CfnVPNGatewayRoutePropagation(this, 'RoutePropagation', {
|
@@ -402,6 +436,103 @@ export class NatGateway extends Resource implements IRouteTarget {
|
402 | 436 | }
|
403 | 437 | }
|
404 | 438 |
|
| 439 | +/** |
| 440 | + * Creates a peering connection between two VPCs |
| 441 | + * @resource AWS::EC2::VPCPeeringConnection |
| 442 | + */ |
| 443 | +export class VPCPeeringConnection extends Resource implements IRouteTarget { |
| 444 | + |
| 445 | + /** |
| 446 | + * The type of router used in the route. |
| 447 | + */ |
| 448 | + readonly routerType: RouterType; |
| 449 | + |
| 450 | + /** |
| 451 | + * The ID of the route target. |
| 452 | + */ |
| 453 | + readonly routerTargetId: string; |
| 454 | + |
| 455 | + /** |
| 456 | + * The VPC peering connection CFN resource. |
| 457 | + */ |
| 458 | + public readonly resource: CfnVPCPeeringConnection; |
| 459 | + |
| 460 | + constructor(scope: Construct, id: string, props: VPCPeeringConnectionProps) { |
| 461 | + super(scope, id); |
| 462 | + |
| 463 | + this.routerType = RouterType.VPC_PEERING_CONNECTION; |
| 464 | + |
| 465 | + const isCrossAccount = props.requestorVpc.ownerAccountId !== props.acceptorVpc.ownerAccountId; |
| 466 | + |
| 467 | + if (!isCrossAccount && props.peerRoleArn) { |
| 468 | + throw new Error('peerRoleArn is not needed for same account peering'); |
| 469 | + } |
| 470 | + |
| 471 | + if (isCrossAccount && !props.peerRoleArn) { |
| 472 | + throw new Error('Cross account VPC peering requires peerRoleArn'); |
| 473 | + } |
| 474 | + |
| 475 | + const overlap = this.validateVpcCidrOverlap(props.requestorVpc, props.acceptorVpc); |
| 476 | + if (overlap) { |
| 477 | + throw new Error('CIDR block should not overlap with each other for establishing a peering connection'); |
| 478 | + } |
| 479 | + |
| 480 | + this.resource = new CfnVPCPeeringConnection(this, 'VPCPeeringConnection', { |
| 481 | + vpcId: props.requestorVpc.vpcId, |
| 482 | + peerVpcId: props.acceptorVpc.vpcId, |
| 483 | + peerOwnerId: props.acceptorVpc.ownerAccountId, |
| 484 | + peerRegion: props.acceptorVpc.region, |
| 485 | + peerRoleArn: isCrossAccount ? props.peerRoleArn : undefined, |
| 486 | + }); |
| 487 | + |
| 488 | + this.routerTargetId = this.resource.attrId; |
| 489 | + this.node.defaultChild = this.resource; |
| 490 | + } |
| 491 | + |
| 492 | + /** |
| 493 | + * Validates if the provided IPv4 CIDR block overlaps with existing subnet CIDR blocks within the given VPC. |
| 494 | + * |
| 495 | + * @param requestorVpc The VPC of the requestor. |
| 496 | + * @param acceptorVpc The VPC of the acceptor. |
| 497 | + * @returns True if the IPv4 CIDR block overlaps with each other for two VPCs, false otherwise. |
| 498 | + * @internal |
| 499 | + */ |
| 500 | + private validateVpcCidrOverlap(requestorVpc: IVpcV2, acceptorVpc: IVpcV2): boolean { |
| 501 | + |
| 502 | + const requestorCidrs = [requestorVpc.ipv4CidrBlock]; |
| 503 | + const acceptorCidrs = [acceptorVpc.ipv4CidrBlock]; |
| 504 | + |
| 505 | + if (requestorVpc.secondaryCidrBlock) { |
| 506 | + requestorCidrs.push(...requestorVpc.secondaryCidrBlock |
| 507 | + .map(block => block.cidrBlock) |
| 508 | + .filter((cidr): cidr is string => cidr !== undefined)); |
| 509 | + } |
| 510 | + |
| 511 | + if (acceptorVpc.secondaryCidrBlock) { |
| 512 | + acceptorCidrs.push(...acceptorVpc.secondaryCidrBlock |
| 513 | + .map(block => block.cidrBlock) |
| 514 | + .filter((cidr): cidr is string => cidr !== undefined)); |
| 515 | + } |
| 516 | + |
| 517 | + for (const requestorCidr of requestorCidrs) { |
| 518 | + const requestorRange = new CidrBlock(requestorCidr); |
| 519 | + const requestorIpRange: [string, string] = [requestorRange.minIp(), requestorRange.maxIp()]; |
| 520 | + |
| 521 | + for (const acceptorCidr of acceptorCidrs) { |
| 522 | + const acceptorRange = new CidrBlock(acceptorCidr); |
| 523 | + const acceptorIpRange: [string, string] = [acceptorRange.minIp(), acceptorRange.maxIp()]; |
| 524 | + |
| 525 | + if (requestorRange.rangesOverlap(acceptorIpRange, requestorIpRange)) { |
| 526 | + return true; |
| 527 | + } |
| 528 | + } |
| 529 | + } |
| 530 | + |
| 531 | + return false; |
| 532 | + } |
| 533 | + |
| 534 | +} |
| 535 | + |
405 | 536 | /**
|
406 | 537 | * The type of endpoint or gateway being targeted by the route.
|
407 | 538 | */
|
@@ -534,7 +665,7 @@ export class Route extends Resource implements IRouteV2 {
|
534 | 665 | /**
|
535 | 666 | * The type of router the route is targetting
|
536 | 667 | */
|
537 |
| - public readonly targetRouterType: RouterType |
| 668 | + public readonly targetRouterType: RouterType; |
538 | 669 |
|
539 | 670 | /**
|
540 | 671 | * The route CFN resource.
|
|
0 commit comments