Skip to content
Closed
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
28 changes: 26 additions & 2 deletions .github/workflows/cicd-1-pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,36 @@ jobs:
--overrideProjectName "nhs" \
--overrideRoleName "nhs-main-acct-client-callbacks-github-deploy" \
--overrides "branch_name=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}"
pr-create-dynamic-environment-clients:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i was expecting this just to be changes not entirely new code?

name: Create Dynamic Environment (clients)
needs: [metadata, pr-create-dynamic-environment]
runs-on: ubuntu-latest
if: needs.metadata.outputs.does_pull_request_exist == 'true' && github.ref != 'refs/heads/main'
env:
APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }}
APP_PEM_FILE: ${{ secrets.APP_PEM_FILE }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Trigger dynamic environment creation (cbc)
shell: bash
run: |
.github/scripts/dispatch_internal_repo_workflow.sh \
--infraRepoName "$(echo ${{ github.repository }} | cut -d'/' -f2)" \
--releaseVersion "${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" \
--targetWorkflow "dispatch-deploy-dynamic-env.yaml" \
--targetEnvironment "pr${{ needs.metadata.outputs.pr_number }}" \
--targetComponent "cbc" \
--targetAccountGroup "nhs-notify-client-callbacks-dev" \
--terraformAction "apply" \
--overrideProjectName "nhs" \
--overrideRoleName "nhs-main-acct-client-callbacks-github-deploy" \
--overrides "branch_name=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}"
acceptance-stage: # Recommended maximum execution time is 10 minutes
name: "Acceptance stage"
needs: [metadata, build-stage, pr-create-dynamic-environment]
needs: [metadata, build-stage, pr-create-dynamic-environment, pr-create-dynamic-environment-clients]
uses: ./.github/workflows/stage-4-acceptance.yaml
if: >-
contains(fromJSON('["success", "skipped"]'), needs.pr-create-dynamic-environment.result) &&
contains(fromJSON('["success", "skipped"]'), needs.pr-create-dynamic-environment-clients.result) &&
(needs.metadata.outputs.does_pull_request_exist == 'true' || (github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'reopened')) || (github.event_name == 'push' && github.ref == 'refs/heads/main'))
with:
build_datetime: "${{ needs.metadata.outputs.build_datetime }}"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr_closed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
strategy:
max-parallel: 1
matrix:
component: [callbacks]
component: [callbacks, cbc]

steps:
- name: Checkout repository
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/pr_destroy_dynamic_env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ jobs:

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Trigger dynamic environment destroy (cbc)
env:
APP_PEM_FILE: ${{ secrets.APP_PEM_FILE }}
APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }}
shell: bash
run: |
.github/scripts/dispatch_internal_repo_workflow.sh \
--infraRepoName "$(echo ${{ github.repository }} | cut -d'/' -f2)" \
--releaseVersion "${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" \
--targetWorkflow "dispatch-deploy-dynamic-env.yaml" \
--targetEnvironment "pr${{ github.event.number }}" \
--targetComponent "cbc" \
--targetAccountGroup "nhs-notify-client-callbacks-dev" \
--terraformAction "destroy" \
--overrideProjectName "nhs" \
--overrideRoleName "nhs-main-acct-client-callbacks-github-deploy" \
- name: Trigger dynamic environment destroy
env:
APP_PEM_FILE: ${{ secrets.APP_PEM_FILE }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release_created.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
max-parallel: 1
matrix:
component: [callbacks]
component: [callbacks, cbc]

steps:
- name: Checkout repository
Expand Down
19 changes: 19 additions & 0 deletions infrastructure/terraform/components/callbacks-clients/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!-- BEGIN_TF_DOCS -->
<!-- markdownlint-disable -->
<!-- vale off -->

## Requirements

No requirements.
## Inputs

No inputs.
## Modules

No modules.
## Outputs

No outputs.
<!-- vale on -->
<!-- markdownlint-enable -->
<!-- END_TF_DOCS -->
57 changes: 57 additions & 0 deletions infrastructure/terraform/components/callbacks-clients/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
locals {
aws_lambda_functions_dir_path = "../../../../lambdas"

clients_dir_path = "${path.module}/../../modules/clients"

config_clients = merge([
for filename in fileset(local.clients_dir_path, "*.json") : {
(replace(filename, ".json", "")) = jsondecode(file("${local.clients_dir_path}/${filename}"))
}
]...)

# When deploying mock clients, replace sentinel placeholder values with the mock webhook URL and API key.
# Only used for S3 object content — must not be used as a for_each source (contains apply-time values).
enriched_mock_config_clients = var.deploy_mock_clients ? {
for client_id, client in local.config_clients :
client_id => merge(client, {
targets = [
for target in try(client.targets, []) :
merge(target, {
invocationEndpoint = "https://${local.callbacks.mock_webhook_alb_dns_name}/${target.targetId}"
apiKey = merge(target.apiKey, { headerValue = local.callbacks.mock_webhook_api_key })
delivery = merge(try(target.delivery, {}), {
mtls = merge(try(target.delivery.mtls, {}), {
certPinning = merge(try(target.delivery.mtls.certPinning, {}), try(target.delivery.mtls.certPinning.enabled, false) ? {
spkiHash = local.callbacks.mock_server_spki_hash
} : {})
})
})
})
]
})
} : local.config_clients

client_subscriptions = {
for client_id, data in local.config_clients :
client_id => {
for subscription in try(data.subscriptions, []) :
subscription.subscriptionId => {
subscription_id = subscription.subscriptionId
target_ids = try(subscription.targetIds, [])
}
}
}

client_subscription_targets = {
for client_id, data in local.config_clients :
client_id => merge([
for subscription in try(data.subscriptions, []) : {
for target_id in try(subscription.targetIds, []) :
"${subscription.subscriptionId}-${target_id}" => {
subscription_id = subscription.subscriptionId
target_id = target_id
}
}
]...)
}
}
21 changes: 20 additions & 1 deletion infrastructure/terraform/components/callbacks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
| Name | Source | Version |
|------|--------|---------|
| <a name="module_client_config_bucket"></a> [client\_config\_bucket](#module\_client\_config\_bucket) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.7/terraform-s3bucket.zip | n/a |
| <a name="module_client_delivery"></a> [client\_delivery](#module\_client\_delivery) | ../../modules/client-delivery | n/a |
| <a name="module_client_transform_filter_lambda"></a> [client\_transform\_filter\_lambda](#module\_client\_transform\_filter\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.7/terraform-lambda.zip | n/a |
| <a name="module_kms"></a> [kms](#module\_kms) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.7/terraform-kms.zip | n/a |
| <a name="module_mock_webhook_lambda"></a> [mock\_webhook\_lambda](#module\_mock\_webhook\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/3.0.7/terraform-lambda.zip | n/a |
Expand All @@ -61,8 +60,28 @@

| Name | Description |
|------|-------------|
| <a name="output_applications_map_parameter_name"></a> [applications\_map\_parameter\_name](#output\_applications\_map\_parameter\_name) | SSM Parameter Store path for the clientId-to-applicationData map |
| <a name="output_client_config_bucket"></a> [client\_config\_bucket](#output\_client\_config\_bucket) | S3 bucket name for client subscription configuration |
| <a name="output_client_config_bucket_arn"></a> [client\_config\_bucket\_arn](#output\_client\_config\_bucket\_arn) | S3 bucket ARN for client subscription configuration |
| <a name="output_deployment"></a> [deployment](#output\_deployment) | Deployment details used for post-deployment scripts |
| <a name="output_elasticache_cache_name"></a> [elasticache\_cache\_name](#output\_elasticache\_cache\_name) | ElastiCache cache name for SigV4 token presigning |
| <a name="output_elasticache_endpoint"></a> [elasticache\_endpoint](#output\_elasticache\_endpoint) | ElastiCache Serverless endpoint address |
| <a name="output_elasticache_iam_username"></a> [elasticache\_iam\_username](#output\_elasticache\_iam\_username) | IAM username for ElastiCache authentication |
| <a name="output_event_bus_name"></a> [event\_bus\_name](#output\_event\_bus\_name) | EventBridge bus name for client subscription rules |
| <a name="output_kms_key_arn"></a> [kms\_key\_arn](#output\_kms\_key\_arn) | KMS key ARN for encryption at rest |
| <a name="output_lambda_s3_bucket"></a> [lambda\_s3\_bucket](#output\_lambda\_s3\_bucket) | S3 bucket for Lambda function artefacts |
| <a name="output_lambda_security_group_id"></a> [lambda\_security\_group\_id](#output\_lambda\_security\_group\_id) | Security group ID for per-client HTTPS Client Lambda functions |
| <a name="output_log_destination_arn"></a> [log\_destination\_arn](#output\_log\_destination\_arn) | Firehose destination ARN for log forwarding |
| <a name="output_log_subscription_role_arn"></a> [log\_subscription\_role\_arn](#output\_log\_subscription\_role\_arn) | IAM role ARN for CloudWatch log subscription |
| <a name="output_mock_server_spki_hash"></a> [mock\_server\_spki\_hash](#output\_mock\_server\_spki\_hash) | Base64 SHA-256 SPKI hash of the mock server certificate |
| <a name="output_mock_webhook_alb_dns_name"></a> [mock\_webhook\_alb\_dns\_name](#output\_mock\_webhook\_alb\_dns\_name) | DNS name of the mock webhook ALB (dev/test only) |
| <a name="output_mock_webhook_api_key"></a> [mock\_webhook\_api\_key](#output\_mock\_webhook\_api\_key) | API key for the mock webhook endpoint (dev/test only) |
| <a name="output_mock_webhook_lambda_log_group_name"></a> [mock\_webhook\_lambda\_log\_group\_name](#output\_mock\_webhook\_lambda\_log\_group\_name) | CloudWatch log group name for mock webhook lambda (for integration test queries) |
| <a name="output_mtls_cert_secret_arn"></a> [mtls\_cert\_secret\_arn](#output\_mtls\_cert\_secret\_arn) | Secrets Manager ARN for the shared mTLS client certificate |
| <a name="output_mtls_test_ca_s3_key"></a> [mtls\_test\_ca\_s3\_key](#output\_mtls\_test\_ca\_s3\_key) | S3 key for dev CA certificate PEM bundle |
| <a name="output_mtls_test_cert_s3_bucket"></a> [mtls\_test\_cert\_s3\_bucket](#output\_mtls\_test\_cert\_s3\_bucket) | S3 bucket for dev mTLS test certificates |
| <a name="output_mtls_test_cert_s3_key"></a> [mtls\_test\_cert\_s3\_key](#output\_mtls\_test\_cert\_s3\_key) | S3 key for dev mTLS test certificate bundle |
| <a name="output_vpc_subnet_ids"></a> [vpc\_subnet\_ids](#output\_vpc\_subnet\_ids) | VPC subnet IDs for Lambda execution |
<!-- vale on -->
<!-- markdownlint-enable -->
<!-- END_TF_DOCS -->
55 changes: 0 additions & 55 deletions infrastructure/terraform/components/callbacks/locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,65 +5,10 @@ locals {
root_domain_name = "${var.environment}.${local.acct.route53_zone_names["client-callbacks"]}" # e.g. [main|dev|abxy0].smsnudge.[dev|nonprod|prod].nhsnotify.national.nhs.uk
root_domain_id = local.acct.route53_zone_ids["client-callbacks"]

clients_dir_path = "${path.module}/../../modules/clients"

config_clients = merge([
for filename in fileset(local.clients_dir_path, "*.json") : {
(replace(filename, ".json", "")) = jsondecode(file("${local.clients_dir_path}/${filename}"))
}
]...)

# SPKI hash of the mock webhook server certificate for cert-pinning enrichment.
# Computed via external data source because Terraform cannot SHA-256 hash raw binary (DER) data natively.
mock_server_spki_hash = var.deploy_mock_clients ? data.external.mock_server_spki_hash[0].result.hash : ""

# When deploying mock clients, replace sentinel placeholder values with the mock webhook URL and API key.
# Only used for S3 object content — must not be used as a for_each source (contains apply-time values).
enriched_mock_config_clients = var.deploy_mock_clients ? {
for client_id, client in local.config_clients :
client_id => merge(client, {
targets = [
for target in try(client.targets, []) :
merge(target, {
invocationEndpoint = "https://${aws_lb.mock_webhook_mtls[0].dns_name}/${target.targetId}"
apiKey = merge(target.apiKey, { headerValue = random_password.mock_webhook_api_key[0].result })
delivery = merge(try(target.delivery, {}), {
mtls = merge(try(target.delivery.mtls, {}), {
certPinning = merge(try(target.delivery.mtls.certPinning, {}), try(target.delivery.mtls.certPinning.enabled, false) ? {
spkiHash = local.mock_server_spki_hash
} : {})
})
})
})
]
})
} : local.config_clients


client_subscriptions = {
for client_id, data in local.config_clients :
client_id => {
for subscription in try(data.subscriptions, []) :
subscription.subscriptionId => {
subscription_id = subscription.subscriptionId
target_ids = try(subscription.targetIds, [])
}
}
}

client_subscription_targets = {
for client_id, data in local.config_clients :
client_id => merge([
for subscription in try(data.subscriptions, []) : {
for target_id in try(subscription.targetIds, []) :
"${subscription.subscriptionId}-${target_id}" => {
subscription_id = subscription.subscriptionId
target_id = target_id
}
}
]...)
}

applications_map_parameter_name = coalesce(var.applications_map_parameter_name, "/${var.project}/${var.environment}/${var.component}/applications-map")

client_config_bucket_arn = "arn:aws:s3:::${var.project}-${var.aws_account_id}-${var.region}-${var.environment}-${var.component}-subscription-config"
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ data "aws_iam_policy_document" "kms" {
values = [
"arn:aws:sqs:${var.region}:${var.aws_account_id}:${var.project}-${var.environment}-callbacks-inbound-event-queue",
"arn:aws:sqs:${var.region}:${var.aws_account_id}:${var.project}-${var.environment}-callbacks-inbound-event-dlq",
"arn:aws:sqs:${var.region}:${var.aws_account_id}:${var.project}-${var.environment}-callbacks-*-dlq-queue" #wildcard here so that DLQs for clients can also use this key
"arn:aws:sqs:${var.region}:${var.aws_account_id}:${var.project}-${var.environment}-cbc-*-delivery-queue",
"arn:aws:sqs:${var.region}:${var.aws_account_id}:${var.project}-${var.environment}-cbc-*-delivery-dlq-queue",
]
}
}
Expand Down
Loading
Loading