Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(app-delivery) IAM policy for deploy stack #1165

Merged
merged 1 commit into from
Nov 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 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,25 +77,39 @@ 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('service:SomeAction')
.addResource(myResource.myResourceArn)
// add more Action(s) and/or Resource(s) here, as needed
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't comment out the whole thing, but instead just the usecase-specific part:

deployServiceAAction.addToRolePolicy(
  new iam.PolicyStatement()
    .addAction('service:SomeAction')
    .addResource(myResource.myResourceArn)
    // add more Action(s) and/or Resource(s) here, as needed
);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

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
});
```
skinny85 marked this conversation as resolved.
Show resolved Hide resolved

#### `buildspec.yml`
The `PipelineDeployStackAction` expects it's `inputArtifact` to contain the result of synthesizing a CDK App using the
`cdk synth -o <directory>` command.
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.

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:
For example, a *TypeScript* or *Javascript* CDK App can add the following `buildspec.yml`
at the root of the repository:

```yml
version: 0.2
Expand All @@ -109,3 +131,8 @@ artifacts:
base-directory: dist
files: '**/*'
```

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


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;
skinny85 marked this conversation as resolved.
Show resolved Hide resolved
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.1",
"@aws-cdk/aws-codebuild": "^0.18.1",
"@aws-cdk/aws-codepipeline-api": "^0.18.1",
"@aws-cdk/aws-iam": "^0.18.1",
"@aws-cdk/cdk": "^0.18.1",
"@aws-cdk/cx-api": "^0.18.1"
},
"devDependencies": {
"@aws-cdk/assert": "^0.18.1",
"@aws-cdk/aws-codepipeline": "^0.18.1",
"@aws-cdk/aws-s3": "^0.18.1",
"cdk-build-tools": "^0.18.1",
Expand All @@ -62,7 +64,9 @@
"cdk"
],
"peerDependencies": {
"@aws-cdk/aws-cloudformation": "^0.18.1",
"@aws-cdk/aws-codepipeline-api": "^0.18.1",
"@aws-cdk/aws-iam": "^0.18.1",
"@aws-cdk/cdk": "^0.18.1"
}
}
}
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