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
3 changes: 2 additions & 1 deletion .github/actions/acceptance-tests/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ runs:
shell: bash
env:
PROJECT: nhs
COMPONENT: ${{ inputs.targetComponent }}
COMPONENT: cb
CLIENT_COMPONENT: cbc
run: |
make test-${{ inputs.testType }}
14 changes: 14 additions & 0 deletions .github/workflows/cicd-1-pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,20 @@ jobs:
--overrideProjectName "nhs" \
--overrideRoleName "nhs-main-acct-client-callbacks-github-deploy" \
--overrides "branch_name=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}"
- name: Trigger callback-clients dynamic environment creation
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 "callback-clients" \
--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]
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, callback-clients]

steps:
- name: Checkout repository
Expand Down
20 changes: 18 additions & 2 deletions .github/workflows/pr_destroy_dynamic_env.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,23 @@ jobs:

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Trigger dynamic environment destroy
- name: Trigger callback-clients dynamic environment destroy
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 "callback-clients" \
--targetAccountGroup "nhs-notify-client-callbacks-dev" \
--terraformAction "destroy" \
--overrideProjectName "nhs" \
--overrideRoleName "nhs-main-acct-client-callbacks-github-deploy"
- name: Trigger callbacks dynamic environment destroy
env:
APP_PEM_FILE: ${{ secrets.APP_PEM_FILE }}
APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }}
Expand All @@ -34,4 +50,4 @@ jobs:
--targetAccountGroup "nhs-notify-client-callbacks-dev" \
--terraformAction "destroy" \
--overrideProjectName "nhs" \
--overrideRoleName "nhs-main-acct-client-callbacks-github-deploy" \
--overrideRoleName "nhs-main-acct-client-callbacks-github-deploy"
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, callback-clients]

steps:
- name: Checkout repository
Expand Down
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ nodejs 24.14.1
pnpm 10.33.0
pre-commit 3.6.0
ruby 3.3.6
terraform 1.10.1
terraform 1.14.3
terraform-docs 0.19.0
#trivy 0.61.0 - TODO - Re-visit Trivy usage https://nhsd-jira.digital.nhs.uk/browse/CCM-15549
vale 3.6.0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform 1.14.3
52 changes: 52 additions & 0 deletions infrastructure/terraform/components/callback-clients/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<!-- BEGIN_TF_DOCS -->
<!-- markdownlint-disable -->
<!-- vale off -->

## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.10.1 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | 6.13 |
| <a name="requirement_external"></a> [external](#requirement\_external) | ~> 2.0 |
| <a name="requirement_random"></a> [random](#requirement\_random) | ~> 3.0 |
| <a name="requirement_tls"></a> [tls](#requirement\_tls) | ~> 4.0 |
## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_applications_map_s3_bucket"></a> [applications\_map\_s3\_bucket](#input\_applications\_map\_s3\_bucket) | S3 bucket for the applications map | `string` | n/a | yes |
| <a name="input_aws_account_id"></a> [aws\_account\_id](#input\_aws\_account\_id) | The AWS Account ID (numeric) | `string` | n/a | yes |
| <a name="input_client_config_s3_bucket"></a> [client\_config\_s3\_bucket](#input\_client\_config\_s3\_bucket) | S3 bucket for client subscription configuration | `string` | n/a | yes |
| <a name="input_default_tags"></a> [default\_tags](#input\_default\_tags) | A map of default tags to apply to all taggable resources within the component | `map(string)` | `{}` | no |
| <a name="input_deploy_mock_clients"></a> [deploy\_mock\_clients](#input\_deploy\_mock\_clients) | Flag to deploy mock webhook lambda for integration testing | `bool` | `false` | no |
| <a name="input_enable_xray_tracing"></a> [enable\_xray\_tracing](#input\_enable\_xray\_tracing) | Enable AWS X-Ray active tracing for Lambda functions | `bool` | `false` | no |
| <a name="input_environment"></a> [environment](#input\_environment) | The name of the tfscaffold environment | `string` | n/a | yes |
| <a name="input_force_lambda_code_deploy"></a> [force\_lambda\_code\_deploy](#input\_force\_lambda\_code\_deploy) | If the lambda package in s3 has the same commit id tag as the terraform build branch, the lambda will not update automatically. Set to True if making changes to Lambda code from on the same commit for example during development | `bool` | `false` | no |
| <a name="input_group"></a> [group](#input\_group) | The group variables are being inherited from (often synonmous with account short-name) | `string` | n/a | yes |
| <a name="input_log_level"></a> [log\_level](#input\_log\_level) | The log level to be used in lambda functions within the component | `string` | `"INFO"` | no |
| <a name="input_log_retention_in_days"></a> [log\_retention\_in\_days](#input\_log\_retention\_in\_days) | The retention period in days for the Cloudwatch Logs events to be retained, default of 0 is indefinite | `number` | `0` | no |
| <a name="input_mtls_ca_s3_key"></a> [mtls\_ca\_s3\_key](#input\_mtls\_ca\_s3\_key) | S3 key for the CA certificate PEM bundle used for server verification | `string` | `""` | no |
| <a name="input_mtls_cert_s3_bucket"></a> [mtls\_cert\_s3\_bucket](#input\_mtls\_cert\_s3\_bucket) | S3 bucket containing the mTLS client certificate bundle | `string` | `""` | no |
| <a name="input_mtls_cert_s3_key"></a> [mtls\_cert\_s3\_key](#input\_mtls\_cert\_s3\_key) | S3 key for the mTLS client certificate PEM bundle | `string` | `""` | no |
| <a name="input_parent_acct_environment"></a> [parent\_acct\_environment](#input\_parent\_acct\_environment) | Name of the environment responsible for the acct resources used, affects things like DNS zone. Useful for named dev environments | `string` | `"main"` | no |
| <a name="input_parent_callbacks_environment"></a> [parent\_callbacks\_environment](#input\_parent\_callbacks\_environment) | The name of the environment which deployed the parent callbacks component. Used to identify the appropriate state file. | `string` | `"main"` | no |
| <a name="input_project"></a> [project](#input\_project) | The name of the tfscaffold project | `string` | n/a | yes |
| <a name="input_region"></a> [region](#input\_region) | The AWS Region | `string` | n/a | yes |
| <a name="input_s3_enable_force_destroy"></a> [s3\_enable\_force\_destroy](#input\_s3\_enable\_force\_destroy) | Whether to enable force destroy for the S3 buckets created in this module | `bool` | `false` | no |
| <a name="input_token_bucket_burst_capacity"></a> [token\_bucket\_burst\_capacity](#input\_token\_bucket\_burst\_capacity) | Token bucket burst capacity used by the rate limiter | `number` | `2250` | no |
## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_client_delivery"></a> [client\_delivery](#module\_client\_delivery) | ../../modules/client-delivery | 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 |
## Outputs

| Name | Description |
|------|-------------|
| <a name="output_applications_map_s3"></a> [applications\_map\_s3](#output\_applications\_map\_s3) | S3 location of the client-to-application map |
| <a name="output_mock_webhook_alb_dns"></a> [mock\_webhook\_alb\_dns](#output\_mock\_webhook\_alb\_dns) | DNS name of the mock webhook ALB |
<!-- vale on -->
<!-- markdownlint-enable -->
<!-- END_TF_DOCS -->
62 changes: 62 additions & 0 deletions infrastructure/terraform/components/callback-clients/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
locals {
bc_name = "client-callbacks"
aws_lambda_functions_dir_path = "../../../../lambdas"
log_destination_arn = local.callbacks.log_destination_arn
log_subscription_role_arn = local.callbacks.log_subscription_role_arn

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}"))
}
]...)

mock_server_spki_hash = var.deploy_mock_clients ? data.external.mock_server_spki_hash[0].result.hash : ""

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_s3_key = "${var.environment}/applications-map.json"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
locals {
callbacks = data.terraform_remote_state.callbacks.outputs
acct = data.terraform_remote_state.acct.outputs
}

data "terraform_remote_state" "callbacks" {
backend = "s3"

config = {
bucket = local.terraform_state_bucket

key = format(
"%s/%s/%s/%s/callbacks.tfstate",
var.project,
var.aws_account_id,
"eu-west-2",
var.parent_callbacks_environment
)

region = "eu-west-2"
}
}

data "terraform_remote_state" "acct" {
backend = "s3"

config = {
bucket = local.terraform_state_bucket

key = format(
"%s/%s/%s/%s/acct.tfstate",
var.project,
var.aws_account_id,
"eu-west-2",
var.parent_acct_environment
)

region = "eu-west-2"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
locals {
component = "cbc"

terraform_state_bucket = format(
"%s-tfscaffold-%s-%s",
var.project,
var.aws_account_id,
var.region,
)

csi = replace(
format(
"%s-%s-%s",
var.project,
var.environment,
local.component,
),
"_",
"",
)

# CSI for use in resources with a global namespace, i.e. S3 Buckets
csi_global = replace(
format(
"%s-%s-%s-%s-%s",
var.project,
var.aws_account_id,
var.region,
var.environment,
local.component,
),
"_",
"",
)

default_tags = merge(
var.default_tags,
{
Project = var.project
Environment = var.environment
Component = local.component
Group = var.group
Name = local.csi
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,25 @@ module "client_delivery" {
project = var.project
aws_account_id = var.aws_account_id
region = var.region
component = var.component
component = local.component
environment = var.environment
group = var.group

client_id = each.key
client_bus_name = aws_cloudwatch_event_bus.main.name
kms_key_arn = module.kms.key_arn
client_bus_name = local.callbacks.eventbus_name.name
kms_key_arn = local.callbacks.kms_key_arn

subscriptions = local.client_subscriptions[each.key]
subscription_targets = local.client_subscription_targets[each.key]

client_config_bucket = module.client_config_bucket.bucket
client_config_bucket_arn = module.client_config_bucket.arn
client_config_bucket = var.client_config_s3_bucket
client_config_bucket_arn = local.callbacks.client_config_bucket.arn
client_config_key_prefix = "${var.environment}/client_subscriptions/"

applications_map_parameter_name = local.applications_map_parameter_name
applications_map_s3_bucket = var.applications_map_s3_bucket
applications_map_s3_key = local.applications_map_s3_key

lambda_s3_bucket = local.acct.s3_buckets["lambda_function_artefacts"]["id"]
lambda_s3_bucket = local.callbacks.lambda_s3_bucket
lambda_code_base_path = local.aws_lambda_functions_dir_path

force_lambda_code_deploy = var.force_lambda_code_deploy
Expand All @@ -30,18 +32,18 @@ module "client_delivery" {
enable_xray_tracing = var.enable_xray_tracing

log_destination_arn = local.log_destination_arn
log_subscription_role_arn = local.acct.log_subscription_role_arn
log_subscription_role_arn = local.log_subscription_role_arn

elasticache_endpoint = aws_elasticache_serverless_cache.delivery_state.endpoint[0].address
elasticache_cache_name = aws_elasticache_serverless_cache.delivery_state.name
elasticache_iam_username = "${var.project}-${var.environment}-${var.component}-elasticache-user"
elasticache_endpoint = local.callbacks.elasticache.endpoint
elasticache_cache_name = local.callbacks.elasticache.cache_name
elasticache_iam_username = local.callbacks.elasticache.iam_username

mtls_cert_s3_bucket = local.mtls_cert_s3_bucket
mtls_cert_s3_key = local.mtls_cert_s3_key # gitleaks:allow
mtls_ca_s3_key = local.mtls_ca_s3_key # gitleaks:allow

token_bucket_burst_capacity = var.token_bucket_burst_capacity

vpc_subnet_ids = try(local.acct.private_subnets[local.bc_name], [])
lambda_security_group_id = aws_security_group.https_client_lambda.id
vpc_subnet_ids = local.callbacks.vpc_subnet_ids
lambda_security_group_id = local.callbacks.security_group_id
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ resource "aws_security_group" "mock_webhook_alb" {
resource "aws_vpc_security_group_ingress_rule" "mock_webhook_alb_https" {
count = var.deploy_mock_clients ? 1 : 0
security_group_id = aws_security_group.mock_webhook_alb[0].id
referenced_security_group_id = aws_security_group.https_client_lambda.id
referenced_security_group_id = local.callbacks.security_group_id
from_port = 443
to_port = 443
ip_protocol = "tcp"
Expand All @@ -41,17 +41,17 @@ resource "aws_acm_certificate" "mock_webhook_server" {

resource "aws_lb" "mock_webhook_mtls" {
count = var.deploy_mock_clients ? 1 : 0
name = substr("${local.csi}-mock-mtls", 0, 32)
name = "${local.csi}-mock-mtls"
internal = true
load_balancer_type = "application"
security_groups = [aws_security_group.mock_webhook_alb[0].id]
subnets = try(local.acct.private_subnets[local.bc_name], [])
subnets = local.callbacks.vpc_subnet_ids
tags = local.default_tags
}

resource "aws_lb_target_group" "mock_webhook_mtls" {
count = var.deploy_mock_clients ? 1 : 0
name = substr("${local.csi}-mock-mtls", 0, 32)
name = "${local.csi}-mock-mtls"
target_type = "lambda"
tags = local.default_tags
}
Expand Down
Loading
Loading