Skip to content
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
244 changes: 244 additions & 0 deletions .github/workflows/cicd-3-test-deploy-updated.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
name: "Updated - 3. CD | Deploy to Test"

#on:
# workflow_run:
# workflows: ["2. CD | Deploy to Dev"]
# types: [completed]

concurrency:
group: test-deployments
cancel-in-progress: false

permissions:
contents: read
id-token: write
actions: read

jobs:
metadata:
name: "Resolve metadata from triggering run"
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
outputs:
terraform_version: ${{ steps.vars.outputs.terraform_version }}
tag: ${{ steps.tag.outputs.name }}
steps:
- name: "Checkout exact commit from CI/CD publish"
uses: actions/checkout@v6
with:
ref: ${{ github.event.workflow_run.head_sha }}

- name: "Set CI/CD variables"
id: vars
run: |
echo "terraform_version=$(grep '^terraform' .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT

- name: "Resolve the dev-* tag for this commit"
id: tag
run: |
git fetch --tags --force
SHA="${{ github.event.workflow_run.head_sha }}"
TAG=$(git tag --points-at "$SHA" | grep '^dev-' | sort -r | head -n1 || true)
if [ -z "$TAG" ]; then
echo "No dev-* tag found on $SHA" >&2
exit 1
fi
echo "name=$TAG" >> $GITHUB_OUTPUT
echo "Resolved tag: $TAG"

sign-lambda-artifact:
name: "Sign lambda artifact for TEST"
runs-on: ubuntu-latest
needs: [metadata]
environment: test
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
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: "Configure AWS Credentials"
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.

Likewise (to below comment), this should still have the iams roles deployment

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: "Download lambda artefact from dev workflow"
uses: actions/download-artifact@v7
with:
name: lambda-${{ needs.metadata.outputs.tag }}
path: ./dist
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
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm not clear on intention here - no apply step, which would presumably block, unless the intention is to run the other workflow, to deploy the codesigning infra, then ths to test it out / turn it on?

Copy link
Copy Markdown
Contributor Author

@TOEL2 TOEL2 Mar 31, 2026

Choose a reason for hiding this comment

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

its to get the outputs from the init so that we can use them in the signing step below (120)

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.

then once we have a signed lambda we can do the next job in this workflow - deploy which does the tf apply

- 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

- name: "Upload unsigned lambda artifact to S3"
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.

I'm wondering now whether each environment will need to sign, as we're using separate accounts (so Prod has no reason to trust PreProd etc....)

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
WORKSPACE: "default"
TF_VAR_API_CA_CERT: ${{ secrets.API_CA_CERT }}
TF_VAR_API_CLIENT_CERT: ${{ secrets.API_CLIENT_CERT }}
TF_VAR_API_PRIVATE_KEY_CERT: ${{ secrets.API_PRIVATE_KEY_CERT }}
TF_VAR_SPLUNK_HEC_TOKEN: ${{ secrets.SPLUNK_HEC_TOKEN }}
TF_VAR_SPLUNK_HEC_ENDPOINT: ${{ secrets.SPLUNK_HEC_ENDPOINT }}
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 }}"
echo "Running: make terraform env=$ENVIRONMENT workspace=$WORKSPACE stack=networking tf-command=apply"
make terraform env=$ENVIRONMENT stack=networking tf-command=apply workspace=$WORKSPACE
echo "Running: make terraform env=$ENVIRONMENT workspace=$WORKSPACE stack=api-layer tf-command=apply"
make terraform env=$ENVIRONMENT stack=api-layer tf-command=apply workspace=$WORKSPACE
working-directory: ./infrastructure

- name: "Validate Feature Toggles"
env:
ENV: test
run: |
pip install boto3
python scripts/feature_toggle/validate_toggles.py

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

regression-tests:
name: "Regression Tests"
needs: deploy
uses: ./.github/workflows/regression-tests.yml
with:
ENVIRONMENT: "test"
VERSION_NUMBER: "main"
secrets: inherit
1 change: 0 additions & 1 deletion .github/workflows/cicd-3-test-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,3 @@ jobs:
ENVIRONMENT: "test"
VERSION_NUMBER: "main"
secrets: inherit

3 changes: 2 additions & 1 deletion infrastructure/modules/lambda/lambda.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
resource "aws_lambda_function" "eligibility_signposting_lambda" {
#checkov:skip=CKV_AWS_116: No deadletter queue is configured for this Lambda function, as the requests are synchronous
#checkov:skip=CKV_AWS_115: Concurrent execution limit will be set at APIM level, not at Lambda level
#checkov:skip=CKV_AWS_272: Skipping code signing but flagged to create ticket to investigate on ELI-238
# If the file is not in the current working directory you will need to include a
# path.module in the filename.
filename = var.file_name
Expand All @@ -11,6 +10,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

runtime = var.runtime
timeout = 30
memory_size = 2048
Expand Down
5 changes: 5 additions & 0 deletions infrastructure/modules/lambda/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
locals {
enable_lambda_code_signing = false
# enable_lambda_code_signing = contains(["test", "preprod", "prod"], var.environment)
# For the next deployment ^
}
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 = "${terraform.workspace == "default" ? "" : "${terraform.workspace}"}EligibilityApiLambdaSigningProfile"
#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"
}

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 @@ -740,6 +740,63 @@ resource "aws_iam_policy" "kinesis_management" {
tags = merge(local.tags, { Name = "kinesis-management" })
}

resource "aws_iam_policy" "code_signing_management" {
#checkov:skip=CKV_AWS_290: Actions require wildcard resource for Lambda code signing configs and Signer jobs
#checkov:skip=CKV_AWS_235: Actions require wildcard resource for Lambda code signing configs and Signer jobs
#checkov:skip=CKV_AWS_355: Actions require wildcard resource for Lambda code signing configs and Signer jobs
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 = "*"
},
{
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/*"
},
{
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

}
]
})

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 @@ -859,3 +916,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 @@ -92,6 +92,9 @@ data "aws_iam_policy_document" "permissions_boundary" {
# Kinesis Stream - audit log streaming
"kinesis:*",

# CodeSigning
"signer:*",

# IAM - specific role and policy management
"iam:GetRole*",
"iam:GetPolicy*",
Expand Down Expand Up @@ -159,6 +162,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