Skip to content

Commit 50c7319

Browse files
authored
fix(codepipeline): invoked Lambda doesn't have permissions to the pipeline bucket (#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 #3274
1 parent e02258f commit 50c7319

File tree

2 files changed

+164
-6
lines changed

2 files changed

+164
-6
lines changed

packages/@aws-cdk/aws-codepipeline-actions/lib/lambda/invoke-action.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ export class LambdaInvokeAction extends Action {
8787
resources: [this.props.lambda.functionArn]
8888
}));
8989

90+
// allow the Role access to the Bucket, if there are any inputs/outputs
91+
if ((this.actionProperties.inputs || []).length > 0) {
92+
options.bucket.grantRead(options.role);
93+
}
94+
if ((this.actionProperties.outputs || []).length > 0) {
95+
options.bucket.grantWrite(options.role);
96+
}
97+
9098
// allow lambda to put job results for this pipeline
9199
// CodePipeline requires this to be granted to '*'
92100
// (the Pipeline ARN will not be enough)

packages/@aws-cdk/aws-codepipeline-actions/test/lambda/test.lambda-invoke-action.ts

Lines changed: 156 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ export = {
1111
'Lambda invoke Action': {
1212
'properly serializes the object passed in userParameters'(test: Test) {
1313
const stack = stackIncludingLambdaInvokeCodePipeline({
14-
key: 1234,
14+
userParams: {
15+
key: 1234,
16+
},
1517
});
1618

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

3537
'properly resolves any Tokens passed in userParameters'(test: Test) {
3638
const stack = stackIncludingLambdaInvokeCodePipeline({
37-
key: Lazy.stringValue({ produce: () => Aws.REGION }),
39+
userParams: {
40+
key: Lazy.stringValue({ produce: () => Aws.REGION }),
41+
},
3842
});
3943

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

6973
'properly resolves any stringified Tokens passed in userParameters'(test: Test) {
7074
const stack = stackIncludingLambdaInvokeCodePipeline({
71-
key: Token.asString(null),
75+
userParams: {
76+
key: Token.asString(null),
77+
},
7278
});
7379

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

8995
test.done();
9096
},
97+
98+
"assigns the Action's Role with read permissions to the Bucket if it has only inputs"(test: Test) {
99+
const stack = stackIncludingLambdaInvokeCodePipeline({
100+
lambdaInput: new codepipeline.Artifact(),
101+
});
102+
103+
expect(stack).to(haveResourceLike('AWS::IAM::Policy', {
104+
"PolicyDocument": {
105+
"Statement": [
106+
{
107+
"Action": "lambda:ListFunctions",
108+
"Resource": "*",
109+
"Effect": "Allow",
110+
},
111+
{
112+
"Action": "lambda:InvokeFunction",
113+
"Effect": "Allow",
114+
},
115+
{
116+
"Action": [
117+
"s3:GetObject*",
118+
"s3:GetBucket*",
119+
"s3:List*",
120+
],
121+
"Effect": "Allow",
122+
},
123+
{
124+
"Action": [
125+
"kms:Decrypt",
126+
"kms:DescribeKey",
127+
],
128+
"Effect": "Allow",
129+
},
130+
],
131+
},
132+
}));
133+
134+
test.done();
135+
},
136+
137+
"assigns the Action's Role with write permissions to the Bucket if it has only outputs"(test: Test) {
138+
const stack = stackIncludingLambdaInvokeCodePipeline({
139+
lambdaOutput: new codepipeline.Artifact(),
140+
// no input to the Lambda Action - we want write permissions only in this case
141+
});
142+
143+
expect(stack).to(haveResourceLike('AWS::IAM::Policy', {
144+
"PolicyDocument": {
145+
"Statement": [
146+
{
147+
"Action": "lambda:ListFunctions",
148+
"Resource": "*",
149+
"Effect": "Allow",
150+
},
151+
{
152+
"Action": "lambda:InvokeFunction",
153+
"Effect": "Allow",
154+
},
155+
{
156+
"Action": [
157+
"s3:DeleteObject*",
158+
"s3:PutObject*",
159+
"s3:Abort*",
160+
],
161+
"Effect": "Allow",
162+
},
163+
{
164+
"Action": [
165+
"kms:Encrypt",
166+
"kms:ReEncrypt*",
167+
"kms:GenerateDataKey*",
168+
],
169+
"Effect": "Allow",
170+
},
171+
],
172+
},
173+
}));
174+
175+
test.done();
176+
},
177+
178+
"assigns the Action's Role with read-write permissions to the Bucket if it has both inputs and outputs"(test: Test) {
179+
const stack = stackIncludingLambdaInvokeCodePipeline({
180+
lambdaInput: new codepipeline.Artifact(),
181+
lambdaOutput: new codepipeline.Artifact(),
182+
});
183+
184+
expect(stack).to(haveResourceLike('AWS::IAM::Policy', {
185+
"PolicyDocument": {
186+
"Statement": [
187+
{
188+
"Action": "lambda:ListFunctions",
189+
"Resource": "*",
190+
"Effect": "Allow",
191+
},
192+
{
193+
"Action": "lambda:InvokeFunction",
194+
"Effect": "Allow",
195+
},
196+
{
197+
"Action": [
198+
"s3:GetObject*",
199+
"s3:GetBucket*",
200+
"s3:List*",
201+
],
202+
"Effect": "Allow",
203+
},
204+
{
205+
"Action": [
206+
"kms:Decrypt",
207+
"kms:DescribeKey",
208+
],
209+
"Effect": "Allow",
210+
},
211+
{
212+
"Action": [
213+
"s3:DeleteObject*",
214+
"s3:PutObject*",
215+
"s3:Abort*",
216+
],
217+
"Effect": "Allow",
218+
},
219+
{
220+
"Action": [
221+
"kms:Encrypt",
222+
"kms:ReEncrypt*",
223+
"kms:GenerateDataKey*",
224+
],
225+
"Effect": "Allow",
226+
},
227+
],
228+
},
229+
}));
230+
231+
test.done();
232+
},
91233
},
92234
};
93235

94-
function stackIncludingLambdaInvokeCodePipeline(userParams: { [key: string]: any }) {
236+
interface HelperProps {
237+
readonly userParams?: { [key: string]: any };
238+
readonly lambdaInput?: codepipeline.Artifact;
239+
readonly lambdaOutput?: codepipeline.Artifact;
240+
}
241+
242+
function stackIncludingLambdaInvokeCodePipeline(props: HelperProps) {
95243
const stack = new Stack();
96244

97245
new codepipeline.Pipeline(stack, 'Pipeline', {
@@ -101,7 +249,7 @@ function stackIncludingLambdaInvokeCodePipeline(userParams: { [key: string]: any
101249
actions: [
102250
new cpactions.GitHubSourceAction({
103251
actionName: 'GitHub',
104-
output: new codepipeline.Artifact(),
252+
output: props.lambdaInput || new codepipeline.Artifact(),
105253
oauthToken: SecretValue.plainText('secret'),
106254
owner: 'awslabs',
107255
repo: 'aws-cdk',
@@ -118,7 +266,9 @@ function stackIncludingLambdaInvokeCodePipeline(userParams: { [key: string]: any
118266
handler: 'index.handler',
119267
runtime: lambda.Runtime.NODEJS_8_10,
120268
}),
121-
userParameters: userParams,
269+
userParameters: props.userParams,
270+
inputs: props.lambdaInput ? [props.lambdaInput] : undefined,
271+
outputs: props.lambdaOutput ? [props.lambdaOutput] : undefined,
122272
}),
123273
],
124274
},

0 commit comments

Comments
 (0)