Skip to content

Commit

Permalink
feat(aws-codepipeline): support for pipeline action’s service role
Browse files Browse the repository at this point in the history
In realation to aws#49

The action’s service roles is a role which will be assumed
by pipeline during execution of this action.

The pipeline action’s service role can be used to perform more
advanced configuration, when i.e. elevation of permissions
is required, or when fine grained access control may be required.

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codepipeline-pipeline-stages-actions.html

This commit is motivated by enabling cross-account deployments,
for which service role’s will be used as jump role to assume
one used by Cloud Formation in target account.
  • Loading branch information
Rado Smogura committed Jan 2, 2019
1 parent 1de9bcd commit 79cdbb2
Show file tree
Hide file tree
Showing 6 changed files with 356 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export abstract class PipelineCloudFormationAction extends codepipeline.Action {
super(parent, id, {
stage: props.stage,
runOrder: props.runOrder,
actionRole: props.actionRole,
region: props.region,
artifactBounds: {
minInputs: 0,
Expand Down
20 changes: 20 additions & 0 deletions packages/@aws-cdk/aws-codepipeline-api/lib/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,15 @@ export interface CommonActionProps {
* @see https://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html
*/
runOrder?: number;

/**
* The service role that is assumed during execution of action.
* This role is not mandatory, however more advanced configuration
* may require specifying it.
*
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codepipeline-pipeline-stages-actions.html
*/
actionRole?: iam.IRole;
}

/**
Expand Down Expand Up @@ -210,6 +219,15 @@ export abstract class Action extends cdk.Construct {
*/
public readonly configuration?: any;

/**
* The service role that is assumed during execution of action.
* This role is not mandatory, however more advanced configuration
* may require specifying it.
*
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codepipeline-pipeline-stages-actions.html
*/
public readonly actionRole?: iam.IRole;

/**
* The order in which AWS CodePipeline runs this action.
* For more information, see the AWS CodePipeline User Guide.
Expand All @@ -223,6 +241,7 @@ export abstract class Action extends cdk.Construct {

private readonly _actionInputArtifacts = new Array<Artifact>();
private readonly _actionOutputArtifacts = new Array<Artifact>();

private readonly artifactBounds: ActionArtifactBounds;
private readonly stage: IStage;

Expand All @@ -240,6 +259,7 @@ export abstract class Action extends cdk.Construct {
this.artifactBounds = props.artifactBounds;
this.runOrder = props.runOrder === undefined ? 1 : props.runOrder;
this.stage = props.stage;
this.actionRole = props.actionRole;

this.stage._internal._attachAction(this);
}
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-codepipeline/lib/stage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export class Stage extends cdk.Construct implements cpapi.IStage, cpapi.IInterna
configuration: action.configuration,
outputArtifacts: action._outputArtifacts.map(a => ({ name: a.name })),
runOrder: action.runOrder,
roleArn: action.actionRole ? action.actionRole.roleArn : undefined
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
{
"Resources": {
"MyBucketF68F3FF0": {
"Type": "AWS::S3::Bucket",
"Properties": {
"VersioningConfiguration": {
"Status": "Enabled"
}
}
},
"MyPipelineRoleC0D47CA4": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "codepipeline.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
}
},
"MyPipelineRoleDefaultPolicy34F09EFA": {
"Type": "AWS::IAM::Policy",
"Properties": {
"PolicyDocument": {
"Statement": [
{
"Action": [
"s3:GetObject*",
"s3:GetBucket*",
"s3:List*",
"s3:DeleteObject*",
"s3:PutObject*",
"s3:Abort*"
],
"Effect": "Allow",
"Resource": [
{
"Fn::GetAtt": [
"MyBucketF68F3FF0",
"Arn"
]
},
{
"Fn::Join": [
"",
[
{
"Fn::GetAtt": [
"MyBucketF68F3FF0",
"Arn"
]
},
"/*"
]
]
}
]
},
{
"Action": [
"s3:GetObject*",
"s3:GetBucket*",
"s3:List*"
],
"Effect": "Allow",
"Resource": [
{
"Fn::GetAtt": [
"MyBucketF68F3FF0",
"Arn"
]
},
{
"Fn::Join": [
"",
[
{
"Fn::GetAtt": [
"MyBucketF68F3FF0",
"Arn"
]
},
"/*"
]
]
}
]
},
{
"Action": "iam:PassRole",
"Effect": "Allow",
"Resource": {
"Fn::GetAtt": [
"CFNDeployRole68D5E8D3",
"Arn"
]
}
},
{
"Action": [
"cloudformation:CreateStack",
"cloudformation:DescribeStack*",
"cloudformation:GetStackPolicy",
"cloudformation:GetTemplate*",
"cloudformation:SetStackPolicy",
"cloudformation:UpdateStack",
"cloudformation:ValidateTemplate"
],
"Effect": "Allow",
"Resource": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":cloudformation:",
{
"Ref": "AWS::Region"
},
":",
{
"Ref": "AWS::AccountId"
},
":stack/aws-cdk-codepipeline-cross-region-deploy-stack/*"
]
]
}
}
],
"Version": "2012-10-17"
},
"PolicyName": "MyPipelineRoleDefaultPolicy34F09EFA",
"Roles": [
{
"Ref": "MyPipelineRoleC0D47CA4"
}
]
}
},
"MyPipelineAED38ECF": {
"Type": "AWS::CodePipeline::Pipeline",
"Properties": {
"RoleArn": {
"Fn::GetAtt": [
"MyPipelineRoleC0D47CA4",
"Arn"
]
},
"Stages": [
{
"Actions": [
{
"ActionTypeId": {
"Category": "Source",
"Owner": "AWS",
"Provider": "S3",
"Version": "1"
},
"Configuration": {
"S3Bucket": {
"Ref": "MyBucketF68F3FF0"
},
"S3ObjectKey": "some/path",
"PollForSourceChanges": true
},
"InputArtifacts": [],
"Name": "S3",
"OutputArtifacts": [
{
"Name": "Artifact_awscdkcodepipelinecloudformationcrossregionwithactionroleMyBucketS30423514B"
}
],
"RunOrder": 1
}
],
"Name": "Source"
},
{
"Actions": [
{
"ActionTypeId": {
"Category": "Deploy",
"Owner": "AWS",
"Provider": "CloudFormation",
"Version": "1"
},
"Configuration": {
"StackName": "aws-cdk-codepipeline-cross-region-deploy-stack",
"ActionMode": "CREATE_UPDATE",
"TemplatePath": "Artifact_awscdkcodepipelinecloudformationcrossregionwithactionroleMyBucketS30423514B::template.yml",
"RoleArn": {
"Fn::GetAtt": [
"CFNDeployRole68D5E8D3",
"Arn"
]
}
},
"InputArtifacts": [
{
"Name": "Artifact_awscdkcodepipelinecloudformationcrossregionwithactionroleMyBucketS30423514B"
}
],
"Name": "CFN_Deploy",
"OutputArtifacts": [],
"RoleArn": "arn:aws:iam::000000000000:role/action-roles",
"RunOrder": 1
}
],
"Name": "CFN"
}
],
"ArtifactStore": {
"Location": {
"Ref": "MyBucketF68F3FF0"
},
"Type": "S3"
}
},
"DependsOn": [
"MyPipelineRoleC0D47CA4",
"MyPipelineRoleDefaultPolicy34F09EFA"
]
},
"CFNDeployRole68D5E8D3": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": "cloudformation.amazonaws.com"
}
}
],
"Version": "2012-10-17"
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import cloudformation = require('@aws-cdk/aws-cloudformation');
import iam = require('@aws-cdk/aws-iam');
import s3 = require('@aws-cdk/aws-s3');
import cdk = require('@aws-cdk/cdk');
import codepipeline = require('../lib');

const app = new cdk.App();

const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-cloudformation-cross-region-with-action-role', {});

const bucket = new s3.Bucket(stack, 'MyBucket', {
versioned: true,
removalPolicy: cdk.RemovalPolicy.Destroy,
});

const pipeline = new codepipeline.Pipeline(stack, 'MyPipeline', {
artifactBucket: bucket,
});

const sourceStage = pipeline.addStage('Source');
const sourceAction = bucket.addToPipeline(sourceStage, 'S3', {
bucketKey: 'some/path',
});

const cfnStage = pipeline.addStage('CFN');
new cloudformation.PipelineCreateUpdateStackAction(stack, 'CFN_Deploy', {
stage: cfnStage,
stackName: 'aws-cdk-codepipeline-cross-region-deploy-stack',
templatePath: sourceAction.outputArtifact.atPath('template.yml'),
adminPermissions: false,
actionRole: iam.Role.import(stack, 'DeployCFNActionRole', {
roleArn: 'arn:aws:iam::000000000000:role/action-role'
})
});

app.run();
Loading

0 comments on commit 79cdbb2

Please sign in to comment.