adrianhesketh.com

Export all CloudFormation templates for a pentest

Prior to the first release of a project, alongside internal security testing, and security tooling carried out as part of CI/CD, it’s common to have a pentest carried out by a 3rd party security team to check over everything.

Security teams often request the CloudFormation templates used to create infrastructure, since having good documentation gives them the best chance to find issues.

In my latest project, there are 21 CDK projects that make up the overall solution, so when I was asked, my first thought was to check out each of the projects, and run the cdk synth to export the raw CloudFormation, but that would take a relatively long time, so I picked up the AWS CLI instead.

Just tell me how to do it

Install jq

Create run.sh

aws cloudformation list-stacks --output=json | jq --raw-output '.StackSummaries | map(.StackName) | .[]' | xargs -L 1 ./get.sh

Create get.sh

Create a get.sh file with the following contents.

aws cloudformation get-template --output=json --stack-name=$1 | jq --raw-output ".TemplateBody | ." > $1.yaml

Run it

Log in to your AWS account on the command line, and run ./run.sh.

How does it work?

Getting the template for a single stack

The AWS CLI allows us to get a single stack contents but doesn’t output the raw template contents, so the output needs to be tidied up.

aws cloudformation get-template --output=json --stack-name=myteststack`

Outputting JSON enables the output to be piped into the jq command line tool [0] which can execute filter and transform operations on JSON to extract just the required information.

I have my CLI set to output JSON by default [1] to enable scripting, but you can add the --output=json flag to any AWS CLI command to force it.

jq can filter the result to only output the contents of the TemplateBody field and produce plain text output (using the --raw-output parameter).

The built-in shell operator > can write the output to a file.

Adding the command to a get.sh file, provides a resuable script that takes a positional parameter ($1) for the stack name, e.g. ./get.sh mytesttemplate:

aws cloudformation get-template --output=json --stack-name=$1 | jq --raw-output ".TemplateBody | ." > $1.yaml

Listing all CloudFormation stacks

Listing the stacks is straightforward.

aws cloudformation list-stacks --output=json

But as per the docs [1], the output looks something like this:

{
  "StackSummaries": [
    {
        "StackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/myteststack/466df9e0-0dff-08e3-8e2f-5088487c4896",
        "TemplateDescription": "AWS CloudFormation Sample Template S3_Bucket: Sample template showing how to create a publicly accessible S3 bucket. **WARNING** This template creates an S3 bucket. You will be billed for the AWS resources used if you create a stack from this template.",
        "StackStatusReason": null,
        "CreationTime": "2013-08-26T03:27:10.190Z",
        "StackName": "myteststack",
        "StackStatus": "CREATE_COMPLETE"
    }
  ]
}

Since only the stack name is required, jq can be used to filter the output and output all the stack names, one on each line.

aws cloudformation list-stacks --output json | jq --raw-output '.StackSummaries | map(.StackName) | .[]'

Breaking down the jq parameters:

  • '.StackSummaries | map(.StackName)' gets the contents of the StackSummaries field, and then extracts the value of each StackName field into an output array.
  • .[] outputs the array.
  • --raw-output means that jq outputs plain text instead of JSON.

The output looks something like this:

myteststack
stack2

Connecting up with xargs

To execute the ./get.sh script for every stack, the output of the list-stacks command gets piped into the xargs command.

The -L 1 parameter of xargs tells xargs that each line of the input should be used to execute the ./get.sh script with the contents of the input line as a parameter.

aws cloudformation list-stacks| jq --raw-output '.StackSummaries | map(.StackName) | .[]' | xargs -L 1 ./get.sh