Skip to content

Commit 6e5fe96

Browse files
fix(core): Removing StackSet for installing Pipeline role in Org Accounts (aws-samples#568)
* Creating Accelerator Execution Role in all accounts * Adding Cloudformation service to assume pipelinerole * Setting maxConcurrency for installing pipeline roles to 40
1 parent d5962df commit 6e5fe96

File tree

7 files changed

+65
-25
lines changed

7 files changed

+65
-25
lines changed

src/core/cdk/src/assets/execution-role.template.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@
3131
"Principal": {
3232
"Service": "ssm.amazonaws.com"
3333
}
34+
},
35+
{
36+
"Action": "sts:AssumeRole",
37+
"Effect": "Allow",
38+
"Principal": {
39+
"Service": "cloudformation.amazonaws.com"
40+
}
3441
}
3542
],
3643
"Version": "2012-10-17"

src/core/cdk/src/initial-setup.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ export namespace InitialSetup {
206206
'phases.$': '$.configuration.baselineOutput.phases',
207207
'acceleratorVersion.$': '$.configuration.acceleratorVersion',
208208
'configRootFilePath.$': '$.configuration.configRootFilePath',
209+
'organizationAdmiRole.$': '$.configuration.baselineOutput.organizationAdmiRole',
209210
},
210211
resultPath: '$.configuration',
211212
});
@@ -225,6 +226,7 @@ export namespace InitialSetup {
225226
'phases.$': '$.configuration.baselineOutput.phases',
226227
'acceleratorVersion.$': '$.configuration.acceleratorVersion',
227228
'configRootFilePath.$': '$.configuration.configRootFilePath',
229+
'organizationAdmiRole.$': '$.configuration.baselineOutput.organizationAdmiRole',
228230
},
229231
resultPath: '$.configuration',
230232
});
@@ -350,6 +352,7 @@ export namespace InitialSetup {
350352
'regions.$': '$.configuration.regions',
351353
'accounts.$': '$.configuration.accounts',
352354
'configRootFilePath.$': '$.configuration.configRootFilePath',
355+
'organizationAdmiRole.$': '$.configuration.organizationAdmiRole',
353356
},
354357
resultPath: '$',
355358
});
@@ -389,18 +392,15 @@ export namespace InitialSetup {
389392
},
390393
);
391394

392-
const installRoleTemplate = new s3assets.Asset(this, 'ExecutionRoleTemplate', {
393-
path: path.join(__dirname, 'assets', 'execution-role.template.json'),
394-
});
395-
396-
// Make sure the Lambda can read the template
397-
installRoleTemplate.bucket.grantRead(pipelineRole);
395+
const accountsPath = path.join(__dirname, 'assets', 'execution-role.template.json');
396+
const executionRoleContent = fs.readFileSync(accountsPath);
398397

399398
const installRolesStateMachine = new sfn.StateMachine(this, `${props.acceleratorPrefix}InstallRoles_sm`, {
400399
stateMachineName: `${props.acceleratorPrefix}InstallRoles_sm`,
401-
definition: new CreateStackSetTask(this, 'Install', {
400+
definition: new CreateStackTask(this, 'Install', {
402401
lambdaCode,
403402
role: pipelineRole,
403+
suffix: 'ExecutionRole',
404404
}),
405405
});
406406

@@ -416,16 +416,25 @@ export namespace InitialSetup {
416416
// TODO Only add root role for development environments
417417
AssumedByRoleArn: `arn:aws:iam::${stack.account}:root,${pipelineRole.roleArn}`,
418418
},
419-
stackTemplate: {
420-
s3BucketName: installRoleTemplate.s3BucketName,
421-
s3ObjectKey: installRoleTemplate.s3ObjectKey,
422-
},
423-
'instanceAccounts.$': '$.accounts',
424-
instanceRegions: [stack.region],
419+
stackTemplate: executionRoleContent.toString(),
420+
'accountId.$': '$.accountId',
421+
'assumedRoleName.$': '$.organizationAdmiRole',
425422
}),
426423
resultPath: 'DISCARD',
427424
});
428425

426+
const installExecRolesInAccounts = new sfn.Map(this, `Install Execution Roles Map`, {
427+
itemsPath: '$.accounts',
428+
resultPath: 'DISCARD',
429+
maxConcurrency: 40,
430+
parameters: {
431+
'accountId.$': '$$.Map.Item.Value',
432+
'organizationAdmiRole.$': '$.organizationAdmiRole',
433+
},
434+
});
435+
436+
installExecRolesInAccounts.iterator(installRolesTask);
437+
429438
const deleteVpcSfn = new sfn.StateMachine(this, 'Delete Default Vpcs Sfn', {
430439
stateMachineName: `${props.acceleratorPrefix}DeleteDefaultVpcs_sfn`,
431440
definition: new RunAcrossAccountsTask(this, 'DeleteDefaultVPCs', {
@@ -853,7 +862,7 @@ export namespace InitialSetup {
853862

854863
const commonDefinition = loadOrganizationsTask.startState
855864
.next(loadAccountsTask)
856-
.next(installRolesTask)
865+
.next(installExecRolesInAccounts)
857866
.next(deleteVpcTask)
858867
.next(loadLimitsTask)
859868
.next(enableTrustedAccessForServicesTask)

src/core/cdk/src/tasks/create-stack-task.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export namespace CreateStackTask {
99
role: iam.IRole;
1010
lambdaCode: lambda.Code;
1111
waitSeconds?: number;
12+
suffix?: string;
1213
}
1314
}
1415

@@ -19,7 +20,7 @@ export class CreateStackTask extends sfn.StateMachineFragment {
1920
constructor(scope: cdk.Construct, id: string, props: CreateStackTask.Props) {
2021
super(scope, id);
2122

22-
const { role, lambdaCode, waitSeconds = 10 } = props;
23+
const { role, lambdaCode, suffix, waitSeconds = 10 } = props;
2324

2425
role.addToPrincipalPolicy(
2526
new iam.PolicyStatement({
@@ -36,7 +37,7 @@ export class CreateStackTask extends sfn.StateMachineFragment {
3637
}),
3738
);
3839

39-
const deployTask = new CodeTask(scope, `Deploy`, {
40+
const deployTask = new CodeTask(scope, `Deploy${suffix || ''}`, {
4041
resultPath: 'DISCARD',
4142
functionProps: {
4243
role,
@@ -47,7 +48,7 @@ export class CreateStackTask extends sfn.StateMachineFragment {
4748

4849
const verifyTaskResultPath = '$.verify';
4950
const verifyTaskStatusPath = `${verifyTaskResultPath}.status`;
50-
const verifyTask = new CodeTask(scope, 'Verify', {
51+
const verifyTask = new CodeTask(scope, `Verify${suffix || ''}`, {
5152
resultPath: verifyTaskResultPath,
5253
functionProps: {
5354
role,
@@ -56,19 +57,19 @@ export class CreateStackTask extends sfn.StateMachineFragment {
5657
},
5758
});
5859

59-
const waitTask = new sfn.Wait(scope, 'Wait', {
60+
const waitTask = new sfn.Wait(scope, `Wait${suffix || ''}`, {
6061
time: sfn.WaitTime.duration(cdk.Duration.seconds(waitSeconds)),
6162
});
6263

63-
const pass = new sfn.Pass(this, 'Succeeded');
64+
const pass = new sfn.Pass(this, `Succeeded${suffix || ''}`);
6465

65-
const fail = new sfn.Fail(this, 'Failed');
66+
const fail = new sfn.Fail(this, `Failed${suffix || ''}`);
6667

6768
const chain = sfn.Chain.start(deployTask)
6869
.next(waitTask)
6970
.next(verifyTask)
7071
.next(
71-
new sfn.Choice(scope, 'Choice')
72+
new sfn.Choice(scope, `Choice${suffix || ''}`)
7273
.when(sfn.Condition.stringEquals(verifyTaskStatusPath, 'SUCCESS'), pass)
7374
.when(sfn.Condition.stringEquals(verifyTaskStatusPath, 'IN_PROGRESS'), waitTask)
7475
.otherwise(fail)

src/core/runtime/src/create-stack/create.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,36 @@
11
import { CloudFormation, objectToCloudFormationParameters } from '@aws-accelerator/common/src/aws/cloudformation';
22
import { StackTemplateLocation, getTemplateBody } from '../create-stack-set/create-stack-set';
3+
import { STS } from '@aws-accelerator/common/src/aws/sts';
34

45
interface CreateStackInput {
56
stackName: string;
67
stackCapabilities: string[];
78
stackParameters: { [key: string]: string };
89
stackTemplate: StackTemplateLocation;
10+
accountId?: string;
11+
assumedRoleName?: string;
912
}
1013

1114
export const handler = async (input: CreateStackInput) => {
1215
console.log(`Creating stack...`);
1316
console.log(JSON.stringify(input, null, 2));
1417

15-
const { stackName, stackCapabilities, stackParameters, stackTemplate } = input;
18+
const { stackName, stackCapabilities, stackParameters, stackTemplate, accountId, assumedRoleName } = input;
1619

1720
console.debug(`Creating stack template`);
1821
console.debug(stackTemplate);
1922

2023
// Load the template body from the given location
2124
const templateBody = await getTemplateBody(stackTemplate);
2225

23-
const cfn = new CloudFormation();
26+
let cfn: CloudFormation;
27+
if (accountId && assumedRoleName) {
28+
const sts = new STS();
29+
const credentials = await sts.getCredentialsForAccountAndRole(accountId, assumedRoleName);
30+
cfn = new CloudFormation(credentials);
31+
} else {
32+
cfn = new CloudFormation();
33+
}
2434
await cfn.createOrUpdateStack({
2535
StackName: stackName,
2636
TemplateBody: templateBody,

src/core/runtime/src/create-stack/verify.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,29 @@
11
import { CloudFormation } from '@aws-accelerator/common/src/aws/cloudformation';
2+
import { STS } from '@aws-accelerator/common/src/aws/sts';
23

34
const SUCCESS_STATUSES = ['CREATE_COMPLETE', 'UPDATE_COMPLETE'];
45
const FAILED_STATUSES = ['CREATE_FAILED', 'ROLLBACK_COMPLETE', 'ROLLBACK_FAILED', 'UPDATE_ROLLBACK_COMPLETE'];
56

67
interface CheckStepInput {
78
stackName?: string;
9+
accountId?: string;
10+
assumedRoleName?: string;
811
}
912

1013
export const handler = async (input: Partial<CheckStepInput>) => {
1114
console.log(`Verifying stack with parameters ${JSON.stringify(input, null, 2)}`);
1215

13-
const { stackName } = input;
16+
const { stackName, accountId, assumedRoleName } = input;
1417

1518
// Deploy the stack using the assumed role in the current region
16-
const cfn = new CloudFormation();
19+
let cfn: CloudFormation;
20+
if (accountId && assumedRoleName) {
21+
const sts = new STS();
22+
const credentials = await sts.getCredentialsForAccountAndRole(accountId, assumedRoleName);
23+
cfn = new CloudFormation(credentials);
24+
} else {
25+
cfn = new CloudFormation();
26+
}
1727
const stack = await cfn.describeStack(stackName!);
1828
if (!stack) {
1929
return {

src/core/runtime/src/get-baseline-step.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface GetBaseelineOutput {
2020
baseline: string;
2121
storeAllOutputs: boolean;
2222
phases: number[];
23+
organizationAdmiRole: string;
2324
}
2425

2526
const dynamoDB = new DynamoDB();
@@ -68,5 +69,6 @@ export const handler = async (input: GetBaseLineInput): Promise<GetBaseelineOutp
6869
baseline,
6970
storeAllOutputs: runStoreAllOutputs,
7071
phases: [-1, 0, 1, 2, 3],
72+
organizationAdmiRole: globalOptionsConfig['organization-admin-role'] || 'AWSCloudFormationStackSetExecutionRole',
7173
};
7274
};

src/core/runtime/src/load-configuration-step.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export interface LoadConfigurationInput {
44
configFilePath: string;
55
configRepositoryName: string;
66
configCommitId: string;
7+
organizationAdmiRole: string;
78
baseline?: BaseLineType;
89
acceleratorVersion?: string;
910
configRootFilePath?: string;

0 commit comments

Comments
 (0)