A couple of months ago, I was working on increasing the security posture of our AWS Lambda functions. Code Signing was one of the AWS features that I tested out. In this post I will talk a bit about my experience with Code Signing for Lambda.
Introduction Link to heading
AWS Code Signing for Lambda was released 4 years ago in 2020. This ensures that only trusted code can be deployed to the Lambda functions by validating the code signature with the signing profile. AWS Signer is one of the requirement in implementing code signing. The good news is that there is no additional charge to use AWS Signer with AWS Lambda. ๐
Setup Link to heading
This terraform code snippet below will get you up and running with Code Signing for Lambda.
# main.tf
provider "aws" {
region = "us-east-1"
}
data "archive_file" "dummy" {
output_path = "${path.module}/dummy.zip"
type = "zip"
source {
content = "dummy"
filename = "dummy.py"
}
}
resource "random_id" "this" {
byte_length = 2
}
module "signing_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
version = "~> 4.1.0"
bucket = "signing-${random_id.this.dec}"
force_destroy = true
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
versioning = {
status = true
}
lifecycle_rule = [
{
id = "cleanOldObjects"
enabled = true
expiration = {
days = 1
}
noncurrent_version_expiration = {
days = 1
}
abort_incomplete_multipart_upload_days = 1
}
]
}
resource "aws_signer_signing_profile" "this" {
platform_id = "AWSLambda-SHA384-ECDSA"
signature_validity_period {
value = 1
type = "MONTHS"
}
}
resource "aws_s3_object" "this" {
bucket = module.signing_bucket.s3_bucket_id
key = "dummy.zip"
source = data.archive_file.dummy.output_path
}
resource "aws_signer_signing_job" "this" {
profile_name = aws_signer_signing_profile.this.name
source {
s3 {
bucket = module.signing_bucket.s3_bucket_id
key = aws_s3_object.this.id
version = aws_s3_object.this.version_id
}
}
destination {
s3 {
bucket = module.signing_bucket.s3_bucket_id
prefix = "signed/"
}
}
ignore_signing_job_failure = false
}
resource "aws_lambda_code_signing_config" "this" {
description = "Test Code Signing Config for Lambda"
allowed_publishers {
signing_profile_version_arns = [
aws_signer_signing_profile.this.version_arn,
]
}
policies {
untrusted_artifact_on_deployment = "Enforce"
}
}
module "lambda" {
source = "terraform-aws-modules/lambda/aws"
version = "~> 7.2.0"
function_name = "lambda-code-signing-test"
description = "Test Lambda Function for Code Signing"
handler = "index.handler"
runtime = "python3.8"
create_package = false
code_signing_config_arn = aws_lambda_code_signing_config.this.arn
s3_existing_package = {
bucket = aws_signer_signing_job.this.signed_object[0].s3[0].bucket
key = aws_signer_signing_job.this.signed_object[0].s3[0].key
}
}
Figure 1. Sample terraform code to provision the resources
After applying the terraform code above, you will see the following resources provisioned in the us-east-1 region:
Figure 2. Lambda function with code signing configured
Figure 3. Lambda code signing config with Enforce
policy
Figure 4. S3 signing bucket
Figure 5. Signer signing profile
Here’s an example when a rogue actor (has access to the lambda function without permission to the S3 bucket or AWS Signer) tries to update the lambda function code with an unsigned code:
Figure 6. Lambda function failed code update
The code update fails as the Lambda function do not accept code that are unsigned or signed by Signing profile that are not defined in the Lambda code signing configuration.
We can see how it’s easy to increase the security of the Lambda function by enabling signature validation.
Cloudtrail also records information related to the function code update in the UpdateFunctionCode20150331v2
event. Under the event record, the signatureStatus will either show VALID
or MISMATCH
(if signature validation set to Warn
and unsigned code is deployed).
Figure 7. Cloudtrail UpdateFunctionCode20150331v2
event
Figure 8. Cloudtrail UpdateFunctionCode20150331v2
event record
Note Link to heading
- Lambda Code Signing Config can be set to either
Warn
orEnforce
.
Warn
will not prevent deployment from failing if signature validation fails. - Any Lambda layer or extensions enabled on the Lambda function should be signed as well.
- The terraform code in Figure 1 doesn’t create the IAM role for GitHub Action to trigger signing job and update lambda function code.
Deployment Flow Link to heading
Figure 9. Deployment flow from repository to Lambda function
Figure 9 shows the deployment flow for deploying signed code to AWS Lambda. When a deployment is triggered from the CI/CD pipeline (ie. GitHub Action) to deploy signed code to Lambda, the following flows will happen:
- Upload zipped code to S3 bucket for signing.
- Trigger AWS Signer signing job to sign the code from S3 bucket with signing profile.
- AWS Signer signs the code and uploads it to S3 bucket.
- Update Lambda function code with the signed code from S3 bucket.
- Lambda function retrieve signed code from S3 bucket, validates the signature, accept the update only if the signature is valid.
Limitation Link to heading
There are some limitation when having code signing enabled for Lambda.
-
More steps in the deployment process
Usual lambda deployment only involves updating the lambda function code with the zipped code. The image in figure 6 shows that there are additional steps from uploading to s3 to triggering the signing job. -
Lambda extension needs to be signed with the signing profile defined in the code signing config.
This includes AWS managed Lambda extensions such as AWS Parameter and Secrets Lambda extension as they are deployed as Lambda Layer. This can be done by downloading the layer using the AWS CLI and curl, and then signing the layer (this is a pain to deal with).
All the Lambda Layers attached to the Lambda’s are subjected to signature validation when the code signing config is set.
Conclusion Link to heading
AWS Code Signing for Lambda is a great feature to add to your Lambda functions to increase your organisation security posture. You should however be aware of the limitation that comes especially if you have some extensions enabled for the Lambda function. The lack of native support for signing Lambda extensions will cause an additional overhead in managing the signing process.
Nonetheless, give it a try and let me know what you think.