Signing Serverless Lambda code with GitHub Actions

Alex Smolen
3 min readMay 8, 2022

Code signatures help prevent unauthorized code execution. They bridge trust between build and execution environments. This post shows you how to sign AWS Lambda function code built with GitHub Actions.

Background

In November 2020 AWS released support for signing AWS Lambda code. Lambda function owners can specify signing verification requirements using Code Signing Configs. Code publishers can sign their code using AWS Signer Signing Profiles. AWS described configuring code signing with the Serverless Application Model (SAM). They left the “how to build and sign the code” as an exercise for the reader.

In November 2021 GitHub released OpenID Connect for GitHub Actions. AWS owners can connect GitHub Actions to an IAM role using configure-aws-credentials.

Google discussed improving software supply chain security with tamper-proof builds. They use GitHub Actions reusable workflows to publish a Go build to Sigstore. Because it uses OpenID Connect, it requires no direct handling of key material. It allows downstream users to verify code provenance:

Performing these steps guarantees to the consumer that the binary was produced in the trusted builder at a given commit hash attested to in the provenance. They can trust that the information in the provenance was non-forgeable, allowing them to trust the build “recipe” and trace their artifact verifiably back to the source.

I combined these approaches to sign my Lambda code with GitHub Actions. I’m using Serverless instead of SAM, but there’s not a lot of moving infrastructure parts. Because we use GitHub OIDC, you don’t need to manage any secrets. AWS IAM controls access to both the signing capabilities and execution restrictions. This solution could scale to support an invariant from the Security team:

All Lambda functions should use code from approved build systems.

Signing Lambda function code with GitHub actions

First, I created the deployment IAM Role for the GitHub Action. You can start with the sample CloudFormation. To determine the right permissions, I attached a policy with broad permissions to the role. I then generated the policy based on CloudTrail logs using IAM Access Analyzer. The additional policy statements look something like:

IAM policy for deployment IAM role

Next, I created a GitHub Action to build and deploy my code with this file in my repository at .github/deploy.yml:

GitHub Action deployment configuration

This uses the caonfigure-aws-credentials and serverless/github-action GitHub Actions to deploy the main branch to production.

Next we’ll configure Serverless to sign the code. Here’s the necessary configuration in our serverless.yml:

Serverless resources for code signing

I created an AWS Signer SigningProfile referenced by a AWS Lambda CodeSigningConfig. I also specified the CodeSigningConfigArn for my Lambda function. This property is not directly supported by Serverless. Instead, I passed the property through custom configuration.

Et voilà, the next time I committed to main, my Lambda code is signed and deployed. Any attempt to update the Lambda function with code built outside GitHub Actions will fail.

Thoughts on this approach? Let me know on Twitter.

--

--