Skip to content

Commit

Permalink
feat(codepipeline): cross-environment (account+region) actions (#3694)
Browse files Browse the repository at this point in the history
This changes the scaffolding stack logic for the cross-region CodePipelines to include a KMS key and alias as part of it, which are required if an action is simultaneously cross-region and cross-account. We also change to use the KMS key ID instead of the key ARN when rendering the ArtifactStores property.

We also add an alias to the default pipeline artifact bucket.

This required a bunch of changes to the KMS and S3 modules:

* Alias now implements IKey
* Added the keyId property to IKey
* Added removalPolicy property to Alias
* Granting permissions to a key works if the principal belongs to a stack that is a dependent of the key stack
* Allow specifying a key when importing a bucket

Fixes #52
Concerns #1584
Fixes #2517
Fixes #2569
Concerns #3275
Fixes #3138
Fixes #3388
  • Loading branch information
skinny85 committed Sep 11, 2019
1 parent 389af27 commit 69bff3d
Show file tree
Hide file tree
Showing 23 changed files with 838 additions and 136 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,20 @@
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": {
"Type": "AWS::KMS::Alias",
"Properties": {
"AliasName": "alias/codepipeline-awscdkcodepipelinecloudformationpipeline7dbde619",
"TargetKeyId": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
}
},
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"PipelineRoleD68726F7": {
"Type": "AWS::IAM::Role",
"Properties": {
Expand Down Expand Up @@ -379,10 +393,7 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,20 @@
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": {
"Type": "AWS::KMS::Alias",
"Properties": {
"AliasName": "alias/codepipeline-pipelinestackpipeline9db740af",
"TargetKeyId": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
}
},
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"PipelineRoleD68726F7": {
"Type": "AWS::IAM::Role",
"Properties": {
Expand Down Expand Up @@ -504,10 +518,7 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,20 @@
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": {
"Type": "AWS::KMS::Alias",
"Properties": {
"AliasName": "alias/codepipeline-awscdkcodepipelinelambdapipeline87a4b3d3",
"TargetKeyId": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
}
},
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"PipelineRoleD68726F7": {
"Type": "AWS::IAM::Role",
"Properties": {
Expand Down Expand Up @@ -282,10 +296,7 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,20 @@
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": {
"Type": "AWS::KMS::Alias",
"Properties": {
"AliasName": "alias/codepipeline-awscdkcodepipelinealexadeploypipeline961107f5",
"TargetKeyId": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
}
},
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"PipelineRoleD68726F7": {
"Type": "AWS::IAM::Role",
"Properties": {
Expand Down Expand Up @@ -281,10 +295,7 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,20 @@
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": {
"Type": "AWS::KMS::Alias",
"Properties": {
"AliasName": "alias/codepipeline-awscdkcodepipelinecloudformationpipeline7dbde619",
"TargetKeyId": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
}
},
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"PipelineRoleD68726F7": {
"Type": "AWS::IAM::Role",
"Properties": {
Expand Down Expand Up @@ -367,10 +381,7 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,20 @@
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": {
"Type": "AWS::KMS::Alias",
"Properties": {
"AliasName": "alias/codepipeline-awscdkcodepipelinecodecommitcodebuildpipeline9540e1f5",
"TargetKeyId": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
}
},
"UpdateReplacePolicy": "Retain",
"DeletionPolicy": "Retain"
},
"PipelineRoleD68726F7": {
"Type": "AWS::IAM::Role",
"Properties": {
Expand Down Expand Up @@ -579,10 +593,7 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,20 @@
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": {
"Type": "AWS::KMS::Alias",
"Properties": {
"AliasName": "alias/codepipeline-awscdkcodepipelinecodecommitpipelinef780ca18",
"TargetKeyId": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
}
},
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"PipelineRoleD68726F7": {
"Type": "AWS::IAM::Role",
"Properties": {
Expand Down Expand Up @@ -353,10 +367,7 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,20 @@
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"MyPipelineArtifactsBucketEncryptionKeyAlias9D4F8C59": {
"Type": "AWS::KMS::Alias",
"Properties": {
"AliasName": "alias/codepipeline-awscdkpipelineeventtargetmypipeline4ae5d407",
"TargetKeyId": {
"Fn::GetAtt": [
"MyPipelineArtifactsBucketEncryptionKey8BF0A7F3",
"Arn"
]
}
},
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"MyPipelineRoleC0D47CA4": {
"Type": "AWS::IAM::Role",
"Properties": {
Expand Down Expand Up @@ -316,10 +330,7 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Fn::GetAtt": [
"MyPipelineArtifactsBucketEncryptionKey8BF0A7F3",
"Arn"
]
"Ref": "MyPipelineArtifactsBucketEncryptionKey8BF0A7F3"
},
"Type": "KMS"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,20 @@
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"PipelineArtifactsBucketEncryptionKeyAlias5C510EEE": {
"Type": "AWS::KMS::Alias",
"Properties": {
"AliasName": "alias/codepipeline-awscdkcodepipelines3deploypipeline907bf1e7",
"TargetKeyId": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
}
},
"DeletionPolicy": "Retain",
"UpdateReplacePolicy": "Retain"
},
"PipelineRoleD68726F7": {
"Type": "AWS::IAM::Role",
"Properties": {
Expand Down Expand Up @@ -318,10 +332,7 @@
"ArtifactStore": {
"EncryptionKey": {
"Id": {
"Fn::GetAtt": [
"PipelineArtifactsBucketEncryptionKey01D58D69",
"Arn"
]
"Ref": "PipelineArtifactsBucketEncryptionKey01D58D69"
},
"Type": "KMS"
},
Expand Down
58 changes: 55 additions & 3 deletions packages/@aws-cdk/aws-codepipeline/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,18 @@ into a different region than your Pipeline is in.

It works like this:

```ts
```typescript
const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', {
// ...
crossRegionReplicationBuckets: {
// note that a physical name of the replication Bucket must be known at synthesis time
'us-west-1': s3.Bucket.fromBucketName(this, 'UsWest1ReplicationBucket',
'my-us-west-1-replication-bucket'),
'us-west-1': s3.Bucket.fromBucketAttributes(this, 'UsWest1ReplicationBucket', {
bucketName: 'my-us-west-1-replication-bucket',
// optional KMS key
encryptionKey: kms.Key.fromKeyArn(this, 'UsWest1ReplicationKey',
'arn:aws:kms:us-west-1:123456789012:key/1234-5678-9012'
),
}),
},
});

Expand Down Expand Up @@ -128,6 +133,53 @@ $ cdk deploy MyMainStack
See [the AWS docs here](https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-create-cross-region.html)
for more information on cross-region CodePipelines.

#### Creating an encrypted replication bucket

If you're passing a replication bucket created in a different stack,
like this:

```typescript
const replicationStack = new Stack(app, 'ReplicationStack', {
env: {
region: 'us-west-1',
},
});
const key = new kms.Key(replicationStack, 'ReplicationKey');
const replicationBucket = new s3.Bucket(replicationStack, 'ReplicationBucket', {
// like was said above - replication buckets need a set physical name
bucketName: PhysicalName.GENERATE_IF_NEEDED,
encryptionKey: key, // does not work!
});

// later...
new codepipeline.Pipeline(pipelineStack, 'Pipeline', {
crossRegionReplicationBuckets: {
'us-west-1': replicationBucket,
},
});
```

When trying to encrypt it
(and note that if any of the cross-region actions happen to be cross-account as well,
the bucket *has to* be encrypted - otherwise the pipeline will fail at runtime),
you cannot use a key directly - KMS keys don't have physical names,
and so you can't reference them across environments.

In this case, you need to use an alias in place of the key when creating the bucket:

```typescript
const key = new kms.Key(replicationStack, 'ReplicationKey');
const alias = new kms.Alias(replicationStack, 'ReplicationAlias', {
// aliasName is required
aliasName: PhysicalName.GENERATE_IF_NEEDED,
targetKey: key,
});
const replicationBucket = new s3.Bucket(replicationStack, 'ReplicationBucket', {
bucketName: PhysicalName.GENERATE_IF_NEEDED,
encryptionKey: alias,
});
```

### Events

#### Using a pipeline as an event target
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import kms = require('@aws-cdk/aws-kms');
import s3 = require('@aws-cdk/aws-s3');
import cdk = require('@aws-cdk/core');

Expand Down Expand Up @@ -44,8 +45,15 @@ export class CrossRegionSupportStack extends cdk.Stack {
},
});

const encryptionKey = new kms.Key(this, 'CrossRegionCodePipelineReplicationBucketEncryptionKey');
const encryptionAlias = new kms.Alias(this, 'CrossRegionCodePipelineReplicationBucketEncryptionAlias', {
targetKey: encryptionKey,
aliasName: cdk.PhysicalName.GENERATE_IF_NEEDED,
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
this.replicationBucket = new s3.Bucket(this, 'CrossRegionCodePipelineReplicationBucket', {
bucketName: cdk.PhysicalName.GENERATE_IF_NEEDED,
encryptionKey: encryptionAlias,
});
}
}
Expand Down

0 comments on commit 69bff3d

Please sign in to comment.