Skip to content

Commit

Permalink
feat(pipeline): allow enabling KMS key rotation for cross-region Stac…
Browse files Browse the repository at this point in the history
…ks (#16468)

As suggested by @skinny85 I created an updated PR as successor of #14381

How does it work for v2. Do I need to create another PR for `v2-main`?

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
pboeder authored Sep 20, 2021
1 parent 1e9d8be commit 2a629dd
Show file tree
Hide file tree
Showing 15 changed files with 155 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -792,4 +792,4 @@
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1622,4 +1622,4 @@
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -788,4 +788,4 @@
]
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -393,4 +393,4 @@
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -731,4 +731,4 @@
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -815,4 +815,4 @@
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -545,4 +545,4 @@
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -842,4 +842,4 @@
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -540,4 +540,4 @@
"DeletionPolicy": "Retain"
}
}
}
}
11 changes: 11 additions & 0 deletions packages/@aws-cdk/aws-codepipeline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', {
});
```

If you want to enable key rotation for the generated KMS keys,
you can configure it by passing `enableKeyRotation: true` when creating the pipeline.
Note that key rotation will incur an additional cost of **$1/month**.

```ts
const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', {
// ...
enableKeyRotation: true,
});
```

## Stages

You can provide Stages when creating the Pipeline:
Expand Down
26 changes: 22 additions & 4 deletions packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export interface PipelineProps {
readonly stages?: StageProps[];

/**
* Create KMS keys for cross-account deployments
* Create KMS keys for cross-account deployments.
*
* This controls whether the pipeline is enabled for cross-account deployments.
*
Expand All @@ -126,6 +126,16 @@ export interface PipelineProps {
* @default true
*/
readonly crossAccountKeys?: boolean;

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

abstract class PipelineBase extends Resource implements IPipeline {
Expand Down Expand Up @@ -317,6 +327,7 @@ export class Pipeline extends PipelineBase {
private readonly _crossRegionSupport: { [region: string]: CrossRegionSupport } = {};
private readonly _crossAccountSupport: { [account: string]: Stack } = {};
private readonly crossAccountKeys: boolean;
private readonly enableKeyRotation?: boolean;

constructor(scope: Construct, id: string, props: PipelineProps = {}) {
super(scope, id, {
Expand All @@ -330,9 +341,14 @@ export class Pipeline extends PipelineBase {
throw new Error('Only one of artifactBucket and crossRegionReplicationBuckets can be specified!');
}


// @deprecated(v2): switch to default false
this.crossAccountKeys = props.crossAccountKeys ?? true;
this.enableKeyRotation = props.enableKeyRotation;

// Cross account keys must be set for key rotation to be enabled
if (this.enableKeyRotation && !this.crossAccountKeys) {
throw new Error("Setting 'enableKeyRotation' to true also requires 'crossAccountKeys' to be enabled");
}

// If a bucket has been provided, use it - otherwise, create a bucket.
let propsBucket = this.getArtifactBucketFromProps(props);
Expand All @@ -345,6 +361,7 @@ export class Pipeline extends PipelineBase {
// remove the key - there is a grace period of a few days before it's gone for good,
// that should be enough for any emergency access to the bucket artifacts
removalPolicy: RemovalPolicy.DESTROY,
enableKeyRotation: this.enableKeyRotation,
});
// add an alias to make finding the key in the console easier
new kms.Alias(this, 'ArtifactsBucketEncryptionKeyAlias', {
Expand Down Expand Up @@ -573,8 +590,7 @@ export class Pipeline extends PipelineBase {
return crossRegionSupport;
}

private createSupportResourcesForRegion(otherStack: Stack | undefined, actionRegion: string):
CrossRegionSupport {
private createSupportResourcesForRegion(otherStack: Stack | undefined, actionRegion: string): CrossRegionSupport {
// if we have a stack from the resource passed - use that!
if (otherStack) {
// check if the stack doesn't have this magic construct already
Expand All @@ -583,6 +599,7 @@ export class Pipeline extends PipelineBase {
if (!crossRegionSupportConstruct) {
crossRegionSupportConstruct = new CrossRegionSupportConstruct(otherStack, id, {
createKmsKey: this.crossAccountKeys,
enableKeyRotation: this.enableKeyRotation,
});
}

Expand All @@ -609,6 +626,7 @@ export class Pipeline extends PipelineBase {
account: pipelineAccount,
synthesizer: this.getCrossRegionSupportSynthesizer(),
createKmsKey: this.crossAccountKeys,
enableKeyRotation: this.enableKeyRotation,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ export interface CrossRegionSupportConstructProps {
* @default true
*/
readonly createKmsKey?: boolean;

/**
* Enables KMS key rotation for cross-account keys.
*
* @default - false (key rotation is disabled)
*/
readonly enableKeyRotation?: boolean;
}

export class CrossRegionSupportConstruct extends Construct {
Expand All @@ -58,6 +65,7 @@ export class CrossRegionSupportConstruct extends Construct {
if (createKmsKey) {
const encryptionKey = new kms.Key(this, 'CrossRegionCodePipelineReplicationBucketEncryptionKey', {
removalPolicy: cdk.RemovalPolicy.DESTROY,
enableKeyRotation: props.enableKeyRotation,
});
encryptionAlias = new AliasWithShorterGeneratedName(this, 'CrossRegionCodePipelineReplicationBucketEncryptionAlias', {
targetKey: encryptionKey,
Expand Down Expand Up @@ -106,6 +114,13 @@ export interface CrossRegionSupportStackProps {
* @default true
*/
readonly createKmsKey?: boolean;

/**
* Enables KMS key rotation for cross-account keys.
*
* @default - false (key rotation is disabled)
*/
readonly enableKeyRotation?: boolean;
}

/**
Expand All @@ -130,6 +145,7 @@ export class CrossRegionSupportStack extends cdk.Stack {

const crossRegionSupportConstruct = new CrossRegionSupportConstruct(this, 'Default', {
createKmsKey: props.createKmsKey,
enableKeyRotation: props.enableKeyRotation,
});
this.replicationBucket = crossRegionSupportConstruct.replicationBucket;
}
Expand Down
35 changes: 35 additions & 0 deletions packages/@aws-cdk/aws-codepipeline/test/pipeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,41 @@ describe('', () => {


});

test('does not allow enabling key rotation if cross account keys have been disabled', () => {
const app = new cdk.App();
const stack = new cdk.Stack(app, 'PipelineStack');

expect(() => {
new codepipeline.Pipeline(stack, 'Pipeline', {
crossAccountKeys: false,
enableKeyRotation: true,
});
}).toThrow("Setting 'enableKeyRotation' to true also requires 'crossAccountKeys' to be enabled");
});

test("enabling key rotation sets 'EnableKeyRotation' to 'true' in the main generated KMS key", () => {
const app = new cdk.App();
const stack = new cdk.Stack(app, 'PipelineStack');
const sourceOutput = new codepipeline.Artifact();
new codepipeline.Pipeline(stack, 'Pipeline', {
enableKeyRotation: true,
stages: [
{
stageName: 'Source',
actions: [new FakeSourceAction({ actionName: 'Source', output: sourceOutput })],
},
{
stageName: 'Build',
actions: [new FakeBuildAction({ actionName: 'Build', input: sourceOutput })],
},
],
});

expect(stack).toHaveResourceLike('AWS::KMS::Key', {
'EnableKeyRotation': true,
});
});
});
});
});
Expand Down
17 changes: 17 additions & 0 deletions packages/@aws-cdk/pipelines/lib/legacy/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,19 @@ export interface CdkPipelineProps {
readonly crossAccountKeys?: boolean;
// @deprecated(v2): switch to default false


/**
* Enables KMS key rotation for cross-account keys.
*
* Cannot be set if `crossAccountKeys` was set to `false`.
*
* Key rotation costs $1/month when enabled.
*
* @default - false (key rotation is disabled)
*/
readonly enableKeyRotation?: boolean;


/**
* CDK CLI version to use in pipeline
*
Expand Down Expand Up @@ -221,12 +234,16 @@ export class CdkPipeline extends CoreConstruct {
if (props.crossAccountKeys !== undefined) {
throw new Error('Cannot set \'crossAccountKeys\' if an existing CodePipeline is given using \'codePipeline\'');
}
if (props.enableKeyRotation !== undefined) {
throw new Error('Cannot set \'enableKeyRotation\' if an existing CodePipeline is given using \'codePipeline\'');
}

this._pipeline = props.codePipeline;
} else {
this._pipeline = new codepipeline.Pipeline(this, 'Pipeline', {
pipelineName: props.pipelineName,
crossAccountKeys: props.crossAccountKeys,
enableKeyRotation: props.enableKeyRotation,
restartExecutionOnUpdate: true,
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as codePipeline from '@aws-cdk/aws-codepipeline';
import * as cdk from '@aws-cdk/core';
import * as cdkp from '../../lib';

test('Does not allow setting a pipelineName if an existing CodePipeline is given', () => {
const app = new cdk.App();
const stack = new cdk.Stack(app, 'PipelineStack');
const existingCodePipeline = new codePipeline.Pipeline(stack, 'CustomCodePipeline');

expect(() => {
new cdkp.CdkPipeline(stack, 'CDKPipeline', {
pipelineName: 'CustomPipelineName',
codePipeline: existingCodePipeline,
cloudAssemblyArtifact: new codePipeline.Artifact(),
});
}).toThrow("Cannot set 'pipelineName' if an existing CodePipeline is given using 'codePipeline'");
});

test('Does not allow enabling crossAccountKeys if an existing CodePipeline is given', () => {
const app = new cdk.App();
const stack = new cdk.Stack(app, 'PipelineStack');
const existingCodePipeline = new codePipeline.Pipeline(stack, 'CustomCodePipeline');

expect(() => {
new cdkp.CdkPipeline(stack, 'CDKPipeline', {
crossAccountKeys: true,
codePipeline: existingCodePipeline,
cloudAssemblyArtifact: new codePipeline.Artifact(),
});
}).toThrow("Cannot set 'crossAccountKeys' if an existing CodePipeline is given using 'codePipeline'");
});

test('Does not allow enabling key rotation if an existing CodePipeline is given', () => {
const app = new cdk.App();
const stack = new cdk.Stack(app, 'PipelineStack');
const existingCodePipeline = new codePipeline.Pipeline(stack, 'CustomCodePipeline');

expect(() => {
new cdkp.CdkPipeline(stack, 'CDKPipeline', {
enableKeyRotation: true,
codePipeline: existingCodePipeline,
cloudAssemblyArtifact: new codePipeline.Artifact(),
});
}).toThrow("Cannot set 'enableKeyRotation' if an existing CodePipeline is given using 'codePipeline'");
});

0 comments on commit 2a629dd

Please sign in to comment.