in src/deployments/cdk/src/deployments/central-endpoints/step-2.ts [57:408]
export async function step2(props: CentralEndpointsStep2Props) {
const { accountStacks, config, outputs, accounts } = props;
// Create resolvers for all VPC configs
const vpcConfigs = config.getVpcConfigs();
const madConfigs = config.getMadConfigs();
const allStaticResources: StaticResourcesOutput[] = StaticResourcesOutputFinder.findAll({
outputs,
}).filter(sr => sr.resourceType === RESOURCE_TYPE);
const centralVpcStaticResources: StaticResourcesOutput[] = StaticResourcesOutputFinder.findAll({
outputs,
}).filter(sr => sr.resourceType === CENTRAL_VPC_RESOURCE_TYPE);
// Initiate previous stacks to handle deletion of previously deployed stack if there are no resources
for (const sr of allStaticResources) {
const localAccount = accounts.find(acc => acc.key === sr.accountKey);
accountStacks.tryGetOrCreateAccountStack(
sr.accountKey,
sr.region,
`${STACK_COMMON_SUFFIX}-${sr.suffix}`,
localAccount?.inScope,
);
}
// Initiate previous stacks to handle deletion of previously deployed stack if there are no resources
for (const sr of centralVpcStaticResources) {
const localAccount = accounts.find(acc => acc.key === sr.accountKey);
accountStacks.tryGetOrCreateAccountStack(
sr.accountKey,
sr.region,
STACK_CENTRAL_VPC_COMMON_SUFFIX,
localAccount?.inScope,
);
}
const accountStaticResourcesConfig: { [accountKey: string]: StaticResourcesOutput[] } = {};
const accountRegionExistingResources: {
[accountKey: string]: {
[region: string]: string[];
};
} = {};
const accountRegionMaxSuffix: {
[accountKey: string]: {
[region: string]: number;
};
} = {};
for (const { accountKey, vpcConfig } of vpcConfigs) {
const resolversConfig = vpcConfig.resolvers;
if (!resolversConfig) {
console.debug(`Skipping resolver creation for VPC "${vpcConfig.name}" in account "${accountKey}"`);
continue;
}
/**
* Checking if current VPC is under Regional Central VPCs (global-options/zones),
* If yes then only we will share Rules from this account to another accounts
*/
const isRuleShareNeeded = vpcConfig['central-endpoint'];
const vpcSubnet = vpcConfig.subnets?.find(s => s.name === resolversConfig.subnet);
if (!vpcSubnet) {
console.error(
`Subnet provided in resolvers doesn't exist in Subnet = ${resolversConfig.subnet} and VPC = ${vpcConfig.name}`,
);
continue;
}
const vpcOutput = VpcOutputFinder.tryFindOneByAccountAndRegionAndName({
outputs,
accountKey,
region: vpcConfig.region,
vpcName: vpcConfig.name,
});
if (!vpcOutput) {
console.error(`Cannot find resolved VPC with name "${vpcConfig.name}"`);
continue;
}
const roleOutput = IamRoleOutputFinder.tryFindOneByName({
outputs,
accountKey,
roleKey: 'CentralEndpointDeployment',
});
if (!roleOutput) {
continue;
}
const subnetIds = vpcOutput.subnets.filter(s => s.subnetName === resolversConfig.subnet).map(s => s.subnetId);
if (subnetIds.length === 0) {
console.error(
`Cannot find subnet IDs for subnet name = ${resolversConfig.subnet} and VPC = ${vpcConfig.name} in outputs`,
);
continue;
}
let suffix: number;
let stackSuffix: string;
let newResource = true;
const constructName = `${STACK_COMMON_SUFFIX}-${vpcConfig.name}`;
// Load all account stacks to object
if (!accountStaticResourcesConfig[accountKey]) {
accountStaticResourcesConfig[accountKey] = allStaticResources.filter(sr => sr.accountKey === accountKey);
}
if (!accountRegionMaxSuffix[accountKey]) {
accountRegionMaxSuffix[accountKey] = {};
}
// Load Max suffix for each region of account to object
if (!accountRegionMaxSuffix[accountKey][vpcConfig.region]) {
const localSuffix = accountStaticResourcesConfig[accountKey]
.filter(sr => sr.region === vpcConfig.region)
.flatMap(r => r.suffix);
accountRegionMaxSuffix[accountKey][vpcConfig.region] = localSuffix.length === 0 ? 1 : Math.max(...localSuffix);
}
if (!accountRegionExistingResources[accountKey]) {
const localRegionalResources = accountStaticResourcesConfig[accountKey]
.filter(sr => sr.region === vpcConfig.region)
.flatMap(sr => sr.resources);
accountRegionExistingResources[accountKey] = {};
accountRegionExistingResources[accountKey][vpcConfig.region] = localRegionalResources;
} else if (!accountRegionExistingResources[accountKey][vpcConfig.region]) {
const localRegionalResources = accountStaticResourcesConfig[accountKey]
.filter(sr => sr.region === vpcConfig.region)
.flatMap(sr => sr.resources);
accountRegionExistingResources[accountKey][vpcConfig.region] = localRegionalResources;
}
suffix = accountRegionMaxSuffix[accountKey][vpcConfig.region];
stackSuffix = `${STACK_COMMON_SUFFIX}-${suffix}`;
const centralVpc = vpcConfig['central-endpoint'];
if (centralVpc) {
stackSuffix = STACK_CENTRAL_VPC_COMMON_SUFFIX;
} else {
if (accountRegionExistingResources[accountKey][vpcConfig.region].includes(constructName)) {
newResource = false;
const regionStacks = accountStaticResourcesConfig[accountKey].filter(sr => sr.region === vpcConfig.region);
for (const rs of regionStacks) {
if (rs.resources.includes(constructName)) {
stackSuffix = `${STACK_COMMON_SUFFIX}-${rs.suffix}`;
break;
}
}
} else {
const existingResources = accountStaticResourcesConfig[accountKey].find(
sr => sr.region === vpcConfig.region && sr.suffix === suffix,
);
if (existingResources && existingResources.resources.length >= MAX_RESOURCES_IN_STACK) {
accountRegionMaxSuffix[accountKey][vpcConfig.region] = ++suffix;
}
stackSuffix = `${STACK_COMMON_SUFFIX}-${suffix}`;
}
}
const localAccount = accounts.find(acc => acc.key === accountKey);
const accountStack = accountStacks.tryGetOrCreateAccountStack(
accountKey,
vpcConfig.region,
stackSuffix,
localAccount?.inScope,
);
if (!accountStack) {
console.error(`Cannot find account stack ${accountKey}: ${vpcConfig.region}, while deploying Resolver Endpoints`);
continue;
}
// Call r53-resolver-endpoint per Account
const r53ResolverEndpoints = new ResolverEndpoint(
accountStack,
`${STACK_COMMON_SUFFIX}-${accountKey}-${vpcConfig.name}`,
{
vpcId: vpcOutput.vpcId,
name: vpcConfig.name,
subnetIds,
},
);
const resolverOutput: ResolversOutput = {
vpcName: vpcConfig.name,
accountKey,
region: vpcConfig.region,
};
const resolverRulesOutput: ResolverRulesOutput = {};
if (resolversConfig.inbound) {
r53ResolverEndpoints.enableInboundEndpoint();
resolverOutput.inBound = r53ResolverEndpoints.inboundEndpointRef;
}
const onPremRules: string[] = [];
const madRules: string[] = [];
if (resolversConfig.outbound) {
r53ResolverEndpoints.enableOutboundEndpoint();
resolverOutput.outBound = r53ResolverEndpoints.outboundEndpointRef;
// For each on-premise domain defined in the parameters file, create a Resolver rule which points to the specified IP's
for (const onPremRuleConfig of vpcConfig['on-premise-rules'] || []) {
const targetIps: TargetIp[] = onPremRuleConfig['outbound-ips'].map(ip => ({
Ip: ip,
Port: 53,
}));
const rule = new CreateResolverRule(accountStack, `${domainToName(onPremRuleConfig.zone)}-${vpcConfig.name}`, {
domainName: onPremRuleConfig.zone,
resolverEndpointId: r53ResolverEndpoints.outboundEndpointRef!,
roleArn: roleOutput.roleArn,
targetIps,
vpcId: vpcOutput.vpcId,
name: createRuleName(`${vpcConfig.name}-onprem-${domainToName(onPremRuleConfig.zone)}`),
});
rule.node.addDependency(r53ResolverEndpoints.outboundEndpoint!);
onPremRules.push(rule.ruleId);
}
resolverRulesOutput.onPremRules = onPremRules;
// Check for MAD configuration whose resolver is current account VPC
const madConfigsWithVpc = madConfigs.filter(
mc =>
mc.mad['central-resolver-rule-account'] === accountKey &&
mc.mad.region === vpcConfig.region &&
mc.mad['central-resolver-rule-vpc'] === vpcConfig.name,
);
for (const { accountKey: madAccountKey, mad } of madConfigsWithVpc) {
const madOutput = getStackJsonOutput(outputs, {
accountKey: madAccountKey,
outputType: 'MadOutput',
});
if (madOutput.length === 0) {
console.warn(`MAD is not deployed yet in account ${accountKey}`);
continue;
}
const madIPs: string[] = madOutput[0].dnsIps.split(',');
const targetIps: TargetIp[] = madIPs.map(ip => ({
Ip: ip,
Port: 53,
}));
const madRule = new CreateResolverRule(accountStack, `${domainToName(mad['dns-domain'])}-${vpcConfig.name}`, {
domainName: mad['dns-domain'],
resolverEndpointId: r53ResolverEndpoints.outboundEndpointRef!,
roleArn: roleOutput.roleArn,
targetIps,
vpcId: vpcOutput.vpcId,
name: createRuleName(`${vpcConfig.name}-mad-${domainToName(mad['dns-domain'])}`),
});
madRule.node.addDependency(r53ResolverEndpoints.outboundEndpoint!);
madRules.push(madRule.ruleId);
}
resolverRulesOutput.madRules = madRules;
}
resolverOutput.rules = resolverRulesOutput;
new JsonOutputValue(accountStack, `ResolverOutput-${resolverOutput.vpcName}`, {
type: 'GlobalOptionsOutput',
value: resolverOutput,
});
if (isRuleShareNeeded) {
const regionVpcs = config
.getVpcConfigs()
.filter(
vc =>
vc.vpcConfig.region === vpcConfig.region &&
vc.vpcConfig['use-central-endpoints'] &&
vc.accountKey !== accountKey,
);
const sharedToAccountKeys = regionVpcs.map(rv => rv.accountKey);
const sharedToAccountIds: string[] = sharedToAccountKeys.map(accId => getAccountId(accounts, accId)!);
if (sharedToAccountIds.length > 0) {
const ruleArns: string[] = [
...madRules.map(
ruleId => `arn:aws:route53resolver:${vpcConfig.region}:${cdk.Aws.ACCOUNT_ID}:resolver-rule/${ruleId}`,
),
...onPremRules.map(
ruleId => `arn:aws:route53resolver:${vpcConfig.region}:${cdk.Aws.ACCOUNT_ID}:resolver-rule/${ruleId}`,
),
];
// share the route53 resolver rules
new ram.CfnResourceShare(accountStack, `ResolverRuleShare-${vpcConfig.name}`, {
name: createName({
name: `${vpcConfig.name}-ResolverRules`,
}),
allowExternalPrincipals: false,
principals: sharedToAccountIds,
resourceArns: ruleArns,
});
}
}
if (centralVpc) {
const currentResourcesObject = {
accountKey,
id: `${CENTRAL_VPC_RESOURCE_TYPE}-${vpcConfig.region}-${accountKey}-${suffix}`,
region: vpcConfig.region,
resourceType: CENTRAL_VPC_RESOURCE_TYPE,
resources: [`${STACK_CENTRAL_VPC_COMMON_SUFFIX}-${vpcConfig.name}`],
// Setting sufix to -1 since will only have one Central VPC per region
suffix: -1,
};
new CfnStaticResourcesOutput(
accountStack,
`CentralVpcResolverEndpointsOutput-${vpcConfig.name}`,
currentResourcesObject,
);
}
if (newResource && !centralVpc) {
const currentSuffixIndex = allStaticResources.findIndex(
sr => sr.region === vpcConfig.region && sr.suffix === suffix && sr.accountKey === accountKey,
);
const currentAccountSuffixIndex = accountStaticResourcesConfig[accountKey].findIndex(
sr => sr.region === vpcConfig.region && sr.suffix === suffix,
);
if (currentSuffixIndex === -1) {
const currentResourcesObject = {
accountKey,
id: `${RESOURCE_TYPE}-${vpcConfig.region}-${accountKey}-${suffix}`,
region: vpcConfig.region,
resourceType: RESOURCE_TYPE,
resources: [constructName],
suffix,
};
allStaticResources.push(currentResourcesObject);
accountStaticResourcesConfig[accountKey].push(currentResourcesObject);
} else {
const currentResourcesObject = allStaticResources[currentSuffixIndex];
const currentAccountResourcesObject = accountStaticResourcesConfig[accountKey][currentAccountSuffixIndex];
if (!currentResourcesObject.resources.includes(constructName)) {
currentResourcesObject.resources.push(constructName);
}
if (!currentAccountResourcesObject.resources.includes(constructName)) {
currentAccountResourcesObject.resources.push(constructName);
}
allStaticResources[currentSuffixIndex] = currentResourcesObject;
accountStaticResourcesConfig[accountKey][currentAccountSuffixIndex] = currentAccountResourcesObject;
}
}
}
for (const sr of allStaticResources) {
const srLocalAccount = accounts.find(acc => acc.key === sr.accountKey);
const accountStack = accountStacks.tryGetOrCreateAccountStack(
sr.accountKey,
sr.region,
`${STACK_COMMON_SUFFIX}-${sr.suffix}`,
srLocalAccount?.inScope,
);
if (!accountStack) {
throw new Error(
`Not able to get or create stack for ${sr.accountKey}: ${sr.region}: ${STACK_COMMON_SUFFIX}-${sr.suffix}`,
);
}
new CfnStaticResourcesOutput(accountStack, `StaticResourceOutput-${sr.suffix}`, sr);
}
}