Skip to content
3 changes: 3 additions & 0 deletions packages/@aws-cdk/pipelines/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -525,12 +525,15 @@ pipeline.
This will encrypt the artifact bucket(s), but incurs a cost for maintaining the
KMS key.

You may also wish to enable automatic key rotation for the created KMS key.

Example:

```ts
const pipeline = new pipelines.CodePipeline(this, 'Pipeline', {
// Encrypt artifacts, required for cross-account deployments
crossAccountKeys: true,
enableKeyRotation: true, // optional
synth: new pipelines.ShellStep('Synth', {
input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', {
connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });',
Expand Down
14 changes: 14 additions & 0 deletions packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,16 @@ export interface CodePipelineProps {
* @default true
*/
readonly useChangeSets?: boolean;

/**
* Enable KMS key rotation for the generated KMS keys.
*
* By default KMS key rotation is disabled, but will add
* additional costs when enabled.
*
* @default - false (key rotation is disabled)
*/
readonly enableKeyRotation?: boolean;
}

/**
Expand Down Expand Up @@ -381,6 +391,9 @@ export class CodePipeline extends PipelineBase {
if (this.props.crossAccountKeys !== undefined) {
throw new Error('Cannot set \'crossAccountKeys\' if an existing CodePipeline is given using \'codePipeline\'');
}
if (this.props.enableKeyRotation !== undefined) {
throw new Error('Cannot set \'enableKeyRotation\' if an existing CodePipeline is given using \'codePipeline\'');
}
if (this.props.reuseCrossRegionSupportStacks !== undefined) {
throw new Error('Cannot set \'reuseCrossRegionSupportStacks\' if an existing CodePipeline is given using \'codePipeline\'');
}
Expand All @@ -398,6 +411,7 @@ export class CodePipeline extends PipelineBase {
// to happen only after the builds of the latest pipeline definition).
restartExecutionOnUpdate: true,
role: this.props.role,
enableKeyRotation: this.props.enableKeyRotation,
});
}

Expand Down
77 changes: 72 additions & 5 deletions packages/@aws-cdk/pipelines/test/codepipeline/codepipeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ describe('Providing codePipeline parameter and prop(s) of codePipeline parameter
pipelineName: 'randomName',
}).create()).toThrowError('Cannot set \'pipelineName\' if an existing CodePipeline is given using \'codePipeline\'');
});
test('Providing codePipeline parameter and enableKeyRotation parameter should throw error', () => {
expect(() => new CodePipelinePropsCheckTest(app, 'CodePipeline', {
enableKeyRotation: true,
}).create()).toThrowError('Cannot set \'enableKeyRotation\' if an existing CodePipeline is given using \'codePipeline\'');
});
test('Providing codePipeline parameter and crossAccountKeys parameter should throw error', () => {
expect(() => new CodePipelinePropsCheckTest(app, 'CodePipeline', {
crossAccountKeys: true,
Expand Down Expand Up @@ -192,6 +197,60 @@ test('CodeBuild action role has the right AssumeRolePolicyDocument', () => {
});
});

test('CodePipeline throws when key rotation is enabled without enabling cross account keys', ()=>{
const pipelineStack = new cdk.Stack(app, 'PipelineStack', { env: PIPELINE_ENV });
const repo = new ccommit.Repository(pipelineStack, 'Repo', {
repositoryName: 'MyRepo',
});
const cdkInput = cdkp.CodePipelineSource.codeCommit(
repo,
'main',
);

expect(() => new CodePipeline(pipelineStack, 'Pipeline', {
enableKeyRotation: true,
synth: new cdkp.ShellStep('Synth', {
input: cdkInput,
installCommands: ['npm ci'],
commands: [
'npm run build',
'npx cdk synth',
],
}),
}).buildPipeline()).toThrowError('Setting \'enableKeyRotation\' to true also requires \'crossAccountKeys\' to be enabled');
});


test('CodePipeline enables key rotation on cross account keys', ()=>{
const pipelineStack = new cdk.Stack(app, 'PipelineStack', { env: PIPELINE_ENV });
const repo = new ccommit.Repository(pipelineStack, 'Repo', {
repositoryName: 'MyRepo',
});
const cdkInput = cdkp.CodePipelineSource.codeCommit(
repo,
'main',
);

new CodePipeline(pipelineStack, 'Pipeline', {
enableKeyRotation: true,
crossAccountKeys: true, // requirement of key rotation
Copy link
Contributor

Choose a reason for hiding this comment

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

Given this dependency, this should be enforced in code. If someone adds rotation without setting this to true, what's the behavior? We don't want a deploy time failure or a silent failure here.

Copy link
Contributor Author

@tkglaser tkglaser Jan 12, 2023

Choose a reason for hiding this comment

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

This is already enforced by the underlying Pipeline construct. Added a unit test to capture that.

synth: new cdkp.ShellStep('Synth', {
input: cdkInput,
installCommands: ['npm ci'],
commands: [
'npm run build',
'npx cdk synth',
],
}),
});

const template = Template.fromStack(pipelineStack);

template.hasResourceProperties('AWS::KMS::Key', {
EnableKeyRotation: true,
});
});

test('CodePipeline supports use of existing role', () => {
const pipelineStack = new cdk.Stack(app, 'PipelineStack', { env: PIPELINE_ENV });
const repo = new ccommit.Repository(pipelineStack, 'Repo', {
Expand Down Expand Up @@ -358,6 +417,7 @@ class ReuseStack extends cdk.Stack {
interface CodePipelineStackProps extends cdk.StackProps {
pipelineName?: string;
crossAccountKeys?: boolean;
enableKeyRotation?: boolean;
reuseCrossRegionSupportStacks?: boolean;
role?: iam.IRole;
}
Expand All @@ -379,21 +439,28 @@ class CodePipelinePropsCheckTest extends cdk.Stack {
if (this.cProps.crossAccountKeys !== undefined) {
new cdkp.CodePipeline(this, 'CodePipeline2', {
crossAccountKeys: this.cProps.crossAccountKeys,
codePipeline: new Pipeline(this, 'Pipline2'),
codePipeline: new Pipeline(this, 'Pipeline2'),
synth: new cdkp.ShellStep('Synth', { commands: ['ls'] }),
}).buildPipeline();
}
if (this.cProps.reuseCrossRegionSupportStacks !== undefined) {
if (this.cProps.enableKeyRotation !== undefined) {
new cdkp.CodePipeline(this, 'CodePipeline3', {
enableKeyRotation: this.cProps.enableKeyRotation,
codePipeline: new Pipeline(this, 'Pipeline3'),
synth: new cdkp.ShellStep('Synth', { commands: ['ls'] }),
}).buildPipeline();
}
if (this.cProps.reuseCrossRegionSupportStacks !== undefined) {
new cdkp.CodePipeline(this, 'CodePipeline4', {
reuseCrossRegionSupportStacks: this.cProps.reuseCrossRegionSupportStacks,
codePipeline: new Pipeline(this, 'Pipline3'),
codePipeline: new Pipeline(this, 'Pipeline4'),
synth: new cdkp.ShellStep('Synth', { commands: ['ls'] }),
}).buildPipeline();
}
if (this.cProps.role !== undefined) {
new cdkp.CodePipeline(this, 'CodePipeline4', {
new cdkp.CodePipeline(this, 'CodePipeline5', {
role: this.cProps.role,
codePipeline: new Pipeline(this, 'Pipline4'),
codePipeline: new Pipeline(this, 'Pipeline5'),
synth: new cdkp.ShellStep('Synth', { commands: ['ls'] }),
}).buildPipeline();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "29.0.0",
"files": {
"21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": {
"source": {
"path": "LambdaTestDefaultTestDeployAssert1AF2B360.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"Parameters": {
"BootstrapVersion": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "/cdk-bootstrap/hnb659fds/version",
"Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]"
}
},
"Rules": {
"CheckBootstrapVersion": {
"Assertions": [
{
"Assert": {
"Fn::Not": [
{
"Fn::Contains": [
[
"1",
"2",
"3",
"4",
"5"
],
{
"Ref": "BootstrapVersion"
}
]
}
]
},
"AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI."
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"version": "29.0.0",
"files": {
"c1bda7e2c84e91a5658af0b194a4c8918dead52ad783e29acbc3701149e73f45": {
"source": {
"path": "PipelineStack.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "c1bda7e2c84e91a5658af0b194a4c8918dead52ad783e29acbc3701149e73f45.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
}
},
"dockerImages": {}
}
Loading