Skip to content

Commit

Permalink
fix(codepipeline): invoked Lambda doesn't have permissions to the pip…
Browse files Browse the repository at this point in the history
…eline bucket (aws#3303)

The Lambda invoke action was missing granting permissions to the artifact bucket,
which meant attempts to use the credentials passed to it in the event resulted in an "access denied"
when trying to read and/or write the action pipeline artifacts.
The fix is to grant the action role permissions to the artifact bucket
(write if the action has any outputs, read if the action has any inputs).

Fixes aws#3274
  • Loading branch information
skinny85 authored Jul 16, 2019
1 parent e02258f commit 50c7319
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ export class LambdaInvokeAction extends Action {
resources: [this.props.lambda.functionArn]
}));

// allow the Role access to the Bucket, if there are any inputs/outputs
if ((this.actionProperties.inputs || []).length > 0) {
options.bucket.grantRead(options.role);
}
if ((this.actionProperties.outputs || []).length > 0) {
options.bucket.grantWrite(options.role);
}

// allow lambda to put job results for this pipeline
// CodePipeline requires this to be granted to '*'
// (the Pipeline ARN will not be enough)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export = {
'Lambda invoke Action': {
'properly serializes the object passed in userParameters'(test: Test) {
const stack = stackIncludingLambdaInvokeCodePipeline({
key: 1234,
userParams: {
key: 1234,
},
});

expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', {
Expand All @@ -34,7 +36,9 @@ export = {

'properly resolves any Tokens passed in userParameters'(test: Test) {
const stack = stackIncludingLambdaInvokeCodePipeline({
key: Lazy.stringValue({ produce: () => Aws.REGION }),
userParams: {
key: Lazy.stringValue({ produce: () => Aws.REGION }),
},
});

expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', {
Expand Down Expand Up @@ -68,7 +72,9 @@ export = {

'properly resolves any stringified Tokens passed in userParameters'(test: Test) {
const stack = stackIncludingLambdaInvokeCodePipeline({
key: Token.asString(null),
userParams: {
key: Token.asString(null),
},
});

expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', {
Expand All @@ -88,10 +94,152 @@ export = {

test.done();
},

"assigns the Action's Role with read permissions to the Bucket if it has only inputs"(test: Test) {
const stack = stackIncludingLambdaInvokeCodePipeline({
lambdaInput: new codepipeline.Artifact(),
});

expect(stack).to(haveResourceLike('AWS::IAM::Policy', {
"PolicyDocument": {
"Statement": [
{
"Action": "lambda:ListFunctions",
"Resource": "*",
"Effect": "Allow",
},
{
"Action": "lambda:InvokeFunction",
"Effect": "Allow",
},
{
"Action": [
"s3:GetObject*",
"s3:GetBucket*",
"s3:List*",
],
"Effect": "Allow",
},
{
"Action": [
"kms:Decrypt",
"kms:DescribeKey",
],
"Effect": "Allow",
},
],
},
}));

test.done();
},

"assigns the Action's Role with write permissions to the Bucket if it has only outputs"(test: Test) {
const stack = stackIncludingLambdaInvokeCodePipeline({
lambdaOutput: new codepipeline.Artifact(),
// no input to the Lambda Action - we want write permissions only in this case
});

expect(stack).to(haveResourceLike('AWS::IAM::Policy', {
"PolicyDocument": {
"Statement": [
{
"Action": "lambda:ListFunctions",
"Resource": "*",
"Effect": "Allow",
},
{
"Action": "lambda:InvokeFunction",
"Effect": "Allow",
},
{
"Action": [
"s3:DeleteObject*",
"s3:PutObject*",
"s3:Abort*",
],
"Effect": "Allow",
},
{
"Action": [
"kms:Encrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
],
"Effect": "Allow",
},
],
},
}));

test.done();
},

"assigns the Action's Role with read-write permissions to the Bucket if it has both inputs and outputs"(test: Test) {
const stack = stackIncludingLambdaInvokeCodePipeline({
lambdaInput: new codepipeline.Artifact(),
lambdaOutput: new codepipeline.Artifact(),
});

expect(stack).to(haveResourceLike('AWS::IAM::Policy', {
"PolicyDocument": {
"Statement": [
{
"Action": "lambda:ListFunctions",
"Resource": "*",
"Effect": "Allow",
},
{
"Action": "lambda:InvokeFunction",
"Effect": "Allow",
},
{
"Action": [
"s3:GetObject*",
"s3:GetBucket*",
"s3:List*",
],
"Effect": "Allow",
},
{
"Action": [
"kms:Decrypt",
"kms:DescribeKey",
],
"Effect": "Allow",
},
{
"Action": [
"s3:DeleteObject*",
"s3:PutObject*",
"s3:Abort*",
],
"Effect": "Allow",
},
{
"Action": [
"kms:Encrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
],
"Effect": "Allow",
},
],
},
}));

test.done();
},
},
};

function stackIncludingLambdaInvokeCodePipeline(userParams: { [key: string]: any }) {
interface HelperProps {
readonly userParams?: { [key: string]: any };
readonly lambdaInput?: codepipeline.Artifact;
readonly lambdaOutput?: codepipeline.Artifact;
}

function stackIncludingLambdaInvokeCodePipeline(props: HelperProps) {
const stack = new Stack();

new codepipeline.Pipeline(stack, 'Pipeline', {
Expand All @@ -101,7 +249,7 @@ function stackIncludingLambdaInvokeCodePipeline(userParams: { [key: string]: any
actions: [
new cpactions.GitHubSourceAction({
actionName: 'GitHub',
output: new codepipeline.Artifact(),
output: props.lambdaInput || new codepipeline.Artifact(),
oauthToken: SecretValue.plainText('secret'),
owner: 'awslabs',
repo: 'aws-cdk',
Expand All @@ -118,7 +266,9 @@ function stackIncludingLambdaInvokeCodePipeline(userParams: { [key: string]: any
handler: 'index.handler',
runtime: lambda.Runtime.NODEJS_8_10,
}),
userParameters: userParams,
userParameters: props.userParams,
inputs: props.lambdaInput ? [props.lambdaInput] : undefined,
outputs: props.lambdaOutput ? [props.lambdaOutput] : undefined,
}),
],
},
Expand Down

0 comments on commit 50c7319

Please sign in to comment.