Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Code signing support #658 #660

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ No modules.
| [aws_lambda_permission.unqualified_alias_triggers](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_permission) | resource |
| [aws_lambda_provisioned_concurrency_config.current_version](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_provisioned_concurrency_config) | resource |
| [aws_s3_object.lambda_package](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource |
| [aws_signer_signing_job.lambda_code_signing](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/signer_signing_job) | resource |
| [local_file.archive_plan](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource |
| [null_resource.archive](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
| [null_resource.sam_metadata_aws_lambda_function](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
Expand Down Expand Up @@ -783,6 +784,7 @@ No modules.
| <a name="input_docker_image"></a> [docker\_image](#input\_docker\_image) | Docker image to use for the build | `string` | `""` | no |
| <a name="input_docker_pip_cache"></a> [docker\_pip\_cache](#input\_docker\_pip\_cache) | Whether to mount a shared pip cache folder into docker environment or not | `any` | `null` | no |
| <a name="input_docker_with_ssh_agent"></a> [docker\_with\_ssh\_agent](#input\_docker\_with\_ssh\_agent) | Whether to pass SSH\_AUTH\_SOCK into docker environment or not | `bool` | `false` | no |
| <a name="input_enable_code_signing"></a> [enable\_code\_signing](#input\_enable\_code\_signing) | Must be used with a lambda storing code on s3. Set this to true for triggering a signing job creating a signed copy of the lambda zip. https://docs.aws.amazon.com/lambda/latest/dg/configuration-codesigning.html | `bool` | `false` | no |
| <a name="input_environment_variables"></a> [environment\_variables](#input\_environment\_variables) | A map that defines environment variables for the Lambda Function. | `map(string)` | `{}` | no |
| <a name="input_ephemeral_storage_size"></a> [ephemeral\_storage\_size](#input\_ephemeral\_storage\_size) | Amount of ephemeral storage (/tmp) in MB your Lambda Function can use at runtime. Valid value between 512 MB to 10,240 MB (10 GB). | `number` | `512` | no |
| <a name="input_event_source_mapping"></a> [event\_source\_mapping](#input\_event\_source\_mapping) | Map of event source mapping | `any` | `{}` | no |
Expand All @@ -792,6 +794,7 @@ No modules.
| <a name="input_function_tags"></a> [function\_tags](#input\_function\_tags) | A map of tags to assign only to the lambda function | `map(string)` | `{}` | no |
| <a name="input_handler"></a> [handler](#input\_handler) | Lambda Function entrypoint in your code | `string` | `""` | no |
| <a name="input_hash_extra"></a> [hash\_extra](#input\_hash\_extra) | The string to add into hashing function. Useful when building same source path for different functions. | `string` | `""` | no |
| <a name="input_ignore_signing_job_failure"></a> [ignore\_signing\_job\_failure](#input\_ignore\_signing\_job\_failure) | Set this argument to true to ignore signing job failures and retrieve failed status and reason | `bool` | `false` | no |
| <a name="input_ignore_source_code_hash"></a> [ignore\_source\_code\_hash](#input\_ignore\_source\_code\_hash) | Whether to ignore changes to the function's source code hash. Set to true if you manage infrastructure and code deployments separately. | `bool` | `false` | no |
| <a name="input_image_config_command"></a> [image\_config\_command](#input\_image\_config\_command) | The CMD for the docker image | `list(string)` | `[]` | no |
| <a name="input_image_config_entry_point"></a> [image\_config\_entry\_point](#input\_image\_config\_entry\_point) | The ENTRYPOINT for the docker image | `list(string)` | `[]` | no |
Expand All @@ -803,6 +806,7 @@ No modules.
| <a name="input_kms_key_arn"></a> [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of KMS key to use by your Lambda Function | `string` | `null` | no |
| <a name="input_lambda_at_edge"></a> [lambda\_at\_edge](#input\_lambda\_at\_edge) | Set this to true if using Lambda@Edge, to enable publishing, limit the timeout, and allow edgelambda.amazonaws.com to invoke the function | `bool` | `false` | no |
| <a name="input_lambda_at_edge_logs_all_regions"></a> [lambda\_at\_edge\_logs\_all\_regions](#input\_lambda\_at\_edge\_logs\_all\_regions) | Whether to specify a wildcard in IAM policy used by Lambda@Edge to allow logging in all regions | `bool` | `true` | no |
| <a name="input_lambda_code_signing_profile_name"></a> [lambda\_code\_signing\_profile\_name](#input\_lambda\_code\_signing\_profile\_name) | Lambda code signing profile name https://console.aws.amazon.com/lambda/home#/code-signing-configurations | `string` | `null` | no |
| <a name="input_lambda_role"></a> [lambda\_role](#input\_lambda\_role) | IAM role ARN attached to the Lambda Function. This governs both who / what can invoke your Lambda Function, as well as what resources our Lambda Function has access to. See Lambda Permission Model for more details. | `string` | `""` | no |
| <a name="input_layer_name"></a> [layer\_name](#input\_layer\_name) | Name of Lambda Layer to create | `string` | `""` | no |
| <a name="input_layer_skip_destroy"></a> [layer\_skip\_destroy](#input\_layer\_skip\_destroy) | Whether to retain the old version of a previously deployed Lambda Layer. | `bool` | `false` | no |
Expand Down Expand Up @@ -852,6 +856,8 @@ No modules.
| <a name="input_s3_object_tags_only"></a> [s3\_object\_tags\_only](#input\_s3\_object\_tags\_only) | Set to true to not merge tags with s3\_object\_tags. Useful to avoid breaching S3 Object 10 tag limit. | `bool` | `false` | no |
| <a name="input_s3_prefix"></a> [s3\_prefix](#input\_s3\_prefix) | Directory name where artifacts should be stored in the S3 bucket. If unset, the path from `artifacts_dir` is used | `string` | `null` | no |
| <a name="input_s3_server_side_encryption"></a> [s3\_server\_side\_encryption](#input\_s3\_server\_side\_encryption) | Specifies server-side encryption of the object in S3. Valid values are "AES256" and "aws:kms". | `string` | `null` | no |
| <a name="input_s3_signing_bucket"></a> [s3\_signing\_bucket](#input\_s3\_signing\_bucket) | Bucket where to upload the signed s3 file. If omitted default to var.s3\_bucket | `string` | `null` | no |
| <a name="input_s3_signing_prefix"></a> [s3\_signing\_prefix](#input\_s3\_signing\_prefix) | Prefix for the generated signed object. If omitted default to var.s3\_prefix | `string` | `null` | no |
| <a name="input_skip_destroy"></a> [skip\_destroy](#input\_skip\_destroy) | Set to true if you do not wish the function to be deleted at destroy time, and instead just remove the function from the Terraform state. Useful for Lambda@Edge functions attached to CloudFront distributions. | `bool` | `null` | no |
| <a name="input_snap_start"></a> [snap\_start](#input\_snap\_start) | (Optional) Snap start settings for low-latency startups | `bool` | `false` | no |
| <a name="input_source_path"></a> [source\_path](#input\_source\_path) | The absolute path to a local file or directory containing your Lambda source code | `any` | `null` | no |
Expand Down
1 change: 0 additions & 1 deletion examples/code-signing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ Note that this example may create resources which cost money. Run `terraform des
|------|------|
| [aws_lambda_code_signing_config.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lambda_code_signing_config) | resource |
| [aws_s3_object.unsigned](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource |
| [aws_signer_signing_job.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/signer_signing_job) | resource |
| [aws_signer_signing_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/signer_signing_profile) | resource |
| [random_pet.this](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet) | resource |

Expand Down
54 changes: 21 additions & 33 deletions examples/code-signing/main.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
locals {
lambda_code_signing_profile_name = replace(random_pet.this.id, "-", "")
}
provider "aws" {
region = "eu-west-1"

Expand All @@ -14,21 +17,23 @@ provider "aws" {
module "lambda" {
source = "../../"

function_name = random_pet.this.id
handler = "index.lambda_handler"
runtime = "python3.12"
code_signing_config_arn = aws_lambda_code_signing_config.this.arn
create_package = false
function_name = random_pet.this.id
handler = "index.lambda_handler"
runtime = "python3.12"
create_package = false
enable_code_signing = true
code_signing_config_arn = aws_lambda_code_signing_config.this.arn
lambda_code_signing_profile_name = local.lambda_code_signing_profile_name
s3_signing_prefix = "signed/"

store_on_s3 = true
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
bucket = module.s3_bucket.s3_bucket_id
key = aws_s3_object.unsigned.key
version_id = aws_s3_object.unsigned.version_id
}
}

################################################################################
# Lambda Code Signing
################################################################################
}

resource "aws_s3_object" "unsigned" {
bucket = module.s3_bucket.s3_bucket_id
Expand All @@ -41,38 +46,21 @@ resource "aws_s3_object" "unsigned" {
]
}

# ################################################################################
# # Lambda Code Signing
# ################################################################################

resource "aws_signer_signing_profile" "this" {
platform_id = "AWSLambda-SHA384-ECDSA"
# invalid value for name (must be alphanumeric with max length of 64 characters)
name = replace(random_pet.this.id, "-", "")
name = local.lambda_code_signing_profile_name

signature_validity_period {
value = 3
type = "MONTHS"
}
}

resource "aws_signer_signing_job" "this" {
profile_name = aws_signer_signing_profile.this.name

source {
s3 {
bucket = module.s3_bucket.s3_bucket_id
key = aws_s3_object.unsigned.id
version = aws_s3_object.unsigned.version_id
}
}

destination {
s3 {
bucket = module.s3_bucket.s3_bucket_id
prefix = "signed/"
}
}

ignore_signing_job_failure = true
}

resource "aws_lambda_code_signing_config" "this" {
allowed_publishers {
signing_profile_version_arns = [aws_signer_signing_profile.this.version_arn]
Expand Down
41 changes: 35 additions & 6 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ locals {
s3_key = var.s3_existing_package != null ? try(var.s3_existing_package.key, null) : (var.store_on_s3 ? var.s3_prefix != null ? format("%s%s", var.s3_prefix, replace(local.archive_filename_string, "/^.*//", "")) : replace(local.archive_filename_string, "/^\\.//", "") : null)
s3_object_version = var.s3_existing_package != null ? try(var.s3_existing_package.version_id, null) : (var.store_on_s3 ? try(aws_s3_object.lambda_package[0].version_id, null) : null)

# s3_signing
s3_signing_enabled = local.s3_key != null && local.s3_bucket != null && var.enable_code_signing && var.lambda_code_signing_profile_name != null
s3_signing_bucket = var.s3_signing_bucket != null && local.s3_signing_enabled ? var.s3_signing_bucket : local.s3_bucket
s3_signing_prefix = var.s3_signing_prefix != null && local.s3_signing_enabled ? var.s3_signing_prefix : (var.s3_prefix != null ? var.s3_prefix : "")

lambda_s3_bucket = local.s3_signing_enabled ? aws_signer_signing_job.lambda_code_signing[0].signed_object[0].s3[0].bucket : local.s3_bucket
lambda_s3_key = local.s3_signing_enabled ? aws_signer_signing_job.lambda_code_signing[0].signed_object[0].s3[0].key : local.s3_key
lambda_s3_version = local.s3_signing_enabled ? null : local.s3_object_version # aws_signer_signing_job does not return a version id
}

resource "aws_lambda_function" "this" {
Expand Down Expand Up @@ -55,9 +63,9 @@ resource "aws_lambda_function" "this" {
filename = local.filename
source_code_hash = var.ignore_source_code_hash ? null : (local.filename == null ? false : fileexists(local.filename)) && !local.was_missing ? filebase64sha256(local.filename) : null

s3_bucket = local.s3_bucket
s3_key = local.s3_key
s3_object_version = local.s3_object_version
s3_bucket = local.lambda_s3_bucket
s3_key = local.lambda_s3_key
s3_object_version = local.lambda_s3_version

dynamic "image_config" {
for_each = length(var.image_config_entry_point) > 0 || length(var.image_config_command) > 0 || var.image_config_working_directory != null ? [true] : []
Expand Down Expand Up @@ -181,9 +189,9 @@ resource "aws_lambda_layer_version" "this" {
filename = local.filename
source_code_hash = var.ignore_source_code_hash ? null : (local.filename == null ? false : fileexists(local.filename)) && !local.was_missing ? filebase64sha256(local.filename) : null

s3_bucket = local.s3_bucket
s3_key = local.s3_key
s3_object_version = local.s3_object_version
s3_bucket = local.lambda_s3_bucket
s3_key = local.lambda_s3_key
s3_object_version = local.lambda_s3_version

depends_on = [null_resource.archive, aws_s3_object.lambda_package]
}
Expand Down Expand Up @@ -215,6 +223,27 @@ resource "aws_s3_object" "lambda_package" {
depends_on = [null_resource.archive]
}

resource "aws_signer_signing_job" "lambda_code_signing" {
count = local.s3_signing_enabled ? 1 : 0
profile_name = var.lambda_code_signing_profile_name

source {
s3 {
bucket = local.s3_bucket
key = local.s3_key
version = local.s3_object_version
}
}

destination {
s3 {
bucket = local.s3_signing_bucket
prefix = local.s3_signing_prefix
}
}
ignore_signing_job_failure = var.ignore_signing_job_failure
}

data "aws_cloudwatch_log_group" "lambda" {
count = local.create && var.create_function && !var.create_layer && var.use_existing_cloudwatch_log_group ? 1 : 0

Expand Down
34 changes: 34 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -843,3 +843,37 @@ variable "recursive_loop" {
type = string
default = null
}

###############
# Code Signing
###############

variable "enable_code_signing" {
description = "Must be used with a lambda storing code on s3. Set this to true for triggering a signing job creating a signed copy of the lambda zip. https://docs.aws.amazon.com/lambda/latest/dg/configuration-codesigning.html"
type = bool
default = false
}

variable "lambda_code_signing_profile_name" {
description = "Lambda code signing profile name https://console.aws.amazon.com/lambda/home#/code-signing-configurations"
type = string
default = null
}

variable "s3_signing_bucket" {
description = "Bucket where to upload the signed s3 file. If omitted default to var.s3_bucket"
type = string
default = null
}

variable "s3_signing_prefix" {
description = "Prefix for the generated signed object. If omitted default to var.s3_prefix"
type = string
default = null
}

variable "ignore_signing_job_failure" {
description = "Set this argument to true to ignore signing job failures and retrieve failed status and reason"
type = bool
default = false
}
5 changes: 5 additions & 0 deletions wrappers/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ module "wrapper" {
docker_image = try(each.value.docker_image, var.defaults.docker_image, "")
docker_pip_cache = try(each.value.docker_pip_cache, var.defaults.docker_pip_cache, null)
docker_with_ssh_agent = try(each.value.docker_with_ssh_agent, var.defaults.docker_with_ssh_agent, false)
enable_code_signing = try(each.value.enable_code_signing, var.defaults.enable_code_signing, false)
environment_variables = try(each.value.environment_variables, var.defaults.environment_variables, {})
ephemeral_storage_size = try(each.value.ephemeral_storage_size, var.defaults.ephemeral_storage_size, 512)
event_source_mapping = try(each.value.event_source_mapping, var.defaults.event_source_mapping, {})
Expand All @@ -62,6 +63,7 @@ module "wrapper" {
function_tags = try(each.value.function_tags, var.defaults.function_tags, {})
handler = try(each.value.handler, var.defaults.handler, "")
hash_extra = try(each.value.hash_extra, var.defaults.hash_extra, "")
ignore_signing_job_failure = try(each.value.ignore_signing_job_failure, var.defaults.ignore_signing_job_failure, false)
ignore_source_code_hash = try(each.value.ignore_source_code_hash, var.defaults.ignore_source_code_hash, false)
image_config_command = try(each.value.image_config_command, var.defaults.image_config_command, [])
image_config_entry_point = try(each.value.image_config_entry_point, var.defaults.image_config_entry_point, [])
Expand All @@ -73,6 +75,7 @@ module "wrapper" {
kms_key_arn = try(each.value.kms_key_arn, var.defaults.kms_key_arn, null)
lambda_at_edge = try(each.value.lambda_at_edge, var.defaults.lambda_at_edge, false)
lambda_at_edge_logs_all_regions = try(each.value.lambda_at_edge_logs_all_regions, var.defaults.lambda_at_edge_logs_all_regions, true)
lambda_code_signing_profile_name = try(each.value.lambda_code_signing_profile_name, var.defaults.lambda_code_signing_profile_name, null)
lambda_role = try(each.value.lambda_role, var.defaults.lambda_role, "")
layer_name = try(each.value.layer_name, var.defaults.layer_name, "")
layer_skip_destroy = try(each.value.layer_skip_destroy, var.defaults.layer_skip_destroy, false)
Expand Down Expand Up @@ -122,6 +125,8 @@ module "wrapper" {
s3_object_tags_only = try(each.value.s3_object_tags_only, var.defaults.s3_object_tags_only, false)
s3_prefix = try(each.value.s3_prefix, var.defaults.s3_prefix, null)
s3_server_side_encryption = try(each.value.s3_server_side_encryption, var.defaults.s3_server_side_encryption, null)
s3_signing_bucket = try(each.value.s3_signing_bucket, var.defaults.s3_signing_bucket, null)
s3_signing_prefix = try(each.value.s3_signing_prefix, var.defaults.s3_signing_prefix, null)
skip_destroy = try(each.value.skip_destroy, var.defaults.skip_destroy, null)
snap_start = try(each.value.snap_start, var.defaults.snap_start, false)
source_path = try(each.value.source_path, var.defaults.source_path, null)
Expand Down