From e716136dbbdf78db2aa05cd8904b32659c0ea22c Mon Sep 17 00:00:00 2001 From: Kate Bobyn Date: Fri, 15 Nov 2024 11:43:32 +0000 Subject: [PATCH 01/13] NRL-853 WIP cloud backup setup --- .../dev/aws-backups.tf | 136 ++++++++++++ .../dev/dynamodb__pointers-table.tf | 5 +- .../account-wide-infrastructure/dev/s3.tf | 6 +- .../modules/backup-source/README.md | 37 ++++ .../modules/backup-source/backup_framework.tf | 149 +++++++++++++ .../backup-source/backup_notification.tf | 12 ++ .../modules/backup-source/backup_plan.tf | 85 ++++++++ .../backup-source/backup_report_plan.tf | 72 +++++++ .../backup_restore_testing.tf_disabled | 26 +++ .../modules/backup-source/backup_vault.tf | 4 + .../backup-source/backup_vault_policy.tf | 47 +++++ .../modules/backup-source/data.tf | 8 + .../modules/backup-source/iam.tf | 37 ++++ .../modules/backup-source/kms.tf | 34 +++ .../modules/backup-source/locals.tf | 3 + .../modules/backup-source/sns.tf | 35 +++ .../modules/backup-source/variables.tf | 199 ++++++++++++++++++ .../modules/permissions-store-bucket/vars.tf | 6 + .../modules/pointers-table/dynamodb.tf | 1 + .../modules/pointers-table/vars.tf | 6 + .../modules/truststore-bucket/vars.tf | 6 + .../modules/aws-backup-destination/README.md | 31 +++ .../modules/aws-backup-destination/backup.tf | 8 + .../backup_vault_lock.tf | 7 + .../backup_vault_policy.tf | 68 ++++++ .../aws-backup-destination/variables.tf | 67 ++++++ .../backup-infrastructure/test/aws-backup.tf | 42 ++++ terraform/backup-infrastructure/test/data.tf | 5 + .../backup-infrastructure/test/locals.tf | 8 + terraform/backup-infrastructure/test/main.tf | 14 ++ terraform/backup-infrastructure/test/vars.tf | 4 + 31 files changed, 1164 insertions(+), 4 deletions(-) create mode 100644 terraform/account-wide-infrastructure/dev/aws-backups.tf create mode 100644 terraform/account-wide-infrastructure/modules/backup-source/README.md create mode 100644 terraform/account-wide-infrastructure/modules/backup-source/backup_framework.tf create mode 100644 terraform/account-wide-infrastructure/modules/backup-source/backup_notification.tf create mode 100644 terraform/account-wide-infrastructure/modules/backup-source/backup_plan.tf create mode 100644 terraform/account-wide-infrastructure/modules/backup-source/backup_report_plan.tf create mode 100644 terraform/account-wide-infrastructure/modules/backup-source/backup_restore_testing.tf_disabled create mode 100644 terraform/account-wide-infrastructure/modules/backup-source/backup_vault.tf create mode 100644 terraform/account-wide-infrastructure/modules/backup-source/backup_vault_policy.tf create mode 100644 terraform/account-wide-infrastructure/modules/backup-source/data.tf create mode 100644 terraform/account-wide-infrastructure/modules/backup-source/iam.tf create mode 100644 terraform/account-wide-infrastructure/modules/backup-source/kms.tf create mode 100644 terraform/account-wide-infrastructure/modules/backup-source/locals.tf create mode 100644 terraform/account-wide-infrastructure/modules/backup-source/sns.tf create mode 100644 terraform/account-wide-infrastructure/modules/backup-source/variables.tf create mode 100644 terraform/backup-infrastructure/modules/aws-backup-destination/README.md create mode 100644 terraform/backup-infrastructure/modules/aws-backup-destination/backup.tf create mode 100644 terraform/backup-infrastructure/modules/aws-backup-destination/backup_vault_lock.tf create mode 100644 terraform/backup-infrastructure/modules/aws-backup-destination/backup_vault_policy.tf create mode 100644 terraform/backup-infrastructure/modules/aws-backup-destination/variables.tf create mode 100644 terraform/backup-infrastructure/test/aws-backup.tf create mode 100644 terraform/backup-infrastructure/test/data.tf create mode 100644 terraform/backup-infrastructure/test/locals.tf create mode 100644 terraform/backup-infrastructure/test/main.tf create mode 100644 terraform/backup-infrastructure/test/vars.tf diff --git a/terraform/account-wide-infrastructure/dev/aws-backups.tf b/terraform/account-wide-infrastructure/dev/aws-backups.tf new file mode 100644 index 000000000..09fd9da1f --- /dev/null +++ b/terraform/account-wide-infrastructure/dev/aws-backups.tf @@ -0,0 +1,136 @@ +provider "aws" { + alias = "source" + region = "eu-west-2" +} + +variable "destination_vault_arn" { + description = "ARN of the backup vault in the destination account" + type = string + default = "" +} + +#data "aws_arn" "destination_vault_arn" { +# arn = var.destination_vault_arn +#} + +data "aws_secretsmanager_secret" "backup-account-secret" { + name = "nhsd-nrlf--dev--test-backup-account-id" +} +data "aws_secretsmanager_secret_version" "destination_account_id" { + secret_id = data.aws_secretsmanager_secret.backup-account-secret.id +} + +locals { + # Adjust these as required + project_name = "dev-backups-poc" + environment_name = "dev" + + source_account_id = data.aws_caller_identity.current.account_id + # destination_account_id = data.aws_arn.destination_vault_arn.account + destination_account_id = data.aws_secretsmanager_secret_version.destination_account_id.secret_string +} + +# First, we create an S3 bucket for compliance reports. You may already have a module for creating +# S3 buckets with more refined access rules, which you may prefer to use. + +resource "aws_s3_bucket" "backup_reports" { + bucket_prefix = "${local.project_name}-backup-reports" +} + +# Now we have to configure access to the report bucket. + +resource "aws_s3_bucket_ownership_controls" "backup_reports" { + bucket = aws_s3_bucket.backup_reports.id + rule { + object_ownership = "BucketOwnerPreferred" + } +} + +resource "aws_s3_bucket_acl" "backup_reports" { + depends_on = [aws_s3_bucket_ownership_controls.backup_reports] + + bucket = aws_s3_bucket.backup_reports.id + acl = "private" +} + +# We need a key for the SNS topic that will be used for notifications from AWS Backup. This key +# will be used to encrypt the messages sent to the topic before they are sent to the subscribers, +# but isn't needed by the recipients of the messages. + +# First we need some contextual data +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} + +# Now we can define the key itself +resource "aws_kms_key" "backup_notifications" { + description = "KMS key for AWS Backup notifications" + deletion_window_in_days = 7 + enable_key_rotation = true + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Sid = "Enable IAM User Permissions" + Principal = { + AWS = "arn:aws:iam::${local.source_account_id}:root" + } + Action = "kms:*" + Resource = "*" + }, + { + Effect = "Allow" + Principal = { + Service = "sns.amazonaws.com" + } + Action = ["kms:GenerateDataKey*", "kms:Decrypt"] + Resource = "*" + }, + ] + }) +} + +# Now we can deploy the source and destination modules, referencing the resources we've created above. + +module "source" { + source = "../modules/backup-source" + + backup_copy_vault_account_id = local.destination_account_id + # backup_copy_vault_arn = data.aws_arn.destination_vault_arn.arn + environment_name = local.environment_name + bootstrap_kms_key_arn = aws_kms_key.backup_notifications.arn + project_name = local.project_name + reports_bucket = aws_s3_bucket.backup_reports.bucket + #terraform_role_arn = data.aws_caller_identity.current.arn + terraform_role_arn = "arn:aws:iam::${var.assume_account}:role/${var.assume_role}" + + backup_plan_config = { + "compliance_resource_types" : [ + "S3" + ], + "rules" : [ + { + "copy_action" : { + "delete_after" : 4 + }, + "lifecycle" : { + "delete_after" : 2 + }, + "name" : "daily_kept_for_2_days", + "schedule" : "cron(0 0 * * ? *)" + } + ], + "selection_tag" : "NHSE-Enable-Backup" + } + # Note here that we need to explicitly disable DynamoDB backups in the source account. + # The default config in the module enables backups for all resource types. + backup_plan_config_dynamodb = { + "compliance_resource_types" : [ + "DynamoDB" + ], + "rules" : [ + ], + "enable" : false, + "selection_tag" : "NHSE-Enable-Backup" + } +} diff --git a/terraform/account-wide-infrastructure/dev/dynamodb__pointers-table.tf b/terraform/account-wide-infrastructure/dev/dynamodb__pointers-table.tf index fccaa1b00..4a6403208 100644 --- a/terraform/account-wide-infrastructure/dev/dynamodb__pointers-table.tf +++ b/terraform/account-wide-infrastructure/dev/dynamodb__pointers-table.tf @@ -1,6 +1,7 @@ module "dev-pointers-table" { - source = "../modules/pointers-table" - name_prefix = "nhsd-nrlf--dev" + source = "../modules/pointers-table" + name_prefix = "nhsd-nrlf--dev" + enable_backups = true } module "dev-sandbox-pointers-table" { diff --git a/terraform/account-wide-infrastructure/dev/s3.tf b/terraform/account-wide-infrastructure/dev/s3.tf index 472189d41..b90bf677f 100644 --- a/terraform/account-wide-infrastructure/dev/s3.tf +++ b/terraform/account-wide-infrastructure/dev/s3.tf @@ -1,6 +1,7 @@ module "dev-permissions-store-bucket" { - source = "../modules/permissions-store-bucket" - name_prefix = "nhsd-nrlf--dev" + source = "../modules/permissions-store-bucket" + name_prefix = "nhsd-nrlf--dev" + enable_backups = true } module "dev-sandbox-permissions-store-bucket" { @@ -12,6 +13,7 @@ module "dev-truststore-bucket" { source = "../modules/truststore-bucket" name_prefix = "nhsd-nrlf--dev" server_certificate_file = "../../../truststore/server/dev.pem" + enable_backups = true } module "dev-sandbox-truststore-bucket" { diff --git a/terraform/account-wide-infrastructure/modules/backup-source/README.md b/terraform/account-wide-infrastructure/modules/backup-source/README.md new file mode 100644 index 000000000..3f1b8bdb6 --- /dev/null +++ b/terraform/account-wide-infrastructure/modules/backup-source/README.md @@ -0,0 +1,37 @@ +# AWS Backup Module + +The AWS Backup Module helps automates the setup of AWS Backup resources in a source account. It streamlines the process of creating, managing, and standardising backup configurations. + +## 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
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"
}
| no | +| [backup_plan_config_dynamodb](#input_backup_plan_config_dynamodb) | Configuration for backup plans with dynamodb |
object({
enable = bool
selection_tag = 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"
}
| 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 | +| [notifications_target_email_address](#input_notifications_target_email_address) | The email address to which backup notifications will be sent 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 | + +## Example + +```terraform +module "test_aws_backup" { + source = "./modules/aws-backup" + + environment_name = "environment_name" + bootstrap_kms_key_arn = kms_key[0].arn + project_name = "testproject" + reports_bucket = "compliance-reports" + terraform_role_arn = data.aws_iam_role.terraform_role.arn +} +``` diff --git a/terraform/account-wide-infrastructure/modules/backup-source/backup_framework.tf b/terraform/account-wide-infrastructure/modules/backup-source/backup_framework.tf new file mode 100644 index 000000000..d10b43137 --- /dev/null +++ b/terraform/account-wide-infrastructure/modules/backup-source/backup_framework.tf @@ -0,0 +1,149 @@ +resource "aws_backup_framework" "main" { + # must be underscores instead of dashes + name = replace("${local.resource_name_prefix}-framework", "-", "_") + description = "${var.project_name} Backup Framework" + + # Evaluates if recovery points are encrypted. + control { + name = "BACKUP_RECOVERY_POINT_ENCRYPTED" + + scope { + tags = { + "environment_name" = var.environment_name + } + } + } + + # Evaluates if backup vaults do not allow manual deletion of recovery points with the exception of certain IAM roles. + control { + name = "BACKUP_RECOVERY_POINT_MANUAL_DELETION_DISABLED" + + scope { + tags = { + "environment_name" = var.environment_name + } + } + + input_parameter { + name = "principalArnList" + value = var.terraform_role_arn + } + } + + # Evaluates if recovery point retention period is at least 1 month. + control { + name = "BACKUP_RECOVERY_POINT_MINIMUM_RETENTION_CHECK" + + scope { + tags = { + "environment_name" = var.environment_name + } + } + + input_parameter { + name = "requiredRetentionDays" + value = "35" + } + } + + # Evaluates if backup plan creates backups at least every 1 day and retains them for at least 1 month before deleting them. + control { + name = "BACKUP_PLAN_MIN_FREQUENCY_AND_MIN_RETENTION_CHECK" + + scope { + tags = { + "environment_name" = var.environment_name + } + } + + input_parameter { + name = "requiredFrequencyUnit" + value = "days" + } + + input_parameter { + name = "requiredRetentionDays" + value = "35" + } + + input_parameter { + name = "requiredFrequencyValue" + value = "1" + } + } + + # Evaluates if resources are protected by a backup plan. + control { + name = "BACKUP_RESOURCES_PROTECTED_BY_BACKUP_PLAN" + + scope { + compliance_resource_types = var.backup_plan_config.compliance_resource_types + tags = { + (var.backup_plan_config.selection_tag) = "True" + } + } + } + + # Evaluates if resources have at least one recovery point created within the past 1 day. + control { + name = "BACKUP_LAST_RECOVERY_POINT_CREATED" + + input_parameter { + name = "recoveryPointAgeUnit" + value = "days" + } + + input_parameter { + name = "recoveryPointAgeValue" + value = "1" + } + + scope { + compliance_resource_types = var.backup_plan_config.compliance_resource_types + tags = { + (var.backup_plan_config.selection_tag) = "True" + } + } + } +} + +resource "aws_backup_framework" "dynamodb" { + count = var.backup_plan_config_dynamodb.enable ? 1 : 0 + # must be underscores instead of dashes + name = replace("${local.resource_name_prefix}-dynamodb-framework", "-", "_") + description = "${var.project_name} DynamoDB Backup Framework" + + # Evaluates if resources are protected by a backup plan. + control { + name = "BACKUP_RESOURCES_PROTECTED_BY_BACKUP_PLAN" + + scope { + compliance_resource_types = var.backup_plan_config_dynamodb.compliance_resource_types + tags = { + (var.backup_plan_config_dynamodb.selection_tag) = "True" + } + } + } + + # Evaluates if resources have at least one recovery point created within the past 1 day. + control { + name = "BACKUP_LAST_RECOVERY_POINT_CREATED" + + input_parameter { + name = "recoveryPointAgeUnit" + value = "days" + } + + input_parameter { + name = "recoveryPointAgeValue" + value = "1" + } + + scope { + compliance_resource_types = var.backup_plan_config_dynamodb.compliance_resource_types + tags = { + (var.backup_plan_config_dynamodb.selection_tag) = "True" + } + } + } +} diff --git a/terraform/account-wide-infrastructure/modules/backup-source/backup_notification.tf b/terraform/account-wide-infrastructure/modules/backup-source/backup_notification.tf new file mode 100644 index 000000000..cb712321f --- /dev/null +++ b/terraform/account-wide-infrastructure/modules/backup-source/backup_notification.tf @@ -0,0 +1,12 @@ +resource "aws_backup_vault_notifications" "backup_notification" { + count = var.notifications_target_email_address != "" ? 1 : 0 + backup_vault_name = aws_backup_vault.main.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/terraform/account-wide-infrastructure/modules/backup-source/backup_plan.tf b/terraform/account-wide-infrastructure/modules/backup-source/backup_plan.tf new file mode 100644 index 000000000..4d2cf5066 --- /dev/null +++ b/terraform/account-wide-infrastructure/modules/backup-source/backup_plan.tf @@ -0,0 +1,85 @@ +resource "aws_backup_plan" "default" { + name = "${local.resource_name_prefix}-plan" + + dynamic "rule" { + for_each = var.backup_plan_config.rules + content { + 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 + 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 + } + dynamic "copy_action" { + for_each = var.backup_copy_vault_arn != "" && var.backup_copy_vault_account_id != "" && rule.value.copy_action != null ? rule.value.copy_action : {} + content { + lifecycle { + delete_after = copy_action.value + } + destination_vault_arn = var.backup_copy_vault_arn + } + } + } + } +} + +# this backup plan shouldn't include a continous backup rule as it isn't supported for DynamoDB +resource "aws_backup_plan" "dynamodb" { + count = var.backup_plan_config_dynamodb.enable ? 1 : 0 + name = "${local.resource_name_prefix}-dynamodb-plan" + + dynamic "rule" { + for_each = var.backup_plan_config_dynamodb.rules + content { + 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 + 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 + } + dynamic "copy_action" { + for_each = var.backup_copy_vault_arn != "" && var.backup_copy_vault_account_id != "" && rule.value.copy_action != null ? rule.value.copy_action : {} + content { + lifecycle { + delete_after = copy_action.value + } + destination_vault_arn = var.backup_copy_vault_arn + } + } + } + } +} + +resource "aws_backup_selection" "default" { + iam_role_arn = aws_iam_role.backup.arn + name = "${local.resource_name_prefix}-selection" + plan_id = aws_backup_plan.default.id + + selection_tag { + key = var.backup_plan_config.selection_tag + type = "STRINGEQUALS" + value = "True" + } +} + +resource "aws_backup_selection" "dynamodb" { + count = var.backup_plan_config_dynamodb.enable ? 1 : 0 + iam_role_arn = aws_iam_role.backup.arn + name = "${local.resource_name_prefix}-dynamodb-selection" + plan_id = aws_backup_plan.dynamodb[0].id + + selection_tag { + key = var.backup_plan_config_dynamodb.selection_tag + type = "STRINGEQUALS" + value = "True" + } +} diff --git a/terraform/account-wide-infrastructure/modules/backup-source/backup_report_plan.tf b/terraform/account-wide-infrastructure/modules/backup-source/backup_report_plan.tf new file mode 100644 index 000000000..e0f20d41e --- /dev/null +++ b/terraform/account-wide-infrastructure/modules/backup-source/backup_report_plan.tf @@ -0,0 +1,72 @@ +# Create the reports +resource "aws_backup_report_plan" "backup_jobs" { + name = "backup_jobs" + description = "Report for showing whether backups ran successfully in the last 24 hours" + + report_delivery_channel { + formats = [ + "JSON" + ] + s3_bucket_name = var.reports_bucket + s3_key_prefix = "backup_jobs" + } + + report_setting { + report_template = "BACKUP_JOB_REPORT" + } +} + +# Create the restore testing completion reports +resource "aws_backup_report_plan" "backup_restore_testing_jobs" { + name = "backup_restore_testing_jobs" + description = "Report for showing whether backup restore test ran successfully in the last 24 hours" + + report_delivery_channel { + formats = [ + "JSON" + ] + s3_bucket_name = var.reports_bucket + s3_key_prefix = "backup_restore_testing_jobs" + } + + report_setting { + report_template = "RESTORE_JOB_REPORT" + } +} + +resource "aws_backup_report_plan" "resource_compliance" { + name = "resource_compliance" + description = "Report for showing whether resources are compliant with the framework" + + report_delivery_channel { + formats = [ + "JSON" + ] + s3_bucket_name = var.reports_bucket + s3_key_prefix = "resource_compliance" + } + + report_setting { + framework_arns = var.backup_plan_config_dynamodb.enable ? [aws_backup_framework.main.arn, aws_backup_framework.dynamodb[0].arn] : [aws_backup_framework.main.arn] + number_of_frameworks = 2 + report_template = "RESOURCE_COMPLIANCE_REPORT" + } +} + +resource "aws_backup_report_plan" "copy_jobs" { + count = var.backup_copy_vault_arn != "" && var.backup_copy_vault_account_id != "" ? 1 : 0 + name = "copy_jobs" + description = "Report for showing whether copies ran successfully in the last 24 hours" + + report_delivery_channel { + formats = [ + "JSON" + ] + s3_bucket_name = var.reports_bucket + s3_key_prefix = "copy_jobs" + } + + report_setting { + report_template = "COPY_JOB_REPORT" + } +} diff --git a/terraform/account-wide-infrastructure/modules/backup-source/backup_restore_testing.tf_disabled b/terraform/account-wide-infrastructure/modules/backup-source/backup_restore_testing.tf_disabled new file mode 100644 index 000000000..6c4b6f3a9 --- /dev/null +++ b/terraform/account-wide-infrastructure/modules/backup-source/backup_restore_testing.tf_disabled @@ -0,0 +1,26 @@ +resource "awscc_backup_restore_testing_plan" "backup_restore_testing_plan" { + restore_testing_plan_name = "backup_restore_testing_plan" + schedule_expression = var.restore_testing_plan_scheduled_expression + 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] + recovery_point_types = var.restore_testing_plan_recovery_point_types + selection_window_days = var.restore_testing_plan_selection_window_days + } +} + +resource "awscc_backup_restore_testing_selection" "backup_restore_testing_selection_dynamodb" { + count = var.backup_plan_config_dynamodb.enable ? 1 : 0 + iam_role_arn = aws_iam_role.backup.arn + protected_resource_type = "DynamoDB" + restore_testing_plan_name = awscc_backup_restore_testing_plan.backup_restore_testing_plan.restore_testing_plan_name + restore_testing_selection_name = "backup_restore_testing_selection_dynamodb" + protected_resource_arns = ["*"] + protected_resource_conditions = { + string_equals = [{ + key = "aws:ResourceTag/${var.backup_plan_config_dynamodb.selection_tag}" + value = "True" + }] + } +} diff --git a/terraform/account-wide-infrastructure/modules/backup-source/backup_vault.tf b/terraform/account-wide-infrastructure/modules/backup-source/backup_vault.tf new file mode 100644 index 000000000..49f79ca49 --- /dev/null +++ b/terraform/account-wide-infrastructure/modules/backup-source/backup_vault.tf @@ -0,0 +1,4 @@ +resource "aws_backup_vault" "main" { + name = "${local.resource_name_prefix}-vault" + kms_key_arn = aws_kms_key.aws_backup_key.arn +} diff --git a/terraform/account-wide-infrastructure/modules/backup-source/backup_vault_policy.tf b/terraform/account-wide-infrastructure/modules/backup-source/backup_vault_policy.tf new file mode 100644 index 000000000..09d4ec117 --- /dev/null +++ b/terraform/account-wide-infrastructure/modules/backup-source/backup_vault_policy.tf @@ -0,0 +1,47 @@ +resource "aws_backup_vault_policy" "vault_policy" { + backup_vault_name = aws_backup_vault.main.name + policy = data.aws_iam_policy_document.vault_policy.json +} + +data "aws_iam_policy_document" "vault_policy" { + + + statement { + sid = "DenyApartFromTerraform" + effect = "Deny" + + principals { + type = "AWS" + identifiers = ["*"] + } + + condition { + test = "ArnNotEquals" + values = [var.terraform_role_arn] + variable = "aws:PrincipalArn" + } + + actions = [ + "backup:DeleteRecoveryPoint", + "backup:PutBackupVaultAccessPolicy", + "backup:UpdateRecoveryPointLifecycle" + ] + + resources = ["*"] + } + dynamic "statement" { + for_each = var.backup_copy_vault_arn != "" && var.backup_copy_vault_account_id != "" ? [1] : [] + content { + sid = "Allow account to copy into backup vault" + effect = "Allow" + + actions = ["backup:CopyIntoBackupVault"] + resources = ["*"] + + principals { + type = "AWS" + identifiers = ["arn:aws:iam::${var.backup_copy_vault_account_id}:root"] + } + } + } +} diff --git a/terraform/account-wide-infrastructure/modules/backup-source/data.tf b/terraform/account-wide-infrastructure/modules/backup-source/data.tf new file mode 100644 index 000000000..9275ede2e --- /dev/null +++ b/terraform/account-wide-infrastructure/modules/backup-source/data.tf @@ -0,0 +1,8 @@ +data "aws_caller_identity" "current" {} + +data "aws_region" "current" {} + +data "aws_iam_roles" "roles" { + name_regex = "AWSReservedSSO_Admin_.*" + path_prefix = "/aws-reserved/sso.amazonaws.com/" +} diff --git a/terraform/account-wide-infrastructure/modules/backup-source/iam.tf b/terraform/account-wide-infrastructure/modules/backup-source/iam.tf new file mode 100644 index 000000000..e4d58dcc4 --- /dev/null +++ b/terraform/account-wide-infrastructure/modules/backup-source/iam.tf @@ -0,0 +1,37 @@ +data "aws_iam_policy_document" "assume_role" { + statement { + effect = "Allow" + + principals { + type = "Service" + identifiers = ["backup.amazonaws.com"] + } + + actions = ["sts:AssumeRole"] + } +} + +resource "aws_iam_role" "backup" { + name = "${var.project_name}BackupRole" + assume_role_policy = data.aws_iam_policy_document.assume_role.json +} + +resource "aws_iam_role_policy_attachment" "backup" { + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup" + role = aws_iam_role.backup.name +} + +resource "aws_iam_role_policy_attachment" "restore" { + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores" + role = aws_iam_role.backup.name +} + +resource "aws_iam_role_policy_attachment" "s3_restore" { + policy_arn = "arn:aws:iam::aws:policy/AWSBackupServiceRolePolicyForS3Restore" + role = aws_iam_role.backup.name +} + +resource "aws_iam_role_policy_attachment" "s3_backup" { + policy_arn = "arn:aws:iam::aws:policy/AWSBackupServiceRolePolicyForS3Backup" + role = aws_iam_role.backup.name +} diff --git a/terraform/account-wide-infrastructure/modules/backup-source/kms.tf b/terraform/account-wide-infrastructure/modules/backup-source/kms.tf new file mode 100644 index 000000000..ee8183220 --- /dev/null +++ b/terraform/account-wide-infrastructure/modules/backup-source/kms.tf @@ -0,0 +1,34 @@ +resource "aws_kms_key" "aws_backup_key" { + description = "AWS Backup KMS Key" + deletion_window_in_days = 30 + enable_key_rotation = true + policy = data.aws_iam_policy_document.backup_key_policy.json +} + +resource "aws_kms_alias" "backup_key" { + name = "alias/${var.environment_name}/backup-key" + target_key_id = aws_kms_key.aws_backup_key.key_id +} + +data "aws_iam_policy_document" "backup_key_policy" { + #checkov:skip=CKV_AWS_109:See (CERSS-25168) for more info + #checkov:skip=CKV_AWS_111:See (CERSS-25169) for more info + statement { + sid = "AllowBackupUseOfKey" + principals { + type = "Service" + identifiers = ["backup.amazonaws.com"] + } + actions = ["kms:GenerateDataKey", "kms:Decrypt", "kms:Encrypt"] + resources = ["*"] + } + statement { + sid = "EnableIAMUserPermissions" + principals { + type = "AWS" + identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root", data.aws_caller_identity.current.arn] + } + actions = ["kms:*"] + resources = ["*"] + } +} diff --git a/terraform/account-wide-infrastructure/modules/backup-source/locals.tf b/terraform/account-wide-infrastructure/modules/backup-source/locals.tf new file mode 100644 index 000000000..e6929817b --- /dev/null +++ b/terraform/account-wide-infrastructure/modules/backup-source/locals.tf @@ -0,0 +1,3 @@ +locals { + resource_name_prefix = "${data.aws_region.current.name}-${data.aws_caller_identity.current.account_id}-backup" +} diff --git a/terraform/account-wide-infrastructure/modules/backup-source/sns.tf b/terraform/account-wide-infrastructure/modules/backup-source/sns.tf new file mode 100644 index 000000000..cdfec7ff2 --- /dev/null +++ b/terraform/account-wide-infrastructure/modules/backup-source/sns.tf @@ -0,0 +1,35 @@ +resource "aws_sns_topic" "backup" { + count = var.notifications_target_email_address != "" ? 1 : 0 + 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 +} + +data "aws_iam_policy_document" "allow_backup_to_sns" { + policy_id = "backup" + + statement { + actions = [ + "SNS:Publish", + ] + + effect = "Allow" + + principals { + type = "Service" + identifiers = ["backup.amazonaws.com"] + } + + resources = ["*"] + + sid = "allow_backup" + } +} + +resource "aws_sns_topic_subscription" "aws_backup_notifications_email_target" { + count = var.notifications_target_email_address != "" ? 1 : 0 + topic_arn = aws_sns_topic.backup[0].arn + protocol = "email" + endpoint = var.notifications_target_email_address + filter_policy = jsonencode({ "State" : [{ "anything-but" : "COMPLETED" }] }) +} diff --git a/terraform/account-wide-infrastructure/modules/backup-source/variables.tf b/terraform/account-wide-infrastructure/modules/backup-source/variables.tf new file mode 100644 index 000000000..0873063fe --- /dev/null +++ b/terraform/account-wide-infrastructure/modules/backup-source/variables.tf @@ -0,0 +1,199 @@ +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" + type = string +} + +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 + 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) + })) + })) + }) + default = { + selection_tag = "BackupLocal" + 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 + 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 = "BackupDynamoDB" + 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 + } + } + ] + } +} diff --git a/terraform/account-wide-infrastructure/modules/permissions-store-bucket/vars.tf b/terraform/account-wide-infrastructure/modules/permissions-store-bucket/vars.tf index f593893ae..b5584a65c 100644 --- a/terraform/account-wide-infrastructure/modules/permissions-store-bucket/vars.tf +++ b/terraform/account-wide-infrastructure/modules/permissions-store-bucket/vars.tf @@ -8,3 +8,9 @@ variable "enable_bucket_force_destroy" { description = "A boolean flag to enable force destroy of the S3 bucket, so that all objects in the bucket are deleted when the bucket is destroyed." default = false } + +variable "enable_backups" { + type = bool + descirption = "enable AWS cloud backups" + default = false +} diff --git a/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf b/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf index 1da659046..39a2d7d4f 100644 --- a/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf +++ b/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf @@ -51,4 +51,5 @@ resource "aws_dynamodb_table" "pointers" { point_in_time_recovery { enabled = var.enable_pitr } + #tags conditional } diff --git a/terraform/account-wide-infrastructure/modules/pointers-table/vars.tf b/terraform/account-wide-infrastructure/modules/pointers-table/vars.tf index 738e3b99e..29d04b60e 100644 --- a/terraform/account-wide-infrastructure/modules/pointers-table/vars.tf +++ b/terraform/account-wide-infrastructure/modules/pointers-table/vars.tf @@ -20,3 +20,9 @@ variable "kms_deletion_window_in_days" { description = "The duration in days after which the key is deleted after destruction of the resource." default = 7 } + +variable "enable_backups" { + type = bool + description = "Enable AwS cloud backup" + default = false +} diff --git a/terraform/account-wide-infrastructure/modules/truststore-bucket/vars.tf b/terraform/account-wide-infrastructure/modules/truststore-bucket/vars.tf index 3c6fa8790..e3b2f6f43 100644 --- a/terraform/account-wide-infrastructure/modules/truststore-bucket/vars.tf +++ b/terraform/account-wide-infrastructure/modules/truststore-bucket/vars.tf @@ -13,3 +13,9 @@ variable "enable_bucket_force_destroy" { description = "A boolean flag to enable force destroy of the S3 bucket, so that all objects in the bucket are deleted when the bucket is destroyed." default = false } + +variable "enable_backups" { + type = bool + description = "enable AWS cloud backups" + default = false +} diff --git a/terraform/backup-infrastructure/modules/aws-backup-destination/README.md b/terraform/backup-infrastructure/modules/aws-backup-destination/README.md new file mode 100644 index 000000000..10e01514b --- /dev/null +++ b/terraform/backup-infrastructure/modules/aws-backup-destination/README.md @@ -0,0 +1,31 @@ +# AWS Backup Module + +The AWS Backup Module helps automates the setup of AWS Backup resources in a destination account. It streamlines the process of creating, managing, and standardising backup configurations. + +## Inputs + +| Name | Description | Type | Default | Required | +| ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------- | :------: | +| [account_id](#input_account_id) | The id of the account that the vault will be in | `string` | n/a | yes | +| [changeable_for_days](#input_changeable_for_days) | How long you want the vault lock to be changeable for, only applies to compliance mode. This value is expressed in days no less than 3 and no greater than 36,500; otherwise, an error will return. | `number` | `14` | no | +| [enable_vault_protection](#input_enable_vault_protection) | Flag which controls if the vault lock is enabled | `bool` | `false` | no | +| [kms_key](#input_kms_key) | The KMS key used to secure the vault | `string` | n/a | yes | +| [region](#input_region) | The region we should be operating in | `string` | `"eu-west-2"` | no | +| [source_account_id](#input_source_account_id) | The id of the account that backups will come from | `string` | n/a | yes | +| [source_account_name](#input_source_account_name) | The name of the account that backups will come from | `string` | n/a | yes | +| [vault_lock_max_retention_days](#input_vault_lock_max_retention_days) | The maximum retention period that the vault retains its recovery points | `number` | `365` | no | +| [vault_lock_min_retention_days](#input_vault_lock_min_retention_days) | The minimum retention period that the vault retains its recovery points | `number` | `365` | no | +| [vault_lock_type](#input_vault_lock_type) | The type of lock that the vault should be, will default to governance | `string` | `"governance"` | no | + +## Example + +```terraform +module "test_backup_vault" { + source = "./modules/aws_backup" + source_account_name = "test" + account_id = local.aws_accounts_ids["backup"] + source_account_id = local.aws_accounts_ids["test"] + kms_key = aws_kms_key.backup_key.arn + enable_vault_protection = true +} +``` diff --git a/terraform/backup-infrastructure/modules/aws-backup-destination/backup.tf b/terraform/backup-infrastructure/modules/aws-backup-destination/backup.tf new file mode 100644 index 000000000..1df7f2acf --- /dev/null +++ b/terraform/backup-infrastructure/modules/aws-backup-destination/backup.tf @@ -0,0 +1,8 @@ +resource "aws_backup_vault" "vault" { + name = "${var.source_account_name}-backup-vault" + kms_key_arn = var.kms_key +} + +output "vault_arn" { + value = aws_backup_vault.vault.arn +} diff --git a/terraform/backup-infrastructure/modules/aws-backup-destination/backup_vault_lock.tf b/terraform/backup-infrastructure/modules/aws-backup-destination/backup_vault_lock.tf new file mode 100644 index 000000000..e1a31781e --- /dev/null +++ b/terraform/backup-infrastructure/modules/aws-backup-destination/backup_vault_lock.tf @@ -0,0 +1,7 @@ +resource "aws_backup_vault_lock_configuration" "vault_lock" { + count = var.enable_vault_protection ? 1 : 0 + backup_vault_name = aws_backup_vault.vault.name + changeable_for_days = var.vault_lock_type == "compliance" ? var.changeable_for_days : null + max_retention_days = var.vault_lock_max_retention_days + min_retention_days = var.vault_lock_min_retention_days +} diff --git a/terraform/backup-infrastructure/modules/aws-backup-destination/backup_vault_policy.tf b/terraform/backup-infrastructure/modules/aws-backup-destination/backup_vault_policy.tf new file mode 100644 index 000000000..224904193 --- /dev/null +++ b/terraform/backup-infrastructure/modules/aws-backup-destination/backup_vault_policy.tf @@ -0,0 +1,68 @@ +resource "aws_backup_vault_policy" "vault_policy" { + backup_vault_name = aws_backup_vault.vault.name + policy = data.aws_iam_policy_document.vault_policy.json +} + +data "aws_iam_policy_document" "vault_policy" { + + statement { + sid = "AllowCopyToVault" + effect = "Allow" + + principals { + type = "AWS" + identifiers = ["arn:aws:iam::${var.source_account_id}:root"] + } + + actions = [ + "backup:CopyIntoBackupVault" + ] + resources = ["*"] + } + + dynamic "statement" { + for_each = var.enable_vault_protection ? [1] : [] + content { + sid = "DenyBackupVaultAccess" + effect = "Deny" + + principals { + type = "AWS" + identifiers = ["*"] + } + actions = [ + "backup:DeleteRecoveryPoint", + "backup:PutBackupVaultAccessPolicy", + "backup:UpdateRecoveryPointLifecycle", + "backup:DeleteBackupVault", + "backup:StartRestoreJob", + "backup:DeleteBackupVaultLockConfiguration", + ] + resources = ["*"] + } + } + + dynamic "statement" { + for_each = var.enable_vault_protection ? [1] : [] + content { + sid = "DenyBackupCopyExceptToSourceAccount" + effect = "Deny" + + principals { + type = "AWS" + identifiers = ["arn:aws:iam::${var.account_id}:root"] + } + actions = [ + "backup:CopyFromBackupVault" + ] + resources = ["*"] + condition { + test = "StringNotEquals" + variable = "backup:CopyTargets" + values = [ + "arn:aws:backup:${var.region}:${var.source_account_id}:backup-vault:${var.region}-${var.source_account_id}-backup-vault" + ] + } + } + } +} diff --git a/terraform/backup-infrastructure/modules/aws-backup-destination/variables.tf b/terraform/backup-infrastructure/modules/aws-backup-destination/variables.tf new file mode 100644 index 000000000..75e620cfa --- /dev/null +++ b/terraform/backup-infrastructure/modules/aws-backup-destination/variables.tf @@ -0,0 +1,67 @@ +variable "source_account_name" { + # This is used as a prefix for the vault name, and referenced by the policy and the lock. + # It doesn't have to match anything in the source AWS account. + description = "The name of the account that backups will come from" + type = string +} + +variable "source_account_id" { + # The source account ID is used in the policy to allow permit root in the source account + # to copy backups into the vault. + description = "The id of the account that backups will come from" + type = string +} + +variable "account_id" { + # This is used to deny root from being able to copy backups from the vault + # to anywhere other than the source account. The constraint will need to + # be removed if the original source account is lost. + description = "The id of the account that the vault will be in" + type = string +} + +variable "region" { + description = "The region we should be operating in" + type = string + default = "eu-west-2" +} + +variable "kms_key" { + description = "The KMS key used to secure the vault" + type = string +} + +variable "enable_vault_protection" { + # With this set to true, privileges are locked down so that the vault can't be deleted or + # have its policy changed. The minimum and maximum retention periods are also set only if this is true. + description = "Flag which controls if the vault lock is enabled" + type = bool + default = false +} + +variable "vault_lock_type" { + description = "The type of lock that the vault should be, will default to governance" + type = string + # See toplevel README.md: + # DO NOT SET THIS TO compliance UNTIL YOU ARE SURE THAT YOU WANT TO LOCK THE VAULT PERMANENTLY + # When you do, you will also need to set "enable_vault_protection" to true for it to take effect. + default = "governance" +} + +variable "vault_lock_min_retention_days" { + description = "The minimum retention period that the vault retains its recovery points" + type = number + default = 365 +} + +variable "vault_lock_max_retention_days" { + description = "The maximum retention period that the vault retains its recovery points" + type = number + default = 365 +} + +variable "changeable_for_days" { + description = "How long you want the vault lock to be changeable for, only applies to compliance mode. This value is expressed in days no less than 3 and no greater than 36,500; otherwise, an error will return." + type = number + default = 14 +} diff --git a/terraform/backup-infrastructure/test/aws-backup.tf b/terraform/backup-infrastructure/test/aws-backup.tf new file mode 100644 index 000000000..19ee2e43a --- /dev/null +++ b/terraform/backup-infrastructure/test/aws-backup.tf @@ -0,0 +1,42 @@ + +# We need a key for the backup vaults. This key will be used to encrypt the backups themselves. +# We need one per vault (on the assumption that each vault will be in a different account). +resource "aws_kms_key" "destination_backup_key" { + description = "KMS key for AWS Backup vaults" + deletion_window_in_days = 7 + enable_key_rotation = true + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Sid = "Enable IAM User Permissions" + Principal = { + AWS = "arn:aws:iam::${local.destination_account_id}:root" + } + Action = "kms:*" + Resource = "*" + } + ] + }) +} + +module "destination" { + source = "../modules/aws-backup-destination" + + source_account_name = "test" # please note that the assigned value would be the prefix in aws_backup_vault.vault.name + account_id = local.destination_account_id + source_account_id = local.source_account_id + kms_key = aws_kms_key.destination_backup_key.arn + enable_vault_protection = false +} + +### +# Destination vault ARN output +### + +output "destination_vault_arn" { + # The ARN of the backup vault in the destination account is needed by + # the source account to copy backups into it. + value = module.destination.vault_arn +} diff --git a/terraform/backup-infrastructure/test/data.tf b/terraform/backup-infrastructure/test/data.tf new file mode 100644 index 000000000..f8b5aa3c4 --- /dev/null +++ b/terraform/backup-infrastructure/test/data.tf @@ -0,0 +1,5 @@ +data "aws_arn" "source_terraform_role" { + arn = var.source_terraform_role_arn +} + +data "aws_caller_identity" "current" {} diff --git a/terraform/backup-infrastructure/test/locals.tf b/terraform/backup-infrastructure/test/locals.tf new file mode 100644 index 000000000..0303cd337 --- /dev/null +++ b/terraform/backup-infrastructure/test/locals.tf @@ -0,0 +1,8 @@ +locals { + # Adjust these as required + project_name = "nrlf-test-backup" + environment_name = "dev" + + source_account_id = data.aws_arn.source_terraform_role.account + destination_account_id = data.aws_caller_identity.current.account_id +} diff --git a/terraform/backup-infrastructure/test/main.tf b/terraform/backup-infrastructure/test/main.tf new file mode 100644 index 000000000..f32f1468f --- /dev/null +++ b/terraform/backup-infrastructure/test/main.tf @@ -0,0 +1,14 @@ +#terraform { +# backend "s3" { +# bucket = "project-env-backup-tf-bucket" # change this to the destination account terraform state s3 bucket name +# key = "project-env-backup.tfstate" # change this to the destination account terraform state s3 key name +# dynamodb_table = "project-env-backup-lock-table" # change this to the destination account terraform state dynamodb table name +# region = "eu-west-2" +# } +#} + + +provider "aws" { + alias = "source" + region = "eu-west-2" +} diff --git a/terraform/backup-infrastructure/test/vars.tf b/terraform/backup-infrastructure/test/vars.tf new file mode 100644 index 000000000..e6e55ff45 --- /dev/null +++ b/terraform/backup-infrastructure/test/vars.tf @@ -0,0 +1,4 @@ +variable "source_terraform_role_arn" { + description = "ARN of the terraform role in the source account" + type = string +} From 732c833c52d90fa0a8e89bfb6b7682db1e41a1ea Mon Sep 17 00:00:00 2001 From: Matt Dean Date: Mon, 18 Nov 2024 09:13:02 +0000 Subject: [PATCH 02/13] [NRL-853] Fix perms errors with AWSCC resources for backup restore testing --- terraform/account-wide-infrastructure/dev/main.tf | 7 +++++++ ...store_testing.tf_disabled => backup_restore_testing.tf} | 0 .../modules/backup-source/kms.tf | 7 +++++-- .../modules/permissions-store-bucket/vars.tf | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) rename terraform/account-wide-infrastructure/modules/backup-source/{backup_restore_testing.tf_disabled => backup_restore_testing.tf} (100%) diff --git a/terraform/account-wide-infrastructure/dev/main.tf b/terraform/account-wide-infrastructure/dev/main.tf index cfed956f2..6a15ca71b 100644 --- a/terraform/account-wide-infrastructure/dev/main.tf +++ b/terraform/account-wide-infrastructure/dev/main.tf @@ -10,7 +10,14 @@ provider "aws" { workspace = terraform.workspace } } +} + +provider "awscc" { + region = local.region + assume_role = { + role_arn = "arn:aws:iam::${var.assume_account}:role/${var.assume_role}" + } } terraform { diff --git a/terraform/account-wide-infrastructure/modules/backup-source/backup_restore_testing.tf_disabled b/terraform/account-wide-infrastructure/modules/backup-source/backup_restore_testing.tf similarity index 100% rename from terraform/account-wide-infrastructure/modules/backup-source/backup_restore_testing.tf_disabled rename to terraform/account-wide-infrastructure/modules/backup-source/backup_restore_testing.tf diff --git a/terraform/account-wide-infrastructure/modules/backup-source/kms.tf b/terraform/account-wide-infrastructure/modules/backup-source/kms.tf index ee8183220..55efeeec1 100644 --- a/terraform/account-wide-infrastructure/modules/backup-source/kms.tf +++ b/terraform/account-wide-infrastructure/modules/backup-source/kms.tf @@ -25,8 +25,11 @@ data "aws_iam_policy_document" "backup_key_policy" { statement { sid = "EnableIAMUserPermissions" principals { - type = "AWS" - identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:root", data.aws_caller_identity.current.arn] + type = "AWS" + identifiers = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root", + var.terraform_role_arn + ] } actions = ["kms:*"] resources = ["*"] diff --git a/terraform/account-wide-infrastructure/modules/permissions-store-bucket/vars.tf b/terraform/account-wide-infrastructure/modules/permissions-store-bucket/vars.tf index b5584a65c..4a4db27b6 100644 --- a/terraform/account-wide-infrastructure/modules/permissions-store-bucket/vars.tf +++ b/terraform/account-wide-infrastructure/modules/permissions-store-bucket/vars.tf @@ -11,6 +11,6 @@ variable "enable_bucket_force_destroy" { variable "enable_backups" { type = bool - descirption = "enable AWS cloud backups" + description = "enable AWS cloud backups" default = false } From b00d59882fb26d06a602e49f96a8c2a6226e4017 Mon Sep 17 00:00:00 2001 From: Matt Dean Date: Mon, 18 Nov 2024 14:40:45 +0000 Subject: [PATCH 03/13] [NRL-853] Move notification email config out of lambda-error module and re-use for backup notifications --- terraform/account-wide-infrastructure/dev/aws-backups.tf | 2 ++ terraform/account-wide-infrastructure/dev/cloudwatch.tf | 2 ++ terraform/account-wide-infrastructure/dev/data.tf | 8 ++++++++ terraform/account-wide-infrastructure/dev/locals.tf | 2 ++ terraform/account-wide-infrastructure/dev/secrets.tf | 4 ++++ .../modules/backup-source/backup_notification.tf | 3 +-- .../modules/backup-source/sns.tf | 7 +++---- .../modules/backup-source/variables.tf | 8 ++++---- .../modules/lambda-errors-metric-alarm/secretsmanager.tf | 8 -------- .../modules/lambda-errors-metric-alarm/sns.tf | 2 +- .../modules/lambda-errors-metric-alarm/vars.tf | 6 ++++++ 11 files changed, 33 insertions(+), 19 deletions(-) delete mode 100644 terraform/account-wide-infrastructure/modules/lambda-errors-metric-alarm/secretsmanager.tf diff --git a/terraform/account-wide-infrastructure/dev/aws-backups.tf b/terraform/account-wide-infrastructure/dev/aws-backups.tf index 09fd9da1f..041ef3b37 100644 --- a/terraform/account-wide-infrastructure/dev/aws-backups.tf +++ b/terraform/account-wide-infrastructure/dev/aws-backups.tf @@ -104,6 +104,8 @@ module "source" { #terraform_role_arn = data.aws_caller_identity.current.arn terraform_role_arn = "arn:aws:iam::${var.assume_account}:role/${var.assume_role}" + notification_target_email_addresses = local.notification_emails + backup_plan_config = { "compliance_resource_types" : [ "S3" diff --git a/terraform/account-wide-infrastructure/dev/cloudwatch.tf b/terraform/account-wide-infrastructure/dev/cloudwatch.tf index 8a54e5854..031a6e36d 100644 --- a/terraform/account-wide-infrastructure/dev/cloudwatch.tf +++ b/terraform/account-wide-infrastructure/dev/cloudwatch.tf @@ -2,6 +2,8 @@ module "lambda_errors_cloudwatch_metric_alarm_dev" { source = "../modules/lambda-errors-metric-alarm" name_prefix = "nhsd-nrlf--dev" + notification_emails = local.notification_emails + evaluation_periods = 1 period = 60 threshold = 1 diff --git a/terraform/account-wide-infrastructure/dev/data.tf b/terraform/account-wide-infrastructure/dev/data.tf index fe0eefc7c..7b3c623de 100644 --- a/terraform/account-wide-infrastructure/dev/data.tf +++ b/terraform/account-wide-infrastructure/dev/data.tf @@ -1,3 +1,11 @@ data "aws_secretsmanager_secret_version" "identities_account_id" { secret_id = aws_secretsmanager_secret.identities_account_id.name } + +data "aws_secretsmanager_secret" "emails" { + name = "${local.prefix}-emails" +} + +data "aws_secretsmanager_secret_version" "emails" { + secret_id = data.aws_secretsmanager_secret.emails.id +} diff --git a/terraform/account-wide-infrastructure/dev/locals.tf b/terraform/account-wide-infrastructure/dev/locals.tf index 0929b0d38..9b06efdfe 100644 --- a/terraform/account-wide-infrastructure/dev/locals.tf +++ b/terraform/account-wide-infrastructure/dev/locals.tf @@ -3,4 +3,6 @@ locals { project = "nhsd-nrlf" environment = terraform.workspace prefix = "${local.project}--${local.environment}" + + notification_emails = nonsensitive(toset(tolist(jsondecode(data.aws_secretsmanager_secret_version.emails.secret_string)))) } diff --git a/terraform/account-wide-infrastructure/dev/secrets.tf b/terraform/account-wide-infrastructure/dev/secrets.tf index 46c339fc9..2559c81cd 100644 --- a/terraform/account-wide-infrastructure/dev/secrets.tf +++ b/terraform/account-wide-infrastructure/dev/secrets.tf @@ -2,6 +2,10 @@ resource "aws_secretsmanager_secret" "identities_account_id" { name = "${local.prefix}--nhs-identities-account-id" } +resource "aws_secretsmanager_secret" "notification_email_addresses" { + name = "${local.prefix}-dev-notification-email-addresses" +} + resource "aws_secretsmanager_secret" "dev_smoke_test_apigee_app" { name = "${local.prefix}--dev--apigee-app--smoke-test" description = "APIGEE App used to run Smoke Tests against the DEV environment" diff --git a/terraform/account-wide-infrastructure/modules/backup-source/backup_notification.tf b/terraform/account-wide-infrastructure/modules/backup-source/backup_notification.tf index cb712321f..554f2ad49 100644 --- a/terraform/account-wide-infrastructure/modules/backup-source/backup_notification.tf +++ b/terraform/account-wide-infrastructure/modules/backup-source/backup_notification.tf @@ -1,7 +1,6 @@ resource "aws_backup_vault_notifications" "backup_notification" { - count = var.notifications_target_email_address != "" ? 1 : 0 backup_vault_name = aws_backup_vault.main.name - sns_topic_arn = aws_sns_topic.backup[0].arn + sns_topic_arn = aws_sns_topic.backup.arn backup_vault_events = [ "BACKUP_JOB_COMPLETED", "RESTORE_JOB_COMPLETED", diff --git a/terraform/account-wide-infrastructure/modules/backup-source/sns.tf b/terraform/account-wide-infrastructure/modules/backup-source/sns.tf index cdfec7ff2..f91b26b96 100644 --- a/terraform/account-wide-infrastructure/modules/backup-source/sns.tf +++ b/terraform/account-wide-infrastructure/modules/backup-source/sns.tf @@ -1,5 +1,4 @@ resource "aws_sns_topic" "backup" { - count = var.notifications_target_email_address != "" ? 1 : 0 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 @@ -27,9 +26,9 @@ data "aws_iam_policy_document" "allow_backup_to_sns" { } resource "aws_sns_topic_subscription" "aws_backup_notifications_email_target" { - count = var.notifications_target_email_address != "" ? 1 : 0 - topic_arn = aws_sns_topic.backup[0].arn + for_each = var.notification_target_email_addresses + topic_arn = aws_sns_topic.backup.arn protocol = "email" - endpoint = var.notifications_target_email_address + endpoint = each.value filter_policy = jsonencode({ "State" : [{ "anything-but" : "COMPLETED" }] }) } diff --git a/terraform/account-wide-infrastructure/modules/backup-source/variables.tf b/terraform/account-wide-infrastructure/modules/backup-source/variables.tf index 0873063fe..a76f7a4d2 100644 --- a/terraform/account-wide-infrastructure/modules/backup-source/variables.tf +++ b/terraform/account-wide-infrastructure/modules/backup-source/variables.tf @@ -8,10 +8,10 @@ variable "environment_name" { type = string } -variable "notifications_target_email_address" { - description = "The email address to which backup notifications will be sent via SNS." - type = string - default = "" +variable "notification_target_email_addresses" { + description = "The email addresses to which backup notifications will be sent via SNS." + type = set(string) + default = [] } variable "bootstrap_kms_key_arn" { diff --git a/terraform/account-wide-infrastructure/modules/lambda-errors-metric-alarm/secretsmanager.tf b/terraform/account-wide-infrastructure/modules/lambda-errors-metric-alarm/secretsmanager.tf deleted file mode 100644 index 984bd41e3..000000000 --- a/terraform/account-wide-infrastructure/modules/lambda-errors-metric-alarm/secretsmanager.tf +++ /dev/null @@ -1,8 +0,0 @@ -data "aws_secretsmanager_secret" "emails" { - name = "${var.name_prefix}-emails" -} - -data "aws_secretsmanager_secret_version" "emails" { - secret_id = data.aws_secretsmanager_secret.emails.id - -} diff --git a/terraform/account-wide-infrastructure/modules/lambda-errors-metric-alarm/sns.tf b/terraform/account-wide-infrastructure/modules/lambda-errors-metric-alarm/sns.tf index 011568f53..5abaa0a6c 100644 --- a/terraform/account-wide-infrastructure/modules/lambda-errors-metric-alarm/sns.tf +++ b/terraform/account-wide-infrastructure/modules/lambda-errors-metric-alarm/sns.tf @@ -4,7 +4,7 @@ resource "aws_sns_topic" "sns_topic" { } resource "aws_sns_topic_subscription" "sns_subscription" { - for_each = nonsensitive(toset(tolist(jsondecode(data.aws_secretsmanager_secret_version.emails.secret_string)))) + for_each = var.notification_emails topic_arn = aws_sns_topic.sns_topic.arn protocol = "email" endpoint = sensitive(each.value) diff --git a/terraform/account-wide-infrastructure/modules/lambda-errors-metric-alarm/vars.tf b/terraform/account-wide-infrastructure/modules/lambda-errors-metric-alarm/vars.tf index a244243e1..605569262 100644 --- a/terraform/account-wide-infrastructure/modules/lambda-errors-metric-alarm/vars.tf +++ b/terraform/account-wide-infrastructure/modules/lambda-errors-metric-alarm/vars.tf @@ -25,3 +25,9 @@ variable "kms_deletion_window_in_days" { description = "The duration in days after which the key is deleted after destruction of the resource." default = 7 } + +variable "notification_emails" { + type = set(string) + description = "The email addresses to which notifications will be sent." + default = [] +} From b465e2bd7fb7a05e13fea98f93710a8f0a5a0018 Mon Sep 17 00:00:00 2001 From: Kate Bobyn Date: Mon, 18 Nov 2024 15:49:25 +0000 Subject: [PATCH 04/13] NRL-853 combine dynamodb and s3 policies, protect access to report bucket --- .../dev/aws-backups.tf | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/terraform/account-wide-infrastructure/dev/aws-backups.tf b/terraform/account-wide-infrastructure/dev/aws-backups.tf index 041ef3b37..5996ff3a3 100644 --- a/terraform/account-wide-infrastructure/dev/aws-backups.tf +++ b/terraform/account-wide-infrastructure/dev/aws-backups.tf @@ -37,6 +37,24 @@ resource "aws_s3_bucket" "backup_reports" { bucket_prefix = "${local.project_name}-backup-reports" } +resource "aws_s3_bucket_public_access_block" "backup_reports" { + bucket = aws_s3_bucket.backup_reports.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "backup_reports" { + bucket = aws_s3_bucket.backup_reports.bucket + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } +} # Now we have to configure access to the report bucket. resource "aws_s3_bucket_ownership_controls" "backup_reports" { @@ -108,7 +126,7 @@ module "source" { backup_plan_config = { "compliance_resource_types" : [ - "S3" + "S3", "DynamoDB" ], "rules" : [ { @@ -124,15 +142,4 @@ module "source" { ], "selection_tag" : "NHSE-Enable-Backup" } - # Note here that we need to explicitly disable DynamoDB backups in the source account. - # The default config in the module enables backups for all resource types. - backup_plan_config_dynamodb = { - "compliance_resource_types" : [ - "DynamoDB" - ], - "rules" : [ - ], - "enable" : false, - "selection_tag" : "NHSE-Enable-Backup" - } } From 87b73169a3b1ec2a13c958ebb6149b4e79052861 Mon Sep 17 00:00:00 2001 From: Kate Bobyn Date: Mon, 18 Nov 2024 15:51:43 +0000 Subject: [PATCH 05/13] NRL-853 add backup tag to pointer table --- .../modules/pointers-table/dynamodb.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf b/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf index 39a2d7d4f..4bb3745e1 100644 --- a/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf +++ b/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf @@ -51,5 +51,6 @@ resource "aws_dynamodb_table" "pointers" { point_in_time_recovery { enabled = var.enable_pitr } - #tags conditional + + tags = var.enable_backups ? { NHSE-Enable-Backup : daily } : {} } From e18d11776ad5068287b773fd3f2d4bc6156533c7 Mon Sep 17 00:00:00 2001 From: Kate Bobyn Date: Mon, 18 Nov 2024 16:19:02 +0000 Subject: [PATCH 06/13] NRL-853 disallow http requests in s3 buckets --- .../dev/aws-backups.tf | 32 ++++++++++++++++--- .../modules/permissions-store-bucket/s3.tf | 26 +++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/terraform/account-wide-infrastructure/dev/aws-backups.tf b/terraform/account-wide-infrastructure/dev/aws-backups.tf index 5996ff3a3..8dd30b4e0 100644 --- a/terraform/account-wide-infrastructure/dev/aws-backups.tf +++ b/terraform/account-wide-infrastructure/dev/aws-backups.tf @@ -30,9 +30,7 @@ locals { destination_account_id = data.aws_secretsmanager_secret_version.destination_account_id.secret_string } -# First, we create an S3 bucket for compliance reports. You may already have a module for creating -# S3 buckets with more refined access rules, which you may prefer to use. - +# First, we create an S3 bucket for compliance reports. resource "aws_s3_bucket" "backup_reports" { bucket_prefix = "${local.project_name}-backup-reports" } @@ -55,7 +53,33 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "backup_reports" { } } } -# Now we have to configure access to the report bucket. + +resource "aws_s3_bucket_policy" "backup_reports_bucket_policy" { + bucket = aws_s3_bucket.backup_reports.id + + policy = jsonencode({ + Version = "2012-10-17" + Id = "backup_reports_bucket_policy" + Statement = [ + { + Sid = "HTTPSOnly" + Effect = "Deny" + Principal = "*" + Action = "s3:*" + Resource = [ + aws_s3_bucket.backup_reports.arn, + "${aws_s3_bucket.backup_reports.arn}/*", + ] + Condition = { + Bool = { + "aws:SecureTransport" = "false" + } + } + }, + ] + }) +} + resource "aws_s3_bucket_ownership_controls" "backup_reports" { bucket = aws_s3_bucket.backup_reports.id diff --git a/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf b/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf index ad37cff7d..5e6b80440 100644 --- a/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf +++ b/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf @@ -27,6 +27,32 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "authorization-sto } } +resource "aws_s3_bucket_policy" "authorization_store_bucket_policy" { + bucket = aws_s3_bucket.authorization-store.id + + policy = jsonencode({ + Version = "2012-10-17" + Id = "authorization_store_bucket_policy" + Statement = [ + { + Sid = "HTTPSOnly" + Effect = "Deny" + Principal = "*" + Action = "s3:*" + Resource = [ + aws_s3_bucket.authorization-store.arn, + "${aws_s3_bucket.authorization-store.arn}/*", + ] + Condition = { + Bool = { + "aws:SecureTransport" = "false" + } + } + }, + ] + }) +} + resource "aws_s3_bucket_versioning" "authorization-store" { bucket = aws_s3_bucket.authorization-store.id versioning_configuration { From a4a9fd85bc1a2989ffabff581ef749104b7c51e1 Mon Sep 17 00:00:00 2001 From: Matt Dean Date: Tue, 19 Nov 2024 09:03:34 +0000 Subject: [PATCH 07/13] [NRL-853] Move backup infrastructure TF state into S3 and add README. Fixup account bootstrap script to work on non-mgmt accounts --- scripts/bootstrap.sh | 28 +++--- terraform/backup-infrastructure/README.md | 87 +++++++++++++++++++ terraform/backup-infrastructure/test/data.tf | 4 - .../backup-infrastructure/test/locals.tf | 6 +- terraform/backup-infrastructure/test/main.tf | 40 ++++++--- terraform/backup-infrastructure/test/vars.tf | 15 +++- 6 files changed, 149 insertions(+), 31 deletions(-) create mode 100644 terraform/backup-infrastructure/README.md diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index 89eb4baa3..b3e9069b2 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -1,4 +1,6 @@ #!/bin/bash +# Setup mgmt and non-mgmt AWS accounts for NRLF +set -o errexit -o nounset -o pipefail AWS_REGION_NAME="eu-west-2" PROFILE_PREFIX="nhsd-nrlf" @@ -32,18 +34,12 @@ function _check_mgmt() { } function _check_non_mgmt() { - if [[ "$(aws iam list-account-aliases --query 'AccountAliases[0]' --output text)" != 'nhsd-ddc-spine-nrlf-mgmt' ]]; then + if [[ "$(aws iam list-account-aliases --query 'AccountAliases[0]' --output text)" == 'nhsd-ddc-spine-nrlf-mgmt' ]]; then echo "Please log in as a non-mgmt account" >&2 return 1 fi } -function _get_mgmt_account(){ - if ! _check_mgmt; then return 1; fi - return $(aws sts get-caller-identity --query Account --output text) -} - - function _bootstrap() { local command=$1 local admin_policy_arn="arn:aws:iam::aws:policy/AdministratorAccess" @@ -55,7 +51,7 @@ function _bootstrap() { "create-mgmt") _check_mgmt || return 1 - cd $root/terraform/bootstrap/mgmt + cd terraform/bootstrap/mgmt aws s3api create-bucket --bucket "${truststore_bucket_name}" --region us-east-1 --create-bucket-configuration LocationConstraint="${AWS_REGION_NAME}" aws s3api create-bucket --bucket "${state_bucket_name}" --region us-east-1 --create-bucket-configuration LocationConstraint="${AWS_REGION_NAME}" aws s3api put-public-access-block --bucket "${state_bucket_name}" --public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true" @@ -69,7 +65,7 @@ function _bootstrap() { "delete-mgmt") _check_mgmt || return 1 - cd $root/terraform/bootstrap/mgmt + cd terraform/bootstrap/mgmt aws dynamodb delete-table --table-name "${state_lock_table_name}" || return 1 local versioned_objects versioned_objects=$(aws s3api list-object-versions \ @@ -90,10 +86,20 @@ function _bootstrap() { "create-non-mgmt") _check_non_mgmt || return 1 - cd $root/terraform/bootstrap/non-mgmt + cd terraform/bootstrap/non-mgmt local tf_assume_role_policy local mgmt_account_id - mgmt_account_id=$(_get_mgmt_account) + + set +e + mgmt_account_id=$(aws secretsmanager get-secret-value --secret-id "${MGMT_ACCOUNT_ID_LOCATION}" --query SecretString --output text) + + if [ "${mgmt_account_id}" == "" ]; then + aws secretsmanager create-secret --name "${MGMT_ACCOUNT_ID_LOCATION}" + echo "Please set ${MGMT_ACCOUNT_ID_LOCATION} in the Secrets Manager and rerun the script" + exit 1 + fi + set -e + tf_assume_role_policy=$(awk "{sub(/REPLACEME/,\"${mgmt_account_id}\")}1" terraform-trust-policy.json) aws iam create-role --role-name "${TERRAFORM_ROLE_NAME}" --assume-role-policy-document "${tf_assume_role_policy}" || return 1 aws iam attach-role-policy --policy-arn "${admin_policy_arn}" --role-name "${TERRAFORM_ROLE_NAME}" || return 1 diff --git a/terraform/backup-infrastructure/README.md b/terraform/backup-infrastructure/README.md new file mode 100644 index 000000000..8af8ae7db --- /dev/null +++ b/terraform/backup-infrastructure/README.md @@ -0,0 +1,87 @@ +# NRLF Backup Infrastructure + +This directory contains AWS backup terraform resources which are global to a given account. + +Each subdirectory corresponds to each AWS account (`prod` and `test`). + +**Backup infrastructure should be deployed manually and not be run as part of CI.** + +## Table of Contents + +1. [Prerequisites](#prerequisites) +2. [Initialise shell environment](#initialise-shell-environment) +3. [Deploy backup resources](#deploy-backup-resources) +4. [Tear down backup resources](#tear-down-backup-resources) + +## Prerequisites + +Before deploying the NRLF backup infrastructure, you will need: + +- An AWS backup account that have already been bootstrapped, as described in [bootstrap/README.md](../bootstrap/README.md). This is a one-time account setup step. + +## Deploy backup resources + +To deploy the backup resources, first login to the AWS mgmt account on the CLI. + +Then, initialise the terraform backup workspace. For the test account: + +```shell +$ cd test +$ terraform init && ( \ + terraform workspace new backup-infra-test || \ + terraform workspace select backup-infra-test ) +``` + +If you want to apply changes to prod, use the `prod` directory and the `backup-infra-prod` terraform workspace. + +Once you have your workspace set, you can plan your changes with: + +```shell +$ terraform plan \ + -var 'source_account_id=SOURCE_ACCOUNT_ID" \ + -var 'assume_account=AWS_ACCOUNT_ID' \ + -var 'assume_role=terraform' +``` + +Replacing SOURCE_ACCOUNT with the account id that will be sending backups to the backup account and AWS_ACCOUNT_ID with the AWS account id of your backup account. + +Once you're happy with your planned changes, you can apply them with: + +```shell +$ terraform apply \ + -var 'source_account_id=SOURCE_ACCOUNT_ID" \ + -var 'assume_account=AWS_ACCOUNT_ID' \ + -var 'assume_role=terraform' +``` + +Replacing SOURCE_ACCOUNT with the account id that will be sending backups to the backup account and AWS_ACCOUNT_ID with the AWS account id of your backup account. + +## Tear down backup resources + +WARNING - This action will destroy all backup resources from the AWS account. This should +only be done if you are sure that this is safe and are sure that you are signed into the correct +AWS account. + +To tear down backup resources, first login to the AWS mgmt account on the CLI. + +Then, initialise your terraform workspace. For the test account: + +```shell +$ cd test +$ terraform init && ( \ + terraform workspace new backup-infra-test || \ + terraform workspace select backup-infra-test ) +``` + +If you want to destroy resources in prod, use the `prod` directory and the `backup-infra-prod` terraform workspace. + +And then, to tear down: + +```shell +$ terraform destroy \ + -var 'source_account_id=SOURCE_ACCOUNT_ID" \ + -var 'assume_account=AWS_ACCOUNT_ID' \ + -var 'assume_role=terraform' +``` + +Replacing SOURCE_ACCOUNT with the account id that will be sending backups to the backup account and AWS_ACCOUNT_ID with the AWS account id of your backup account. diff --git a/terraform/backup-infrastructure/test/data.tf b/terraform/backup-infrastructure/test/data.tf index f8b5aa3c4..8fc4b38cc 100644 --- a/terraform/backup-infrastructure/test/data.tf +++ b/terraform/backup-infrastructure/test/data.tf @@ -1,5 +1 @@ -data "aws_arn" "source_terraform_role" { - arn = var.source_terraform_role_arn -} - data "aws_caller_identity" "current" {} diff --git a/terraform/backup-infrastructure/test/locals.tf b/terraform/backup-infrastructure/test/locals.tf index 0303cd337..6bc51d571 100644 --- a/terraform/backup-infrastructure/test/locals.tf +++ b/terraform/backup-infrastructure/test/locals.tf @@ -1,8 +1,8 @@ locals { # Adjust these as required project_name = "nrlf-test-backup" - environment_name = "dev" + environment_name = "test" - source_account_id = data.aws_arn.source_terraform_role.account - destination_account_id = data.aws_caller_identity.current.account_id + source_account_id = var.source_account_id + destination_account_id = var.assume_account } diff --git a/terraform/backup-infrastructure/test/main.tf b/terraform/backup-infrastructure/test/main.tf index f32f1468f..260e66173 100644 --- a/terraform/backup-infrastructure/test/main.tf +++ b/terraform/backup-infrastructure/test/main.tf @@ -1,14 +1,32 @@ -#terraform { -# backend "s3" { -# bucket = "project-env-backup-tf-bucket" # change this to the destination account terraform state s3 bucket name -# key = "project-env-backup.tfstate" # change this to the destination account terraform state s3 key name -# dynamodb_table = "project-env-backup-lock-table" # change this to the destination account terraform state dynamodb table name -# region = "eu-west-2" -# } -#} - - provider "aws" { - alias = "source" region = "eu-west-2" + + assume_role { + role_arn = "arn:aws:iam::${var.assume_account}:role/${var.assume_role}" + } + + default_tags { + tags = { + project_name = local.project_name + workspace = terraform.workspace + } + } +} + +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.76.0" + } + } + + backend "s3" { + region = "eu-west-2" + bucket = "nhsd-nrlf--terraform-state" + dynamodb_table = "nhsd-nrlf--terraform-state-lock" + key = "terraform-state-dev-backup-infrastructure" + workspace_key_prefix = "nhsd-nrlf" + encrypt = false + } } diff --git a/terraform/backup-infrastructure/test/vars.tf b/terraform/backup-infrastructure/test/vars.tf index e6e55ff45..e091ee9c5 100644 --- a/terraform/backup-infrastructure/test/vars.tf +++ b/terraform/backup-infrastructure/test/vars.tf @@ -1,4 +1,15 @@ -variable "source_terraform_role_arn" { - description = "ARN of the terraform role in the source account" +variable "assume_account" { + description = "The account id to deploy the infrastructure to" + sensitive = true +} + +variable "assume_role" { + description = "Name of the role to assume to deploy the infrastructure" + type = string +} + +variable "source_account_id" { + description = "The account id of the backup source account" type = string + sensitive = true } From bd0ce6b0a06140d5d6295a36fb7bb9d287b9f928 Mon Sep 17 00:00:00 2001 From: Kate Bobyn Date: Fri, 22 Nov 2024 14:44:20 +0000 Subject: [PATCH 08/13] NRL-853 fix tags for table and buckets --- .../modules/permissions-store-bucket/s3.tf | 6 +++++- .../modules/pointers-table/dynamodb.tf | 2 +- .../modules/truststore-bucket/s3.tf | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf b/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf index 5e6b80440..0a41e073a 100644 --- a/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf +++ b/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf @@ -2,7 +2,11 @@ resource "aws_s3_bucket" "authorization-store" { bucket = "${var.name_prefix}-authorization-store" force_destroy = var.enable_bucket_force_destroy - tags = { + tags = var.enable_backups ? { + Name = "authorization store" + Environment = "${var.name_prefix}" + NHSE-Enable-Backup = "daily" + } : { Name = "authorization store" Environment = "${var.name_prefix}" } diff --git a/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf b/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf index 4bb3745e1..0cdb15e8c 100644 --- a/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf +++ b/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf @@ -52,5 +52,5 @@ resource "aws_dynamodb_table" "pointers" { enabled = var.enable_pitr } - tags = var.enable_backups ? { NHSE-Enable-Backup : daily } : {} + tags = var.enable_backups ? { NHSE-Enable-Backup = "daily" } : {} } diff --git a/terraform/account-wide-infrastructure/modules/truststore-bucket/s3.tf b/terraform/account-wide-infrastructure/modules/truststore-bucket/s3.tf index 6767ecaa5..1eac1f4fa 100644 --- a/terraform/account-wide-infrastructure/modules/truststore-bucket/s3.tf +++ b/terraform/account-wide-infrastructure/modules/truststore-bucket/s3.tf @@ -1,6 +1,7 @@ resource "aws_s3_bucket" "api_truststore" { bucket = "${var.name_prefix}-api-truststore" force_destroy = var.enable_bucket_force_destroy + tags = var.enable_backups ? { NHSE-Enable-Backup = "daily" } : {} } resource "aws_s3_bucket_policy" "api_truststore_bucket_policy" { From ef6a542705d65b680757d1ba6ecb3d95ea70384e Mon Sep 17 00:00:00 2001 From: Kate Bobyn Date: Fri, 22 Nov 2024 15:17:39 +0000 Subject: [PATCH 09/13] NRL-853 fix tags to match policy --- .../modules/permissions-store-bucket/s3.tf | 2 +- .../modules/pointers-table/dynamodb.tf | 2 +- .../account-wide-infrastructure/modules/truststore-bucket/s3.tf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf b/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf index 0a41e073a..7454d13ac 100644 --- a/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf +++ b/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf @@ -5,7 +5,7 @@ resource "aws_s3_bucket" "authorization-store" { tags = var.enable_backups ? { Name = "authorization store" Environment = "${var.name_prefix}" - NHSE-Enable-Backup = "daily" + NHSE-Enable-Backup = "true" } : { Name = "authorization store" Environment = "${var.name_prefix}" diff --git a/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf b/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf index 0cdb15e8c..19c9184cb 100644 --- a/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf +++ b/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf @@ -52,5 +52,5 @@ resource "aws_dynamodb_table" "pointers" { enabled = var.enable_pitr } - tags = var.enable_backups ? { NHSE-Enable-Backup = "daily" } : {} + tags = var.enable_backups ? { NHSE-Enable-Backup = "true" } : {} } diff --git a/terraform/account-wide-infrastructure/modules/truststore-bucket/s3.tf b/terraform/account-wide-infrastructure/modules/truststore-bucket/s3.tf index 1eac1f4fa..a3d4f970b 100644 --- a/terraform/account-wide-infrastructure/modules/truststore-bucket/s3.tf +++ b/terraform/account-wide-infrastructure/modules/truststore-bucket/s3.tf @@ -1,7 +1,7 @@ resource "aws_s3_bucket" "api_truststore" { bucket = "${var.name_prefix}-api-truststore" force_destroy = var.enable_bucket_force_destroy - tags = var.enable_backups ? { NHSE-Enable-Backup = "daily" } : {} + tags = var.enable_backups ? { NHSE-Enable-Backup = "true" } : {} } resource "aws_s3_bucket_policy" "api_truststore_bucket_policy" { From 1d715a2b367f6683f4a8e6156770a37122522a77 Mon Sep 17 00:00:00 2001 From: Kate Bobyn Date: Fri, 22 Nov 2024 15:45:47 +0000 Subject: [PATCH 10/13] NRL-853 split back into different plans for s3 and ddb --- .../dev/aws-backups.tf | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/terraform/account-wide-infrastructure/dev/aws-backups.tf b/terraform/account-wide-infrastructure/dev/aws-backups.tf index 8dd30b4e0..6bc45623d 100644 --- a/terraform/account-wide-infrastructure/dev/aws-backups.tf +++ b/terraform/account-wide-infrastructure/dev/aws-backups.tf @@ -150,7 +150,7 @@ module "source" { backup_plan_config = { "compliance_resource_types" : [ - "S3", "DynamoDB" + "S3" ], "rules" : [ { @@ -166,4 +166,24 @@ module "source" { ], "selection_tag" : "NHSE-Enable-Backup" } + + backup_plan_config_dynamodb = { + "compliance_resource_types" : [ + "DynamoDB" + ], + "enable" : true, + "rules" : [ + { + "copy_action" : { + "delete_after" : 4 + }, + "lifecycle" : { + "delete_after" : 2 + }, + "name" : "daily_kept_for_2_days", + "schedule" : "cron(0 0 * * ? *)" + } + ], + "selection_tag" : "NHSE-Enable-Backup" + } } From 179eeb98454102c6b88fba3d34ee46db22a7598e Mon Sep 17 00:00:00 2001 From: Kate Bobyn Date: Fri, 22 Nov 2024 15:47:03 +0000 Subject: [PATCH 11/13] NRL-853 split back into different plans for s3 and ddb --- .../modules/backup-source/backup_plan.tf | 2 +- .../modules/backup-source/variables.tf | 88 +------------------ 2 files changed, 2 insertions(+), 88 deletions(-) diff --git a/terraform/account-wide-infrastructure/modules/backup-source/backup_plan.tf b/terraform/account-wide-infrastructure/modules/backup-source/backup_plan.tf index 4d2cf5066..b5ac3c1df 100644 --- a/terraform/account-wide-infrastructure/modules/backup-source/backup_plan.tf +++ b/terraform/account-wide-infrastructure/modules/backup-source/backup_plan.tf @@ -80,6 +80,6 @@ resource "aws_backup_selection" "dynamodb" { selection_tag { key = var.backup_plan_config_dynamodb.selection_tag type = "STRINGEQUALS" - value = "True" + value = "true" } } diff --git a/terraform/account-wide-infrastructure/modules/backup-source/variables.tf b/terraform/account-wide-infrastructure/modules/backup-source/variables.tf index a76f7a4d2..88acbfd19 100644 --- a/terraform/account-wide-infrastructure/modules/backup-source/variables.tf +++ b/terraform/account-wide-infrastructure/modules/backup-source/variables.tf @@ -89,56 +89,7 @@ variable "backup_plan_config" { })) })) }) - default = { - selection_tag = "BackupLocal" - 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({ @@ -158,42 +109,5 @@ variable "backup_plan_config_dynamodb" { })) }))) }) - default = { - enable = true - selection_tag = "BackupDynamoDB" - 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 - } - } - ] - } + } From 2eea40c4accf10fbd9bec36bd433566ea635a8ba Mon Sep 17 00:00:00 2001 From: Kate Bobyn Date: Sun, 1 Dec 2024 18:21:27 +0000 Subject: [PATCH 12/13] NRL-853 use different tags for S3 an dynamodb resources --- .../dev/aws-backups.tf | 20 +++++++++---------- .../modules/permissions-store-bucket/s3.tf | 11 ++++------ .../modules/pointers-table/dynamodb.tf | 2 +- .../modules/truststore-bucket/s3.tf | 2 +- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/terraform/account-wide-infrastructure/dev/aws-backups.tf b/terraform/account-wide-infrastructure/dev/aws-backups.tf index 6bc45623d..c62666ece 100644 --- a/terraform/account-wide-infrastructure/dev/aws-backups.tf +++ b/terraform/account-wide-infrastructure/dev/aws-backups.tf @@ -9,9 +9,9 @@ variable "destination_vault_arn" { default = "" } -#data "aws_arn" "destination_vault_arn" { -# arn = var.destination_vault_arn -#} +data "aws_arn" "destination_vault_arn" { + arn = var.destination_vault_arn +} data "aws_secretsmanager_secret" "backup-account-secret" { name = "nhsd-nrlf--dev--test-backup-account-id" @@ -138,11 +138,11 @@ module "source" { source = "../modules/backup-source" backup_copy_vault_account_id = local.destination_account_id - # backup_copy_vault_arn = data.aws_arn.destination_vault_arn.arn - environment_name = local.environment_name - bootstrap_kms_key_arn = aws_kms_key.backup_notifications.arn - project_name = local.project_name - reports_bucket = aws_s3_bucket.backup_reports.bucket + backup_copy_vault_arn = data.aws_arn.destination_vault_arn.arn + environment_name = local.environment_name + bootstrap_kms_key_arn = aws_kms_key.backup_notifications.arn + project_name = local.project_name + reports_bucket = aws_s3_bucket.backup_reports.bucket #terraform_role_arn = data.aws_caller_identity.current.arn terraform_role_arn = "arn:aws:iam::${var.assume_account}:role/${var.assume_role}" @@ -164,7 +164,7 @@ module "source" { "schedule" : "cron(0 0 * * ? *)" } ], - "selection_tag" : "NHSE-Enable-Backup" + "selection_tag" : "NHSE-Enable-S3-Backup" } backup_plan_config_dynamodb = { @@ -184,6 +184,6 @@ module "source" { "schedule" : "cron(0 0 * * ? *)" } ], - "selection_tag" : "NHSE-Enable-Backup" + "selection_tag" : "NHSE-Enable-DDB-Backup" } } diff --git a/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf b/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf index 7454d13ac..06e61a58e 100644 --- a/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf +++ b/terraform/account-wide-infrastructure/modules/permissions-store-bucket/s3.tf @@ -2,13 +2,10 @@ resource "aws_s3_bucket" "authorization-store" { bucket = "${var.name_prefix}-authorization-store" force_destroy = var.enable_bucket_force_destroy - tags = var.enable_backups ? { - Name = "authorization store" - Environment = "${var.name_prefix}" - NHSE-Enable-Backup = "true" - } : { - Name = "authorization store" - Environment = "${var.name_prefix}" + tags = { + Name = "authorization store" + Environment = "${var.name_prefix}" + NHSE-Enable-S3-Backup = "${var.enable_backups}" } } diff --git a/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf b/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf index 19c9184cb..93e060fdb 100644 --- a/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf +++ b/terraform/account-wide-infrastructure/modules/pointers-table/dynamodb.tf @@ -52,5 +52,5 @@ resource "aws_dynamodb_table" "pointers" { enabled = var.enable_pitr } - tags = var.enable_backups ? { NHSE-Enable-Backup = "true" } : {} + tags = { NHSE-Enable-DDB-Backup = "${var.enable_backups}" } } diff --git a/terraform/account-wide-infrastructure/modules/truststore-bucket/s3.tf b/terraform/account-wide-infrastructure/modules/truststore-bucket/s3.tf index a3d4f970b..aa32f2f16 100644 --- a/terraform/account-wide-infrastructure/modules/truststore-bucket/s3.tf +++ b/terraform/account-wide-infrastructure/modules/truststore-bucket/s3.tf @@ -1,7 +1,7 @@ resource "aws_s3_bucket" "api_truststore" { bucket = "${var.name_prefix}-api-truststore" force_destroy = var.enable_bucket_force_destroy - tags = var.enable_backups ? { NHSE-Enable-Backup = "true" } : {} + tags = { NHSE-Enable-S3-Backup = "${var.enable_backups}" } } resource "aws_s3_bucket_policy" "api_truststore_bucket_policy" { From 61fad3441f7ab19fc4cb4c34fcf0537ae617aea2 Mon Sep 17 00:00:00 2001 From: Matt Dean Date: Mon, 2 Dec 2024 17:50:12 +0000 Subject: [PATCH 13/13] [NRL-853] Added backup destination vault arn as secret. Moved backup vars/data/locals into dev account-side infra files --- .../dev/{aws-backups.tf => aws-backup.tf} | 47 ++++--------------- .../account-wide-infrastructure/dev/data.tf | 4 ++ .../dev/secrets.tf | 5 ++ .../modules/backup-source/backup_plan.tf | 10 ++-- .../backup-source/backup_report_plan.tf | 2 +- .../backup-source/backup_vault_policy.tf | 2 +- .../modules/backup-source/variables.tf | 1 + 7 files changed, 26 insertions(+), 45 deletions(-) rename terraform/account-wide-infrastructure/dev/{aws-backups.tf => aws-backup.tf} (72%) diff --git a/terraform/account-wide-infrastructure/dev/aws-backups.tf b/terraform/account-wide-infrastructure/dev/aws-backup.tf similarity index 72% rename from terraform/account-wide-infrastructure/dev/aws-backups.tf rename to terraform/account-wide-infrastructure/dev/aws-backup.tf index c62666ece..fc41d32a8 100644 --- a/terraform/account-wide-infrastructure/dev/aws-backups.tf +++ b/terraform/account-wide-infrastructure/dev/aws-backup.tf @@ -1,38 +1,7 @@ -provider "aws" { - alias = "source" - region = "eu-west-2" -} - -variable "destination_vault_arn" { - description = "ARN of the backup vault in the destination account" - type = string - default = "" -} - -data "aws_arn" "destination_vault_arn" { - arn = var.destination_vault_arn -} - -data "aws_secretsmanager_secret" "backup-account-secret" { - name = "nhsd-nrlf--dev--test-backup-account-id" -} -data "aws_secretsmanager_secret_version" "destination_account_id" { - secret_id = data.aws_secretsmanager_secret.backup-account-secret.id -} - -locals { - # Adjust these as required - project_name = "dev-backups-poc" - environment_name = "dev" - - source_account_id = data.aws_caller_identity.current.account_id - # destination_account_id = data.aws_arn.destination_vault_arn.account - destination_account_id = data.aws_secretsmanager_secret_version.destination_account_id.secret_string -} # First, we create an S3 bucket for compliance reports. resource "aws_s3_bucket" "backup_reports" { - bucket_prefix = "${local.project_name}-backup-reports" + bucket_prefix = "${local.prefix}-backup-reports" } resource "aws_s3_bucket_public_access_block" "backup_reports" { @@ -115,7 +84,7 @@ resource "aws_kms_key" "backup_notifications" { Effect = "Allow" Sid = "Enable IAM User Permissions" Principal = { - AWS = "arn:aws:iam::${local.source_account_id}:root" + AWS = "arn:aws:iam::${var.assume_account}:root" } Action = "kms:*" Resource = "*" @@ -137,14 +106,13 @@ resource "aws_kms_key" "backup_notifications" { module "source" { source = "../modules/backup-source" - backup_copy_vault_account_id = local.destination_account_id - backup_copy_vault_arn = data.aws_arn.destination_vault_arn.arn - environment_name = local.environment_name + backup_copy_vault_account_id = jsondecode(data.aws_secretsmanager_secret_version.backup_destination_parameters.secret_string)["account-id"] + backup_copy_vault_arn = jsondecode(data.aws_secretsmanager_secret_version.backup_destination_parameters.secret_string)["vault-arn"] + environment_name = local.environment bootstrap_kms_key_arn = aws_kms_key.backup_notifications.arn - project_name = local.project_name + project_name = "${local.prefix}-" reports_bucket = aws_s3_bucket.backup_reports.bucket - #terraform_role_arn = data.aws_caller_identity.current.arn - terraform_role_arn = "arn:aws:iam::${var.assume_account}:role/${var.assume_role}" + terraform_role_arn = "arn:aws:iam::${var.assume_account}:role/${var.assume_role}" notification_target_email_addresses = local.notification_emails @@ -152,6 +120,7 @@ module "source" { "compliance_resource_types" : [ "S3" ], + "enable" = true, "rules" : [ { "copy_action" : { diff --git a/terraform/account-wide-infrastructure/dev/data.tf b/terraform/account-wide-infrastructure/dev/data.tf index 7b3c623de..bb435ed6b 100644 --- a/terraform/account-wide-infrastructure/dev/data.tf +++ b/terraform/account-wide-infrastructure/dev/data.tf @@ -2,6 +2,10 @@ data "aws_secretsmanager_secret_version" "identities_account_id" { secret_id = aws_secretsmanager_secret.identities_account_id.name } +data "aws_secretsmanager_secret_version" "backup_destination_parameters" { + secret_id = aws_secretsmanager_secret.backup_destination_parameters.name +} + data "aws_secretsmanager_secret" "emails" { name = "${local.prefix}-emails" } diff --git a/terraform/account-wide-infrastructure/dev/secrets.tf b/terraform/account-wide-infrastructure/dev/secrets.tf index 2559c81cd..bc9b0a3cc 100644 --- a/terraform/account-wide-infrastructure/dev/secrets.tf +++ b/terraform/account-wide-infrastructure/dev/secrets.tf @@ -2,6 +2,11 @@ resource "aws_secretsmanager_secret" "identities_account_id" { name = "${local.prefix}--nhs-identities-account-id" } +resource "aws_secretsmanager_secret" "backup_destination_parameters" { + name = "${local.prefix}--backup-destination-parameters" + description = "Parameters used to configure the backup destination" +} + resource "aws_secretsmanager_secret" "notification_email_addresses" { name = "${local.prefix}-dev-notification-email-addresses" } diff --git a/terraform/account-wide-infrastructure/modules/backup-source/backup_plan.tf b/terraform/account-wide-infrastructure/modules/backup-source/backup_plan.tf index b5ac3c1df..0e6cd4ce8 100644 --- a/terraform/account-wide-infrastructure/modules/backup-source/backup_plan.tf +++ b/terraform/account-wide-infrastructure/modules/backup-source/backup_plan.tf @@ -1,5 +1,6 @@ resource "aws_backup_plan" "default" { - name = "${local.resource_name_prefix}-plan" + count = var.backup_plan_config.enable ? 1 : 0 + name = "${local.resource_name_prefix}-plan" dynamic "rule" { for_each = var.backup_plan_config.rules @@ -16,7 +17,7 @@ resource "aws_backup_plan" "default" { cold_storage_after = rule.value.lifecycle.cold_storage_after != null ? rule.value.lifecycle.cold_storage_after : null } dynamic "copy_action" { - for_each = var.backup_copy_vault_arn != "" && var.backup_copy_vault_account_id != "" && rule.value.copy_action != null ? rule.value.copy_action : {} + for_each = rule.value.copy_action != null ? rule.value.copy_action : {} content { lifecycle { delete_after = copy_action.value @@ -47,7 +48,7 @@ resource "aws_backup_plan" "dynamodb" { cold_storage_after = rule.value.lifecycle.cold_storage_after != null ? rule.value.lifecycle.cold_storage_after : null } dynamic "copy_action" { - for_each = var.backup_copy_vault_arn != "" && var.backup_copy_vault_account_id != "" && rule.value.copy_action != null ? rule.value.copy_action : {} + for_each = rule.value.copy_action != null ? rule.value.copy_action : {} content { lifecycle { delete_after = copy_action.value @@ -60,9 +61,10 @@ resource "aws_backup_plan" "dynamodb" { } resource "aws_backup_selection" "default" { + count = var.backup_plan_config.enable ? 1 : 0 iam_role_arn = aws_iam_role.backup.arn name = "${local.resource_name_prefix}-selection" - plan_id = aws_backup_plan.default.id + plan_id = aws_backup_plan.default[0].id selection_tag { key = var.backup_plan_config.selection_tag diff --git a/terraform/account-wide-infrastructure/modules/backup-source/backup_report_plan.tf b/terraform/account-wide-infrastructure/modules/backup-source/backup_report_plan.tf index e0f20d41e..7120bfe70 100644 --- a/terraform/account-wide-infrastructure/modules/backup-source/backup_report_plan.tf +++ b/terraform/account-wide-infrastructure/modules/backup-source/backup_report_plan.tf @@ -54,7 +54,7 @@ resource "aws_backup_report_plan" "resource_compliance" { } resource "aws_backup_report_plan" "copy_jobs" { - count = var.backup_copy_vault_arn != "" && var.backup_copy_vault_account_id != "" ? 1 : 0 + count = var.backup_plan_config.enable || var.backup_plan_config_dynamodb.enable ? 1 : 0 name = "copy_jobs" description = "Report for showing whether copies ran successfully in the last 24 hours" diff --git a/terraform/account-wide-infrastructure/modules/backup-source/backup_vault_policy.tf b/terraform/account-wide-infrastructure/modules/backup-source/backup_vault_policy.tf index 09d4ec117..f1e6222e9 100644 --- a/terraform/account-wide-infrastructure/modules/backup-source/backup_vault_policy.tf +++ b/terraform/account-wide-infrastructure/modules/backup-source/backup_vault_policy.tf @@ -30,7 +30,7 @@ data "aws_iam_policy_document" "vault_policy" { resources = ["*"] } dynamic "statement" { - for_each = var.backup_copy_vault_arn != "" && var.backup_copy_vault_account_id != "" ? [1] : [] + for_each = var.backup_plan_config.enable || var.backup_plan_config_dynamodb.enable ? [1] : [] content { sid = "Allow account to copy into backup vault" effect = "Allow" diff --git a/terraform/account-wide-infrastructure/modules/backup-source/variables.tf b/terraform/account-wide-infrastructure/modules/backup-source/variables.tf index 88acbfd19..72cc612f6 100644 --- a/terraform/account-wide-infrastructure/modules/backup-source/variables.tf +++ b/terraform/account-wide-infrastructure/modules/backup-source/variables.tf @@ -74,6 +74,7 @@ variable "backup_copy_vault_account_id" { variable "backup_plan_config" { description = "Configuration for backup plans" type = object({ + enable = bool selection_tag = string compliance_resource_types = list(string) rules = list(object({