A few weeks ago, the Linux Foundation and partners announced the sigstore project, a non-profit software signing service. The recent Solarwind security issues have shown that it is vital to protect your software supply chain from unauthorized changes. As security is an important part of any application, developers and organisations should follow the guidelines of the Secure Software Development Lifecycle (SSDLC). The Solarwinds hacks have renewed interest in SSLDC and the software development pipeline.
In a nutshell, the SSDCL has 5 phases:
In this blog post, we’ll look into AWS Signer and how it can assist with Phase 5 - Deployment, in a serverless environment with AWS Lambda.
> AWS Signer is a fully managed code-signing service to ensure the trust and integrity of your code. Organizations validate code against a digital signature to confirm that the code is unaltered and from a trusted publisher. source
So what is Code signing? Code signing is the process of applying a digital signature to software artifacts. This provides a level of trust that the code has not been tampered with since being published. Code signing helps prove:
> A code signing configuration defines a list of allowed signing profiles and defines the code-signing validation policy. source
The creation of a code signing configuration is straightforward, with just 2 properties deserving some extra attention:
You can find all the source code for the examples in this blogpost in this Github repository.
The following tools are used:
Your local environment should be configured to deploy the infrastructure generated by the CDK and run commands on the AWS CLI.
You’re probably wondering why we aren’t creating the lambda function as well using the CDK For the demo, we’ll configure the code signing config on Enforce. This means that creating an AWS Lambda without a signed code package, will fail. This feature is currently not supported out of the box in the CDK, so we’ll have to manage the resource using the AWS CLI. If we would have configured the Code Signing Config with Warn, then we could have used the CDK to deploy a lambda.
With the infrastructure in place, we’ll start with creating a Lambda function and attach the code signing config that we’ve created with the CDK. Make sure you’ve uploaded some sample code in the versioned bucket, as we will deploy this later. You can find a zip file in the example repo in the folder `function`.
Now, let’s have a look at the IAM statements to deploy the lambda. The code signing configuration supports `Condition`[Condition](https://docs.aws.amazon.com/service-authorization/latest/reference/list_awslambda.html) in IAM policies. This can be leveraged to force new lambdas to be created and updated using the code signing configuration.
{
"Condition": {
"StringEquals": {
"lambda:CodeSigningConfigArn": [
"arn:aws:lambda:<region>:<acount-id>:code-signing-config:csc-<csc-id>"
]
}
},
"Action": [
"lambda:CreateFunction",
"lambda:PutFunctionCodeSigningConfig"
],
"Resource": "*",
"Effect": "Allow"
}
The policy above will block all attempts to create a Lambda function without the Code Signing Configuration specified in the policy. Let’s try and create the Lambda:
aws lambda create-function \
--function-name "code-signed-function" \
--runtime "nodejs14.x" \
--role <lambda-role> \
--code S3Bucket=<bucketname>,S3Key=<zip key>, S3ObjectVersion=<version> \
--handler index.handler \
--code-signing-config-arn <code-signing-config-arn>
When executing the above command, we have as expected an error.
An error occurred (CodeVerificationFailedException) when calling the CreateFunction operation: Lambda cannot deploy the function. The function or layer might be signed using a signature that the client is not configured to accept. Check the provided signature for code-signed-function
This means that we need to sign our code first.
Let’s have a quick look at the minimum IAM policies to start a code signing job.
{
"Action": [
"s3:GetObjectVersion",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::<bucket-name>/*",
"arn:aws:s3:::<bucket-name>"
],
"Effect": "Allow"
},
{
"Action": [
"signer:GetSigningProfile",
"signer:StartSigningJob"
],
"Resource": "arn:aws:signer:<region>:<account-id>:/signing-profiles/<code-signing-profile-id>",
"Effect": "Allow"
}
When configured correctly, we now have permissions to start the code signing job. We’ll tell AWS Signer to put all our signed artifacts in the `/signed` folder in the S3 bucket.
Starting a job using the AWS CLI is rather easy and straightforward and can be done with the following command:
aws signer start-signing-job \
--source 's3={bucketName=<lambda-bucket>, version=<version-string>, key=<zip-key>}' \
--destination 's3={bucketName=<lambda-bucket>, prefix=signed/}' \
--profile-name <aws-signer-profile>
When the signing job is complete, we can retry to create the function, using the signed zip code. This will create the lambda, with the specific code signing config. So now we are sure that our lambda will require a code signed deployment package.
But what about updates? Trying to update a function without a code signed deployment package will throw the following error:
An error occurred (CodeVerificationFailedException) when calling the UpdateFunctionCode operation: Lambda cannot deploy the function. The function or layer might be signed using a signature that the client is not configured to accept. Check the provided signature
So the same workflow as for creating a lambda function is required when updating your function code. This ensures that all your deployed code artifacts are signed.
With the correct IAM statements Lambda creation can only pass when created with a specific code signing configuration
The code signing configuration can enforce that all Lambda deployment artifacts are code signed using AWS Signer.
The example in this post showed how you can secure your serverless applications using AWS Signer as a managed code-signing service combined with code signing configuration for AWS Lambda. Both the creation of Lambda functions and its updates are having their code signature verified.
This recent event is an example of an intended malicious code injection in a NPM package so keep in mind that code signing alone does not protect against all malicious attacks and signing your code is just 1 step in the SSDLC process. So proper validation of the code and its dependencies is still required before it gets signed.