Skip to content

Conversation

kunduso
Copy link
Owner

@kunduso kunduso commented May 29, 2025

This PR implements AWS Lambda code signing functionality to enhance the security of our Lambda deployments. Key changes include:

  • Added AWS Signer configuration with SHA384-ECDSA signing profile
  • Created secure S3 bucket for Lambda code storage with appropriate security controls
  • Implemented Lambda code signing workflow with proper dependency management
  • Configured code signing verification with "Enforce" policy for maximum security
  • Applied security best practices including KMS encryption, public access blocking, and lifecycle policies
  • Added documentation comments explaining security control suppressions

These changes improve our security posture by ensuring Lambda code integrity through cryptographic signing while maintaining a secure deployment pipeline.

The code signing policy is set to "Enforce" mode, which provides the highest level of security by preventing deployment of unsigned or improperly signed code to our Lambda functions.

@kunduso kunduso self-assigned this May 29, 2025
Comment on lines +3 to +12
resource "aws_s3_bucket" "lambda_source" {
bucket = "${var.name}-lambda-source-${data.aws_caller_identity.current.account_id}"
force_destroy = true
#checkov:skip=CKV_AWS_18: AWS Access logging not enabled on S3 buckets
#checkov:skip=CKV_AWS_144: Region replication not enabled on S3 bucket
#checkov:skip=CKV2_AWS_62: Ensure S3 buckets should have event notifications enabled
# These security controls are suppressed as this bucket is only used temporarily for Lambda code signing
# and is not intended for long-term storage or public access. The bucket has other security measures
# like encryption and public access blocking enabled.
}

Check warning

Code scanning / checkov

Ensure S3 buckets should have event notifications enabled Warning

Ensure S3 buckets should have event notifications enabled
Comment on lines +3 to +12
resource "aws_s3_bucket" "lambda_source" {
bucket = "${var.name}-lambda-source-${data.aws_caller_identity.current.account_id}"
force_destroy = true
#checkov:skip=CKV_AWS_18: AWS Access logging not enabled on S3 buckets
#checkov:skip=CKV_AWS_144: Region replication not enabled on S3 bucket
#checkov:skip=CKV2_AWS_62: Ensure S3 buckets should have event notifications enabled
# These security controls are suppressed as this bucket is only used temporarily for Lambda code signing
# and is not intended for long-term storage or public access. The bucket has other security measures
# like encryption and public access blocking enabled.
}

Check warning

Code scanning / checkov

Ensure the S3 bucket has access logging enabled Warning

Ensure the S3 bucket has access logging enabled
Comment on lines +3 to +12
resource "aws_s3_bucket" "lambda_source" {
bucket = "${var.name}-lambda-source-${data.aws_caller_identity.current.account_id}"
force_destroy = true
#checkov:skip=CKV_AWS_18: AWS Access logging not enabled on S3 buckets
#checkov:skip=CKV_AWS_144: Region replication not enabled on S3 bucket
#checkov:skip=CKV2_AWS_62: Ensure S3 buckets should have event notifications enabled
# These security controls are suppressed as this bucket is only used temporarily for Lambda code signing
# and is not intended for long-term storage or public access. The bucket has other security measures
# like encryption and public access blocking enabled.
}

Check warning

Code scanning / checkov

Ensure that S3 bucket has cross-region replication enabled Warning

Ensure that S3 bucket has cross-region replication enabled
Copy link
Contributor

⏳ I'm reviewing this pull request for security vulnerabilities and code quality issues. I'll provide an update when I'm done

Copy link

💰 Infracost report

Monthly estimate generated

Estimate details (includes details of unsupported resources)
──────────────────────────────────
1 project has no cost estimate change.
Run the following command to see its breakdown: infracost breakdown --path=/path/to/code

──────────────────────────────────
51 cloud resources were detected:
∙ 10 were estimated
∙ 39 were free
∙ 2 are not supported yet, see https://infracost.io/requested-resources:
  ∙ 1 x aws_signer_signing_job
  ∙ 1 x aws_signer_signing_profile
This comment will be updated when code changes.

Copy link

Terraform Format and Style 🖌success

Terraform Initialization ⚙️success

Terraform Plan 📖success

Terraform Validation 🤖success

Show Plan

terraform
random_string.suffix: Refreshing state... [id=54f3on9g]
data.archive_file.python_file: Reading...
data.archive_file.python_file: Read complete after 0s [id=efca5ba5d15c470502277886d6f89aea9b62262c]
module.vpc.data.aws_iam_policy_document.assume_role: Reading...
aws_signer_signing_profile.lambda_signing_profile: Refreshing state... [id=app_7_lambda_signing_profile_54f3on9g]
module.vpc.data.aws_caller_identity.current: Reading...
module.vpc.aws_vpc.this: Refreshing state... [id=vpc-0ba805dce231d72af]
data.aws_caller_identity.current: Reading...
aws_kms_key.encryption: Refreshing state... [id=faf78ecc-02a3-465f-85a8-2fcc0d2709e0]
aws_iam_role.lambda_role: Refreshing state... [id=app-7-lambda-role]
module.vpc.data.aws_iam_policy_document.assume_role: Read complete after 0s [id=2717921857]
module.vpc.data.aws_availability_zones.available: Reading...
aws_kms_key.encrypt_storage: Refreshing state... [id=c8558991-2dd9-4bb6-b4b7-0a0b72c97693]
aws_cloudwatch_event_rule.lambda_trigger: Refreshing state... [id=app-7-lambda-trigger-rule]
module.vpc.aws_kms_key.custom_kms_key[0]: Refreshing state... [id=2c86ea54-c020-4502-be62-abbc92e042bb]
data.aws_caller_identity.current: Read complete after 0s [id=743794601996]
module.vpc.aws_iam_role.vpc_flow_log_role[0]: Refreshing state... [id=app-7-vpc-flow-role]
module.vpc.data.aws_caller_identity.current: Read complete after 1s [id=743794601996]
aws_s3_bucket.lambda_source: Refreshing state... [id=app-7-lambda-source-743794601996]
aws_lambda_code_signing_config.configuration: Refreshing state... [id=arn:aws:lambda:us-east-2:743794601996:code-signing-config:csc-09c26746ded3203ba]
module.vpc.data.aws_availability_zones.available: Read complete after 1s [id=us-east-2]
aws_kms_alias.encrypt_storage: Refreshing state... [id=alias/app-7-encrypt-storage]
aws_kms_alias.encryption: Refreshing state... [id=alias/app-7]
data.aws_iam_policy_document.encrypt_storage_policy: Reading...
data.aws_iam_policy_document.encrypt_storage_policy: Read complete after 0s [id=2186263626]
data.aws_iam_policy_document.encryption_policy: Reading...
aws_ssm_parameter.parameter: Refreshing state... [id=/app-7]
data.aws_iam_policy_document.encryption_policy: Read complete after 0s [id=687347941]
aws_cloudwatch_log_group.lambda_log: Refreshing state... [id=/aws/lambda/app-7]
aws_sqs_queue.dlq: Refreshing state... [id=https://sqs.us-east-2.amazonaws.com/743794601996/app-7-lambda-dlq]
aws_kms_key_policy.encrypt_storage: Refreshing state... [id=c8558991-2dd9-4bb6-b4b7-0a0b72c97693]
module.vpc.aws_kms_key_policy.encrypt_log[0]: Refreshing state... [id=2c86ea54-c020-4502-be62-abbc92e042bb]
module.vpc.aws_kms_alias.key[0]: Refreshing state... [id=alias/app-7-encrypt-flow-log]
module.vpc.aws_cloudwatch_log_group.network_flow_logging[0]: Refreshing state... [id=app-7-flow-logs]
aws_kms_key_policy.encryption: Refreshing state... [id=faf78ecc-02a3-465f-85a8-2fcc0d2709e0]
aws_iam_role_policy_attachment.managed_vpc_policy_attachement: Refreshing state... [id=app-7-lambda-role-20250529131640859000000001]
aws_cloudwatch_log_stream.log_stream: Refreshing state... [id=app-7-lambda-log-stream]
module.vpc.data.aws_iam_policy_document.vpc_flow_log_policy_document[0]: Reading...
module.vpc.data.aws_iam_policy_document.vpc_flow_log_policy_document[0]: Read complete after 0s [id=2325604698]
module.vpc.aws_iam_role_policy.vpc_flow_log_role_policy[0]: Refreshing state... [id=app-7-vpc-flow-role:app-7-vpc-flow-policy]
module.vpc.aws_route_table.private[2]: Refreshing state... [id=rtb-0085bbc315a1dae2e]
module.vpc.aws_route_table.private[0]: Refreshing state... [id=rtb-0dfde5e46b07baed1]
module.vpc.aws_route_table.private[1]: Refreshing state... [id=rtb-0b77108cddd3fccb2]
module.vpc.aws_default_security_group.default: Refreshing state... [id=sg-0d767a8cd3fd365e1]
module.vpc.aws_flow_log.network_flow_logging[0]: Refreshing state... [id=fl-0d4509d95b6d3bafe]
module.vpc.aws_subnet.private[1]: Refreshing state... [id=subnet-0d9dc0b1a30148e66]
module.vpc.aws_subnet.private[2]: Refreshing state... [id=subnet-00d1e7407a7fc4355]
module.vpc.aws_subnet.private[0]: Refreshing state... [id=subnet-0cc0a6123f1fe8823]
aws_security_group.endpoint_sg: Refreshing state... [id=sg-0287116141a01ef1f]
aws_security_group.lambda: Refreshing state... [id=sg-07d15f685496cb125]
aws_s3_bucket_lifecycle_configuration.lambda_source: Refreshing state... [id=app-7-lambda-source-743794601996]
aws_s3_object.lambda_zip: Refreshing state... [id=lambda_function.zip]
aws_iam_policy.lambda_policy: Refreshing state... [id=arn:aws:iam::743794601996:policy/app-7-lambda-policy]
aws_s3_bucket_server_side_encryption_configuration.lambda_source: Refreshing state... [id=app-7-lambda-source-743794601996]
aws_s3_bucket_versioning.lambda_source: Refreshing state... [id=app-7-lambda-source-743794601996]
aws_s3_bucket_public_access_block.lambda_source: Refreshing state... [id=app-7-lambda-source-743794601996]
aws_signer_signing_job.build_signing_job: Refreshing state... [id=4e3c986a-2dd8-4694-a575-15e1abec58c4]
aws_security_group_rule.ingress_vpc_endpoint: Refreshing state... [id=sgrule-1285863216]
aws_security_group_rule.egress_vpc_endpoint_lambda: Refreshing state... [id=sgrule-675117604]
module.vpc.aws_route_table_association.private[1]: Refreshing state... [id=rtbassoc-05c5e45a28523ba4f]
module.vpc.aws_route_table_association.private[2]: Refreshing state... [id=rtbassoc-0d5a521248a39bd8d]
module.vpc.aws_route_table_association.private[0]: Refreshing state... [id=rtbassoc-05579247a045eca09]
aws_vpc_endpoint.ssm: Refreshing state... [id=vpce-0eaad262e72a8df0a]
aws_vpc_endpoint.logs: Refreshing state... [id=vpce-0a410b5fd3167049d]
aws_iam_role_policy_attachment.lambda_policy_attachement: Refreshing state... [id=app-7-lambda-role-20250529131714868500000008]
aws_lambda_function.lambda_run: Refreshing state... [id=app-7]
aws_lambda_permission.allow_cloudwatch: Refreshing state... [id=AllowExecutionFromCloudWatch]
aws_cloudwatch_event_target.lambda_target: Refreshing state... [id=app-7-lambda-trigger-rule-lambda_target]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place
-/+ destroy and then create replacement

Terraform will perform the following actions:

  # aws_lambda_function.lambda_run will be updated in-place
  ~ resource "aws_lambda_function" "lambda_run" {
        id                             = "app-7"
      ~ last_modified                  = "2025-05-29T13:19:30.984+0000" -> (known after apply)
      ~ s3_bucket                      = "app-7-lambda-source-743794601996" -> (known after apply)
      ~ s3_key                         = "signed/4e3c986a-2dd8-4694-a575-15e1abec58c4.zip" -> (known after apply)
      ~ source_code_hash               = "gJXSwLOgwrAOlBMoA0N1eQdc7+eDDvnHuM/2lQzkTDk=" -> "r/IEcoyUiZqdbjpfqNbrR03Z+f3qKrwjwkDn/9yI5Wo="
        tags                           = {}
        # (26 unchanged attributes hidden)

        # (6 unchanged blocks hidden)
    }

  # aws_s3_bucket_lifecycle_configuration.lambda_source will be updated in-place
  ~ resource "aws_s3_bucket_lifecycle_configuration" "lambda_source" {
        id                    = "app-7-lambda-source-743794601996"
        # (2 unchanged attributes hidden)

      ~ rule {
          ~ id     = "cleanup-old-versions" -> "abort-multipart-uploads"
            # (2 unchanged attributes hidden)

          + abort_incomplete_multipart_upload {
              + days_after_initiation = 3
            }

          - noncurrent_version_expiration {
              - noncurrent_days           = 90 -> null
                # (1 unchanged attribute hidden)
            }

            # (1 unchanged block hidden)
        }
      + rule {
          + id     = "cleanup-old-versions"
          + status = "Enabled"

          + noncurrent_version_expiration {
              + noncurrent_days = 90
            }
        }
    }

  # aws_s3_object.lambda_zip will be updated in-place
  ~ resource "aws_s3_object" "lambda_zip" {
      ~ etag                          = "1641dccdbe60ffb5c4de5cddc254bbbd" -> "896432c8ae57333538e53dee9fdbd7dc"
        id                            = "lambda_function.zip"
        tags                          = {}
      ~ version_id                    = "null" -> (known after apply)
        # (23 unchanged attributes hidden)
    }

  # aws_signer_signing_job.build_signing_job must be replaced
-/+ resource "aws_signer_signing_job" "build_signing_job" {
      ~ completed_at               = "2025-05-29T13:19:10Z" -> (known after apply)
      ~ created_at                 = "2025-05-29T13:19:09Z" -> (known after apply)
      ~ id                         = "4e3c986a-2dd8-4694-a575-15e1abec58c4" -> (known after apply)
      ~ job_id                     = "4e3c986a-2dd8-4694-a575-15e1abec58c4" -> (known after apply)
      ~ job_invoker                = "743794601996" -> (known after apply)
      ~ job_owner                  = "743794601996" -> (known after apply)
      ~ platform_display_name      = "AWS Lambda" -> (known after apply)
      ~ platform_id                = "AWSLambda-SHA384-ECDSA" -> (known after apply)
      ~ profile_version            = "kG4KHpWGTq" -> (known after apply)
      ~ requested_by               = "arn:aws:sts::743794601996:assumed-role/Admin/kunduso-Isengard" -> (known after apply)
      ~ revocation_record          = [] -> (known after apply)
      ~ signature_expires_at       = "2036-08-29T13:19:10Z" -> (known after apply)
      ~ signed_object              = [
          - {
              - s3 = [
                  - {
                      - bucket = "app-7-lambda-source-743794601996"
                      - key    = "signed/4e3c986a-2dd8-4694-a575-15e1abec58c4.zip"
                    },
                ]
            },
        ] -> (known after apply)
      ~ status                     = "Succeeded" -> (known after apply)
      ~ status_reason              = "Signing Succeeded" -> (known after apply)
        # (2 unchanged attributes hidden)

      ~ source {
          ~ s3 {
              ~ version = "null" -> (known after apply) # forces replacement
                # (2 unchanged attributes hidden)
            }
        }

        # (1 unchanged block hidden)
    }

  # aws_ssm_parameter.parameter will be updated in-place
  ~ resource "aws_ssm_parameter" "parameter" {
        id              = "/app-7"
      + insecure_value  = (known after apply)
        name            = "/app-7"
        tags            = {}
      ~ value           = (sensitive value)
      ~ version         = 2 -> (known after apply)
        # (8 unchanged attributes hidden)
    }

Plan: 1 to add, 4 to change, 1 to destroy.

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: TFplan.JSON

To perform exactly these actions, run the following command to apply:
    terraform apply "TFplan.JSON"

Pushed by: @kunduso, Action: pull_request

effect = "Allow"
principals {
type = "AWS"
identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Description: The KMS key policy grants broad permissions to the root user, which may pose security risks. Consider limiting the permissions granted to the root user in the KMS key policy. Use the principle of least privilege.

Severity: High

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix addresses the security concern by limiting the permissions granted to the root user in the KMS key policy. Instead of granting broad permissions to the root user, the fix assumes an AdminRole and grants it a more restricted set of permissions. The actions list has been reduced to include only the essential KMS operations. This change implements the principle of least privilege, reducing potential security risks.

Suggested change
identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
effect = "Allow"
principals {
type = "AWS"
identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/AdminRole"] # Assuming an AdminRole exists
}
actions = [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
]
resources = [aws_kms_key.encrypt_storage.arn]
}

type = "AWS"
identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
}
actions = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Description: The KMS key policy includes a long list of actions, which could be simplified for better readability and maintainability. Consider grouping similar actions or using wildcards where appropriate to reduce the length of the action list.

Severity: Low

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix simplifies the long list of KMS actions in the first statement by using a wildcard "kms:*" instead of listing all individual actions. This improves readability and maintainability of the policy document while still granting full KMS permissions to the root user. The second statement for S3 permissions remains unchanged as it requires specific actions.

Suggested change
actions = [
identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
}
actions = [
"kms:*"
]
resources = [aws_kms_key.encrypt_storage.arn]
}

Copy link
Contributor

✅ I finished the code review, and left comments with the issues I found. I will now generate code fix suggestions.

@kunduso kunduso merged commit 332e901 into main May 29, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant