raindancers-network
Version:
Extensions to the ec2.Vpc Constructs
780 lines • 122 kB
JavaScript
"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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