Using AWS API Gateway and Step Functions without Exposing Your ARN

Recently I’ve been playing around with AWS Step Functions as a way to remove state from my lambda code. There are a few articles and posts on this subject but I could not find a good basic example that leveraged native API Gateway features to avoid information leakage concerns.

So what are my goals:

  1. API Gateway calls Step Function execution— no Lambda glue
  2. Get Step Function execution response via API Gateway — again, no Lambda glue
  3. Never expose an ARN through the API Gateway!

If you’re new to this topic then I recommend you start with the AWS Step Functions API Using API Gateway tutorial. In my example I need to create a lab object as well as a set quantity of apps (think of a lab as a training event and an app as a virtual environment for each student). I want to make a single API call to create the lab and a subsequent call to check the status.

Goal #1: API Gateway Calls Step Function Execution

Marcia Villalba has a nice article/video that covers this but she uses Lambda to call the Step Function to address information leakage; such as your ARN data. AWS has since updated their documentation in the tutorial to resolve the information leakage concerns by using an API Gateway mapping template on the Integration Request:

{
"input": "$util.escapeJavaScript($input.json('$'))",
"stateMachineArn": "arn:aws:states:us-west-2:123456789:stateMachine:LambdaStepTest-20"
}

Our API Gateway is executing the StartExecution method of the Step Function API which returns the executionArn.

{
"executionArn: "arn:aws:states:us-west-2:123456789:execution:LambdaStepTest-20:1d971731-fdfe-4bab-9303-893fb83714d9"
"startDate": 1507304303.90
}

Again, this is another information leakage concern so we’ll use the API Gateway mapping template on the Integration Response to resolve this and return the last portion of the ARN as a token — this is the unique name of the Step Function execution:

{
"token": "$input.json('$.executionArn').split(':')[7].replace('"', "")"
}

This results in the desired output of the execution token/name:

{
"token": "1d971731-fdfe-4bab-9303-893fb83714d9"
}

Goal #2: Get Execution Response via API Gateway

To obtain the Step Function execution’s response we’ll create a new API Gateway resource and POST method. Assuming you’re following the AWS tutorial you’ll need to call this resource name something like “/results” (see Step 2: To create a resource) and change the Action from StartExecution to DescribeExecution (see Step 2: To configure the method).

DescribeExecution requires the executionArn in the request syntax. To avoid information leakage we will use another API Gateway body-mapping template on the Integration Request to take an incoming token and appending it to the step function’s execution base ARN:

{
"executionArn": "arn:aws:states:us-west-2:123456789:execution:LambdaStepTest-20:$input.json('$.token').replace('"', "")"
}

Something to note, $input.json(‘$.token’) returns a string and double quotes are not allowed inside the ARN. So use the Apache Velocity replace syntax to remove the double quotes.

DescribeExecution responds with the executionArn which is another information leakage issue:

{
"executionArn": "arn:aws:states:us-west-2:123456789:execution:LambdaStepTest-20:1d971731-fdfe-4bab-9303-893fb83714d9",
"input": "{\"name\":\"dns102\",\"quantity\":3}",
"name": "1d971731-fdfe-4bab-9303-893fb83714d9",
"output": "{\"name\":\"dns102\",\"quantity\":3,\"apps\":{\"leftToProcess\":0,\"apps\":[{\"name\":\"dns102-1\"},{\"name\":\"dns102-2\"},{\"name\":\"dns102-3\"}]}}",
"startDate": 1507304303.901,
"stateMachineArn": "arn:aws:states:us-west-2:123456789:stateMachine:LambdaStepTest-20",
"status": "SUCCEEDED",
"stopDate": 1507304305.064
}

So again, we’ll need to customize the response with an API Gateway mapping template on the Integration Response; see the pattern of information leakage issues.

{
"output": "$input.json('$.output').replace('"', "")",
"status": "$input.json('$.status').replace('"', "")"
}

This results in the desired output:

{
"output": "{\name\:\dns102\,\quantity\:3,\apps\:{\leftToProcess\:0,\apps\:[{\name\:\dns102-1\},{\name\:\dns102-2\},{\name\:\dns102-3\}]}}",
"status": "SUCCEEDED"
}

Goal #3: Never expose an ARN through the API Gateway!

While defining an API Gateway mapping template to both the request and response can seem a bit tedious, it allows me to keep my Lambda code/logic simple and focused on the task at hand. I’m a fan of Serverless design and try to offload as much operations/management to other systems versus maintaining it in my code. However, you may have a better way of handling this and I look forward to your feedback.