From b1adb00d6e21d818e344216fd5376e1e8afaea83 Mon Sep 17 00:00:00 2001 From: Edvaldo Szymonek Date: Fri, 28 Mar 2025 14:21:16 -0300 Subject: [PATCH 1/3] fix: correctly resolve lambda function name from Arguments or Parameters --- lib/deploy/stepFunctions/compileIamRole.js | 7 ++-- .../stepFunctions/compileIamRole.test.js | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/lib/deploy/stepFunctions/compileIamRole.js b/lib/deploy/stepFunctions/compileIamRole.js index 8772a20..3635873 100644 --- a/lib/deploy/stepFunctions/compileIamRole.js +++ b/lib/deploy/stepFunctions/compileIamRole.js @@ -362,7 +362,7 @@ function getRedshiftDataPermissions(action, state) { function getLambdaPermissions(state) { // function name can be name-only, name-only with alias, full arn or partial arn // https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#API_Invoke_RequestParameters - const functionName = state.Parameters.FunctionName; + const functionName = getParameterOrArgument(state, 'FunctionName'); if (_.isString(functionName)) { const segments = functionName.split(':'); @@ -429,10 +429,11 @@ function getLambdaPermissions(state) { }]; } - if (state.Parameters['FunctionName.$']) { + if (getParameterOrArgument(state, 'FunctionName.$')) { + const allowedFunctions = getParameterOrArgument(state, 'AllowedFunctions'); return [{ action: 'lambda:InvokeFunction', - resource: state.Parameters.AllowedFunctions ? state.Parameters.AllowedFunctions : '*', + resource: allowedFunctions || '*', }]; } diff --git a/lib/deploy/stepFunctions/compileIamRole.test.js b/lib/deploy/stepFunctions/compileIamRole.test.js index e6603ef..245f0e0 100644 --- a/lib/deploy/stepFunctions/compileIamRole.test.js +++ b/lib/deploy/stepFunctions/compileIamRole.test.js @@ -3587,6 +3587,41 @@ describe('#compileIamRole', () => { ]); }); + it('should resolve FunctionName from the Arguments property when there is no Parameters property', () => { + serverless.service.stepFunctions = { + stateMachines: { + myStateMachine1: { + id: 'StateMachine1', + definition: { + StartAt: 'A', + States: { + A: { + Type: 'Task', + Resource: 'arn:aws:states:::lambda:invoke', + Arguments: { + FunctionName: 'arn:aws:lambda:us-west-2:1234567890:function:foo', + Payload: '{% $states.input.Payload %}', + }, + End: true, + }, + }, + }, + }, + }, + }; + + serverlessStepFunctions.compileIamRole(); + const statements = serverlessStepFunctions.serverless.service + .provider.compiledCloudFormationTemplate.Resources.StateMachine1Role + .Properties.Policies[0].PolicyDocument.Statement; + const lambdaPermissions = statements.filter(s => _.isEqual(s.Action, ['lambda:InvokeFunction'])); + expect(lambdaPermissions).to.have.lengthOf(1); + expect(lambdaPermissions[0].Resource).to.deep.equal([ + 'arn:aws:lambda:us-west-2:1234567890:function:foo', + 'arn:aws:lambda:us-west-2:1234567890:function:foo:*', + ]); + }); + it('should support variable FunctionName', () => { serverless.service.stepFunctions = { stateMachines: { From f61123193b82b72394417abcce2bd315ba09b58f Mon Sep 17 00:00:00 2001 From: Edvaldo Szymonek Date: Tue, 1 Apr 2025 09:35:55 -0300 Subject: [PATCH 2/3] test: use itParam to test both JSONPath and JSONata scenarios --- lib/deploy/stepFunctions/compileIamRole.test.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/deploy/stepFunctions/compileIamRole.test.js b/lib/deploy/stepFunctions/compileIamRole.test.js index 245f0e0..32ededa 100644 --- a/lib/deploy/stepFunctions/compileIamRole.test.js +++ b/lib/deploy/stepFunctions/compileIamRole.test.js @@ -3587,7 +3587,7 @@ describe('#compileIamRole', () => { ]); }); - it('should resolve FunctionName from the Arguments property when there is no Parameters property', () => { + itParam('should resolve FunctionName: ${value}', ['JSONPath', 'JSONata'], (queryLanguage) => { serverless.service.stepFunctions = { stateMachines: { myStateMachine1: { @@ -3598,10 +3598,17 @@ describe('#compileIamRole', () => { A: { Type: 'Task', Resource: 'arn:aws:states:::lambda:invoke', - Arguments: { - FunctionName: 'arn:aws:lambda:us-west-2:1234567890:function:foo', - Payload: '{% $states.input.Payload %}', - }, + ...getParamsOrArgs( + queryLanguage, + { + FunctionName: 'arn:aws:lambda:us-west-2:1234567890:function:foo', + 'Payload.$': '$.Payload', + }, + { + FunctionName: 'arn:aws:lambda:us-west-2:1234567890:function:foo', + Payload: '{% $states.input.Payload %}', + }, + ), End: true, }, }, From f282ed261af5b7b9ecffc25422aa2026ff6b2ff5 Mon Sep 17 00:00:00 2001 From: zirkelc Date: Sat, 3 May 2025 06:24:37 +0200 Subject: [PATCH 3/3] fix: lambda permissions for jsonata --- lib/deploy/stepFunctions/compileIamRole.js | 17 ++-- .../stepFunctions/compileIamRole.test.js | 99 +++++++++++++------ 2 files changed, 80 insertions(+), 36 deletions(-) diff --git a/lib/deploy/stepFunctions/compileIamRole.js b/lib/deploy/stepFunctions/compileIamRole.js index 3635873..90ec17e 100644 --- a/lib/deploy/stepFunctions/compileIamRole.js +++ b/lib/deploy/stepFunctions/compileIamRole.js @@ -360,9 +360,18 @@ function getRedshiftDataPermissions(action, state) { } function getLambdaPermissions(state) { + if (isJsonPathParameter(state, 'FunctionName') || isJsonataArgument(state, 'FunctionName')) { + const allowedFunctions = getParameterOrArgument(state, 'AllowedFunctions'); + return [{ + action: 'lambda:InvokeFunction', + resource: allowedFunctions || '*', + }]; + } + // function name can be name-only, name-only with alias, full arn or partial arn // https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#API_Invoke_RequestParameters const functionName = getParameterOrArgument(state, 'FunctionName'); + if (_.isString(functionName)) { const segments = functionName.split(':'); @@ -429,14 +438,6 @@ function getLambdaPermissions(state) { }]; } - if (getParameterOrArgument(state, 'FunctionName.$')) { - const allowedFunctions = getParameterOrArgument(state, 'AllowedFunctions'); - return [{ - action: 'lambda:InvokeFunction', - resource: allowedFunctions || '*', - }]; - } - // hope for the best... return [{ action: 'lambda:InvokeFunction', diff --git a/lib/deploy/stepFunctions/compileIamRole.test.js b/lib/deploy/stepFunctions/compileIamRole.test.js index 32ededa..0772986 100644 --- a/lib/deploy/stepFunctions/compileIamRole.test.js +++ b/lib/deploy/stepFunctions/compileIamRole.test.js @@ -3629,7 +3629,7 @@ describe('#compileIamRole', () => { ]); }); - it('should support variable FunctionName', () => { + itParam('should support variable FunctionName: ${value}', ['JSONPath', 'JSONata'], (queryLanguage) => { serverless.service.stepFunctions = { stateMachines: { myStateMachine1: { @@ -3640,26 +3640,47 @@ describe('#compileIamRole', () => { A: { Type: 'Task', Resource: 'arn:aws:states:::lambda:invoke.waitForTaskToken', - Parameters: { - 'FunctionName.$': '$.functionName', - Payload: { - 'model.$': '$.new_model', - 'token.$': '$$.Task.Token', + ...getParamsOrArgs( + queryLanguage, + { + 'FunctionName.$': '$.functionName', + Payload: { + 'model.$': '$.new_model', + 'token.$': '$$.Task.Token', + }, }, - }, + { + FunctionName: '{% $states.input.functionName %}', + Payload: { + model: '{% $states.input.new_model %}', + token: '{% $states.context.Task.Token %}', + }, + }, + ), Next: 'B', }, B: { Type: 'Task', Resource: 'arn:aws:states:::lambda:invoke.waitForTaskToken', - Parameters: { - 'FunctionName.$': '$.functionName', - AllowedFunctions: '*limited*', - Payload: { - 'model.$': '$.new_model', - 'token.$': '$$.Task.Token', + ...getParamsOrArgs( + queryLanguage, + { + 'FunctionName.$': '$.functionName', + AllowedFunctions: '*limited*', + Payload: { + 'model.$': '$.new_model', + 'token.$': '$$.Task.Token', + }, }, - }, + { + FunctionName: '{% $states.input.functionName %}', + AllowedFunctions: '*limited*', + Payload: { + model: '{% $states.input.new_model %}', + token: '{% $states.context.Task.Token %}', + }, + }, + ), End: true, }, }, @@ -3685,27 +3706,49 @@ describe('#compileIamRole', () => { A: { Type: 'Task', Resource: 'arn:aws:states:::lambda:invoke.waitForTaskToken', - Parameters: { - 'FunctionName.$': '$.functionName', - AllowedFunctions: 'arn:aws:lambda:us-west-2:1234567890:function:foo', - Payload: { - 'model.$': '$.new_model', - 'token.$': '$$.Task.Token', + ...getParamsOrArgs( + queryLanguage, + { + 'FunctionName.$': '$.functionName', + AllowedFunctions: 'arn:aws:lambda:us-west-2:1234567890:function:foo', + Payload: { + 'model.$': '$.new_model', + 'token.$': '$$.Task.Token', + }, }, - }, + { + FunctionName: '{% $states.input.functionName %}', + AllowedFunctions: 'arn:aws:lambda:us-west-2:1234567890:function:foo', + Payload: { + model: '{% $states.input.new_model %}', + token: '{% $states.context.Task.Token %}', + }, + }, + ), Next: 'B', }, B: { Type: 'Task', Resource: 'arn:aws:states:::lambda:invoke.waitForTaskToken', - Parameters: { - 'FunctionName.$': '$.functionName', - AllowedFunctions: '*limited*', - Payload: { - 'model.$': '$.new_model', - 'token.$': '$$.Task.Token', + ...getParamsOrArgs( + queryLanguage, + { + 'FunctionName.$': '$.functionName', + AllowedFunctions: '*limited*', + Payload: { + 'model.$': '$.new_model', + 'token.$': '$$.Task.Token', + }, }, - }, + { + FunctionName: '{% $states.input.functionName %}', + AllowedFunctions: '*limited*', + Payload: { + model: '{% $states.input.new_model %}', + token: '{% $states.context.Task.Token %}', + }, + }, + ), End: true, }, },