From 9b80c10cb8725e23d07b867eaf6d75b8ad4b0843 Mon Sep 17 00:00:00 2001 From: Mark Ramsden Date: Wed, 12 Mar 2025 10:55:43 +0000 Subject: [PATCH 1/4] CCM-8524: add public signing keys bucket and frontend --- .../app/module_public_signing_keys.tf | 8 + .../app/s3_bucket_public_signing_keys.tf | 137 ++++++++++++++ .../terraform/components/app/versions.tf | 4 + .../sandbox/module_public_signing_keys.tf | 8 + .../terraform/components/sandbox/versions.tf | 14 ++ ...dfront_distribution_public_signing_keys.tf | 81 ++++++++ .../public-signing-keys/github_ip_ranges.tf | 1 + .../iam_policy_s3_public_signing_keys.tf | 23 +++ .../modules/public-signing-keys/locals.tf | 3 + .../public-signing-keys/provider_aws.tf | 24 +++ .../s3_bucket_policy_public_signing_keys.tf | 53 ++++++ .../s3_bucket_public_signing_keys.tf | 137 ++++++++++++++ .../modules/public-signing-keys/variables.tf | 60 ++++++ .../public-signing-keys/wafv2_ip_set.tf | 23 +++ .../public-signing-keys/wafv2_web_acl.tf | 179 ++++++++++++++++++ 15 files changed, 755 insertions(+) create mode 100644 infrastructure/terraform/components/app/module_public_signing_keys.tf create mode 100644 infrastructure/terraform/components/app/s3_bucket_public_signing_keys.tf create mode 100644 infrastructure/terraform/components/sandbox/module_public_signing_keys.tf create mode 100644 infrastructure/terraform/components/sandbox/versions.tf create mode 100644 infrastructure/terraform/modules/public-signing-keys/cloudfront_distribution_public_signing_keys.tf create mode 100644 infrastructure/terraform/modules/public-signing-keys/github_ip_ranges.tf create mode 100644 infrastructure/terraform/modules/public-signing-keys/iam_policy_s3_public_signing_keys.tf create mode 100644 infrastructure/terraform/modules/public-signing-keys/locals.tf create mode 100644 infrastructure/terraform/modules/public-signing-keys/provider_aws.tf create mode 100644 infrastructure/terraform/modules/public-signing-keys/s3_bucket_policy_public_signing_keys.tf create mode 100644 infrastructure/terraform/modules/public-signing-keys/s3_bucket_public_signing_keys.tf create mode 100644 infrastructure/terraform/modules/public-signing-keys/variables.tf create mode 100644 infrastructure/terraform/modules/public-signing-keys/wafv2_ip_set.tf create mode 100644 infrastructure/terraform/modules/public-signing-keys/wafv2_web_acl.tf diff --git a/infrastructure/terraform/components/app/module_public_signing_keys.tf b/infrastructure/terraform/components/app/module_public_signing_keys.tf new file mode 100644 index 00000000..392d93ee --- /dev/null +++ b/infrastructure/terraform/components/app/module_public_signing_keys.tf @@ -0,0 +1,8 @@ +module "public_signing_keys" { + source = "../../modules/public-signing-keys" + aws_account_id = var.aws_account_id + environment = var.environment + region = var.region + project = var.project + csi = local.csi +} diff --git a/infrastructure/terraform/components/app/s3_bucket_public_signing_keys.tf b/infrastructure/terraform/components/app/s3_bucket_public_signing_keys.tf new file mode 100644 index 00000000..1762414b --- /dev/null +++ b/infrastructure/terraform/components/app/s3_bucket_public_signing_keys.tf @@ -0,0 +1,137 @@ +module "s3bucket_signing_keys" { + source = "git::https://github.com/NHSDigital/nhs-notify-shared-modules.git//infrastructure/modules/s3bucket?ref=v1.0.9" + + name = "public-signing-keys" + + aws_account_id = var.aws_account_id + region = var.region + project = var.project + environment = var.environment + component = var.component + + acl = "public-read" + force_destroy = false + versioning = true + + lifecycle_rules = [ + { + enabled = true + + noncurrent_version_transition = [ + { + noncurrent_days = "30" + storage_class = "STANDARD_IA" + } + ] + + noncurrent_version_expiration = { + noncurrent_days = "90" + } + + abort_incomplete_multipart_upload = { + days = "1" + } + } + ] + + policy_documents = [ + data.aws_iam_policy_document.s3bucket_signing_keys.json + ] + + public_access = { + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true + } + + + default_tags = { + Name = "Public signing keys for federated auth suppliers" + } +} + +data "aws_iam_policy_document" "s3bucket_signing_keys" { + statement { + sid = "DontAllowNonSecureConnection" + effect = "Deny" + + actions = [ + "s3:*", + ] + + resources = [ + module.s3bucket_signing_keys.arn, + "${module.s3bucket_signing_keys.arn}/*", + ] + + principals { + type = "AWS" + + identifiers = [ + "*", + ] + } + + condition { + test = "Bool" + variable = "aws:SecureTransport" + + values = [ + "false", + ] + } + } + + statement { + sid = "AllowManagedAccountsToList" + effect = "Allow" + + actions = [ + "s3:ListBucket", + ] + + resources = [ + module.s3bucket_signing_keys.arn, + ] + + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${var.aws_account_id}:root" + ] + } + } + + statement { + sid = "AllowManagedAccountsToGet" + effect = "Allow" + + actions = [ + "s3:GetObject", + ] + + resources = [ + "${module.s3bucket_signing_keys.arn}/*", + ] + + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${var.aws_account_id}:root" + ] + } + } +} + +resource "aws_s3_bucket_cors_configuration" "public_signing_keys" { + bucket = module.s3bucket_signing_keys.bucket + + cors_rule { + allowed_headers = [] + allowed_methods = ["GET"] + allowed_origins = ["*"] + expose_headers = ["ETag"] + max_age_seconds = 300 + } +} diff --git a/infrastructure/terraform/components/app/versions.tf b/infrastructure/terraform/components/app/versions.tf index 5fba18d2..3b352980 100644 --- a/infrastructure/terraform/components/app/versions.tf +++ b/infrastructure/terraform/components/app/versions.tf @@ -4,6 +4,10 @@ terraform { source = "hashicorp/aws" version = "~> 5.50" } + github = { + source = "integrations/github" + version = "~> 6.0" + } } required_version = ">= 1.9.0" diff --git a/infrastructure/terraform/components/sandbox/module_public_signing_keys.tf b/infrastructure/terraform/components/sandbox/module_public_signing_keys.tf new file mode 100644 index 00000000..392d93ee --- /dev/null +++ b/infrastructure/terraform/components/sandbox/module_public_signing_keys.tf @@ -0,0 +1,8 @@ +module "public_signing_keys" { + source = "../../modules/public-signing-keys" + aws_account_id = var.aws_account_id + environment = var.environment + region = var.region + project = var.project + csi = local.csi +} diff --git a/infrastructure/terraform/components/sandbox/versions.tf b/infrastructure/terraform/components/sandbox/versions.tf new file mode 100644 index 00000000..3b352980 --- /dev/null +++ b/infrastructure/terraform/components/sandbox/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.50" + } + github = { + source = "integrations/github" + version = "~> 6.0" + } + } + + required_version = ">= 1.9.0" +} diff --git a/infrastructure/terraform/modules/public-signing-keys/cloudfront_distribution_public_signing_keys.tf b/infrastructure/terraform/modules/public-signing-keys/cloudfront_distribution_public_signing_keys.tf new file mode 100644 index 00000000..4fa51494 --- /dev/null +++ b/infrastructure/terraform/modules/public-signing-keys/cloudfront_distribution_public_signing_keys.tf @@ -0,0 +1,81 @@ +resource "aws_cloudfront_distribution" "signing_keys" { + provider = aws.us-east-1 + + enabled = true + is_ipv6_enabled = true + comment = "Public Signing Keys (${local.csi})" + default_root_object = "index.html" + price_class = "PriceClass_100" # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-cloudfront-distribution-distributionconfig.html#cfn-cloudfront-distribution-distributionconfig-priceclass + web_acl_id = aws_wafv2_web_acl.public_signing_keys.arn + + restrictions { + geo_restriction { + restriction_type = "none" # Moved to WAF + locations = [] # Moved to WAF + } + } + + # TODO + # aliases = flatten([ + # [ + # local.root_domain_name, + # ], + # var.cdn_sans + # ]) + + # TODO + # viewer_certificate { + # acm_certificate_arn = aws_acm_certificate.main.arn + # minimum_protocol_version = "TLSv1.2_2021" # Supports 1.2 & 1.3 - https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/secure-connections-supported-viewer-protocols-ciphers.html + # ssl_support_method = "sni-only" + # } + viewer_certificate { + cloudfront_default_certificate = true + } + + # TODO + # logging_config { + # bucket = module.s3bucket_cf_logs.bucket_regional_domain_name + # include_cookies = false + # } + + origin { + domain_name = module.s3bucket_public_keys.bucket_regional_domain_name + origin_id = "${local.csi}-public-keys" + s3_origin_config { + origin_access_identity = aws_cloudfront_origin_access_identity.signing_keys.cloudfront_access_identity_path + } + } + + # Github Web-CMS behaviour + default_cache_behavior { + allowed_methods = [ + "GET", + "HEAD", + ] + cached_methods = [ + "GET", + "HEAD", + ] + target_origin_id = "${local.csi}-public-keys" + + forwarded_values { + query_string = false + headers = ["Origin"] + + cookies { + forward = "none" + } + } + + viewer_protocol_policy = "redirect-to-https" + min_ttl = 0 + default_ttl = 3600 + max_ttl = 86400 + compress = true + } +} + +resource "aws_cloudfront_origin_access_identity" "signing_keys" { + comment = "Used to access the S3 content for the public signing keys bucket" +} diff --git a/infrastructure/terraform/modules/public-signing-keys/github_ip_ranges.tf b/infrastructure/terraform/modules/public-signing-keys/github_ip_ranges.tf new file mode 100644 index 00000000..0540d96e --- /dev/null +++ b/infrastructure/terraform/modules/public-signing-keys/github_ip_ranges.tf @@ -0,0 +1 @@ +data "github_ip_ranges" "main" {} diff --git a/infrastructure/terraform/modules/public-signing-keys/iam_policy_s3_public_signing_keys.tf b/infrastructure/terraform/modules/public-signing-keys/iam_policy_s3_public_signing_keys.tf new file mode 100644 index 00000000..17e42be7 --- /dev/null +++ b/infrastructure/terraform/modules/public-signing-keys/iam_policy_s3_public_signing_keys.tf @@ -0,0 +1,23 @@ +resource "aws_iam_policy" "public_signing_keys" { + name = "${local.csi}-public-signing-keys" + description = "Access policy to allow access to public signing keys in S3" + path = "/" + policy = data.aws_iam_policy_document.public_signing_keys.json +} + +data "aws_iam_policy_document" "public_signing_keys" { + statement { + sid = "AllowS3Read" + effect = "Allow" + + actions = [ + "s3:List*", + "s3:Get*", + ] + + resources = [ + module.s3bucket_public_keys.arn, + "${module.s3bucket_public_keys.arn}/*", + ] + } +} diff --git a/infrastructure/terraform/modules/public-signing-keys/locals.tf b/infrastructure/terraform/modules/public-signing-keys/locals.tf new file mode 100644 index 00000000..b7b8b8e1 --- /dev/null +++ b/infrastructure/terraform/modules/public-signing-keys/locals.tf @@ -0,0 +1,3 @@ +locals { + csi = "${var.csi}-${var.component}" +} diff --git a/infrastructure/terraform/modules/public-signing-keys/provider_aws.tf b/infrastructure/terraform/modules/public-signing-keys/provider_aws.tf new file mode 100644 index 00000000..5403bf35 --- /dev/null +++ b/infrastructure/terraform/modules/public-signing-keys/provider_aws.tf @@ -0,0 +1,24 @@ +provider "aws" { + region = var.region + + allowed_account_ids = [ + var.aws_account_id, + ] + + default_tags { + tags = var.default_tags + } +} + +provider "aws" { + alias = "us-east-1" + region = "us-east-1" + + default_tags { + tags = var.default_tags + } + + allowed_account_ids = [ + var.aws_account_id, + ] +} diff --git a/infrastructure/terraform/modules/public-signing-keys/s3_bucket_policy_public_signing_keys.tf b/infrastructure/terraform/modules/public-signing-keys/s3_bucket_policy_public_signing_keys.tf new file mode 100644 index 00000000..2e570451 --- /dev/null +++ b/infrastructure/terraform/modules/public-signing-keys/s3_bucket_policy_public_signing_keys.tf @@ -0,0 +1,53 @@ +resource "aws_s3_bucket_policy" "public_signing_keys" { + bucket = module.s3bucket_public_keys.id + policy = data.aws_iam_policy_document.bucket_policy_public_signing_keys.json + } + + data "aws_iam_policy_document" "bucket_policy_public_signing_keys" { + statement { + actions = ["s3:GetObject"] + resources = [ + "${module.s3bucket_public_keys.arn}/*" + ] + + principals { + type = "AWS" + identifiers = ["arn:aws:iam::${var.aws_account_id}:root", aws_cloudfront_origin_access_identity.signing_keys.iam_arn] + } + } + + statement { + actions = ["s3:ListBucket"] + resources = [ + module.s3bucket_public_keys.arn + ] + + principals { + type = "AWS" + identifiers = ["arn:aws:iam::${var.aws_account_id}:root", aws_cloudfront_origin_access_identity.signing_keys.iam_arn] + } + } + + statement { + effect = "Deny" + actions = ["s3:*"] + resources = [ + module.s3bucket_public_keys.arn, + "${module.s3bucket_public_keys.arn}/*", + ] + + principals { + type = "AWS" + identifiers = ["*"] + } + + condition { + test = "Bool" + variable = "aws:SecureTransport" + values = [ + false + ] + } + } + } + \ No newline at end of file diff --git a/infrastructure/terraform/modules/public-signing-keys/s3_bucket_public_signing_keys.tf b/infrastructure/terraform/modules/public-signing-keys/s3_bucket_public_signing_keys.tf new file mode 100644 index 00000000..71dff855 --- /dev/null +++ b/infrastructure/terraform/modules/public-signing-keys/s3_bucket_public_signing_keys.tf @@ -0,0 +1,137 @@ +module "s3bucket_public_keys" { + source = "git::https://github.com/NHSDigital/nhs-notify-shared-modules.git//infrastructure/modules/s3bucket?ref=v1.0.9" + + name = "public-keys" + + aws_account_id = var.aws_account_id + region = var.region + project = var.project + environment = var.environment + component = var.component + + acl = "public-read" + force_destroy = false + versioning = true + + lifecycle_rules = [ + { + enabled = true + + noncurrent_version_transition = [ + { + noncurrent_days = "30" + storage_class = "STANDARD_IA" + } + ] + + noncurrent_version_expiration = { + noncurrent_days = "90" + } + + abort_incomplete_multipart_upload = { + days = "1" + } + } + ] + + policy_documents = [ + data.aws_iam_policy_document.s3bucket_public_keys.json + ] + + public_access = { + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true + } + + + default_tags = { + Name = "Public signing keys for federated auth suppliers" + } +} + +data "aws_iam_policy_document" "s3bucket_public_keys" { + statement { + sid = "DontAllowNonSecureConnection" + effect = "Deny" + + actions = [ + "s3:*", + ] + + resources = [ + module.s3bucket_public_keys.arn, + "${module.s3bucket_public_keys.arn}/*", + ] + + principals { + type = "AWS" + + identifiers = [ + "*", + ] + } + + condition { + test = "Bool" + variable = "aws:SecureTransport" + + values = [ + "false", + ] + } + } + + statement { + sid = "AllowManagedAccountsToList" + effect = "Allow" + + actions = [ + "s3:ListBucket", + ] + + resources = [ + module.s3bucket_public_keys.arn, + ] + + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${var.aws_account_id}:root" + ] + } + } + + statement { + sid = "AllowManagedAccountsToGet" + effect = "Allow" + + actions = [ + "s3:GetObject", + ] + + resources = [ + "${module.s3bucket_public_keys.arn}/*", + ] + + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${var.aws_account_id}:root" + ] + } + } +} + +resource "aws_s3_bucket_cors_configuration" "public_public_keys" { + bucket = module.s3bucket_public_keys.bucket + + cors_rule { + allowed_headers = ["Authorization"] + allowed_methods = ["GET"] + allowed_origins = ["*"] + expose_headers = ["ETag"] + max_age_seconds = 300 + } +} diff --git a/infrastructure/terraform/modules/public-signing-keys/variables.tf b/infrastructure/terraform/modules/public-signing-keys/variables.tf new file mode 100644 index 00000000..26e49ff3 --- /dev/null +++ b/infrastructure/terraform/modules/public-signing-keys/variables.tf @@ -0,0 +1,60 @@ +## +# Basic Required Variables for tfscaffold Components +## + +variable "project" { + type = string + description = "The name of the tfscaffold project" +} + +variable "environment" { + type = string + description = "The name of the tfscaffold environment" +} + +variable "aws_account_id" { + type = string + description = "The AWS Account ID (numeric)" +} + +variable "region" { + type = string + description = "The AWS Region" +} + +## +# tfscaffold variables specific to this component +## + +# This is the only primary variable to have its value defined as +# a default within its declaration in this file, because the variables +# purpose is as an identifier unique to this component, rather +# then to the environment from where all other variables come. +variable "component" { + type = string + description = "The variable encapsulating the name of this component" + default = "app" +} + +variable "default_tags" { + type = map(string) + description = "A map of default tags to apply to all taggable resources within the component" + default = {} +} + +variable "csi" { + type = string + description = "CSI from the parent component" +} + +variable "enable_github_actions_ip_access" { + type = bool + description = "Should the Github actions runner IP addresses be permitted access to this distribution. This should not be enabled in production environments" + default = false +} + +variable "waf_rate_limit_cdn" { + type = number + description = "The rate limit is the maximum number of CDN requests from a single IP address that are allowed in a five-minute period" + default = 20000 +} diff --git a/infrastructure/terraform/modules/public-signing-keys/wafv2_ip_set.tf b/infrastructure/terraform/modules/public-signing-keys/wafv2_ip_set.tf new file mode 100644 index 00000000..2f1755a8 --- /dev/null +++ b/infrastructure/terraform/modules/public-signing-keys/wafv2_ip_set.tf @@ -0,0 +1,23 @@ +resource "aws_wafv2_ip_set" "github_actions_ipv4" { + count = var.enable_github_actions_ip_access ? 1 : 0 + + provider = aws.us-east-1 + + name = "${local.csi}-github-actions-ipv4" + description = "Public references for github actions runner IP addresses" + scope = "CLOUDFRONT" + ip_address_version = "IPV4" + addresses = data.github_ip_ranges.main.actions_ipv4 +} + +resource "aws_wafv2_ip_set" "github_actions_ipv6" { + count = var.enable_github_actions_ip_access ? 1 : 0 + + provider = aws.us-east-1 + + name = "${local.csi}-github-actions-ipv6" + description = "Public references for github actions runner IP addresses" + scope = "CLOUDFRONT" + ip_address_version = "IPV6" + addresses = data.github_ip_ranges.main.actions_ipv6 +} diff --git a/infrastructure/terraform/modules/public-signing-keys/wafv2_web_acl.tf b/infrastructure/terraform/modules/public-signing-keys/wafv2_web_acl.tf new file mode 100644 index 00000000..ec6547e9 --- /dev/null +++ b/infrastructure/terraform/modules/public-signing-keys/wafv2_web_acl.tf @@ -0,0 +1,179 @@ +resource "aws_wafv2_web_acl" "public_signing_keys" { + provider = aws.us-east-1 + + name = local.csi + description = "${var.environment} WAF" + scope = "CLOUDFRONT" + + default_action { + allow {} + } + + dynamic "rule" { + for_each = var.enable_github_actions_ip_access ? [1] : [] + + content { + name = "GithubActionsIPRestriction" + priority = 10 + + action { + allow {} + } + + statement { + or_statement { + statement { + ip_set_reference_statement { + arn = aws_wafv2_ip_set.github_actions_ipv4[0].arn + } + } + + statement { + ip_set_reference_statement { + arn = aws_wafv2_ip_set.github_actions_ipv6[0].arn + } + } + } + } + + visibility_config { + metric_name = "${local.csi}_gha_ip_restrictions_metric" + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + } + } + } + + rule { + name = "GeoLocationTrafficWhitelist" + priority = 20 + + action { + block {} + } + + statement { + not_statement { + statement { + geo_match_statement { + country_codes = ["GB"] + } + } + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + metric_name = "${local.csi}_geo_location_whitelist" + } + } + + rule { + name = "AWSManagedRulesCommonRuleSet" + priority = 30 + override_action { + none {} + } + statement { + managed_rule_group_statement { + name = "AWSManagedRulesCommonRuleSet" + vendor_name = "AWS" + + rule_action_override { + name = "GenericRFI_QUERYARGUMENTS" + action_to_use { + count {} + } + } + } + } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "${local.csi}_waf_aws_managed_common" + sampled_requests_enabled = true + } + } + + rule { + name = "AWSManagedRulesKnownBadInputsRuleSet" + priority = 40 + override_action { + none {} + } + statement { + managed_rule_group_statement { + name = "AWSManagedRulesKnownBadInputsRuleSet" + vendor_name = "AWS" + } + } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "${local.csi}_waf_aws_managed_input" + sampled_requests_enabled = true + } + } + + rule { + name = "AWSManagedRulesSQLiRuleSet" + priority = 50 + override_action { + none {} + } + statement { + managed_rule_group_statement { + name = "AWSManagedRulesSQLiRuleSet" + vendor_name = "AWS" + } + } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "${local.csi}_waf_aws_managed_sql" + sampled_requests_enabled = true + } + } + + rule { + name = "AWSManagedRulesAmazonIpReputationList" + priority = 60 + override_action { + none {} + } + statement { + managed_rule_group_statement { + name = "AWSManagedRulesAmazonIpReputationList" + vendor_name = "AWS" + } + } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "${local.csi}_waf_aws_managed_reputation" + sampled_requests_enabled = true + } + } + + rule { + name = "RateLimit" + priority = 100 + action { + block {} + } + statement { + rate_based_statement { + limit = var.waf_rate_limit_cdn + aggregate_key_type = "IP" + } + } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "${local.csi}_waf_rate_limit" + sampled_requests_enabled = true + } + } + + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "${local.csi}_waf" + sampled_requests_enabled = true + } +} From 5098738e6eb922a8acaf8169d3b5aab9c18f1977 Mon Sep 17 00:00:00 2001 From: Mark Ramsden Date: Tue, 18 Mar 2025 11:36:36 +0000 Subject: [PATCH 2/4] CCM-8524: add public signing keys bucket and frontend --- infrastructure/terraform/bin/terraform.sh | 2 +- .../terraform/components/acct/versions.tf | 2 +- .../app/module_public_signing_keys.tf | 1 + .../terraform/components/app/versions.tf | 2 +- .../terraform/components/branch/versions.tf | 2 +- .../components/sandbox/locals_remote_state.tf | 40 ++++ .../sandbox/module_public_signing_keys.tf | 1 + .../terraform/components/sandbox/versions.tf | 2 +- ...dfront_distribution_public_signing_keys.tf | 9 +- .../modules/public-signing-keys/locals.tf | 2 +- .../module_s3bucket_cf_logs.tf | 171 ++++++++++++++++++ .../s3_bucket_public_signing_keys.tf | 4 + .../modules/public-signing-keys/variables.tf | 11 ++ 13 files changed, 238 insertions(+), 11 deletions(-) create mode 100644 infrastructure/terraform/components/sandbox/locals_remote_state.tf create mode 100644 infrastructure/terraform/modules/public-signing-keys/module_s3bucket_cf_logs.tf diff --git a/infrastructure/terraform/bin/terraform.sh b/infrastructure/terraform/bin/terraform.sh index 756b4ef8..dbf0dd85 100755 --- a/infrastructure/terraform/bin/terraform.sh +++ b/infrastructure/terraform/bin/terraform.sh @@ -539,7 +539,7 @@ fi; [ -f "${dynamic_file_path}" ] && tf_var_file_paths+=("${dynamic_file_path}"); # Warn on duplication -duplicate_variables="$(cat "${tf_var_file_paths[@]}" | sed -n -e 's/\(^[a-zA-Z0-9_\-]\+\)\s*=.*$/\1/p' | sort | uniq -d)"; +duplicate_variables="$([ ${#tf_var_file_paths[@]} -gt 0 ] && cat "${tf_var_file_paths[@]}" | sed -n -e 's/\(^[a-zA-Z0-9_\-]\+\)\s*=.*$/\1/p' | sort | uniq -d)"; [ -n "${duplicate_variables}" ] \ && echo -e " ################################################################### diff --git a/infrastructure/terraform/components/acct/versions.tf b/infrastructure/terraform/components/acct/versions.tf index 5fba18d2..1dbd936a 100644 --- a/infrastructure/terraform/components/acct/versions.tf +++ b/infrastructure/terraform/components/acct/versions.tf @@ -6,5 +6,5 @@ terraform { } } - required_version = ">= 1.9.0" + required_version = ">= 1.9.2" } diff --git a/infrastructure/terraform/components/app/module_public_signing_keys.tf b/infrastructure/terraform/components/app/module_public_signing_keys.tf index 392d93ee..8a721b31 100644 --- a/infrastructure/terraform/components/app/module_public_signing_keys.tf +++ b/infrastructure/terraform/components/app/module_public_signing_keys.tf @@ -5,4 +5,5 @@ module "public_signing_keys" { region = var.region project = var.project csi = local.csi + acct = local.acct } diff --git a/infrastructure/terraform/components/app/versions.tf b/infrastructure/terraform/components/app/versions.tf index 3b352980..e7d6a6a0 100644 --- a/infrastructure/terraform/components/app/versions.tf +++ b/infrastructure/terraform/components/app/versions.tf @@ -10,5 +10,5 @@ terraform { } } - required_version = ">= 1.9.0" + required_version = ">= 1.9.2" } diff --git a/infrastructure/terraform/components/branch/versions.tf b/infrastructure/terraform/components/branch/versions.tf index 5fba18d2..1dbd936a 100644 --- a/infrastructure/terraform/components/branch/versions.tf +++ b/infrastructure/terraform/components/branch/versions.tf @@ -6,5 +6,5 @@ terraform { } } - required_version = ">= 1.9.0" + required_version = ">= 1.9.2" } diff --git a/infrastructure/terraform/components/sandbox/locals_remote_state.tf b/infrastructure/terraform/components/sandbox/locals_remote_state.tf new file mode 100644 index 00000000..50c57af6 --- /dev/null +++ b/infrastructure/terraform/components/sandbox/locals_remote_state.tf @@ -0,0 +1,40 @@ +locals { + bootstrap = data.terraform_remote_state.bootstrap.outputs + acct = data.terraform_remote_state.acct.outputs +} + +data "terraform_remote_state" "bootstrap" { + backend = "s3" + + config = { + bucket = local.terraform_state_bucket + + key = format( + "%s/%s/%s/%s/bootstrap.tfstate", + var.project, + var.aws_account_id, + "eu-west-2", + "bootstrap" + ) + + region = "eu-west-2" + } +} + +data "terraform_remote_state" "acct" { + backend = "s3" + + config = { + bucket = local.terraform_state_bucket + + key = format( + "%s/%s/%s/%s/acct.tfstate", + var.project, + var.aws_account_id, + "eu-west-2", + "main" + ) + + region = "eu-west-2" + } +} diff --git a/infrastructure/terraform/components/sandbox/module_public_signing_keys.tf b/infrastructure/terraform/components/sandbox/module_public_signing_keys.tf index 392d93ee..8a721b31 100644 --- a/infrastructure/terraform/components/sandbox/module_public_signing_keys.tf +++ b/infrastructure/terraform/components/sandbox/module_public_signing_keys.tf @@ -5,4 +5,5 @@ module "public_signing_keys" { region = var.region project = var.project csi = local.csi + acct = local.acct } diff --git a/infrastructure/terraform/components/sandbox/versions.tf b/infrastructure/terraform/components/sandbox/versions.tf index 3b352980..e7d6a6a0 100644 --- a/infrastructure/terraform/components/sandbox/versions.tf +++ b/infrastructure/terraform/components/sandbox/versions.tf @@ -10,5 +10,5 @@ terraform { } } - required_version = ">= 1.9.0" + required_version = ">= 1.9.2" } diff --git a/infrastructure/terraform/modules/public-signing-keys/cloudfront_distribution_public_signing_keys.tf b/infrastructure/terraform/modules/public-signing-keys/cloudfront_distribution_public_signing_keys.tf index 4fa51494..7627dbb5 100644 --- a/infrastructure/terraform/modules/public-signing-keys/cloudfront_distribution_public_signing_keys.tf +++ b/infrastructure/terraform/modules/public-signing-keys/cloudfront_distribution_public_signing_keys.tf @@ -33,11 +33,10 @@ resource "aws_cloudfront_distribution" "signing_keys" { cloudfront_default_certificate = true } - # TODO - # logging_config { - # bucket = module.s3bucket_cf_logs.bucket_regional_domain_name - # include_cookies = false - # } + logging_config { + bucket = module.s3bucket_cf_logs.bucket_regional_domain_name + include_cookies = false + } origin { domain_name = module.s3bucket_public_keys.bucket_regional_domain_name diff --git a/infrastructure/terraform/modules/public-signing-keys/locals.tf b/infrastructure/terraform/modules/public-signing-keys/locals.tf index b7b8b8e1..5e50b64f 100644 --- a/infrastructure/terraform/modules/public-signing-keys/locals.tf +++ b/infrastructure/terraform/modules/public-signing-keys/locals.tf @@ -1,3 +1,3 @@ locals { - csi = "${var.csi}-${var.component}" + csi = "${var.csi}-${var.component}" } diff --git a/infrastructure/terraform/modules/public-signing-keys/module_s3bucket_cf_logs.tf b/infrastructure/terraform/modules/public-signing-keys/module_s3bucket_cf_logs.tf new file mode 100644 index 00000000..403bc547 --- /dev/null +++ b/infrastructure/terraform/modules/public-signing-keys/module_s3bucket_cf_logs.tf @@ -0,0 +1,171 @@ +module "s3bucket_cf_logs" { + source = "git::https://github.com/NHSDigital/nhs-notify-shared-modules.git//infrastructure/modules/s3bucket?ref=v1.0.9" + providers = { + aws = aws.us-east-1 + } + + name = "cf-logs" + + aws_account_id = var.aws_account_id + region = "us-east-1" + project = var.project + environment = var.environment + component = var.component + + acl = "private" + force_destroy = false + versioning = true + + object_ownership = "ObjectWriter" + + lifecycle_rules = [ + { + prefix = "" + enabled = true + + transition = [ + { + days = "90" + storage_class = "STANDARD_IA" + }, + { + days = "180" + storage_class = "GLACIER" + } + ] + + expiration = { + days = "365" + } + + + noncurrent_version_transition = [ + { + noncurrent_days = "30" + storage_class = "STANDARD_IA" + }, + { + noncurrent_days = "180" + storage_class = "GLACIER" + } + + ] + + noncurrent_version_expiration = { + noncurrent_days = "365" + } + + abort_incomplete_multipart_upload = { + days = "1" + } + } + ] + + policy_documents = [ + data.aws_iam_policy_document.s3bucket_cf_logs.json + ] + + public_access = { + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true + } + + default_tags = { + Name = "Lambda function artefact bucket" + } +} + +data "aws_iam_policy_document" "s3bucket_cf_logs" { + statement { + sid = "DontAllowNonSecureConnection" + effect = "Deny" + + actions = [ + "s3:*", + ] + + resources = [ + module.s3bucket_cf_logs.arn, + "${module.s3bucket_cf_logs.arn}/*", + ] + + principals { + type = "AWS" + + identifiers = [ + "*", + ] + } + + condition { + test = "Bool" + variable = "aws:SecureTransport" + + values = [ + "false", + ] + } + } + + statement { + effect = "Allow" + actions = ["s3:PutObject"] + resources = [ + "${module.s3bucket_cf_logs.arn}/*", + ] + + principals { + type = "Service" + identifiers = ["logging.s3.amazonaws.com"] + } + condition { + test = "StringEquals" + variable = "aws:SourceAccount" + values = [ + var.aws_account_id + ] + } + } + + statement { + sid = "AllowManagedAccountsToList" + effect = "Allow" + + actions = [ + "s3:ListBucket", + ] + + resources = [ + module.s3bucket_cf_logs.arn, + ] + + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${var.aws_account_id}:root" + ] + } + } + + statement { + sid = "AllowManagedAccountsToGet" + effect = "Allow" + + actions = [ + "s3:GetObject", + ] + + resources = [ + "${module.s3bucket_cf_logs.arn}/*", + ] + + principals { + type = "AWS" + identifiers = [ + "arn:aws:iam::${var.aws_account_id}:root" + ] + } + } +} diff --git a/infrastructure/terraform/modules/public-signing-keys/s3_bucket_public_signing_keys.tf b/infrastructure/terraform/modules/public-signing-keys/s3_bucket_public_signing_keys.tf index 71dff855..d778daa9 100644 --- a/infrastructure/terraform/modules/public-signing-keys/s3_bucket_public_signing_keys.tf +++ b/infrastructure/terraform/modules/public-signing-keys/s3_bucket_public_signing_keys.tf @@ -13,6 +13,10 @@ module "s3bucket_public_keys" { force_destroy = false versioning = true + bucket_logging_target = { + bucket = var.acct.s3_buckets["access_logs"]["id"] + } + lifecycle_rules = [ { enabled = true diff --git a/infrastructure/terraform/modules/public-signing-keys/variables.tf b/infrastructure/terraform/modules/public-signing-keys/variables.tf index 26e49ff3..00da2ef5 100644 --- a/infrastructure/terraform/modules/public-signing-keys/variables.tf +++ b/infrastructure/terraform/modules/public-signing-keys/variables.tf @@ -58,3 +58,14 @@ variable "waf_rate_limit_cdn" { description = "The rate limit is the maximum number of CDN requests from a single IP address that are allowed in a five-minute period" default = 20000 } + +variable "acct" { + type = object({ + s3_buckets = object({ + access_logs = optional(object({ + id = optional(string) + })) + }) + }) + description = "Simplified account level settings" +} From 55d053d60078ef07f037419ca893de261c9d8135 Mon Sep 17 00:00:00 2001 From: Mark Ramsden Date: Tue, 18 Mar 2025 11:41:42 +0000 Subject: [PATCH 3/4] CCM-8524: add public signing keys bucket and frontend --- .../app/s3_bucket_public_signing_keys.tf | 137 ------------------ 1 file changed, 137 deletions(-) delete mode 100644 infrastructure/terraform/components/app/s3_bucket_public_signing_keys.tf diff --git a/infrastructure/terraform/components/app/s3_bucket_public_signing_keys.tf b/infrastructure/terraform/components/app/s3_bucket_public_signing_keys.tf deleted file mode 100644 index 1762414b..00000000 --- a/infrastructure/terraform/components/app/s3_bucket_public_signing_keys.tf +++ /dev/null @@ -1,137 +0,0 @@ -module "s3bucket_signing_keys" { - source = "git::https://github.com/NHSDigital/nhs-notify-shared-modules.git//infrastructure/modules/s3bucket?ref=v1.0.9" - - name = "public-signing-keys" - - aws_account_id = var.aws_account_id - region = var.region - project = var.project - environment = var.environment - component = var.component - - acl = "public-read" - force_destroy = false - versioning = true - - lifecycle_rules = [ - { - enabled = true - - noncurrent_version_transition = [ - { - noncurrent_days = "30" - storage_class = "STANDARD_IA" - } - ] - - noncurrent_version_expiration = { - noncurrent_days = "90" - } - - abort_incomplete_multipart_upload = { - days = "1" - } - } - ] - - policy_documents = [ - data.aws_iam_policy_document.s3bucket_signing_keys.json - ] - - public_access = { - block_public_acls = true - block_public_policy = true - ignore_public_acls = true - restrict_public_buckets = true - } - - - default_tags = { - Name = "Public signing keys for federated auth suppliers" - } -} - -data "aws_iam_policy_document" "s3bucket_signing_keys" { - statement { - sid = "DontAllowNonSecureConnection" - effect = "Deny" - - actions = [ - "s3:*", - ] - - resources = [ - module.s3bucket_signing_keys.arn, - "${module.s3bucket_signing_keys.arn}/*", - ] - - principals { - type = "AWS" - - identifiers = [ - "*", - ] - } - - condition { - test = "Bool" - variable = "aws:SecureTransport" - - values = [ - "false", - ] - } - } - - statement { - sid = "AllowManagedAccountsToList" - effect = "Allow" - - actions = [ - "s3:ListBucket", - ] - - resources = [ - module.s3bucket_signing_keys.arn, - ] - - principals { - type = "AWS" - identifiers = [ - "arn:aws:iam::${var.aws_account_id}:root" - ] - } - } - - statement { - sid = "AllowManagedAccountsToGet" - effect = "Allow" - - actions = [ - "s3:GetObject", - ] - - resources = [ - "${module.s3bucket_signing_keys.arn}/*", - ] - - principals { - type = "AWS" - identifiers = [ - "arn:aws:iam::${var.aws_account_id}:root" - ] - } - } -} - -resource "aws_s3_bucket_cors_configuration" "public_signing_keys" { - bucket = module.s3bucket_signing_keys.bucket - - cors_rule { - allowed_headers = [] - allowed_methods = ["GET"] - allowed_origins = ["*"] - expose_headers = ["ETag"] - max_age_seconds = 300 - } -} From 6b168803405daf853124073d468b3ea696817df5 Mon Sep 17 00:00:00 2001 From: Mark Ramsden Date: Tue, 18 Mar 2025 11:53:24 +0000 Subject: [PATCH 4/4] CCM-8524: add public signing keys bucket and frontend --- .../s3_bucket_policy_public_signing_keys.tf | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/infrastructure/terraform/modules/public-signing-keys/s3_bucket_policy_public_signing_keys.tf b/infrastructure/terraform/modules/public-signing-keys/s3_bucket_policy_public_signing_keys.tf index 2e570451..5b98fc3f 100644 --- a/infrastructure/terraform/modules/public-signing-keys/s3_bucket_policy_public_signing_keys.tf +++ b/infrastructure/terraform/modules/public-signing-keys/s3_bucket_policy_public_signing_keys.tf @@ -1,53 +1,53 @@ resource "aws_s3_bucket_policy" "public_signing_keys" { - bucket = module.s3bucket_public_keys.id - policy = data.aws_iam_policy_document.bucket_policy_public_signing_keys.json + bucket = module.s3bucket_public_keys.id + policy = data.aws_iam_policy_document.bucket_policy_public_signing_keys.json +} + +data "aws_iam_policy_document" "bucket_policy_public_signing_keys" { + statement { + actions = ["s3:GetObject"] + resources = [ + "${module.s3bucket_public_keys.arn}/*" + ] + + principals { + type = "AWS" + identifiers = ["arn:aws:iam::${var.aws_account_id}:root", aws_cloudfront_origin_access_identity.signing_keys.iam_arn] + } } - - data "aws_iam_policy_document" "bucket_policy_public_signing_keys" { - statement { - actions = ["s3:GetObject"] - resources = [ - "${module.s3bucket_public_keys.arn}/*" - ] - - principals { - type = "AWS" - identifiers = ["arn:aws:iam::${var.aws_account_id}:root", aws_cloudfront_origin_access_identity.signing_keys.iam_arn] - } + + statement { + actions = ["s3:ListBucket"] + resources = [ + module.s3bucket_public_keys.arn + ] + + principals { + type = "AWS" + identifiers = ["arn:aws:iam::${var.aws_account_id}:root", aws_cloudfront_origin_access_identity.signing_keys.iam_arn] } - - statement { - actions = ["s3:ListBucket"] - resources = [ - module.s3bucket_public_keys.arn - ] - - principals { - type = "AWS" - identifiers = ["arn:aws:iam::${var.aws_account_id}:root", aws_cloudfront_origin_access_identity.signing_keys.iam_arn] - } + } + + statement { + effect = "Deny" + actions = ["s3:*"] + resources = [ + module.s3bucket_public_keys.arn, + "${module.s3bucket_public_keys.arn}/*", + ] + + principals { + type = "AWS" + identifiers = ["*"] } - - statement { - effect = "Deny" - actions = ["s3:*"] - resources = [ - module.s3bucket_public_keys.arn, - "${module.s3bucket_public_keys.arn}/*", + + condition { + test = "Bool" + variable = "aws:SecureTransport" + values = [ + false ] - - principals { - type = "AWS" - identifiers = ["*"] - } - - condition { - test = "Bool" - variable = "aws:SecureTransport" - values = [ - false - ] - } } } - \ No newline at end of file +} +