Skip to content

Commit

Permalink
feat(app-delivery) IAM policy for deploy stack
Browse files Browse the repository at this point in the history
 * The changeset and apply changeset 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.
  • Loading branch information
moofish32 committed Nov 20, 2018
1 parent 127bb59 commit 51401d3
Show file tree
Hide file tree
Showing 20 changed files with 461 additions and 98 deletions.
36 changes: 31 additions & 5 deletions packages/@aws-cdk/app-delivery/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,17 @@ const source = new codepipeline.GitHubSourceAction(pipelineStack, 'GitHub', {
/* ... */
});
const project = new codebuild.PipelineProject(pipelineStack, 'CodeBuild', {
/* ... */
/**
* Choose an environment configuration that meets your use case. For NodeJS
* this might be
* environment: {
* buildImage: codebuild.LinuxBuildImage.UBUNTU_14_04_NODEJS_10_1_0,
* },
*/
});
const synthesizedApp = project.outputArtifact;
const buildStage = pipeline.addStage('build');
const buildAction = project.addBuildToPipeline(buildStage, 'CodeBuild');
const synthesizedApp = buildAction.outputArtifact;

// Optionally, self-update the pipeline stack
const selfUpdateStage = pipeline.addStage('SelfUpdate');
Expand All @@ -69,26 +77,44 @@ const deployStage = pipeline.addStage('Deploy');
const serviceStackA = new MyServiceStackA(app, 'ServiceStackA', { /* ... */ });
const serviceStackB = new MyServiceStackB(app, 'ServiceStackB', { /* ... */ });
// Add actions to deploy the stacks in the deploy stage:
new cicd.PipelineDeployStackAction(pipelineStack, 'DeployServiceStackA', {
const deployServiceAAction = new cicd.PipelineDeployStackAction(pipelineStack, 'DeployServiceStackA', {
stage: deployStage,
stack: serviceStackA,
inputArtifact: synthesizedApp,
// See the note below for details about this option.
adminPermissions: false,
});
new cicd.PipelineDeployStackAction(pipelineStack, 'DeployServiceStackB', {

// Add the necessary permissions for you service deploy action. This role is
// is passed to CloudFormation and needs the permissions necessary to deploy
// stack. Alternatively you can enable [Administrator](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_job-functions.html#jf_administrator) permissions above,
// users should understand the privileged nature of this role.
deployServiceAAction.addToRolePolicy(
// new iam.PolicyStatement().
// ... addAction('actions that you need').
// add resource
);
const deployServiceBAction = new cicd.PipelineDeployStackAction(pipelineStack, 'DeployServiceStackB', {
stage: deployStage,
stack: serviceStackB,
inputArtifact: synthesizedApp,
createChangeSetRunOrder: 998,
});
adminPermissions: true, // no need to modify the role with admin
});
```

#### `buildspec.yml`
The repository can contain a file at the root level named `buildspec.yml`, or
you can in-line the buildspec. Note that `buildspec.yaml` is not compatible.

The `PipelineDeployStackAction` expects it's `inputArtifact` to contain the result of synthesizing a CDK App using the
`cdk synth -o <directory>` command.

For example, a *TypeScript* or *Javascript* CDK App can add the following `buildspec.yml` at the root of the repository
configured in the `Source` stage:

Example contents of `buildspec.yml`.

```yml
version: 0.2
phases:
Expand Down
82 changes: 80 additions & 2 deletions packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import cfn = require('@aws-cdk/aws-cloudformation');
import codepipeline = require('@aws-cdk/aws-codepipeline-api');
import iam = require('@aws-cdk/aws-iam');
import cdk = require('@aws-cdk/cdk');
import cxapi = require('@aws-cdk/cx-api');

Expand Down Expand Up @@ -41,6 +41,47 @@ export interface PipelineDeployStackActionProps {
* @default ``createChangeSetRunOrder + 1``
*/
executeChangeSetRunOrder?: number;

/**
* IAM role to assume when deploying changes.
*
* If not specified, a fresh role is created. The role is created with zero
* permissions unless `adminPermissions` is true, in which case the role will have
* admin permissions.
*
* @default A fresh role with admin or no permissions (depending on the value of `adminPermissions`).
*/
role?: iam.Role;

/**
* Acknowledge certain changes made as part of deployment
*
* For stacks that contain certain resources, explicit acknowledgement that AWS CloudFormation
* might create or update those resources. For example, you must specify AnonymousIAM if your
* stack template contains AWS Identity and Access Management (IAM) resources. For more
* information
*
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-iam-template.html#using-iam-capabilities
* @default AnonymousIAM, unless `adminPermissions` is true
*/
capabilities?: cfn.CloudFormationCapabilities;

/**
* Whether to grant admin permissions to CloudFormation while deploying this template.
*
* Setting this to `true` affects the defaults for `role` and `capabilities`, if you
* don't specify any alternatives.
*
* The default role that will be created for you will have admin (i.e., `*`)
* permissions on all resources, and the deployment will have named IAM
* capabilities (i.e., able to create all IAM resources).
*
* This is a shorthand that you can use if you fully trust the templates that
* are deployed in this pipeline. If you want more fine-grained permissions,
* use `addToRolePolicy` and `capabilities` to control what the CloudFormation
* deployment is allowed to do.
*/
adminPermissions: boolean;
}

/**
Expand All @@ -52,6 +93,12 @@ export interface PipelineDeployStackActionProps {
* CodePipeline is hosted.
*/
export class PipelineDeployStackAction extends cdk.Construct {

/**
* The role used by CloudFormation for the deploy action
*/
public readonly role: iam.Role;

private readonly stack: cdk.Stack;

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

new cfn.PipelineCreateReplaceChangeSetAction(this, 'ChangeSet', {
const capabilities = cfnCapabilities(props.adminPermissions, props.capabilities);
const changeSetAction = new cfn.PipelineCreateReplaceChangeSetAction(this, 'ChangeSet', {
changeSetName,
runOrder: createChangeSetRunOrder,
stackName: props.stack.name,
stage: props.stage,
templatePath: props.inputArtifact.atPath(`${props.stack.name}.template.yaml`),
adminPermissions: props.adminPermissions,
role: props.role,
capabilities,
});
this.role = changeSetAction.role;

new cfn.PipelineExecuteChangeSetAction(this, 'Execute', {
changeSetName,
Expand All @@ -97,4 +149,30 @@ export class PipelineDeployStackAction extends cdk.Construct {
}
return result;
}

/**
* Add policy statements to the role deploying the stack.
*
* This role is passed to CloudFormation and must have the IAM permissions
* necessary to deploy the stack or you can grant this role `adminPermissions`
* by using that option during creation. If you do not grant
* `adminPermissions` you need to identify the proper statements to add to
* this role based on the CloudFormation Resources in your stack.
*/
public addToRolePolicy(statement: iam.PolicyStatement) {
this.role.addToPolicy(statement);
}
}

function cfnCapabilities(adminPermissions: boolean, capabilities?: cfn.CloudFormationCapabilities): cfn.CloudFormationCapabilities {
if (adminPermissions && capabilities === undefined) {
// admin true default capability to NamedIAM
return cfn.CloudFormationCapabilities.NamedIAM;
} else if (capabilities === undefined) {
// else capabilities are undefined set AnonymousIAM
return cfn.CloudFormationCapabilities.AnonymousIAM;
} else {
// else capabilities are defined use them
return capabilities;
}
}
6 changes: 5 additions & 1 deletion packages/@aws-cdk/app-delivery/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@
"@aws-cdk/aws-cloudformation": "^0.18.0",
"@aws-cdk/aws-codebuild": "^0.18.0",
"@aws-cdk/aws-codepipeline-api": "^0.18.0",
"@aws-cdk/aws-iam": "^0.18.0",
"@aws-cdk/cdk": "^0.18.0",
"@aws-cdk/cx-api": "^0.18.0"
},
"devDependencies": {
"@aws-cdk/assert": "^0.18.0",
"@aws-cdk/aws-codepipeline": "^0.18.0",
"@aws-cdk/aws-s3": "^0.18.0",
"cdk-build-tools": "^0.18.0",
Expand All @@ -62,7 +64,9 @@
"cdk"
],
"peerDependencies": {
"@aws-cdk/aws-cloudformation": "^0.18.0",
"@aws-cdk/aws-codepipeline-api": "^0.18.0",
"@aws-cdk/aws-iam": "^0.18.0",
"@aws-cdk/cdk": "^0.18.0"
}
}
}
6 changes: 5 additions & 1 deletion packages/@aws-cdk/app-delivery/test/integ.cicd.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import cfn = require('@aws-cdk/aws-cloudformation');
import code = require('@aws-cdk/aws-codepipeline');
import s3 = require('@aws-cdk/aws-s3');
import cdk = require('@aws-cdk/cdk');
Expand All @@ -16,13 +17,16 @@ const source = new code.GitHubSourceAction(stack, 'GitHub', {
oauthToken: new cdk.Secret('DummyToken'),
pollForSourceChanges: true,
});
const stage = pipeline.addStage('Deploy');
new cicd.PipelineDeployStackAction(stack, 'DeployStack', {
stage: pipeline.addStage('Deploy'),
stage,
stack,
changeSetName: 'CICD-ChangeSet',
createChangeSetRunOrder: 10,
executeChangeSetRunOrder: 999,
inputArtifact: source.outputArtifact,
adminPermissions: false,
capabilities: cfn.CloudFormationCapabilities.None,
});

app.run();
Loading

0 comments on commit 51401d3

Please sign in to comment.