typescriptaws-lambdaaws-cdkaws-step-functions

AWS CDK: Invoke Lambda By ARN Given to Step Function


Due to some security constraints I'm dealing with, I need to create a Step Function which can execute a Lambda that is identified in the JSON input to the Step Function. I have created such a Step Function, using the CallAwsService construct, and I call it with a JSON like the below;

{
  "lambda": "arn:aws:lambda:us-east-2:314995866118:function:prefix-dev-t-sfn-check-copy-file-to-bucket",
  "payload": {
    "file": "raw_data.csv",
    "year": "2024",
    "month": "05",
    "day": "30"
  }
}

However, when I do this the Step Function Fails with the below error. The error says that the ARN I am calling the Step Function with doesn't match the needed Regex, but looking at it I cannot see any error.

Value '{"type":0,"value":"arn:aws:lambda:us-east-2:314995866118:function:prefix-dev-t-sfn-check-copy-file-to-bucket"}' at 'functionName' failed to satisfy constraint: Member must satisfy regular expression pattern: (arn:(aws[a-zA-Z-]*)?:lambda:)?([a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\d{1}:)?(\d{12}:)?(function:)?([a-zA-Z0-9-.]+)(:($LATEST|[a-zA-Z0-9-]+))?

I declare the step function in my code with the below TypeScript CDK definition;

import { Construct } from 'constructs';
import * as sfn from 'aws-cdk-lib/aws-stepfunctions';
import { Role } from 'aws-cdk-lib/aws-iam';
import { Stack } from "aws-cdk-lib";
import { CallAwsService } from 'aws-cdk-lib/aws-stepfunctions-tasks';

export interface ExecuteNominatedLambdaProps {
    environment: string
    constructPrefix:string
    sfnRole: Role
}

export class ExecuteNominatedLambda extends Construct {
    stateMachine: sfn.StateMachine

    constructor(scope: Stack, id: string, props: ExecuteNominatedLambdaProps) {
        super(scope, id);

        // Create Call-AWS object to invoke a Lambda given in JSON
        const lambdaInvocation = new CallAwsService(
            scope,
            "Invoke Nominated Lambda",
            {
                service: "lambda",
                action: "invoke",
                parameters: {
                    FunctionName: sfn.TaskInput.fromJsonPathAt("$.lambda"),
                    InvocationType: "RequestResponse",
                    Payload: sfn.TaskInput.fromJsonPathAt("$.payload")
                },
                iamResources: ["*"],
                iamAction: "lambda:InvokeFunction",
                resultSelector: {
                    result: sfn.JsonPath.stringAt('$.Payload'),
                },
                resultPath: '$.checkStepFunctions',
            }
        );

        // Create the step function's definition and name
        const sfn_chain = sfn.DefinitionBody.fromChainable(lambdaInvocation)
        const sfn_name = `${props.constructPrefix}-invoke-nominated-lambda`

        this.stateMachine = new sfn.StateMachine(
            scope,
            `${id}-sfn`,
            {
                definitionBody: sfn_chain,
                role: props.sfnRole,
                stateMachineName: sfn_name
            }
        );
    }
};

Can anybody see what I am doing wrong?


Solution

  • In writing out the question I figured out the answer, so here it is for anyone who wants to do the same thing.

    In my original Step Function definition, I defined the parameters of the Lambda using the below object;

    parameters: {
        FunctionName: sfn.TaskInput.fromJsonPathAt("$.lambda"),
        InvocationType: "RequestResponse",
        Payload: sfn.TaskInput.fromJsonPathAt("$.payload")
    }
    

    Because I used the function sfn.TaskInput.fromJsonPathAt for the FunctionName and Payload parameters, these are technically pulled as JSON objects which I believe are encoded as byte-strings, but the field FunctionName must be a plain string.

    I fixed the error I was getting by updating the parameters object to the following;

    parameters: {
        FunctionName: sfn.JsonPath.stringAt("$.lambda"),
        InvocationType: "RequestResponse",
        Payload: sfn.JsonPath.objectAt("$.payload")
    }
    

    Using the JsonPath functions rather than the TaskInput functions meant I could ensure that the parameters were of the required types.