From b9218b4cafa8a0d588077c133b6131c9161425a2 Mon Sep 17 00:00:00 2001 From: Michel Fasen Date: Mon, 9 Feb 2026 13:32:08 +0000 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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