raindancers-network
Version:
Extensions to the ec2.Vpc Constructs
585 lines • 88.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Evpc = void 0;
const path = require("path");
const cdk = require("aws-cdk-lib");
const aws_cdk_lib_1 = require("aws-cdk-lib");
/**
*
* @param ip A ip address in dotted decimal format
* @returns an integer representing the ip address
*/
function ipToInt32(ip) {
const ipSplit = ip.split('.');
var ipInt = 0;
ipSplit.forEach((value, index) => {
var octect = value;
//ipInt += octect << (24-index*8);
ipInt += octect * Math.pow(2, (24 - index * 8));
});
return ipInt;
}
/**
* Extends the ec2.Vpc construct to provide additional functionality
* - support for using AWS IPAM
* - methods for integration
* - Flow logs and Athena Querys
* - Create and share 53 zones
*/
class Evpc extends aws_cdk_lib_1.aws_ec2.Vpc {
constructor(scope, id, props = {}) {
// make a copy of props as its properties are read Only. ( Jsii restriction )
// https://bobbyhadz.com/blog/typescript-change-readonly-to-mutable
const vpcProps = props;
// do some validation of props, for sanity
if (props.cidr && props.ipamPoolId) {
throw new Error('can only have one of cidr, or ipamPoolId');
}
// if using an IPAM pool, we will set the cidr to 0/0, so, network builder can build the subnets which we then later manipulate
if (props.ipamPoolId) {
vpcProps.cidr = `0.0.0.0/${props.netmaskLength}`;
}
super(scope, id, vpcProps);
//use escape hatches to remove the Cidr Block and set the Ipam Masks.
const CfnVPC = this.node.defaultChild;
CfnVPC.addPropertyDeletionOverride('CidrBlock');
CfnVPC.addPropertyOverride('Ipv4IpamPoolId', props.ipamPoolId);
CfnVPC.addPropertyOverride('Ipv4NetmaskLength', props.netmaskLength);
// Overide the subnets
[this.privateSubnets, this.isolatedSubnets, this.publicSubnets].forEach((subnetType) => {
subnetType.forEach((subnet) => {
var subnetInt = ipToInt32(subnet.ipv4CidrBlock.split('/')[0]);
var subnetMask = subnet.ipv4CidrBlock.split('/')[1];
var subnetPostion = subnetInt / 2 ** (32 - subnetMask);
var cfnSubnet = subnet.node.defaultChild;
cfnSubnet.addPropertyOverride('CidrBlock', cdk.Fn.select(subnetPostion, cdk.Fn.cidr(CfnVPC.attrCidrBlock, subnetPostion + 1, `${32 - subnetMask}`)));
}); // end of subnetType
}); // end of all subnets
// flow logs
if (!props.disableFlowlog) {
// write to a shared VPC Flow log, that is specificed in the cdk.context
const s3LogBucket = aws_cdk_lib_1.aws_s3.Bucket.fromBucketName(this, 'flowlogbucket', this.node.tryGetContext('flowlogbucket'));
// create a flow log for the vpc
const flowlog = new aws_cdk_lib_1.aws_ec2.FlowLog(this, 'VpcFlowLogs', {
destination: aws_cdk_lib_1.aws_ec2.FlowLogDestination.toS3(s3LogBucket, 'VpcFlowLogs', {
fileFormat: aws_cdk_lib_1.aws_ec2.FlowLogFileFormat.PARQUET,
hiveCompatiblePartitions: false,
perHourPartition: true,
}),
trafficType: aws_cdk_lib_1.aws_ec2.FlowLogTrafficType.ALL,
flowLogName: 'sharedVpcFlowLogs',
resourceType: aws_cdk_lib_1.aws_ec2.FlowLogResourceType.fromVpc(this),
});
// allow for more grandular flowlog at the minimum increment.
if (props.oneMinuteFlowLogs !== undefined) {
const CfnFlowLog = flowlog.node.defaultChild;
CfnFlowLog.addPropertyOverride('MaxAggregationInterval', 60);
}
// athena results bucket
const athenaResultsBucket = new aws_cdk_lib_1.aws_s3.Bucket(this, 'AthenaFlowLogResults', {
blockPublicAccess: aws_cdk_lib_1.aws_s3.BlockPublicAccess.BLOCK_ALL,
bucketName: `athena-${props.vpcName?.toLowerCase()}`,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
// set up athena querys with a custom resource
const athenaLogsHandler = new aws_cdk_lib_1.aws_lambda.Function(this, 'Function', {
code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(path.join(__dirname, '../../lambda/evpc')),
runtime: aws_cdk_lib_1.aws_lambda.Runtime.PYTHON_3_9,
handler: 'flowlogintegration.on_event',
});
athenaLogsHandler.addToRolePolicy(new aws_cdk_lib_1.aws_iam.PolicyStatement({
effect: aws_cdk_lib_1.aws_iam.Effect.ALLOW,
resources: ['*'],
actions: [
'*',
'ec2:ListCoreNetworks',
'ec2:GetFlowLogsIntegrationTemplate',
'cloudformation:CreateStack',
'cloudformation:DeleteStack',
],
}));
new cdk.CustomResource(this, 'MyCustomResource', {
serviceToken: new aws_cdk_lib_1.custom_resources.Provider(this, 'flowlogBuilderCR', {
onEventHandler: athenaLogsHandler,
}).serviceToken,
properties: {
AthenaBucket: athenaResultsBucket.bucketArn,
FlowLogId: flowlog.flowLogId,
VpcName: props.vpcName,
},
});
}
// if this is a VPC that we attach to the the core vpc, create a custom lookup to do lookups of the corewan
if (props.attachToCoreNetworkSegment) {
const lookupIdLambda = new aws_cdk_lib_1.aws_lambda.Function(this, 'lookupIdLambda-evpc', {
runtime: aws_cdk_lib_1.aws_lambda.Runtime.PYTHON_3_9,
handler: 'get_core_network_id.on_event',
code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(path.join(__dirname, '../../lambda/evpc')),
});
lookupIdLambda.addToRolePolicy(new aws_cdk_lib_1.aws_iam.PolicyStatement({
effect: aws_cdk_lib_1.aws_iam.Effect.ALLOW,
resources: ['*'],
actions: ['networkmanager:ListCoreNetworks'],
}));
const networkManagerProvider = new aws_cdk_lib_1.custom_resources.Provider(this, 'NetworkManagerProvider', {
onEventHandler: lookupIdLambda,
});
this.lookUpProvider = networkManagerProvider;
}
// create an internal zone for the vpc
if (props.r53InternalZoneName) {
// create a new internal zone
this.privateR53Zone = new aws_cdk_lib_1.aws_route53.PrivateHostedZone(this, 'privatednszone', {
zoneName: props.r53InternalZoneName,
vpc: this,
});
this.privateR53ZoneId = this.privateR53Zone.hostedZoneId;
}
// if this is the centralResolvingV VPC we dont' need to associate this zone with itself.
// This needs completing.
this.centralResolvingVpc = false;
if (props.centralResolvingVpc === true) {
this.centralResolvingVpc = true;
}
}
; // end of constructor
/**
* Attach the VPC to a cloud wan segment
* @param coreNetworkName
* @param segment
* @returns transport attachment id
*/
attachToCloudWan(coreNetworkName, segment) {
// get the parameters from the core.
const coreNetwork = new cdk.CustomResource(this, 'idfinderCR', {
serviceToken: this.lookUpProvider.serviceToken,
properties: {
CoreNetworkName: coreNetworkName,
},
});
const linknetsubnetarns = [];
const linknetSelection = this.selectSubnets({ subnetGroupName: 'linknet' });
for (const subnet of linknetSelection.subnets) {
linknetsubnetarns.push(`arn:aws:ec2:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:subnet/${subnet.subnetId}`);
}
const transportAttachmentId = new aws_cdk_lib_1.aws_networkmanager.CfnVpcAttachment(this, 'attachtowan', {
coreNetworkId: coreNetwork.getAtt('CoreNetworkId'),
subnetArns: linknetsubnetarns,
tags: [
{
key: 'NetworkSegment',
value: segment,
},
],
vpcArn: this.vpcArn,
});
return transportAttachmentId.attrAttachmentId;
} // end of attachToCloudWan
/**
* Add a route to routing tables attached to the private subnets.
* @param destinationCidr cidr eg, 0.0.0.0/0
* @param coreNetworkId
*/
addRouteForPrivateSubnetstoCloudWan(destinationCidr, coreNetworkId) {
var subnetSelection = [];
try {
const subnetSelectionPrivateIsolated = this.selectSubnets({ subnetType: aws_cdk_lib_1.aws_ec2.SubnetType.PRIVATE_ISOLATED });
subnetSelection = [...subnetSelection, ...subnetSelectionPrivateIsolated.subnets];
}
catch (error) {
console.log('there are no Private Subnets');
}
try {
const subnetSelectionPrivateIsolated = this.selectSubnets({ subnetType: aws_cdk_lib_1.aws_ec2.SubnetType.PRIVATE_WITH_EGRESS });
subnetSelection = [...subnetSelection, ...subnetSelectionPrivateIsolated.subnets];
}
catch (error) {
console.log('there are noSubnets with Egress');
}
for (const subnet of subnetSelection) {
// there is no cloudformation method for a cloudwan yet!
const addRoutev4SdkCall = {
service: 'EC2',
action: 'createRoute',
parameters: {
CoreNetworkArn: `arn:aws:networkmanager::${this.node.tryGetContext('centralAccount')}:core-network/${coreNetworkId}`,
RouteTableId: subnet.routeTable.routeTableId,
DestinationCidrBlock: destinationCidr,
},
region: cdk.Aws.REGION,
physicalResourceId: aws_cdk_lib_1.custom_resources.PhysicalResourceId.of(subnet.routeTable.routeTableId + '-cloudwan-route'),
};
const deleteRoutev4SdkCall = {
service: 'EC2',
action: 'deleteRoute',
parameters: {
RouteTableId: subnet.routeTable.routeTableId,
DestinationCidrBlock: destinationCidr,
},
region: cdk.Aws.REGION,
physicalResourceId: aws_cdk_lib_1.custom_resources.PhysicalResourceId.of(subnet.routeTable.routeTableId + '-cloudwan-route'),
};
new aws_cdk_lib_1.custom_resources.AwsCustomResource(this, `${subnet.node.id}${destinationCidr}-v4route`, {
onCreate: addRoutev4SdkCall,
onUpdate: addRoutev4SdkCall,
onDelete: deleteRoutev4SdkCall,
logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.SEVEN_YEARS,
policy: aws_cdk_lib_1.custom_resources.AwsCustomResourcePolicy.fromSdkCalls({
resources: aws_cdk_lib_1.custom_resources.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
}
}
; // addRouteForPrivateSubnetstoCloudWan
/**
* Add routes to point at Network Firewalls, for specific subnetGroups.
* this will place routes on a per AZ basis
*
* @param destinationCidr
* @param subnetgroup
* @param fwArn
*/
addRoutetoFirewall(destinationCidr, subnetgroup, fwArn) {
/**
* the reponse from the API call, exceeds 4096 bytes, so need to limit it with Output Paths
*/
const outputPaths = [];
const azlist = cdk.Stack.of(this).availabilityZones;
azlist.forEach((az) => {
outputPaths.push(`FirewallStatus.SyncStates.${az}.Attachment.EndpointId`);
});
const getfwendpoint = {
service: 'NetworkFirewall',
action: 'describeFirewall',
parameters: {
FirewallArn: fwArn,
},
region: cdk.Aws.REGION,
physicalResourceId: aws_cdk_lib_1.custom_resources.PhysicalResourceId.of('DescribeFirewall'),
outputPaths: outputPaths,
};
const fwDescription = new aws_cdk_lib_1.custom_resources.AwsCustomResource(this, `DescribeFirewall${subnetgroup}`, {
onCreate: getfwendpoint,
logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.SEVEN_YEARS,
policy: aws_cdk_lib_1.custom_resources.AwsCustomResourcePolicy.fromSdkCalls({
resources: aws_cdk_lib_1.custom_resources.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
this.selectSubnets({ subnetGroupName: subnetgroup }).subnets.forEach(subnet => {
new aws_cdk_lib_1.aws_ec2.CfnRoute(this, 'FirewallRoute-' + subnet.node.path.split('/').pop(), {
destinationCidrBlock: destinationCidr,
routeTableId: subnet.routeTable.routeTableId,
vpcEndpointId: fwDescription.getResponseField(`FirewallStatus.SyncStates.${subnet.availabilityZone}.Attachment.EndpointId`),
});
});
}
; // end of addRoutetoFirewall
/**
* Add routes to routing tables associated with publicSubnets to Cloudwan
* @param destinationCidr
* @param coreNetworkId
*/
addRouteForPublicSubnetstoCloudWan(destinationCidr, coreNetworkId) {
const subnetSelection = this.selectSubnets({ subnetType: aws_cdk_lib_1.aws_ec2.SubnetType.PUBLIC });
for (const subnet of subnetSelection.subnets) {
// there is no cloudformation method for a cloudwan yet!
const addRoutev4SdkCall = {
service: 'EC2',
action: 'createRoute',
parameters: {
CoreNetworkArn: `arn:aws:networkmanager::${this.node.tryGetContext('centralAccount')}:core-network/${coreNetworkId}`,
RouteTableId: subnet.routeTable.routeTableId,
DestinationCidrBlock: destinationCidr,
},
region: cdk.Aws.REGION,
physicalResourceId: aws_cdk_lib_1.custom_resources.PhysicalResourceId.of(subnet.routeTable.routeTableId + '-cloudwan-route'),
};
const deleteRoutev4SdkCall = {
service: 'EC2',
action: 'deleteRoute',
parameters: {
RouteTableId: subnet.routeTable.routeTableId,
DestinationCidrBlock: destinationCidr,
},
region: cdk.Aws.REGION,
physicalResourceId: aws_cdk_lib_1.custom_resources.PhysicalResourceId.of(subnet.routeTable.routeTableId + '-cloudwan-route'),
};
new aws_cdk_lib_1.custom_resources.AwsCustomResource(this, `${subnet.node.id}${destinationCidr}-v4route`, {
onCreate: addRoutev4SdkCall,
onUpdate: addRoutev4SdkCall,
onDelete: deleteRoutev4SdkCall,
logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.SEVEN_YEARS,
policy: aws_cdk_lib_1.custom_resources.AwsCustomResourcePolicy.fromSdkCalls({
resources: aws_cdk_lib_1.custom_resources.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
}
}
; // end of addRouteForPublicSubnetstoCloudWan
/**
* Create a connect Attachment to Cloudwan for Appliances
* @param coreNetworkId
* @param transportAttachmentId
* @returns
*/
createConnectAttachment(coreNetworkId, transportAttachmentId) {
const connectAttachment = new aws_cdk_lib_1.aws_networkmanager.CfnConnectAttachment(this, 'connectattachment', {
coreNetworkId: coreNetworkId,
edgeLocation: cdk.Aws.REGION,
options: {
protocol: 'GRE',
},
transportAttachmentId: transportAttachmentId,
});
return connectAttachment.attrAttachmentId;
}
; // end of createConnectAttachment
/**
* Associate any rules shared to this vpc
* @param owner
* @param updatetopic
*/
associateSharedRoute53ResolverRules(owner, updatetopic) {
const associateRouteResolvers = new aws_cdk_lib_1.aws_lambda.Function(this, 'associateLambda', {
runtime: aws_cdk_lib_1.aws_lambda.Runtime.PYTHON_3_9,
handler: 'getRouteResolvers.on_event',
code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(path.join(__dirname, '../../lambda/evpc')),
});
associateRouteResolvers.addToRolePolicy(new aws_cdk_lib_1.aws_iam.PolicyStatement({
effect: aws_cdk_lib_1.aws_iam.Effect.ALLOW,
resources: ['*'],
actions: [
'route53resolver:*',
'route53resolver:ListResolverRules',
'route53resolver:AssociateResolverRule',
'route53resolver:DisassociateResolverRule',
'*',
],
}));
const rRProvider = new aws_cdk_lib_1.custom_resources.Provider(this, 'rRProvider', {
onEventHandler: associateRouteResolvers,
logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.SEVEN_YEARS,
});
new cdk.CustomResource(this, 'AssociateRouteResolverRules', {
serviceToken: rRProvider.serviceToken,
properties: {
vpcId: this.vpcId,
owner: owner,
},
});
const deadLetterQueue = new aws_cdk_lib_1.aws_sqs.Queue(this, 'deadLetterQueue');
if (updatetopic !== undefined) {
associateRouteResolvers.addEventSource(new aws_cdk_lib_1.aws_lambda_event_sources.SnsEventSource(updatetopic, {
filterPolicy: {},
deadLetterQueue: deadLetterQueue,
}));
}
} // end of associateSharedRouteResolverRules
/**
* Associate the internal R53 Zone with the Central VPC, for Org wide resolution
*/
associateVPCZonewithCentralVPC() {
const fn = aws_cdk_lib_1.aws_lambda.Function.fromFunctionAttributes(this, 'Function', {
functionArn: `arn:aws:lambda:${cdk.Stack.of(this).region}:${this.node.tryGetContext('centralAccount')}:function:${this.node.tryGetContext('CreateAuthforZoneLambdaName')}`,
sameEnvironment: false,
skipPermissions: true,
});
const localVPCZoneProvider = new aws_cdk_lib_1.custom_resources.Provider(this, 'localvpcssnProvider', {
onEventHandler: fn,
});
// get an authorization to associate the zone with the tiritahi vpc
const zoneAssn = new cdk.CustomResource(this, 'AuthZone', {
serviceToken: localVPCZoneProvider.serviceToken,
properties: {
PrivateZoneId: this.privateR53ZoneId,
},
});
// Associate this zone with the central VPC
const associateVpcWithHostedZone = {
service: 'Route53',
action: 'associateVPCWithHostedZone',
parameters: {
HostedZoneId: this.privateR53ZoneId,
VPC: {
VPCId: zoneAssn.getAtt('VpcId'),
VPCregion: zoneAssn.getAtt('VpcRegion'),
},
},
region: cdk.Aws.REGION,
physicalResourceId: aws_cdk_lib_1.custom_resources.PhysicalResourceId.of('InboundRouteResolverIP'),
};
const disassociateVpcWithHostedZone = {
service: 'Route53',
action: 'disassociateVPCFromHostedZone',
parameters: {
HostedZoneId: this.privateR53ZoneId,
VPC: {
VPCId: zoneAssn.getAtt('VpcId'),
VPCregion: zoneAssn.getAtt('VpcRegion'),
},
},
region: cdk.Aws.REGION,
physicalResourceId: aws_cdk_lib_1.custom_resources.PhysicalResourceId.of('InboundRouteResolverIP'),
};
new aws_cdk_lib_1.custom_resources.AwsCustomResource(this, 'AssociateVpcwithHostedZone', {
onCreate: associateVpcWithHostedZone,
onDelete: disassociateVpcWithHostedZone,
logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.SEVEN_YEARS,
policy: aws_cdk_lib_1.custom_resources.AwsCustomResourcePolicy.fromSdkCalls({
resources: aws_cdk_lib_1.custom_resources.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
} // end of associateVPCZonewithCentralVPC()
/**
* Add routes in private Subnets to a instance. Use this for routing to a network appliance.
* @param destinationCidr
* @param instanceId
*/
addRouteForPrivateSubnetstoinstance(destinationCidr, instanceId) {
const subnetSelection = this.selectSubnets({ subnetType: aws_cdk_lib_1.aws_ec2.SubnetType.PRIVATE_ISOLATED });
for (const subnet of subnetSelection.subnets) {
new aws_cdk_lib_1.aws_ec2.CfnRoute(this, 'instanceRoute-' + subnet.node.path.split('/').pop(), {
destinationCidrBlock: destinationCidr,
routeTableId: subnet.routeTable.routeTableId,
instanceId: instanceId,
});
}
}
; // end of Private Routes.
/**
* Add routes for Private Subnets to a Transit Gateway
* @param destinationCidr
* @param TransitGatewayId
*/
addRouteForPrivateSubnetstoTransitGateway(destinationCidr, TransitGatewayId) {
var subnetSelection = [];
try {
const subnetSelectionPrivateIsolated = this.selectSubnets({ subnetType: aws_cdk_lib_1.aws_ec2.SubnetType.PRIVATE_ISOLATED });
subnetSelection = [...subnetSelection, ...subnetSelectionPrivateIsolated.subnets];
}
catch (error) {
// statements to handle any exceptions
//console.error(error); // pass exception object to error handler
console.log('there are not Private Subnets');
}
try {
const subnetSelectionPrivateIsolated = this.selectSubnets({ subnetType: aws_cdk_lib_1.aws_ec2.SubnetType.PRIVATE_WITH_EGRESS });
subnetSelection = [...subnetSelection, ...subnetSelectionPrivateIsolated.subnets];
}
catch (error) {
// statements to handle any exceptions
//console.error(error); // pass exception object to error handler
console.log('there are not Private Subnets with Egress');
}
for (const subnet of subnetSelection) {
new aws_cdk_lib_1.aws_ec2.CfnRoute(this, `Route${subnet.subnetId}${destinationCidr}`, {
routeTableId: subnet.routeTable.routeTableId,
destinationCidrBlock: destinationCidr,
transitGatewayId: TransitGatewayId,
});
}
} // end of privatesubnets to TransitGateway
/**
* Attach a VPC to a Transit Gateway in Appliance mode. Primarly used when the VPC is being used as a centralised egress with firewalls
* A workaround to the problem of their not being support for Appliance mode connections to cloudwan
* @param transitGateway
* @param cidrs
* @returns
*/
attachVpcToTGApplianceMode(transitGateway, cidrs) {
// get the TG's default routing table, as the attribute is not workign
const getDefaultRoutingTableId = new aws_cdk_lib_1.custom_resources.AwsCustomResource(this, 'defaultroutingtableId', {
onCreate: {
service: 'EC2',
action: 'describeTransitGateways',
parameters: {
TransitGatewayIds: [transitGateway.attrId],
},
physicalResourceId: aws_cdk_lib_1.custom_resources.PhysicalResourceId.of('TransitGateways.0.Options.AssociationDefaultRouteTableId'),
},
policy: aws_cdk_lib_1.custom_resources.AwsCustomResourcePolicy.fromSdkCalls({
resources: aws_cdk_lib_1.custom_resources.AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
const routingtableId = getDefaultRoutingTableId.getResponseField('TransitGateways.0.Options.AssociationDefaultRouteTableId');
const transitGatewaypeering = new aws_cdk_lib_1.custom_resources.AwsCustomResource(this, 'AttachtheVPCtoTG', {
onCreate: {
service: 'EC2',
action: 'createTransitGatewayVpcAttachment',
parameters: {
SubnetIds: this.selectSubnets({ subnetGroupName: 'linknet' }).subnetIds,
TransitGatewayId: transitGateway.attrId,
VpcId: this.vpcId,
Options: {
ApplianceModeSupport: 'enable',
},
},
physicalResourceId: aws_cdk_lib_1.custom_resources.PhysicalResourceId.fromResponse('TransitGatewayVpcAttachment.TransitGatewayAttachmentId'),
region: 'ap-southeast-2',
},
onDelete: {
service: 'EC2',
action: 'deleteTransitGatewayVpcAttachment',
parameters: {
TransitGatewayAttachmentId: new aws_cdk_lib_1.custom_resources.PhysicalResourceIdReference(),
},
region: 'ap-southeast-2',
},
logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.ONE_DAY,
policy: aws_cdk_lib_1.custom_resources.AwsCustomResourcePolicy.fromStatements([
new aws_cdk_lib_1.aws_iam.PolicyStatement({
actions: ['*'],
resources: ['*'],
}),
]),
});
if (cidrs !== undefined) {
// need to implment a check here to see if the attachment is in valid state to use.
const waitForTransitGatewayPeering = new aws_cdk_lib_1.aws_lambda.Function(this, 'oneventttobeready', {
runtime: aws_cdk_lib_1.aws_lambda.Runtime.PYTHON_3_9,
handler: 'transitgatewayRoutes.on_event',
code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(path.join(__dirname, '../../lambda/evpc')),
});
// need to be able to delete the routes.
waitForTransitGatewayPeering.addToRolePolicy(new aws_cdk_lib_1.aws_iam.PolicyStatement({
actions: ['ec2:deleteTransitGatewayRoute'],
resources: ['*'],
effect: aws_cdk_lib_1.aws_iam.Effect.ALLOW,
}));
const isCompleteTransitGatewayPeering = new aws_cdk_lib_1.aws_lambda.Function(this, 'iscompleterattachmenttobeready', {
runtime: aws_cdk_lib_1.aws_lambda.Runtime.PYTHON_3_9,
handler: 'transitgatewayRoutes.is_complete',
code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(path.join(__dirname, '../../lambda/evpc')),
});
// need to be able to check if the attachment is done, and if it is add routes.
isCompleteTransitGatewayPeering.addToRolePolicy(new aws_cdk_lib_1.aws_iam.PolicyStatement({
actions: [
'ec2:createTransitGatewayRoute',
'ec2:describeTransitGatewayVpcAttachments',
],
resources: ['*'],
effect: aws_cdk_lib_1.aws_iam.Effect.ALLOW,
}));
const tgReady = new aws_cdk_lib_1.custom_resources.Provider(this, 'tgReady', {
onEventHandler: waitForTransitGatewayPeering,
isCompleteHandler: isCompleteTransitGatewayPeering,
logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.ONE_WEEK,
queryInterval: cdk.Duration.seconds(10),
});
// create a resource for each route.. rather than pass the list of routes
cidrs.forEach((cidr) => {
new cdk.CustomResource(this, `WaitforTGCR${cidr}`, {
serviceToken: tgReady.serviceToken,
properties: {
transitGatewayAttachmentId: transitGatewaypeering.getResponseField('TransitGatewayVpcAttachment.TransitGatewayAttachmentId'),
cidr: cidr,
transitGatewayRouteTableId: routingtableId,
},
});
});
} // end of adding transitgateway routes.
return transitGatewaypeering.getResponseField('TransitGatewayVpcAttachment.TransitGatewayAttachmentId');
} // end attachVpcToTGApplianceMode
}
exports.Evpc = Evpc;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXZwYy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9ldnBjL2V2cGMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsNkJBQTZCO0FBQzdCLG1DQUFtQztBQUNuQyw2Q0FhcUI7QUFNckI7Ozs7R0FJRztBQUVILFNBQVMsU0FBUyxDQUFDLEVBQVU7SUFDM0IsTUFBTSxPQUFPLEdBQUcsRUFBRSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUM5QixJQUFJLEtBQUssR0FBVyxDQUFDLENBQUM7SUFDdEIsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLEtBQUssRUFBRSxLQUFLLEVBQUUsRUFBRTtRQUMvQixJQUFJLE1BQU0sR0FBVyxLQUEwQixDQUFDO1FBQ2hELGtDQUFrQztRQUNsQyxLQUFLLElBQUksTUFBTSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxHQUFDLEtBQUssR0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzlDLENBQUMsQ0FBQyxDQUFDO0lBQ0gsT0FBTyxLQUFLLENBQUM7QUFDZixDQUFDO0FBcUREOzs7Ozs7R0FNRztBQUNILE1BQWEsSUFBSyxTQUFRLHFCQUFHLENBQUMsR0FBRztJQThCL0IsWUFBWSxLQUEyQixFQUFFLEVBQVUsRUFBRSxRQUFtQixFQUFFO1FBR3hFLDZFQUE2RTtRQUM3RSxtRUFBbUU7UUFLdEUsTUFBTSxRQUFRLEdBQUcsS0FBMkIsQ0FBQztRQUc3QywwQ0FBMEM7UUFDMUMsSUFBSSxLQUFLLENBQUMsSUFBSSxJQUFJLEtBQUssQ0FBQyxVQUFVLEVBQUU7WUFDbEMsTUFBTSxJQUFJLEtBQUssQ0FBQywwQ0FBMEMsQ0FBQyxDQUFDO1NBRTdEO1FBQ0QsK0hBQStIO1FBQy9ILElBQUksS0FBSyxDQUFDLFVBQVUsRUFBRTtZQUVwQixRQUFRLENBQUMsSUFBSSxHQUFHLFdBQVcsS0FBSyxDQUFDLGFBQWEsRUFBRSxDQUFDO1NBQ2xEO1FB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