Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3b31922
[ELI-702] - adding the new signing resources and attaching to lambda
TOEL2 Mar 25, 2026
272126c
[ELI-702] - changing workflow to sign and upload first before deployment
TOEL2 Mar 25, 2026
f4e1924
[ELI-702] - expanding github role permissions with new policy
TOEL2 Mar 25, 2026
2153103
[ELI-702] - changing name to something valid
TOEL2 Mar 25, 2026
d55e992
[ELI-702] - excepting for dev
TOEL2 Mar 25, 2026
0fda371
[ELI-702] - adding permissions
TOEL2 Mar 25, 2026
358dc0c
[ELI-702] - adding permissions
TOEL2 Mar 25, 2026
3ad3203
[ELI-702] - restricting permissions
TOEL2 Mar 25, 2026
5fb9a3b
[ELI-702] - removing suppression
TOEL2 Mar 26, 2026
41eca6d
[ELI-702] - swapping env for workspace
TOEL2 Mar 26, 2026
2c460bd
Merge branch 'main' into feature/ELI-702-code-signing
TOEL2 Mar 27, 2026
afa6788
[ELI-702] - swapping arn to all for config actions
TOEL2 Mar 27, 2026
a94fdc0
Merge branch 'main' into feature/ELI-702-code-signing
TOEL2 Mar 30, 2026
a383695
[ELI-702] - disabling signing enforcement for now
TOEL2 Mar 30, 2026
c9d8c2a
[ELI-702] - checkov suppression
TOEL2 Mar 30, 2026
4e61c0c
[ELI-702] - removing workflow changes for now
TOEL2 Mar 30, 2026
cbba2ef
Merge branch 'main' into feature/ELI-702-code-signing
TOEL2 Mar 30, 2026
9bf8b61
Merge branch 'main' into feature/ELI-702-code-signing
TOEL2 Mar 31, 2026
a78a48f
Merge branch 'main' into feature/ELI-702-code-signing
TOEL2 Apr 9, 2026
547344d
[ELI-702] Update resource name
TOEL2 Apr 9, 2026
6e8cba3
[ELI-702] pulling in main
TOEL2 Apr 11, 2026
59b54ca
[ELI-702] removing duplicate signer perm
TOEL2 Apr 11, 2026
f888da3
[ELI-702] slight name change
TOEL2 Apr 13, 2026
05685f5
[ELI-702] converting to manual to test
TOEL2 Apr 13, 2026
de5413b
[ELI-702] workflow name change
TOEL2 Apr 13, 2026
339738e
[ELI] formatting
TOEL2 Apr 14, 2026
a632668
Merge branch 'main' into feature/ELI-702-code-signing
TOEL2 Apr 14, 2026
153f63c
[ELI-702] removing unnecessary deployment
TOEL2 Apr 14, 2026
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
139 changes: 125 additions & 14 deletions .github/workflows/cicd-3-test-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,17 @@ jobs:
echo "name=$TAG" >> $GITHUB_OUTPUT
echo "Resolved tag: $TAG"

deploy:
name: "Deploy to TEST (approval required)"
sign-lambda-artifact:
name: "Sign lambda artifact for TEST"
runs-on: ubuntu-latest
needs: [metadata]
environment: test
timeout-minutes: 10080
timeout-minutes: 45
permissions:
id-token: write
contents: read
outputs:
bucket_name: ${{ steps.tf_output.outputs.bucket_name }}
steps:
- name: "Checkout same commit"
uses: actions/checkout@v6
Expand All @@ -80,6 +82,124 @@ jobs:
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}

- name: "Terraform Init (TEST api-layer)"
env:
ENVIRONMENT: test
WORKSPACE: "default"
run: |
echo "Running: make terraform env=$ENVIRONMENT workspace=$WORKSPACE stack=api-layer tf-command=init"
make terraform env=$ENVIRONMENT stack=api-layer tf-command=init workspace=$WORKSPACE
working-directory: ./infrastructure
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

The signing job depends on a signing profile that must already exist in the target AWS account/state before this workflow runs (it only does init + output). On the first deployment after introducing these Terraform resources, the profile/config won’t exist yet, so signing will fail before the workflow reaches the later terraform apply that would create them. Consider a bootstrap path (e.g. create signing profile/config in a prior step/stack, or handle missing outputs by applying the signing resources first).

Suggested change
working-directory: ./infrastructure
working-directory: ./infrastructure/stacks/api-layer

Copilot uses AI. Check for mistakes.

- name: "Extract S3 bucket name from Terraform output"
id: tf_output
run: |
BUCKET=$(terraform output -raw lambda_artifact_bucket)
PROFILE=$(terraform output -raw lambda_signing_profile_name)
echo "bucket_name=$BUCKET" >> $GITHUB_OUTPUT
echo "signing_profile_name=$PROFILE" >> $GITHUB_OUTPUT
working-directory: ./infrastructure/stacks/api-layer
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

This step runs terraform output -raw lambda_signing_profile_name, but the api-layer stack currently doesn’t define an output "lambda_signing_profile_name" (only lambda_artifact_bucket exists in infrastructure/stacks/api-layer/s3_buckets.tf). As a result, this workflow will fail here. Expose the module output from the stack (or avoid relying on a Terraform output by deriving/standardising the profile name).

Copilot uses AI. Check for mistakes.

- name: "Upload unsigned lambda artifact to S3"
run: |
aws s3 cp ./dist/lambda.zip \
s3://${{ steps.tf_output.outputs.bucket_name }}/unsigned/${{ needs.metadata.outputs.tag }}/lambda.zip \
--region eu-west-2

- name: "Get uploaded source object version"
id: source_object
run: |
VERSION_ID=$(aws s3api head-object \
--bucket "${{ steps.tf_output.outputs.bucket_name }}" \
--key "unsigned/${{ needs.metadata.outputs.tag }}/lambda.zip" \
--query 'VersionId' \
--output text \
--region eu-west-2)
echo "version_id=$VERSION_ID" >> $GITHUB_OUTPUT

- name: "Start signing job"
id: signing
env:
SIGNING_PROFILE_NAME: ${{ steps.tf_output.outputs.signing_profile_name }}
run: |
JOB_ID=$(aws signer start-signing-job \
--source "s3={bucketName=${{ steps.tf_output.outputs.bucket_name }},key=unsigned/${{ needs.metadata.outputs.tag }}/lambda.zip,version=${{ steps.source_object.outputs.version_id }}}" \
--destination "s3={bucketName=${{ steps.tf_output.outputs.bucket_name }},prefix=signed/${{ needs.metadata.outputs.tag }}/}" \
--profile-name "$SIGNING_PROFILE_NAME" \
--query 'jobId' \
--output text \
--region eu-west-2)
echo "job_id=$JOB_ID" >> $GITHUB_OUTPUT

- name: "Wait for signing job"
run: |
aws signer wait successful-signing-job \
--job-id "${{ steps.signing.outputs.job_id }}" \
--region eu-west-2

- name: "Resolve signed artifact location"
id: signed_object
run: |
SIGNED_BUCKET=$(aws signer describe-signing-job \
--job-id "${{ steps.signing.outputs.job_id }}" \
--region eu-west-2 \
--query 'signedObject.s3.bucketName' \
--output text)

SIGNED_KEY=$(aws signer describe-signing-job \
--job-id "${{ steps.signing.outputs.job_id }}" \
--region eu-west-2 \
--query 'signedObject.s3.key' \
--output text)

echo "bucket_name=$SIGNED_BUCKET" >> $GITHUB_OUTPUT
echo "object_key=$SIGNED_KEY" >> $GITHUB_OUTPUT

- name: "Download signed lambda artifact"
run: |
aws s3 cp \
"s3://${{ steps.signed_object.outputs.bucket_name }}/${{ steps.signed_object.outputs.object_key }}" \
./dist/lambda.zip \
--region eu-west-2

- name: "Upload signed lambda artifact for current workflow"
uses: actions/upload-artifact@v6
with:
name: lambda-${{ needs.metadata.outputs.tag }}
path: ./dist/lambda.zip

deploy:
name: "Deploy to TEST (approval required)"
runs-on: ubuntu-latest
needs: [metadata, sign-lambda-artifact]
environment: test
timeout-minutes: 10080
permissions:
id-token: write
contents: read
steps:
- name: "Checkout same commit"
uses: actions/checkout@v6
with:
ref: ${{ github.event.workflow_run.head_sha }}

- name: "Setup Terraform"
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ needs.metadata.outputs.terraform_version }}

- name: "Download signed lambda artefact"
uses: actions/download-artifact@v7
with:
name: lambda-${{ needs.metadata.outputs.tag }}
path: ./dist

- name: "Configure AWS Credentials"
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/service-roles/github-actions-api-deployment-role
aws-region: eu-west-2

- name: "Terraform Apply (TEST)"
env:
ENVIRONMENT: test
Expand All @@ -92,7 +212,6 @@ jobs:
TF_VAR_OPERATOR_EMAILS: ${{ vars.SECRET_ROTATION_OPERATOR_EMAILS }}
TF_VAR_PROXYGEN_PRIVATE_KEY_PTL: ${{ secrets.PROXYGEN_PRIVATE_KEY_PTL }}
TF_VAR_PROXYGEN_PRIVATE_KEY_PROD: ${{ secrets.PROXYGEN_PRIVATE_KEY_PROD }}

run: |
mkdir -p ./build
echo "Deploying tag: ${{ needs.metadata.outputs.tag }}"
Expand All @@ -109,17 +228,10 @@ jobs:
pip install boto3
python scripts/feature_toggle/validate_toggles.py

- name: "Extract S3 bucket name from Terraform output"
id: tf_output
run: |
BUCKET=$(terraform output -raw lambda_artifact_bucket)
echo "bucket_name=$BUCKET" >> $GITHUB_OUTPUT
working-directory: ./infrastructure/stacks/api-layer

- name: "Upload lambda artifact to S3"
- name: "Upload signed lambda artifact to S3"
run: |
aws s3 cp ./dist/lambda.zip \
s3://${{ steps.tf_output.outputs.bucket_name }}/artifacts/${{ needs.metadata.outputs.tag }}/lambda.zip \
s3://${{ needs.sign-lambda-artifact.outputs.bucket_name }}/artifacts/${{ needs.metadata.outputs.tag }}/lambda.zip \
--region eu-west-2

regression-tests:
Expand All @@ -130,4 +242,3 @@ jobs:
ENVIRONMENT: "test"
VERSION_NUMBER: "main"
secrets: inherit

2 changes: 2 additions & 0 deletions infrastructure/modules/lambda/lambda.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ resource "aws_lambda_function" "eligibility_signposting_lambda" {

source_code_hash = filebase64sha256(var.file_name)

code_signing_config_arn = local.enable_lambda_code_signing ? aws_lambda_code_signing_config.signing_config.arn : null

Comment thread
TOEL2 marked this conversation as resolved.
runtime = var.runtime
timeout = 30
memory_size = 2048
Expand Down
3 changes: 3 additions & 0 deletions infrastructure/modules/lambda/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
locals {
enable_lambda_code_signing = contains(["test", "preprod", "prod"], var.environment)
}
29 changes: 29 additions & 0 deletions infrastructure/modules/lambda/signing.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
resource "aws_signer_signing_profile" "lambda_signing" {
name = "eligibilityapi${var.environment}lambdasigningprofile"
Comment thread
TOEL2 marked this conversation as resolved.
Outdated
#aws signer is strict with names, does not like hyphens or underscores

platform_id = "AWSLambda-SHA384-ECDSA"

signature_validity_period {
value = 365
type = "DAYS"
}
}

resource "aws_lambda_code_signing_config" "signing_config" {
allowed_publishers {
signing_profile_version_arns = [
aws_signer_signing_profile.lambda_signing.version_arn
]
}

policies {
untrusted_artifact_on_deployment = "Enforce"
}

Comment thread
TOEL2 marked this conversation as resolved.
description = "Only allow Lambda bundles signed by our trusted signer profile"
}

output "lambda_signing_profile_name" {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this might need to be added to outputs.tf in the module, then potentially re-output in the stack (in infrastructure/stacks/api-layer/lambda.tf ?)

value = aws_signer_signing_profile.lambda_signing.name
}
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,60 @@ resource "aws_iam_policy" "kinesis_management" {
tags = merge(local.tags, { Name = "kinesis-management" })
}

resource "aws_iam_policy" "code_signing_management" {
name = "code-signing-management"
description = "Allow GitHub Actions to manage Lambda code signing and start Signer jobs"
path = "/service-policies/"

policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Sid = "LambdaCodeSigningConfigManagement",
Effect = "Allow",
Action = [
"lambda:CreateCodeSigningConfig",
"lambda:UpdateCodeSigningConfig",
"lambda:DeleteCodeSigningConfig",
"lambda:GetCodeSigningConfig",
"lambda:ListCodeSigningConfigs",
"lambda:GetFunctionCodeSigningConfig",
"lambda:ListTags",
"lambda:DeleteFunctionCodeSigningConfig",
"lambda:PutFunctionCodeSigningConfig"
],
Resource = "arn:aws:lambda:*:${data.aws_caller_identity.current.account_id}:function:eligibility_signposting_api:*",
Comment thread
TOEL2 marked this conversation as resolved.
Outdated
},
{
Sid = "SignerJobUsage",
Effect = "Allow",
Action = [
"signer:StartSigningJob",
"signer:DescribeSigningJob"
],
Resource = "arn:aws:signer:${var.default_aws_region}:${data.aws_caller_identity.current.account_id}:/signing-jobs/*"
},
Comment thread
TOEL2 marked this conversation as resolved.
Outdated
{
Sid = "SignerProfileManagement",
Effect = "Allow",
Action = [
"signer:PutSigningProfile",
"signer:GetSigningProfile",
"signer:ListSigningProfiles",
"signer:ListTagsForResource",
"signer:TagResource",
"signer:UntagResource",
"signer:CancelSigningProfile",
"signer:RevokeSignature"
],
Resource = "arn:aws:signer:${var.default_aws_region}:${data.aws_caller_identity.current.account_id}:/signing-profiles/eligibility-signposting-api-*"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The resource gets a name like 'EligibilityApiLambdaSigningProfile'

e.g. line 2 of signing.tf:

"${terraform.workspace}"}EligibilityApiLambdaSigningProfile"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah oversight there, changed it in one place and not the other - will fix

}
Comment thread
TOEL2 marked this conversation as resolved.
Outdated
]
})

tags = merge(local.tags, { Name = "code-signing-management" })
}

resource "aws_iam_policy" "cloudwatch_management" {
#checkov:skip=CKV_AWS_355: GetMetricWidgetImage requires wildcard resource
#checkov:skip=CKV_AWS_290: GetMetricWidgetImage requires wildcard resource
Expand Down Expand Up @@ -828,3 +882,8 @@ resource "aws_iam_role_policy_attachment" "kinesis_management_attach" {
role = aws_iam_role.github_actions.name
policy_arn = aws_iam_policy.kinesis_management.arn
}

resource "aws_iam_role_policy_attachment" "code_signing_management" {
role = aws_iam_role.github_actions.name
policy_arn = aws_iam_policy.code_signing_management.arn
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ data "aws_iam_policy_document" "permissions_boundary" {
# Kinesis Stream - audit log streaming
"kinesis:*",

# CodeSigning
"signer:*",
Comment thread
TOEL2 marked this conversation as resolved.
Outdated

# IAM - specific role and policy management
"iam:GetRole*",
"iam:GetPolicy*",
Expand Down Expand Up @@ -156,6 +159,11 @@ data "aws_iam_policy_document" "permissions_boundary" {
"lambda:DeleteProvisionedConcurrencyConfig",
"lambda:ListProvisionedConcurrencyConfigs",
"lambda:PutFunctionConcurrency",
"lambda:GetCodeSigningConfig",
"lambda:DeleteFunctionCodeSigningConfig",
"lambda:PutFunctionCodeSigningConfig",
"lambda:DeleteCodeSigningConfig",
"lambda:CreateCodeSigningConfig",

# CloudWatch Logs - log management
"logs:*",
Expand Down
Loading