Skip to content

Commit 798b0dd

Browse files
feat(convert-config): Add option to add unique suffixes to VPC names (#1300)
* fix(asea): initial duplicate vpc names implementation (#1286) * fix(asea): add option to append unique suffixes to vpc names * update docuentation for duplicate VPC names --------- Co-authored-by: Brian Crissup <crissupb@amazon.com>
1 parent be9cd88 commit 798b0dd

File tree

4 files changed

+74
-35
lines changed

4 files changed

+74
-35
lines changed

reference-artifacts/Custom-Scripts/lza-upgrade/src/asea-config/index.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* and limitations under the License.
1212
*/
1313
import * as t from './types';
14+
import * as crypto from 'crypto';
1415

1516
export const MandatoryAccountType = t.enums('MandatoryAccountType', [
1617
'master',
@@ -1317,16 +1318,18 @@ export class AcceleratorConfig {
13171318
* Find all VPC configurations in mandatory accounts, workload accounts and organizational units. VPC configuration in
13181319
* organizational units will have the correct `accountKey` based on the `deploy` value of the VPC configuration.
13191320
*/
1320-
getVpcConfigs(): ResolvedVpcConfig[] {
1321+
getVpcConfigs(appendSuffix: boolean): ResolvedVpcConfig[] {
13211322
const vpcConfigs: ResolvedVpcConfig[] = [];
13221323

13231324
// Add mandatory account VPC configuration first
13241325
for (const [accountKey, accountConfig] of this.getMandatoryAccountConfigs()) {
13251326
for (const vpcConfig of accountConfig.vpc || []) {
1327+
const lzaVpcName = createLzaVpcName(vpcConfig.name, accountKey, vpcConfig.region, appendSuffix);
13261328
vpcConfigs.push({
13271329
accountKey,
13281330
vpcConfig,
13291331
ouKey: accountConfig.ou,
1332+
lzaVpcName
13301333
});
13311334
}
13321335
}
@@ -1346,13 +1349,14 @@ export class AcceleratorConfig {
13461349
continue;
13471350
}
13481351
}
1349-
vpcConfig.lzaVpcName = `${vpcConfig.name}_${accountKey}`;
1352+
vpcConfig.lzaVpcName = createLzaVpcName(vpcConfig.name, accountKey, vpcConfig.region, appendSuffix);
13501353
if (vpcConfig['cidr-src'] === 'dynamic') {
1354+
const lzaVpcName = createLzaVpcName(vpcConfig.name, accountKey, vpcConfig.region, appendSuffix);
13511355
vpcConfigs.push({
13521356
ouKey,
13531357
accountKey,
13541358
vpcConfig,
1355-
lzaVpcName: `${vpcConfig.name}_${accountKey}`,
1359+
lzaVpcName,
13561360
});
13571361
}
13581362
}
@@ -1361,6 +1365,7 @@ export class AcceleratorConfig {
13611365
ouKey,
13621366
vpcConfig,
13631367
excludeAccounts,
1368+
lzaVpcName: createLzaVpcName(vpcConfig.name, ouKey, vpcConfig.region, appendSuffix),
13641369
});
13651370
}
13661371
} else {
@@ -1369,6 +1374,7 @@ export class AcceleratorConfig {
13691374
ouKey,
13701375
accountKey: destinationAccountKey,
13711376
vpcConfig,
1377+
lzaVpcName: createLzaVpcName(vpcConfig.name, destinationAccountKey, vpcConfig.region, appendSuffix)
13721378
});
13731379
}
13741380
}
@@ -1381,15 +1387,16 @@ export class AcceleratorConfig {
13811387
accountKey,
13821388
vpcConfig,
13831389
ouKey: accountConfig.ou,
1390+
lzaVpcName: createLzaVpcName(vpcConfig.name, accountKey, vpcConfig.region, appendSuffix),
13841391
});
13851392
}
13861393
}
13871394

13881395
return vpcConfigs;
13891396
}
13901397

1391-
getAzSubnets(accountKey: string, vpcName: string, subnetName: string) {
1392-
const vpcConfigs = this.getVpcConfigs();
1398+
getAzSubnets(accountKey: string, vpcName: string, subnetName: string, appendSuffix: boolean) {
1399+
const vpcConfigs = this.getVpcConfigs(appendSuffix);
13931400
const vpcConfig = vpcConfigs.find((v) => v.accountKey === accountKey && v.vpcConfig.name === vpcName)?.vpcConfig;
13941401
if (!vpcConfig) {
13951402
throw new Error(`VPC named "${vpcName}" not found in account "${accountKey}"`);
@@ -1406,3 +1413,10 @@ export class AcceleratorConfig {
14061413
}));
14071414
}
14081415
}
1416+
1417+
export function createLzaVpcName(vpcName: string, accountKey: string, region: string, appendSuffix: boolean): string {
1418+
const md5Hash = crypto.createHash('md5').update(`${vpcName}_${accountKey}_${region}`).digest('hex');
1419+
const vpcNameWithType = vpcName.endsWith('_vpc') ? vpcName : `${vpcName}_vpc`;
1420+
const lzaVpcName = appendSuffix ? `${vpcNameWithType}..${md5Hash.substring(0,5)}` : vpcNameWithType;
1421+
return lzaVpcName;
1422+
}

reference-artifacts/Custom-Scripts/lza-upgrade/src/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface Config {
3636
skipDriftDetection?: boolean;
3737
localConfigFilePath?: string;
3838
enableTerminationProtection?: boolean;
39+
appendUniqueSuffixToVPCNames?: boolean
3940
lzaInstallerTemplateBucket?: string
4041
lzaInstallerTemplateKey?: string
4142
}

reference-artifacts/Custom-Scripts/lza-upgrade/src/convert-config.ts

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
TransitGatewayRouteConfig,
3636
VpcConfig,
3737
VpcFlowLogsDestinationConfig,
38+
createLzaVpcName,
3839
} from './asea-config';
3940
import { loadAseaConfig } from './asea-config/load';
4041
import * as WriteToSourcesTypes from './common//utils/types/writeToSourcesTypes';
@@ -189,6 +190,7 @@ export class ConvertAseaConfig {
189190
private readonly assumeRoleName: string;
190191
private readonly writeFilesConfig: WriteToSourcesTypes.WriteToSourcesConfig;
191192
private readonly ouToNestedOuMap: Map<string, Set<string>> = new Map();
193+
private readonly appendVpcSuffixes: boolean;
192194
private accounts: Account[] = [];
193195
private outputs: StackOutput[] = [];
194196
private vpcAssignedCidrs: VpcAssignedCidr[] = [];
@@ -212,6 +214,7 @@ export class ConvertAseaConfig {
212214
this.region = config.homeRegion;
213215
this.centralBucketName = config.centralBucket!;
214216
this.aseaPrefix = config.aseaPrefix!.endsWith('-') ? config.aseaPrefix! : `${config.aseaPrefix}-`;
217+
this.appendVpcSuffixes = config.appendUniqueSuffixToVPCNames ?? false;
215218
this.parametersTable = `${this.aseaPrefix}Parameters`;
216219
this.acceleratorName = config.acceleratorName!;
217220
this.sts = new STS();
@@ -251,7 +254,7 @@ export class ConvertAseaConfig {
251254
this.subnetAssignedCidrs = await loadSubnetAssignedCidrs(subnetsCidrsTableName(this.aseaPrefix), this.dynamoDb);
252255
this.outputs = await loadOutputs(`${this.aseaPrefix}Outputs`, this.dynamoDb);
253256
this.globalOptions = aseaConfig['global-options'];
254-
this.vpcConfigs = aseaConfig.getVpcConfigs();
257+
this.vpcConfigs = aseaConfig.getVpcConfigs(this.appendVpcSuffixes);
255258
const regionsWithVpc = this.vpcConfigs.map((resolvedConfig) => resolvedConfig.vpcConfig.region);
256259
this.regionsWithoutVpc = this.globalOptions['supported-regions'].filter(
257260
(region) => !regionsWithVpc.includes(region),
@@ -539,9 +542,9 @@ export class ConvertAseaConfig {
539542
name: createNetworkFirewallName(firewallConfigName, this.aseaPrefix),
540543
subnetChangeProtection: false,
541544
tags: [],
542-
vpc: createVpcName(lzaVpcName ?? vpcConfig.name),
545+
vpc: lzaVpcName!,
543546
subnets: this.getAzSubnets(vpcConfig, networkFirewallConfig.subnet.name).map((subnet) =>
544-
createSubnetName(lzaVpcName ?? vpcConfig.name, subnet.subnetName, subnet.az),
547+
createSubnetName(vpcConfig.name, subnet.subnetName, subnet.az),
545548
),
546549
});
547550
}
@@ -1781,7 +1784,7 @@ export class ConvertAseaConfig {
17811784
name: instanceNameWithAz,
17821785
account,
17831786
launchTemplate,
1784-
vpc: `${vpcName}_vpc`,
1787+
vpc: firewallScopedVpcConfig?.lzaVpcName!,
17851788
terminationProtection,
17861789
detailedMonitoring,
17871790
tags,
@@ -2437,7 +2440,7 @@ export class ConvertAseaConfig {
24372440
const setConfigRulesConfig = async () => {
24382441
if (!globalOptions['aws-config']) return;
24392442
// TODO: Consider account regions for deploymentTargets
2440-
const currentNodeRuntime = 'nodejs18.x';
2443+
const currentNodeRuntime = 'nodejs20.x';
24412444
const rulesWithTarget: (AwsConfigRule & {
24422445
deployTo?: string[];
24432446
excludedAccounts?: string[];
@@ -2815,7 +2818,7 @@ export class ConvertAseaConfig {
28152818
if (route['target-vpc']) {
28162819
return {
28172820
account: this.getAccountKeyforLza(globalOptions, route['target-account'] || accountKey),
2818-
vpcName: createVpcName(route['target-vpc']),
2821+
vpcName: this.getLzaVpcName(route['target-vpc']),
28192822
};
28202823
} else if (route['target-vpn']) {
28212824
return {
@@ -2982,9 +2985,9 @@ export class ConvertAseaConfig {
29822985
sources: [],
29832986
};
29842987
for (const source of rule.source) {
2985-
let sourceVpcAccountKey: string | undefined = undefined;
2988+
let sourceVpcConfig: ResolvedVpcConfig | undefined;
29862989
if (SubnetSourceConfig.is(source)) {
2987-
sourceVpcAccountKey = this.vpcConfigs.find(({ vpcConfig }) => vpcConfig.name === source.vpc)?.accountKey;
2990+
sourceVpcConfig = this.vpcConfigs.find(({ vpcConfig }) => vpcConfig.name === source.vpc);
29882991
}
29892992
if (SecurityGroupSourceConfig.is(source)) {
29902993
lzaRule.sources.push({
@@ -2995,14 +2998,14 @@ export class ConvertAseaConfig {
29952998
//account: this.getAccountKeyforLza(globalOptions, source.account || accountKey || ''),
29962999
account: this.getAccountKeyforLza(
29973000
globalOptions,
2998-
sourceVpcAccountKey || source.account || accountKey || '',
3001+
sourceVpcConfig?.accountKey || source.account || accountKey || '',
29993002
),
30003003
subnets: source.subnet.flatMap((sourceSubnet) =>
30013004
aseaConfig
3002-
.getAzSubnets(sourceVpcAccountKey || source.account || accountKey || '', source.vpc, sourceSubnet)
3005+
.getAzSubnets(sourceVpcConfig?.accountKey || source.account || accountKey || '', source.vpc, sourceSubnet, this.appendVpcSuffixes)
30033006
.map((s) => createSubnetName(source.vpc, s.subnetName, s.az)),
30043007
),
3005-
vpc: createVpcName(source.vpc),
3008+
vpc: sourceVpcConfig?.lzaVpcName ?? source.vpc,
30063009
});
30073010
} else {
30083011
lzaRule.sources.push(source);
@@ -3026,7 +3029,6 @@ export class ConvertAseaConfig {
30263029
rules: NaclConfig[],
30273030
vpcConfig: VpcConfig,
30283031
accountKey?: string,
3029-
lzaVpcName?: string,
30303032
) => {
30313033
const lzaRules: (ConvertConfigTypes.LzaNaclInboundRuleType | ConvertConfigTypes.LzaNaclOutboundRuleType)[] = [];
30323034
for (const rule of rules) {
@@ -3070,18 +3072,17 @@ export class ConvertAseaConfig {
30703072
});
30713073
} else {
30723074
// determine which vpc the nacl rule references
3073-
// use the lzaVpcName when the config is from ou
30743075
let destination: string;
30753076
if (dest.vpc === vpcConfig.name) {
3076-
destination = createVpcName(lzaVpcName ?? vpcConfig.name);
3077+
destination = vpcConfig.name;
30773078
} else {
3078-
destination = createVpcName(dest.vpc);
3079+
destination = dest.vpc;
30793080
}
3081+
const destinationAccountKey = destinationVpcKey ? this.getAccountKeyforLza(globalOptions, destinationVpcKey): undefined;
30803082
target = {
3081-
account: destinationVpcKey ? this.getAccountKeyforLza(globalOptions, destinationVpcKey) : undefined,
3083+
account: destinationAccountKey,
30823084
subnet: createSubnetName(dest.vpc, ruleSubnet.subnetName, ruleSubnet.az),
3083-
//vpc: createVpcName(dest.vpc),
3084-
vpc: destination,
3085+
vpc: createLzaVpcName(destination, destinationAccountKey!, vpcConfig.region, this.appendVpcSuffixes),
30853086
region: targetRegion,
30863087
};
30873088
}
@@ -3101,7 +3102,7 @@ export class ConvertAseaConfig {
31013102
}
31023103
return lzaRules;
31033104
};
3104-
const prepareNaclConfig = (vpcConfig: VpcConfig, accountKey?: string, lzaVpcName?: string) => {
3105+
const prepareNaclConfig = (vpcConfig: VpcConfig, accountKey?: string) => {
31053106
const naclSubnetConfigs = vpcConfig.subnets?.filter((s) => !!s.nacls);
31063107
if (!naclSubnetConfigs) return;
31073108
const nacls = [];
@@ -3115,8 +3116,8 @@ export class ConvertAseaConfig {
31153116
subnetAssociations: this.getAzSubnets(vpcConfig, subnetConfig.name).map((s) =>
31163117
createSubnetName(vpcConfig.name, s.subnetName, s.az),
31173118
),
3118-
inboundRules: prepareNaclRules(inboundRules, vpcConfig, accountKey, lzaVpcName),
3119-
outboundRules: prepareNaclRules(outboundRules, vpcConfig, accountKey, lzaVpcName),
3119+
inboundRules: prepareNaclRules(inboundRules, vpcConfig, accountKey),
3120+
outboundRules: prepareNaclRules(outboundRules, vpcConfig, accountKey),
31203121
});
31213122
}
31223123
return nacls;
@@ -3220,14 +3221,15 @@ export class ConvertAseaConfig {
32203221
vpcConfig: VpcConfig,
32213222
lzaEndpointsConfig: ConvertConfigTypes.ResolverEndpointsType[],
32223223
lzaEndpointsRulesConfig: ConvertConfigTypes.ResolverEndpointRulesType[],
3224+
accountKey: string | undefined,
32233225
): ConvertConfigTypes.ResolverEndpointsType[] => {
32243226
let inboundResolver = vpcConfig.resolvers!.inbound;
32253227
let outboundResolver = vpcConfig.resolvers!.outbound;
32263228
if (vpcConfig.resolvers) {
32273229
if (inboundResolver) {
32283230
lzaEndpointsConfig.push({
32293231
name: `${vpcConfig.name}InboundEndpoint`,
3230-
vpc: createVpcName(vpcConfig.lzaVpcName ?? vpcConfig.name),
3232+
vpc: createLzaVpcName(vpcConfig.name, accountKey!, vpcConfig.region, this.appendVpcSuffixes),
32313233
subnets:
32323234
vpcConfig.subnets
32333235
?.find((subnetItem) => subnetItem.name === vpcConfig.resolvers?.subnet)
@@ -3241,7 +3243,7 @@ export class ConvertAseaConfig {
32413243
if (outboundResolver) {
32423244
lzaEndpointsConfig.push({
32433245
name: `${vpcConfig.name}OutboundEndpoint`,
3244-
vpc: createVpcName(vpcConfig.lzaVpcName ?? vpcConfig.name),
3246+
vpc: createLzaVpcName(vpcConfig.name, accountKey!, vpcConfig.region, this.appendVpcSuffixes),
32453247
subnets:
32463248
vpcConfig.subnets
32473249
?.find((subnetItem) => subnetItem.name === vpcConfig.resolvers?.subnet)
@@ -3277,7 +3279,7 @@ export class ConvertAseaConfig {
32773279
return lzaEndpointsRulesConfig;
32783280
};
32793281

3280-
const prepareResolverConfig = (vpcConfig: VpcConfig) => {
3282+
const prepareResolverConfig = (vpcConfig: VpcConfig, accountKey: string | undefined) => {
32813283
let lzaResolverConfig: {
32823284
endpoints: ConvertConfigTypes.ResolverEndpointsType[] | undefined;
32833285
queryLogs: { name: string; destinations: string[] } | undefined;
@@ -3289,7 +3291,7 @@ export class ConvertAseaConfig {
32893291
let endpoints: any[] = [];
32903292
if (vpcConfig.resolvers) {
32913293
rules = prepareRulesConfig(vpcConfig, lzaEndpointsRulesConfig);
3292-
endpoints = prepareEndpointsConfig(vpcConfig, lzaEndpointsConfig, rules!);
3294+
endpoints = prepareEndpointsConfig(vpcConfig, lzaEndpointsConfig, rules!, accountKey);
32933295
}
32943296

32953297
lzaResolverConfig = {
@@ -3434,7 +3436,7 @@ export class ConvertAseaConfig {
34343436

34353437
const prepareVpcConfig = ({ accountKey, ouKey, vpcConfig, excludeAccounts, lzaVpcName }: ResolvedVpcConfig) => {
34363438
return {
3437-
name: createVpcName(lzaVpcName ?? vpcConfig.name),
3439+
name: lzaVpcName ?? createVpcName(vpcConfig.name),
34383440
account: accountKey ? this.getAccountKeyforLza(globalOptions, accountKey) : undefined,
34393441
deploymentTargets: !accountKey
34403442
? {
@@ -3473,13 +3475,13 @@ export class ConvertAseaConfig {
34733475
useCentralEndpoints: vpcConfig['use-central-endpoints'],
34743476
natGateways: prepareNatGatewayConfig(vpcConfig),
34753477
securityGroups: prepareSecurityGroupsConfig(vpcConfig, accountKey),
3476-
networkAcls: prepareNaclConfig(vpcConfig, accountKey, lzaVpcName),
3478+
networkAcls: prepareNaclConfig(vpcConfig, accountKey),
34773479
vpcFlowLogs: prepareVpcFlowLogs(vpcConfig['flow-logs']),
34783480
subnets: prepareSubnetConfig(vpcConfig, ouKey, accountKey),
34793481
transitGatewayAttachments: prepareTgwAttachConfig(vpcConfig),
34803482
virtualPrivateGateway: vpcConfig.vgw,
34813483
routeTables: prepareRouteTableConfig(vpcConfig, accountKey),
3482-
vpcRoute53Resolver: prepareResolverConfig(vpcConfig),
3484+
vpcRoute53Resolver: prepareResolverConfig(vpcConfig, accountKey),
34833485
};
34843486
};
34853487

@@ -3508,7 +3510,7 @@ export class ConvertAseaConfig {
35083510
.filter(({ vpcConfig }) => !!vpcConfig.pcx)
35093511
.map(({ vpcConfig }) => ({
35103512
name: peeringConnectionName(vpcConfig.name, vpcConfig.pcx!['source-vpc']),
3511-
vpcs: [createVpcName(vpcConfig.lzaVpcName ?? vpcConfig.name), createVpcName(vpcConfig.pcx!['source-vpc'])],
3513+
vpcs: [this.getLzaVpcName(vpcConfig.name), this.getLzaVpcName(vpcConfig.pcx!['source-vpc'])],
35123514
}));
35133515
};
35143516
await setCertificatesConfig();
@@ -3675,6 +3677,10 @@ export class ConvertAseaConfig {
36753677
);
36763678
}
36773679

3680+
private getLzaVpcName(vpcName: string): string {
3681+
return this.vpcConfigs.find((vc) => vc.vpcConfig.name === vpcName )?.lzaVpcName!
3682+
}
3683+
36783684
private getVpcCidr({ accountKey, vpcConfig, ouKey }: { accountKey?: string; vpcConfig: VpcConfig; ouKey?: string }) {
36793685
const cidrs: string[] = [];
36803686
if (vpcConfig['cidr-src'] === 'provided') {
@@ -3736,7 +3742,7 @@ export class ConvertAseaConfig {
37363742
return ipv4CidrBlock;
37373743
}
37383744
private async createCloudFormationStacksForALBIpForwarding(aseaConfig: AcceleratorConfig) {
3739-
const vpcs = aseaConfig.getVpcConfigs();
3745+
const vpcs = aseaConfig.getVpcConfigs(this.appendVpcSuffixes);
37403746
const vpcMaps = [];
37413747
for (const vpc of vpcs) {
37423748
if (vpc.vpcConfig['alb-forwarding']) {

src/mkdocs/docs/lza-upgrade/known-issues.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,24 @@ cat network-config.yaml | yq '[.vpcs[] | select(.account == "shared-network" and
6868

6969
**Resolution or workaround:** If resource synchronization issues are encountered, executing the LZA pipeline can be re-run from the beginning to synchronize resource mappings.
7070

71+
### Duplicate VPC Names
72+
73+
**Description:** After converting the AESA to LZA configuration files you get an error about duplicate VPC names in LZA configuration validation.
74+
> config-validator | network-config.yaml has 1 issues: Duplicate VPC/VPC template names exist. VPC names must be unique
75+
76+
**Root cause:** The name of VPC names in LZA `network-config.yaml` need to be unique globally.
77+
78+
**Resolution or workaround:** A workaround was implemented into LZA to support existing ASEA VPCs that have non-unique names across different accounts or regions. Suffixes in the form of `..uniquehash` can be added to the names of VPC (i.e. `Central_vpc..f7678`) in the LZA configurations files and all their references. LZA will remove the suffix at runtime to match with the existing VPC (`Central_vpc`).
79+
80+
You can activate a configuration option in the upgrade tool to generate suffixes on all VPC references in the configuration.
81+
82+
1. After running the `yarn migration-config` command from the [Preparation phase](./preparation/prereq-config/#configuration)
83+
2. Edit the `src/input-config/input-config.json` file
84+
3. Add the following property to enable the behavior: `"appendUniqueSuffixToVPCNames": true`
85+
4. Continue with the remaining of the LZA upgrade steps. When running `yarn convert-config` with this option, suffixes will be appended to all VPCs from the configuration.
86+
87+
**Note:** The existing VPC resources won't be renamed, the suffix is only used in the configuration. Some internal references will include the suffixes, such as SSM Parameters describing networking resources and path of VPC Flow Logs on S3.
88+
7189
## Landing Zone Accelerator known issues
7290
The following issues will not prevent a successful upgrade from ASEA to LZA, but can impact functionalities and operations in the upgraded Landing Zone.
7391

0 commit comments

Comments
 (0)