Skip to content

Commit edc9a21

Browse files
moofish32Elad Ben-Israel
authored and
Elad Ben-Israel
committed
feat(app-delivery) IAM policy for deploy stack (#1165)
* The "changeset" and "apply changeset" actions can now apply role IAM permissions, and CloudFormation Capabilities * Updated CloudFormationCapabilities enum to include `None` * User must set adminPermissions boolean for pipeline action * app-delivery defaults pipelin-action capabilities to AnonymousIAM * Document updates for proper build stage configuration * Fixes #1151 BREAKING CHANGE: `CloudFormationCapabilities.IAM` renamed to `CloudFormation.AnonymousIAM` and `PipelineCloudFormationDeployActionProps.capabilities?: CloudFormationCapabilities[]` has been changed to `PipelineCloudFormationDeployActionProps.capabilities?: CloudFormationCapabilities` no longer an array. `PipelineCloudFormationDeployActionProps.fullPermissions?:` has been renamed to `PipelineCloudFormationDeployActionProps.adminPermissions:` and is required instead of optional.
1 parent 01cc8a2 commit edc9a21

12 files changed

+400
-35
lines changed

Diff for: packages/@aws-cdk/app-delivery/README.md

+36-9
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,17 @@ const source = new codepipeline.GitHubSourceAction(pipelineStack, 'GitHub', {
5252
/* ... */
5353
});
5454
const project = new codebuild.PipelineProject(pipelineStack, 'CodeBuild', {
55-
/* ... */
55+
/**
56+
* Choose an environment configuration that meets your use case. For NodeJS
57+
* this might be
58+
* environment: {
59+
* buildImage: codebuild.LinuxBuildImage.UBUNTU_14_04_NODEJS_10_1_0,
60+
* },
61+
*/
5662
});
57-
const synthesizedApp = project.outputArtifact;
63+
const buildStage = pipeline.addStage('build');
64+
const buildAction = project.addBuildToPipeline(buildStage, 'CodeBuild');
65+
const synthesizedApp = buildAction.outputArtifact;
5866

5967
// Optionally, self-update the pipeline stack
6068
const selfUpdateStage = pipeline.addStage('SelfUpdate');
@@ -69,25 +77,39 @@ const deployStage = pipeline.addStage('Deploy');
6977
const serviceStackA = new MyServiceStackA(app, 'ServiceStackA', { /* ... */ });
7078
const serviceStackB = new MyServiceStackB(app, 'ServiceStackB', { /* ... */ });
7179
// Add actions to deploy the stacks in the deploy stage:
72-
new cicd.PipelineDeployStackAction(pipelineStack, 'DeployServiceStackA', {
80+
const deployServiceAAction = new cicd.PipelineDeployStackAction(pipelineStack, 'DeployServiceStackA', {
7381
stage: deployStage,
7482
stack: serviceStackA,
7583
inputArtifact: synthesizedApp,
84+
// See the note below for details about this option.
85+
adminPermissions: false,
7686
});
77-
new cicd.PipelineDeployStackAction(pipelineStack, 'DeployServiceStackB', {
87+
88+
// Add the necessary permissions for you service deploy action. This role is
89+
// is passed to CloudFormation and needs the permissions necessary to deploy
90+
// stack. Alternatively you can enable [Administrator](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_job-functions.html#jf_administrator) permissions above,
91+
// users should understand the privileged nature of this role.
92+
deployServiceAAction.addToRolePolicy(
93+
new iam.PolicyStatement()
94+
.addAction('service:SomeAction')
95+
.addResource(myResource.myResourceArn)
96+
// add more Action(s) and/or Resource(s) here, as needed
97+
);
98+
const deployServiceBAction = new cicd.PipelineDeployStackAction(pipelineStack, 'DeployServiceStackB', {
7899
stage: deployStage,
79100
stack: serviceStackB,
80101
inputArtifact: synthesizedApp,
81102
createChangeSetRunOrder: 998,
82-
});
103+
adminPermissions: true, // no need to modify the role with admin
104+
});
83105
```
84106

85107
#### `buildspec.yml`
86-
The `PipelineDeployStackAction` expects it's `inputArtifact` to contain the result of synthesizing a CDK App using the
87-
`cdk synth -o <directory>` command.
108+
The repository can contain a file at the root level named `buildspec.yml`, or
109+
you can in-line the buildspec. Note that `buildspec.yaml` is not compatible.
88110

89-
For example, a *TypeScript* or *Javascript* CDK App can add the following `buildspec.yml` at the root of the repository
90-
configured in the `Source` stage:
111+
For example, a *TypeScript* or *Javascript* CDK App can add the following `buildspec.yml`
112+
at the root of the repository:
91113

92114
```yml
93115
version: 0.2
@@ -109,3 +131,8 @@ artifacts:
109131
base-directory: dist
110132
files: '**/*'
111133
```
134+
135+
The `PipelineDeployStackAction` expects it's `inputArtifact` to contain the result of
136+
synthesizing a CDK App using the `cdk synth -o <directory>`.
137+
138+

Diff for: packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts

+80-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
21
import cfn = require('@aws-cdk/aws-cloudformation');
32
import codepipeline = require('@aws-cdk/aws-codepipeline-api');
3+
import iam = require('@aws-cdk/aws-iam');
44
import cdk = require('@aws-cdk/cdk');
55
import cxapi = require('@aws-cdk/cx-api');
66

@@ -41,6 +41,47 @@ export interface PipelineDeployStackActionProps {
4141
* @default ``createChangeSetRunOrder + 1``
4242
*/
4343
executeChangeSetRunOrder?: number;
44+
45+
/**
46+
* IAM role to assume when deploying changes.
47+
*
48+
* If not specified, a fresh role is created. The role is created with zero
49+
* permissions unless `adminPermissions` is true, in which case the role will have
50+
* admin permissions.
51+
*
52+
* @default A fresh role with admin or no permissions (depending on the value of `adminPermissions`).
53+
*/
54+
role?: iam.Role;
55+
56+
/**
57+
* Acknowledge certain changes made as part of deployment
58+
*
59+
* For stacks that contain certain resources, explicit acknowledgement that AWS CloudFormation
60+
* might create or update those resources. For example, you must specify AnonymousIAM if your
61+
* stack template contains AWS Identity and Access Management (IAM) resources. For more
62+
* information
63+
*
64+
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities
65+
* @default AnonymousIAM, unless `adminPermissions` is true
66+
*/
67+
capabilities?: cfn.CloudFormationCapabilities;
68+
69+
/**
70+
* Whether to grant admin permissions to CloudFormation while deploying this template.
71+
*
72+
* Setting this to `true` affects the defaults for `role` and `capabilities`, if you
73+
* don't specify any alternatives.
74+
*
75+
* The default role that will be created for you will have admin (i.e., `*`)
76+
* permissions on all resources, and the deployment will have named IAM
77+
* capabilities (i.e., able to create all IAM resources).
78+
*
79+
* This is a shorthand that you can use if you fully trust the templates that
80+
* are deployed in this pipeline. If you want more fine-grained permissions,
81+
* use `addToRolePolicy` and `capabilities` to control what the CloudFormation
82+
* deployment is allowed to do.
83+
*/
84+
adminPermissions: boolean;
4485
}
4586

4687
/**
@@ -52,6 +93,12 @@ export interface PipelineDeployStackActionProps {
5293
* CodePipeline is hosted.
5394
*/
5495
export class PipelineDeployStackAction extends cdk.Construct {
96+
97+
/**
98+
* The role used by CloudFormation for the deploy action
99+
*/
100+
public readonly role: iam.Role;
101+
55102
private readonly stack: cdk.Stack;
56103

57104
constructor(parent: cdk.Construct, id: string, props: PipelineDeployStackActionProps) {
@@ -72,13 +119,18 @@ export class PipelineDeployStackAction extends cdk.Construct {
72119
this.stack = props.stack;
73120
const changeSetName = props.changeSetName || 'CDK-CodePipeline-ChangeSet';
74121

75-
new cfn.PipelineCreateReplaceChangeSetAction(this, 'ChangeSet', {
122+
const capabilities = cfnCapabilities(props.adminPermissions, props.capabilities);
123+
const changeSetAction = new cfn.PipelineCreateReplaceChangeSetAction(this, 'ChangeSet', {
76124
changeSetName,
77125
runOrder: createChangeSetRunOrder,
78126
stackName: props.stack.name,
79127
stage: props.stage,
80128
templatePath: props.inputArtifact.atPath(`${props.stack.name}.template.yaml`),
129+
adminPermissions: props.adminPermissions,
130+
role: props.role,
131+
capabilities,
81132
});
133+
this.role = changeSetAction.role;
82134

83135
new cfn.PipelineExecuteChangeSetAction(this, 'Execute', {
84136
changeSetName,
@@ -97,4 +149,30 @@ export class PipelineDeployStackAction extends cdk.Construct {
97149
}
98150
return result;
99151
}
152+
153+
/**
154+
* Add policy statements to the role deploying the stack.
155+
*
156+
* This role is passed to CloudFormation and must have the IAM permissions
157+
* necessary to deploy the stack or you can grant this role `adminPermissions`
158+
* by using that option during creation. If you do not grant
159+
* `adminPermissions` you need to identify the proper statements to add to
160+
* this role based on the CloudFormation Resources in your stack.
161+
*/
162+
public addToRolePolicy(statement: iam.PolicyStatement) {
163+
this.role.addToPolicy(statement);
164+
}
165+
}
166+
167+
function cfnCapabilities(adminPermissions: boolean, capabilities?: cfn.CloudFormationCapabilities): cfn.CloudFormationCapabilities {
168+
if (adminPermissions && capabilities === undefined) {
169+
// admin true default capability to NamedIAM
170+
return cfn.CloudFormationCapabilities.NamedIAM;
171+
} else if (capabilities === undefined) {
172+
// else capabilities are undefined set AnonymousIAM
173+
return cfn.CloudFormationCapabilities.AnonymousIAM;
174+
} else {
175+
// else capabilities are defined use them
176+
return capabilities;
177+
}
100178
}

Diff for: packages/@aws-cdk/app-delivery/package.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@
3535
"@aws-cdk/aws-cloudformation": "^0.18.1",
3636
"@aws-cdk/aws-codebuild": "^0.18.1",
3737
"@aws-cdk/aws-codepipeline-api": "^0.18.1",
38+
"@aws-cdk/aws-iam": "^0.18.1",
3839
"@aws-cdk/cdk": "^0.18.1",
3940
"@aws-cdk/cx-api": "^0.18.1"
4041
},
4142
"devDependencies": {
43+
"@aws-cdk/assert": "^0.18.1",
4244
"@aws-cdk/aws-codepipeline": "^0.18.1",
4345
"@aws-cdk/aws-s3": "^0.18.1",
4446
"cdk-build-tools": "^0.18.1",
@@ -62,7 +64,9 @@
6264
"cdk"
6365
],
6466
"peerDependencies": {
67+
"@aws-cdk/aws-cloudformation": "^0.18.1",
6568
"@aws-cdk/aws-codepipeline-api": "^0.18.1",
69+
"@aws-cdk/aws-iam": "^0.18.1",
6670
"@aws-cdk/cdk": "^0.18.1"
6771
}
68-
}
72+
}

Diff for: packages/@aws-cdk/app-delivery/test/integ.cicd.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import cfn = require('@aws-cdk/aws-cloudformation');
12
import code = require('@aws-cdk/aws-codepipeline');
23
import s3 = require('@aws-cdk/aws-s3');
34
import cdk = require('@aws-cdk/cdk');
@@ -16,13 +17,16 @@ const source = new code.GitHubSourceAction(stack, 'GitHub', {
1617
oauthToken: new cdk.Secret('DummyToken'),
1718
pollForSourceChanges: true,
1819
});
20+
const stage = pipeline.addStage('Deploy');
1921
new cicd.PipelineDeployStackAction(stack, 'DeployStack', {
20-
stage: pipeline.addStage('Deploy'),
22+
stage,
2123
stack,
2224
changeSetName: 'CICD-ChangeSet',
2325
createChangeSetRunOrder: 10,
2426
executeChangeSetRunOrder: 999,
2527
inputArtifact: source.outputArtifact,
28+
adminPermissions: false,
29+
capabilities: cfn.CloudFormationCapabilities.None,
2630
});
2731

2832
app.run();

0 commit comments

Comments
 (0)