Skip to content

Commit

Permalink
fix(codepipeline): correctly pass the replication Buckets to Action.b…
Browse files Browse the repository at this point in the history
…ind().

With the change to generate a Role per Pipeline Action,
I realized we were incorrectly setting permissions on cross-region Actions.
We were always passing the Pipeline Bucket to the Action.bind() method,
while in reality they need permissions to their replication Buckets.

I also changed the strategy of generating the names for the replication Buckets to use our new PhysicalName.GENERATE_IF_NEEDED.
  • Loading branch information
skinny85 committed Jun 29, 2019
1 parent 628ea1b commit e412b57
Show file tree
Hide file tree
Showing 13 changed files with 30 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,14 @@ abstract class CloudFormationAction extends Action {
this.props = props;
}

protected bound(_scope: cdk.Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
protected bound(_scope: cdk.Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
codepipeline.ActionConfig {
const singletonPolicy = SingletonPolicy.forRole(options.role);

if ((this.actionProperties.outputs || []).length > 0) {
stage.pipeline.artifactBucket.grantReadWrite(singletonPolicy);
options.bucket.grantReadWrite(singletonPolicy);
} else if ((this.actionProperties.inputs || []).length > 0) {
stage.pipeline.artifactBucket.grantRead(singletonPolicy);
options.bucket.grantRead(singletonPolicy);
}

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class CodeBuildAction extends Action {
this.props = props;
}

protected bound(_scope: cdk.Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
protected bound(_scope: cdk.Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
codepipeline.ActionConfig {
// grant the Pipeline role the required permissions to this Project
options.role.addToPolicy(new iam.PolicyStatement({
Expand All @@ -97,9 +97,9 @@ export class CodeBuildAction extends Action {

// allow the Project access to the Pipeline's artifact Bucket
if ((this.actionProperties.outputs || []).length > 0) {
stage.pipeline.artifactBucket.grantReadWrite(this.props.project);
options.bucket.grantReadWrite(this.props.project);
} else {
stage.pipeline.artifactBucket.grantRead(this.props.project);
options.bucket.grantRead(this.props.project);
}

const configuration: any = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class CodeCommitSourceAction extends Action {

// the Action will write the contents of the Git repository to the Bucket,
// so its Role needs write permissions to the Pipeline Bucket
stage.pipeline.artifactBucket.grantWrite(options.role);
options.bucket.grantWrite(options.role);

// https://docs.aws.amazon.com/codecommit/latest/userguide/auth-and-access-control-permissions-reference.html#aa-acp
options.role.addToPolicy(new iam.PolicyStatement({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class CodeDeployServerDeployAction extends Action {
this.deploymentGroup = props.deploymentGroup;
}

protected bound(_scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
protected bound(_scope: Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
codepipeline.ActionConfig {
// permissions, based on:
// https://docs.aws.amazon.com/codedeploy/latest/userguide/auth-and-access-control-permissions-reference.html
Expand All @@ -57,11 +57,11 @@ export class CodeDeployServerDeployAction extends Action {

// grant the ASG Role permissions to read from the Pipeline Bucket
for (const asg of this.deploymentGroup.autoScalingGroups || []) {
stage.pipeline.artifactBucket.grantRead(asg.role);
options.bucket.grantRead(asg.role);
}

// the Action's Role needs to read from the Bucket to get artifacts
stage.pipeline.artifactBucket.grantRead(options.role);
options.bucket.grantRead(options.role);

return {
configuration: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class EcrSourceAction extends Action {
});

// the Action Role also needs to write to the Pipeline's bucket
stage.pipeline.artifactBucket.grantWrite(options.role);
options.bucket.grantWrite(options.role);

return {
configuration: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class EcsDeployAction extends Action {
this.props = props;
}

protected bound(_scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
protected bound(_scope: Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
codepipeline.ActionConfig {
// permissions based on CodePipeline documentation:
// https://docs.aws.amazon.com/codepipeline/latest/userguide/how-to-custom-role.html#how-to-update-role-new-services
Expand Down Expand Up @@ -90,7 +90,7 @@ export class EcsDeployAction extends Action {
}
}));

stage.pipeline.artifactBucket.grantRead(options.role);
options.bucket.grantRead(options.role);

return {
configuration: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ export class S3DeployAction extends Action {
this.props = props;
}

protected bound(_scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
protected bound(_scope: Construct, _stage: codepipeline.IStage, options: codepipeline.ActionBindOptions):
codepipeline.ActionConfig {
// pipeline needs permissions to write to the S3 bucket
this.props.bucket.grantWrite(options.role);

// the Action Role also needs to read from the Pipeline's bucket
stage.pipeline.artifactBucket.grantRead(options.role);
options.bucket.grantRead(options.role);

return {
configuration: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class S3SourceAction extends Action {
this.props.bucket.grantRead(options.role);

// ...and write to the Pipeline bucket
stage.pipeline.artifactBucket.grantWrite(options.role);
options.bucket.grantWrite(options.role);

return {
configuration: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ class StageDouble implements codepipeline.IStage {
const actionParent = new cdk.Construct(stageParent, action.actionProperties.actionName);
fullActions.push(new FullAction(action.actionProperties, action.bind(actionParent, this, {
role: pipeline.role,
bucket: pipeline.artifactBucket,
})));
}
this.actions = fullActions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ export = {
"Region": "us-east-1",
"ArtifactStore": {
"Type": "S3",
"Location": "cdk-cross-region-codepipeline-replication-bucket-685c6feea5fb",
"Location": "teststack-support-us-easteplicationbucket1a8063b3cdac6e7e0e73",
},
},
{
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-codepipeline/lib/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export interface ActionProperties {

export interface ActionBindOptions {
readonly role: iam.IRole;

readonly bucket: s3.IBucket;
}

export interface ActionConfig {
Expand Down Expand Up @@ -111,8 +113,6 @@ export interface IPipeline extends IResource {
*/
readonly pipelineArn: string;

readonly artifactBucket: s3.IBucket;

/**
* Define an event rule triggered by this CodePipeline.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import s3 = require('@aws-cdk/aws-s3');
import cdk = require('@aws-cdk/core');
import crypto = require('crypto');

/**
* Construction properties for {@link CrossRegionSupportStack}.
Expand Down Expand Up @@ -45,27 +44,12 @@ export class CrossRegionSupportStack extends cdk.Stack {
},
});

const replicationBucketName = generateUniqueName('cdk-cross-region-codepipeline-replication-bucket-',
props.region, props.account, false, 12);

this.replicationBucket = new s3.Bucket(this, 'CrossRegionCodePipelineReplicationBucket', {
bucketName: replicationBucketName,
bucketName: cdk.PhysicalName.GENERATE_IF_NEEDED,
});
}
}

function generateStackName(props: CrossRegionSupportStackProps): string {
return `${props.pipelineStackName}-support-${props.region}`;
}

function generateUniqueName(baseName: string, region: string, account: string,
toUpperCase: boolean, hashPartLen: number = 8): string {
const sha256 = crypto.createHash('sha256')
.update(baseName)
.update(region)
.update(account);

const hash = sha256.digest('hex').slice(0, hashPartLen);

return baseName + (toUpperCase ? hash.toUpperCase() : hash.toLowerCase());
}
15 changes: 9 additions & 6 deletions packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ export interface PipelineProps {
abstract class PipelineBase extends Resource implements IPipeline {
public abstract pipelineName: string;
public abstract pipelineArn: string;
public abstract artifactBucket: s3.IBucket;

/**
* Defines an event rule triggered by this CodePipeline.
Expand Down Expand Up @@ -305,14 +304,15 @@ export class Pipeline extends PipelineBase {
/** @internal */
public _attachActionToPipeline(stage: Stage, action: IAction, actionScope: Construct): FullActionDescriptor {
// handle cross-region actions here
this.ensureReplicationBucketExistsFor(action.actionProperties.region);
const bucket = this.ensureReplicationBucketExistsFor(action.actionProperties.region);

// get the role for the given action
const actionRole = this.getRoleForAction(stage, action, actionScope);

// bind the Action
const actionDescriptor = action.bind(actionScope, stage, {
role: actionRole ? actionRole : this.role,
bucket,
});

return new FullActionDescriptor(action, actionDescriptor, actionRole);
Expand All @@ -335,17 +335,17 @@ export class Pipeline extends PipelineBase {
];
}

private ensureReplicationBucketExistsFor(region?: string) {
private ensureReplicationBucketExistsFor(region?: string): s3.IBucket {
if (!region) {
return;
return this.artifactBucket;
}

// get the region the Pipeline itself is in
const pipelineRegion = this.requireRegion();

// if we already have an ArtifactStore generated for this region, or it's the Pipeline's region, nothing to do
if (this.artifactStores[region] || region === pipelineRegion) {
return;
if (region === pipelineRegion) {
return this.artifactBucket;
}

let replicationBucket = this.crossRegionReplicationBuckets[region];
Expand All @@ -371,13 +371,16 @@ export class Pipeline extends PipelineBase {
stack: crossRegionScaffoldStack,
replicationBucket,
};
this.crossRegionReplicationBuckets[region] = replicationBucket;
}
replicationBucket.grantReadWrite(this.role);

this.artifactStores[region] = {
location: replicationBucket.bucketName,
type: 'S3',
};

return replicationBucket;
}

/**
Expand Down

0 comments on commit e412b57

Please sign in to comment.