From c0f5a32c9214afce1c660f8a0b82a7f8d960f52e Mon Sep 17 00:00:00 2001 From: Francois Regnoult Date: Tue, 28 Jan 2025 17:34:40 +0000 Subject: [PATCH 1/6] issue-658 --- main.tf | 41 +++++++++++++++++++++++++++++++++++------ variables.tf | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/main.tf b/main.tf index c67f1bbb..30f9c632 100644 --- a/main.tf +++ b/main.tf @@ -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.create && var.store_on_s3 && var.create_package && 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" { @@ -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] : [] @@ -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] } @@ -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 diff --git a/variables.tf b/variables.tf index c71f68ae..684d7d25 100644 --- a/variables.tf +++ b/variables.tf @@ -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 +} \ No newline at end of file From c193923702a1eda133cd000377b46e005ccc3139 Mon Sep 17 00:00:00 2001 From: Francois Regnoult Date: Tue, 28 Jan 2025 17:37:16 +0000 Subject: [PATCH 2/6] issue-658: readme udpate --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 10cdbe39..9c2636b8 100644 --- a/README.md +++ b/README.md @@ -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 | @@ -783,6 +784,7 @@ No modules. | [docker\_image](#input\_docker\_image) | Docker image to use for the build | `string` | `""` | no | | [docker\_pip\_cache](#input\_docker\_pip\_cache) | Whether to mount a shared pip cache folder into docker environment or not | `any` | `null` | no | | [docker\_with\_ssh\_agent](#input\_docker\_with\_ssh\_agent) | Whether to pass SSH\_AUTH\_SOCK into docker environment or not | `bool` | `false` | no | +| [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 | | [environment\_variables](#input\_environment\_variables) | A map that defines environment variables for the Lambda Function. | `map(string)` | `{}` | no | | [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 | | [event\_source\_mapping](#input\_event\_source\_mapping) | Map of event source mapping | `any` | `{}` | no | @@ -792,6 +794,7 @@ No modules. | [function\_tags](#input\_function\_tags) | A map of tags to assign only to the lambda function | `map(string)` | `{}` | no | | [handler](#input\_handler) | Lambda Function entrypoint in your code | `string` | `""` | no | | [hash\_extra](#input\_hash\_extra) | The string to add into hashing function. Useful when building same source path for different functions. | `string` | `""` | no | +| [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 | | [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 | | [image\_config\_command](#input\_image\_config\_command) | The CMD for the docker image | `list(string)` | `[]` | no | | [image\_config\_entry\_point](#input\_image\_config\_entry\_point) | The ENTRYPOINT for the docker image | `list(string)` | `[]` | no | @@ -803,6 +806,7 @@ No modules. | [kms\_key\_arn](#input\_kms\_key\_arn) | The ARN of KMS key to use by your Lambda Function | `string` | `null` | no | | [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 | | [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 | +| [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 | | [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 | | [layer\_name](#input\_layer\_name) | Name of Lambda Layer to create | `string` | `""` | no | | [layer\_skip\_destroy](#input\_layer\_skip\_destroy) | Whether to retain the old version of a previously deployed Lambda Layer. | `bool` | `false` | no | @@ -852,6 +856,8 @@ No modules. | [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 | | [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 | | [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 | +| [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 | +| [s3\_signing\_prefix](#input\_s3\_signing\_prefix) | Prefix for the generated signed object. If omitted default to var.s3\_prefix | `string` | `null` | no | | [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 | | [snap\_start](#input\_snap\_start) | (Optional) Snap start settings for low-latency startups | `bool` | `false` | no | | [source\_path](#input\_source\_path) | The absolute path to a local file or directory containing your Lambda source code | `any` | `null` | no | From 46076c1fee9a1b05c64430f2b67403cef49c6c9c Mon Sep 17 00:00:00 2001 From: Francois Regnoult Date: Tue, 28 Jan 2025 18:24:40 +0000 Subject: [PATCH 3/6] handle existing package + update example --- examples/code-signing/main.tf | 54 ++++++++++++++--------------------- main.tf | 2 +- 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/examples/code-signing/main.tf b/examples/code-signing/main.tf index b899a401..c6d515ad 100644 --- a/examples/code-signing/main.tf +++ b/examples/code-signing/main.tf @@ -1,3 +1,6 @@ +locals { + lambda_code_signing_profile_name = replace(random_pet.this.id, "-", "") +} provider "aws" { region = "eu-west-1" @@ -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 @@ -41,10 +46,14 @@ 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 @@ -52,27 +61,6 @@ resource "aws_signer_signing_profile" "this" { } } -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] diff --git a/main.tf b/main.tf index 30f9c632..755d1dd8 100644 --- a/main.tf +++ b/main.tf @@ -20,7 +20,7 @@ locals { 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.create && var.store_on_s3 && var.create_package && var.enable_code_signing && var.lambda_code_signing_profile_name != null + 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 : "") From 2eecbfbf8231cc15d56fae83dba9ceebc31658ea Mon Sep 17 00:00:00 2001 From: Francois Regnoult Date: Tue, 28 Jan 2025 18:36:29 +0000 Subject: [PATCH 4/6] example readme --- examples/code-signing/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/code-signing/README.md b/examples/code-signing/README.md index 6d4317d8..a39e172e 100644 --- a/examples/code-signing/README.md +++ b/examples/code-signing/README.md @@ -27,8 +27,8 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 5.79 | -| [random](#provider\_random) | >= 2.0 | +| [aws](#provider\_aws) | 5.84.0 | +| [random](#provider\_random) | 3.6.3 | ## Modules @@ -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 | From a751603a5f9bab8490f5caf4321c4944f0661c3d Mon Sep 17 00:00:00 2001 From: Francois Regnoult Date: Tue, 28 Jan 2025 18:46:53 +0000 Subject: [PATCH 5/6] pass precommit --- examples/code-signing/README.md | 4 ++-- variables.tf | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/code-signing/README.md b/examples/code-signing/README.md index a39e172e..c691ca4b 100644 --- a/examples/code-signing/README.md +++ b/examples/code-signing/README.md @@ -27,8 +27,8 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| -| [aws](#provider\_aws) | 5.84.0 | -| [random](#provider\_random) | 3.6.3 | +| [aws](#provider\_aws) | >= 5.79 | +| [random](#provider\_random) | >= 2.0 | ## Modules diff --git a/variables.tf b/variables.tf index 684d7d25..31793865 100644 --- a/variables.tf +++ b/variables.tf @@ -876,4 +876,4 @@ 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 -} \ No newline at end of file +} From 1cae4e6d27fb3f23b92780ad695a7956172d186b Mon Sep 17 00:00:00 2001 From: Francois Regnoult Date: Wed, 29 Jan 2025 12:25:16 +0000 Subject: [PATCH 6/6] pre-commit run checked --- wrappers/main.tf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/wrappers/main.tf b/wrappers/main.tf index 1092b4d3..1266ca19 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -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, {}) @@ -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, []) @@ -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) @@ -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)