From b9218b4cafa8a0d588077c133b6131c9161425a2 Mon Sep 17 00:00:00 2001 From: Michel Fasen Date: Mon, 9 Feb 2026 13:32:08 +0000 Subject: [PATCH 01/11] LZVS-2611: Fix framework compliance and destination KMS alias --- .../parameter_store_kms.tf | 8 ++--- modules/aws-backup-source/backup_plan.tf | 6 +++- .../lambda_copy_recovery_point.tf | 16 ++++----- .../lambda_parameter_store_backup.tf | 34 +++++++++---------- .../lambda_post_build_version.tf | 18 +++++----- .../aws-backup-source/lambda_restore_to_s3.tf | 8 ++--- modules/aws-backup-source/variables.tf | 4 +-- 7 files changed, 49 insertions(+), 45 deletions(-) diff --git a/modules/aws-backup-destination/parameter_store_kms.tf b/modules/aws-backup-destination/parameter_store_kms.tf index dc5e068..95a9a0e 100644 --- a/modules/aws-backup-destination/parameter_store_kms.tf +++ b/modules/aws-backup-destination/parameter_store_kms.tf @@ -1,6 +1,6 @@ data "aws_iam_policy_document" "kms_key_policy" { statement { - sid = "Enable IAM User Permissions" + sid = "Enable IAM User Permissions" effect = "Allow" principals { type = "AWS" @@ -14,10 +14,10 @@ data "aws_iam_policy_document" "kms_key_policy" { for_each = var.enable_cross_account_role_permissions ? ["add_statement"] : [] content { - sid = "Allow Lambda Role from Source Account to Use Key" + sid = "Allow Lambda Role from Source Account to Use Key" effect = "Allow" principals { - type = "AWS" + type = "AWS" identifiers = ["arn:aws:iam::${var.source_account_id}:role/parameter_store_lambda_encryption_role"] } actions = [ @@ -39,7 +39,7 @@ resource "aws_kms_key" "parameter_store_key" { } resource "aws_kms_alias" "parameter_store_alias" { - name = "alias/parameter-store-backup-key" + name = var.name_prefix != null ? "alias/${var.name_prefix}-parameter-store-backup-key" : "alias/${var.source_account_name}-parameter-store-backup-key" target_key_id = aws_kms_key.parameter_store_key.key_id } diff --git a/modules/aws-backup-source/backup_plan.tf b/modules/aws-backup-source/backup_plan.tf index f4e783d..7473401 100644 --- a/modules/aws-backup-source/backup_plan.tf +++ b/modules/aws-backup-source/backup_plan.tf @@ -27,6 +27,10 @@ resource "aws_backup_plan" "default" { } } } + + tags = { + "environment_name" = var.environment_name + } } # this backup plan shouldn't include a continous backup rule as it isn't supported for DynamoDB @@ -125,7 +129,7 @@ resource "aws_backup_plan" "aurora" { resource "aws_backup_plan" "parameter_store" { count = var.backup_plan_config_parameter_store.enable ? 1 : 0 - name = "${local.resource_name_prefix}-ps-plan" + name = "${local.resource_name_prefix}-ps-plan" dynamic "rule" { for_each = var.backup_plan_config_parameter_store.rules diff --git a/modules/aws-backup-source/lambda_copy_recovery_point.tf b/modules/aws-backup-source/lambda_copy_recovery_point.tf index 89cabbe..860d150 100644 --- a/modules/aws-backup-source/lambda_copy_recovery_point.tf +++ b/modules/aws-backup-source/lambda_copy_recovery_point.tf @@ -12,8 +12,8 @@ resource "aws_iam_role" "iam_for_lambda_copy_recovery_point" { assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ - Action = "sts:AssumeRole" - Effect = "Allow" + Action = "sts:AssumeRole" + Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } }] }) @@ -47,7 +47,7 @@ resource "aws_iam_policy" "iam_policy_for_lambda_copy_recovery_point" { { Action = ["sts:AssumeRole"] Resource = var.lambda_copy_recovery_point_assume_role_arn == "" ? null : var.lambda_copy_recovery_point_assume_role_arn - Effect = "Allow" + Effect = "Allow" } ] }) @@ -71,11 +71,11 @@ resource "aws_lambda_function" "lambda_copy_recovery_point" { environment { variables = { - POLL_INTERVAL_SECONDS = var.lambda_copy_recovery_point_poll_interval_seconds - MAX_WAIT_MINUTES = var.lambda_copy_recovery_point_max_wait_minutes - DESTINATION_VAULT_ARN = var.lambda_copy_recovery_point_destination_vault_arn != "" ? var.lambda_copy_recovery_point_destination_vault_arn : var.backup_copy_vault_arn - SOURCE_VAULT_ARN = var.lambda_copy_recovery_point_source_vault_arn != "" ? var.lambda_copy_recovery_point_source_vault_arn : aws_backup_vault.main.arn - ASSUME_ROLE_ARN = var.lambda_copy_recovery_point_assume_role_arn + POLL_INTERVAL_SECONDS = var.lambda_copy_recovery_point_poll_interval_seconds + MAX_WAIT_MINUTES = var.lambda_copy_recovery_point_max_wait_minutes + DESTINATION_VAULT_ARN = var.lambda_copy_recovery_point_destination_vault_arn != "" ? var.lambda_copy_recovery_point_destination_vault_arn : var.backup_copy_vault_arn + SOURCE_VAULT_ARN = var.lambda_copy_recovery_point_source_vault_arn != "" ? var.lambda_copy_recovery_point_source_vault_arn : aws_backup_vault.main.arn + ASSUME_ROLE_ARN = var.lambda_copy_recovery_point_assume_role_arn } } } diff --git a/modules/aws-backup-source/lambda_parameter_store_backup.tf b/modules/aws-backup-source/lambda_parameter_store_backup.tf index 90d6d72..db56553 100644 --- a/modules/aws-backup-source/lambda_parameter_store_backup.tf +++ b/modules/aws-backup-source/lambda_parameter_store_backup.tf @@ -11,19 +11,19 @@ data "aws_iam_policy_document" "lambda_parameter_store_assume_role" { } data "aws_iam_policy_document" "lambda_parameter_store_backup_permissions" { - count = var.backup_plan_config_parameter_store.enable ? 1 : 0 + count = var.backup_plan_config_parameter_store.enable ? 1 : 0 version = "2012-10-17" statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "iam:PassRole" ] resources = [aws_iam_role.iam_for_lambda_parameter_store_backup[0].arn] } statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "ssm:DescribeParameters", "ssm:GetParametersByPath", "ssm:GetParameter", @@ -34,24 +34,24 @@ data "aws_iam_policy_document" "lambda_parameter_store_backup_permissions" { } statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "tag:GetResources", ] resources = ["*"] } statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "kms:Encrypt", ] resources = ["*"] } statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "s3:PutObject", "s3:PutObjectAcl", "s3:ListBucket" @@ -63,8 +63,8 @@ data "aws_iam_policy_document" "lambda_parameter_store_backup_permissions" { } statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" @@ -82,7 +82,7 @@ data "archive_file" "lambda_parameter_store_backup_zip" { resource "aws_s3_bucket" "parameter_store_backup_storage" { - count = var.backup_plan_config_parameter_store.enable ? 1 : 0 + count = var.backup_plan_config_parameter_store.enable ? 1 : 0 bucket = "${local.resource_name_prefix}-parameter-store-backup" tags = { @@ -105,7 +105,7 @@ resource "aws_s3_bucket_versioning" "parameter_store_backup_versioning" { # The IAM role name is fixed as it is referenced in the KMS key policy in the backup destination account. resource "aws_iam_role" "iam_for_lambda_parameter_store_backup" { - count = var.backup_plan_config_parameter_store.enable ? 1 : 0 + count = var.backup_plan_config_parameter_store.enable ? 1 : 0 name = "parameter_store_lambda_encryption_role" assume_role_policy = data.aws_iam_policy_document.lambda_parameter_store_assume_role[0].json } @@ -137,7 +137,7 @@ resource "aws_lambda_function" "lambda_parameter_store_backup" { } resource "aws_cloudwatch_event_rule" "aws_backup_parameter_store_event_rule" { - count = var.backup_plan_config_parameter_store.enable ? 1 : 0 + count = var.backup_plan_config_parameter_store.enable ? 1 : 0 name = "${local.resource_name_prefix}-parameter-store-backup-rule" description = "Triggers the Parameter Store Backup lambda." @@ -158,7 +158,7 @@ resource "aws_lambda_permission" "lambda_parameter_store_allow_eventbridge" { function_name = aws_lambda_function.lambda_parameter_store_backup[0].function_name principal = "events.amazonaws.com" - source_arn = aws_cloudwatch_event_rule.aws_backup_parameter_store_event_rule[0].arn + source_arn = aws_cloudwatch_event_rule.aws_backup_parameter_store_event_rule[0].arn } resource "aws_cloudwatch_log_group" "parameter_store_backup" { diff --git a/modules/aws-backup-source/lambda_post_build_version.tf b/modules/aws-backup-source/lambda_post_build_version.tf index 7e9c599..503fbb0 100644 --- a/modules/aws-backup-source/lambda_post_build_version.tf +++ b/modules/aws-backup-source/lambda_post_build_version.tf @@ -17,16 +17,16 @@ resource "aws_iam_role" "iam_for_lambda_post_build_version" { data "aws_iam_policy_document" "lambda_post_build_version_permissions" { version = "2012-10-17" statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "iam:PassRole" ] resources = [aws_iam_role.iam_for_lambda_post_build_version.arn] } statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" @@ -73,10 +73,10 @@ resource "aws_cloudwatch_event_rule" "aws_backup_post_build_version_event_rule" description = "Triggers the lambda on successful AWS Backup job completion." event_pattern = jsonencode({ - "source": ["aws.backup"], - "detail-type": ["Backup Job State Change"], - "detail": { - "state": ["COMPLETED"] + "source" : ["aws.backup"], + "detail-type" : ["Backup Job State Change"], + "detail" : { + "state" : ["COMPLETED"] } }) } @@ -93,7 +93,7 @@ resource "aws_lambda_permission" "post_build_allow_eventbridge" { function_name = aws_lambda_function.lambda_post_build_version.function_name principal = "events.amazonaws.com" - source_arn = aws_cloudwatch_event_rule.aws_backup_post_build_version_event_rule.arn + source_arn = aws_cloudwatch_event_rule.aws_backup_post_build_version_event_rule.arn } resource "aws_cloudwatch_log_group" "post_build_version_logs" { diff --git a/modules/aws-backup-source/lambda_restore_to_s3.tf b/modules/aws-backup-source/lambda_restore_to_s3.tf index a5b23ab..d7fead2 100644 --- a/modules/aws-backup-source/lambda_restore_to_s3.tf +++ b/modules/aws-backup-source/lambda_restore_to_s3.tf @@ -48,7 +48,7 @@ resource "aws_iam_policy" "iam_policy_for_lambda_restore_to_s3" { Effect = "Allow" }, { - Action = "iam:PassRole" + Action = "iam:PassRole" Resource = aws_iam_role.backup.arn Condition = { StringEquals = { @@ -69,8 +69,8 @@ resource "aws_iam_role_policy_attachment" "lambda_restore_to_s3_policy_attach" { resource "aws_lambda_function" "lambda_restore_to_s3" { - count = var.lambda_restore_to_s3_enable ? 1 : 0 - function_name = "${local.resource_name_prefix}_lambda-restore-to-s3" + count = var.lambda_restore_to_s3_enable ? 1 : 0 + function_name = "${local.resource_name_prefix}_lambda-restore-to-s3" role = aws_iam_role.iam_for_lambda_restore_to_s3[0].arn handler = "restore_to_s3.lambda_handler" @@ -83,7 +83,7 @@ resource "aws_lambda_function" "lambda_restore_to_s3" { variables = { POLL_INTERVAL_SECONDS = var.lambda_restore_to_s3_poll_interval_seconds MAX_WAIT_MINUTES = var.lambda_restore_to_s3_max_wait_minutes - IAM_ROLE_ARN = aws_iam_role.backup.arn + IAM_ROLE_ARN = aws_iam_role.backup.arn } } } diff --git a/modules/aws-backup-source/variables.tf b/modules/aws-backup-source/variables.tf index 6ca9252..d99aa10 100644 --- a/modules/aws-backup-source/variables.tf +++ b/modules/aws-backup-source/variables.tf @@ -365,8 +365,8 @@ variable "backup_plan_config_aurora" { variable "backup_plan_config_parameter_store" { description = "Configuration for backup plans with parameter store" type = object({ - enable = bool - selection_tag = string + enable = bool + selection_tag = string selection_tag_value = optional(string) selection_tags = optional(list(object({ key = optional(string) From bf6cc3f2688be8cb98a2dd8db64c462663e7ba10 Mon Sep 17 00:00:00 2001 From: Michel Fasen Date: Thu, 12 Feb 2026 09:19:17 +0000 Subject: [PATCH 02/11] Fixed Aurora selection plan ignoring non-default value tag --- modules/aws-backup-source/backup_plan.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/aws-backup-source/backup_plan.tf b/modules/aws-backup-source/backup_plan.tf index 7473401..ccf054f 100644 --- a/modules/aws-backup-source/backup_plan.tf +++ b/modules/aws-backup-source/backup_plan.tf @@ -234,7 +234,7 @@ 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 } } From 10c43e56052443e0ba68616e20f98545a9a4c86c Mon Sep 17 00:00:00 2001 From: Michel Fasen Date: Mon, 16 Feb 2026 14:21:09 +0000 Subject: [PATCH 03/11] Add 'selection_tag_value' to 'backup_plan_config_aurora' for 'terraform validate' to pass --- modules/aws-backup-source/variables.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/aws-backup-source/variables.tf b/modules/aws-backup-source/variables.tf index d99aa10..7c8bdd9 100644 --- a/modules/aws-backup-source/variables.tf +++ b/modules/aws-backup-source/variables.tf @@ -307,6 +307,7 @@ variable "backup_plan_config_aurora" { type = object({ enable = bool selection_tag = string + selection_tag_value = optional(string) compliance_resource_types = list(string) restore_testing_overrides = optional(string) rules = optional(list(object({ From 5eb6f23372728c71408b11842a1cd2b302312633 Mon Sep 17 00:00:00 2001 From: Michel Fasen Date: Tue, 17 Feb 2026 10:09:49 +0000 Subject: [PATCH 04/11] Add tags to all backup Plans to pass framework compliance --- modules/aws-backup-source/backup_plan.tf | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/modules/aws-backup-source/backup_plan.tf b/modules/aws-backup-source/backup_plan.tf index ccf054f..ea0bff5 100644 --- a/modules/aws-backup-source/backup_plan.tf +++ b/modules/aws-backup-source/backup_plan.tf @@ -63,6 +63,10 @@ resource "aws_backup_plan" "dynamodb" { } } } + + tags = { + "environment_name" = var.environment_name + } } resource "aws_backup_plan" "ebsvol" { @@ -93,9 +97,13 @@ resource "aws_backup_plan" "ebsvol" { } } } + + tags = { + "environment_name" = var.environment_name + } } -# this backup plan shouldn't include a continous backup rule as it isn't supported for Aurora +# this backup plan shouldn't include a continuous backup rule as it isn't supported for Aurora resource "aws_backup_plan" "aurora" { count = var.backup_plan_config_aurora.enable ? 1 : 0 name = "${local.resource_name_prefix}-aurora-plan" @@ -124,6 +132,10 @@ resource "aws_backup_plan" "aurora" { } } } + + tags = { + "environment_name" = var.environment_name + } } @@ -157,6 +169,10 @@ resource "aws_backup_plan" "parameter_store" { } } } + + tags = { + "environment_name" = var.environment_name + } } From 9275724d1d963809a85bf119b7cb0e3764fafe49 Mon Sep 17 00:00:00 2001 From: Michel Fasen Date: Wed, 18 Feb 2026 09:38:40 +0000 Subject: [PATCH 05/11] Revert terraform fmt to make approval easier --- .../parameter_store_kms.tf | 6 +++--- modules/aws-backup-source/backup_plan.tf | 2 +- .../lambda_copy_recovery_point.tf | 16 ++++++++-------- modules/aws-backup-source/variables.tf | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/modules/aws-backup-destination/parameter_store_kms.tf b/modules/aws-backup-destination/parameter_store_kms.tf index 95a9a0e..3f4217e 100644 --- a/modules/aws-backup-destination/parameter_store_kms.tf +++ b/modules/aws-backup-destination/parameter_store_kms.tf @@ -1,6 +1,6 @@ data "aws_iam_policy_document" "kms_key_policy" { statement { - sid = "Enable IAM User Permissions" + sid = "Enable IAM User Permissions" effect = "Allow" principals { type = "AWS" @@ -14,10 +14,10 @@ data "aws_iam_policy_document" "kms_key_policy" { for_each = var.enable_cross_account_role_permissions ? ["add_statement"] : [] content { - sid = "Allow Lambda Role from Source Account to Use Key" + sid = "Allow Lambda Role from Source Account to Use Key" effect = "Allow" principals { - type = "AWS" + type = "AWS" identifiers = ["arn:aws:iam::${var.source_account_id}:role/parameter_store_lambda_encryption_role"] } actions = [ diff --git a/modules/aws-backup-source/backup_plan.tf b/modules/aws-backup-source/backup_plan.tf index ea0bff5..9bac769 100644 --- a/modules/aws-backup-source/backup_plan.tf +++ b/modules/aws-backup-source/backup_plan.tf @@ -141,7 +141,7 @@ resource "aws_backup_plan" "aurora" { resource "aws_backup_plan" "parameter_store" { count = var.backup_plan_config_parameter_store.enable ? 1 : 0 - name = "${local.resource_name_prefix}-ps-plan" + name = "${local.resource_name_prefix}-ps-plan" dynamic "rule" { for_each = var.backup_plan_config_parameter_store.rules diff --git a/modules/aws-backup-source/lambda_copy_recovery_point.tf b/modules/aws-backup-source/lambda_copy_recovery_point.tf index 860d150..89cabbe 100644 --- a/modules/aws-backup-source/lambda_copy_recovery_point.tf +++ b/modules/aws-backup-source/lambda_copy_recovery_point.tf @@ -12,8 +12,8 @@ resource "aws_iam_role" "iam_for_lambda_copy_recovery_point" { assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [{ - Action = "sts:AssumeRole" - Effect = "Allow" + Action = "sts:AssumeRole" + Effect = "Allow" Principal = { Service = "lambda.amazonaws.com" } }] }) @@ -47,7 +47,7 @@ resource "aws_iam_policy" "iam_policy_for_lambda_copy_recovery_point" { { Action = ["sts:AssumeRole"] Resource = var.lambda_copy_recovery_point_assume_role_arn == "" ? null : var.lambda_copy_recovery_point_assume_role_arn - Effect = "Allow" + Effect = "Allow" } ] }) @@ -71,11 +71,11 @@ resource "aws_lambda_function" "lambda_copy_recovery_point" { environment { variables = { - POLL_INTERVAL_SECONDS = var.lambda_copy_recovery_point_poll_interval_seconds - MAX_WAIT_MINUTES = var.lambda_copy_recovery_point_max_wait_minutes - DESTINATION_VAULT_ARN = var.lambda_copy_recovery_point_destination_vault_arn != "" ? var.lambda_copy_recovery_point_destination_vault_arn : var.backup_copy_vault_arn - SOURCE_VAULT_ARN = var.lambda_copy_recovery_point_source_vault_arn != "" ? var.lambda_copy_recovery_point_source_vault_arn : aws_backup_vault.main.arn - ASSUME_ROLE_ARN = var.lambda_copy_recovery_point_assume_role_arn + POLL_INTERVAL_SECONDS = var.lambda_copy_recovery_point_poll_interval_seconds + MAX_WAIT_MINUTES = var.lambda_copy_recovery_point_max_wait_minutes + DESTINATION_VAULT_ARN = var.lambda_copy_recovery_point_destination_vault_arn != "" ? var.lambda_copy_recovery_point_destination_vault_arn : var.backup_copy_vault_arn + SOURCE_VAULT_ARN = var.lambda_copy_recovery_point_source_vault_arn != "" ? var.lambda_copy_recovery_point_source_vault_arn : aws_backup_vault.main.arn + ASSUME_ROLE_ARN = var.lambda_copy_recovery_point_assume_role_arn } } } diff --git a/modules/aws-backup-source/variables.tf b/modules/aws-backup-source/variables.tf index 7c8bdd9..25a13fe 100644 --- a/modules/aws-backup-source/variables.tf +++ b/modules/aws-backup-source/variables.tf @@ -366,8 +366,8 @@ variable "backup_plan_config_aurora" { variable "backup_plan_config_parameter_store" { description = "Configuration for backup plans with parameter store" type = object({ - enable = bool - selection_tag = string + enable = bool + selection_tag = string selection_tag_value = optional(string) selection_tags = optional(list(object({ key = optional(string) From 7d01c0e3662c99bcb7e7358b1620ab27f8b19910 Mon Sep 17 00:00:00 2001 From: Michel Fasen Date: Wed, 18 Feb 2026 09:42:31 +0000 Subject: [PATCH 06/11] Revert terraform fmt to make approval easier --- .../lambda_parameter_store_backup.tf | 34 +- .../aws-backup-source/lambda_restore_to_s3.tf | 8 +- modules/aws-backup-source/variables.tf | 567 +++--------------- 3 files changed, 94 insertions(+), 515 deletions(-) diff --git a/modules/aws-backup-source/lambda_parameter_store_backup.tf b/modules/aws-backup-source/lambda_parameter_store_backup.tf index db56553..90d6d72 100644 --- a/modules/aws-backup-source/lambda_parameter_store_backup.tf +++ b/modules/aws-backup-source/lambda_parameter_store_backup.tf @@ -11,19 +11,19 @@ data "aws_iam_policy_document" "lambda_parameter_store_assume_role" { } data "aws_iam_policy_document" "lambda_parameter_store_backup_permissions" { - count = var.backup_plan_config_parameter_store.enable ? 1 : 0 + count = var.backup_plan_config_parameter_store.enable ? 1 : 0 version = "2012-10-17" statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "iam:PassRole" ] resources = [aws_iam_role.iam_for_lambda_parameter_store_backup[0].arn] } statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "ssm:DescribeParameters", "ssm:GetParametersByPath", "ssm:GetParameter", @@ -34,24 +34,24 @@ data "aws_iam_policy_document" "lambda_parameter_store_backup_permissions" { } statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "tag:GetResources", ] resources = ["*"] } statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "kms:Encrypt", ] resources = ["*"] } statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "s3:PutObject", "s3:PutObjectAcl", "s3:ListBucket" @@ -63,8 +63,8 @@ data "aws_iam_policy_document" "lambda_parameter_store_backup_permissions" { } statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" @@ -82,7 +82,7 @@ data "archive_file" "lambda_parameter_store_backup_zip" { resource "aws_s3_bucket" "parameter_store_backup_storage" { - count = var.backup_plan_config_parameter_store.enable ? 1 : 0 + count = var.backup_plan_config_parameter_store.enable ? 1 : 0 bucket = "${local.resource_name_prefix}-parameter-store-backup" tags = { @@ -105,7 +105,7 @@ resource "aws_s3_bucket_versioning" "parameter_store_backup_versioning" { # The IAM role name is fixed as it is referenced in the KMS key policy in the backup destination account. resource "aws_iam_role" "iam_for_lambda_parameter_store_backup" { - count = var.backup_plan_config_parameter_store.enable ? 1 : 0 + count = var.backup_plan_config_parameter_store.enable ? 1 : 0 name = "parameter_store_lambda_encryption_role" assume_role_policy = data.aws_iam_policy_document.lambda_parameter_store_assume_role[0].json } @@ -137,7 +137,7 @@ resource "aws_lambda_function" "lambda_parameter_store_backup" { } resource "aws_cloudwatch_event_rule" "aws_backup_parameter_store_event_rule" { - count = var.backup_plan_config_parameter_store.enable ? 1 : 0 + count = var.backup_plan_config_parameter_store.enable ? 1 : 0 name = "${local.resource_name_prefix}-parameter-store-backup-rule" description = "Triggers the Parameter Store Backup lambda." @@ -158,7 +158,7 @@ resource "aws_lambda_permission" "lambda_parameter_store_allow_eventbridge" { function_name = aws_lambda_function.lambda_parameter_store_backup[0].function_name principal = "events.amazonaws.com" - source_arn = aws_cloudwatch_event_rule.aws_backup_parameter_store_event_rule[0].arn + source_arn = aws_cloudwatch_event_rule.aws_backup_parameter_store_event_rule[0].arn } resource "aws_cloudwatch_log_group" "parameter_store_backup" { diff --git a/modules/aws-backup-source/lambda_restore_to_s3.tf b/modules/aws-backup-source/lambda_restore_to_s3.tf index d7fead2..a5b23ab 100644 --- a/modules/aws-backup-source/lambda_restore_to_s3.tf +++ b/modules/aws-backup-source/lambda_restore_to_s3.tf @@ -48,7 +48,7 @@ resource "aws_iam_policy" "iam_policy_for_lambda_restore_to_s3" { Effect = "Allow" }, { - Action = "iam:PassRole" + Action = "iam:PassRole" Resource = aws_iam_role.backup.arn Condition = { StringEquals = { @@ -69,8 +69,8 @@ resource "aws_iam_role_policy_attachment" "lambda_restore_to_s3_policy_attach" { resource "aws_lambda_function" "lambda_restore_to_s3" { - count = var.lambda_restore_to_s3_enable ? 1 : 0 - function_name = "${local.resource_name_prefix}_lambda-restore-to-s3" + count = var.lambda_restore_to_s3_enable ? 1 : 0 + function_name = "${local.resource_name_prefix}_lambda-restore-to-s3" role = aws_iam_role.iam_for_lambda_restore_to_s3[0].arn handler = "restore_to_s3.lambda_handler" @@ -83,7 +83,7 @@ resource "aws_lambda_function" "lambda_restore_to_s3" { variables = { POLL_INTERVAL_SECONDS = var.lambda_restore_to_s3_poll_interval_seconds MAX_WAIT_MINUTES = var.lambda_restore_to_s3_max_wait_minutes - IAM_ROLE_ARN = aws_iam_role.backup.arn + IAM_ROLE_ARN = aws_iam_role.backup.arn } } } diff --git a/modules/aws-backup-source/variables.tf b/modules/aws-backup-source/variables.tf index 25a13fe..7e9c599 100644 --- a/modules/aws-backup-source/variables.tf +++ b/modules/aws-backup-source/variables.tf @@ -1,523 +1,102 @@ -variable "project_name" { - description = "The name of the project this relates to." - type = string -} - -variable "environment_name" { - description = "The name of the environment where AWS Backup is configured." - type = string -} - -variable "notifications_target_email_address" { - description = "The email address to which backup notifications will be sent via SNS." - type = string - default = "" -} - -variable "bootstrap_kms_key_arn" { - description = "The ARN of the bootstrap KMS key used for encryption at rest of the SNS topic." - type = string -} - -variable "reports_bucket" { - description = "Bucket to drop backup reports into" - type = string -} - -variable "terraform_role_arn" { - description = "ARN of Terraform role used to deploy to account (deprecated, please swap to terraform_role_arns)" - type = string - default = "" -} - -variable "terraform_role_arns" { - description = "ARN of Terraform roles used to deploy to account, defaults to caller arn if list is empty" - type = list(string) - default = [] -} - -variable "deletion_allowed_principal_arns" { - description = "List of ARNs of principals allowed to delete backups." - type = list(string) - default = null - nullable = true -} - -variable "restore_testing_plan_algorithm" { - description = "Algorithm of the Recovery Selection Point" - type = string - default = "LATEST_WITHIN_WINDOW" -} - -variable "restore_testing_plan_start_window" { - description = "Start window from the scheduled time during which the test should start" - type = number - default = 1 -} - -variable "restore_testing_plan_scheduled_expression" { - description = "Scheduled Expression of Recovery Selection Point" - type = string - default = "cron(0 1 ? * SUN *)" -} - -variable "restore_testing_plan_recovery_point_types" { - description = "Recovery Point Types" - type = list(string) - default = ["SNAPSHOT"] -} - -variable "restore_testing_plan_selection_window_days" { - description = "Selection window days" - type = number - default = 7 -} - -variable "backup_copy_vault_arn" { - description = "The ARN of the destination backup vault for cross-account backup copies." - type = string - default = "" -} - -variable "backup_copy_vault_account_id" { - description = "The account id of the destination backup vault for allowing restores back into the source account." - type = string - default = "" -} - -variable "backup_plan_config" { - description = "Configuration for backup plans" - type = object({ - selection_tag = string - selection_tag_value = optional(string) - selection_tags = optional(list(object({ - key = optional(string) - value = optional(string) - }))) - compliance_resource_types = list(string) - rules = list(object({ - name = string - schedule = string - completion_window = optional(number) - enable_continuous_backup = optional(bool) - lifecycle = object({ - delete_after = optional(number) - cold_storage_after = optional(number) - }) - copy_action = optional(object({ - delete_after = optional(number) - })) - })) - }) - default = { - selection_tag = "BackupLocal" - selection_tag_value = "True" - selection_tags = [] - compliance_resource_types = ["S3"] - rules = [ - { - name = "daily_kept_5_weeks" - schedule = "cron(0 0 * * ? *)" - lifecycle = { - delete_after = 35 - } - copy_action = { - delete_after = 365 - } - }, - { - name = "weekly_kept_3_months" - schedule = "cron(0 1 ? * SUN *)" - lifecycle = { - delete_after = 90 - } - copy_action = { - delete_after = 365 - } - }, - { - name = "monthly_kept_7_years" - schedule = "cron(0 2 1 * ? *)" - lifecycle = { - cold_storage_after = 30 - delete_after = 2555 - } - copy_action = { - delete_after = 365 - } - }, - { - name = "point_in_time_recovery" - schedule = "cron(0 5 * * ? *)" - enable_continuous_backup = true - lifecycle = { - delete_after = 35 - } - copy_action = { - delete_after = 365 - } - } - ] - } -} - -variable "backup_plan_config_dynamodb" { - description = "Configuration for backup plans with dynamodb" - type = object({ - enable = bool - selection_tag = string - selection_tag_value = optional(string) - selection_tags = optional(list(object({ - key = optional(string) - value = optional(string) - }))) - compliance_resource_types = list(string) - rules = optional(list(object({ - name = string - schedule = string - completion_window = optional(number) - enable_continuous_backup = optional(bool) - lifecycle = object({ - delete_after = number - cold_storage_after = optional(number) - }) - copy_action = optional(object({ - delete_after = optional(number) - })) - }))) - }) - default = { - enable = true - selection_tag = "BackupDynamoDB" - selection_tag_value = "True" - selection_tags = [] - compliance_resource_types = ["DynamoDB"] - rules = [ - { - name = "dynamodb_daily_kept_5_weeks" - schedule = "cron(0 0 * * ? *)" - lifecycle = { - delete_after = 35 - } - copy_action = { - delete_after = 365 - } - }, - { - name = "dynamodb_weekly_kept_3_months" - schedule = "cron(0 1 ? * SUN *)" - lifecycle = { - delete_after = 90 - } - copy_action = { - delete_after = 365 - } - }, - { - name = "dynamodb_monthly_kept_7_years" - schedule = "cron(0 2 1 * ? *)" - lifecycle = { - cold_storage_after = 30 - delete_after = 2555 - } - copy_action = { - delete_after = 365 - } - } - ] +data "aws_iam_policy_document" "lambda_post_build_version_assume_role" { + statement { + effect = "Allow" + principals { + type = "Service" + identifiers = ["lambda.amazonaws.com"] + } + actions = ["sts:AssumeRole"] } } -variable "name_prefix" { - description = "Name prefix for vault resources" - type = string - default = null - validation { - condition = var.name_prefix == null || can(regex("^[^0-9]*$", var.name_prefix)) - error_message = "The name_prefix must not contain any numbers." - } -} - -variable "backup_plan_config_ebsvol" { - description = "Configuration for backup plans with EBS" - type = object({ - enable = bool - selection_tag = string - selection_tag_value = optional(string) - selection_tags = optional(list(object({ - key = optional(string) - value = optional(string) - }))) - compliance_resource_types = list(string) - rules = optional(list(object({ - name = string - schedule = string - enable_continuous_backup = optional(bool) - lifecycle = object({ - delete_after = number - cold_storage_after = optional(number) - }) - copy_action = optional(object({ - delete_after = optional(number) - })) - }))) - }) - default = { - enable = true - selection_tag = "BackupEBSVol" - compliance_resource_types = ["EBS"] - rules = [ - { - name = "ebsvol_daily_kept_5_weeks" - schedule = "cron(0 0 * * ? *)" - lifecycle = { - delete_after = 35 - } - copy_action = { - delete_after = 365 - } - }, - { - name = "ebsvol_weekly_kept_3_months" - schedule = "cron(0 1 ? * SUN *)" - lifecycle = { - delete_after = 90 - } - copy_action = { - delete_after = 365 - } - }, - { - name = "ebsvol_monthly_kept_7_years" - schedule = "cron(0 2 1 * ? *)" - lifecycle = { - cold_storage_after = 30 - delete_after = 2555 - } - copy_action = { - delete_after = 365 - } - } - ] - } +resource "aws_iam_role" "iam_for_lambda_post_build_version" { + name = "${var.name_prefix}_iam_for_lambda_post_build_version" + assume_role_policy = data.aws_iam_policy_document.lambda_post_build_version_assume_role.json } -variable "backup_plan_config_aurora" { - description = "Configuration for backup plans with aurora" - type = object({ - enable = bool - selection_tag = string - selection_tag_value = optional(string) - compliance_resource_types = list(string) - restore_testing_overrides = optional(string) - rules = optional(list(object({ - name = string - schedule = string - enable_continuous_backup = optional(bool) - lifecycle = object({ - delete_after = number - cold_storage_after = optional(number) - }) - copy_action = optional(object({ - delete_after = optional(number) - })) - }))) - }) - default = { - enable = true - selection_tag = "BackupAurora" - compliance_resource_types = ["Aurora"] - rules = [ - { - name = "aurora_daily_kept_5_weeks" - schedule = "cron(0 0 * * ? *)" - lifecycle = { - delete_after = 35 - } - copy_action = { - delete_after = 365 - } - }, - { - name = "aurora_weekly_kept_3_months" - schedule = "cron(0 1 ? * SUN *)" - lifecycle = { - delete_after = 90 - } - copy_action = { - delete_after = 365 - } - }, - { - name = "aurora_monthly_kept_7_years" - schedule = "cron(0 2 1 * ? *)" - lifecycle = { - cold_storage_after = 30 - delete_after = 2555 - } - copy_action = { - delete_after = 365 - } - } +data "aws_iam_policy_document" "lambda_post_build_version_permissions" { + version = "2012-10-17" + statement { + effect = "Allow" + actions = [ + "iam:PassRole" ] + resources = [aws_iam_role.iam_for_lambda_post_build_version.arn] } -} -variable "backup_plan_config_parameter_store" { - description = "Configuration for backup plans with parameter store" - type = object({ - enable = bool - selection_tag = string - selection_tag_value = optional(string) - selection_tags = optional(list(object({ - key = optional(string) - value = optional(string) - }))) - lambda_backup_cron = optional(string) - lambda_timeout_seconds = optional(number) - rules = optional(list(object({ - name = string - schedule = string - completion_window = optional(number) - enable_continuous_backup = optional(bool) - lifecycle = object({ - delete_after = number - cold_storage_after = optional(number) - }) - copy_action = optional(object({ - delete_after = optional(number) - })) - }))) - }) - default = { - enable = true - selection_tag = "BackupParameterStore" - selection_tag_value = "True" - selection_tags = [] - lambda_backup_cron = "0 6 * * ? *" - lambda_timeout_seconds = 300 - compliance_resource_types = ["S3"] - rules = [ - { - name = "daily_kept_5_weeks" - schedule = "cron(0 0 * * ? *)" - lifecycle = { - delete_after = 35 - } - copy_action = { - delete_after = 365 - } - }, - { - name = "weekly_kept_3_months" - schedule = "cron(0 1 ? * SUN *)" - lifecycle = { - delete_after = 90 - } - copy_action = { - delete_after = 365 - } - }, - { - name = "monthly_kept_7_years" - schedule = "cron(0 2 1 * ? *)" - lifecycle = { - cold_storage_after = 30 - delete_after = 2555 - } - copy_action = { - delete_after = 365 - } - }, - { - name = "point_in_time_recovery" - schedule = "cron(0 5 * * ? *)" - enable_continuous_backup = true - lifecycle = { - delete_after = 35 - } - copy_action = { - delete_after = 365 - } - } + statement { + effect = "Allow" + actions = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" ] + resources = ["arn:aws:logs:*:*:*"] } } -variable "iam_role_permissions_boundary" { - description = "Optional permissions boundary ARN for backup role" - type = string - default = "" # Empty by default -} - -variable "api_endpoint" { - description = "API endpoint to send post build version notifications to" - type = string - default = "" -} - -variable "lambda_copy_recovery_point_enable" { - description = "Flag to enable the copy recovery point lambda (copy recovery point from destination vault back to source)." - type = bool - default = false +locals { + module_version = file("${path.module}/version") } -variable "lambda_copy_recovery_point_poll_interval_seconds" { - description = "Polling interval in seconds for copy job status checks." - type = number - default = 30 +resource "aws_iam_role_policy" "lambda_post_build_version_iam_permissions" { + name = "${var.name_prefix}_lambda_post_build_version_iam_permissions_policy" + role = aws_iam_role.iam_for_lambda_post_build_version.id + policy = data.aws_iam_policy_document.lambda_post_build_version_permissions.json } -variable "lambda_copy_recovery_point_max_wait_minutes" { - description = "Maximum number of minutes to wait for a copy job to reach a terminal state before returning running status." - type = number - default = 10 +data "archive_file" "lambda_post_build_version_zip" { + type = "zip" + source_dir = "${path.module}/resources/post_build_version/" + output_path = "${path.module}/.terraform/archive_files/lambda_post_build_version.zip" } -variable "lambda_copy_recovery_point_destination_vault_arn" { - description = "Destination vault ARN containing the recovery point to be copied back (the air-gapped vault)." - type = string - default = "" +resource "aws_lambda_function" "lambda_post_build_version" { + filename = data.archive_file.lambda_post_build_version_zip.output_path + source_code_hash = data.archive_file.lambda_post_build_version_zip.output_base64sha256 + function_name = "${var.name_prefix}-post_build_version" + role = aws_iam_role.iam_for_lambda_post_build_version.arn + handler = "post_build_version.lambda_handler" + runtime = "python3.12" + environment { + variables = { + AWS_ACCOUNT_ID = data.aws_caller_identity.current.account_id + MODULE_VERSION = local.module_version + API_ENDPOINT = var.api_endpoint + API_TOKEN = var.api_token + } + } } -variable "api_token" { - description = "API token to authenticate with the API endpoint" - type = string - default = "" -} +resource "aws_cloudwatch_event_rule" "aws_backup_post_build_version_event_rule" { + name = "${var.name_prefix}-post-build-version-rule" + description = "Triggers the lambda on successful AWS Backup job completion." -variable "lambda_copy_recovery_point_source_vault_arn" { - description = "Source vault ARN to which the recovery point will be copied back." - type = string - default = "" -} - -variable "lambda_copy_recovery_point_assume_role_arn" { - description = "ARN of role in destination account the lambda assumes to initiate the copy job (if required for cross-account)." - type = string - default = "" + event_pattern = jsonencode({ + "source": ["aws.backup"], + "detail-type": ["Backup Job State Change"], + "detail": { + "state": ["COMPLETED"] + } + }) } -variable "destination_parameter_store_kms_key_arn" { - description = "The ARN of the KMS key used to encrypt Parameter Store backups." - type = string - default = "" +resource "aws_cloudwatch_event_target" "lambda_post_build_version_target" { + rule = aws_cloudwatch_event_rule.aws_backup_post_build_version_event_rule.name + arn = aws_lambda_function.lambda_post_build_version.arn + target_id = "${var.name_prefix}postBuildVersionLambdaTarget" } -variable "lambda_restore_to_s3_enable" { - description = "Enable the Lambda function to restore Parameter Store backups to S3." - type = bool - default = false -} +resource "aws_lambda_permission" "post_build_allow_eventbridge" { + statement_id = "${var.name_prefix}AllowExecutionFromEventbridge" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.lambda_post_build_version.function_name + principal = "events.amazonaws.com" -variable "lambda_restore_to_s3_poll_interval_seconds" { - description = "Poll interval in seconds for checking the status of the restore job." - type = number - default = 30 + source_arn = aws_cloudwatch_event_rule.aws_backup_post_build_version_event_rule.arn } -variable "lambda_restore_to_s3_max_wait_minutes" { - description = "Maximum wait time in minutes for the restore job to complete." - type = number - default = 5 +resource "aws_cloudwatch_log_group" "post_build_version_logs" { + name = "/aws/lambda/${var.name_prefix}-post_build_version" + retention_in_days = 30 } From a9411452d8d4d1b2dd86cd0b4742c2ada2843639 Mon Sep 17 00:00:00 2001 From: Michel Fasen Date: Wed, 18 Feb 2026 09:44:45 +0000 Subject: [PATCH 07/11] Revert terraform fmt to make approval easier --- .../lambda_post_build_version.tf | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/aws-backup-source/lambda_post_build_version.tf b/modules/aws-backup-source/lambda_post_build_version.tf index 503fbb0..7e9c599 100644 --- a/modules/aws-backup-source/lambda_post_build_version.tf +++ b/modules/aws-backup-source/lambda_post_build_version.tf @@ -17,16 +17,16 @@ resource "aws_iam_role" "iam_for_lambda_post_build_version" { data "aws_iam_policy_document" "lambda_post_build_version_permissions" { version = "2012-10-17" statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "iam:PassRole" ] resources = [aws_iam_role.iam_for_lambda_post_build_version.arn] } statement { - effect = "Allow" - actions = [ + effect = "Allow" + actions = [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" @@ -73,10 +73,10 @@ resource "aws_cloudwatch_event_rule" "aws_backup_post_build_version_event_rule" description = "Triggers the lambda on successful AWS Backup job completion." event_pattern = jsonencode({ - "source" : ["aws.backup"], - "detail-type" : ["Backup Job State Change"], - "detail" : { - "state" : ["COMPLETED"] + "source": ["aws.backup"], + "detail-type": ["Backup Job State Change"], + "detail": { + "state": ["COMPLETED"] } }) } @@ -93,7 +93,7 @@ resource "aws_lambda_permission" "post_build_allow_eventbridge" { function_name = aws_lambda_function.lambda_post_build_version.function_name principal = "events.amazonaws.com" - source_arn = aws_cloudwatch_event_rule.aws_backup_post_build_version_event_rule.arn + source_arn = aws_cloudwatch_event_rule.aws_backup_post_build_version_event_rule.arn } resource "aws_cloudwatch_log_group" "post_build_version_logs" { From 0e268605ea47c1f51b4ed3a11078082ede4e8b95 Mon Sep 17 00:00:00 2001 From: Michel Fasen Date: Wed, 18 Feb 2026 09:46:14 +0000 Subject: [PATCH 08/11] Revert terraform fmt to make approval easier --- modules/aws-backup-source/variables.tf | 567 +++++++++++++++++++++---- 1 file changed, 494 insertions(+), 73 deletions(-) diff --git a/modules/aws-backup-source/variables.tf b/modules/aws-backup-source/variables.tf index 7e9c599..25a13fe 100644 --- a/modules/aws-backup-source/variables.tf +++ b/modules/aws-backup-source/variables.tf @@ -1,102 +1,523 @@ -data "aws_iam_policy_document" "lambda_post_build_version_assume_role" { - statement { - effect = "Allow" - principals { - type = "Service" - identifiers = ["lambda.amazonaws.com"] - } - actions = ["sts:AssumeRole"] - } +variable "project_name" { + description = "The name of the project this relates to." + type = string +} + +variable "environment_name" { + description = "The name of the environment where AWS Backup is configured." + type = string +} + +variable "notifications_target_email_address" { + description = "The email address to which backup notifications will be sent via SNS." + type = string + default = "" +} + +variable "bootstrap_kms_key_arn" { + description = "The ARN of the bootstrap KMS key used for encryption at rest of the SNS topic." + type = string +} + +variable "reports_bucket" { + description = "Bucket to drop backup reports into" + type = string +} + +variable "terraform_role_arn" { + description = "ARN of Terraform role used to deploy to account (deprecated, please swap to terraform_role_arns)" + type = string + default = "" +} + +variable "terraform_role_arns" { + description = "ARN of Terraform roles used to deploy to account, defaults to caller arn if list is empty" + type = list(string) + default = [] +} + +variable "deletion_allowed_principal_arns" { + description = "List of ARNs of principals allowed to delete backups." + type = list(string) + default = null + nullable = true +} + +variable "restore_testing_plan_algorithm" { + description = "Algorithm of the Recovery Selection Point" + type = string + default = "LATEST_WITHIN_WINDOW" +} + +variable "restore_testing_plan_start_window" { + description = "Start window from the scheduled time during which the test should start" + type = number + default = 1 +} + +variable "restore_testing_plan_scheduled_expression" { + description = "Scheduled Expression of Recovery Selection Point" + type = string + default = "cron(0 1 ? * SUN *)" } -resource "aws_iam_role" "iam_for_lambda_post_build_version" { - name = "${var.name_prefix}_iam_for_lambda_post_build_version" - assume_role_policy = data.aws_iam_policy_document.lambda_post_build_version_assume_role.json +variable "restore_testing_plan_recovery_point_types" { + description = "Recovery Point Types" + type = list(string) + default = ["SNAPSHOT"] } -data "aws_iam_policy_document" "lambda_post_build_version_permissions" { - version = "2012-10-17" - statement { - effect = "Allow" - actions = [ - "iam:PassRole" +variable "restore_testing_plan_selection_window_days" { + description = "Selection window days" + type = number + default = 7 +} + +variable "backup_copy_vault_arn" { + description = "The ARN of the destination backup vault for cross-account backup copies." + type = string + default = "" +} + +variable "backup_copy_vault_account_id" { + description = "The account id of the destination backup vault for allowing restores back into the source account." + type = string + default = "" +} + +variable "backup_plan_config" { + description = "Configuration for backup plans" + type = object({ + selection_tag = string + selection_tag_value = optional(string) + selection_tags = optional(list(object({ + key = optional(string) + value = optional(string) + }))) + compliance_resource_types = list(string) + rules = list(object({ + name = string + schedule = string + completion_window = optional(number) + enable_continuous_backup = optional(bool) + lifecycle = object({ + delete_after = optional(number) + cold_storage_after = optional(number) + }) + copy_action = optional(object({ + delete_after = optional(number) + })) + })) + }) + default = { + selection_tag = "BackupLocal" + selection_tag_value = "True" + selection_tags = [] + compliance_resource_types = ["S3"] + rules = [ + { + name = "daily_kept_5_weeks" + schedule = "cron(0 0 * * ? *)" + lifecycle = { + delete_after = 35 + } + copy_action = { + delete_after = 365 + } + }, + { + name = "weekly_kept_3_months" + schedule = "cron(0 1 ? * SUN *)" + lifecycle = { + delete_after = 90 + } + copy_action = { + delete_after = 365 + } + }, + { + name = "monthly_kept_7_years" + schedule = "cron(0 2 1 * ? *)" + lifecycle = { + cold_storage_after = 30 + delete_after = 2555 + } + copy_action = { + delete_after = 365 + } + }, + { + name = "point_in_time_recovery" + schedule = "cron(0 5 * * ? *)" + enable_continuous_backup = true + lifecycle = { + delete_after = 35 + } + copy_action = { + delete_after = 365 + } + } ] - resources = [aws_iam_role.iam_for_lambda_post_build_version.arn] } +} - statement { - effect = "Allow" - actions = [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" +variable "backup_plan_config_dynamodb" { + description = "Configuration for backup plans with dynamodb" + type = object({ + enable = bool + selection_tag = string + selection_tag_value = optional(string) + selection_tags = optional(list(object({ + key = optional(string) + value = optional(string) + }))) + compliance_resource_types = list(string) + rules = optional(list(object({ + name = string + schedule = string + completion_window = optional(number) + enable_continuous_backup = optional(bool) + lifecycle = object({ + delete_after = number + cold_storage_after = optional(number) + }) + copy_action = optional(object({ + delete_after = optional(number) + })) + }))) + }) + default = { + enable = true + selection_tag = "BackupDynamoDB" + selection_tag_value = "True" + selection_tags = [] + compliance_resource_types = ["DynamoDB"] + rules = [ + { + name = "dynamodb_daily_kept_5_weeks" + schedule = "cron(0 0 * * ? *)" + lifecycle = { + delete_after = 35 + } + copy_action = { + delete_after = 365 + } + }, + { + name = "dynamodb_weekly_kept_3_months" + schedule = "cron(0 1 ? * SUN *)" + lifecycle = { + delete_after = 90 + } + copy_action = { + delete_after = 365 + } + }, + { + name = "dynamodb_monthly_kept_7_years" + schedule = "cron(0 2 1 * ? *)" + lifecycle = { + cold_storage_after = 30 + delete_after = 2555 + } + copy_action = { + delete_after = 365 + } + } ] - resources = ["arn:aws:logs:*:*:*"] } } -locals { - module_version = file("${path.module}/version") +variable "name_prefix" { + description = "Name prefix for vault resources" + type = string + default = null + validation { + condition = var.name_prefix == null || can(regex("^[^0-9]*$", var.name_prefix)) + error_message = "The name_prefix must not contain any numbers." + } } -resource "aws_iam_role_policy" "lambda_post_build_version_iam_permissions" { - name = "${var.name_prefix}_lambda_post_build_version_iam_permissions_policy" - role = aws_iam_role.iam_for_lambda_post_build_version.id - policy = data.aws_iam_policy_document.lambda_post_build_version_permissions.json +variable "backup_plan_config_ebsvol" { + description = "Configuration for backup plans with EBS" + type = object({ + enable = bool + selection_tag = string + selection_tag_value = optional(string) + selection_tags = optional(list(object({ + key = optional(string) + value = optional(string) + }))) + compliance_resource_types = list(string) + rules = optional(list(object({ + name = string + schedule = string + enable_continuous_backup = optional(bool) + lifecycle = object({ + delete_after = number + cold_storage_after = optional(number) + }) + copy_action = optional(object({ + delete_after = optional(number) + })) + }))) + }) + default = { + enable = true + selection_tag = "BackupEBSVol" + compliance_resource_types = ["EBS"] + rules = [ + { + name = "ebsvol_daily_kept_5_weeks" + schedule = "cron(0 0 * * ? *)" + lifecycle = { + delete_after = 35 + } + copy_action = { + delete_after = 365 + } + }, + { + name = "ebsvol_weekly_kept_3_months" + schedule = "cron(0 1 ? * SUN *)" + lifecycle = { + delete_after = 90 + } + copy_action = { + delete_after = 365 + } + }, + { + name = "ebsvol_monthly_kept_7_years" + schedule = "cron(0 2 1 * ? *)" + lifecycle = { + cold_storage_after = 30 + delete_after = 2555 + } + copy_action = { + delete_after = 365 + } + } + ] + } } -data "archive_file" "lambda_post_build_version_zip" { - type = "zip" - source_dir = "${path.module}/resources/post_build_version/" - output_path = "${path.module}/.terraform/archive_files/lambda_post_build_version.zip" +variable "backup_plan_config_aurora" { + description = "Configuration for backup plans with aurora" + type = object({ + enable = bool + selection_tag = string + selection_tag_value = optional(string) + compliance_resource_types = list(string) + restore_testing_overrides = optional(string) + rules = optional(list(object({ + name = string + schedule = string + enable_continuous_backup = optional(bool) + lifecycle = object({ + delete_after = number + cold_storage_after = optional(number) + }) + copy_action = optional(object({ + delete_after = optional(number) + })) + }))) + }) + default = { + enable = true + selection_tag = "BackupAurora" + compliance_resource_types = ["Aurora"] + rules = [ + { + name = "aurora_daily_kept_5_weeks" + schedule = "cron(0 0 * * ? *)" + lifecycle = { + delete_after = 35 + } + copy_action = { + delete_after = 365 + } + }, + { + name = "aurora_weekly_kept_3_months" + schedule = "cron(0 1 ? * SUN *)" + lifecycle = { + delete_after = 90 + } + copy_action = { + delete_after = 365 + } + }, + { + name = "aurora_monthly_kept_7_years" + schedule = "cron(0 2 1 * ? *)" + lifecycle = { + cold_storage_after = 30 + delete_after = 2555 + } + copy_action = { + delete_after = 365 + } + } + ] + } } -resource "aws_lambda_function" "lambda_post_build_version" { - filename = data.archive_file.lambda_post_build_version_zip.output_path - source_code_hash = data.archive_file.lambda_post_build_version_zip.output_base64sha256 - function_name = "${var.name_prefix}-post_build_version" - role = aws_iam_role.iam_for_lambda_post_build_version.arn - handler = "post_build_version.lambda_handler" - runtime = "python3.12" - environment { - variables = { - AWS_ACCOUNT_ID = data.aws_caller_identity.current.account_id - MODULE_VERSION = local.module_version - API_ENDPOINT = var.api_endpoint - API_TOKEN = var.api_token - } +variable "backup_plan_config_parameter_store" { + description = "Configuration for backup plans with parameter store" + type = object({ + enable = bool + selection_tag = string + selection_tag_value = optional(string) + selection_tags = optional(list(object({ + key = optional(string) + value = optional(string) + }))) + lambda_backup_cron = optional(string) + lambda_timeout_seconds = optional(number) + rules = optional(list(object({ + name = string + schedule = string + completion_window = optional(number) + enable_continuous_backup = optional(bool) + lifecycle = object({ + delete_after = number + cold_storage_after = optional(number) + }) + copy_action = optional(object({ + delete_after = optional(number) + })) + }))) + }) + default = { + enable = true + selection_tag = "BackupParameterStore" + selection_tag_value = "True" + selection_tags = [] + lambda_backup_cron = "0 6 * * ? *" + lambda_timeout_seconds = 300 + compliance_resource_types = ["S3"] + rules = [ + { + name = "daily_kept_5_weeks" + schedule = "cron(0 0 * * ? *)" + lifecycle = { + delete_after = 35 + } + copy_action = { + delete_after = 365 + } + }, + { + name = "weekly_kept_3_months" + schedule = "cron(0 1 ? * SUN *)" + lifecycle = { + delete_after = 90 + } + copy_action = { + delete_after = 365 + } + }, + { + name = "monthly_kept_7_years" + schedule = "cron(0 2 1 * ? *)" + lifecycle = { + cold_storage_after = 30 + delete_after = 2555 + } + copy_action = { + delete_after = 365 + } + }, + { + name = "point_in_time_recovery" + schedule = "cron(0 5 * * ? *)" + enable_continuous_backup = true + lifecycle = { + delete_after = 35 + } + copy_action = { + delete_after = 365 + } + } + ] } } -resource "aws_cloudwatch_event_rule" "aws_backup_post_build_version_event_rule" { - name = "${var.name_prefix}-post-build-version-rule" - description = "Triggers the lambda on successful AWS Backup job completion." +variable "iam_role_permissions_boundary" { + description = "Optional permissions boundary ARN for backup role" + type = string + default = "" # Empty by default +} - event_pattern = jsonencode({ - "source": ["aws.backup"], - "detail-type": ["Backup Job State Change"], - "detail": { - "state": ["COMPLETED"] - } - }) +variable "api_endpoint" { + description = "API endpoint to send post build version notifications to" + type = string + default = "" +} + +variable "lambda_copy_recovery_point_enable" { + description = "Flag to enable the copy recovery point lambda (copy recovery point from destination vault back to source)." + type = bool + default = false +} + +variable "lambda_copy_recovery_point_poll_interval_seconds" { + description = "Polling interval in seconds for copy job status checks." + type = number + default = 30 +} + +variable "lambda_copy_recovery_point_max_wait_minutes" { + description = "Maximum number of minutes to wait for a copy job to reach a terminal state before returning running status." + type = number + default = 10 +} + +variable "lambda_copy_recovery_point_destination_vault_arn" { + description = "Destination vault ARN containing the recovery point to be copied back (the air-gapped vault)." + type = string + default = "" } -resource "aws_cloudwatch_event_target" "lambda_post_build_version_target" { - rule = aws_cloudwatch_event_rule.aws_backup_post_build_version_event_rule.name - arn = aws_lambda_function.lambda_post_build_version.arn - target_id = "${var.name_prefix}postBuildVersionLambdaTarget" +variable "api_token" { + description = "API token to authenticate with the API endpoint" + type = string + default = "" } -resource "aws_lambda_permission" "post_build_allow_eventbridge" { - statement_id = "${var.name_prefix}AllowExecutionFromEventbridge" - action = "lambda:InvokeFunction" - function_name = aws_lambda_function.lambda_post_build_version.function_name - principal = "events.amazonaws.com" +variable "lambda_copy_recovery_point_source_vault_arn" { + description = "Source vault ARN to which the recovery point will be copied back." + type = string + default = "" +} + +variable "lambda_copy_recovery_point_assume_role_arn" { + description = "ARN of role in destination account the lambda assumes to initiate the copy job (if required for cross-account)." + type = string + default = "" +} + +variable "destination_parameter_store_kms_key_arn" { + description = "The ARN of the KMS key used to encrypt Parameter Store backups." + type = string + default = "" +} + +variable "lambda_restore_to_s3_enable" { + description = "Enable the Lambda function to restore Parameter Store backups to S3." + type = bool + default = false +} - source_arn = aws_cloudwatch_event_rule.aws_backup_post_build_version_event_rule.arn +variable "lambda_restore_to_s3_poll_interval_seconds" { + description = "Poll interval in seconds for checking the status of the restore job." + type = number + default = 30 } -resource "aws_cloudwatch_log_group" "post_build_version_logs" { - name = "/aws/lambda/${var.name_prefix}-post_build_version" - retention_in_days = 30 +variable "lambda_restore_to_s3_max_wait_minutes" { + description = "Maximum wait time in minutes for the restore job to complete." + type = number + default = 5 } From c42d1a36a364a404051eb92b4b801cf66818eb75 Mon Sep 17 00:00:00 2001 From: Michel Fasen Date: Thu, 19 Feb 2026 15:53:33 +0000 Subject: [PATCH 09/11] Fixed selection tags for restore testing --- modules/aws-backup-source/backup_restore_testing.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/aws-backup-source/backup_restore_testing.tf b/modules/aws-backup-source/backup_restore_testing.tf index b6389fc..2e94756 100644 --- a/modules/aws-backup-source/backup_restore_testing.tf +++ b/modules/aws-backup-source/backup_restore_testing.tf @@ -20,7 +20,7 @@ resource "awscc_backup_restore_testing_selection" "backup_restore_testing_select protected_resource_conditions = { string_equals = [{ key = "aws:ResourceTag/${var.backup_plan_config_dynamodb.selection_tag}" - value = "True" + value = (var.backup_plan_config_dynamodb.selection_tag_value == null) ? "True" : var.backup_plan_config_dynamodb.selection_tag_value }] } } @@ -36,7 +36,7 @@ resource "awscc_backup_restore_testing_selection" "backup_restore_testing_select protected_resource_conditions = { string_equals = [{ key = "aws:ResourceTag/${var.backup_plan_config_ebsvol.selection_tag}" - value = "True" + value = (var.backup_plan_config_ebsvol.selection_tag_value == null) ? "True" : var.backup_plan_config_ebsvol.selection_tag_value }] } } @@ -51,7 +51,7 @@ resource "awscc_backup_restore_testing_selection" "backup_restore_testing_select protected_resource_conditions = { string_equals = [{ key = "aws:ResourceTag/${var.backup_plan_config_aurora.selection_tag}" - value = "True" + value = (var.backup_plan_config_aurora.selection_tag_value == null) ? "True" : var.backup_plan_config_aurora.selection_tag_value }] } restore_metadata_overrides = local.aurora_overrides From 3d459cd60b72c1357de56d68b01d294e06646be7 Mon Sep 17 00:00:00 2001 From: Michel Fasen Date: Thu, 26 Feb 2026 13:16:45 +0000 Subject: [PATCH 10/11] Add support for additional SNS subscribers --- modules/aws-backup-source/README.md | 9 ++++++++- modules/aws-backup-source/backup_notification.tf | 2 +- modules/aws-backup-source/locals.tf | 5 +++-- modules/aws-backup-source/outputs.tf | 10 ++++++++-- modules/aws-backup-source/sns.tf | 10 +++++++++- modules/aws-backup-source/variables.tf | 12 +++++++++++- 6 files changed, 40 insertions(+), 8 deletions(-) diff --git a/modules/aws-backup-source/README.md b/modules/aws-backup-source/README.md index 2bcc0ec..ff6be3e 100644 --- a/modules/aws-backup-source/README.md +++ b/modules/aws-backup-source/README.md @@ -59,6 +59,7 @@ No modules. | [aws_kms_key.aws_backup_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | | [aws_sns_topic.backup](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource | | [aws_sns_topic_subscription.aws_backup_notifications_email_target](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_subscription) | resource | +| [aws_sns_topic_subscription.aws_backup_notifications](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_subscription) | resource | | [awscc_backup_restore_testing_plan.backup_restore_testing_plan](https://registry.terraform.io/providers/hashicorp/awscc/latest/docs/resources/backup_restore_testing_plan) | resource | | [awscc_backup_restore_testing_selection.backup_restore_testing_selection_dynamodb](https://registry.terraform.io/providers/hashicorp/awscc/latest/docs/resources/backup_restore_testing_selection) | resource | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | @@ -83,6 +84,7 @@ No modules. | [environment\_name](#input\_environment\_name) | The name of the environment where AWS Backup is configured. | `string` | n/a | yes | | [name\_prefix](#input\_name\_prefix) | Optional name prefix for vault resources | `string` | `null` | no | | [notifications\_target\_email\_address](#input\_notifications\_target\_email\_address) | The email address to which backup notifications will be sent via SNS. | `string` | `""` | no | +| [notifications\_target](#input\_notifications\_target) | Additional endpoints to send backup notifications via SNS | `string` | `""` | no | | [project\_name](#input\_project\_name) | The name of the project this relates to. | `string` | n/a | yes | | [reports\_bucket](#input\_reports\_bucket) | Bucket to drop backup reports into | `string` | n/a | yes | | [restore\_testing\_plan\_algorithm](#input\_restore\_testing\_plan\_algorithm) | Algorithm of the Recovery Selection Point | `string` | `"LATEST_WITHIN_WINDOW"` | no | @@ -94,5 +96,10 @@ No modules. ## Outputs -No outputs. +| Name | Description | +|---------------------------------| -------------------------| +| backup_role_arn | ARN of the of the backup role | +| backup_vault_arn | ARN of the of the Backup Vault | +| backup_vault_name | Name of the of the Backup Vault | +| backup_sns_topic_arn | ARN of SNS topic to which the Backup events are being send to | diff --git a/modules/aws-backup-source/backup_notification.tf b/modules/aws-backup-source/backup_notification.tf index cb71232..f6f2350 100644 --- a/modules/aws-backup-source/backup_notification.tf +++ b/modules/aws-backup-source/backup_notification.tf @@ -1,5 +1,5 @@ resource "aws_backup_vault_notifications" "backup_notification" { - count = var.notifications_target_email_address != "" ? 1 : 0 + count = local.enable_sns_notifications ? 1 : 0 backup_vault_name = aws_backup_vault.main.name sns_topic_arn = aws_sns_topic.backup[0].arn backup_vault_events = [ diff --git a/modules/aws-backup-source/locals.tf b/modules/aws-backup-source/locals.tf index b3d5d78..d241f0e 100644 --- a/modules/aws-backup-source/locals.tf +++ b/modules/aws-backup-source/locals.tf @@ -16,6 +16,7 @@ locals { var.backup_plan_config_parameter_store.enable ? [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] + 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] + enable_sns_notifications = var.notifications_target_email_address != "" || var.notifications_targets != {} } diff --git a/modules/aws-backup-source/outputs.tf b/modules/aws-backup-source/outputs.tf index 96ab936..d998360 100644 --- a/modules/aws-backup-source/outputs.tf +++ b/modules/aws-backup-source/outputs.tf @@ -5,10 +5,16 @@ output "backup_role_arn" { output "backup_vault_arn" { value = aws_backup_vault.main.arn - description = "ARN of the of the vault" + description = "ARN of the of the Backup Vault" } output "backup_vault_name" { value = aws_backup_vault.main.name - description = "Name of the of the vault" + description = "Name of the of the Backup Vault" } + +output "backup_sns_topic_arn" { + value = local.enable_sns_notifications ? aws_sns_topic.backup[0].arn : null + description = "ARN of SNS topic to which the Backup events are being send to" +} + diff --git a/modules/aws-backup-source/sns.tf b/modules/aws-backup-source/sns.tf index f1e4286..c486b2c 100644 --- a/modules/aws-backup-source/sns.tf +++ b/modules/aws-backup-source/sns.tf @@ -1,5 +1,5 @@ resource "aws_sns_topic" "backup" { - count = var.notifications_target_email_address != "" ? 1 : 0 + count = local.enable_sns_notifications ? 1 : 0 name = "${var.name_prefix}-notifications" kms_master_key_id = var.bootstrap_kms_key_arn policy = data.aws_iam_policy_document.allow_backup_to_sns.json @@ -33,3 +33,11 @@ resource "aws_sns_topic_subscription" "aws_backup_notifications_email_target" { endpoint = var.notifications_target_email_address filter_policy = jsonencode({ "State" : [{ "anything-but" : "COMPLETED" }] }) } + +resource "aws_sns_topic_subscription" "aws_backup_notifications_targets" { + for_each = var.notifications_targets + topic_arn = aws_sns_topic.backup[0].arn + protocol = each.value.protocol + endpoint = each.value.endpoint + filter_policy = jsonencode({ "State" : [{ "anything-but" : "COMPLETED" }] }) +} diff --git a/modules/aws-backup-source/variables.tf b/modules/aws-backup-source/variables.tf index 25a13fe..c5d61c5 100644 --- a/modules/aws-backup-source/variables.tf +++ b/modules/aws-backup-source/variables.tf @@ -9,11 +9,21 @@ variable "environment_name" { } variable "notifications_target_email_address" { - description = "The email address to which backup notifications will be sent via SNS." + description = "The email address to which backup notifications will be sent via SNS" type = string default = "" } + +variable "notifications_targets" { + description = "Additional endpoints to send backup notifications via SNS" + type = map(object({ + protocol = string + endpoint = string + })) + default = {} +} + variable "bootstrap_kms_key_arn" { description = "The ARN of the bootstrap KMS key used for encryption at rest of the SNS topic." type = string From f8f637f1f533838fd744f11a512cc6d26be259f6 Mon Sep 17 00:00:00 2001 From: Michel Fasen Date: Wed, 25 Mar 2026 11:56:22 +0000 Subject: [PATCH 11/11] Resolve conflicts --- modules/aws-backup-source/README.md | 50 +++++++++++++++-------------- modules/aws-backup-source/sns.tf | 2 +- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/modules/aws-backup-source/README.md b/modules/aws-backup-source/README.md index ff6be3e..655cac5 100644 --- a/modules/aws-backup-source/README.md +++ b/modules/aws-backup-source/README.md @@ -16,11 +16,12 @@ module "test_aws_backup" { } ``` + ## Requirements | Name | Version | -|------|---------| +| --- | --- | | [terraform](#requirement\_terraform) | >= 1.9.5 | | [archive](#requirement\_archive) | ~> 2 | | [aws](#requirement\_aws) | ~> 5 | @@ -29,7 +30,7 @@ module "test_aws_backup" { ## Providers | Name | Version | -|------|---------| +| --- | --- | | [aws](#provider\_aws) | ~> 5 | | [awscc](#provider\_awscc) | ~> 1 | @@ -40,7 +41,7 @@ No modules. ## Resources | Name | Type | -|------|------| +| --- | --- | | [aws_backup_framework.dynamodb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_framework) | resource | | [aws_backup_framework.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_framework) | resource | | [aws_backup_plan.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_plan) | resource | @@ -72,27 +73,27 @@ No modules. ## Inputs -| Name | Description | Type | Default | Required | -|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------|------|---------|:--------:| -| [backup\_copy\_vault\_account\_id](#input\_backup\_copy\_vault\_account\_id) | The account id of the destination backup vault for allowing restores back into the source account. | `string` | `""` | no | -| [backup\_copy\_vault\_arn](#input\_backup\_copy\_vault\_arn) | The ARN of the destination backup vault for cross-account backup copies. | `string` | `""` | no | -| [backup\_plan\_config](#input\_backup\_plan\_config) | Configuration for backup plans |
object({
selection_tag = string
selection_tag_value = optional(string)
selection_tags = optional(list(object({
key = optional(string)
value = optional(string)
})))
compliance_resource_types = list(string)
rules = list(object({
name = string
schedule = string
enable_continuous_backup = optional(bool)
lifecycle = object({
delete_after = optional(number)
cold_storage_after = optional(number)
})
copy_action = optional(object({
delete_after = optional(number)
}))
}))
})
|
{
"compliance_resource_types": [
"S3"
],
"rules": [
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"delete_after": 35
},
"name": "daily_kept_5_weeks",
"schedule": "cron(0 0 * * ? *)"
},
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"delete_after": 90
},
"name": "weekly_kept_3_months",
"schedule": "cron(0 1 ? * SUN *)"
},
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"cold_storage_after": 30,
"delete_after": 2555
},
"name": "monthly_kept_7_years",
"schedule": "cron(0 2 1 * ? *)"
},
{
"copy_action": {
"delete_after": 365
},
"enable_continuous_backup": true,
"lifecycle": {
"delete_after": 35
},
"name": "point_in_time_recovery",
"schedule": "cron(0 5 * * ? *)"
}
],
"selection_tag": "BackupLocal",
"selection_tag_value": "True",
"selection_tags": []
}
| no | -| [backup\_plan\_config\_dynamodb](#input\_backup\_plan\_config\_dynamodb) | Configuration for backup plans with dynamodb |
object({
enable = bool
selection_tag = string
selection_tag_value = optional(string)
selection_tags = optional(list(object({
key = optional(string)
value = optional(string)
})))
compliance_resource_types = list(string)
rules = optional(list(object({
name = string
schedule = string
enable_continuous_backup = optional(bool)
lifecycle = object({
delete_after = number
cold_storage_after = optional(number)
})
copy_action = optional(object({
delete_after = optional(number)
}))
})))
})
|
{
"compliance_resource_types": [
"DynamoDB"
],
"enable": true,
"rules": [
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"delete_after": 35
},
"name": "dynamodb_daily_kept_5_weeks",
"schedule": "cron(0 0 * * ? *)"
},
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"delete_after": 90
},
"name": "dynamodb_weekly_kept_3_months",
"schedule": "cron(0 1 ? * SUN *)"
},
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"cold_storage_after": 30,
"delete_after": 2555
},
"name": "dynamodb_monthly_kept_7_years",
"schedule": "cron(0 2 1 * ? *)"
}
],
"selection_tag": "BackupDynamoDB",
"selection_tag_value": "True",
"selection_tags": []
}
| no | -| [backup_plan_config_aurora](#input_backup_plan_config_aurora) | Configuration for backup plans with aurora |
object({
enable = bool
selection_tag = string
compliance_resource_types = list(string)
restore_testing_overrides = optional(string)
rules = optional(list(object({
name = string
schedule = string
enable_continuous_backup = optional(bool)
lifecycle = object({
delete_after = number
cold_storage_after = optional(number)
})
copy_action = optional(object({
delete_after = optional(number)
}))
})))
})
|
{
"compliance_resource_types": [
"Aurora"
],
"enable": true,
"restore_testing_overrides" : "{\"dbsubnetgroupname\": \"test-subnet\"}",
"rules": [
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"delete_after": 35
},
"name": "aurora_daily_kept_5_weeks",
"schedule": "cron(0 0 * * ? *)"
},
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"delete_after": 90
},
"name": "aurora_weekly_kept_3_months",
"schedule": "cron(0 1 ? * SUN *)"
},
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"cold_storage_after": 30,
"delete_after": 2555
},
"name": "aurora_monthly_kept_7_years",
"schedule": "cron(0 2 1 * ? *)"
}
],
"selection_tag": "BackupAurora"
}
| no | -| [parameter_store_backup_config](#input_backup_plan_config_parameter_store) | Configuration for the scheduled Lambda function to backup tagged Parameter Store parameters to S3. |
object({ enable = bool selection_tag = string selection_tag_value = optional(string) selection_tags = optional(list(object({ key = optional(string) value = optional(string) }))) lambda_backup_cron = optional(string) lambda_timeout_seconds = optional(number) air_gapped_kms_key_arn = optional(string) s3_bucket_name = optional(string) rules = optional(list(object({ name = string schedule = string completion_window = optional(number) enable_continuous_backup = optional(bool) lifecycle = object({ delete_after = number cold_storage_after = optional(number) }) copy_action = optional(object({ delete_after = optional(number) })) }))) })
|
{ "air_gapped_kms_key_arn": "", "enable": true, "lambda_backup_cron": "cron(0 6 * * ? *)", "lambda_timeout_seconds": 300, "rules": [ { "copy_action": { "delete_after": 365 }, "lifecycle": { "delete_after": 35 }, "name": "daily_kept_5_weeks", "schedule": "cron(0 0 * * ? *)" }, { "copy_action": { "delete_after": 365 }, "lifecycle": { "delete_after": 90 }, "name": "weekly_kept_3_months", "schedule": "cron(0 1 ? * SUN *)" }, { "copy_action": { "delete_after": 365 }, "lifecycle": { "cold_storage_after": 30, "delete_after": 2555 }, "name": "monthly_kept_7_years", "schedule": "cron(0 2 1 * ? *)" }, { "copy_action": { "delete_after": 365 }, "enable_continuous_backup": true, "lifecycle": { "delete_after": 35 }, "name": "point_in_time_recovery", "schedule": "cron(0 5 * * ? *)" } ], "s3_bucket_name": "", "selection_tag": "BackupParameterStore", "selection_tag_value": "True", "selection_tags": [] }
| no | -| [bootstrap\_kms\_key\_arn](#input\_bootstrap\_kms\_key\_arn) | The ARN of the bootstrap KMS key used for encryption at rest of the SNS topic. | `string` | n/a | yes | -| [environment\_name](#input\_environment\_name) | The name of the environment where AWS Backup is configured. | `string` | n/a | yes | -| [name\_prefix](#input\_name\_prefix) | Optional name prefix for vault resources | `string` | `null` | no | -| [notifications\_target\_email\_address](#input\_notifications\_target\_email\_address) | The email address to which backup notifications will be sent via SNS. | `string` | `""` | no | -| [notifications\_target](#input\_notifications\_target) | Additional endpoints to send backup notifications via SNS | `string` | `""` | no | -| [project\_name](#input\_project\_name) | The name of the project this relates to. | `string` | n/a | yes | -| [reports\_bucket](#input\_reports\_bucket) | Bucket to drop backup reports into | `string` | n/a | yes | -| [restore\_testing\_plan\_algorithm](#input\_restore\_testing\_plan\_algorithm) | Algorithm of the Recovery Selection Point | `string` | `"LATEST_WITHIN_WINDOW"` | no | -| [restore\_testing\_plan\_recovery\_point\_types](#input\_restore\_testing\_plan\_recovery\_point\_types) | Recovery Point Types | `list(string)` |
[
"SNAPSHOT"
]
| no | -| [restore\_testing\_plan\_scheduled\_expression](#input\_restore\_testing\_plan\_scheduled\_expression) | Scheduled Expression of Recovery Selection Point | `string` | `"cron(0 1 ? * SUN *)"` | no | -| [restore\_testing\_plan\_selection\_window\_days](#input\_restore\_testing\_plan\_selection\_window\_days) | Selection window days | `number` | `7` | no | -| [restore\_testing\_plan\_start\_window](#input\_restore\_testing\_plan\_start\_window) | Start window from the scheduled time during which the test should start | `number` | `1` | no | -| [terraform\_role\_arn](#input\_terraform\_role\_arn) | ARN of Terraform role used to deploy to account | `string` | n/a | yes | +| Name | Description | Type | Default | Required | +| --- | --- | --- | --- | :---: | +| [backup\_copy\_vault\_account\_id](#input\_backup\_copy\_vault\_account\_id) | The account id of the destination backup vault for allowing restores back into the source account. | `string` | `""` | no | +| [backup\_copy\_vault\_arn](#input\_backup\_copy\_vault\_arn) | The ARN of the destination backup vault for cross-account backup copies. | `string` | `""` | no | +| [backup\_plan\_config](#input\_backup\_plan\_config) | Configuration for backup plans |
object({
selection_tag = string
selection_tag_value = optional(string)
selection_tags = optional(list(object({
key = optional(string)
value = optional(string)
})))
compliance_resource_types = list(string)
rules = list(object({
name = string
schedule = string
enable_continuous_backup = optional(bool)
lifecycle = object({
delete_after = optional(number)
cold_storage_after = optional(number)
})
copy_action = optional(object({
delete_after = optional(number)
}))
}))
})
|
{
"compliance_resource_types": [
"S3"
],
"rules": [
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"delete_after": 35
},
"name": "daily_kept_5_weeks",
"schedule": "cron(0 0 * * ? *)"
},
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"delete_after": 90
},
"name": "weekly_kept_3_months",
"schedule": "cron(0 1 ? * SUN *)"
},
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"cold_storage_after": 30,
"delete_after": 2555
},
"name": "monthly_kept_7_years",
"schedule": "cron(0 2 1 * ? *)"
},
{
"copy_action": {
"delete_after": 365
},
"enable_continuous_backup": true,
"lifecycle": {
"delete_after": 35
},
"name": "point_in_time_recovery",
"schedule": "cron(0 5 * * ? *)"
}
],
"selection_tag": "BackupLocal",
"selection_tag_value": "True",
"selection_tags": []
}
| no | +| [backup\_plan\_config\_dynamodb](#input\_backup\_plan\_config\_dynamodb) | Configuration for backup plans with dynamodb |
object({
enable = bool
selection_tag = string
selection_tag_value = optional(string)
selection_tags = optional(list(object({
key = optional(string)
value = optional(string)
})))
compliance_resource_types = list(string)
rules = optional(list(object({
name = string
schedule = string
enable_continuous_backup = optional(bool)
lifecycle = object({
delete_after = number
cold_storage_after = optional(number)
})
copy_action = optional(object({
delete_after = optional(number)
}))
})))
})
|
{
"compliance_resource_types": [
"DynamoDB"
],
"enable": true,
"rules": [
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"delete_after": 35
},
"name": "dynamodb_daily_kept_5_weeks",
"schedule": "cron(0 0 * * ? *)"
},
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"delete_after": 90
},
"name": "dynamodb_weekly_kept_3_months",
"schedule": "cron(0 1 ? * SUN *)"
},
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"cold_storage_after": 30,
"delete_after": 2555
},
"name": "dynamodb_monthly_kept_7_years",
"schedule": "cron(0 2 1 * ? *)"
}
],
"selection_tag": "BackupDynamoDB",
"selection_tag_value": "True",
"selection_tags": []
}
| no | +| [backup_plan_config_aurora](#input_backup_plan_config_aurora) | Configuration for backup plans with aurora |
object({
enable = bool
selection_tag = string
compliance_resource_types = list(string)
restore_testing_overrides = optional(string)
rules = optional(list(object({
name = string
schedule = string
enable_continuous_backup = optional(bool)
lifecycle = object({
delete_after = number
cold_storage_after = optional(number)
})
copy_action = optional(object({
delete_after = optional(number)
}))
})))
})
|
{
"compliance_resource_types": [
"Aurora"
],
"enable": true,
"restore_testing_overrides" : "{\"dbsubnetgroupname\": \"test-subnet\"}",
"rules": [
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"delete_after": 35
},
"name": "aurora_daily_kept_5_weeks",
"schedule": "cron(0 0 * * ? *)"
},
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"delete_after": 90
},
"name": "aurora_weekly_kept_3_months",
"schedule": "cron(0 1 ? * SUN *)"
},
{
"copy_action": {
"delete_after": 365
},
"lifecycle": {
"cold_storage_after": 30,
"delete_after": 2555
},
"name": "aurora_monthly_kept_7_years",
"schedule": "cron(0 2 1 * ? *)"
}
],
"selection_tag": "BackupAurora"
}
| no | +| [parameter_store_backup_config](#input_parameter_store_backup_config) | Configuration for the scheduled Lambda function to backup tagged Parameter Store parameters to S3. |
object({ enable = bool selection_tag = string selection_tag_value = optional(string) selection_tags = optional(list(object({ key = optional(string) value = optional(string) }))) lambda_backup_cron = optional(string) lambda_timeout_seconds = optional(number) air_gapped_kms_key_arn = optional(string) s3_bucket_name = optional(string) rules = optional(list(object({ name = string schedule = string completion_window = optional(number) enable_continuous_backup = optional(bool) lifecycle = object({ delete_after = number cold_storage_after = optional(number) }) copy_action = optional(object({ delete_after = optional(number) })) }))) })
|
{ "air_gapped_kms_key_arn": "", "enable": true, "lambda_backup_cron": "cron(0 6 * * ? *)", "lambda_timeout_seconds": 300, "rules": [ { "copy_action": { "delete_after": 365 }, "lifecycle": { "delete_after": 35 }, "name": "daily_kept_5_weeks", "schedule": "cron(0 0 * * ? *)" }, { "copy_action": { "delete_after": 365 }, "lifecycle": { "delete_after": 90 }, "name": "weekly_kept_3_months", "schedule": "cron(0 1 ? * SUN *)" }, { "copy_action": { "delete_after": 365 }, "lifecycle": { "cold_storage_after": 30, "delete_after": 2555 }, "name": "monthly_kept_7_years", "schedule": "cron(0 2 1 * ? *)" }, { "copy_action": { "delete_after": 365 }, "enable_continuous_backup": true, "lifecycle": { "delete_after": 35 }, "name": "point_in_time_recovery", "schedule": "cron(0 5 * * ? *)" } ], "s3_bucket_name": "", "selection_tag": "BackupParameterStore", "selection_tag_value": "True", "selection_tags": [] }
| no | +| [bootstrap\_kms\_key\_arn](#input\_bootstrap\_kms\_key\_arn) | The ARN of the bootstrap KMS key used for encryption at rest of the SNS topic. | `string` | n/a | yes | +| [environment\_name](#input\_environment\_name) | The name of the environment where AWS Backup is configured. | `string` | n/a | yes | +| [name\_prefix](#input\_name\_prefix) | Optional name prefix for vault resources | `string` | `null` | no | +| [notifications\_target\_email\_address](#input\_notifications\_target\_email\_address) | The email address to which backup notifications will be sent via SNS. | `string` | `""` | no | +| [notifications\_target](#input\_notifications\_target) | Additional endpoints to send backup notifications via SNS | `string` | `""` | no | +| [project\_name](#input\_project\_name) | The name of the project this relates to. | `string` | n/a | yes | +| [reports\_bucket](#input\_reports\_bucket) | Bucket to drop backup reports into | `string` | n/a | yes | +| [restore\_testing\_plan\_algorithm](#input\_restore\_testing\_plan\_algorithm) | Algorithm of the Recovery Selection Point | `string` | `"LATEST_WITHIN_WINDOW"` | no | +| [restore\_testing\_plan\_recovery\_point\_types](#input\_restore\_testing\_plan\_recovery\_point\_types) | Recovery Point Types | `list(string)` |
[
"SNAPSHOT"
]
| no | +| [restore\_testing\_plan\_scheduled\_expression](#input\_restore\_testing\_plan\_scheduled\_expression) | Scheduled Expression of Recovery Selection Point | `string` | `"cron(0 1 ? * SUN *)"` | no | +| [restore\_testing\_plan\_selection\_window\_days](#input\_restore\_testing\_plan\_selection\_window\_days) | Selection window days | `number` | `7` | no | +| [restore\_testing\_plan\_start\_window](#input\_restore\_testing\_plan\_start\_window) | Start window from the scheduled time during which the test should start | `number` | `1` | no | +| [terraform\_role\_arn](#input\_terraform\_role\_arn) | ARN of Terraform role used to deploy to account | `string` | n/a | yes | ## Outputs @@ -103,3 +104,4 @@ No modules. | backup_vault_name | Name of the of the Backup Vault | | backup_sns_topic_arn | ARN of SNS topic to which the Backup events are being send to | + diff --git a/modules/aws-backup-source/sns.tf b/modules/aws-backup-source/sns.tf index c486b2c..307c617 100644 --- a/modules/aws-backup-source/sns.tf +++ b/modules/aws-backup-source/sns.tf @@ -1,6 +1,6 @@ resource "aws_sns_topic" "backup" { count = local.enable_sns_notifications ? 1 : 0 - name = "${var.name_prefix}-notifications" + name = "${local.resource_name_prefix}-notifications" kms_master_key_id = var.bootstrap_kms_key_arn policy = data.aws_iam_policy_document.allow_backup_to_sns.json }