-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(code pipeline): cross-account support: wrap artifacts stores
Use new construct `ArtifactStore` to represent pipeline artifact store. If this construct has `artifactsKeyAlias` set than use it in output template as encryption key. For cross-account deployments artifacts must by encrypted and replicated using key to which foreign account has an access, to. By default `aws/s3` key is used which has limited support for setting resource policy. In addition it’s hard to pass KMS keys between stacks, as KMS keys are identified by freshly generated UUID, so it’s impossible to generate scaffold stack and import / export values from it - physical ARN should be passed to simplify things, so alias is used instead of KMS key. This change itself only enables customers to build cross-account pipelines, but doesn’t support them in automatic managing or creation of keys (subsequent changes will give more support)
- Loading branch information
Radoslaw Smogura
committed
Jan 17, 2019
1 parent
3270b47
commit 8b160bb
Showing
13 changed files
with
527 additions
and
61 deletions.
There are no files selected for viewing
217 changes: 217 additions & 0 deletions
217
packages/@aws-cdk/aws-codepipeline/lib/artifacts-store.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
import * as cdk from '@aws-cdk/cdk'; | ||
|
||
import * as iam from '@aws-cdk/aws-iam'; | ||
import * as kms from '@aws-cdk/aws-kms'; | ||
import * as s3 from '@aws-cdk/aws-s3'; | ||
|
||
export interface IArtifactsStore { | ||
/** | ||
* Bucket to store artifacts in given region | ||
*/ | ||
readonly bucket: s3.IBucket; | ||
|
||
/** | ||
* **(Experimental)** The alias of KMS key (or KMS key itself) | ||
* used to encrypt artifacts. In case of cross-account pipelines | ||
* specifying this value may be required, as artifacts should be | ||
* encrypted with KMS key which is allowed to be used in foreign account. | ||
* | ||
* This feature is experimental. It may be removed, or work improperly. | ||
* Known limitations: | ||
* * if cross-region replication is used, build actions should use this key; | ||
* * no permissions is set for KMS key, nor on roles policy. | ||
*/ | ||
readonly artifactsKeyAliasArn?: string; | ||
|
||
// For cross region / account calling this methods requires special care | ||
// like for (S3 ones). There's a risk that pipeline.role will be passed, | ||
// but it's in stack created after artifact's store stack - service like KMS will reject | ||
// creation of such resource policy. | ||
grantRead(identity?: iam.IPrincipal): void; | ||
grantReadWrite(identity?: iam.IPrincipal): void; | ||
} | ||
|
||
export interface ArtifactsStoreProps { | ||
/** | ||
* Bucket to store artifacts in given region | ||
*/ | ||
bucket?: s3.IBucket; | ||
|
||
/** | ||
* **(Experimental)** The KMS key to manage to encrypt artifacts. | ||
* If this value is set *CDK* will manage policy for this key. | ||
* | ||
* **This key is not used to set encryption, but `artifactsKeyAlias`, thus | ||
* `artifactsKeyAlias` should correspond to `artifactsKey`** | ||
*/ | ||
managedArtifactsKey?: kms.EncryptionKey; | ||
|
||
/** | ||
* **(Experimental)** Alias of `key` (or key itself) to use when encrypting artifacts in store. | ||
* | ||
* This value is optional, however when set consider setting encryption | ||
* key on `Project` as pipeline can fail. | ||
* | ||
* @see IArtifactsStore | ||
*/ | ||
artifactsKeyAliasArn?: string; | ||
} | ||
|
||
/** | ||
* Represents artifacts store. | ||
* | ||
* Artifacts store is composed from bucket and eventually KMS key (or alias), which is used to encrypt or | ||
* decrypt artifacts. | ||
*/ | ||
export class ArtifactsStore extends cdk.Construct implements IArtifactsStore { | ||
/** | ||
* Bucket to store artifacts in given region | ||
*/ | ||
public readonly bucket: s3.IBucket; | ||
|
||
/** | ||
* The name of bucket used to store artifacts. | ||
* If store has been created within stack with known account and region | ||
* this value will fully represent physical name, and should not contain | ||
* pseudo parameters. | ||
*/ | ||
public get bucketName() { return this._bucketName; } | ||
|
||
/** | ||
* Encryption key used to encrypt artifacts. In this class | ||
* this attribute represent physical key created in stack. | ||
*/ | ||
public readonly artifactsKey?: kms.EncryptionKey; | ||
|
||
/** | ||
* Artifacts encryption key alias ARN. ARN is synthesized | ||
* from account number, region, and alias name. | ||
*/ | ||
public readonly artifactsKeyAliasArn?: string; | ||
|
||
protected _bucketName: string; | ||
/** | ||
* Constructs new artifacts store with given properties. | ||
* **Consider using `fromBaseName`** | ||
*/ | ||
constructor(scope: cdk.Construct, id: string, props: ArtifactsStoreProps) { | ||
super(scope, id); | ||
this.bucket = props.bucket || new s3.Bucket(this, 'Bucket'); | ||
this._bucketName = this.bucket.bucketName; | ||
this.artifactsKey = props.managedArtifactsKey; | ||
this.artifactsKeyAliasArn = props.artifactsKeyAliasArn; | ||
} | ||
|
||
/** | ||
* Converts store to `ImportedArtifactsStore` which will have `parent`. | ||
*/ | ||
public asImportedStore(parent: cdk.Construct, id: string): ImportedArtifactsStore { | ||
return new ImportedArtifactsStore(parent, id, { | ||
artifactsKeyAliasArn: this.artifactsKeyAliasArn, | ||
bucketName: this.bucketName | ||
}); | ||
} | ||
|
||
public grantRead(identity?: iam.IPrincipal): void { | ||
if (!identity) { | ||
return; | ||
} | ||
this.bucket.grantRead(identity); | ||
} | ||
|
||
public grantReadWrite(identity?: iam.IPrincipal) { | ||
if (!identity) { | ||
return; | ||
} | ||
this.bucket.grantReadWrite(identity); | ||
} | ||
|
||
protected grantKmsActions(identity: iam.IPrincipal | iam.ArnPrincipal, kmsActions: string[]) { | ||
if (this.artifactsKey) { | ||
let principal; | ||
if (identity instanceof iam.ArnPrincipal) { | ||
principal = identity; | ||
} else { | ||
principal = (identity as iam.IPrincipal).principal; | ||
} | ||
|
||
this.artifactsKey.addToResourcePolicy(new iam.PolicyStatement() | ||
.addActions(...kmsActions) | ||
// Can't use role generated by pipeline, as KMS validates if role exists, so | ||
// only root principal can be specified | ||
// TODO Narrow actions & narrow access with IAM tags (PR to be sent) | ||
.addPrincipal(principal) | ||
.addAllResources() | ||
); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Represents configuration of artifact store used for cross-region and cross account replication of deployment artifacts. | ||
* | ||
* Artifacts store is a set of AWS artifacts (like buckets and KMS keys) which are used by pipeline to store | ||
* input and output to and from actions. | ||
*/ | ||
export interface ImportedArtifactsStoreProps { | ||
/** | ||
* The name of the S3 Bucket used for replicating the Pipeline's artifacts into the region. | ||
*/ | ||
bucketName: string; | ||
|
||
/** | ||
* Encryption key used to encrypt artifacts, it can represent key ARN or it can be an alias to key. | ||
*/ | ||
artifactsKeyAliasArn?: string; | ||
} | ||
|
||
/** | ||
* Represents imported artifacts store. | ||
*/ | ||
export class ImportedArtifactsStore extends cdk.Construct implements IArtifactsStore { | ||
public readonly bucket: s3.IBucket; | ||
|
||
public readonly artifactsKeyAliasArn?: string; | ||
|
||
// public readonly artifactKeyTag?: string; // For managing keys by tags | ||
|
||
constructor(scope: cdk.Construct, id: string, props: ImportedArtifactsStoreProps) { | ||
super(scope, id); | ||
|
||
this.bucket = s3.Bucket.import(this, `${id}-Bucket`, { | ||
bucketName: props.bucketName | ||
}); | ||
|
||
this.artifactsKeyAliasArn = props.artifactsKeyAliasArn; | ||
} | ||
|
||
public grantRead(identity?: iam.IPrincipal): void { | ||
if (!identity) { | ||
return; | ||
} | ||
this.bucket.grantRead(identity); | ||
} | ||
|
||
public grantReadWrite(identity?: iam.IPrincipal) { | ||
if (!identity) { | ||
return; | ||
} | ||
this.bucket.grantReadWrite(identity); | ||
} | ||
|
||
protected grantKmsActions(identity: iam.IPrincipal, kmsActions: string[]) { | ||
// In this case we only update the principal, however argument | ||
if (this.artifactsKeyAliasArn) { | ||
if (identity instanceof iam.ArnPrincipal) { | ||
// Nothing to do, as it's imported store, so can't update KMS policy. | ||
return; | ||
} | ||
|
||
(identity as iam.IPrincipal).addToPolicy(new iam.PolicyStatement() | ||
.addActions(...kmsActions) | ||
// TODO Nice to introduce tag base permission management | ||
.addAllResources() | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.