From 299956b4876ecbfdea83abbd9f280a3837a7e53a Mon Sep 17 00:00:00 2001 From: TurboNHS Date: Wed, 22 Apr 2026 15:24:10 +0100 Subject: [PATCH 1/6] Allow to include environment name in resource names. If we're going to be able to have all resources for multiple environments in one account (PR pending), we must make sure that all resources have the environment name in them, so they can be separated. --- modules/aws-backup-source/backup_report_plan.tf | 8 ++++---- modules/aws-backup-source/backup_restore_testing.tf | 2 +- modules/aws-backup-source/iam.tf | 2 +- modules/aws-backup-source/kms.tf | 2 +- modules/aws-backup-source/locals.tf | 2 +- modules/aws-backup-source/variables.tf | 6 ++++++ 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/modules/aws-backup-source/backup_report_plan.tf b/modules/aws-backup-source/backup_report_plan.tf index cdb1d1c..a736884 100644 --- a/modules/aws-backup-source/backup_report_plan.tf +++ b/modules/aws-backup-source/backup_report_plan.tf @@ -1,6 +1,6 @@ # Create the reports resource "aws_backup_report_plan" "backup_jobs" { - name = var.name_prefix != null ? "${var.name_prefix}_backup_jobs" : "backup_jobs" + name = var.name_prefix != null ? "${replace(local.resource_name_prefix, "-", "_")}_backup_jobs" : "backup_jobs" description = "Report for showing whether backups ran successfully in the last 24 hours" report_delivery_channel { @@ -18,7 +18,7 @@ resource "aws_backup_report_plan" "backup_jobs" { # Create the restore testing completion reports resource "aws_backup_report_plan" "backup_restore_testing_jobs" { - name = var.name_prefix != null ? "${var.name_prefix}_backup_restore_testing_jobs" : "backup_restore_testing_jobs" + name = var.name_prefix != null ? "${replace(local.resource_name_prefix, "-", "_")}_backup_restore_testing_jobs" : "backup_restore_testing_jobs" description = "Report for showing whether backup restore test ran successfully in the last 24 hours" report_delivery_channel { @@ -35,7 +35,7 @@ resource "aws_backup_report_plan" "backup_restore_testing_jobs" { } resource "aws_backup_report_plan" "resource_compliance" { - name = var.name_prefix != null ? "${var.name_prefix}_resource_compliance" : "resource_compliance" + name = var.name_prefix != null ? "${replace(local.resource_name_prefix, "-", "_")}_resource_compliance" : "resource_compliance" description = "Report for showing whether resources are compliant with the framework" report_delivery_channel { @@ -55,7 +55,7 @@ resource "aws_backup_report_plan" "resource_compliance" { resource "aws_backup_report_plan" "copy_jobs" { count = var.backup_copy_vault_arn != "" && var.backup_copy_vault_account_id != "" ? 1 : 0 - name = var.name_prefix != null ? "${var.name_prefix}_copy_jobs" : "copy_jobs" + name = var.name_prefix != null ? "${replace(local.resource_name_prefix, "-", "_")}_copy_jobs" : "copy_jobs" description = "Report for showing whether copies ran successfully in the last 24 hours" report_delivery_channel { diff --git a/modules/aws-backup-source/backup_restore_testing.tf b/modules/aws-backup-source/backup_restore_testing.tf index b6389fc..0c64c08 100644 --- a/modules/aws-backup-source/backup_restore_testing.tf +++ b/modules/aws-backup-source/backup_restore_testing.tf @@ -1,5 +1,5 @@ resource "awscc_backup_restore_testing_plan" "backup_restore_testing_plan" { - restore_testing_plan_name = var.name_prefix != null ? "${var.name_prefix}_backup_restore_testing_plan" : "backup_restore_testing_plan" + restore_testing_plan_name = var.name_prefix != null ? "${replace(local.resource_name_prefix, "-", "_")}_backup_restore_testing_plan" : "backup_restore_testing_plan" schedule_expression = var.restore_testing_plan_scheduled_expression start_window_hours = var.restore_testing_plan_start_window recovery_point_selection = { diff --git a/modules/aws-backup-source/iam.tf b/modules/aws-backup-source/iam.tf index 3b81513..384f746 100644 --- a/modules/aws-backup-source/iam.tf +++ b/modules/aws-backup-source/iam.tf @@ -12,7 +12,7 @@ data "aws_iam_policy_document" "assume_role" { } resource "aws_iam_role" "backup" { - name = "${var.project_name}BackupRole" + name = "${var.include_environment_in_resource_names ? "${var.project_name}-${var.environment_name}" : var.project_name}BackupRole" assume_role_policy = data.aws_iam_policy_document.assume_role.json permissions_boundary = length(var.iam_role_permissions_boundary) > 0 ? var.iam_role_permissions_boundary : null } diff --git a/modules/aws-backup-source/kms.tf b/modules/aws-backup-source/kms.tf index e8a07a2..a36e37a 100644 --- a/modules/aws-backup-source/kms.tf +++ b/modules/aws-backup-source/kms.tf @@ -6,7 +6,7 @@ resource "aws_kms_key" "aws_backup_key" { } resource "aws_kms_alias" "backup_key" { - name = var.name_prefix != null ? "alias/${var.name_prefix}/backup-key" : "alias/${var.environment_name}/backup-key" + name = var.name_prefix != null ? "alias/${var.include_environment_in_resource_names ? "${local.resource_name_prefix}" : var.name_prefix}/backup-key" : "alias/${var.environment_name}/backup-key" target_key_id = aws_kms_key.aws_backup_key.key_id } diff --git a/modules/aws-backup-source/locals.tf b/modules/aws-backup-source/locals.tf index 39d37d0..a55be65 100644 --- a/modules/aws-backup-source/locals.tf +++ b/modules/aws-backup-source/locals.tf @@ -1,5 +1,5 @@ locals { - resource_name_prefix = var.name_prefix != null ? var.name_prefix : "${data.aws_region.current.id}-${data.aws_caller_identity.current.account_id}-backup" + resource_name_prefix = var.name_prefix != null ? (var.include_environment_in_resource_names ? "${var.name_prefix}-${var.environment_name}" : var.name_prefix) : (var.include_environment_in_resource_names ? "${data.aws_region.current.id}-${data.aws_caller_identity.current.account_id}-${var.environment_name}-backup" : "${data.aws_region.current.id}-${data.aws_caller_identity.current.account_id}-backup") selection_tag_value_null_checked = (var.backup_plan_config.selection_tag_value == null) ? "True" : var.backup_plan_config.selection_tag_value selection_tag_value_dynamodb_null_checked = (var.backup_plan_config_dynamodb.selection_tag_value == null) ? "True" : var.backup_plan_config_dynamodb.selection_tag_value selection_tags_null_checked = (var.backup_plan_config.selection_tags == null) ? [{ "key" : var.backup_plan_config.selection_tag, "value" : local.selection_tag_value_null_checked }] : var.backup_plan_config.selection_tags diff --git a/modules/aws-backup-source/variables.tf b/modules/aws-backup-source/variables.tf index d99aa10..7b0fba0 100644 --- a/modules/aws-backup-source/variables.tf +++ b/modules/aws-backup-source/variables.tf @@ -520,3 +520,9 @@ variable "lambda_restore_to_s3_max_wait_minutes" { type = number default = 5 } + +variable "include_environment_in_resource_names" { + description = "Should the environment name be included in resource names. Required for 'all resources in the same account'" + type = bool + default = false +} From af74023e2132e9a450fa63d93972b432e1d62871 Mon Sep 17 00:00:00 2001 From: TurboNHS Date: Thu, 23 Apr 2026 15:50:30 +0100 Subject: [PATCH 2/6] Allow creating resources for multiple environments in the same account. The AWSBackup/Framework must be unique in the account. As in, it's not possible to create the framework with the same rules etc. Instead, we "import" the "base" framework, and use that where needed. --- modules/aws-backup-source/backup_framework.tf | 43 +++++++++++++++++-- modules/aws-backup-source/locals.tf | 22 ++++++---- modules/aws-backup-source/variables.tf | 10 +++++ 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/modules/aws-backup-source/backup_framework.tf b/modules/aws-backup-source/backup_framework.tf index 19a2803..7435777 100644 --- a/modules/aws-backup-source/backup_framework.tf +++ b/modules/aws-backup-source/backup_framework.tf @@ -1,4 +1,16 @@ +# There can be only one [framework with x controls in one account]! +# +# For the frameworks of the other envs in the account, we "import" +# them using a `data` record, looking for the environment name set +# in the `resources_in_same_account` variable. + +data "aws_backup_framework" "main" { + count = var.backup_plan_config.enable && var.resources_in_same_account != "" ? 1 : 0 + name = replace("${var.name_prefix}-${var.resources_in_same_account}-framework", "-", "_") +} resource "aws_backup_framework" "main" { + count = var.backup_plan_config.enable && var.resources_in_same_account == "" ? 1 : 0 + # must be underscores instead of dashes name = replace("${local.resource_name_prefix}-framework", "-", "_") description = "${var.project_name} Backup Framework" @@ -131,8 +143,12 @@ resource "aws_backup_framework" "main" { } } +data "aws_backup_framework" "dynamodb" { + count = var.backup_plan_config_dynamodb.enable && var.resources_in_same_account != "" ? 1 : 0 + name = replace("${var.name_prefix}-${var.resources_in_same_account}-dynamodb-framework", "-", "_") +} resource "aws_backup_framework" "dynamodb" { - count = var.backup_plan_config_dynamodb.enable ? 1 : 0 + count = var.backup_plan_config_dynamodb.enable && var.resources_in_same_account == "" ? 1 : 0 # must be underscores instead of dashes name = replace("${local.resource_name_prefix}-dynamodb-framework", "-", "_") description = "${var.project_name} DynamoDB Backup Framework" @@ -172,8 +188,12 @@ resource "aws_backup_framework" "dynamodb" { } } +data "aws_backup_framework" "ebsvol" { + count = var.backup_plan_config_ebsvol.enable && var.resources_in_same_account != "" ? 1 : 0 + name = replace("${var.name_prefix}-${var.resources_in_same_account}-ebsvol-framework", "-", "_") +} resource "aws_backup_framework" "ebsvol" { - count = var.backup_plan_config_ebsvol.enable ? 1 : 0 + count = var.backup_plan_config_ebsvol.enable && var.resources_in_same_account == "" ? 1 : 0 # must be underscores instead of dashes name = replace("${local.resource_name_prefix}-ebsvol-framework", "-", "_") description = "${var.project_name} EBS Backup Framework" @@ -213,8 +233,12 @@ resource "aws_backup_framework" "ebsvol" { } } +data "aws_backup_framework" "aurora" { + count = var.backup_plan_config_aurora.enable && var.resources_in_same_account != "" ? 1 : 0 + name = replace("${var.name_prefix}-${var.resources_in_same_account}-aurora-framework", "-", "_") +} resource "aws_backup_framework" "aurora" { - count = var.backup_plan_config_aurora.enable ? 1 : 0 + count = var.backup_plan_config_aurora.enable && var.resources_in_same_account == "" ? 1 : 0 # must be underscores instead of dashes name = replace("${local.resource_name_prefix}-aurora-framework", "-", "_") description = "${var.project_name} Aurora Backup Framework" @@ -253,8 +277,12 @@ resource "aws_backup_framework" "aurora" { } } +data "aws_backup_framework" "parameter_store" { + count = var.backup_plan_config_parameter_store.enable && var.resources_in_same_account != "" ? 1 : 0 + name = replace("${var.name_prefix}-${var.resources_in_same_account}-parameter-store-framework", "-", "_") +} resource "aws_backup_framework" "parameter_store" { - count = var.backup_plan_config_parameter_store.enable ? 1 : 0 + count = var.backup_plan_config_parameter_store.enable && var.resources_in_same_account == "" ? 1 : 0 # must be underscores instead of dashes name = replace("${local.resource_name_prefix}-parameter-store-framework", "-", "_") description = "${var.project_name} Parameter Store Backup Framework" @@ -292,3 +320,10 @@ resource "aws_backup_framework" "parameter_store" { } } } + +# ----- + +moved { + from = aws_backup_framework.main + to = aws_backup_framework.main[0] +} diff --git a/modules/aws-backup-source/locals.tf b/modules/aws-backup-source/locals.tf index a55be65..750d779 100644 --- a/modules/aws-backup-source/locals.tf +++ b/modules/aws-backup-source/locals.tf @@ -1,21 +1,27 @@ locals { resource_name_prefix = var.name_prefix != null ? (var.include_environment_in_resource_names ? "${var.name_prefix}-${var.environment_name}" : var.name_prefix) : (var.include_environment_in_resource_names ? "${data.aws_region.current.id}-${data.aws_caller_identity.current.account_id}-${var.environment_name}-backup" : "${data.aws_region.current.id}-${data.aws_caller_identity.current.account_id}-backup") + selection_tag_value_null_checked = (var.backup_plan_config.selection_tag_value == null) ? "True" : var.backup_plan_config.selection_tag_value + selection_tag_value_aurora_null_checked = (var.backup_plan_config_aurora.selection_tag_value == null) ? "True" : var.backup_plan_config_aurora.selection_tag_value selection_tag_value_dynamodb_null_checked = (var.backup_plan_config_dynamodb.selection_tag_value == null) ? "True" : var.backup_plan_config_dynamodb.selection_tag_value + selection_tag_value_ebsvol_null_checked = (var.backup_plan_config_ebsvol.selection_tag_value == null) ? "True" : var.backup_plan_config_ebsvol.selection_tag_value + selection_tag_value_parameter_store_null_checked = (var.backup_plan_config_parameter_store.selection_tag_value == null) ? "True" : var.backup_plan_config_parameter_store.selection_tag_value + selection_tags_null_checked = (var.backup_plan_config.selection_tags == null) ? [{ "key" : var.backup_plan_config.selection_tag, "value" : local.selection_tag_value_null_checked }] : var.backup_plan_config.selection_tags + selection_tags_aurora_null_checked = (var.backup_plan_config_aurora.selection_tags == null) ? [{ "key" : var.backup_plan_config_aurora.selection_tag, "value" : local.selection_tag_value_aurora_null_checked }] : var.backup_plan_config_aurora.selection_tags selection_tags_dynamodb_null_checked = (var.backup_plan_config_dynamodb.selection_tags == null) ? [{ "key" : var.backup_plan_config_dynamodb.selection_tag, "value" : local.selection_tag_value_dynamodb_null_checked }] : var.backup_plan_config_dynamodb.selection_tags - selection_tag_value_ebsvol_null_checked = (var.backup_plan_config_ebsvol.selection_tag_value == null) ? "True" : var.backup_plan_config_ebsvol.selection_tag_value selection_tags_ebsvol_null_checked = (var.backup_plan_config_ebsvol.selection_tags == null) ? [{ "key" : var.backup_plan_config_ebsvol.selection_tag, "value" : local.selection_tag_value_ebsvol_null_checked }] : var.backup_plan_config_ebsvol.selection_tags - selection_tag_value_parameter_store_null_checked = (var.backup_plan_config_parameter_store.selection_tag_value == null) ? "True" : var.backup_plan_config_parameter_store.selection_tag_value selection_tags_parameter_store_null_checked = (var.backup_plan_config_parameter_store.selection_tags == null) ? [{ "key" : var.backup_plan_config_parameter_store.selection_tag, "value" : local.selection_tag_value_parameter_store_null_checked }] : var.backup_plan_config_parameter_store.selection_tags - framework_arn_list = flatten(concat( - [aws_backup_framework.main.arn], - var.backup_plan_config_ebsvol.enable ? [aws_backup_framework.ebsvol[0].arn] : [], - var.backup_plan_config_dynamodb.enable ? [aws_backup_framework.dynamodb[0].arn] : [], - var.backup_plan_config_aurora.enable ? [aws_backup_framework.aurora[0].arn] : [], - var.backup_plan_config_parameter_store.enable ? [aws_backup_framework.parameter_store[0].arn] : [] + framework_arn_list = flatten(concat( + var.backup_plan_config.enable ? [var.resources_in_same_account == "" ? aws_backup_framework.main[0].arn : aws_backup_framework.main[0].arn] : [], + var.backup_plan_config_ebsvol.enable ? [var.resources_in_same_account == "" ? aws_backup_framework.ebsvol[0].arn : data.aws_backup_framework.ebsvol[0].arn] : [], + var.backup_plan_config_dynamodb.enable ? [var.resources_in_same_account == "" ? aws_backup_framework.dynamodb[0].arn : data.aws_backup_framework.dynamodb[0].arn] : [], + var.backup_plan_config_aurora.enable ? [var.resources_in_same_account == "" ? aws_backup_framework.aurora[0].arn : data.aws_backup_framework.aurora[0].arn] : [], + var.backup_plan_config_parameter_store.enable ? [var.resources_in_same_account == "" ? aws_backup_framework.parameter_store[0].arn : data.aws_backup_framework.parameter_store[0].arn] : [] )) + aurora_overrides = var.backup_plan_config_aurora.restore_testing_overrides == null ? null : jsondecode(var.backup_plan_config_aurora.restore_testing_overrides) + terraform_role_arns = length(var.terraform_role_arns) > 0 ? var.terraform_role_arns : [var.terraform_role_arn] } diff --git a/modules/aws-backup-source/variables.tf b/modules/aws-backup-source/variables.tf index 7b0fba0..1f8efa6 100644 --- a/modules/aws-backup-source/variables.tf +++ b/modules/aws-backup-source/variables.tf @@ -526,3 +526,13 @@ variable "include_environment_in_resource_names" { type = bool default = false } + +# Plans etc are _account_ specific, not _environment_ specific, so we only want to create some resources +# once. As in, when this is `""` (empty string). For additional envs in the account, set this to the environment +# where the "base" resources are (for example `dev`). +# NOTE: Require `include_environment_in_resource_names` set to `true` for this to work! +variable "resources_in_same_account" { + description = "Should all resources be created in the same account. Set to 'true' if base resources already exists in the account, and they should be reused." + type = string + default = "" +} From f71f2b4f582e9a26e8b13a7c30c7f1c631de96cb Mon Sep 17 00:00:00 2001 From: TurboNHS Date: Thu, 23 Apr 2026 15:51:51 +0100 Subject: [PATCH 3/6] Improve the selection process by utilising the `selection_tags` variable. It was included in the main variable for all types, but Aurora. With a value of: ``` module "source" { [...] backup_plan_config_XXX = { [...] "selection_tags" : [ { "key": "Environment" "value": var.environment }, { "key": "Stack" "value": "rds-cluster" }, { "key": "ManagedBy" "value": "Terraform" } ] [...] } [...] } ``` We narrow down the list of resources that the plan etc will find to only that of the environment (tag `Environment=dev` for example). Before this commit, it would only look for `selection_tag=selection_tag_value`, which would catch all resources with that tag set. One could set different tags for different environments, but that would be confusing in the long run. Better to have the same tag for the backup, but specify the environment as well. --- modules/aws-backup-source/backup_plan.tf | 44 +++++++++++++++--- .../backup_restore_testing.tf | 45 ++++++++++++++----- modules/aws-backup-source/variables.tf | 10 ++++- 3 files changed, 81 insertions(+), 18 deletions(-) diff --git a/modules/aws-backup-source/backup_plan.tf b/modules/aws-backup-source/backup_plan.tf index 4e7a671..3a7755e 100644 --- a/modules/aws-backup-source/backup_plan.tf +++ b/modules/aws-backup-source/backup_plan.tf @@ -168,7 +168,12 @@ resource "aws_backup_selection" "default" { } condition { dynamic "string_equals" { - for_each = local.selection_tags_null_checked + for_each = concat(local.selection_tags_null_checked, [ + { + "key": var.backup_plan_config.selection_tag, + "value": var.backup_plan_config.selection_tag_value != null ? var.backup_plan_config.selection_tag_value : "True" + } + ]) content { key = (try(string_equals.value.key, null) == null) ? null : "aws:ResourceTag/${string_equals.value.key}" value = try(string_equals.value.value, null) @@ -190,7 +195,12 @@ resource "aws_backup_selection" "dynamodb" { } condition { dynamic "string_equals" { - for_each = local.selection_tags_dynamodb_null_checked + for_each = concat(local.selection_tags_dynamodb_null_checked, [ + { + "key": var.backup_plan_config_dynamodb.selection_tag, + "value": var.backup_plan_config_dynamodb.selection_tag_value != null ? var.backup_plan_config_dynamodb.selection_tag_value : "True" + } + ]) content { key = (try(string_equals.value.key, null) == null) ? null : "aws:ResourceTag/${string_equals.value.key}" value = try(string_equals.value.value, null) @@ -212,7 +222,12 @@ resource "aws_backup_selection" "ebsvol" { } condition { dynamic "string_equals" { - for_each = local.selection_tags_ebsvol_null_checked + for_each = concat(local.selection_tags_ebsvol_null_checked, [ + { + "key": var.backup_plan_config_ebsvol.selection_tag, + "value": var.backup_plan_config_ebsvol.selection_tag_value != null ? var.backup_plan_config_ebsvol.selection_tag_value : "True" + } + ]) content { key = (try(string_equals.value.key, null) == null) ? null : "aws:ResourceTag/${string_equals.value.key}" value = try(string_equals.value.value, null) @@ -230,7 +245,21 @@ resource "aws_backup_selection" "aurora" { selection_tag { key = var.backup_plan_config_aurora.selection_tag type = "STRINGEQUALS" - value = "True" + value = (var.backup_plan_config_aurora.selection_tag_value == null) ? "True" : var.backup_plan_config_aurora.selection_tag_value + } + condition { + dynamic "string_equals" { + for_each = concat(local.selection_tags_aurora_null_checked, [ + { + "key": var.backup_plan_config_aurora.selection_tag, + "value": var.backup_plan_config_aurora.selection_tag_value != null ? var.backup_plan_config_aurora.selection_tag_value : "True" + } + ]) + content { + key = (try(string_equals.value.key, null) == null) ? null : "aws:ResourceTag/${string_equals.value.key}" + value = try(string_equals.value.value, null) + } + } } } @@ -247,7 +276,12 @@ resource "aws_backup_selection" "parameter_store" { } condition { dynamic "string_equals" { - for_each = local.selection_tags_parameter_store_null_checked + for_each = concat(local.selection_tags_parameter_store_null_checked, [ + { + "key": var.backup_plan_config_parameter_store.selection_tag, + "value": var.backup_plan_config_parameter_store.selection_tag_value != null ? var.backup_plan_config_parameter_store.selection_tag_value : "True" + } + ]) content { key = (try(string_equals.value.key, null) == null) ? null : "aws:ResourceTag/${string_equals.value.key}" value = try(string_equals.value.value, null) diff --git a/modules/aws-backup-source/backup_restore_testing.tf b/modules/aws-backup-source/backup_restore_testing.tf index 0c64c08..7ddba1c 100644 --- a/modules/aws-backup-source/backup_restore_testing.tf +++ b/modules/aws-backup-source/backup_restore_testing.tf @@ -18,10 +18,17 @@ resource "awscc_backup_restore_testing_selection" "backup_restore_testing_select restore_testing_selection_name = "backup_restore_testing_selection_dynamodb" protected_resource_arns = ["*"] protected_resource_conditions = { - string_equals = [{ - key = "aws:ResourceTag/${var.backup_plan_config_dynamodb.selection_tag}" - value = "True" - }] + string_equals = concat([ + { + key = "aws:ResourceTag/${var.backup_plan_config_dynamodb.selection_tag}" + value = "True" + } + ], [ + for tag in local.selection_tags_dynamodb_null_checked: { + key = "aws:ResourceTag/${tag.key}", + value = tag.value + } + ]) } } @@ -34,10 +41,17 @@ resource "awscc_backup_restore_testing_selection" "backup_restore_testing_select restore_testing_selection_name = "backup_restore_testing_selection_ebsvol" protected_resource_arns = ["*"] protected_resource_conditions = { - string_equals = [{ - key = "aws:ResourceTag/${var.backup_plan_config_ebsvol.selection_tag}" - value = "True" - }] + string_equals = concat([ + { + key = "aws:ResourceTag/${var.backup_plan_config_ebsvol.selection_tag}" + value = "True" + } + ], [ + for tag in local.selection_tags_ebsvol_null_checked: { + key = "aws:ResourceTag/${tag.key}", + value = tag.value + } + ]) } } @@ -49,10 +63,17 @@ resource "awscc_backup_restore_testing_selection" "backup_restore_testing_select restore_testing_selection_name = "backup_restore_testing_selection_aurora" protected_resource_arns = ["*"] protected_resource_conditions = { - string_equals = [{ - key = "aws:ResourceTag/${var.backup_plan_config_aurora.selection_tag}" - value = "True" - }] + string_equals = concat([ + { + key = "aws:ResourceTag/${var.backup_plan_config_aurora.selection_tag}" + value = "True" + } + ], [ + for tag in local.selection_tags_aurora_null_checked: { + key = "aws:ResourceTag/${tag.key}", + value = tag.value + } + ]) } restore_metadata_overrides = local.aurora_overrides } diff --git a/modules/aws-backup-source/variables.tf b/modules/aws-backup-source/variables.tf index 1f8efa6..c34c0bd 100644 --- a/modules/aws-backup-source/variables.tf +++ b/modules/aws-backup-source/variables.tf @@ -265,6 +265,7 @@ variable "backup_plan_config_ebsvol" { default = { enable = true selection_tag = "BackupEBSVol" + selection_tag_value = "True" compliance_resource_types = ["EBS"] rules = [ { @@ -306,7 +307,12 @@ variable "backup_plan_config_aurora" { description = "Configuration for backup plans with aurora" type = object({ enable = bool - selection_tag = string + selection_tag = optional(string) + selection_tag_value = optional(string) + selection_tags = optional(list(object({ + key = optional(string) + value = optional(string) + }))) compliance_resource_types = list(string) restore_testing_overrides = optional(string) rules = optional(list(object({ @@ -325,6 +331,8 @@ variable "backup_plan_config_aurora" { default = { enable = true selection_tag = "BackupAurora" + selection_tag_value = "True" + selection_tags = [] compliance_resource_types = ["Aurora"] rules = [ { From c1626bd68e35e043affe48036ae7ca41e40e1a79 Mon Sep 17 00:00:00 2001 From: TurboNHS Date: Fri, 24 Apr 2026 11:53:22 +0100 Subject: [PATCH 4/6] Allow to include environment name in resource names. --- modules/aws-backup-destination/backup.tf | 13 +++++++-- .../backup_vault_lock.tf | 5 ++-- .../backup_vault_policy.tf | 19 ++++++++++-- modules/aws-backup-destination/iam.tf | 4 +-- .../parameter_store_kms.tf | 29 +++++++++++++++++-- modules/aws-backup-destination/variables.tf | 9 ++++++ 6 files changed, 68 insertions(+), 11 deletions(-) diff --git a/modules/aws-backup-destination/backup.tf b/modules/aws-backup-destination/backup.tf index 2cdad37..e33ae48 100644 --- a/modules/aws-backup-destination/backup.tf +++ b/modules/aws-backup-destination/backup.tf @@ -1,13 +1,22 @@ resource "aws_backup_vault" "vault" { + count = var.resources_in_same_account ? 1 : 0 + name = var.name_prefix != null ? "${var.name_prefix}-backup-vault" : "${var.source_account_name}-backup-vault" kms_key_arn = var.kms_key } output "vault_arn" { - value = aws_backup_vault.vault.arn + value = var.resources_in_same_account ? aws_backup_vault.vault[0].arn : null } output "vault_name" { description = "The name of the backup vault." - value = aws_backup_vault.vault.name + value = var.resources_in_same_account ? aws_backup_vault.vault[0].name : null +} + +# ----- + +moved { + from = aws_backup_vault.vault + to = aws_backup_vault.vault[0] } diff --git a/modules/aws-backup-destination/backup_vault_lock.tf b/modules/aws-backup-destination/backup_vault_lock.tf index e1a3178..ce3e75d 100644 --- a/modules/aws-backup-destination/backup_vault_lock.tf +++ b/modules/aws-backup-destination/backup_vault_lock.tf @@ -1,6 +1,7 @@ resource "aws_backup_vault_lock_configuration" "vault_lock" { - count = var.enable_vault_protection ? 1 : 0 - backup_vault_name = aws_backup_vault.vault.name + count = var.enable_vault_protection && var.resources_in_same_account ? 1 : 0 + + backup_vault_name = aws_backup_vault.vault[0].name changeable_for_days = var.vault_lock_type == "compliance" ? var.changeable_for_days : null max_retention_days = var.vault_lock_max_retention_days min_retention_days = var.vault_lock_min_retention_days diff --git a/modules/aws-backup-destination/backup_vault_policy.tf b/modules/aws-backup-destination/backup_vault_policy.tf index 06e3468..9e94230 100644 --- a/modules/aws-backup-destination/backup_vault_policy.tf +++ b/modules/aws-backup-destination/backup_vault_policy.tf @@ -1,9 +1,12 @@ resource "aws_backup_vault_policy" "vault_policy" { - backup_vault_name = aws_backup_vault.vault.name - policy = data.aws_iam_policy_document.vault_policy.json + count = var.resources_in_same_account ? 1 : 0 + + backup_vault_name = aws_backup_vault.vault[0].name + policy = data.aws_iam_policy_document.vault_policy[0].json } data "aws_iam_policy_document" "vault_policy" { + count = var.resources_in_same_account ? 1 : 0 statement { sid = "AllowCopyToVault" @@ -66,3 +69,15 @@ data "aws_iam_policy_document" "vault_policy" { } } } + +# ----- + +moved { + from = aws_backup_vault_policy.vault_policy + to = aws_backup_vault_policy.vault_policy[0] +} + +moved { + from = data.aws_iam_policy_document.vault_policy + to = data.aws_iam_policy_document.vault_policy[0] +} diff --git a/modules/aws-backup-destination/iam.tf b/modules/aws-backup-destination/iam.tf index c700e86..bcd7641 100644 --- a/modules/aws-backup-destination/iam.tf +++ b/modules/aws-backup-destination/iam.tf @@ -42,7 +42,7 @@ resource "aws_iam_role" "copy_recovery_point" { } data "aws_iam_policy_document" "copy_recovery_point_permissions" { - count = var.enable_cross_account_vault_access ? 1 : 0 + count = var.enable_cross_account_vault_access && var.resources_in_same_account ? 1 : 0 # Start copy job (resource-level supports recoveryPoint*) statement { @@ -71,7 +71,7 @@ data "aws_iam_policy_document" "copy_recovery_point_permissions" { ] resources = [ "arn:aws:backup:${var.region}:${var.account_id}:recovery-point:*", - "arn:aws:backup:${var.region}:${var.account_id}:backup-vault:${aws_backup_vault.vault.name}", + "arn:aws:backup:${var.region}:${var.account_id}:backup-vault:${aws_backup_vault.vault[0].name}", "arn:aws:backup:${var.region}:${var.source_account_id}:backup-vault:*" ] } diff --git a/modules/aws-backup-destination/parameter_store_kms.tf b/modules/aws-backup-destination/parameter_store_kms.tf index 374fda7..409a57f 100644 --- a/modules/aws-backup-destination/parameter_store_kms.tf +++ b/modules/aws-backup-destination/parameter_store_kms.tf @@ -1,4 +1,6 @@ data "aws_iam_policy_document" "kms_key_policy" { + count = var.resources_in_same_account ? 1 : 0 + statement { sid = "Enable IAM User Permissions" effect = "Allow" @@ -33,17 +35,38 @@ data "aws_iam_policy_document" "kms_key_policy" { } resource "aws_kms_key" "parameter_store_key" { + count = var.resources_in_same_account ? 1 : 0 + description = "KMS key for cross-account encryption of Parameter Store backups." deletion_window_in_days = 7 - policy = data.aws_iam_policy_document.kms_key_policy.json + policy = data.aws_iam_policy_document.kms_key_policy[0].json } resource "aws_kms_alias" "parameter_store_alias" { + count = var.resources_in_same_account ? 1 : 0 + name = "alias/parameter-store-backup-key" - target_key_id = aws_kms_key.parameter_store_key.key_id + target_key_id = aws_kms_key.parameter_store_key[0].key_id } output "parameter_store_kms_key_arn" { description = "The ARN of the KMS key created in the backup account." - value = aws_kms_key.parameter_store_key.arn + value = var.resources_in_same_account ? aws_kms_key.parameter_store_key[0].arn : null +} + +# ----- + +moved { + from = data.aws_iam_policy_document.kms_key_policy + to = data.aws_iam_policy_document.kms_key_policy[0] +} + +moved { + from = aws_kms_key.parameter_store_key + to = aws_kms_key.parameter_store_key[0] +} + +moved { + from = aws_kms_alias.parameter_store_alias + to = aws_kms_alias.parameter_store_alias[0] } diff --git a/modules/aws-backup-destination/variables.tf b/modules/aws-backup-destination/variables.tf index bbe68da..bdc6b8e 100644 --- a/modules/aws-backup-destination/variables.tf +++ b/modules/aws-backup-destination/variables.tf @@ -100,3 +100,12 @@ variable "enable_cross_account_vault_access" { type = bool default = false } + +# If we're building this for multiple environments in the same account, some things +# should not be created. Such as the vault! There can be only one - the environment +# vaults should all copy to this, main/backup/immutable vault. +variable "resources_in_same_account" { + description = "Should all resources be created in the same account. Set to 'true' if base resources already exists in the account, and they should be reused." + type = bool + default = false +} From 655393bacddf5225fa38199a184622dae9a7e2b3 Mon Sep 17 00:00:00 2001 From: TurboNHS Date: Mon, 27 Apr 2026 11:18:47 +0100 Subject: [PATCH 5/6] Run code through `terraform fmt`. --- modules/aws-backup-source/backup_framework.tf | 10 ++--- modules/aws-backup-source/backup_plan.tf | 40 +++++++++---------- modules/aws-backup-source/locals.tf | 14 +++---- modules/aws-backup-source/variables.tf | 6 +-- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/modules/aws-backup-source/backup_framework.tf b/modules/aws-backup-source/backup_framework.tf index 7435777..52a6ff0 100644 --- a/modules/aws-backup-source/backup_framework.tf +++ b/modules/aws-backup-source/backup_framework.tf @@ -6,7 +6,7 @@ data "aws_backup_framework" "main" { count = var.backup_plan_config.enable && var.resources_in_same_account != "" ? 1 : 0 - name = replace("${var.name_prefix}-${var.resources_in_same_account}-framework", "-", "_") + name = replace("${var.name_prefix}-${var.resources_in_same_account}-framework", "-", "_") } resource "aws_backup_framework" "main" { count = var.backup_plan_config.enable && var.resources_in_same_account == "" ? 1 : 0 @@ -145,7 +145,7 @@ resource "aws_backup_framework" "main" { data "aws_backup_framework" "dynamodb" { count = var.backup_plan_config_dynamodb.enable && var.resources_in_same_account != "" ? 1 : 0 - name = replace("${var.name_prefix}-${var.resources_in_same_account}-dynamodb-framework", "-", "_") + name = replace("${var.name_prefix}-${var.resources_in_same_account}-dynamodb-framework", "-", "_") } resource "aws_backup_framework" "dynamodb" { count = var.backup_plan_config_dynamodb.enable && var.resources_in_same_account == "" ? 1 : 0 @@ -190,7 +190,7 @@ resource "aws_backup_framework" "dynamodb" { data "aws_backup_framework" "ebsvol" { count = var.backup_plan_config_ebsvol.enable && var.resources_in_same_account != "" ? 1 : 0 - name = replace("${var.name_prefix}-${var.resources_in_same_account}-ebsvol-framework", "-", "_") + name = replace("${var.name_prefix}-${var.resources_in_same_account}-ebsvol-framework", "-", "_") } resource "aws_backup_framework" "ebsvol" { count = var.backup_plan_config_ebsvol.enable && var.resources_in_same_account == "" ? 1 : 0 @@ -235,7 +235,7 @@ resource "aws_backup_framework" "ebsvol" { data "aws_backup_framework" "aurora" { count = var.backup_plan_config_aurora.enable && var.resources_in_same_account != "" ? 1 : 0 - name = replace("${var.name_prefix}-${var.resources_in_same_account}-aurora-framework", "-", "_") + name = replace("${var.name_prefix}-${var.resources_in_same_account}-aurora-framework", "-", "_") } resource "aws_backup_framework" "aurora" { count = var.backup_plan_config_aurora.enable && var.resources_in_same_account == "" ? 1 : 0 @@ -279,7 +279,7 @@ resource "aws_backup_framework" "aurora" { data "aws_backup_framework" "parameter_store" { count = var.backup_plan_config_parameter_store.enable && var.resources_in_same_account != "" ? 1 : 0 - name = replace("${var.name_prefix}-${var.resources_in_same_account}-parameter-store-framework", "-", "_") + name = replace("${var.name_prefix}-${var.resources_in_same_account}-parameter-store-framework", "-", "_") } resource "aws_backup_framework" "parameter_store" { count = var.backup_plan_config_parameter_store.enable && var.resources_in_same_account == "" ? 1 : 0 diff --git a/modules/aws-backup-source/backup_plan.tf b/modules/aws-backup-source/backup_plan.tf index 3a7755e..e235608 100644 --- a/modules/aws-backup-source/backup_plan.tf +++ b/modules/aws-backup-source/backup_plan.tf @@ -169,10 +169,10 @@ resource "aws_backup_selection" "default" { condition { dynamic "string_equals" { for_each = concat(local.selection_tags_null_checked, [ - { - "key": var.backup_plan_config.selection_tag, - "value": var.backup_plan_config.selection_tag_value != null ? var.backup_plan_config.selection_tag_value : "True" - } + { + "key" : var.backup_plan_config.selection_tag, + "value" : var.backup_plan_config.selection_tag_value != null ? var.backup_plan_config.selection_tag_value : "True" + } ]) content { key = (try(string_equals.value.key, null) == null) ? null : "aws:ResourceTag/${string_equals.value.key}" @@ -196,10 +196,10 @@ resource "aws_backup_selection" "dynamodb" { condition { dynamic "string_equals" { for_each = concat(local.selection_tags_dynamodb_null_checked, [ - { - "key": var.backup_plan_config_dynamodb.selection_tag, - "value": var.backup_plan_config_dynamodb.selection_tag_value != null ? var.backup_plan_config_dynamodb.selection_tag_value : "True" - } + { + "key" : var.backup_plan_config_dynamodb.selection_tag, + "value" : var.backup_plan_config_dynamodb.selection_tag_value != null ? var.backup_plan_config_dynamodb.selection_tag_value : "True" + } ]) content { key = (try(string_equals.value.key, null) == null) ? null : "aws:ResourceTag/${string_equals.value.key}" @@ -223,10 +223,10 @@ resource "aws_backup_selection" "ebsvol" { condition { dynamic "string_equals" { for_each = concat(local.selection_tags_ebsvol_null_checked, [ - { - "key": var.backup_plan_config_ebsvol.selection_tag, - "value": var.backup_plan_config_ebsvol.selection_tag_value != null ? var.backup_plan_config_ebsvol.selection_tag_value : "True" - } + { + "key" : var.backup_plan_config_ebsvol.selection_tag, + "value" : var.backup_plan_config_ebsvol.selection_tag_value != null ? var.backup_plan_config_ebsvol.selection_tag_value : "True" + } ]) content { key = (try(string_equals.value.key, null) == null) ? null : "aws:ResourceTag/${string_equals.value.key}" @@ -250,10 +250,10 @@ resource "aws_backup_selection" "aurora" { condition { dynamic "string_equals" { for_each = concat(local.selection_tags_aurora_null_checked, [ - { - "key": var.backup_plan_config_aurora.selection_tag, - "value": var.backup_plan_config_aurora.selection_tag_value != null ? var.backup_plan_config_aurora.selection_tag_value : "True" - } + { + "key" : var.backup_plan_config_aurora.selection_tag, + "value" : var.backup_plan_config_aurora.selection_tag_value != null ? var.backup_plan_config_aurora.selection_tag_value : "True" + } ]) content { key = (try(string_equals.value.key, null) == null) ? null : "aws:ResourceTag/${string_equals.value.key}" @@ -277,10 +277,10 @@ resource "aws_backup_selection" "parameter_store" { condition { dynamic "string_equals" { for_each = concat(local.selection_tags_parameter_store_null_checked, [ - { - "key": var.backup_plan_config_parameter_store.selection_tag, - "value": var.backup_plan_config_parameter_store.selection_tag_value != null ? var.backup_plan_config_parameter_store.selection_tag_value : "True" - } + { + "key" : var.backup_plan_config_parameter_store.selection_tag, + "value" : var.backup_plan_config_parameter_store.selection_tag_value != null ? var.backup_plan_config_parameter_store.selection_tag_value : "True" + } ]) content { key = (try(string_equals.value.key, null) == null) ? null : "aws:ResourceTag/${string_equals.value.key}" diff --git a/modules/aws-backup-source/locals.tf b/modules/aws-backup-source/locals.tf index 750d779..48530fd 100644 --- a/modules/aws-backup-source/locals.tf +++ b/modules/aws-backup-source/locals.tf @@ -1,5 +1,5 @@ locals { - resource_name_prefix = var.name_prefix != null ? (var.include_environment_in_resource_names ? "${var.name_prefix}-${var.environment_name}" : var.name_prefix) : (var.include_environment_in_resource_names ? "${data.aws_region.current.id}-${data.aws_caller_identity.current.account_id}-${var.environment_name}-backup" : "${data.aws_region.current.id}-${data.aws_caller_identity.current.account_id}-backup") + resource_name_prefix = var.name_prefix != null ? (var.include_environment_in_resource_names ? "${var.name_prefix}-${var.environment_name}" : var.name_prefix) : (var.include_environment_in_resource_names ? "${data.aws_region.current.id}-${data.aws_caller_identity.current.account_id}-${var.environment_name}-backup" : "${data.aws_region.current.id}-${data.aws_caller_identity.current.account_id}-backup") selection_tag_value_null_checked = (var.backup_plan_config.selection_tag_value == null) ? "True" : var.backup_plan_config.selection_tag_value selection_tag_value_aurora_null_checked = (var.backup_plan_config_aurora.selection_tag_value == null) ? "True" : var.backup_plan_config_aurora.selection_tag_value @@ -7,11 +7,11 @@ locals { selection_tag_value_ebsvol_null_checked = (var.backup_plan_config_ebsvol.selection_tag_value == null) ? "True" : var.backup_plan_config_ebsvol.selection_tag_value selection_tag_value_parameter_store_null_checked = (var.backup_plan_config_parameter_store.selection_tag_value == null) ? "True" : var.backup_plan_config_parameter_store.selection_tag_value - selection_tags_null_checked = (var.backup_plan_config.selection_tags == null) ? [{ "key" : var.backup_plan_config.selection_tag, "value" : local.selection_tag_value_null_checked }] : var.backup_plan_config.selection_tags - selection_tags_aurora_null_checked = (var.backup_plan_config_aurora.selection_tags == null) ? [{ "key" : var.backup_plan_config_aurora.selection_tag, "value" : local.selection_tag_value_aurora_null_checked }] : var.backup_plan_config_aurora.selection_tags - selection_tags_dynamodb_null_checked = (var.backup_plan_config_dynamodb.selection_tags == null) ? [{ "key" : var.backup_plan_config_dynamodb.selection_tag, "value" : local.selection_tag_value_dynamodb_null_checked }] : var.backup_plan_config_dynamodb.selection_tags - selection_tags_ebsvol_null_checked = (var.backup_plan_config_ebsvol.selection_tags == null) ? [{ "key" : var.backup_plan_config_ebsvol.selection_tag, "value" : local.selection_tag_value_ebsvol_null_checked }] : var.backup_plan_config_ebsvol.selection_tags - selection_tags_parameter_store_null_checked = (var.backup_plan_config_parameter_store.selection_tags == null) ? [{ "key" : var.backup_plan_config_parameter_store.selection_tag, "value" : local.selection_tag_value_parameter_store_null_checked }] : var.backup_plan_config_parameter_store.selection_tags + selection_tags_null_checked = (var.backup_plan_config.selection_tags == null) ? [{ "key" : var.backup_plan_config.selection_tag, "value" : local.selection_tag_value_null_checked }] : var.backup_plan_config.selection_tags + selection_tags_aurora_null_checked = (var.backup_plan_config_aurora.selection_tags == null) ? [{ "key" : var.backup_plan_config_aurora.selection_tag, "value" : local.selection_tag_value_aurora_null_checked }] : var.backup_plan_config_aurora.selection_tags + selection_tags_dynamodb_null_checked = (var.backup_plan_config_dynamodb.selection_tags == null) ? [{ "key" : var.backup_plan_config_dynamodb.selection_tag, "value" : local.selection_tag_value_dynamodb_null_checked }] : var.backup_plan_config_dynamodb.selection_tags + selection_tags_ebsvol_null_checked = (var.backup_plan_config_ebsvol.selection_tags == null) ? [{ "key" : var.backup_plan_config_ebsvol.selection_tag, "value" : local.selection_tag_value_ebsvol_null_checked }] : var.backup_plan_config_ebsvol.selection_tags + selection_tags_parameter_store_null_checked = (var.backup_plan_config_parameter_store.selection_tags == null) ? [{ "key" : var.backup_plan_config_parameter_store.selection_tag, "value" : local.selection_tag_value_parameter_store_null_checked }] : var.backup_plan_config_parameter_store.selection_tags framework_arn_list = flatten(concat( var.backup_plan_config.enable ? [var.resources_in_same_account == "" ? aws_backup_framework.main[0].arn : aws_backup_framework.main[0].arn] : [], @@ -21,7 +21,7 @@ locals { var.backup_plan_config_parameter_store.enable ? [var.resources_in_same_account == "" ? aws_backup_framework.parameter_store[0].arn : data.aws_backup_framework.parameter_store[0].arn] : [] )) - aurora_overrides = var.backup_plan_config_aurora.restore_testing_overrides == null ? null : jsondecode(var.backup_plan_config_aurora.restore_testing_overrides) + aurora_overrides = var.backup_plan_config_aurora.restore_testing_overrides == null ? null : jsondecode(var.backup_plan_config_aurora.restore_testing_overrides) terraform_role_arns = length(var.terraform_role_arns) > 0 ? var.terraform_role_arns : [var.terraform_role_arn] } diff --git a/modules/aws-backup-source/variables.tf b/modules/aws-backup-source/variables.tf index c34c0bd..0b88829 100644 --- a/modules/aws-backup-source/variables.tf +++ b/modules/aws-backup-source/variables.tf @@ -306,9 +306,9 @@ variable "backup_plan_config_ebsvol" { variable "backup_plan_config_aurora" { description = "Configuration for backup plans with aurora" type = object({ - enable = bool - selection_tag = optional(string) - selection_tag_value = optional(string) + enable = bool + selection_tag = optional(string) + selection_tag_value = optional(string) selection_tags = optional(list(object({ key = optional(string) value = optional(string) From b89d827ad30419153f9232abe436f3a071209810 Mon Sep 17 00:00:00 2001 From: TurboNHS Date: Mon, 27 Apr 2026 11:19:43 +0100 Subject: [PATCH 6/6] Run code through `terraform fmt`. --- modules/aws-backup-destination/backup_vault_lock.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aws-backup-destination/backup_vault_lock.tf b/modules/aws-backup-destination/backup_vault_lock.tf index ce3e75d..0509490 100644 --- a/modules/aws-backup-destination/backup_vault_lock.tf +++ b/modules/aws-backup-destination/backup_vault_lock.tf @@ -1,5 +1,5 @@ resource "aws_backup_vault_lock_configuration" "vault_lock" { - count = var.enable_vault_protection && var.resources_in_same_account ? 1 : 0 + count = var.enable_vault_protection && var.resources_in_same_account ? 1 : 0 backup_vault_name = aws_backup_vault.vault[0].name changeable_for_days = var.vault_lock_type == "compliance" ? var.changeable_for_days : null