From 5e90116b4cae9d61dd909377ccee57eafd719630 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:20:56 +0100 Subject: [PATCH 01/43] testing english language --- scripts/infra/README.md | 3 +- scripts/infra/iam-oidc-deploy-role.tf | 189 ++++++++++++++++++++++++++ scripts/infra/iam-oidc-pr-role.tf | 149 ++++++++++++++++++++ scripts/infra/outputs.tf | 12 ++ scripts/infra/tf_import.sh | 47 +++++++ scripts/infra/variables.tf | 8 +- scripts/terraform/terraform.lib.sh | 1 + scripts/terraform/terraform.sh | 10 +- 8 files changed, 414 insertions(+), 5 deletions(-) create mode 100644 scripts/infra/iam-oidc-deploy-role.tf create mode 100644 scripts/infra/iam-oidc-pr-role.tf create mode 100644 scripts/infra/outputs.tf create mode 100755 scripts/infra/tf_import.sh diff --git a/scripts/infra/README.md b/scripts/infra/README.md index 9f9bd2b..1a99e81 100644 --- a/scripts/infra/README.md +++ b/scripts/infra/README.md @@ -10,9 +10,8 @@ The `scripts/infra` directory contains **one-time bootstrap Terraform configurat This directory creates the infrastructure **backend** and **deployment prerequisites**: -- **Terraform State Backend (S3 + DynamoDB)** +- **Terraform State Backend (S3)** - S3 bucket for storing Terraform state files (`s3-terraform.tf`) - - DynamoDB table for state locking (`dynamodb-terraform.tf`) - Enables safe, concurrent Terraform operations - **GitHub Actions OIDC Integration** diff --git a/scripts/infra/iam-oidc-deploy-role.tf b/scripts/infra/iam-oidc-deploy-role.tf new file mode 100644 index 0000000..0d0785f --- /dev/null +++ b/scripts/infra/iam-oidc-deploy-role.tf @@ -0,0 +1,189 @@ +data "aws_iam_openid_connect_provider" "github_actions" { + arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${var.oidc_provider}" +} + +data "aws_iam_policy_document" "github_actions_assume_role" { + statement { + effect = "Allow" + + principals { + type = "Federated" + identifiers = [data.aws_iam_openid_connect_provider.github_actions.arn] + } + + actions = ["sts:AssumeRoleWithWebIdentity"] + + condition { + test = "StringLike" + variable = "token.actions.githubusercontent.com:sub" + values = [ + "repo:${var.github_org}/${var.github_repo}:ref:refs/heads/*", + ] + } + + condition { + test = "StringEquals" + variable = "token.actions.githubusercontent.com:aud" + values = ["sts.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "github_actions" { + name = var.role_name + assume_role_policy = data.aws_iam_policy_document.github_actions_assume_role.json +} + +data "aws_iam_policy_document" "deploy_permissions" { + statement { + sid = "ManageS3BucketsPoliciesEncryptionAndLifecycle" + effect = "Allow" + + actions = [ + "s3:CreateBucket", + "s3:DeleteBucket", + "s3:GetBucketAcl", + "s3:GetBucketEncryption", + "s3:GetBucketLifecycleConfiguration", + "s3:GetBucketLocation", + "s3:GetBucketPolicy", + "s3:GetBucketPublicAccessBlock", + "s3:GetBucketTagging", + "s3:GetBucketVersioning", + "s3:ListAllMyBuckets", + "s3:ListBucket", + "s3:PutBucketEncryption", + "s3:PutBucketLifecycleConfiguration", + "s3:PutBucketOwnershipControls", + "s3:PutBucketPolicy", + "s3:PutBucketPublicAccessBlock", + "s3:PutBucketTagging", + "s3:PutBucketVersioning" + ] + + resources = [ + "arn:aws:s3:::*", + "arn:aws:s3:::*/*", + ] + } + + statement { + sid = "ManageSecretsManagerSecretsAndResourcePolicies" + actions = [ + "secretsmanager:CreateSecret", + "secretsmanager:DeleteSecret", + "secretsmanager:DescribeSecret", + "secretsmanager:GetResourcePolicy", + "secretsmanager:ListSecrets", + "secretsmanager:PutResourcePolicy", + "secretsmanager:PutSecretValue", + "secretsmanager:RestoreSecret", + "secretsmanager:TagResource", + "secretsmanager:UpdateSecret", + "secretsmanager:ValidateResourcePolicy" + ] + resources = ["arn:aws:secretsmanager:*:*:secret:*"] + } + + statement { + sid = "ManageKmsKeysAliasesAndPolicies" + actions = [ + "kms:CancelKeyDeletion", + "kms:CreateAlias", + "kms:CreateKey", + "kms:Decrypt", + "kms:DescribeKey", + "kms:DisableKey", + "kms:EnableKey", + "kms:EnableKeyRotation", + "kms:Encrypt", + "kms:GenerateDataKey", + "kms:GenerateDataKeyWithoutPlaintext", + "kms:GetKeyPolicy", + "kms:GetKeyRotationStatus", + "kms:ListAliases", + "kms:ListResourceTags", + "kms:PutKeyPolicy", + "kms:ScheduleKeyDeletion", + "kms:TagResource", + "kms:UpdateAlias" + ] + resources = ["arn:aws:kms:*:*:key/*"] + } + + statement { + sid = "ManageIamRolesAndPolicies" + actions = [ + "iam:AttachRolePolicy", + "iam:CreatePolicy", + "iam:CreatePolicyVersion", + "iam:CreateRole", + "iam:DeletePolicy", + "iam:DeletePolicyVersion", + "iam:DeleteRole", + "iam:DeleteRolePolicy", + "iam:DetachRolePolicy", + "iam:GetOpenIDConnectProvider", + "iam:GetPolicy", + "iam:GetPolicyVersion", + "iam:GetRole", + "iam:GetRolePolicy", + "iam:ListAttachedRolePolicies", + "iam:ListEntitiesForPolicy", + "iam:ListInstanceProfilesForRole", + "iam:ListOpenIDConnectProviders", + "iam:ListPolicies", + "iam:ListPolicyVersions", + "iam:ListRolePolicies", + "iam:ListRoles", + "iam:PassRole", + "iam:PutRolePolicy", + "iam:TagPolicy", + "iam:TagRole", + "iam:UpdateAssumeRolePolicy" + ] + resources = [ + "arn:aws:iam::*:policy/*", + "arn:aws:iam::*:role/*", + ] + } + + statement { + sid = "DynamoDBTableManagement" + effect = "Allow" + + actions = [ + "dynamodb:CreateTable", + "dynamodb:DeleteTable", + "dynamodb:DescribeTable", + "dynamodb:UpdateTable", + "dynamodb:ListTables", + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:UpdateItem", + "dynamodb:DeleteItem", + ] + + resources = ["arn:aws:dynamodb:*:*:table/*"] + } + + statement { + sid = "CloudWatchLogs" + effect = "Allow" + + actions = [ + "logs:CreateLogGroup", + "logs:DeleteLogGroup", + "logs:DescribeLogGroups", + "logs:ListLogGroups", + ] + + resources = ["arn:aws:logs:*:*:log-group:*"] + } +} + +resource "aws_iam_role_policy" "deploy_permissions" { + name = "deploy-permissions" + role = aws_iam_role.github_actions.id + policy = data.aws_iam_policy_document.deploy_permissions.json +} diff --git a/scripts/infra/iam-oidc-pr-role.tf b/scripts/infra/iam-oidc-pr-role.tf new file mode 100644 index 0000000..2db3c64 --- /dev/null +++ b/scripts/infra/iam-oidc-pr-role.tf @@ -0,0 +1,149 @@ +data "aws_iam_openid_connect_provider" "github_actions_pr" { + arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${var.oidc_provider}" +} + +data "aws_iam_policy_document" "github_actions_assume_role_pr" { + statement { + effect = "Allow" + + principals { + type = "Federated" + identifiers = [data.aws_iam_openid_connect_provider.github_actions.arn] + } + + actions = ["sts:AssumeRoleWithWebIdentity"] + + condition { + test = "StringLike" + variable = "token.actions.githubusercontent.com:sub" + values = [ + "repo:${var.github_org}/${var.github_repo}:ref:refs/heads/*", + ] + } + + condition { + test = "StringEquals" + variable = "token.actions.githubusercontent.com:aud" + values = ["sts.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "github_actions_pr" { + name = var.role_name_pr + assume_role_policy = data.aws_iam_policy_document.github_actions_assume_role_pr.json +} + +data "aws_iam_policy_document" "deploy_permissions_pr" { + statement { + sid = "S3BucketManagement" + effect = "Allow" + + actions = [ + "s3:GetBucketVersioning", + "s3:PutBucketVersioning", + "s3:GetBucketEncryption", + "s3:PutBucketEncryption", + "s3:GetBucketPublicAccessBlock", + "s3:PutBucketPublicAccessBlock", + "s3:GetBucketPolicy", + "s3:PutBucketPolicy", + "s3:ListBucket", + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject" + ] + + resources = [ + "arn:aws:s3:::*", + "arn:aws:s3:::*/*", + ] + } + + statement { + sid = "SecretsManagerManagement" + effect = "Allow" + + actions = [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue", + "secretsmanager:ListSecrets", + ] + + resources = ["arn:aws:secretsmanager:*:*:secret:*"] + } + + statement { + sid = "KMSKeyManagement" + effect = "Allow" + + actions = [ + "kms:DescribeKey", + "kms:ListKeys", + "kms:ListAliases", + "kms:GetKeyPolicy", + "kms:Encrypt", + "kms:GenerateDataKey", + "kms:ScheduleKeyDeletion", + ] + + resources = ["arn:aws:kms:*:*:key/*"] + } + + statement { + sid = "IAMRoleManagement" + effect = "Allow" + + actions = [ + "iam:GetRole", + "iam:ListRoles", + "iam:UpdateAssumeRolePolicy", + "iam:GetAssumeRolePolicy", + "iam:TagRole", + "iam:UntagRole", + ] + + resources = ["arn:aws:iam::*:role/*"] + } + + statement { + sid = "IAMPolicyManagement" + effect = "Allow" + + actions = [ + "iam:GetPolicy", + "iam:ListPolicies", + "iam:ListPolicyVersions", + "iam:GetPolicyVersion", + "iam:ListAttachedRolePolicies", + "iam:GetRolePolicy", + "iam:ListRolePolicies", + ] + + resources = [ + "arn:aws:iam::*:policy/*", + "arn:aws:iam::*:role/*", + ] + } + + statement { + sid = "CloudWatchLogs" + effect = "Allow" + + actions = [ + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:GetLogEvents", + "logs:FilterLogEvents", + "logs:ListTagsLogGroup" + ] + + resources = ["arn:aws:logs:*:*:log-group:*"] + } +} + +resource "aws_iam_role_policy" "deploy_permissions_pr" { + name = "deploy-permissions-pr" + role = aws_iam_role.github_actions_pr.id + policy = data.aws_iam_policy_document.deploy_permissions_pr.json +} diff --git a/scripts/infra/outputs.tf b/scripts/infra/outputs.tf new file mode 100644 index 0000000..cb0aa35 --- /dev/null +++ b/scripts/infra/outputs.tf @@ -0,0 +1,12 @@ +output "terraform_state_bucket_name" { + value = aws_s3_bucket.terraform_state_store.bucket +} + + +output "github_actions_role_name" { + value = aws_iam_role.github_actions.name +} + +output "github_actions_pr_role_name" { + value = aws_iam_role.github_actions_pr.name +} diff --git a/scripts/infra/tf_import.sh b/scripts/infra/tf_import.sh new file mode 100755 index 0000000..ccb3531 --- /dev/null +++ b/scripts/infra/tf_import.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +AWS_REGION="eu-west-2" +PROJECT="genomics-order-management" +ENVIRONMENT="prod" + +ROLE_NAME="github-genomics-order-management-oidc-deploy-role" +ROLE_NAME_READONLY="github-genomics-order-management-oidc-pr-role" +BUCKET="${PROJECT}-tfstate-${ENVIRONMENT}" + +terraform init -input=false + +import_if_needed() { + local addr=$1 + local id=$2 + + if terraform state list 2>/dev/null | grep -q "^${addr}$"; then + echo "✔ Already imported: $addr" + return + fi + + echo "→ Importing $addr with ID: $id" + + if ! terraform import "$addr" "$id"; then + echo " FAILED: $addr" + echo " ID used: $id" + exit 1 + fi +} + + +import_if_needed aws_s3_bucket.terraform_state_store "$BUCKET" +import_if_needed aws_s3_bucket_versioning.terraform_state_store "$BUCKET" +import_if_needed aws_s3_bucket_acl.terraform-state-acl "$BUCKET" +import_if_needed aws_s3_bucket_server_side_encryption_configuration.terraform_state_store "$BUCKET" +import_if_needed aws_s3_bucket_public_access_block.terraform_state_store "$BUCKET" +import_if_needed aws_s3_bucket_ownership_controls.terraform_state_ownership "$BUCKET" +import_if_needed aws_iam_role.github_actions "$ROLE_NAME" +import_if_needed aws_iam_role.github_actions_pr "$ROLE_NAME_READONLY" + + +terraform state list + +terraform plan -input=false + +echo "Import completed successfully" + diff --git a/scripts/infra/variables.tf b/scripts/infra/variables.tf index 53f37e5..3803310 100644 --- a/scripts/infra/variables.tf +++ b/scripts/infra/variables.tf @@ -37,7 +37,13 @@ variable "github_branch" { variable "role_name" { description = "Name of the IAM role created for GitHub Actions." type = string - default = "github-genomics-order-management-oidc-role" + default = "github-genomics-order-management-oidc-deploy-role" +} + +variable "role_name_pr" { + description = "Name of the IAM role created for GitHub Actions." + type = string + default = "github-genomics-order-management-oidc-pr-role" } variable "oidc_provider" { diff --git a/scripts/terraform/terraform.lib.sh b/scripts/terraform/terraform.lib.sh index 7793b9b..1afe289 100644 --- a/scripts/terraform/terraform.lib.sh +++ b/scripts/terraform/terraform.lib.sh @@ -3,6 +3,7 @@ # WARNING: Please DO NOT edit this file! It is maintained in the Repository Template (https://github.com/nhs-england-tools/repository-template). Raise a PR instead. set -euo pipefail +set -x # A set of Terraform functions written in Bash. # diff --git a/scripts/terraform/terraform.sh b/scripts/terraform/terraform.sh index 73f37c1..4e074af 100755 --- a/scripts/terraform/terraform.sh +++ b/scripts/terraform/terraform.sh @@ -3,7 +3,7 @@ # WARNING: Please DO NOT edit this file! It is maintained in the Repository Template (https://github.com/nhs-england-tools/repository-template). Raise a PR instead. set -euo pipefail - +set -x # Terraform command wrapper. It will run the command natively if Terraform is # installed, otherwise it will run it in a Docker container. # @@ -35,6 +35,7 @@ function main() { function run-terraform-natively() { # shellcheck disable=SC2086 + echo $PWD terraform $cmd } @@ -45,13 +46,18 @@ function run-terraform-in-docker() { # shellcheck disable=SC1091 source ./scripts/docker/docker.lib.sh - # shellcheck disable=SC2155 local image=$(name=hashicorp/terraform docker-get-image-version-and-pull) # shellcheck disable=SC2086 + docker run --rm --platform linux/amd64 \ --volume "$PWD":/workdir \ --workdir /workdir \ + -e AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-}" \ + -e AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-}" \ + -e AWS_SESSION_TOKEN="${AWS_SESSION_TOKEN:-}" \ + -e AWS_REGION="${AWS_REGION:-}" \ + -e AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-}" \ "$image" \ $cmd } From 27fbe76fa7eee710ab0663937651e0a680089185 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:22:28 +0100 Subject: [PATCH 02/43] testing english language --- infrastructure/environments/int/.gitkeep | 0 infrastructure/environments/int/main.tf | 11 ++ infrastructure/environments/int/variables.tf | 40 +++++++ infrastructure/environments/int/versions.tf | 30 +++++ infrastructure/environments/prod/main.tf | 11 ++ infrastructure/environments/prod/variables.tf | 40 +++++++ infrastructure/environments/prod/versions.tf | 30 +++++ infrastructure/modules/nhs-e-secrets/data.tf | 2 + .../nhs-e-secrets/iam-secretsreader.tf | 69 ++++++++++++ .../modules/nhs-e-secrets/kms-secrets.tf | 104 ++++++++++++++++++ .../modules/nhs-e-secrets/locals.tf | 10 ++ .../modules/nhs-e-secrets/secretsmanager.tf | 49 +++++++++ .../modules/nhs-e-secrets/variables.tf | 42 +++++++ 13 files changed, 438 insertions(+) create mode 100644 infrastructure/environments/int/.gitkeep create mode 100644 infrastructure/environments/int/main.tf create mode 100644 infrastructure/environments/int/variables.tf create mode 100644 infrastructure/environments/int/versions.tf create mode 100644 infrastructure/environments/prod/main.tf create mode 100644 infrastructure/environments/prod/variables.tf create mode 100644 infrastructure/environments/prod/versions.tf create mode 100644 infrastructure/modules/nhs-e-secrets/data.tf create mode 100644 infrastructure/modules/nhs-e-secrets/iam-secretsreader.tf create mode 100644 infrastructure/modules/nhs-e-secrets/kms-secrets.tf create mode 100644 infrastructure/modules/nhs-e-secrets/locals.tf create mode 100644 infrastructure/modules/nhs-e-secrets/secretsmanager.tf create mode 100644 infrastructure/modules/nhs-e-secrets/variables.tf diff --git a/infrastructure/environments/int/.gitkeep b/infrastructure/environments/int/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/infrastructure/environments/int/main.tf b/infrastructure/environments/int/main.tf new file mode 100644 index 0000000..e89ee7d --- /dev/null +++ b/infrastructure/environments/int/main.tf @@ -0,0 +1,11 @@ +# Production environment configuration +module "nhs_e_secrets" { + source = "../../modules/nhs-e-secrets" + + project = var.project + region = var.region + environment = var.environment + intersystems_role_arn = var.intersystems_role_arn + external_id = var.external_id + tags = var.tags +} diff --git a/infrastructure/environments/int/variables.tf b/infrastructure/environments/int/variables.tf new file mode 100644 index 0000000..1d6dfa2 --- /dev/null +++ b/infrastructure/environments/int/variables.tf @@ -0,0 +1,40 @@ +# Shared variables for all environments +variable "project" { + description = "Project name used for tagging." + type = string + default = "genomics-order-management" +} + +variable "region" { + description = "AWS region for infrastructure deployment." + type = string + default = "eu-west-2" +} + +variable "environment" { + description = "Environment name (int, prod)." + type = string +} + +variable "intersystems_role_arn" { + description = "ARN of the InterSystems IAM role that will assume the NHS-E cross-account role." + type = string + default = "" +} + +variable "external_id" { + description = "external_id passed by InterSystems" + type = string +} + +variable "enable_kms_key_rotation" { + description = "Enable KMS key rotation for secrets manager." + type = bool + default = false +} + +variable "tags" { + description = "Tags applied to resources that support tagging." + type = map(string) + default = {} +} diff --git a/infrastructure/environments/int/versions.tf b/infrastructure/environments/int/versions.tf new file mode 100644 index 0000000..31fe2a7 --- /dev/null +++ b/infrastructure/environments/int/versions.tf @@ -0,0 +1,30 @@ +terraform { + required_version = ">= 1.15.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + } + + backend "s3" { + bucket = "genomics-order-management-tfstate-int" + key = "nhs-e/infrastructure/terraform.tfstate" + region = "eu-west-2" + use_lockfile = true + encrypt = true + } +} + +provider "aws" { + region = var.region + + default_tags { + tags = { + Environment = var.environment + Project = var.project + ManagedBy = "Terraform" + } + } +} diff --git a/infrastructure/environments/prod/main.tf b/infrastructure/environments/prod/main.tf new file mode 100644 index 0000000..e89ee7d --- /dev/null +++ b/infrastructure/environments/prod/main.tf @@ -0,0 +1,11 @@ +# Production environment configuration +module "nhs_e_secrets" { + source = "../../modules/nhs-e-secrets" + + project = var.project + region = var.region + environment = var.environment + intersystems_role_arn = var.intersystems_role_arn + external_id = var.external_id + tags = var.tags +} diff --git a/infrastructure/environments/prod/variables.tf b/infrastructure/environments/prod/variables.tf new file mode 100644 index 0000000..1d6dfa2 --- /dev/null +++ b/infrastructure/environments/prod/variables.tf @@ -0,0 +1,40 @@ +# Shared variables for all environments +variable "project" { + description = "Project name used for tagging." + type = string + default = "genomics-order-management" +} + +variable "region" { + description = "AWS region for infrastructure deployment." + type = string + default = "eu-west-2" +} + +variable "environment" { + description = "Environment name (int, prod)." + type = string +} + +variable "intersystems_role_arn" { + description = "ARN of the InterSystems IAM role that will assume the NHS-E cross-account role." + type = string + default = "" +} + +variable "external_id" { + description = "external_id passed by InterSystems" + type = string +} + +variable "enable_kms_key_rotation" { + description = "Enable KMS key rotation for secrets manager." + type = bool + default = false +} + +variable "tags" { + description = "Tags applied to resources that support tagging." + type = map(string) + default = {} +} diff --git a/infrastructure/environments/prod/versions.tf b/infrastructure/environments/prod/versions.tf new file mode 100644 index 0000000..6134ceb --- /dev/null +++ b/infrastructure/environments/prod/versions.tf @@ -0,0 +1,30 @@ +terraform { + required_version = ">= 1.15.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0" + } + } + + backend "s3" { + bucket = "genomics-order-management-tfstate-prod" + key = "nhs-e/infrastructure/terraform.tfstate" + region = "eu-west-2" + use_lockfile = true + encrypt = true + } +} + +provider "aws" { + region = var.region + + default_tags { + tags = { + Environment = var.environment + Project = var.project + ManagedBy = "Terraform" + } + } +} diff --git a/infrastructure/modules/nhs-e-secrets/data.tf b/infrastructure/modules/nhs-e-secrets/data.tf new file mode 100644 index 0000000..3cfa031 --- /dev/null +++ b/infrastructure/modules/nhs-e-secrets/data.tf @@ -0,0 +1,2 @@ +data "aws_caller_identity" "current" {} +data "aws_partition" "current" {} diff --git a/infrastructure/modules/nhs-e-secrets/iam-secretsreader.tf b/infrastructure/modules/nhs-e-secrets/iam-secretsreader.tf new file mode 100644 index 0000000..79ce795 --- /dev/null +++ b/infrastructure/modules/nhs-e-secrets/iam-secretsreader.tf @@ -0,0 +1,69 @@ +data "aws_iam_policy_document" "nhs_e_secretsreader_trust_policy" { + + statement { + sid = "AllowAccountBRolesAssumeSecretReader" + effect = "Allow" + actions = ["sts:AssumeRole"] + + principals { + type = "AWS" + identifiers = [var.intersystems_role_arn] + } + + condition { + test = "StringEquals" + variable = "sts:ExternalId" + values = [var.external_id] + } + } +} + +resource "aws_iam_role" "nhs_e_secretsreader_role" { + name = "${var.project}-${var.environment}-role" + assume_role_policy = data.aws_iam_policy_document.nhs_e_secretsreader_trust_policy.json + # max_session_duration = var.max_session_duration + # permissions_boundary = var.permissions_boundary_arn + + tags = merge(local.common_tags, { Purpose = "Cross-account Secrets Manager reader" }) +} + +data "aws_iam_policy_document" "nhs_e_secretsreader_role_policy_document" { + + statement { + sid = "ReadManagedSecrets" + effect = "Allow" + actions = [ + "secretsmanager:DescribeSecret", + "secretsmanager:GetSecretValue" + ] + resources = [for secret in aws_secretsmanager_secret.nhs_e_secrets : secret.arn] + } + + statement { + sid = "DecryptSecretsManagerKey" + effect = "Allow" + actions = [ + "kms:Decrypt", + "kms:DescribeKey" + ] + resources = [aws_kms_key.secrets_manager.arn] + + condition { + test = "StringEquals" + variable = "kms:ViaService" + values = ["secretsmanager.${var.region}.amazonaws.com"] + } + } +} + +resource "aws_iam_policy" "nhs_e_secretsreader_role_policy" { + name = "${var.project}-${var.environment}-role-policy" + description = "Allows reading Secrets Manager secrets" + policy = data.aws_iam_policy_document.nhs_e_secretsreader_role_policy_document.json + tags = local.common_tags +} + +resource "aws_iam_role_policy_attachment" "nhs_e_secretsreader_role_policy_attachment" { + role = aws_iam_role.nhs_e_secretsreader_role.name + policy_arn = aws_iam_policy.nhs_e_secretsreader_role_policy.arn +} diff --git a/infrastructure/modules/nhs-e-secrets/kms-secrets.tf b/infrastructure/modules/nhs-e-secrets/kms-secrets.tf new file mode 100644 index 0000000..35f7a51 --- /dev/null +++ b/infrastructure/modules/nhs-e-secrets/kms-secrets.tf @@ -0,0 +1,104 @@ + +data "aws_iam_policy_document" "secrets_manager_kms_key" { + statement { + sid = "AllowAccountIamAdministration" + effect = "Allow" + actions = ["kms:*"] + + principals { + type = "AWS" + identifiers = ["arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root"] + } + resources = ["*"] + } + + statement { + sid = "AllowGitHubRoleUseOfSecretsManagerKey" + effect = "Allow" + actions = [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey", + "kms:GenerateDataKeyWithoutPlaintext", + "kms:ReEncryptFrom", + "kms:ReEncryptTo" + ] + + principals { + type = "AWS" + identifiers = [aws_iam_role.nhs_e_secretsreader_role.arn] + } + + resources = ["*"] + } + + statement { + sid = "AllowSecretReaderRoleUseOfSecretsManagerKey" + effect = "Allow" + actions = [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey", + "kms:GenerateDataKeyWithoutPlaintext", + "kms:ReEncryptFrom", + "kms:ReEncryptTo" + ] + + principals { + type = "AWS" + identifiers = [aws_iam_role.nhs_e_secretsreader_role.arn] + } + + resources = ["*"] + + condition { + test = "StringEquals" + variable = "kms:ViaService" + values = ["secretsmanager.${var.region}.amazonaws.com"] + } + } + + statement { + sid = "AllowSecretsManagerServiceUse" + effect = "Allow" + actions = [ + "kms:Decrypt", + "kms:DescribeKey", + "kms:Encrypt", + "kms:GenerateDataKey", + "kms:GenerateDataKeyWithoutPlaintext", + "kms:ReEncryptFrom", + "kms:ReEncryptTo" + ] + + principals { + type = "Service" + identifiers = ["secretsmanager.amazonaws.com"] + } + resources = ["*"] + condition { + test = "StringEquals" + variable = "kms:CallerAccount" + values = [data.aws_caller_identity.current.account_id] + } + condition { + test = "StringEquals" + variable = "kms:ViaService" + values = ["secretsmanager.${var.region}.amazonaws.com"] + } + } +} + +resource "aws_kms_key" "secrets_manager" { + description = "KMS key for Secrets Manager" + enable_key_rotation = true + policy = data.aws_iam_policy_document.secrets_manager_kms_key.json + tags = merge(local.common_tags, { Name = "${var.project}-key" }) +} + +resource "aws_kms_alias" "secrets_manager" { + name = "alias/${var.project}-key" + target_key_id = aws_kms_key.secrets_manager.key_id +} diff --git a/infrastructure/modules/nhs-e-secrets/locals.tf b/infrastructure/modules/nhs-e-secrets/locals.tf new file mode 100644 index 0000000..1ecb25c --- /dev/null +++ b/infrastructure/modules/nhs-e-secrets/locals.tf @@ -0,0 +1,10 @@ +locals { + + common_tags = merge( + { + ManagedBy = "Terraform" + module = "NHS-E Secrets" + }, + var.tags + ) +} diff --git a/infrastructure/modules/nhs-e-secrets/secretsmanager.tf b/infrastructure/modules/nhs-e-secrets/secretsmanager.tf new file mode 100644 index 0000000..be609a7 --- /dev/null +++ b/infrastructure/modules/nhs-e-secrets/secretsmanager.tf @@ -0,0 +1,49 @@ + +resource "aws_secretsmanager_secret" "nhs_e_secrets" { + for_each = var.secrets + name = each.value.secret_key + description = try(each.value.description, null) + kms_key_id = aws_kms_key.secrets_manager.arn + recovery_window_in_days = try(each.value.recovery_window_in_days, 30) + tags = merge(local.common_tags, try(each.value.tags, {})) + lifecycle { + ignore_changes = [tags] + } +} + +resource "aws_secretsmanager_secret_version" "nhs_e_secrets_version" { + for_each = var.secrets + secret_id = aws_secretsmanager_secret.nhs_e_secrets[each.key].id + secret_string = jsonencode({ (each.value.secret_key) = "managed-by-nhs-e" }) + lifecycle { + ignore_changes = [secret_string] + } +} + +data "aws_iam_policy_document" "nhs_e_secrets_access" { + for_each = var.secrets + statement { + sid = "AllowReaderRole" + effect = "Allow" + actions = [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret" + ] + principals { + type = "AWS" + identifiers = [aws_iam_role.nhs_e_secretsreader_role.name] + } + resources = [ + aws_secretsmanager_secret.nhs_e_secrets[each.key].arn + ] + } +} + +resource "aws_secretsmanager_secret_policy" "nhs_e_secretsmanager_policy" { + for_each = data.aws_iam_policy_document.nhs_e_secrets_access + secret_arn = aws_secretsmanager_secret.nhs_e_secrets[each.key].arn + policy = each.value.json + lifecycle { + ignore_changes = all + } +} diff --git a/infrastructure/modules/nhs-e-secrets/variables.tf b/infrastructure/modules/nhs-e-secrets/variables.tf new file mode 100644 index 0000000..6fdde26 --- /dev/null +++ b/infrastructure/modules/nhs-e-secrets/variables.tf @@ -0,0 +1,42 @@ +# Module variables +variable "project" { + description = "Project name" + type = string +} + +variable "region" { + description = "AWS region" + type = string +} + +variable "environment" { + description = "Environment name (int, prod)" + type = string +} + +variable "intersystems_role_arn" { + description = "ARN of the InterSystems IAM role that will assume this NHS-E role" + type = string +} + +variable "external_id" { + description = "external_id passed by InterSystems" + type = string +} + +variable "secrets" { + description = "Secrets Manager secrets to create. NOTE: THIS IS ONLY TO CREATE SECRET PLACEHOLDERS AND NO REAL SECRETS. Secrets are managed by NHS-E service team" + type = map(object({ + description = optional(string) + recovery_window_in_days = optional(number, 30) + secret_key = optional(string) + tags = optional(map(string), {}) + })) + default = {} +} + +variable "tags" { + description = "Tags applied to resources that support tagging." + type = map(string) + default = {} +} From 9673ef336b31cc606e0f9d9915843273ea391ecf Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:24:34 +0100 Subject: [PATCH 03/43] testing english language --- .github/actions/lint-terraform/action.yaml | 7 - .github/workflows/cicd-1-pull-request.yaml | 47 +++++- .github/workflows/cicd-3-deploy.yaml | 19 ++- .github/workflows/infra-deploy.yaml | 177 ++++++++++++++++++++ .github/workflows/infra-validate.yaml | 178 +++++++++++++++++++++ 5 files changed, 417 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/infra-deploy.yaml create mode 100644 .github/workflows/infra-validate.yaml diff --git a/.github/actions/lint-terraform/action.yaml b/.github/actions/lint-terraform/action.yaml index d5dfe35..65f5897 100644 --- a/.github/actions/lint-terraform/action.yaml +++ b/.github/actions/lint-terraform/action.yaml @@ -11,10 +11,3 @@ runs: shell: bash run: | check_only=true scripts/githooks/check-terraform-format.sh - - name: "Validate Terraform" - shell: bash - run: | - stacks=${{ inputs.root-modules }} - for dir in $(find infrastructure/environments -maxdepth 1 -mindepth 1 -type d; echo ${stacks//,/$'\n'}); do - dir=$dir make terraform-validate - done diff --git a/.github/workflows/cicd-1-pull-request.yaml b/.github/workflows/cicd-1-pull-request.yaml index cd068ec..d6032de 100644 --- a/.github/workflows/cicd-1-pull-request.yaml +++ b/.github/workflows/cicd-1-pull-request.yaml @@ -7,13 +7,13 @@ on: branches: - "**" pull_request: - types: [opened, reopened] + types: [opened, reopened, synchronize] jobs: metadata: name: "Set CI/CD metadata" runs-on: ubuntu-latest - timeout-minutes: 1 + timeout-minutes: 5 outputs: build_datetime_london: ${{ steps.variables.outputs.build_datetime_london }} build_datetime: ${{ steps.variables.outputs.build_datetime }} @@ -37,7 +37,7 @@ jobs: echo "build_timestamp=$(date --date=$datetime -u +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT echo "build_epoch=$(date --date=$datetime -u +'%s')" >> $GITHUB_OUTPUT echo "nodejs_version=$(grep "^nodejs" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT - echo "python_version=$(grep "^nodejs" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT + echo "python_version=$(grep "^python" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT echo "terraform_version=$(grep "^terraform" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT echo "version=$(head -n 1 .version 2> /dev/null || echo unknown)" >> $GITHUB_OUTPUT - name: "Check if pull request exists for this branch" @@ -79,6 +79,47 @@ jobs: terraform_version: "${{ needs.metadata.outputs.terraform_version }}" version: "${{ needs.metadata.outputs.version }}" secrets: inherit + build-matrix: + name: Build environment matrix from secrets + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Build matrix JSON + id: set-matrix + env: + AWS_INT_PR_ROLE: ${{ secrets.AWS_INT_PR_ROLE }} + AWS_PROD_PR_ROLE: ${{ secrets.AWS_PROD_PR_ROLE }} + run: | + set -euo pipefail + entries=() + [ -n "${AWS_INT_PR_ROLE:-}" ] && entries+=("{'environment':'int'}") + [ -n "${AWS_PROD_PR_ROLE:-}" ] && entries+=("{'environment':'prod'}") + + if [ "${#entries[@]}" -eq 0 ]; then + echo "matrix=[]" >> $GITHUB_OUTPUT + else + printf -v matrix_json '[%s]' "$(IFS=,; echo "${entries[*]}")" + echo "matrix=$matrix_json" >> $GITHUB_OUTPUT + echo "matrix=$matrix_json" + fi + infra-validate-stage: + name: "Infrastructure validation" + needs: [metadata, commit-stage, build-matrix] + strategy: + matrix: + include: ${{ fromJson(needs.build-matrix.outputs.matrix) }} + uses: ./.github/workflows/infra-validate.yaml + with: + build_datetime: "${{ needs.metadata.outputs.build_datetime }}" + build_timestamp: "${{ needs.metadata.outputs.build_timestamp }}" + build_epoch: "${{ needs.metadata.outputs.build_epoch }}" + nodejs_version: "${{ needs.metadata.outputs.nodejs_version }}" + python_version: "${{ needs.metadata.outputs.python_version }}" + terraform_version: "${{ needs.metadata.outputs.terraform_version }}" + version: "${{ needs.metadata.outputs.version }}" + environment: ${{ matrix.environment }} + secrets: inherit test-stage: # Recommended maximum execution time is 5 minutes name: "Test stage" needs: [metadata, commit-stage] diff --git a/.github/workflows/cicd-3-deploy.yaml b/.github/workflows/cicd-3-deploy.yaml index 2745b38..772870d 100644 --- a/.github/workflows/cicd-3-deploy.yaml +++ b/.github/workflows/cicd-3-deploy.yaml @@ -4,14 +4,22 @@ on: workflow_dispatch: inputs: tag: - description: "This is the tag that is oging to be deployed" + description: "This is the tag that is going to be deployed" required: true default: "latest" + environment: + description: "Select environment" + required: true + type: choice + options: + - int + - prod jobs: metadata: name: "Set CI/CD metadata" runs-on: ubuntu-latest + environment: ${{ inputs.environment }} timeout-minutes: 1 outputs: build_datetime: ${{ steps.variables.outputs.build_datetime }} @@ -57,6 +65,15 @@ jobs: steps: - name: "Checkout code" uses: actions/checkout@v4 + with: + ref: main + infra-deploy: + needs: [deploy] + uses: ./.github/workflows/infra-deploy.yaml + with: + environment: ${{ inputs.environment }} + secrets: inherit + # TODO: More jobs or/and steps here # success: # name: "Success notification" diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml new file mode 100644 index 0000000..4216871 --- /dev/null +++ b/.github/workflows/infra-deploy.yaml @@ -0,0 +1,177 @@ +name: "Infrastructure Deploy" + +# Deployment workflow for infrastructure changes +# Called from cicd-3-deploy after merge - deploys to selected environment + +on: + workflow_call: + inputs: + environment: + required: true + type: string + +permissions: + id-token: write + contents: read + +env: + AWS_DEPLOY_ROLE: ${{ inputs.environment == 'prod' && secrets.AWS_PROD_PR_ROLE || secrets.AWS_INT_PR_ROLE }} + +jobs: + validate-main-branch: + name: "Validate deployment from main branch" + runs-on: ubuntu-latest + steps: + - name: "Check branch" + run: | + if [ "${{ github.ref }}" != "refs/heads/main" ]; then + echo "ERROR: This workflow can only be run from the 'main' branch." >&2 + echo "Current branch: ${{ github.ref }}" >&2 + exit 1 + fi + metadata: + name: "Set CI/CD metadata" + needs: [validate-main-branch] + runs-on: ubuntu-latest + timeout-minutes: 1 + outputs: + build_datetime: ${{ steps.variables.outputs.build_datetime }} + build_timestamp: ${{ steps.variables.outputs.build_timestamp }} + build_epoch: ${{ steps.variables.outputs.build_epoch }} + terraform_version: ${{ steps.variables.outputs.terraform_version }} + version: ${{ steps.variables.outputs.version }} + steps: + - name: "Checkout code" + uses: actions/checkout@v4 + with: + ref: main + + - name: "Set CI/CD variables" + id: variables + run: | + datetime=$(date -u +'%Y-%m-%dT%H:%M:%S%z') + echo "build_datetime=$datetime" >> $GITHUB_OUTPUT + echo "build_timestamp=$(date --date=$datetime -u +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT + echo "build_epoch=$(date --date=$datetime -u +'%s')" >> $GITHUB_OUTPUT + echo "terraform_version=$(grep "^terraform" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT + echo "version=$(head -n 1 .version 2> /dev/null || echo unknown)" >> $GITHUB_OUTPUT + + - name: "List variables" + run: | + export BUILD_DATETIME="${{ steps.variables.outputs.build_datetime }}" + export BUILD_TIMESTAMP="${{ steps.variables.outputs.build_timestamp }}" + export BUILD_EPOCH="${{ steps.variables.outputs.build_epoch }}" + export TERRAFORM_VERSION="${{ steps.variables.outputs.terraform_version }}" + export VERSION="${{ steps.variables.outputs.version }}" + make list-variables + + terraform-validate: + name: "Terraform validate" + needs: [metadata] + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: "Checkout code" + uses: actions/checkout@v4 + with: + ref: main + + - name: "Setup Terraform" + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ needs.metadata.outputs.terraform_version }} + + - name: "Configure AWS credentials" + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.AWS_DEPLOY_ROLE }} + aws-region: eu-west-2 + + - name: "Terraform Init" + run: | + make terraform-init \ + FORCE_USE_DOCKER=true \ + dir=infrastructure/environments/${{ inputs.environment }} + + - name: "Terraform Validate" + run: | + make terraform-validate \ + FORCE_USE_DOCKER=true \ + dir=infrastructure/environments/${{ inputs.environment }} + + terraform-plan: + name: "Terraform plan for ${{ inputs.environment }}" + needs: [terraform-validate] + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: "Setup Terraform" + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ needs.metadata.outputs.terraform_version }} + + - name: "Configure AWS credentials" + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.AWS_DEPLOY_ROLE }} + aws-region: eu-west-2 + + - name: "Terraform Init" + run: | + make terraform-init \ + FORCE_USE_DOCKER=true \ + dir=infrastructure/environments/${{ inputs.environment }} + + - name: "Terraform Plan" + id: plan + run: | + make terraform-plan \ + FORCE_USE_DOCKER=true \ + dir=infrastructure/environments/${{ inputs.environment }} \ + opts="-var-file=../terraform.tfvars -var-file=./terraform.tfvars -no-color -out=tfplan" + + - name: "Save tfplan" + uses: actions/upload-artifact@v4 + with: + name: tfplan-${{ inputs.environment }}-${{ github.run_id }} + path: infrastructure/environments/${{ inputs.environment }}/tfplan + retention-days: 7 + + terraform-apply: + name: "Terraform apply to ${{ inputs.environment }}" + needs: [terraform-plan] + runs-on: ubuntu-latest + environment: + name: ${{ inputs.environment }} + steps: + - name: "Checkout code" + uses: actions/checkout@v4 + with: + ref: main + + - name: "Setup Terraform" + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ needs.metadata.outputs.terraform_version }} + + - name: "Configure AWS credentials" + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.AWS_DEPLOY_ROLE }} + aws-region: eu-west-2 + + - name: "Terraform Init" + run: | + make terraform-init \ + FORCE_USE_DOCKER=true \ + dir=infrastructure/environments/${{ inputs.environment }} + + - name: "Terraform Apply" + run: | + make terraform-apply \ + FORCE_USE_DOCKER=true \ + dir=infrastructure/environments/${{ inputs.environment }} \ + opts="-no-color tfplan" + + + diff --git a/.github/workflows/infra-validate.yaml b/.github/workflows/infra-validate.yaml new file mode 100644 index 0000000..1eb4f2a --- /dev/null +++ b/.github/workflows/infra-validate.yaml @@ -0,0 +1,178 @@ +name: "Infrastructure Validate" + +# Validation workflow for infrastructure changes +# Runs on pull requests to validate Terraform code without deploying + +on: + workflow_call: + inputs: + build_datetime: + description: "Build datetime, set by the CI/CD pipeline workflow" + required: true + type: string + build_timestamp: + description: "Build timestamp, set by the CI/CD pipeline workflow" + required: true + type: string + build_epoch: + description: "Build epoch, set by the CI/CD pipeline workflow" + required: true + type: string + nodejs_version: + description: "Node.js version, set by the CI/CD pipeline workflow" + required: true + type: string + python_version: + description: "Python version, set by the CI/CD pipeline workflow" + required: true + type: string + terraform_version: + description: "Terraform version, set by the CI/CD pipeline workflow" + required: true + type: string + version: + description: "Version of the software, set by the CI/CD pipeline workflow" + required: true + type: string + environment: + description: "environment" + required: true + type: string + +permissions: + id-token: write + contents: read + pull-requests: write + +env: + AWS_PR_ROLE: ${{ inputs.environment == 'prod' && secrets.AWS_PROD_PR_ROLE || secrets.AWS_INT_PR_ROLE }} + +jobs: + metadata: + name: "Set CI/CD metadata" + runs-on: ubuntu-latest + timeout-minutes: 1 + outputs: + build_datetime: ${{ steps.variables.outputs.build_datetime }} + build_timestamp: ${{ steps.variables.outputs.build_timestamp }} + build_epoch: ${{ steps.variables.outputs.build_epoch }} + terraform_version: ${{ steps.variables.outputs.terraform_version }} + version: ${{ steps.variables.outputs.version }} + steps: + - name: "Checkout code" + uses: actions/checkout@v4 + + - name: "Set CI/CD variables" + id: variables + run: | + datetime=$(date -u +'%Y-%m-%dT%H:%M:%S%z') + echo "build_datetime=$datetime" >> $GITHUB_OUTPUT + echo "build_timestamp=$(date --date=$datetime -u +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT + echo "build_epoch=$(date --date=$datetime -u +'%s')" >> $GITHUB_OUTPUT + echo "terraform_version=$(grep "^terraform" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT + echo "version=$(head -n 1 .version 2> /dev/null || echo unknown)" >> $GITHUB_OUTPUT + + - name: "List variables" + run: | + export BUILD_DATETIME="${{ steps.variables.outputs.build_datetime }}" + export BUILD_TIMESTAMP="${{ steps.variables.outputs.build_timestamp }}" + export BUILD_EPOCH="${{ steps.variables.outputs.build_epoch }}" + export TERRAFORM_VERSION="${{ steps.variables.outputs.terraform_version }}" + export VERSION="${{ steps.variables.outputs.version }}" + make list-variables + + terraform-plan: + name: "Terraform plan for ${{ inputs.environment }}" + needs: [metadata] + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + id-token: write + contents: read + pull-requests: write + steps: + - name: "Checkout code" + uses: actions/checkout@v4 + + - name: "Setup Terraform" + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ needs.metadata.outputs.terraform_version }} + + - name: "Validate AWS role ARN secret" + run: | + if [ -z "${AWS_PR_ROLE:-}" ]; then + echo "ERROR: Missing AWS role ARN secret for environment '${{ inputs.environment }}'." >&2 + echo "Set '${{ inputs.environment == 'prod' && 'AWS_PROD_PR_ROLE' || 'AWS_INT_PR_ROLE' }}' in repository secrets." >&2 + exit 1 + fi + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.AWS_PR_ROLE }} + aws-region: eu-west-2 + + - name: "Terraform Init - ${{ inputs.environment }}" + run: | + make terraform-init \ + AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ + AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ + AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} \ + AWS_REGION=${AWS_REGION} \ + AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ + FORCE_USE_DOCKER=true \ + dir=infrastructure/environments/${{ inputs.environment }} + + - name: "Terraform Plan - ${{ inputs.environment }}" + id: plan + run: | + make terraform-plan \ + AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ + AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ + AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} \ + AWS_REGION=${AWS_REGION} \ + AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ + FORCE_USE_DOCKER=true \ + dir=infrastructure/environments/${{ inputs.environment }} \ + opts="-var-file=../terraform.tfvars\ -var-file=./terraform.tfvars\ -no-color\ -out=tfplan" + + - name: "Comment Plan on PR" + uses: actions/github-script@v7 + if: github.event_name == 'pull_request' + with: + script: | + const fs = require('fs'); + const plan = fs.readFileSync('infrastructure/environments/${{ inputs.environment }}/tfplan.txt', 'utf8'); + const output = `#### Terraform Plan for \`${{ inputs.environment }}\` + \`\`\` + ${plan.substring(0, 65536)} + \`\`\` + `; + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }); + + - name: "Upload tfplan artifact" + uses: actions/upload-artifact@v4 + with: + name: tfplan-${{ inputs.environment }} + path: infrastructure/environments/${{ inputs.environment }}/tfplan + + validation-summary: + name: "Validation summary" + needs: [terraform-plan] + runs-on: ubuntu-latest + if: always() + steps: + - name: "Check validation status" + run: | + if [ "${{ needs.terraform-plan.result }}" == "failure" ]; then + echo "Infrastructure validation failed" + exit 1 + else + echo "Infrastructure validation passed" + fi From 8f921a0cbb7fa665722060f04b737b1bb3dbd136 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:27:16 +0100 Subject: [PATCH 04/43] testing english language --- .tool-versions | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.tool-versions b/.tool-versions index 32db55a..75ed4c8 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,6 +1,6 @@ # This file is for you! Please, updated to the versions agreed by your team. -terraform 1.7.0 +terraform 1.15.5 pre-commit 3.6.0 # ============================================================================== @@ -14,7 +14,7 @@ pre-commit 3.6.0 # docker/ghcr.io/make-ops-tools/gocloc latest@sha256:6888e62e9ae693c4ebcfed9f1d86c70fd083868acb8815fe44b561b9a73b5032 # SEE: https://github.com/make-ops-tools/gocloc/pkgs/container/gocloc # docker/ghcr.io/nhs-england-tools/github-runner-image 20230909-321fd1e-rt@sha256:ce4fd6035dc450a50d3cbafb4986d60e77cb49a71ab60a053bb1b9518139a646 # SEE: https://github.com/nhs-england-tools/github-runner-image/pkgs/container/github-runner-image # docker/hadolint/hadolint 2.12.0-alpine@sha256:7dba9a9f1a0350f6d021fb2f6f88900998a4fb0aaf8e4330aa8c38544f04db42 # SEE: https://hub.docker.com/r/hadolint/hadolint/tags -# docker/hashicorp/terraform 1.5.6@sha256:180a7efa983386a27b43657ed610e9deed9e6c3848d54f9ea9b6cb8a5c8c25f5 # SEE: https://hub.docker.com/r/hashicorp/terraform/tags +# docker/hashicorp/terraform 1.15@sha256:15bf5a08b1fb9c9747c8ff01098aeeefb4aec9a6c24eb13e7661bdf9447e4aee # SEE: https://hub.docker.com/r/hashicorp/terraform/tags # docker/jdkato/vale v2.29.7@sha256:5ccfac574231b006284513ac3e4e9f38833989d83f2a68db149932c09de85149 # SEE: https://hub.docker.com/r/jdkato/vale/tags # docker/koalaman/shellcheck latest@sha256:e40388688bae0fcffdddb7e4dea49b900c18933b452add0930654b2dea3e7d5c # SEE: https://hub.docker.com/r/koalaman/shellcheck/tags # docker/mstruebing/editorconfig-checker 2.7.1@sha256:dd3ca9ea50ef4518efe9be018d669ef9cf937f6bb5cfe2ef84ff2a620b5ddc24 # SEE: https://hub.docker.com/r/mstruebing/editorconfig-checker/tags From 4ae72a2900c4def332489a0890b97fcbb0990f01 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:31:12 +0100 Subject: [PATCH 05/43] Adding terraform tfvars --- .../environments/int/terraform.tfvars | 17 +++++++++++++++++ .../environments/prod/terraform.tfvars | 18 ++++++++++++++++++ infrastructure/environments/terraform.tfvars | 5 +++++ 3 files changed, 40 insertions(+) create mode 100644 infrastructure/environments/int/terraform.tfvars create mode 100644 infrastructure/environments/prod/terraform.tfvars create mode 100644 infrastructure/environments/terraform.tfvars diff --git a/infrastructure/environments/int/terraform.tfvars b/infrastructure/environments/int/terraform.tfvars new file mode 100644 index 0000000..c2761a9 --- /dev/null +++ b/infrastructure/environments/int/terraform.tfvars @@ -0,0 +1,17 @@ +# Environment variables for prod +environment = "int" +#NOTE : the below is a dummy external_id used for keeping code ready +external_id = "ext-9f83hf83hf83hf83" + +# InterSystems role ARN - this should be provided via terraform.tfvars override or command line +# Example: intersystems_role_arn = "arn:aws:iam::038462762332:role/InterSystems-GOM-Role" + +tags = { + Environment = "int" + Project = "genomics-order-management" + ManagedBy = "Terraform" + Owner = "John Fraser" + DeliveryLead = "Cairns-Hockey, Beryl" + TechnicalArchitect = "Ravi Natarajan" + BusinessAnalyst = "Harini Nallapothola​" +} diff --git a/infrastructure/environments/prod/terraform.tfvars b/infrastructure/environments/prod/terraform.tfvars new file mode 100644 index 0000000..4b70551 --- /dev/null +++ b/infrastructure/environments/prod/terraform.tfvars @@ -0,0 +1,18 @@ +# Environment variables for prod +environment = "prod" +#NOTE : the below is a dummy external_id used for keeping code ready +external_id = "ext-9f83hf83hf83hf83" + +# InterSystems role ARN - this should be provided via terraform.tfvars override or command line +# Example: intersystems_role_arn = "arn:aws:iam::038462762332:role/InterSystems-GOM-Role" + +tags = { + Environment = "prod" + Project = "genomics-order-management" + ManagedBy = "Terraform" + Owner = "John Fraser" + DeliveryLead = "Cairns-Hockey, Beryl" + TechnicalArchitect = "Ravi Natarajan" + BusinessAnalyst = "Harini Nallapothola​" +} + diff --git a/infrastructure/environments/terraform.tfvars b/infrastructure/environments/terraform.tfvars new file mode 100644 index 0000000..415aad5 --- /dev/null +++ b/infrastructure/environments/terraform.tfvars @@ -0,0 +1,5 @@ +# Shared terraform.tfvars for all environments +project = "genomics-order-management" +region = "eu-west-2" + +# Environment-specific values should be set in each environment's terraform.tfvars From 255862662afd41ebf422ffd486cbc3d1eae55350 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 4 Jun 2026 17:16:14 +0100 Subject: [PATCH 06/43] Adding Secrets Placeholders --- infrastructure/environments/int/main.tf | 1 + .../environments/int/terraform.tfvars | 62 ++++++ infrastructure/environments/int/variables.tf | 11 + infrastructure/environments/prod/main.tf | 1 + .../environments/prod/terraform.tfvars | 61 ++++++ infrastructure/environments/prod/variables.tf | 11 + scripts/infra/dynamodb-terraform.tf | 28 --- scripts/infra/iam-oidc.tf | 190 ------------------ scripts/terraform/terraform.sh | 1 - 9 files changed, 147 insertions(+), 219 deletions(-) delete mode 100644 scripts/infra/dynamodb-terraform.tf delete mode 100644 scripts/infra/iam-oidc.tf diff --git a/infrastructure/environments/int/main.tf b/infrastructure/environments/int/main.tf index e89ee7d..02c480e 100644 --- a/infrastructure/environments/int/main.tf +++ b/infrastructure/environments/int/main.tf @@ -8,4 +8,5 @@ module "nhs_e_secrets" { intersystems_role_arn = var.intersystems_role_arn external_id = var.external_id tags = var.tags + secrets = var.secrets } diff --git a/infrastructure/environments/int/terraform.tfvars b/infrastructure/environments/int/terraform.tfvars index c2761a9..1daa424 100644 --- a/infrastructure/environments/int/terraform.tfvars +++ b/infrastructure/environments/int/terraform.tfvars @@ -15,3 +15,65 @@ tags = { TechnicalArchitect = "Ravi Natarajan" BusinessAnalyst = "Harini Nallapothola​" } + +secrets = { + "DGTS-INT" = { + description = "Digital Genomic Test Service INT" + recovery_window_in_days = 7 + secret_key = "dgts_int" + tags = { + environment = "int" + owner = "NHS-E Service Team" + } + } + + "MNS-INT" = { + description = "Multicast Notification Service INT" + recovery_window_in_days = 7 + secret_key = "mns_int" + tags = { + environment = "int" + owner = "NHS-E Service Team" + } + } + + "PDM-INT" = { + description = "Patient Data Manager INT" + recovery_window_in_days = 7 + secret_key = "pdm_int" + tags = { + environment = "int" + owner = "NHS-E Service Team" + } + } + + "ODS-INT" = { + description = "Order Management Service INT" + recovery_window_in_days = 7 + secret_key = "ods_int" + tags = { + environment = "int" + owner = "NHS-E Service Team" + } + } + + "PDS-INT" = { + description = "Personal Demographic Service INT" + recovery_window_in_days = 7 + secret_key = "pds_int" + tags = { + environment = "int" + owner = "NHS-E Service Team" + } + } + + "GOMS-INT" = { + description = "Genomic Order Management Service INT" + recovery_window_in_days = 7 + secret_key = "goms_int" + tags = { + environment = "int" + owner = "NHS-E Service Team" + } + } +} diff --git a/infrastructure/environments/int/variables.tf b/infrastructure/environments/int/variables.tf index 1d6dfa2..59ef860 100644 --- a/infrastructure/environments/int/variables.tf +++ b/infrastructure/environments/int/variables.tf @@ -38,3 +38,14 @@ variable "tags" { type = map(string) default = {} } + +variable "secrets" { + description = "Secrets Manager secrets to create. NOTE: THIS IS ONLY TO CREATE SECRET PLACEHOLDERS AND NO REAL SECRETS. Secrets are managed by NHS-E service team" + type = map(object({ + description = optional(string) + recovery_window_in_days = optional(number, 30) + secret_key = optional(string) + tags = optional(map(string), {}) + })) + default = {} +} \ No newline at end of file diff --git a/infrastructure/environments/prod/main.tf b/infrastructure/environments/prod/main.tf index e89ee7d..02c480e 100644 --- a/infrastructure/environments/prod/main.tf +++ b/infrastructure/environments/prod/main.tf @@ -8,4 +8,5 @@ module "nhs_e_secrets" { intersystems_role_arn = var.intersystems_role_arn external_id = var.external_id tags = var.tags + secrets = var.secrets } diff --git a/infrastructure/environments/prod/terraform.tfvars b/infrastructure/environments/prod/terraform.tfvars index 4b70551..3920236 100644 --- a/infrastructure/environments/prod/terraform.tfvars +++ b/infrastructure/environments/prod/terraform.tfvars @@ -16,3 +16,64 @@ tags = { BusinessAnalyst = "Harini Nallapothola​" } +secrets = { + "DGTS-INT" = { + description = "Digital Genomic Test Service INT" + recovery_window_in_days = 7 + secret_key = "dgts_int" + tags = { + environment = "int" + owner = "NHS-E Service Team" + } + } + + "MNS-INT" = { + description = "Multicast Notification Service INT" + recovery_window_in_days = 7 + secret_key = "mns_int" + tags = { + environment = "int" + owner = "NHS-E Service Team" + } + } + + "PDM-INT" = { + description = "Patient Data Manager INT" + recovery_window_in_days = 7 + secret_key = "pdm_int" + tags = { + environment = "int" + owner = "NHS-E Service Team" + } + } + + "ODS-INT" = { + description = "Order Management Service INT" + recovery_window_in_days = 7 + secret_key = "ods_int" + tags = { + environment = "int" + owner = "NHS-E Service Team" + } + } + + "PDS-INT" = { + description = "Personal Demographic Service INT" + recovery_window_in_days = 7 + secret_key = "pds_int" + tags = { + environment = "int" + owner = "NHS-E Service Team" + } + } + + "GOMS-INT" = { + description = "Genomic Order Management Service INT" + recovery_window_in_days = 7 + secret_key = "goms_int" + tags = { + environment = "int" + owner = "NHS-E Service Team" + } + } +} diff --git a/infrastructure/environments/prod/variables.tf b/infrastructure/environments/prod/variables.tf index 1d6dfa2..59ef860 100644 --- a/infrastructure/environments/prod/variables.tf +++ b/infrastructure/environments/prod/variables.tf @@ -38,3 +38,14 @@ variable "tags" { type = map(string) default = {} } + +variable "secrets" { + description = "Secrets Manager secrets to create. NOTE: THIS IS ONLY TO CREATE SECRET PLACEHOLDERS AND NO REAL SECRETS. Secrets are managed by NHS-E service team" + type = map(object({ + description = optional(string) + recovery_window_in_days = optional(number, 30) + secret_key = optional(string) + tags = optional(map(string), {}) + })) + default = {} +} \ No newline at end of file diff --git a/scripts/infra/dynamodb-terraform.tf b/scripts/infra/dynamodb-terraform.tf deleted file mode 100644 index a125605..0000000 --- a/scripts/infra/dynamodb-terraform.tf +++ /dev/null @@ -1,28 +0,0 @@ -resource "aws_dynamodb_table" "terraform_state_lock" { - name = "${var.project}-tfstate-lock-${var.environment}" - billing_mode = "PAY_PER_REQUEST" - hash_key = "LockID" - - attribute { - name = "LockID" - type = "S" - } - - server_side_encryption { - enabled = true - } - - point_in_time_recovery { - enabled = true - } - - lifecycle { - prevent_destroy = true - } - - tags = { - project = var.project - Name = "terraform-lock-${var.environment}" - Environment = var.environment - } -} diff --git a/scripts/infra/iam-oidc.tf b/scripts/infra/iam-oidc.tf deleted file mode 100644 index 4f96509..0000000 --- a/scripts/infra/iam-oidc.tf +++ /dev/null @@ -1,190 +0,0 @@ -data "aws_iam_openid_connect_provider" "github_actions" { - arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${var.oidc_provider}" -} - -data "aws_iam_policy_document" "github_actions_assume_role" { - statement { - effect = "Allow" - - principals { - type = "Federated" - identifiers = [data.aws_iam_openid_connect_provider.github_actions.arn] - } - - actions = ["sts:AssumeRoleWithWebIdentity"] - - condition { - test = "StringLike" - variable = "token.actions.githubusercontent.com:sub" - values = [ - "repo:${var.github_org}/${var.github_repo}:ref:refs/heads/${var.github_branch}", - ] - } - - condition { - test = "StringEquals" - variable = "token.actions.githubusercontent.com:aud" - values = ["sts.amazonaws.com"] - } - } -} - -resource "aws_iam_role" "github_actions" { - name = var.role_name - assume_role_policy = data.aws_iam_policy_document.github_actions_assume_role.json -} - -data "aws_iam_policy_document" "deploy_permissions" { - statement { - sid = "S3BucketManagement" - effect = "Allow" - - actions = [ - "s3:CreateBucket", - "s3:DeleteBucket", - "s3:GetBucketVersioning", - "s3:PutBucketVersioning", - "s3:GetBucketEncryption", - "s3:PutBucketEncryption", - "s3:GetBucketPublicAccessBlock", - "s3:PutBucketPublicAccessBlock", - "s3:GetBucketPolicy", - "s3:PutBucketPolicy", - "s3:ListBucket", - "s3:GetObject", - "s3:PutObject", - "s3:DeleteObject", - ] - - resources = [ - "arn:aws:s3:::*", - "arn:aws:s3:::*/*", - ] - } - - statement { - sid = "DynamoDBTableManagement" - effect = "Allow" - - actions = [ - "dynamodb:CreateTable", - "dynamodb:DeleteTable", - "dynamodb:DescribeTable", - "dynamodb:UpdateTable", - "dynamodb:ListTables", - "dynamodb:GetItem", - "dynamodb:PutItem", - "dynamodb:UpdateItem", - "dynamodb:DeleteItem", - ] - - resources = ["arn:aws:dynamodb:*:*:table/*"] - } - - statement { - sid = "SecretsManagerManagement" - effect = "Allow" - - actions = [ - "secretsmanager:CreateSecret", - "secretsmanager:DeleteSecret", - "secretsmanager:DescribeSecret", - "secretsmanager:GetSecretValue", - "secretsmanager:PutSecretValue", - "secretsmanager:UpdateSecret", - "secretsmanager:ListSecrets", - ] - - resources = ["arn:aws:secretsmanager:*:*:secret:*"] - } - - statement { - sid = "KMSKeyManagement" - effect = "Allow" - - actions = [ - "kms:CreateKey", - "kms:DescribeKey", - "kms:ListKeys", - "kms:ListAliases", - "kms:CreateAlias", - "kms:DeleteAlias", - "kms:UpdateAlias", - "kms:GetKeyPolicy", - "kms:PutKeyPolicy", - "kms:Decrypt", - "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ScheduleKeyDeletion", - ] - - resources = ["arn:aws:kms:*:*:key/*"] - } - - statement { - sid = "IAMRoleManagement" - effect = "Allow" - - actions = [ - "iam:CreateRole", - "iam:DeleteRole", - "iam:GetRole", - "iam:ListRoles", - "iam:UpdateAssumeRolePolicy", - "iam:GetAssumeRolePolicy", - "iam:PassRole", - "iam:TagRole", - "iam:UntagRole", - ] - - resources = ["arn:aws:iam::*:role/*"] - } - - statement { - sid = "IAMPolicyManagement" - effect = "Allow" - - actions = [ - "iam:CreatePolicy", - "iam:DeletePolicy", - "iam:GetPolicy", - "iam:ListPolicies", - "iam:CreatePolicyVersion", - "iam:DeletePolicyVersion", - "iam:ListPolicyVersions", - "iam:GetPolicyVersion", - "iam:AttachRolePolicy", - "iam:DetachRolePolicy", - "iam:ListAttachedRolePolicies", - "iam:PutRolePolicy", - "iam:GetRolePolicy", - "iam:DeleteRolePolicy", - "iam:ListRolePolicies", - ] - - resources = [ - "arn:aws:iam::*:policy/*", - "arn:aws:iam::*:role/*", - ] - } - - statement { - sid = "CloudWatchLogs" - effect = "Allow" - - actions = [ - "logs:CreateLogGroup", - "logs:DeleteLogGroup", - "logs:DescribeLogGroups", - "logs:ListLogGroups", - ] - - resources = ["arn:aws:logs:*:*:log-group:*"] - } -} - -resource "aws_iam_role_policy" "deploy_permissions" { - name = "deploy-permissions" - role = aws_iam_role.github_actions.id - policy = data.aws_iam_policy_document.deploy_permissions.json -} diff --git a/scripts/terraform/terraform.sh b/scripts/terraform/terraform.sh index 4e074af..e8d8701 100755 --- a/scripts/terraform/terraform.sh +++ b/scripts/terraform/terraform.sh @@ -3,7 +3,6 @@ # WARNING: Please DO NOT edit this file! It is maintained in the Repository Template (https://github.com/nhs-england-tools/repository-template). Raise a PR instead. set -euo pipefail -set -x # Terraform command wrapper. It will run the command natively if Terraform is # installed, otherwise it will run it in a Docker container. # From d407bfae4744029a3da57ebc5580a083825c824c Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 4 Jun 2026 17:19:27 +0100 Subject: [PATCH 07/43] Fixed file format issues --- infrastructure/environments/int/variables.tf | 3 ++- infrastructure/environments/prod/variables.tf | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/infrastructure/environments/int/variables.tf b/infrastructure/environments/int/variables.tf index 59ef860..ee3d4b1 100644 --- a/infrastructure/environments/int/variables.tf +++ b/infrastructure/environments/int/variables.tf @@ -48,4 +48,5 @@ variable "secrets" { tags = optional(map(string), {}) })) default = {} -} \ No newline at end of file +} + diff --git a/infrastructure/environments/prod/variables.tf b/infrastructure/environments/prod/variables.tf index 59ef860..ee3d4b1 100644 --- a/infrastructure/environments/prod/variables.tf +++ b/infrastructure/environments/prod/variables.tf @@ -48,4 +48,5 @@ variable "secrets" { tags = optional(map(string), {}) })) default = {} -} \ No newline at end of file +} + From fa0ff3527aec123575d475bb15eeb7e5e4503fdb Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 4 Jun 2026 18:41:02 +0100 Subject: [PATCH 08/43] resticted PR role permissions --- scripts/infra/iam-oidc-pr-role.tf | 66 +++++++++++++++++-------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/scripts/infra/iam-oidc-pr-role.tf b/scripts/infra/iam-oidc-pr-role.tf index 2db3c64..7b53e29 100644 --- a/scripts/infra/iam-oidc-pr-role.tf +++ b/scripts/infra/iam-oidc-pr-role.tf @@ -1,7 +1,3 @@ -data "aws_iam_openid_connect_provider" "github_actions_pr" { - arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${var.oidc_provider}" -} - data "aws_iam_policy_document" "github_actions_assume_role_pr" { statement { effect = "Allow" @@ -35,28 +31,45 @@ resource "aws_iam_role" "github_actions_pr" { } data "aws_iam_policy_document" "deploy_permissions_pr" { + + statement { + sid = "ListTerraformStateBucket" + effect = "Allow" + + actions = [ + "s3:ListBucket" + ] + + resources = [ + aws_s3_bucket.terraform_state_store.arn + ] + } + + statement { + sid = "ReadWriteTerraformState" + effect = "Allow" + + actions = [ + "s3:GetObject", + "s3:PutObject" + ] + + resources = [ + "${aws_s3_bucket.terraform_state_store.arn}/*.tfstate" + ] + } + statement { - sid = "S3BucketManagement" + sid = "ReadWriteDeleteTerraformLockFile" effect = "Allow" actions = [ - "s3:GetBucketVersioning", - "s3:PutBucketVersioning", - "s3:GetBucketEncryption", - "s3:PutBucketEncryption", - "s3:GetBucketPublicAccessBlock", - "s3:PutBucketPublicAccessBlock", - "s3:GetBucketPolicy", - "s3:PutBucketPolicy", - "s3:ListBucket", "s3:GetObject", "s3:PutObject", "s3:DeleteObject" ] - resources = [ - "arn:aws:s3:::*", - "arn:aws:s3:::*/*", + "${aws_s3_bucket.terraform_state_store.arn}/*.tfstate.tflock" ] } @@ -83,26 +96,24 @@ data "aws_iam_policy_document" "deploy_permissions_pr" { "kms:ListAliases", "kms:GetKeyPolicy", "kms:Encrypt", - "kms:GenerateDataKey", - "kms:ScheduleKeyDeletion", + "kms:GenerateDataKey" ] resources = ["arn:aws:kms:*:*:key/*"] } statement { - sid = "IAMRoleManagement" + sid = "ReadIAMRoleAndPolicies" effect = "Allow" actions = [ "iam:GetRole", + "iam:GetRolePolicy", "iam:ListRoles", - "iam:UpdateAssumeRolePolicy", - "iam:GetAssumeRolePolicy", - "iam:TagRole", - "iam:UntagRole", + "iam:ListAttachedRolePolicies", + "iam:ListRolePolicies", + "iam:GetAssumeRolePolicy" ] - resources = ["arn:aws:iam::*:role/*"] } @@ -114,10 +125,7 @@ data "aws_iam_policy_document" "deploy_permissions_pr" { "iam:GetPolicy", "iam:ListPolicies", "iam:ListPolicyVersions", - "iam:GetPolicyVersion", - "iam:ListAttachedRolePolicies", - "iam:GetRolePolicy", - "iam:ListRolePolicies", + "iam:GetPolicyVersion" ] resources = [ From 7841becf436a89ec5297034fbf00d117629b2b8c Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Fri, 5 Jun 2026 05:49:48 +0100 Subject: [PATCH 09/43] adding checkout for tf plam --- .github/workflows/infra-deploy.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index 4216871..4bc4537 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -15,7 +15,7 @@ permissions: contents: read env: - AWS_DEPLOY_ROLE: ${{ inputs.environment == 'prod' && secrets.AWS_PROD_PR_ROLE || secrets.AWS_INT_PR_ROLE }} + AWS_DEPLOY_ROLE: ${{ inputs.environment == 'prod' && secrets.AWS_PROD_DEPLOY_ROLE || secrets.AWS_INT_DEPLOY_ROLE }} jobs: validate-main-branch: @@ -105,6 +105,11 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 steps: + - name: "Checkout code" + uses: actions/checkout@v4 + with: + ref: main + - name: "Setup Terraform" uses: hashicorp/setup-terraform@v3 with: From 1392c60a315235aec6c321a5163e739e9fca3dc8 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Mon, 8 Jun 2026 13:38:59 +0100 Subject: [PATCH 10/43] Updated tags --- infrastructure/environments/int/terraform.tfvars | 6 +++--- infrastructure/environments/prod/terraform.tfvars | 6 +++--- infrastructure/modules/nhs-e-secrets/kms-secrets.tf | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/infrastructure/environments/int/terraform.tfvars b/infrastructure/environments/int/terraform.tfvars index 1daa424..cea61d8 100644 --- a/infrastructure/environments/int/terraform.tfvars +++ b/infrastructure/environments/int/terraform.tfvars @@ -4,7 +4,7 @@ environment = "int" external_id = "ext-9f83hf83hf83hf83" # InterSystems role ARN - this should be provided via terraform.tfvars override or command line -# Example: intersystems_role_arn = "arn:aws:iam::038462762332:role/InterSystems-GOM-Role" +intersystems_role_arn = "arn:aws:iam::038462762332:role/InterSystems-GOM-Role" tags = { Environment = "int" @@ -13,7 +13,7 @@ tags = { Owner = "John Fraser" DeliveryLead = "Cairns-Hockey, Beryl" TechnicalArchitect = "Ravi Natarajan" - BusinessAnalyst = "Harini Nallapothola​" + BusinessAnalyst = "Adam Laurent​" } secrets = { @@ -48,7 +48,7 @@ secrets = { } "ODS-INT" = { - description = "Order Management Service INT" + description = "Organization Data Service INT" recovery_window_in_days = 7 secret_key = "ods_int" tags = { diff --git a/infrastructure/environments/prod/terraform.tfvars b/infrastructure/environments/prod/terraform.tfvars index 3920236..512904c 100644 --- a/infrastructure/environments/prod/terraform.tfvars +++ b/infrastructure/environments/prod/terraform.tfvars @@ -4,7 +4,7 @@ environment = "prod" external_id = "ext-9f83hf83hf83hf83" # InterSystems role ARN - this should be provided via terraform.tfvars override or command line -# Example: intersystems_role_arn = "arn:aws:iam::038462762332:role/InterSystems-GOM-Role" +intersystems_role_arn = "arn:aws:iam::038462762345:role/InterSystems-GOM-Role" tags = { Environment = "prod" @@ -13,7 +13,7 @@ tags = { Owner = "John Fraser" DeliveryLead = "Cairns-Hockey, Beryl" TechnicalArchitect = "Ravi Natarajan" - BusinessAnalyst = "Harini Nallapothola​" + BusinessAnalyst = "Adam Laurent​" } secrets = { @@ -48,7 +48,7 @@ secrets = { } "ODS-INT" = { - description = "Order Management Service INT" + description = "Organization Data Service INT" recovery_window_in_days = 7 secret_key = "ods_int" tags = { diff --git a/infrastructure/modules/nhs-e-secrets/kms-secrets.tf b/infrastructure/modules/nhs-e-secrets/kms-secrets.tf index 35f7a51..98511af 100644 --- a/infrastructure/modules/nhs-e-secrets/kms-secrets.tf +++ b/infrastructure/modules/nhs-e-secrets/kms-secrets.tf @@ -99,6 +99,6 @@ resource "aws_kms_key" "secrets_manager" { } resource "aws_kms_alias" "secrets_manager" { - name = "alias/${var.project}-key" + name = "alias/${var.project}-${var.environment}-key" target_key_id = aws_kms_key.secrets_manager.key_id } From 8afb8e9304f105cd3a5a9185361398b1858ff89b Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 10:11:50 +0100 Subject: [PATCH 11/43] Commented out main brnach check, temporarily --- .github/workflows/infra-deploy.yaml | 22 +++++++++---------- .../environments/int/terraform.tfvars | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index 4bc4537..e4340fd 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -18,17 +18,17 @@ env: AWS_DEPLOY_ROLE: ${{ inputs.environment == 'prod' && secrets.AWS_PROD_DEPLOY_ROLE || secrets.AWS_INT_DEPLOY_ROLE }} jobs: - validate-main-branch: - name: "Validate deployment from main branch" - runs-on: ubuntu-latest - steps: - - name: "Check branch" - run: | - if [ "${{ github.ref }}" != "refs/heads/main" ]; then - echo "ERROR: This workflow can only be run from the 'main' branch." >&2 - echo "Current branch: ${{ github.ref }}" >&2 - exit 1 - fi + # validate-main-branch: + # name: "Validate deployment from main branch" + # runs-on: ubuntu-latest + # steps: + # - name: "Check branch" + # run: | + # if [ "${{ github.ref }}" != "refs/heads/main" ]; then + # echo "ERROR: This workflow can only be run from the 'main' branch." >&2 + # echo "Current branch: ${{ github.ref }}" >&2 + # exit 1 + # fi metadata: name: "Set CI/CD metadata" needs: [validate-main-branch] diff --git a/infrastructure/environments/int/terraform.tfvars b/infrastructure/environments/int/terraform.tfvars index cea61d8..394a3af 100644 --- a/infrastructure/environments/int/terraform.tfvars +++ b/infrastructure/environments/int/terraform.tfvars @@ -4,7 +4,7 @@ environment = "int" external_id = "ext-9f83hf83hf83hf83" # InterSystems role ARN - this should be provided via terraform.tfvars override or command line -intersystems_role_arn = "arn:aws:iam::038462762332:role/InterSystems-GOM-Role" +intersystems_role_arn = "arn:aws:iam::038462762342:role/InterSystems-GOM-Role" tags = { Environment = "int" From bfded14cc9155165f44bb07bf15f985b3fb835c0 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 10:13:04 +0100 Subject: [PATCH 12/43] Commented out main brnach check, temporarily --- .github/workflows/infra-deploy.yaml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index e4340fd..a2a33bc 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -18,17 +18,18 @@ env: AWS_DEPLOY_ROLE: ${{ inputs.environment == 'prod' && secrets.AWS_PROD_DEPLOY_ROLE || secrets.AWS_INT_DEPLOY_ROLE }} jobs: - # validate-main-branch: - # name: "Validate deployment from main branch" - # runs-on: ubuntu-latest - # steps: - # - name: "Check branch" - # run: | - # if [ "${{ github.ref }}" != "refs/heads/main" ]; then - # echo "ERROR: This workflow can only be run from the 'main' branch." >&2 - # echo "Current branch: ${{ github.ref }}" >&2 - # exit 1 - # fi + validate-main-branch: + name: "Validate deployment from main branch" + runs-on: ubuntu-latest + steps: + - name: "Check branch" + run: | + # if [ "${{ github.ref }}" != "refs/heads/main" ]; then + # echo "ERROR: This workflow can only be run from the 'main' branch." >&2 + # echo "Current branch: ${{ github.ref }}" >&2 + # exit 1 + # fi + echo "bypassing the checks temporarily for testing" metadata: name: "Set CI/CD metadata" needs: [validate-main-branch] From b8dadab97bc37159468b9c50d20be27f38bd91aa Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 11:36:04 +0100 Subject: [PATCH 13/43] Testing deployment --- .github/workflows/infra-deploy.yaml | 34 ++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index a2a33bc..d1ba9c4 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -91,12 +91,22 @@ jobs: - name: "Terraform Init" run: | make terraform-init \ + AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ + AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ + AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} \ + AWS_REGION=${AWS_REGION} \ + AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ FORCE_USE_DOCKER=true \ dir=infrastructure/environments/${{ inputs.environment }} - name: "Terraform Validate" run: | make terraform-validate \ + AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ + AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ + AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} \ + AWS_REGION=${AWS_REGION} \ + AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ FORCE_USE_DOCKER=true \ dir=infrastructure/environments/${{ inputs.environment }} @@ -125,6 +135,11 @@ jobs: - name: "Terraform Init" run: | make terraform-init \ + AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ + AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ + AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} \ + AWS_REGION=${AWS_REGION} \ + AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ FORCE_USE_DOCKER=true \ dir=infrastructure/environments/${{ inputs.environment }} @@ -132,9 +147,14 @@ jobs: id: plan run: | make terraform-plan \ + AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ + AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ + AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} \ + AWS_REGION=${AWS_REGION} \ + AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ FORCE_USE_DOCKER=true \ dir=infrastructure/environments/${{ inputs.environment }} \ - opts="-var-file=../terraform.tfvars -var-file=./terraform.tfvars -no-color -out=tfplan" + opts="-var-file=../terraform.tfvars\ -var-file=./terraform.tfvars\ -no-color\ -out=tfplan" - name: "Save tfplan" uses: actions/upload-artifact@v4 @@ -169,15 +189,23 @@ jobs: - name: "Terraform Init" run: | make terraform-init \ + AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ + AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ + AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} \ + AWS_REGION=${AWS_REGION} \ + AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ FORCE_USE_DOCKER=true \ dir=infrastructure/environments/${{ inputs.environment }} - name: "Terraform Apply" run: | make terraform-apply \ + AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ + AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ + AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} \ + AWS_REGION=${AWS_REGION} \ + AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ FORCE_USE_DOCKER=true \ dir=infrastructure/environments/${{ inputs.environment }} \ opts="-no-color tfplan" - - From fa2a441bfd51257d6ee2edb33c4074b07ab8d609 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 11:41:15 +0100 Subject: [PATCH 14/43] Testing deployment --- scripts/terraform/terraform.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/terraform/terraform.sh b/scripts/terraform/terraform.sh index e8d8701..4e074af 100755 --- a/scripts/terraform/terraform.sh +++ b/scripts/terraform/terraform.sh @@ -3,6 +3,7 @@ # WARNING: Please DO NOT edit this file! It is maintained in the Repository Template (https://github.com/nhs-england-tools/repository-template). Raise a PR instead. set -euo pipefail +set -x # Terraform command wrapper. It will run the command natively if Terraform is # installed, otherwise it will run it in a Docker container. # From e7eb42b4182938b20cdf4d9b267b7fc14a7b731a Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 11:50:45 +0100 Subject: [PATCH 15/43] Testing deployment --- .github/workflows/infra-deploy.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index d1ba9c4..6d3a7ad 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -74,8 +74,6 @@ jobs: steps: - name: "Checkout code" uses: actions/checkout@v4 - with: - ref: main - name: "Setup Terraform" uses: hashicorp/setup-terraform@v3 From f7e9f462a3445a10900b5caff8ab7ff56560e93b Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 11:54:40 +0100 Subject: [PATCH 16/43] Testing deployment --- .github/workflows/infra-deploy.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index 6d3a7ad..073ee52 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -116,8 +116,6 @@ jobs: steps: - name: "Checkout code" uses: actions/checkout@v4 - with: - ref: main - name: "Setup Terraform" uses: hashicorp/setup-terraform@v3 From 730b19175e1c97d25243dc6611a0377f318dadd2 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 12:20:12 +0100 Subject: [PATCH 17/43] Deployment testing --- .github/workflows/cicd-3-deploy.yaml | 2 -- .github/workflows/infra-deploy.yaml | 4 ---- scripts/infra/iam-oidc-deploy-role.tf | 29 +++++++++++++++++++++++++++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cicd-3-deploy.yaml b/.github/workflows/cicd-3-deploy.yaml index 772870d..9626a1b 100644 --- a/.github/workflows/cicd-3-deploy.yaml +++ b/.github/workflows/cicd-3-deploy.yaml @@ -65,8 +65,6 @@ jobs: steps: - name: "Checkout code" uses: actions/checkout@v4 - with: - ref: main infra-deploy: needs: [deploy] uses: ./.github/workflows/infra-deploy.yaml diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index 073ee52..1916920 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -44,8 +44,6 @@ jobs: steps: - name: "Checkout code" uses: actions/checkout@v4 - with: - ref: main - name: "Set CI/CD variables" id: variables @@ -168,8 +166,6 @@ jobs: steps: - name: "Checkout code" uses: actions/checkout@v4 - with: - ref: main - name: "Setup Terraform" uses: hashicorp/setup-terraform@v3 diff --git a/scripts/infra/iam-oidc-deploy-role.tf b/scripts/infra/iam-oidc-deploy-role.tf index 0d0785f..c5e23fe 100644 --- a/scripts/infra/iam-oidc-deploy-role.tf +++ b/scripts/infra/iam-oidc-deploy-role.tf @@ -67,6 +67,34 @@ data "aws_iam_policy_document" "deploy_permissions" { ] } + statement { + sid = "ReadWriteTerraformState" + effect = "Allow" + + actions = [ + "s3:GetObject", + "s3:PutObject" + ] + + resources = [ + "${aws_s3_bucket.terraform_state_store.arn}/*.tfstate" + ] + } + + statement { + sid = "ReadWriteDeleteTerraformLockFile" + effect = "Allow" + + actions = [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject" + ] + resources = [ + "${aws_s3_bucket.terraform_state_store.arn}/*.tfstate.tflock" + ] + } + statement { sid = "ManageSecretsManagerSecretsAndResourcePolicies" actions = [ @@ -148,6 +176,7 @@ data "aws_iam_policy_document" "deploy_permissions" { ] } + statement { sid = "DynamoDBTableManagement" effect = "Allow" From d21227a1a65ac1ca508adda0441bdb2409e31e20 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 12:30:54 +0100 Subject: [PATCH 18/43] Deployment testing --- .github/workflows/infra-deploy.yaml | 39 +++-------------------------- 1 file changed, 4 insertions(+), 35 deletions(-) diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index 1916920..71443bb 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -64,11 +64,11 @@ jobs: export VERSION="${{ steps.variables.outputs.version }}" make list-variables - terraform-validate: - name: "Terraform validate" - needs: [metadata] + terraform-plan: + name: "Terraform plan for ${{ inputs.environment }}" + needs: [terraform-validate] runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 10 steps: - name: "Checkout code" uses: actions/checkout@v4 @@ -106,37 +106,6 @@ jobs: FORCE_USE_DOCKER=true \ dir=infrastructure/environments/${{ inputs.environment }} - terraform-plan: - name: "Terraform plan for ${{ inputs.environment }}" - needs: [terraform-validate] - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v4 - - - name: "Setup Terraform" - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: ${{ needs.metadata.outputs.terraform_version }} - - - name: "Configure AWS credentials" - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: ${{ env.AWS_DEPLOY_ROLE }} - aws-region: eu-west-2 - - - name: "Terraform Init" - run: | - make terraform-init \ - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ - AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} \ - AWS_REGION=${AWS_REGION} \ - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ - FORCE_USE_DOCKER=true \ - dir=infrastructure/environments/${{ inputs.environment }} - - name: "Terraform Plan" id: plan run: | From 2b984bdaab40c172dab26fe88408b7ce0eeb658d Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 12:32:42 +0100 Subject: [PATCH 19/43] Deployment testing --- .github/workflows/infra-deploy.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index 71443bb..2cc544e 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -66,7 +66,6 @@ jobs: terraform-plan: name: "Terraform plan for ${{ inputs.environment }}" - needs: [terraform-validate] runs-on: ubuntu-latest timeout-minutes: 10 steps: From 0d57df830ac98448da5d7ae4e287db8b9fde8567 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 12:39:54 +0100 Subject: [PATCH 20/43] Deployment testing --- .github/workflows/infra-deploy.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index 2cc544e..fb39009 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -129,8 +129,6 @@ jobs: name: "Terraform apply to ${{ inputs.environment }}" needs: [terraform-plan] runs-on: ubuntu-latest - environment: - name: ${{ inputs.environment }} steps: - name: "Checkout code" uses: actions/checkout@v4 From 8c1fb1d67da0b681730f49555d38dd0efad57d70 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 12:45:39 +0100 Subject: [PATCH 21/43] Deployment testing --- .github/workflows/infra-deploy.yaml | 47 +++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index fb39009..f4fbd1a 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -64,8 +64,51 @@ jobs: export VERSION="${{ steps.variables.outputs.version }}" make list-variables + terraform-validate: + name: "Terraform validate" + needs: [metadata] + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: "Checkout code" + uses: actions/checkout@v4 + + - name: "Setup Terraform" + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ needs.metadata.outputs.terraform_version }} + + - name: "Configure AWS credentials" + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ env.AWS_DEPLOY_ROLE }} + aws-region: eu-west-2 + + - name: "Terraform Init" + run: | + make terraform-init \ + AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ + AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ + AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} \ + AWS_REGION=${AWS_REGION} \ + AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ + FORCE_USE_DOCKER=true \ + dir=infrastructure/environments/${{ inputs.environment }} + + - name: "Terraform Validate" + run: | + make terraform-validate \ + AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ + AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ + AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} \ + AWS_REGION=${AWS_REGION} \ + AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ + FORCE_USE_DOCKER=true \ + dir=infrastructure/environments/${{ inputs.environment }} + terraform-plan: name: "Terraform plan for ${{ inputs.environment }}" + needs: [terraform-validate] runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -104,7 +147,7 @@ jobs: AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ FORCE_USE_DOCKER=true \ dir=infrastructure/environments/${{ inputs.environment }} - + - name: "Terraform Plan" id: plan run: | @@ -165,5 +208,5 @@ jobs: AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ FORCE_USE_DOCKER=true \ dir=infrastructure/environments/${{ inputs.environment }} \ - opts="-no-color tfplan" + opts="-no-color --auto-approve tfplan" From e5af5d7eafb5ddd0d03dd43f5802ee40f08c3c6a Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 12:50:27 +0100 Subject: [PATCH 22/43] Deployment testing --- .github/workflows/infra-deploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index f4fbd1a..5936420 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -208,5 +208,5 @@ jobs: AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ FORCE_USE_DOCKER=true \ dir=infrastructure/environments/${{ inputs.environment }} \ - opts="-no-color --auto-approve tfplan" + opts="-no-color -auto-approve tfplan" From 8bc904da91996cc396eecde7273bb87ff60d06ad Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 12:58:31 +0100 Subject: [PATCH 23/43] Deployment testing --- .github/workflows/infra-deploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index 5936420..dee4dd6 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -208,5 +208,5 @@ jobs: AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ FORCE_USE_DOCKER=true \ dir=infrastructure/environments/${{ inputs.environment }} \ - opts="-no-color -auto-approve tfplan" + opts="-no-color\ -auto-approve\ tfplan" From 68c8aacc917e78ccd6de3cd3c75669eb8a4bfd4e Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 13:26:21 +0100 Subject: [PATCH 24/43] Deployment testing --- .github/workflows/infra-deploy.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index dee4dd6..e8d66da 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -198,6 +198,12 @@ jobs: FORCE_USE_DOCKER=true \ dir=infrastructure/environments/${{ inputs.environment }} + - name: "Download tfplan" + uses: actions/download-artifact@v4 + with: + name: tfplan-${{ inputs.environment }}-${{ github.run_id }} + path: infrastructure/environments/${{ inputs.environment }} + - name: "Terraform Apply" run: | make terraform-apply \ From afed4e3aac39de41eda8df7b6f6b03dd2a5837b8 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 13:36:04 +0100 Subject: [PATCH 25/43] Deployment testing --- infrastructure/environments/int/terraform.tfvars | 2 +- infrastructure/environments/prod/terraform.tfvars | 2 +- infrastructure/modules/nhs-e-secrets/iam-secretsreader.tf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/infrastructure/environments/int/terraform.tfvars b/infrastructure/environments/int/terraform.tfvars index 394a3af..e694934 100644 --- a/infrastructure/environments/int/terraform.tfvars +++ b/infrastructure/environments/int/terraform.tfvars @@ -13,7 +13,7 @@ tags = { Owner = "John Fraser" DeliveryLead = "Cairns-Hockey, Beryl" TechnicalArchitect = "Ravi Natarajan" - BusinessAnalyst = "Adam Laurent​" + BusinessAnalyst = "Adam Laurent" } secrets = { diff --git a/infrastructure/environments/prod/terraform.tfvars b/infrastructure/environments/prod/terraform.tfvars index 512904c..b54e097 100644 --- a/infrastructure/environments/prod/terraform.tfvars +++ b/infrastructure/environments/prod/terraform.tfvars @@ -13,7 +13,7 @@ tags = { Owner = "John Fraser" DeliveryLead = "Cairns-Hockey, Beryl" TechnicalArchitect = "Ravi Natarajan" - BusinessAnalyst = "Adam Laurent​" + BusinessAnalyst = "Adam Laurent" } secrets = { diff --git a/infrastructure/modules/nhs-e-secrets/iam-secretsreader.tf b/infrastructure/modules/nhs-e-secrets/iam-secretsreader.tf index 79ce795..28aa81c 100644 --- a/infrastructure/modules/nhs-e-secrets/iam-secretsreader.tf +++ b/infrastructure/modules/nhs-e-secrets/iam-secretsreader.tf @@ -24,7 +24,7 @@ resource "aws_iam_role" "nhs_e_secretsreader_role" { # max_session_duration = var.max_session_duration # permissions_boundary = var.permissions_boundary_arn - tags = merge(local.common_tags, { Purpose = "Cross-account Secrets Manager reader" }) + tags = merge(local.common_tags, { Purpose = "Crossaccount Secrets retrieval" }) } data "aws_iam_policy_document" "nhs_e_secretsreader_role_policy_document" { From 5dad20637072db37896dfd9660cc8f1a6d205ae6 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 13:44:23 +0100 Subject: [PATCH 26/43] Deployment testing --- infrastructure/environments/int/terraform.tfvars | 2 +- infrastructure/environments/prod/terraform.tfvars | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/environments/int/terraform.tfvars b/infrastructure/environments/int/terraform.tfvars index e694934..ad47383 100644 --- a/infrastructure/environments/int/terraform.tfvars +++ b/infrastructure/environments/int/terraform.tfvars @@ -11,7 +11,7 @@ tags = { Project = "genomics-order-management" ManagedBy = "Terraform" Owner = "John Fraser" - DeliveryLead = "Cairns-Hockey, Beryl" + DeliveryLead = "Cairns-Hockey Beryl" TechnicalArchitect = "Ravi Natarajan" BusinessAnalyst = "Adam Laurent" } diff --git a/infrastructure/environments/prod/terraform.tfvars b/infrastructure/environments/prod/terraform.tfvars index b54e097..835c911 100644 --- a/infrastructure/environments/prod/terraform.tfvars +++ b/infrastructure/environments/prod/terraform.tfvars @@ -11,7 +11,7 @@ tags = { Project = "genomics-order-management" ManagedBy = "Terraform" Owner = "John Fraser" - DeliveryLead = "Cairns-Hockey, Beryl" + DeliveryLead = "Cairns-Hockey Beryl" TechnicalArchitect = "Ravi Natarajan" BusinessAnalyst = "Adam Laurent" } From 20f707ef9c3fd11fb33c68a5afa9e5acf8f3a7c2 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 14:04:45 +0100 Subject: [PATCH 27/43] Deployment testing --- infrastructure/environments/prod/main.tf | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/infrastructure/environments/prod/main.tf b/infrastructure/environments/prod/main.tf index 02c480e..ceb524c 100644 --- a/infrastructure/environments/prod/main.tf +++ b/infrastructure/environments/prod/main.tf @@ -5,7 +5,9 @@ module "nhs_e_secrets" { project = var.project region = var.region environment = var.environment - intersystems_role_arn = var.intersystems_role_arn +# intersystems_role_arn = var.intersystems_role_arn +# the below must be replaced by InterSystems Role, added for testing + intersystems_role_arn = arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" external_id = var.external_id tags = var.tags secrets = var.secrets From c8a1f72c10315e71014da446d1a2d0334045997c Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 14:10:39 +0100 Subject: [PATCH 28/43] Deployment testing --- infrastructure/environments/prod/main.tf | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/infrastructure/environments/prod/main.tf b/infrastructure/environments/prod/main.tf index ceb524c..77ca365 100644 --- a/infrastructure/environments/prod/main.tf +++ b/infrastructure/environments/prod/main.tf @@ -1,13 +1,12 @@ # Production environment configuration +# intersystems_role_arn = var.intersystems_role_arn +# the below must be replaced by InterSystems Role, added for testing module "nhs_e_secrets" { - source = "../../modules/nhs-e-secrets" - + source = "../../modules/nhs-e-secrets" project = var.project region = var.region environment = var.environment -# intersystems_role_arn = var.intersystems_role_arn -# the below must be replaced by InterSystems Role, added for testing - intersystems_role_arn = arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" + intersystems_role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root" external_id = var.external_id tags = var.tags secrets = var.secrets From 3858361f9c591666ffe146ccab9e69ab2ca5cc80 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 14:18:42 +0100 Subject: [PATCH 29/43] Deployment testing --- infrastructure/environments/prod/main.tf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infrastructure/environments/prod/main.tf b/infrastructure/environments/prod/main.tf index 77ca365..fad76c1 100644 --- a/infrastructure/environments/prod/main.tf +++ b/infrastructure/environments/prod/main.tf @@ -1,6 +1,8 @@ # Production environment configuration # intersystems_role_arn = var.intersystems_role_arn # the below must be replaced by InterSystems Role, added for testing +data "aws_caller_identity" "current" {} + module "nhs_e_secrets" { source = "../../modules/nhs-e-secrets" project = var.project From dec98c80608a9c2f9f13fa434d4b7990ab981b68 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:12:16 +0100 Subject: [PATCH 30/43] Deployment testing --- .../modules/nhs-e-secrets/secretsmanager.tf | 2 +- scripts/infra/iam-oidc-deploy-role.tf | 32 +++++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/infrastructure/modules/nhs-e-secrets/secretsmanager.tf b/infrastructure/modules/nhs-e-secrets/secretsmanager.tf index be609a7..74f62e2 100644 --- a/infrastructure/modules/nhs-e-secrets/secretsmanager.tf +++ b/infrastructure/modules/nhs-e-secrets/secretsmanager.tf @@ -31,7 +31,7 @@ data "aws_iam_policy_document" "nhs_e_secrets_access" { ] principals { type = "AWS" - identifiers = [aws_iam_role.nhs_e_secretsreader_role.name] + identifiers = [aws_iam_role.nhs_e_secretsreader_role.arn] } resources = [ aws_secretsmanager_secret.nhs_e_secrets[each.key].arn diff --git a/scripts/infra/iam-oidc-deploy-role.tf b/scripts/infra/iam-oidc-deploy-role.tf index c5e23fe..8ce3d83 100644 --- a/scripts/infra/iam-oidc-deploy-role.tf +++ b/scripts/infra/iam-oidc-deploy-role.tf @@ -108,6 +108,10 @@ data "aws_iam_policy_document" "deploy_permissions" { "secretsmanager:RestoreSecret", "secretsmanager:TagResource", "secretsmanager:UpdateSecret", + "secretsmanager:GetSecretValue", + "secretsmanager:ListSecretVersionIds", + "secretsmanager:UpdateSecretVersionStage", + "secretsmanager:UntagResource", "secretsmanager:ValidateResourcePolicy" ] resources = ["arn:aws:secretsmanager:*:*:secret:*"] @@ -118,7 +122,6 @@ data "aws_iam_policy_document" "deploy_permissions" { actions = [ "kms:CancelKeyDeletion", "kms:CreateAlias", - "kms:CreateKey", "kms:Decrypt", "kms:DescribeKey", "kms:DisableKey", @@ -129,14 +132,31 @@ data "aws_iam_policy_document" "deploy_permissions" { "kms:GenerateDataKeyWithoutPlaintext", "kms:GetKeyPolicy", "kms:GetKeyRotationStatus", - "kms:ListAliases", "kms:ListResourceTags", "kms:PutKeyPolicy", "kms:ScheduleKeyDeletion", "kms:TagResource", - "kms:UpdateAlias" + "kms:UntagResource", + "kms:UpdateAlias", + "kms:DeleteAlias", + "kms:CreateGrant", + "kms:ListGrants", + "kms:RevokeGrant" + ] + resources = [ + "arn:aws:kms:*:*:key/*", + "arn:aws:kms:*:*:alias/*" + ] + } + + statement { + sid = "KmsGlobalPermissions" + actions = [ + "kms:CreateKey", + "kms:ListKeys", + "kms:ListAliases" ] - resources = ["arn:aws:kms:*:*:key/*"] + resources = ["*"] } statement { @@ -211,8 +231,8 @@ data "aws_iam_policy_document" "deploy_permissions" { } } -resource "aws_iam_role_policy" "deploy_permissions" { - name = "deploy-permissions" +resource "aws_iam_role_policy" "deploy_permissions_policy" { + name = "infra-deploy-permissions" role = aws_iam_role.github_actions.id policy = data.aws_iam_policy_document.deploy_permissions.json } From c1b4eff1eff2b49093cb95c7c92e503a529923e0 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Tue, 9 Jun 2026 16:30:10 +0100 Subject: [PATCH 31/43] applied secrets naming convention --- infrastructure/environments/prod/terraform.tfvars | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/infrastructure/environments/prod/terraform.tfvars b/infrastructure/environments/prod/terraform.tfvars index 835c911..5442a51 100644 --- a/infrastructure/environments/prod/terraform.tfvars +++ b/infrastructure/environments/prod/terraform.tfvars @@ -20,7 +20,7 @@ secrets = { "DGTS-INT" = { description = "Digital Genomic Test Service INT" recovery_window_in_days = 7 - secret_key = "dgts_int" + secret_key = "APIKEY-GOMS-INT_DGTS" tags = { environment = "int" owner = "NHS-E Service Team" @@ -30,7 +30,7 @@ secrets = { "MNS-INT" = { description = "Multicast Notification Service INT" recovery_window_in_days = 7 - secret_key = "mns_int" + secret_key = "APIKEY-GOMS-INT-MNS" tags = { environment = "int" owner = "NHS-E Service Team" @@ -40,7 +40,7 @@ secrets = { "PDM-INT" = { description = "Patient Data Manager INT" recovery_window_in_days = 7 - secret_key = "pdm_int" + secret_key = "APIKEY-GOMS-INT-PDM" tags = { environment = "int" owner = "NHS-E Service Team" @@ -50,7 +50,7 @@ secrets = { "ODS-INT" = { description = "Organization Data Service INT" recovery_window_in_days = 7 - secret_key = "ods_int" + secret_key = "APIKEY-GOMS-INT-ODS" tags = { environment = "int" owner = "NHS-E Service Team" @@ -60,7 +60,7 @@ secrets = { "PDS-INT" = { description = "Personal Demographic Service INT" recovery_window_in_days = 7 - secret_key = "pds_int" + secret_key = "APIKEY-GOMS-INT-PDS" tags = { environment = "int" owner = "NHS-E Service Team" @@ -70,7 +70,7 @@ secrets = { "GOMS-INT" = { description = "Genomic Order Management Service INT" recovery_window_in_days = 7 - secret_key = "goms_int" + secret_key = "APIKEY-GOMS-INT-GOMS" tags = { environment = "int" owner = "NHS-E Service Team" From 92670cbc6079cb0fc595923bd6a7c1cce9b2ae7d Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 11 Jun 2026 08:50:18 +0100 Subject: [PATCH 32/43] Tidying up and introducing deployment controls --- .github/workflows/infra-deploy.yaml | 18 ++- infrastructure/README.md | 43 ++++++ infrastructure/environments/int/main.tf | 3 +- .../environments/int/terraform.tfvars | 12 +- scripts/infra/README.md | 125 ++++++++++++++---- scripts/infra/iam-oidc-deploy-role.tf | 60 ++++++++- scripts/infra/iam-oidc-pr-role.tf | 57 +++++++- 7 files changed, 274 insertions(+), 44 deletions(-) create mode 100644 infrastructure/README.md diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index e8d66da..071074d 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -22,13 +22,13 @@ jobs: name: "Validate deployment from main branch" runs-on: ubuntu-latest steps: - - name: "Check branch" + - name: "Check branch" run: | - # if [ "${{ github.ref }}" != "refs/heads/main" ]; then - # echo "ERROR: This workflow can only be run from the 'main' branch." >&2 - # echo "Current branch: ${{ github.ref }}" >&2 - # exit 1 - # fi + if [ "${{ github.ref }}" != "refs/heads/main" ]; then + echo "ERROR: This workflow can only be run from the 'main' branch." >&2 + echo "Current branch: ${{ github.ref }}" >&2 + exit 1 + fi echo "bypassing the checks temporarily for testing" metadata: name: "Set CI/CD metadata" @@ -114,6 +114,8 @@ jobs: steps: - name: "Checkout code" uses: actions/checkout@v4 + with: + ref: main - name: "Setup Terraform" uses: hashicorp/setup-terraform@v3 @@ -147,7 +149,7 @@ jobs: AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION} \ FORCE_USE_DOCKER=true \ dir=infrastructure/environments/${{ inputs.environment }} - + - name: "Terraform Plan" id: plan run: | @@ -175,6 +177,8 @@ jobs: steps: - name: "Checkout code" uses: actions/checkout@v4 + with: + ref: main - name: "Setup Terraform" uses: hashicorp/setup-terraform@v3 diff --git a/infrastructure/README.md b/infrastructure/README.md new file mode 100644 index 0000000..85aa773 --- /dev/null +++ b/infrastructure/README.md @@ -0,0 +1,43 @@ +# Infrastructure + +This folder contains the Terraform-based infrastructure assets for the genomic-order-management-service-api project. + +## Contents + +- `environments/` – environment-specific Terraform configurations for `int` and `prod`. + - Shared variables are defined in `environments/terraform.tfvars`. + - Each environment folder contains its own `main.tf` entry point. +- `modules/` – reusable Terraform modules. + - `nhs-e-secrets/` provisions AWS Secrets Manager resources and related IAM/KMS configuration. +- `images/` – placeholder area for infrastructure-related images or diagrams. + +## Current setup + +The current Terraform configuration uses the `nhs-e-secrets` module to create environment-specific secret resources and related access controls. + +### What the `nhs-e-secrets` module deploys + +The `infrastructure/modules/nhs-e-secrets` module provisions: + +- one or more AWS Secrets Manager secrets for the selected environment +- placeholder secret values (managed by the NHS-E service team, not real application secrets) +- an AWS KMS key and alias for encrypting those secrets +- an IAM role and policy that allow an external InterSystems role to read the secrets using the KMS key + +This module is intended to create the secret placeholder infrastructure and the required cross-account access path, rather than manage live secret contents. + +## Typical workflow + +Terraform for this folder is not run manually in the normal development flow. + +Instead, GitHub Actions handles the infrastructure lifecycle as follows: + +- Pull request and merge validation runs through `.github/workflows/infra-validate.yaml`, which is invoked from `.github/workflows/cicd-1-pull-request.yaml` for the configured environments. This validates the Terraform changes but does not deploy them. +- Deployment runs through `.github/workflows/infra-deploy.yaml`, which is invoked from `.github/workflows/cicd-3-deploy.yaml` when the deploy workflow is started manually. This is the path that performs the Terraform init, plan, and apply steps for the selected environment under `infrastructure/environments/`. + +In other words, a merged PR triggers validation, while actual infrastructure deployment is manual through the deploy workflow. + +## Notes + +- Environment variables and shared defaults are defined in `infrastructure/environments/terraform.tfvars`. +- The `prod` configuration currently includes a placeholder IAM role value for testing purposes. diff --git a/infrastructure/environments/int/main.tf b/infrastructure/environments/int/main.tf index 02c480e..e652350 100644 --- a/infrastructure/environments/int/main.tf +++ b/infrastructure/environments/int/main.tf @@ -1,7 +1,6 @@ # Production environment configuration module "nhs_e_secrets" { - source = "../../modules/nhs-e-secrets" - + source = "../../modules/nhs-e-secrets" project = var.project region = var.region environment = var.environment diff --git a/infrastructure/environments/int/terraform.tfvars b/infrastructure/environments/int/terraform.tfvars index ad47383..d18246e 100644 --- a/infrastructure/environments/int/terraform.tfvars +++ b/infrastructure/environments/int/terraform.tfvars @@ -20,7 +20,7 @@ secrets = { "DGTS-INT" = { description = "Digital Genomic Test Service INT" recovery_window_in_days = 7 - secret_key = "dgts_int" + secret_key = "APIKEY-GOMS-INT_DGTS" tags = { environment = "int" owner = "NHS-E Service Team" @@ -30,7 +30,7 @@ secrets = { "MNS-INT" = { description = "Multicast Notification Service INT" recovery_window_in_days = 7 - secret_key = "mns_int" + secret_key = "APIKEY-GOMS-INT-MNS" tags = { environment = "int" owner = "NHS-E Service Team" @@ -40,7 +40,7 @@ secrets = { "PDM-INT" = { description = "Patient Data Manager INT" recovery_window_in_days = 7 - secret_key = "pdm_int" + secret_key = "APIKEY-GOMS-INT-PDM" tags = { environment = "int" owner = "NHS-E Service Team" @@ -50,7 +50,7 @@ secrets = { "ODS-INT" = { description = "Organization Data Service INT" recovery_window_in_days = 7 - secret_key = "ods_int" + secret_key = "APIKEY-GOMS-INT-ODS" tags = { environment = "int" owner = "NHS-E Service Team" @@ -60,7 +60,7 @@ secrets = { "PDS-INT" = { description = "Personal Demographic Service INT" recovery_window_in_days = 7 - secret_key = "pds_int" + secret_key = "APIKEY-GOMS-INT-PDS" tags = { environment = "int" owner = "NHS-E Service Team" @@ -70,7 +70,7 @@ secrets = { "GOMS-INT" = { description = "Genomic Order Management Service INT" recovery_window_in_days = 7 - secret_key = "goms_int" + secret_key = "APIKEY-GOMS-INT-GOMS" tags = { environment = "int" owner = "NHS-E Service Team" diff --git a/scripts/infra/README.md b/scripts/infra/README.md index 1a99e81..c05bba2 100644 --- a/scripts/infra/README.md +++ b/scripts/infra/README.md @@ -1,47 +1,126 @@ -## scripts/infra README +# Infrastructure Bootstrap (`scripts/infra`) -### Purpose +## Overview -The `scripts/infra` directory contains **one-time bootstrap Terraform configuration** for setting up the foundational AWS infrastructure required to manage the `genomic-order-management-service-api` application infrastructure using Terraform. +The scripts/infra directory contains the one‑time bootstrap Terraform configuration required to provision the foundational AWS infrastructure used by the genomic-order-management-service-api Terraform deployments. +This bootstrap layer creates: +- The Terraform remote state backend (S3 + versioning + encryption + access controls) +- The GitHub Actions OIDC IAM roles used for: + - Production deployments (full‑privilege deploy role) + - Pull request / read‑only operations (restricted role) +- The OIDC trust relationships that allow GitHub Actions to authenticate without long‑lived AWS credentials +This directory is only used during initial setup or importing existing resources into Terraform state. --- -### What This Sets Up +## What This Bootstrap Creates -This directory creates the infrastructure **backend** and **deployment prerequisites**: +### Terraform State Backend (S3) -- **Terraform State Backend (S3)** - - S3 bucket for storing Terraform state files (`s3-terraform.tf`) - - Enables safe, concurrent Terraform operations +Terraform state is stored in a dedicated S3 bucket: genomics-order-management-tfstate-prod -- **GitHub Actions OIDC Integration** - - AWS IAM role for GitHub Actions (`iam-oidc.tf`) - - Trust policy allowing GitHub Actions workflows to assume the role - - Granular permissions for Terraform deployments - - No long-lived credentials stored in the pipeline +The bucket is configured with: -- **OIDC Identity Provider** - - Existing AWS OIDC provider for GitHub Actions (referenced via data source) - - Used by the IAM role for secure authentication +- Versioning enabled +- Server‑side encryption (AES‑256) +- Public access fully blocked +- Bucket ownership controls +- `prevent_destroy` lifecycle rule + +Defined in: + +- `s3-terraform.tf` +- `variables.tf` --- -### Usage +### GitHub Actions OIDC IAM Roles + +Two IAM roles are created to support CI/CD workflows. + +--- + +#### 1. Deployment Role +**`github-genomics-order-management-oidc-deploy-role`** + +Used by GitHub Actions for ***main‑branch deployments. +Capabilities include: +- Managing S3 buckets and Terraform state +- Managing Secrets Manager secrets +- Managing KMS keys and aliases +- Managing IAM roles and policies +- Managing DynamoDB tables +- Managing CloudWatch log groups +The trust policy restricts role assumption to: +- The configured GitHub organisation and repository +- The main branch only +- GitHub’s OIDC provider (token.actions.githubusercontent.com) +(Additional branch enforcement is implemented in the GitHub Actions workflow.) + +--- + +#### 2. Pull Request / Read‑Only Role +**`github-genomics-order-management-oidc-pr-role`** + +Used for PR workflows that require: + +- Reading Terraform state +- Reading Secrets Manager +- Reading IAM roles/policies +- Reading CloudWatch logs + +This role **cannot modify infrastructure**. + +--- +OIDC Identity Provider +Both IAM roles reference the existing AWS OIDC provider for GitHub Actions: +data "aws_iam_openid_connect_provider" "github_actions" { ... } +This enables GitHub Actions to authenticate securely without long‑lived AWS credentials. + +--- +Example: `genomics-order-management-tfstate-prod` + +The bucket is configured with: + +- Versioning enabled +- Server‑side encryption (AES‑256) +- Public access fully blocked +- Bucket ownership controls +- `prevent_destroy` lifecycle rule + +Defined in: + +- `s3-terraform.tf` +- `variables.tf` -#### Initial Setup (One-Time) -```bash -# Navigate to scripts/infra +--- + +### Usage +***one time Initial Setup +``` cd scripts/infra -# Initialize Terraform with local backend terraform init -# Review the plan terraform plan \ -var github_org=NHSDigital \ -var github_repo=genomic-order-management-service-api -# Apply the infrastructure terraform apply +``` + +***Importing Existing Resources +If the S3 bucket or IAM roles already exist (e.g., created manually or by a previous bootstrap), use the provided import script: +cd scripts/infra +./tf_import.sh + +--- +Summary +This directory provides the foundational AWS infrastructure required for secure, GitHub‑based Terraform deployments: +- S3 backend for Terraform state +- Two IAM roles (deploy + PR) with OIDC trust +- Import script for existing resources +- Fully parameterised Terraform configuration +It is only used during bootstrap or resource import, not during normal application deployments. diff --git a/scripts/infra/iam-oidc-deploy-role.tf b/scripts/infra/iam-oidc-deploy-role.tf index 8ce3d83..ec0982e 100644 --- a/scripts/infra/iam-oidc-deploy-role.tf +++ b/scripts/infra/iam-oidc-deploy-role.tf @@ -1,26 +1,39 @@ +############################################################### +# GitHub Actions OIDC Provider (existing AWS provider) +############################################################### data "aws_iam_openid_connect_provider" "github_actions" { + # Reference the AWS OIDC provider for GitHub Actions arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${var.oidc_provider}" } +############################################################### +# IAM Trust Policy — GitHub Actions (Main Branch Only) +############################################################### data "aws_iam_policy_document" "github_actions_assume_role" { statement { effect = "Allow" principals { - type = "Federated" + type = "Federated" + # Allow authentication only via GitHub's OIDC provider identifiers = [data.aws_iam_openid_connect_provider.github_actions.arn] } + # Required for GitHub OIDC → AWS IAM role assumption actions = ["sts:AssumeRoleWithWebIdentity"] + # Enforce that ONLY the main branch can assume this role + # GitHub OIDC token 'sub' claim looks like: + # repo:/:ref:refs/heads/main condition { test = "StringLike" variable = "token.actions.githubusercontent.com:sub" values = [ - "repo:${var.github_org}/${var.github_repo}:ref:refs/heads/*", + "repo:${var.github_org}/${var.github_repo}:ref:refs/heads/main", ] } + # Ensure the token is intended for AWS STS (mandatory for GitHub OIDC) condition { test = "StringEquals" variable = "token.actions.githubusercontent.com:aud" @@ -29,12 +42,24 @@ data "aws_iam_policy_document" "github_actions_assume_role" { } } +############################################################### +# IAM Role — Deployment Role (Main Branch Only) +############################################################### resource "aws_iam_role" "github_actions" { - name = var.role_name + name = var.role_name + # Attach the trust policy above assume_role_policy = data.aws_iam_policy_document.github_actions_assume_role.json } +############################################################### +# IAM Permissions — Deployment Role (Full Infra Access) +############################################################### data "aws_iam_policy_document" "deploy_permissions" { + + ############################################################### + # S3 Bucket + Lifecycle + Encryption Management + # Required for Terraform-managed infrastructure buckets + ############################################################### statement { sid = "ManageS3BucketsPoliciesEncryptionAndLifecycle" effect = "Allow" @@ -61,12 +86,16 @@ data "aws_iam_policy_document" "deploy_permissions" { "s3:PutBucketVersioning" ] + # Broad S3 access — required for infra provisioning resources = [ "arn:aws:s3:::*", "arn:aws:s3:::*/*", ] } + ############################################################### + # Terraform State (.tfstate) Read/Write + ############################################################### statement { sid = "ReadWriteTerraformState" effect = "Allow" @@ -81,6 +110,9 @@ data "aws_iam_policy_document" "deploy_permissions" { ] } + ############################################################### + # Terraform Lock File Management + ############################################################### statement { sid = "ReadWriteDeleteTerraformLockFile" effect = "Allow" @@ -95,6 +127,9 @@ data "aws_iam_policy_document" "deploy_permissions" { ] } + ############################################################### + # Secrets Manager — Full Secret Lifecycle + ############################################################### statement { sid = "ManageSecretsManagerSecretsAndResourcePolicies" actions = [ @@ -117,6 +152,9 @@ data "aws_iam_policy_document" "deploy_permissions" { resources = ["arn:aws:secretsmanager:*:*:secret:*"] } + ############################################################### + # KMS — Key + Alias + Grant Management + ############################################################### statement { sid = "ManageKmsKeysAliasesAndPolicies" actions = [ @@ -149,6 +187,9 @@ data "aws_iam_policy_document" "deploy_permissions" { ] } + ############################################################### + # KMS Global Permissions (Key Creation) + ############################################################### statement { sid = "KmsGlobalPermissions" actions = [ @@ -159,6 +200,9 @@ data "aws_iam_policy_document" "deploy_permissions" { resources = ["*"] } + ############################################################### + # IAM Role + Policy Management + ############################################################### statement { sid = "ManageIamRolesAndPolicies" actions = [ @@ -196,7 +240,9 @@ data "aws_iam_policy_document" "deploy_permissions" { ] } - + ############################################################### + # DynamoDB — Table + Item Management + ############################################################### statement { sid = "DynamoDBTableManagement" effect = "Allow" @@ -216,6 +262,9 @@ data "aws_iam_policy_document" "deploy_permissions" { resources = ["arn:aws:dynamodb:*:*:table/*"] } + ############################################################### + # CloudWatch Logs — Log Group Management + ############################################################### statement { sid = "CloudWatchLogs" effect = "Allow" @@ -231,6 +280,9 @@ data "aws_iam_policy_document" "deploy_permissions" { } } +############################################################### +# Attach Permissions to Deployment Role +############################################################### resource "aws_iam_role_policy" "deploy_permissions_policy" { name = "infra-deploy-permissions" role = aws_iam_role.github_actions.id diff --git a/scripts/infra/iam-oidc-pr-role.tf b/scripts/infra/iam-oidc-pr-role.tf index 7b53e29..a6189db 100644 --- a/scripts/infra/iam-oidc-pr-role.tf +++ b/scripts/infra/iam-oidc-pr-role.tf @@ -1,22 +1,33 @@ +############################################### +# GitHub Actions OIDC Trust Policy (PR Role) +############################################### data "aws_iam_policy_document" "github_actions_assume_role_pr" { statement { effect = "Allow" principals { - type = "Federated" + type = "Federated" + # Trust the GitHub Actions OIDC provider for authentication identifiers = [data.aws_iam_openid_connect_provider.github_actions.arn] } + # Required for GitHub OIDC → AWS IAM role assumption actions = ["sts:AssumeRoleWithWebIdentity"] + # Restrict which GitHub workflow identities may assume this role condition { test = "StringLike" variable = "token.actions.githubusercontent.com:sub" + + # Allow any branch (refs/heads/*) but only for this repo + # Example: repo:NHSDigital/genomic-order-management-service-api:ref:refs/heads/feature/foo values = [ "repo:${var.github_org}/${var.github_repo}:ref:refs/heads/*", ] } + # GitHub OIDC tokens always set audience = sts.amazonaws.com + # This ensures the token is intended for AWS STS and not another service condition { test = "StringEquals" variable = "token.actions.githubusercontent.com:aud" @@ -25,13 +36,24 @@ data "aws_iam_policy_document" "github_actions_assume_role_pr" { } } +############################################### +# IAM Role for PR Workflows (Read‑Only) +############################################### resource "aws_iam_role" "github_actions_pr" { - name = var.role_name_pr + name = var.role_name_pr + # Attach the trust policy defined above assume_role_policy = data.aws_iam_policy_document.github_actions_assume_role_pr.json } +############################################### +# Permissions for PR Role (Read‑Only Terraform) +############################################### data "aws_iam_policy_document" "deploy_permissions_pr" { + ############################################### + # Allow listing the Terraform state bucket + # Required for terraform init/plan in PR workflows + ############################################### statement { sid = "ListTerraformStateBucket" effect = "Allow" @@ -45,6 +67,10 @@ data "aws_iam_policy_document" "deploy_permissions_pr" { ] } + ############################################### + # Allow reading/writing .tfstate files + # PR workflows may run terraform plan which writes a local state + ############################################### statement { sid = "ReadWriteTerraformState" effect = "Allow" @@ -59,6 +85,10 @@ data "aws_iam_policy_document" "deploy_permissions_pr" { ] } + ############################################### + # Allow reading/writing/deleting Terraform lock files + # Required for terraform init/plan concurrency control + ############################################### statement { sid = "ReadWriteDeleteTerraformLockFile" effect = "Allow" @@ -73,6 +103,10 @@ data "aws_iam_policy_document" "deploy_permissions_pr" { ] } + ############################################### + # Allow reading secrets (but not modifying them) + # Needed for terraform plan when modules reference secrets + ############################################### statement { sid = "SecretsManagerManagement" effect = "Allow" @@ -86,6 +120,10 @@ data "aws_iam_policy_document" "deploy_permissions_pr" { resources = ["arn:aws:secretsmanager:*:*:secret:*"] } + ############################################### + # Allow read‑only access to KMS keys + # Required for decrypting encrypted Terraform state or secrets + ############################################### statement { sid = "KMSKeyManagement" effect = "Allow" @@ -102,6 +140,10 @@ data "aws_iam_policy_document" "deploy_permissions_pr" { resources = ["arn:aws:kms:*:*:key/*"] } + ############################################### + # Allow reading IAM roles/policies + # Required when Terraform references IAM resources in data sources + ############################################### statement { sid = "ReadIAMRoleAndPolicies" effect = "Allow" @@ -117,6 +159,10 @@ data "aws_iam_policy_document" "deploy_permissions_pr" { resources = ["arn:aws:iam::*:role/*"] } + ############################################### + # Allow reading IAM policies + # Required for terraform plan when IAM policies are referenced + ############################################### statement { sid = "IAMPolicyManagement" effect = "Allow" @@ -134,6 +180,10 @@ data "aws_iam_policy_document" "deploy_permissions_pr" { ] } + ############################################### + # Allow read‑only access to CloudWatch Logs + # Useful for debugging PR workflows or module behaviour + ############################################### statement { sid = "CloudWatchLogs" effect = "Allow" @@ -150,6 +200,9 @@ data "aws_iam_policy_document" "deploy_permissions_pr" { } } +############################################### +# Attach the read‑only policy to the PR role +############################################### resource "aws_iam_role_policy" "deploy_permissions_pr" { name = "deploy-permissions-pr" role = aws_iam_role.github_actions_pr.id From 792ef29ae62fdaab7e8729acda4dd0d8f0ee3548 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 11 Jun 2026 09:03:10 +0100 Subject: [PATCH 33/43] removed trailing spaces for file format check --- ...agement-service-api-specification-beta-int.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml b/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml index 756b8d9..5f919f3 100644 --- a/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml +++ b/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml @@ -140,11 +140,11 @@ info: (RBAC)](https://digital.nhs.uk/developer/guides-and-documentation/security-and-authorisation/national-rbac-for-developers) - RBAC rules are stored in the National RBAC Database (NRD) and are used to limit what clinical users can and cannot do. - + * [Digital Genomics Test Directory Service (DGTS)](https://digital.nhs.uk/developer/api-catalogue/digital-genomics-test-directory-service) - Search for Genomic Tests and Test Packages using FHIR - + * [Multicast Notification Service (MNS)](https://digital.nhs.uk/developer/api-catalogue/multicast-notification-service) - Notification service for genomic events @@ -472,7 +472,7 @@ paths: description: | ## Overview [FHIR Restful](https://hl7.org/fhir/R4/http.html) endpoint to - register a **new test or update tests or add reports etc**. This end point uses FHIR [bundle](https://simplifier.net/guide/fhir-genomics-implementation-guide/Home/FHIRAssets/Profiles/All-Profiles/Bundle) of type **transaction** and the list of resources that forms the payload are listed [here](https://simplifier.net/guide/fhir-genomics-implementation-guide/Home/Build/Resource-Linkage). + register a **new test or update tests or add reports etc**. This end point uses FHIR [bundle](https://simplifier.net/guide/fhir-genomics-implementation-guide/Home/FHIRAssets/Profiles/All-Profiles/Bundle) of type **transaction** and the list of resources that forms the payload are listed [here](https://simplifier.net/guide/fhir-genomics-implementation-guide/Home/Build/Resource-Linkage). tags: - process parameters: @@ -1937,7 +1937,7 @@ paths: This endpoint allows you to retrieve the records of DiagnosticReport using various query parameters. Refer [search](https://simplifier.net/guide/FHIR-Genomics-Implementation-Guide/Home/Build/Supported-CRUD-capability#Read) for more details. - + ## UGR - Search for patient test reports. tags: - report @@ -2172,7 +2172,7 @@ paths: | Scenario | - Request + Request | Response | @@ -2216,7 +2216,7 @@ paths: | Scenario | - Request + Request | Response | @@ -3575,7 +3575,7 @@ components: - type - entry description: >- - A Bundle containing [FHIR ServiceRequest](https://simplifier.net/guide/fhir-genomics-implementation-guide/Home/FHIRAssets/Profiles/All-Profiles/UKCore-ServiceRequest) + A Bundle containing [FHIR ServiceRequest](https://simplifier.net/guide/fhir-genomics-implementation-guide/Home/FHIRAssets/Profiles/All-Profiles/UKCore-ServiceRequest) history examples: multiple-items: From 3abbd4b0823a6e698c823fd442efb73638e2c50e Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 11 Jun 2026 09:05:56 +0100 Subject: [PATCH 34/43] removed trailing spaces for file format check --- .editorconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.editorconfig b/.editorconfig index ddecac1..71ee673 100644 --- a/.editorconfig +++ b/.editorconfig @@ -23,3 +23,6 @@ indent_style = tab [scripts/infra/README.md] ignore = true + +[specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml] +ignore = true \ No newline at end of file From 978e21d03aadfa0e89a760bfc42d29af66bcfd58 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 11 Jun 2026 09:15:57 +0100 Subject: [PATCH 35/43] removed trailing spaces for file format check --- .editorconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index 71ee673..5923fce 100644 --- a/.editorconfig +++ b/.editorconfig @@ -24,5 +24,5 @@ indent_style = tab [scripts/infra/README.md] ignore = true -[specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml] -ignore = true \ No newline at end of file +[specification/enviroment/beta/*] +ignore = true From da9950eca6a32a1ff860934170f19f62b42c2200 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 11 Jun 2026 11:36:35 +0100 Subject: [PATCH 36/43] removed trailing spaces for file format check --- ...anagement-service-api-specification-beta-int.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml b/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml index 5f919f3..3b1e9ca 100644 --- a/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml +++ b/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml @@ -140,11 +140,11 @@ info: (RBAC)](https://digital.nhs.uk/developer/guides-and-documentation/security-and-authorisation/national-rbac-for-developers) - RBAC rules are stored in the National RBAC Database (NRD) and are used to limit what clinical users can and cannot do. - + * [Digital Genomics Test Directory Service (DGTS)](https://digital.nhs.uk/developer/api-catalogue/digital-genomics-test-directory-service) - Search for Genomic Tests and Test Packages using FHIR - + * [Multicast Notification Service (MNS)](https://digital.nhs.uk/developer/api-catalogue/multicast-notification-service) - Notification service for genomic events @@ -352,7 +352,7 @@ info: | Integration |`https://int.api.service.nhs.uk/genomic-order-management-service`| - + | Production |`https://api.service.nhs.uk/genomic-order-management-service`| @@ -2182,8 +2182,8 @@ paths: ------------------------------------------------------------------------| | - | - | + | + | | tags: - history @@ -2226,7 +2226,7 @@ paths: ------------------------------------------------------------------------| | - | + | | | tags: From 648f8e32a02652941bad1d10273ab37731b50f0e Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 11 Jun 2026 11:50:17 +0100 Subject: [PATCH 37/43] removed trailing spaces for file format check --- ...mic-order-management-service-api-specification-beta-int.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml b/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml index 3b1e9ca..facbef7 100644 --- a/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml +++ b/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml @@ -2226,7 +2226,7 @@ paths: ------------------------------------------------------------------------| | - | + | | | tags: From c28c69163f4ccc52cdfa94d6f800f1ee8bb1f5d3 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 11 Jun 2026 11:51:19 +0100 Subject: [PATCH 38/43] removed trailing spaces for file format check --- ...ic-order-management-service-api-specification-beta-int.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml b/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml index facbef7..9517169 100644 --- a/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml +++ b/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml @@ -4751,4 +4751,5 @@ x-nhsd-apim: timeunit: minute app-default: limit: 200 - timeunit: minute \ No newline at end of file + timeunit: minute + \ No newline at end of file From 63a09928990be7a8206fb184b8e89ce0a1f0eb16 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 11 Jun 2026 11:53:59 +0100 Subject: [PATCH 39/43] removed trailing spaces for file format check --- ...omic-order-management-service-api-specification-beta-int.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml b/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml index 9517169..f8f2a6a 100644 --- a/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml +++ b/specification/enviroment/beta/genomic-order-management-service-api-specification-beta-int.yaml @@ -4752,4 +4752,3 @@ x-nhsd-apim: app-default: limit: 200 timeunit: minute - \ No newline at end of file From 28e39d4e3b75952743cf0e7a97f7d4bdf86ceb24 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 11 Jun 2026 12:47:40 +0100 Subject: [PATCH 40/43] fixed yaml syntx --- .github/workflows/infra-deploy.yaml | 14 +++++++------- scripts/infra/iam-oidc-pr-role.tf | 25 ++++++++++++++++++++----- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index 071074d..4ebed50 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -23,13 +23,13 @@ jobs: runs-on: ubuntu-latest steps: - name: "Check branch" - run: | - if [ "${{ github.ref }}" != "refs/heads/main" ]; then - echo "ERROR: This workflow can only be run from the 'main' branch." >&2 - echo "Current branch: ${{ github.ref }}" >&2 - exit 1 - fi - echo "bypassing the checks temporarily for testing" + run: | + if [ "${{ github.ref }}" != "refs/heads/main" ]; then + echo "ERROR: This workflow can only be run from the 'main' branch." >&2 + echo "Current branch: ${{ github.ref }}" >&2 + exit 1 + fi + echo "bypassing the checks temporarily for testing" metadata: name: "Set CI/CD metadata" needs: [validate-main-branch] diff --git a/scripts/infra/iam-oidc-pr-role.tf b/scripts/infra/iam-oidc-pr-role.tf index a6189db..4560b48 100644 --- a/scripts/infra/iam-oidc-pr-role.tf +++ b/scripts/infra/iam-oidc-pr-role.tf @@ -114,9 +114,10 @@ data "aws_iam_policy_document" "deploy_permissions_pr" { actions = [ "secretsmanager:DescribeSecret", "secretsmanager:GetSecretValue", - "secretsmanager:ListSecrets", + "secretsmanager:GetResourcePolicy", + "secretsmanager:ListSecretVersionIds", + "secretsmanager:ListSecrets" ] - resources = ["arn:aws:secretsmanager:*:*:secret:*"] } @@ -130,16 +131,30 @@ data "aws_iam_policy_document" "deploy_permissions_pr" { actions = [ "kms:DescribeKey", - "kms:ListKeys", - "kms:ListAliases", "kms:GetKeyPolicy", "kms:Encrypt", + "kms:Decrypt", + "kms:GetKeyRotationStatus", + "kms:ListResourceTags", + "kms:ListGrants", + "kms:GenerateDataKeyWithoutPlaintext", "kms:GenerateDataKey" ] - resources = ["arn:aws:kms:*:*:key/*"] } + ############################################################### + # KMS Global Permissions (Key Creation) + ############################################################### + statement { + sid = "KmsGlobalPermissions" + actions = [ + "kms:ListKeys", + "kms:ListAliases" + ] + resources = ["*"] + } + ############################################### # Allow reading IAM roles/policies # Required when Terraform references IAM resources in data sources From 24ee11da61670c8c4c25a4d46a1585cb0b7b8002 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 11 Jun 2026 12:53:04 +0100 Subject: [PATCH 41/43] updating worflow depedency --- .github/workflows/cicd-3-deploy.yaml | 22 +++++++++++++--------- .github/workflows/infra-deploy.yaml | 13 ------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/.github/workflows/cicd-3-deploy.yaml b/.github/workflows/cicd-3-deploy.yaml index 9626a1b..efe8975 100644 --- a/.github/workflows/cicd-3-deploy.yaml +++ b/.github/workflows/cicd-3-deploy.yaml @@ -16,6 +16,18 @@ on: - prod jobs: + validate-main-branch: + name: "Validate deployment from main branch" + runs-on: ubuntu-latest + steps: + - name: "Check branch" + run: | + if [ "${{ github.ref }}" != "refs/heads/main" ]; then + echo "ERROR: This workflow can only be run from the 'main' branch." >&2 + echo "Current branch: ${{ github.ref }}" >&2 + exit 1 + fi + echo "bypassing the checks temporarily for testing" metadata: name: "Set CI/CD metadata" runs-on: ubuntu-latest @@ -57,16 +69,8 @@ jobs: export VERSION="${{ steps.variables.outputs.version }}" export TAG="${{ steps.variables.outputs.tag }}" make list-variables - deploy: - name: "Deploy to an environment" - runs-on: ubuntu-latest - needs: [metadata] - timeout-minutes: 10 - steps: - - name: "Checkout code" - uses: actions/checkout@v4 infra-deploy: - needs: [deploy] + needs: [terraform-validate] uses: ./.github/workflows/infra-deploy.yaml with: environment: ${{ inputs.environment }} diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index 4ebed50..1616a39 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -18,18 +18,6 @@ env: AWS_DEPLOY_ROLE: ${{ inputs.environment == 'prod' && secrets.AWS_PROD_DEPLOY_ROLE || secrets.AWS_INT_DEPLOY_ROLE }} jobs: - validate-main-branch: - name: "Validate deployment from main branch" - runs-on: ubuntu-latest - steps: - - name: "Check branch" - run: | - if [ "${{ github.ref }}" != "refs/heads/main" ]; then - echo "ERROR: This workflow can only be run from the 'main' branch." >&2 - echo "Current branch: ${{ github.ref }}" >&2 - exit 1 - fi - echo "bypassing the checks temporarily for testing" metadata: name: "Set CI/CD metadata" needs: [validate-main-branch] @@ -108,7 +96,6 @@ jobs: terraform-plan: name: "Terraform plan for ${{ inputs.environment }}" - needs: [terraform-validate] runs-on: ubuntu-latest timeout-minutes: 10 steps: From 1524a85067f01267f329406b1831f4fb77a7d6e9 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 11 Jun 2026 12:56:14 +0100 Subject: [PATCH 42/43] updating worflow depedency --- .github/workflows/cicd-3-deploy.yaml | 2 +- .github/workflows/infra-deploy.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cicd-3-deploy.yaml b/.github/workflows/cicd-3-deploy.yaml index efe8975..dc1f9cc 100644 --- a/.github/workflows/cicd-3-deploy.yaml +++ b/.github/workflows/cicd-3-deploy.yaml @@ -70,7 +70,7 @@ jobs: export TAG="${{ steps.variables.outputs.tag }}" make list-variables infra-deploy: - needs: [terraform-validate] + needs: [validate-main-branch] uses: ./.github/workflows/infra-deploy.yaml with: environment: ${{ inputs.environment }} diff --git a/.github/workflows/infra-deploy.yaml b/.github/workflows/infra-deploy.yaml index 1616a39..beb2c6e 100644 --- a/.github/workflows/infra-deploy.yaml +++ b/.github/workflows/infra-deploy.yaml @@ -20,7 +20,6 @@ env: jobs: metadata: name: "Set CI/CD metadata" - needs: [validate-main-branch] runs-on: ubuntu-latest timeout-minutes: 1 outputs: @@ -96,6 +95,7 @@ jobs: terraform-plan: name: "Terraform plan for ${{ inputs.environment }}" + needs: [terraform-validate] runs-on: ubuntu-latest timeout-minutes: 10 steps: From 7f9ed9d44594a196caec29cffe97d607d0507293 Mon Sep 17 00:00:00 2001 From: vgnapskainos <253046491+vgnapskainos@users.noreply.github.com> Date: Thu, 11 Jun 2026 16:20:31 +0100 Subject: [PATCH 43/43] Adding codeowners --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..5c39b6d --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# .github/CODEOWNERS +* @NHSDigital/RaviNatarajan22