diff --git a/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts index f9053aed6fd3c..40441253a4bc7 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts @@ -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) diff --git a/packages/@aws-cdk/aws-codepipeline-actions/test/lambda/test.lambda-invoke-action.ts b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda/test.lambda-invoke-action.ts index 8c44fe8d86de9..9c8c86862727e 100644 --- a/packages/@aws-cdk/aws-codepipeline-actions/test/lambda/test.lambda-invoke-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-actions/test/lambda/test.lambda-invoke-action.ts @@ -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', { @@ -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', { @@ -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', { @@ -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', { @@ -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', @@ -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, }), ], },