UNPKG

raindancers-network

Version:
780 lines 122 kB
"use strict"; var _a, _b; Object.defineProperty(exports, "__esModule", { value: true }); exports.EnterpriseVpc = exports.Destination = exports.ApplianceMode = exports.SubnetWildCards = exports.SubnetGroup = void 0; const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti"); const path = require("path"); const cdk = require("aws-cdk-lib"); const aws_cdk_lib_1 = require("aws-cdk-lib"); const constructs = require("constructs"); const enterprisevpclambdas_1 = require("./enterprisevpclambdas"); const dnsfirewall_1 = require("../dns/dnsfirewall"); const dnsResolvers_1 = require("../dns/dnsResolvers"); const enterpriseZone_1 = require("../dns/enterpriseZone"); const forwardingRules_1 = require("../dns/forwardingRules"); const resolverRules_1 = require("../dns/resolverRules"); const awsServiceEndpoints_1 = require("../endpoints/awsServiceEndpoints"); const firewall_1 = require("../nwfirewall/firewall"); class SubnetGroup extends constructs.Construct { constructor(scope, id, props) { super(scope, id); const mysubnet = { name: props.name, subnetType: props.subnetType, cidrMask: props.cidrMask, }; this.subnet = mysubnet; } } exports.SubnetGroup = SubnetGroup; _a = JSII_RTTI_SYMBOL_1; SubnetGroup[_a] = { fqn: "raindancers-network.network.SubnetGroup", version: "1.29.3" }; var SubnetWildCards; (function (SubnetWildCards) { SubnetWildCards["ALLSUBNETS"] = "ALLSUBNETS"; })(SubnetWildCards = exports.SubnetWildCards || (exports.SubnetWildCards = {})); /** * Propertys for Appliance Mode */ var ApplianceMode; (function (ApplianceMode) { /** enable Connecting VPC to TransitGateway in Appliance Mode */ ApplianceMode["ENABLED"] = "enable"; })(ApplianceMode = exports.ApplianceMode || (exports.ApplianceMode = {})); /** * The Destinations for Adding Routes */ var Destination; (function (Destination) { /** route to the cloudwan that the vpc is attached to */ Destination["CLOUDWAN"] = "Cloudwan"; /** route to the transitGateway that the vpc is attached to */ Destination["TRANSITGATEWAY"] = "TransitGateway"; //** route to a gateway loadbalancer end point */ Destination["NWFIREWALL"] = "NetworkFirewall"; })(Destination = exports.Destination || (exports.Destination = {})); /** * Enteprise VPC's take the stock ec2.Vpc and provide numerous convience methods primarly related to * connecting to internal networks */ class EnterpriseVpc extends constructs.Construct { /** * * @param scope * @param id * @param props */ constructor(scope, id, props) { super(scope, id); this.subnetConfiguration = []; if (props.vpc && props.evpc) { throw new Error('Can not have both vpc and evpc defined'); } if (props.vpc) { this.vpc = props.vpc; } else { // this is an evpc configuration.. // if (!(props.evpc?.subnetGroups)) { // throw new Error('An Evpc must have a subnetGroup property'); // } this.vpc = new aws_cdk_lib_1.aws_ec2.Vpc(this, 'evpc', { ...props.evpc, }); } const crHandlders = new enterprisevpclambdas_1.EnterpriseVpcLambda(this, 'vpclambda'); this.addRoutesProvider = crHandlders.addRoutesProvider; this.tgWaiterProvider = crHandlders.tgWaiterProvider; this.attachToCloudwanProvider = crHandlders.attachToCloudwanProvider; } associateSharedResolverRules(domainNames) { new forwardingRules_1.AssociateSharedResolverRule(this, 'r53associationRules', { domainNames: domainNames, vpc: this.vpc, }); } createAndAttachR53EnterprizeZone(props) { const zone = new enterpriseZone_1.EnterpriseZone(this, `EnterpriseZone${props.domainname}`, { enterpriseDomainName: props.domainname, localVpc: this.vpc, hubVpcs: props.hubVpcs, }); return zone.privateZone; } createAndAttachR53PrivateZone(zoneName) { return new aws_cdk_lib_1.aws_route53.PrivateHostedZone(this, `privatezone${zoneName}`, { zoneName: zoneName, vpc: this.vpc, }); } attachAWSManagedDNSFirewallRules() { const awsManagedDNSFirewallGroup = new dnsfirewall_1.AwsManagedDNSFirewallRuleGroup(this, 'ManagedFirewallRuleGroup'); // The creation of the firewall Rule Group is aysyncrhonous, and it is possible that it will not be ready // to associate immediately after it is created, despite Cloudformation thinking that it is complete // A custom resource with a iscomplete handler is used to create a waiter. const checkDNSFirewallRuleGroupIsReadyFn = new aws_cdk_lib_1.aws_lambda.Function(this, 'checkDNSFirewallRuleGroupIsReadyFn', { runtime: aws_cdk_lib_1.aws_lambda.Runtime.PYTHON_3_9, logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.ONE_MONTH, handler: 'checkDNSFirewallRuleGroupIsReadyFn.on_event', code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(path.join(__dirname, '../../lambda/dns')), timeout: cdk.Duration.seconds(899), }); const checkDNSFirewallRuleGroupisCompleteFn = new aws_cdk_lib_1.aws_lambda.Function(this, 'checkDNSFirewallRuleGroupIsCompleteFn', { runtime: aws_cdk_lib_1.aws_lambda.Runtime.PYTHON_3_9, logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.ONE_MONTH, handler: 'checkDNSFirewallRuleGroupIsReadyFn.is_complete', code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(path.join(__dirname, '../../lambda/dns')), timeout: cdk.Duration.seconds(899), }); // aws route53resolver get-firewall-rule-group checkDNSFirewallRuleGroupisCompleteFn.addToRolePolicy(new aws_cdk_lib_1.aws_iam.PolicyStatement({ actions: ['route53resolver:GetFirewallRuleGroup'], effect: aws_cdk_lib_1.aws_iam.Effect.ALLOW, resources: ['*'], })); const checkDNSFirewallRuleGroupIsReadyCr = new cdk.CustomResource(this, 'checkDNSFirewallRuleGroupIsReadyCr', { resourceType: 'Custom::AssociateInternalZone', properties: { ResolverRuleId: awsManagedDNSFirewallGroup.resolverRuleId, }, serviceToken: new aws_cdk_lib_1.custom_resources.Provider(this, 'checkDNSFirewallRuleGroupIsReadyProvider', { onEventHandler: checkDNSFirewallRuleGroupIsReadyFn, isCompleteHandler: checkDNSFirewallRuleGroupisCompleteFn, totalTimeout: cdk.Duration.minutes(119), queryInterval: cdk.Duration.seconds(10), logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.TWO_YEARS, providerFunctionName: cdk.PhysicalName.GENERATE_IF_NEEDED, }).serviceToken, }); const assn = new aws_cdk_lib_1.aws_route53resolver.CfnResolverRuleAssociation(this, 'DNSFirewallWallAssociation', { resolverRuleId: awsManagedDNSFirewallGroup.resolverRuleId, vpcId: this.vpc.vpcId, }); // Cloudformation has no way to know that the Association can not happen untill the Is ready check is done. // so a dependency needs to be added assn.node.addDependency(checkDNSFirewallRuleGroupIsReadyCr); } /** * Add a collection of service endpopints to the VPC * @param props */ addServiceEndpoints(props) { new awsServiceEndpoints_1.AwsServiceEndPoints(this, 'AWSEndpoints', { services: props.services, subnetGroup: props.subnetGroup.subnet.name, vpc: this.vpc, dynamoDBGatewayInterface: props.dynamoDbGateway, s3GatewayInterface: props.s3GatewayInterface, }); } addNetworkFirewall(firewallName, firewallPolicy, subnet) { const nwFirewall = new firewall_1.NetworkFirewall(this, 'Aparua', { firewallName: firewallName, firewallPolicy: firewallPolicy, subnetGroup: subnet.subnet.name, vpc: this.vpc, }); this.firewallArn = nwFirewall.firewallArn; } addPrivateHostedZone(zonename) { const zone = new aws_cdk_lib_1.aws_route53.PrivateHostedZone(this, `privateZone${zonename}`, { zoneName: zonename, vpc: this.vpc, }); return zone; } addR53Resolvers(subnet) { const r53Resolvers = new dnsResolvers_1.R53Resolverendpoints(this, 'RouteResolvers', { vpc: this.vpc, subnetGroup: subnet.subnet.name, }); this.r53endpointResolvers = r53Resolvers; return r53Resolvers; } addCentralResolverRules(domains, searchTag) { new resolverRules_1.CentralResolverRules(this, 'centralResolverRules', { domains: domains, resolvers: this.r53endpointResolvers, vpc: this.vpc, vpcSearchTag: searchTag, }); } addConditionalFowardingRules(forwardingRules) { new dnsResolvers_1.ConditionalForwarder(this, 'ConditionalForwarder', { outboundResolver: this.r53endpointResolvers?.outboundResolver, vpc: this.vpc, inboundResolverTargets: this.r53endpointResolvers?.inboundResolversIp, forwardingRules: forwardingRules, }); } addCrossAccountR53AssociationRole(rolename) { new enterpriseZone_1.CentralAccountAssnRole(this, 'assnRole', { vpc: this.vpc, orgId: this.node.tryGetContext('orgId'), roleName: rolename, }); } /** * This is a convience method to present the routing for the Vpc in a simpler format, * than the addRoutes Method, which it calls. * @param routerGroups */ router(routerGroups) { // Extract all the subnets, these will be tokens let allSubnetGroups = []; routerGroups.forEach((routerGroup) => { allSubnetGroups.push(routerGroup.subnetGroup); }); routerGroups.forEach((routerGroup) => { routerGroup.routes.forEach((route) => { // Sanity Check the inputs // only one of cidr or subnet can be supplied if (!(route.cidr || route.subnet)) { throw new Error('Only one of cidr or subnet can be supplied'); } let routecidr = []; // if a subnet is supplied, it can be be a SubnetWildCard or a subnet. // if its a cidr, add it to the cidrs if (route.cidr) { routecidr.push(route.cidr); // it must be a subnet route } else { // if the wild card is used; we need to create routes for all subnets, except // for the subnet where this is being routed from. if (route.subnet === SubnetWildCards.ALLSUBNETS) { // remove the calling subnet from the wildcard list let subnetsNotMe = allSubnetGroups; subnetsNotMe.splice(subnetsNotMe.findIndex(item => item === routerGroup.subnetGroup)); // get the subnetIds let subnets = []; allSubnetGroups.forEach((subnetGroup) => { subnets.concat(this.vpc.selectSubnets({ subnetGroupName: subnetGroup.subnet.name }).subnets); }); // allSubnetCidrs is a list of subnets, from which to create routes let allSubnetCidrs = []; subnets.forEach((subnet) => { allSubnetCidrs.push(subnet.ipv4CidrBlock); }); routecidr.concat(allSubnetCidrs); // if the subnets are supplied } else { let subnets = []; subnets = this.vpc.selectSubnets({ subnetGroupName: route.subnet?.subnet.name }).subnets; let subnetGroupCidrs = []; subnets.forEach((subnet) => { subnetGroupCidrs.push(subnet.ipv4CidrBlock); }); routecidr.concat(subnetGroupCidrs); } } // .addRoutes takes a list of cidrs, and will deal to them so that traffic reamins symetric. this.addRoutes({ cidr: routecidr, description: route.description, subnetGroups: [routerGroup.subnetGroup.subnet.name], destination: route.destination, }); }); }); } createAndShareSubnetPrefixList(name, subnets, orgArn) { let cidrs = []; this.vpc.selectSubnets(subnets).subnets.forEach((subnet) => { //const subnet = ec2.Subnet.fromSubnetId(this, `subnet${subnetId}`, subnetId); cidrs.push({ cidr: subnet.ipv4CidrBlock }); }); const prefixList = new aws_cdk_lib_1.aws_ec2.CfnPrefixList(this, `${name}`, { addressFamily: 'IPv4', maxEntries: cidrs.length, prefixListName: name, entries: cidrs, tags: [{ key: 'PrefixListName', value: name, }], }); new aws_cdk_lib_1.aws_ram.CfnResourceShare(this, `${name}shareprefix`, { name: `${name}PLShare`, allowExternalPrincipals: false, principals: [orgArn], resourceArns: [prefixList.attrArn], tags: [{ key: 'PrefixListName', value: name, }], }); return prefixList; } ; /** * Create Enterprise VPC Flow Logs (to central log account) and advanced diagnostics with Athena Querys * @param props */ createFlowLog(props) { const flowlog = new aws_cdk_lib_1.aws_ec2.FlowLog(this, 'VpcFlowLogs', { destination: aws_cdk_lib_1.aws_ec2.FlowLogDestination.toS3(props.bucket, '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.vpc), }); // allow for more grandular flowlog at the minimum increment. if (props.oneMinuteFlowLogs === true) { const CfnFlowLog = flowlog.node.defaultChild; CfnFlowLog.addPropertyOverride('MaxAggregationInterval', 60); } if (props.localAthenaQuerys === true) { const athenaResultsBucket = new aws_cdk_lib_1.aws_s3.Bucket(this, 'AthenaFlowLogResults', { blockPublicAccess: aws_cdk_lib_1.aws_s3.BlockPublicAccess.BLOCK_ALL, removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, }); // 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', timeout: cdk.Duration.seconds(300), }); 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: this.vpc.vpcId, }, }); } } //end of createFlowLog /** * attachToCloudWan will attach a VPC to CloudWan, in a particular Segment. * @param props */ attachToCloudWan(props) { this.cloudWanName = props.coreNetworkName; // get the coreNetwork Id from the name provided 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'], })); // check to see if the coreNetwork is ready. const isReadyFn = new aws_cdk_lib_1.aws_lambda.Function(this, 'isreadyFn', { runtime: aws_cdk_lib_1.aws_lambda.Runtime.PYTHON_3_9, handler: 'get_core_network_id.is_complete', code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(path.join(__dirname, '../../lambda/evpc')), }); isReadyFn.addToRolePolicy(new aws_cdk_lib_1.aws_iam.PolicyStatement({ effect: aws_cdk_lib_1.aws_iam.Effect.ALLOW, resources: ['*'], actions: [ 'networkmanager:GetCoreNetwork', 'networkmanager:ListCoreNetworks', ], })); const networkManagerProvider = new aws_cdk_lib_1.custom_resources.Provider(this, 'NetworkManagerProvider', { onEventHandler: lookupIdLambda, isCompleteHandler: isReadyFn, queryInterval: cdk.Duration.seconds(15), totalTimeout: cdk.Duration.minutes(119), logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.TWO_YEARS, }); const coreNetwork = new cdk.CustomResource(this, 'idfinderCR', { serviceToken: networkManagerProvider.serviceToken, properties: { CoreNetworkName: props.coreNetworkName, }, }); this.cloudWanCoreId = coreNetwork.getAtt('CoreNetworkId'); // find the subnets which to make the cloudwan attachment. let attachmentSubnetGroup = ''; // if the subnetGroup name is not defined, it will default to using linknet if (!props.attachmentSubnetGroup) { attachmentSubnetGroup = 'linknet'; } else { attachmentSubnetGroup = props.attachmentSubnetGroup; } const linknetsubnetarns = []; const linknetSelection = this.vpc.selectSubnets({ subnetGroupName: attachmentSubnetGroup }); for (const subnet of linknetSelection.subnets) { linknetsubnetarns.push(`arn:aws:ec2:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:subnet/${subnet.subnetId}`); } let applianceMode = false; if (props.applianceMode === true) { applianceMode = true; } const props64 = cdk.Fn.base64(cdk.Stack.of(this).toJsonString({ VpcArn: this.vpc.vpcArn, SubnetArns: linknetsubnetarns, CoreNetworkId: coreNetwork.getAtt('CoreNetworkId'), Options: { ApplianceModeSupport: applianceMode, }, Tags: [ { Key: 'NetworkSegment', Value: props.segmentName, }, ], })); // attach the vpc to the cloudwaattachToCloudwanProvidern. // this custom resource has a waiter, so will not complete untill the vpc is in the avaialble state const attachmentCR = new cdk.CustomResource(this, 'attachVPCtoCloudwan', { serviceToken: this.attachToCloudwanProvider.serviceToken, properties: { props64: props64 }, }); this.vpcAttachmentSegmentName = props.segmentName; this.vpcAttachmentCR = attachmentCR; this.vpcAttachmentId = attachmentCR.getAttString('AttachmentId'); return attachmentCR.getAttString('AttachmentId'); } // end of attachToCloudwan /** * Attach a vpc to a transit gateway, possibly in appliance mode * Its intended purpose is provide a * @param props */ attachToTransitGateway(props) { let attachmentSubnetGroup = 'linknet'; if (props.attachmentSubnetGroup) { attachmentSubnetGroup = props.attachmentSubnetGroup; } const transitGatewaypeering = new aws_cdk_lib_1.custom_resources.AwsCustomResource(this, 'AttachtheVPCtoTG', { onCreate: { service: 'EC2', action: 'createTransitGatewayVpcAttachment', parameters: { SubnetIds: this.vpc.selectSubnets({ subnetGroupName: attachmentSubnetGroup }).subnetIds, TransitGatewayId: props.transitGateway.attrId, VpcId: this.vpc.vpcId, Options: { ApplianceModeSupport: props.applicanceMode, }, }, 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: ['*'], }), ]), }); this.transitGWID = props.transitGateway.attrId; this.transitGWAttachmentID = transitGatewaypeering.getResponseField('TransitGatewayVpcAttachment.TransitGatewayAttachmentId'); return transitGatewaypeering.getResponseField('TransitGatewayVpcAttachment.TransitGatewayAttachmentId'); } // end of attachToTransitGateway /** * Share a subnetGroup with another AWS Account. * @param props ShareSubnetGroup */ shareSubnetGroup(props) { var subnetarns = []; const selection = this.vpc.selectSubnets({ subnetGroupName: props.subnetGroup.subnet.name, }); selection.subnets.forEach((subnet) => { subnetarns.push(`arn:${cdk.Aws.PARTITION}:ec2:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:subnet/${subnet.subnetId}`); }); new aws_cdk_lib_1.aws_ram.CfnResourceShare(this, `ramshare${props.subnetGroup.subnet.name}$`, { name: `shareSubnetGroup-${props.subnetGroup.subnet.name}`, allowExternalPrincipals: false, principals: props.accounts, resourceArns: subnetarns, }); } /** * Enable CloudWanRoutingProtocol * @param props */ cloudWanRoutingProtocol(props) { var routeTableIds = []; // get all the routeTableIds for the subnets that will particpate props.subnetGroups.forEach((groupName) => { const selection = this.vpc.selectSubnets({ subnetGroupName: groupName, }); selection.subnets.forEach((subnet) => { routeTableIds.push(subnet.routeTable.routeTableId); }); }); // setCoreRoutesLambda const setCoreRoutes = new aws_cdk_lib_1.aws_lambda.Function(this, 'setCoreRoutesLambda', { runtime: aws_cdk_lib_1.aws_lambda.Runtime.PYTHON_3_9, logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.ONE_MONTH, handler: 'setcoreroutes.on_event', code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(path.join(__dirname, '../../lambda/evpc')), timeout: cdk.Duration.seconds(899), environment: { RouteTableIds: cdk.Stack.of(this).toJsonString(routeTableIds), DenyFilter: cdk.Stack.of(this).toJsonString(props.acceptRouteFilter), AcceptFilter: cdk.Stack.of(this).toJsonString(props.denyRouteFilter), CloudWanCoreId: this.cloudWanCoreId, AttachmentSegment: this.vpcAttachmentSegmentName, }, }); setCoreRoutes.addToRolePolicy(new aws_cdk_lib_1.aws_iam.PolicyStatement({ actions: ['networkManager'], resources: ['*'], effect: aws_cdk_lib_1.aws_iam.Effect.ALLOW, })); const setCoreRoutesProvider = new aws_cdk_lib_1.custom_resources.Provider(this, 'setCoreRoutesProvider', { onEventHandler: setCoreRoutes, }); const setCoreRoutesCR = new cdk.CustomResource(this, 'setCoreRoutesCR', { serviceToken: setCoreRoutesProvider.serviceToken, }); setCoreRoutesCR.node.addDependency(this.vpcAttachmentCR); // the lambda needs to run when a topology change occurs. const topologyChange = new aws_cdk_lib_1.aws_events.Rule(this, 'CoreWanPolicyChange', { description: 'topology Change in CoreNetwork', }); topologyChange.addEventPattern({ source: ['aws.networkmanager'], detailType: ['Network Manager Policy Update'], detail: { changeType: 'CHANGE-SET-EXECUTED', }, }); topologyChange.addTarget(new aws_cdk_lib_1.aws_events_targets.LambdaFunction(setCoreRoutes, { deadLetterQueue: new aws_cdk_lib_1.aws_sqs.Queue(this, 'topologychangeDLQ'), maxEventAge: cdk.Duration.hours(2), retryAttempts: 2, })); } /** * Add routes to SubnetGroups ( by implication their routing tables ) * @param props */ addRoutes(props) { if (props.destination === Destination.TRANSITGATEWAY || props.destination === Destination.CLOUDWAN) { var routeTableIds = []; // get all the routeTableIds for the subnets props.subnetGroups.forEach((groupName) => { // array of subnets const selection = this.vpc.selectSubnets({ subnetGroupName: groupName }); selection.subnets.forEach((subnet) => { routeTableIds.push({ routeTableId: subnet.routeTable.routeTableId, groupName: groupName, }); }); }); // check that the cidrs are valid const ipRegex = new RegExp('(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([1-3][0-2]$|[0-2][0-9]$|0?[0-9]$)'); // iterate over the route tables. routeTableIds.forEach((routeTableId, index) => { props.cidr.forEach((network) => { if (ipRegex.test(network) === false) { throw new Error(`cidr ${network} is invalid`); } switch (props.destination) { case Destination.CLOUDWAN: { const cloudwanroute = new cdk.CustomResource(this, `${routeTableId.groupName}${index}cloudwanroute${network}`, { serviceToken: this.addRoutesProvider.serviceToken, properties: { cidr: network, RouteTableId: routeTableId.routeTableId, Destination: props.destination, CloudWanName: this.cloudWanName, }, }); cloudwanroute.node.addDependency(this.vpcAttachmentCR); break; } case Destination.TRANSITGATEWAY: { const waiter = new cdk.CustomResource(this, `t${routeTableId.groupName}${index}tgwaiter${network}`, { serviceToken: this.tgWaiterProvider.serviceToken, properties: { transitGatewayId: this.transitGWID, transitGatewayAttachmentId: this.transitGWAttachmentID, }, }); const transitgatewayroute = new aws_cdk_lib_1.aws_ec2.CfnRoute(this, `${routeTableId.groupName}${index}tgroute${network}`, { routeTableId: routeTableId.routeTableId, destinationCidrBlock: network, transitGatewayId: this.transitGWID, }); transitgatewayroute.node.addDependency(waiter); break; } default: { throw new Error('No valid destinations for this method. '); } } }); }); } else if (props.destination === Destination.NWFIREWALL) { /** * the respose from the API call, exceeds 4096, so need to limit it with an output path */ const outputPaths = []; const azlist = cdk.Stack.of(this).availabilityZones; azlist.forEach((az) => { outputPaths.push(`FirewallStatus.SyncStates.${az}.Attachment.EndpointId`); }); const fwDescription = new aws_cdk_lib_1.custom_resources.AwsCustomResource(this, `DescribeFirewall${hashProps(props)}${props.description}`, { onCreate: { service: 'NetworkFirewall', action: 'describeFirewall', parameters: { FirewallArn: this.firewallArn, }, region: cdk.Aws.REGION, physicalResourceId: aws_cdk_lib_1.custom_resources.PhysicalResourceId.of('DescribeFirewall'), outputPaths: outputPaths, }, 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, }), }); props.subnetGroups.forEach((subnetGroup) => { props.cidr.forEach((destinationCidr) => { this.vpc.selectSubnets({ subnetGroupName: subnetGroup }).subnets.forEach((subnet, index) => { new aws_cdk_lib_1.aws_ec2.CfnRoute(this, 'FirewallRoute-' + hashProps(props) + subnet.node.path.split('/').pop() + index, { destinationCidrBlock: destinationCidr, routeTableId: subnet.routeTable.routeTableId, vpcEndpointId: fwDescription.getResponseField(`FirewallStatus.SyncStates.${subnet.availabilityZone}.Attachment.EndpointId`), }); }); }); }); } else { throw new Error('Unsupported Destination for Route'); } } // end of add routes //for other accounts, need to share their accounts across. // https://aws.amazon.com/premiumsupport/knowledge-center/route53-private-hosted-zone/ addR53Zone(props) { const zone = new aws_cdk_lib_1.aws_route53.PrivateHostedZone(this, `vpcZone${props.zone}`, { zoneName: props.zone, vpc: this.vpc, }); if (props.centralVpc) { zone.addVpc(props.centralVpc); } } // policyTable string // segments string[] // destinations [] addCoreRoutes(props) { // import the dynamodb table. const policyTable = aws_cdk_lib_1.aws_dynamodb.Table.fromTableArn(this, `${props.description}policytable`, props.policyTableArn); // create the lambda that const onEvent = new aws_cdk_lib_1.aws_lambda.SingletonFunction(this, `${props.description}putItems`, { uuid: 'AA002244FF', environment: { policyTableName: policyTable.tableName }, runtime: aws_cdk_lib_1.aws_lambda.Runtime.PYTHON_3_9, handler: 'putitems.on_event', code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(path.join(__dirname, '../../lambda/cloudwan')), }); policyTable.grantFullAccess(onEvent); const policyTableProvider = new aws_cdk_lib_1.custom_resources.Provider(this, `${props.description}UpdateProvider`, { onEventHandler: onEvent, logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.FIVE_DAYS, }); let pushPolicyDependsOn = []; // add routes into segments as required. props.segments.forEach((segment) => { const segmentAction = {}; segmentAction.description = props.description; segmentAction.action = 'create-route', segmentAction.segment = segment, segmentAction['destination-cidr-blocks'] = props.destinationCidrBlocks; segmentAction.destinations = [props.attachmentId]; const addCoreRoute = new cdk.CustomResource(this, `${props.description}CloudwanSegmentRoute${segment}`, { serviceToken: policyTableProvider.serviceToken, properties: { segmentAction: cdk.Fn.base64(cdk.Stack.of(this).toJsonString(segmentAction)), }, }); pushPolicyDependsOn.push(addCoreRoute); }); // now update the routetable // this updates the policy const updatePolicyLambda = new aws_cdk_lib_1.aws_lambda.Function(this, `${props.description}UpdateCoreNetworkCoreRoutesLambda`, { environment: { coreNetworkName: props.coreName }, runtime: aws_cdk_lib_1.aws_lambda.Runtime.PYTHON_3_9, handler: 'updatepolicy.on_event', code: aws_cdk_lib_1.aws_lambda.Code.fromAsset(path.join(__dirname, '../../lambda/cloudwan')), timeout: cdk.Duration.seconds(899), }); updatePolicyLambda.addToRolePolicy(new aws_cdk_lib_1.aws_iam.PolicyStatement({ effect: aws_cdk_lib_1.aws_iam.Effect.ALLOW, resources: ['*'], actions: [ 'networkmanager:putCoreNetworkPolicy', 'networkmanager:executeCoreNetworkChangeSet', '*', ], })); // let the lambda have access to the dynamo table. policyTable.grantFullAccess(updatePolicyLambda); const PolicyTableUpdateProvider = new aws_cdk_lib_1.custom_resources.Provider(this, `${props.description}UpdateProviderCoreRoutes`, { onEventHandler: updatePolicyLambda, logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.FIVE_DAYS, providerFunctionName: cdk.PhysicalName.GENERATE_IF_NEEDED, }); const updatePolicy = new cdk.CustomResource(this, `${props.description}UpdatePolicyCoreRoutes`, { serviceToken: PolicyTableUpdateProvider.serviceToken, properties: { TableName: policyTable.tableName, coreNetworkId: this.cloudWanCoreId, random: new Date().toISOString(), }, }); // we need to force this to not happen till all the updates are done. pushPolicyDependsOn.forEach((resource) => { updatePolicy.node.addDependency(resource); }); } } exports.EnterpriseVpc = EnterpriseVpc; _b = JSII_RTTI_SYMBOL_1; EnterpriseVpc[_b] = { fqn: "raindancers-network.network.EnterpriseVpc", version: "1.29.3" }; // this provides a unique string based on the props function hashProps(props) { const str = JSON.stringify(props); var h = 0; for (var i = 0; i < str.length; i++) { h = 31 * h + str.charCodeAt(i); } return h.toString(16).substring(0, 12); } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW50ZXJwcmlzZXZwYy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9ldnBjL2VudGVycHJpc2V2cGMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBQSw2QkFBNkI7QUFDN0IsbUNBQW1DO0FBQ25DLDZDQWlCcUI7QUFFckIseUNBQXlDO0FBQ3pDLGlFQUE2RDtBQUM3RCxvREFBb0U7QUFDcEUsc0RBQXlHO0FBQ3pHLDBEQUF1RjtBQUN2Riw0REFBcUU7QUFDckUsd0RBQTREO0FBQzVELDBFQUF1RTtBQUN2RSxxREFBeUQ7QUEyQnpELE1BQWEsV0FBWSxTQUFRLFVBQVUsQ0FBQyxTQUFTO0lBSW5ELFlBQVksS0FBMkIsRUFBRSxFQUFVLEVBQUUsS0FBd0I7UUFDM0UsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztRQUVqQixNQUFNLFFBQVEsR0FBZ0I7WUFDNUIsSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO1lBQ2hCLFVBQVUsRUFBRSxLQUFLLENBQUMsVUFBVTtZQUM1QixRQUFRLEVBQUUsS0FBSyxDQUFDLFFBQVE7U0FDekIsQ0FBQztRQUVGLElBQUksQ0FBQyxNQUFNLEdBQUcsUUFBUSxDQUFDO0lBQ3pCLENBQUM7O0FBZEgsa0NBZUM7OztBQVVELElBQVksZUFFWDtBQUZELFdBQVksZUFBZTtJQUN6Qiw0Q0FBeUIsQ0FBQTtBQUMzQixDQUFDLEVBRlcsZUFBZSxHQUFmLHVCQUFlLEtBQWYsdUJBQWUsUUFFMUI7QUEyQ0Q7O0dBRUc7QUFDSCxJQUFZLGFBR1g7QUFIRCxXQUFZLGFBQWE7SUFDdkIsZ0VBQWdFO0lBQ2hFLG1DQUFrQixDQUFBO0FBQ3BCLENBQUMsRUFIVyxhQUFhLEdBQWIscUJBQWEsS0FBYixxQkFBYSxRQUd4QjtBQW1ERDs7R0FFRztBQUNILElBQVksV0FPWDtBQVBELFdBQVksV0FBVztJQUNyQix3REFBd0Q7SUFDeEQsb0NBQXFCLENBQUE7SUFDckIsOERBQThEO0lBQzlELGdE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