diff --git a/modules/aws-backup-source/README.md b/modules/aws-backup-source/README.md index 2a1c461..f650c21 100644 --- a/modules/aws-backup-source/README.md +++ b/modules/aws-backup-source/README.md @@ -49,7 +49,9 @@ No modules. | [aws_backup_selection.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_selection) | resource | | [aws_backup_selection.dynamodb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_selection) | resource | | [aws_backup_vault.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault) | resource | +| [aws_backup_logically_air_gapped_vault.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_logically_air_gapped_vault) | resource | | [aws_backup_vault_notifications.backup_notification](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault_notifications) | resource | +| [aws_backup_vault_notifications.backup_notification_lag](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault_notifications) | resource | | [aws_backup_vault_policy.vault_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault_policy) | resource | | [aws_iam_role.backup](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role_policy_attachment.backup](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | @@ -95,6 +97,13 @@ 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 | +| logically_air_gapped_vault_arn | ARN of the of the Logically Air-gapped Vault | +| logically_air_gapped_vault_name | Name of the of the Logically Air-gapped Vault | + diff --git a/modules/aws-backup-source/backup_framework.tf b/modules/aws-backup-source/backup_framework.tf index 19a2803..9d2a586 100644 --- a/modules/aws-backup-source/backup_framework.tf +++ b/modules/aws-backup-source/backup_framework.tf @@ -66,7 +66,7 @@ resource "aws_backup_framework" "main" { input_parameter { name = "requiredRetentionDays" - value = "35" + value = var.vault_lock_min_retention_days } } @@ -87,7 +87,7 @@ resource "aws_backup_framework" "main" { input_parameter { name = "requiredRetentionDays" - value = "35" + value = var.vault_lock_min_retention_days } input_parameter { diff --git a/modules/aws-backup-source/backup_notification.tf b/modules/aws-backup-source/backup_notification.tf index cb71232..f4f1e08 100644 --- a/modules/aws-backup-source/backup_notification.tf +++ b/modules/aws-backup-source/backup_notification.tf @@ -10,3 +10,16 @@ resource "aws_backup_vault_notifications" "backup_notification" { "COPY_JOB_FAILED" ] } + +resource "aws_backup_vault_notifications" "backup_notification_lag" { + count = var.enable_logically_air_gapped_vault && var.notifications_target_email_address != "" ? 1 : 0 + backup_vault_name = aws_backup_logically_air_gapped_vault.main[0].name + sns_topic_arn = aws_sns_topic.backup[0].arn + backup_vault_events = [ + "BACKUP_JOB_COMPLETED", + "RESTORE_JOB_COMPLETED", + "S3_BACKUP_OBJECT_FAILED", + "S3_RESTORE_OBJECT_FAILED", + "COPY_JOB_FAILED" + ] +} diff --git a/modules/aws-backup-source/backup_plan.tf b/modules/aws-backup-source/backup_plan.tf index 4e7a671..877d365 100644 --- a/modules/aws-backup-source/backup_plan.tf +++ b/modules/aws-backup-source/backup_plan.tf @@ -7,11 +7,12 @@ resource "aws_backup_plan" "default" { recovery_point_tags = { backup_rule_name = rule.value.name } - rule_name = rule.value.name - target_vault_name = aws_backup_vault.main.name - schedule = rule.value.schedule - completion_window = rule.value.completion_window - enable_continuous_backup = rule.value.enable_continuous_backup != null ? rule.value.enable_continuous_backup : null + rule_name = rule.value.name + target_vault_name = aws_backup_vault.main.name + target_logically_air_gapped_backup_vault_arn = var.enable_logically_air_gapped_vault ? aws_backup_logically_air_gapped_vault.main[0].arn : null + schedule = rule.value.schedule + completion_window = rule.value.completion_window + enable_continuous_backup = rule.value.enable_continuous_backup != null ? rule.value.enable_continuous_backup : null lifecycle { delete_after = rule.value.lifecycle.delete_after != null ? rule.value.lifecycle.delete_after : null cold_storage_after = rule.value.lifecycle.cold_storage_after != null ? rule.value.lifecycle.cold_storage_after : null @@ -40,10 +41,11 @@ resource "aws_backup_plan" "dynamodb" { recovery_point_tags = { backup_rule_name = rule.value.name } - rule_name = rule.value.name - target_vault_name = aws_backup_vault.main.name - schedule = rule.value.schedule - completion_window = rule.value.completion_window + rule_name = rule.value.name + target_vault_name = aws_backup_vault.main.name + target_logically_air_gapped_backup_vault_arn = var.enable_logically_air_gapped_vault ? aws_backup_logically_air_gapped_vault.main[0].arn : null + schedule = rule.value.schedule + completion_window = rule.value.completion_window lifecycle { delete_after = rule.value.lifecycle.delete_after != null ? rule.value.lifecycle.delete_after : null cold_storage_after = rule.value.lifecycle.cold_storage_after != null ? rule.value.lifecycle.cold_storage_after : null @@ -71,9 +73,10 @@ resource "aws_backup_plan" "ebsvol" { recovery_point_tags = { backup_rule_name = rule.value.name } - rule_name = rule.value.name - target_vault_name = aws_backup_vault.main.name - schedule = rule.value.schedule + rule_name = rule.value.name + target_vault_name = aws_backup_vault.main.name + target_logically_air_gapped_backup_vault_arn = var.enable_logically_air_gapped_vault ? aws_backup_logically_air_gapped_vault.main[0].arn : null + schedule = rule.value.schedule lifecycle { delete_after = rule.value.lifecycle.delete_after != null ? rule.value.lifecycle.delete_after : null cold_storage_after = rule.value.lifecycle.cold_storage_after != null ? rule.value.lifecycle.cold_storage_after : null @@ -102,9 +105,10 @@ resource "aws_backup_plan" "aurora" { recovery_point_tags = { backup_rule_name = rule.value.name } - rule_name = rule.value.name - target_vault_name = aws_backup_vault.main.name - schedule = rule.value.schedule + rule_name = rule.value.name + target_vault_name = aws_backup_vault.main.name + target_logically_air_gapped_backup_vault_arn = var.enable_logically_air_gapped_vault ? aws_backup_logically_air_gapped_vault.main[0].arn : null + schedule = rule.value.schedule lifecycle { delete_after = rule.value.lifecycle.delete_after != null ? rule.value.lifecycle.delete_after : null cold_storage_after = rule.value.lifecycle.cold_storage_after != null ? rule.value.lifecycle.cold_storage_after : null @@ -133,11 +137,12 @@ resource "aws_backup_plan" "parameter_store" { recovery_point_tags = { backup_rule_name = rule.value.name } - rule_name = rule.value.name - target_vault_name = aws_backup_vault.main.name - schedule = rule.value.schedule - completion_window = rule.value.completion_window - enable_continuous_backup = rule.value.enable_continuous_backup != null ? rule.value.enable_continuous_backup : null + rule_name = rule.value.name + target_vault_name = aws_backup_vault.main.name + target_logically_air_gapped_backup_vault_arn = var.enable_logically_air_gapped_vault ? aws_backup_logically_air_gapped_vault.main[0].arn : null + schedule = rule.value.schedule + completion_window = rule.value.completion_window + enable_continuous_backup = rule.value.enable_continuous_backup != null ? rule.value.enable_continuous_backup : null lifecycle { delete_after = rule.value.lifecycle.delete_after cold_storage_after = rule.value.lifecycle.cold_storage_after diff --git a/modules/aws-backup-source/backup_restore_testing.tf b/modules/aws-backup-source/backup_restore_testing.tf index b6389fc..685779d 100644 --- a/modules/aws-backup-source/backup_restore_testing.tf +++ b/modules/aws-backup-source/backup_restore_testing.tf @@ -4,7 +4,7 @@ resource "awscc_backup_restore_testing_plan" "backup_restore_testing_plan" { start_window_hours = var.restore_testing_plan_start_window recovery_point_selection = { algorithm = var.restore_testing_plan_algorithm - include_vaults = [aws_backup_vault.main.arn] + include_vaults = concat([aws_backup_vault.main.arn], (var.enable_logically_air_gapped_vault ? [aws_backup_logically_air_gapped_vault.main[0].arn] : [])) recovery_point_types = var.restore_testing_plan_recovery_point_types selection_window_days = var.restore_testing_plan_selection_window_days } @@ -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 diff --git a/modules/aws-backup-source/backup_vault.tf b/modules/aws-backup-source/backup_vault.tf index 49f79ca..c5aedd5 100644 --- a/modules/aws-backup-source/backup_vault.tf +++ b/modules/aws-backup-source/backup_vault.tf @@ -2,3 +2,10 @@ resource "aws_backup_vault" "main" { name = "${local.resource_name_prefix}-vault" kms_key_arn = aws_kms_key.aws_backup_key.arn } + +resource "aws_backup_logically_air_gapped_vault" "main" { + count = var.enable_logically_air_gapped_vault ? 1 : 0 + name = "${local.resource_name_prefix}-vault-lag" + min_retention_days = var.vault_lock_min_retention_days + max_retention_days = var.vault_lock_max_retention_days +} diff --git a/modules/aws-backup-source/kms.tf b/modules/aws-backup-source/kms.tf index e8a07a2..ae5d543 100644 --- a/modules/aws-backup-source/kms.tf +++ b/modules/aws-backup-source/kms.tf @@ -36,21 +36,24 @@ data "aws_iam_policy_document" "backup_key_policy" { actions = ["kms:*"] resources = ["*"] } - statement { - sid = "Allow attachment of persistent resources" - principals { - type = "AWS" - identifiers = ["arn:aws:iam::${var.backup_copy_vault_account_id}:root"] + dynamic "statement" { + for_each = var.backup_copy_vault_arn != "" && var.backup_copy_vault_account_id != "" ? [1] : [] + content { + sid = "Allow attachment of persistent resources" + principals { + type = "AWS" + identifiers = ["arn:aws:iam::${var.backup_copy_vault_account_id}:root"] + } + actions = [ + "kms:Encrypt", + "kms:Decrypt", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:CreateGrant", + "kms:ListGrants", + "kms:DescribeKey" + ] + resources = ["*"] } - actions = [ - "kms:Encrypt", - "kms:Decrypt", - "kms:ReEncrypt*", - "kms:GenerateDataKey*", - "kms:CreateGrant", - "kms:ListGrants", - "kms:DescribeKey" - ] - resources = ["*"] } } diff --git a/modules/aws-backup-source/outputs.tf b/modules/aws-backup-source/outputs.tf index 96ab936..0fc5ff6 100644 --- a/modules/aws-backup-source/outputs.tf +++ b/modules/aws-backup-source/outputs.tf @@ -5,10 +5,20 @@ 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 "logically_air_gapped_vault_arn" { + value = var.enable_logically_air_gapped_vault ? aws_backup_logically_air_gapped_vault.main[0].arn : null + description = "ARN of the of the Logically Air-gapped Vault" +} + +output "logically_air_gapped_vault_name" { + value = var.enable_logically_air_gapped_vault ? aws_backup_logically_air_gapped_vault.main[0].name : null + description = "Name of the of the Logically Air-gapped Vault" } diff --git a/modules/aws-backup-source/variables.tf b/modules/aws-backup-source/variables.tf index d99aa10..40a5475 100644 --- a/modules/aws-backup-source/variables.tf +++ b/modules/aws-backup-source/variables.tf @@ -520,3 +520,32 @@ variable "lambda_restore_to_s3_max_wait_minutes" { type = number default = 5 } + +variable "enable_logically_air_gapped_vault" { + description = "Enable backing up to Logically Air-gapped Vault for supported resources" + type = bool + default = false +} + +variable "vault_lock_min_retention_days" { + description = "The minimum retention period that the Backup Vault retains its recovery points" + type = number + default = 35 + + validation { + condition = var.vault_lock_min_retention_days >= 7 + error_message = "The minimum retention in days must be at least 7" + } +} + +variable "vault_lock_max_retention_days" { + description = "The maximum retention period that the Backup Vault retains its recovery points" + type = number + default = 365 +} + +variable "logically_air_gapped_vault_approval_team_arn" { + description = "The ARN of the Multi-party approval Team to be assigned to the logically air-gapped vault" + type = string + default = null +}