in src/deployments/cdk/src/common/vpc.ts [177:741]
constructor(scope: cdk.Construct, name: string, vpcProps: VpcProps) {
super(scope, name);
const props = { vpcProps };
const {
accountKey,
accounts,
vpcConfig,
organizationalUnitName,
limiter,
vpcConfigs,
accountStacks,
acceleratorName,
installerVersion,
vpcOutput,
vpcPools,
subnetPools,
existingAttachments,
} = props.vpcProps;
const vpcName = props.vpcProps.vpcConfig.name;
const currentVpcPools: AssignedVpcCidrPool[] = [];
const currentSubnetPools: AssignedSubnetCidrPool[] = [];
if (['lookup', 'dynamic'].includes(vpcConfig['cidr-src'])) {
currentVpcPools.push(
...getVpcCidrPools(vpcPools, accountKey, vpcConfig.region, vpcConfig.name, organizationalUnitName),
);
currentSubnetPools.push(
...getSubnetCidrPools({
subnetPools,
accountKey,
region: vpcConfig.region,
vpcName: vpcConfig.name,
organizationalUnitName,
}),
);
}
this.ddbKmsKey = props.vpcProps.ddbKmsKey || '';
this.name = props.vpcProps.vpcConfig.name;
const vpcCidrs = props.vpcProps.vpcConfig.cidr;
this.region = vpcConfig.region;
// Retrive CIDR
if (props.vpcProps.vpcConfig['cidr-src'] === 'dynamic') {
this.cidrBlock = currentVpcPools.find(vpcPool => vpcPool.pool === vpcCidrs[0].pool)?.cidr!;
if (!this.cidrBlock) {
throw new Error(`No CIDR found for VPC : ${vpcConfig.name} in DynamoDB "cidr-vpc-assign"`);
}
if (vpcCidrs.length > 1) {
this.cidr2Block.push(...currentVpcPools.filter(vpcPool => vpcPool.pool !== vpcCidrs[0].pool).map(c => c.cidr));
}
} else if (props.vpcProps.vpcConfig['cidr-src'] === 'lookup') {
if (currentVpcPools.length === 0) {
throw new Error(`No CIDR found for VPC : ${vpcConfig.name} in DDB`);
}
currentVpcPools.sort((a, b) => (a['vpc-assigned-id']! > b['vpc-assigned-id']! ? 1 : -1));
this.cidrBlock = currentVpcPools[0].cidr;
if (currentVpcPools.length > 1) {
this.cidr2Block.push(...currentVpcPools.slice(1, currentVpcPools.length).map(c => c.cidr));
}
} else {
if (!vpcCidrs) {
throw new Error(`No CIDR found for VPC : ${vpcConfig.name} in Configuration`);
}
this.cidrBlock = vpcCidrs[0].value?.toCidrString()!;
if (vpcCidrs.length > 1) {
this.cidr2Block.push(...vpcCidrs.slice(1, vpcCidrs.length).map(c => c.value?.toCidrString()!));
}
}
// Create Custom VPC using CFN construct as tags override option not available in default construct
const vpcObj = new ec2.CfnVPC(this, vpcName, {
cidrBlock: this.cidrBlock,
enableDnsHostnames: true,
enableDnsSupport: true,
instanceTenancy: props.vpcProps.vpcConfig['dedicated-tenancy']
? ec2.DefaultInstanceTenancy.DEDICATED
: ec2.DefaultInstanceTenancy.DEFAULT,
});
this.vpcId = vpcObj.ref;
const extendVpc: ec2.CfnVPCCidrBlock[] = [];
this.cidr2Block.forEach((additionalCidr, index) => {
let id = `ExtendVPC-${index}`;
if (index === 0) {
id = 'ExtendVPC';
}
const extendVpcCidr = new ec2.CfnVPCCidrBlock(this, id, {
cidrBlock: additionalCidr,
vpcId: vpcObj.ref,
});
extendVpc.push(extendVpcCidr);
this.additionalCidrBlocks.push(additionalCidr);
});
let igw;
let igwAttach;
if (props.vpcProps.vpcConfig.igw) {
// Create IGW
igw = new ec2.CfnInternetGateway(this, `${vpcName}_igw`);
// Attach IGW to VPC
igwAttach = new ec2.CfnVPCGatewayAttachment(this, `${props.vpcProps.vpcConfig.name}_attach_igw`, {
vpcId: vpcObj.ref,
internetGatewayId: igw.ref,
});
}
let vgw;
let vgwAttach;
const vgwConfig = props.vpcProps.vpcConfig.vgw;
if (vgwConfig) {
const amazonSideAsn = config.VirtualPrivateGatewayConfig.is(vgwConfig) ? vgwConfig.asn : undefined;
// Create VGW
vgw = new ec2.CfnVPNGateway(this, `${props.vpcProps.vpcConfig.name}_vpg`, {
type: 'ipsec.1',
amazonSideAsn,
});
// Attach VGW to VPC
vgwAttach = new ec2.CfnVPCGatewayAttachment(this, `${props.vpcProps.vpcConfig.name}_attach_vgw`, {
vpcId: vpcObj.ref,
vpnGatewayId: vgw.ref,
});
}
const s3Routes: string[] = [];
const dynamoRoutes: string[] = [];
const routeTablesProps = props.vpcProps.vpcConfig['route-tables'];
const tgwAttach = props.vpcProps.vpcConfig['tgw-attach'];
const natRouteTables: string[] = [];
if (routeTablesProps) {
// Create Route Tables
for (const routeTableProp of routeTablesProps) {
if (routeTableProp.name === 'default') {
continue;
}
const routeTableName = routeTableProp.name;
const routeTable = new ec2.CfnRouteTable(this, routeTableName, {
vpcId: vpcObj.ref,
});
this.routeTableNameToIdMap[routeTableName] = routeTable.ref;
}
}
const subnetsConfig = props.vpcProps.vpcConfig.subnets || [];
for (const subnetConfig of subnetsConfig) {
const subnetName = subnetConfig.name;
for (const subnetDefinition of subnetConfig.definitions.values()) {
if (subnetDefinition.disabled) {
continue;
}
let subnetCidr: string = '';
if (['lookup', 'dynamic'].includes(vpcConfig['cidr-src'])) {
const subnetCidrPool = currentSubnetPools.find(
s =>
s.az === subnetDefinition.az &&
s['subnet-name'] === subnetConfig.name &&
s['vpc-name'] === vpcConfig.name &&
s.region === vpcConfig.region,
);
if (subnetCidrPool) {
subnetCidr = subnetCidrPool.cidr;
}
} else {
subnetCidr = subnetDefinition.cidr?.value?.toCidrString()!;
}
if (!subnetCidr) {
console.warn(`Subnet with name "${subnetName}" and AZ "${subnetDefinition.az}" does not have a CIDR block`);
continue;
}
const subnetId = `${subnetName}_${vpcName}_az${subnetDefinition.az}`;
const subnet = new ec2.CfnSubnet(this, subnetId, {
cidrBlock: subnetCidr,
vpcId: vpcObj.ref,
availabilityZone: `${this.region}${subnetDefinition.az}`,
});
for (const extensions of extendVpc) {
subnet.addDependsOn(extensions);
}
this.azSubnets.push({
subnet,
subnetName,
id: subnet.ref,
name: subnetName,
az: subnetDefinition.az,
cidrBlock: subnetCidr,
});
// Attach Subnet to Route-Table
const routeTableName = subnetDefinition['route-table'];
if (routeTableName === 'default') {
continue;
}
// Find the route table ID for the route table name
const routeTableId = this.routeTableNameToIdMap[routeTableName];
if (!routeTableId) {
console.warn(`Cannot find route table with name "${routeTableName}"`);
continue;
}
// Associate the route table with the subnet
new ec2.CfnSubnetRouteTableAssociation(this, `RouteTable${subnetId}`, {
routeTableId,
subnetId: subnet.ref,
});
}
// Check for NACL's
if (subnetConfig.nacls) {
console.log(`NACL's Defined in VPC "${vpcName}" in Subnet "${subnetName}"`);
new Nacl(this, `NACL-${subnetName}`, {
accountKey,
subnetConfig,
vpcConfig,
vpcId: this.vpcId,
subnets: this.azSubnets,
vpcConfigs: vpcConfigs!,
vpcPools,
subnetPools,
});
}
}
let tgw: TransitGatewayOutput | undefined;
let tgwAttachment: TransitGatewayAttachment | undefined;
if (config.TransitGatewayAttachConfigType.is(tgwAttach)) {
const tgwName = tgwAttach['associate-to-tgw'];
// Find TGW in outputs
tgw = TransitGatewayOutputFinder.tryFindOneByName({
outputs: vpcProps.outputs,
accountKey: tgwAttach.account,
name: tgwName,
});
if (!tgw) {
throw new Error(`Cannot find transit gateway with name "${tgwName}"`);
} else {
const attachSubnetsConfig = tgwAttach['attach-subnets'] || [];
const associateConfig = tgwAttach['tgw-rt-associate'] || [];
const propagateConfig = tgwAttach['tgw-rt-propagate'] || [];
const blackhole = tgwAttach['blackhole-route'];
const subnetIds: string[] = [];
if (vpcOutput && vpcOutput.initialSubnets.length > 0) {
subnetIds.push(
...attachSubnetsConfig.flatMap(
subnet =>
vpcOutput.initialSubnets
.filter(s => s.subnetName === subnet)
.map(sub => this.azSubnets.getAzSubnetIdForNameAndAz(sub.subnetName, sub.az)!) || [],
),
);
} else if (vpcOutput) {
subnetIds.push(
...attachSubnetsConfig.flatMap(
subnet =>
vpcOutput.subnets
.filter(s => s.subnetName === subnet)
.map(sub => this.azSubnets.getAzSubnetIdForNameAndAz(sub.subnetName, sub.az)!) || [],
),
);
} else {
subnetIds.push(
...attachSubnetsConfig.flatMap(subnet => this.azSubnets.getAzSubnetIdsForSubnetName(subnet) || []),
);
}
if (subnetIds.length === 0) {
// TODO Throw or warn?
// throw new Error(`Cannot attach to TGW ${tgw.name}: no subnets found to attach to for VPC ${vpcConfig.name}`);
}
const tgwRouteAssociates = associateConfig.map(route => tgw!.tgwRouteTableNameToIdMap[route]);
const tgwRoutePropagates = propagateConfig.map(route => tgw!.tgwRouteTableNameToIdMap[route]);
// Attach VPC To TGW
tgwAttachment = new TransitGatewayAttachment(this, 'TgwAttach', {
name: `${vpcConfig.name}_${tgw.name}_att`,
vpcId: this.vpcId,
subnetIds,
transitGatewayId: tgw.tgwId,
});
const currentSubnets = attachSubnetsConfig.flatMap(
subnet => this.azSubnets.getAzSubnetIdsForSubnetName(subnet) || [],
);
const ec2OpsRole = IamRoleOutputFinder.tryFindOneByName({
outputs: props.vpcProps.outputs,
accountKey,
roleKey: 'Ec2Operations',
});
if (ec2OpsRole) {
const modifyTgwAttach = new ModifyTransitGatewayAttachment(this, 'ModifyTgwAttach', {
roleArn: ec2OpsRole.roleArn,
subnetIds: currentSubnets,
transitGatewayAttachmentId: tgwAttachment.transitGatewayAttachmentId,
ignoreWhileDeleteSubnets: subnetIds,
});
modifyTgwAttach.node.addDependency(tgwAttachment);
}
// TODO add VPC To TGW attachment output
this.tgwAttachments.push({
name: tgw.name,
id: tgwAttachment.transitGatewayAttachmentId,
});
const ownerAccountId = getAccountId(accounts, tgwAttach.account);
if (ownerAccountId) {
// Add tags in the TGW owner account
new AddTagsToResourcesOutput(this, 'TgwAttachTags', {
dependencies: [tgwAttachment],
produceResources: () => [
{
resourceId: tgwAttachment!.transitGatewayAttachmentId,
resourceType: 'tgw-attachment',
tags: tgwAttachment!.resource.tags.renderTags(),
targetAccountIds: [ownerAccountId],
region: cdk.Aws.REGION,
},
],
});
}
// in case TGW attachment is created for the same account, we create using the same stack
// otherwise, we will store tgw attachment output and do it in next phase
if (tgwAttach.account === accountKey) {
new TransitGatewayRoute(this, 'TgwRoute', {
tgwAttachmentId: tgwAttachment.transitGatewayAttachmentId,
tgwRouteAssociates,
tgwRoutePropagates,
blackhole,
cidr: this.cidrBlock,
});
} else {
let constructIndex: string;
let existingAttachment: TransitGatewayAttachmentOutput | undefined;
existingAttachment = existingAttachments.find(
att =>
att.accountKey === tgwAttach.account &&
att.region === this.region &&
att.cidr === this.cidrBlock &&
att.vpc === vpcName,
);
if (!existingAttachment) {
existingAttachment = existingAttachments.find(
att => att.accountKey === tgwAttach.account && att.region === this.region && att.cidr === this.cidrBlock,
);
}
if (!existingAttachment) {
// Generate hash
constructIndex = hashSum({
accountKey: tgwAttach.account,
rgion: this.region,
cidr: this.cidrBlock,
vpc: vpcName,
});
} else {
// This might cause failure if existing users already having multiple tgw cross account attachments in same account and region
constructIndex =
existingAttachment.constructIndex ||
existingAttachments
.findIndex(
att =>
att.accountKey === tgwAttach.account && att.region === this.region && att.cidr === this.cidrBlock,
)
.toString();
}
new CfnTransitGatewayAttachmentOutput(this, 'TgwAttachmentOutput', {
accountKey: tgwAttach.account,
region: this.region,
tgwAttachmentId: tgwAttachment.transitGatewayAttachmentId,
tgwRouteAssociates,
tgwRoutePropagates,
blackhole: blackhole ?? false,
cidr: this.cidrBlock,
vpc: vpcName,
constructIndex,
});
}
}
}
const natgwProps = vpcConfig.natgw;
if (config.NatGatewayConfig.is(natgwProps)) {
const subnetConfig = natgwProps.subnet;
const natSubnets: AzSubnet[] = [];
if (subnetConfig.az) {
natSubnets.push(this.azSubnets.getAzSubnetForNameAndAz(subnetConfig.name, subnetConfig.az)!);
} else {
natSubnets.push(...this.azSubnets.getAzSubnetsForSubnetName(subnetConfig.name));
}
for (const natSubnet of natSubnets) {
console.log(`Creating natgw for Subnet "${natSubnet.name}" az: "${natSubnet.az}"`);
const natGWName = `NATGW_${natSubnet.name}_${natSubnet.az}_natgw`;
const eip = new ec2.CfnEIP(this, `EIP_natgw_${natSubnet.az}`);
const natgw = new ec2.CfnNatGateway(this, natGWName, {
allocationId: eip.attrAllocationId,
subnetId: natSubnet.id,
});
this.natgwNameToIdMap[`NATGW_${natSubnet.name}_az${natSubnet.az.toUpperCase()}`.toLowerCase()] = natgw.ref;
}
}
const nfwProps = vpcConfig.nfw;
if (config.AWSNetworkFirewallConfig.is(nfwProps)) {
const subnetConfig = nfwProps.subnet;
const nfwSubnets: AzSubnet[] = [];
if (subnetConfig.az) {
nfwSubnets.push(this.azSubnets.getAzSubnetForNameAndAz(subnetConfig.name, subnetConfig.az)!);
} else {
nfwSubnets.push(...this.azSubnets.getAzSubnetsForSubnetName(subnetConfig.name));
}
this.nfw = new Nfw(this, `${nfwProps['firewall-name']}`, {
nfwPolicy: nfwProps.policyString,
nfwPolicyConfig: nfwProps.policy || { name: 'Sample-Firewall-Policy', path: 'nfw/nfw-example-policy.json' },
subnets: nfwSubnets,
vpcId: this.vpcId,
nfwName: nfwProps['firewall-name'] || `${vpcConfig.name}-nfw`,
acceleratorPrefix: vpcProps.acceleratorPrefix || '',
nfwFlowLogging: nfwProps['flow-dest'] || 'None',
nfwAlertLogging: nfwProps['alert-dest'] || 'None',
logBucket: vpcProps.logBucket,
});
}
if (vpcConfig?.['alb-forwarding']) {
console.log('Deploying ALB forwarding');
const albipforward = new AlbIpForwarding(this, 'albIpForwarding', {
vpcId: this.vpcId,
ddbKmsKey: this.ddbKmsKey,
acceleratorPrefix: vpcProps.acceleratorPrefix || '',
});
console.log('ALB forwarding enabled');
} else {
console.log('alb ip forwarding not enabled. Skipping.');
}
// Add Routes to Route Tables
if (routeTablesProps) {
for (const routeTableProp of routeTablesProps) {
if (routeTableProp.name === 'default') {
continue;
}
const routeTableName = routeTableProp.name;
const routeTableObj = this.routeTableNameToIdMap[routeTableName];
if (!routeTableProp.routes?.find(r => r.target === 'IGW')) {
natRouteTables.push(routeTableProp.name);
}
// Add Routes to RouteTable
for (const route of routeTableProp.routes ? routeTableProp.routes : []) {
let dependsOn: cdk.CfnResource | undefined;
let gatewayId: string | undefined;
if (route.target === 'IGW') {
gatewayId = igw?.ref;
dependsOn = igwAttach;
} else if (route.target === 'VGW') {
gatewayId = vgw?.ref;
dependsOn = vgwAttach;
} else if (route.target.toLowerCase() === 's3') {
s3Routes.push(routeTableObj);
continue;
} else if (route.target.toLowerCase() === 'dynamodb') {
dynamoRoutes.push(routeTableObj);
continue;
} else if (route.target === 'TGW' && tgw && tgwAttachment) {
let constructName = `${routeTableName}_${route.target}`;
if (typeof route.destination !== 'string') {
console.warn(`Route for TGW only supports cidr as destination`);
continue;
}
if (route.destination !== '0.0.0.0/0') {
constructName = `${routeTableName}_${route.target}_${route.destination}`;
}
const tgwRoute = new ec2.CfnRoute(this, constructName, {
routeTableId: routeTableObj,
destinationCidrBlock: route.destination,
transitGatewayId: tgw.tgwId,
});
tgwRoute.addDependsOn(tgwAttachment.resource);
continue;
} else if (route.target.startsWith('NATGW_')) {
if (typeof route.destination !== 'string') {
console.warn(`Route for NATGW only supports cidr as destination`);
continue;
}
let constructName = `${routeTableName}_natgw_route`;
if (route.destination !== '0.0.0.0/0') {
constructName = `${routeTableName}_natgw_${route.destination}_route`;
}
const routeParams: ec2.CfnRouteProps = {
routeTableId: routeTableObj,
destinationCidrBlock: route.destination,
natGatewayId: this.natgwNameToIdMap[route.target.toLowerCase()],
};
new ec2.CfnRoute(this, constructName, routeParams);
continue;
} else {
// Need to add for different Routes
continue;
}
const params: ec2.CfnRouteProps = {
routeTableId: routeTableObj,
destinationCidrBlock: route.destination as string,
gatewayId,
};
const cfnRoute = new ec2.CfnRoute(this, `${routeTableName}_${route.target}`, params);
if (dependsOn) {
cfnRoute.addDependsOn(dependsOn);
}
}
}
}
// Create VPC Gateway End Point
const gatewayEndpoints = props.vpcProps.vpcConfig['gateway-endpoints'] || [];
for (const gwEndpointName of gatewayEndpoints) {
const gwService = new ec2.GatewayVpcEndpointAwsService(gwEndpointName.toLowerCase());
new ec2.CfnVPCEndpoint(this, `Endpoint_${gwEndpointName}`, {
serviceName: gwService.name,
vpcId: vpcObj.ref,
routeTableIds: gwEndpointName.toLocaleLowerCase() === 's3' ? s3Routes : dynamoRoutes,
});
}
// Create all security groups
if (vpcConfig['security-groups']) {
this.securityGroup = new SecurityGroup(this, `SecurityGroups-${vpcConfig.name}`, {
securityGroups: vpcConfig['security-groups'],
vpcName: vpcConfig.name,
vpcId: this.vpcId,
accountKey,
vpcConfigs: vpcConfigs!,
installerVersion,
vpcPools,
subnetPools,
});
}
// Share VPC subnet
new VpcSubnetSharing(this, 'Sharing', {
accountStacks,
accountKey,
accounts,
vpcConfig,
organizationalUnitName,
subnets: this.azSubnets,
limiter,
vpc: vpcObj,
});
const vpcSecurityGroup = new VpcDefaultSecurityGroup(this, 'VpcDefaultSecurityGroup', {
vpcId: this.vpcId,
acceleratorName,
});
vpcSecurityGroup.node.addDependency(vpcObj);
}