diff --git a/CHANGELOG.md b/CHANGELOG.md index c805ab5..48bba27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. +## 0.23.2 (March 5, 2025) + +FIXES: + +* Add missing README file + ## 0.23.1 (March 4, 2025) FIXES: diff --git a/README.md b/README.md new file mode 100644 index 0000000..fdb8338 --- /dev/null +++ b/README.md @@ -0,0 +1,528 @@ + +![Terraform](https://lgallardo.com/images/terraform.jpg) + +# terraform-aws-backup + +Terraform module to create AWS Backup plans. AWS Backup is a fully managed backup service that makes it easy to centralize and automate the back up of data across AWS services (EBS volumes, RDS databases, DynamoDB tables, EFS file systems, and Storage Gateway volumes). + +## Features + +* Flexible backup plan customization +* Comprehensive backup management: + - Rules and selections + - Copy actions and lifecycle policies + - Retention periods and windows + - Resource tagging +* Advanced capabilities: + - IAM role management + - Multi-region support + - Vault management + - Framework integration + - Organization policies +* Enterprise features: + - Notifications system + - Audit Manager integration + - Cross-account backups + - Compliance controls + +## Usage + +You can use this module to create a simple plan using the module's `rule_*` variables. You can also use the `rules` and `selections` list of maps variables to build a more complete plan by defining several rules and selections at once. + +Check the [examples](/examples/) folder where you can see how to configure backup plans with different selection criteria. + +### Simple plan + +```hcl +# AWS SNS Topic +resource "aws_sns_topic" "backup_vault_notifications" { + name = "backup-vault-events" +} + +# AWS Backup +module "aws_backup_example" { + source = "../.." + + # Vault + vault_name = "vault-3" + + # Vault lock configuration + min_retention_days = 7 # Minimum retention of 7 days + max_retention_days = 90 # Maximum retention of 90 days + + # Plan + plan_name = "simple-plan" + + # Multiple rules using a list of maps + rules = [ + { + name = "rule-1" + schedule = "cron(0 12 * * ? *)" + start_window = 120 + completion_window = 360 + lifecycle = { + cold_storage_after = 0 + delete_after = 90 + } + copy_actions = [] + recovery_point_tags = { + Environment = "prod" + } + }, + { + name = "rule-2" + target_vault_name = "Default" + schedule = "cron(0 7 * * ? *)" + start_window = 120 + completion_window = 360 + lifecycle = { + cold_storage_after = 0 + delete_after = 90 + } + copy_actions = [] + recovery_point_tags = { + Environment = "prod" + } + } + ] + + # Multiple selections + selections = [ + { + name = "selection-1" + resources = [ + "arn:aws:dynamodb:us-east-1:123456789101:table/mydynamodb-table1", + "arn:aws:dynamodb:us-east-1:123456789101:table/mydynamodb-table2" + ] + selection_tags = [ + { + type = "STRINGEQUALS" + key = "Environment" + value = "prod" + } + ] + } + ] + + tags = { + Owner = "backup team" + Environment = "prod" + Terraform = true + } +} +``` + +### Simple plan using variables + +```hcl +# AWS SNS Topic +resource "aws_sns_topic" "backup_vault_notifications" { + name = "backup-vault-events" +} + +# AWS Backup +module "aws_backup_example" { + source = "../.." + + # Vault + vault_name = "vault-1" + + # Vault lock configuration + min_retention_days = 7 + max_retention_days = 120 + + # Plan + plan_name = "simple-plan" + + # Rule + rule_name = "rule-1" + rule_schedule = "cron(0 12 * * ? *)" + rule_start_window = 120 + rule_completion_window = 360 + rule_lifecycle_cold_storage_after = 30 + rule_lifecycle_delete_after = 120 + rule_recovery_point_tags = { + Environment = "prod" + } + + # Selection + selection_name = "selection-1" + selection_resources = [ + "arn:aws:dynamodb:us-east-1:123456789101:table/mydynamodb-table1", + "arn:aws:dynamodb:us-east-1:123456789101:table/mydynamodb-table2" + ] + selection_tags = [ + { + type = "STRINGEQUALS" + key = "Environment" + value = "prod" + } + ] + + # Tags + tags = { + Owner = "backup team" + Environment = "prod" + Terraform = true + } +} +``` + + +### Complete plan + +```hcl +# AWS SNS Topic +resource "aws_sns_topic" "backup_vault_notifications" { + name = "backup-vault-events" +} + +# AWS Backup +module "aws_backup_example" { + source = "../.." + + # Vault configuration + vault_name = "complete_vault" + vault_kms_key_arn = "arn:aws:kms:us-west-2:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab" + vault_force_destroy = true + min_retention_days = 7 + max_retention_days = 360 + locked = true + changeable_for_days = 3 + + # Backup plan configuration + plan_name = "complete_backup_plan" + + # Backup rules configuration + rules = [ + { + name = "rule_1" + schedule = "cron(0 5 ? * * *)" + start_window = 480 + completion_window = 561 + enable_continuous_backup = false + lifecycle = { + cold_storage_after = 30 + delete_after = 180 + } + recovery_point_tags = { + Environment = "prod" + } + copy_actions = [ + { + destination_vault_arn = "arn:aws:backup:us-east-1:123456789012:backup-vault:secondary_vault" + lifecycle = { + cold_storage_after = 30 + delete_after = 180 + } + } + ] + }, + { + name = "rule_2" + schedule = "cron(0 5 ? * * *)" + start_window = 480 + completion_window = 561 + enable_continuous_backup = false + lifecycle = { + cold_storage_after = 30 + delete_after = 360 + } + recovery_point_tags = { + Environment = "prod" + } + copy_actions = [ + { + destination_vault_arn = "arn:aws:backup:us-east-1:123456789012:backup-vault:secondary_vault" + lifecycle = { + cold_storage_after = 30 + delete_after = 360 + } + } + ] + } + ] + + # Backup selection configuration + selections = [ + { + name = "complete_selection" + selection_tag = { + type = "STRINGEQUALS" + key = "Environment" + value = "prod" + } + resources = [ + "arn:aws:dynamodb:us-west-2:123456789012:table/my-table", + "arn:aws:ec2:us-west-2:123456789012:volume/vol-12345678" + ] + } + ] + + tags = { + Environment = "prod" + Project = "complete_backup" + } +} +``` + + +### Simple plan using AWS Organizations backup policies + +```hcl +module "aws_backup_example" { + source = "../.." + + # Backup Plan configuration + plan_name = "organization_backup_plan" + + # Vault configuration + vault_name = "organization_backup_vault" + min_retention_days = 7 + max_retention_days = 365 + + rules = [ + { + name = "critical_systems" + target_vault_name = "critical_systems_vault" + schedule = "cron(0 5 ? * * *)" + start_window = 480 + completion_window = 561 + enable_continuous_backup = false + lifecycle = { + cold_storage_after = 30 + delete_after = 365 + } + recovery_point_tags = { + Environment = "prod" + Criticality = "high" + } + copy_actions = [ + { + destination_vault_arn = "arn:aws:backup:us-east-1:123456789012:backup-vault:secondary_vault" + lifecycle = { + cold_storage_after = 30 + delete_after = 365 + } + } + ] + }, + { + name = "standard_systems" + target_vault_name = "standard_systems_vault" + schedule = "cron(0 5 ? * * *)" + start_window = 480 + completion_window = 561 + enable_continuous_backup = false + lifecycle = { + cold_storage_after = 0 + delete_after = 90 + } + recovery_point_tags = { + Environment = "prod" + Criticality = "standard" + } + copy_actions = [ + { + destination_vault_arn = "arn:aws:backup:us-east-1:123456789012:backup-vault:secondary_vault" + lifecycle = { + cold_storage_after = 0 + delete_after = 90 + } + } + ] + } + ] + + # Selection configuration + selections = [ + { + name = "critical_systems" + selection_tag = { + type = "STRINGEQUALS" + key = "Criticality" + value = "high" + } + }, + { + name = "standard_systems" + selection_tag = { + type = "STRINGEQUALS" + key = "Criticality" + value = "standard" + } + } + ] + + tags = { + Environment = "prod" + Project = "organization_backup" + } +} +``` + +### AWS Backup Audit Manager Framework + +```hcl +# AWS Backup +module "aws_backup_example" { + source = "../.." + + # Audit Framework + audit_framework = { + create = true + name = "exampleFramework" + description = "this is an example framework" + + controls = [ + # Vault lock check - ensures resources are protected by vault lock + { + name = "BACKUP_RESOURCES_PROTECTED_BY_BACKUP_VAULT_LOCK" + parameter_name = "maxRetentionDays" + parameter_value = "100" # Maximum retention period allowed by vault lock + }, + ] + } + + # Tags are now specified separately + tags = { + Name = "Example Framework" + } +} +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0.0 | +| [aws](#requirement\_aws) | >= 4.0.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 5.89.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_backup_framework.ab_framework](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_framework) | resource | +| [aws_backup_plan.ab_plan](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_plan) | resource | +| [aws_backup_report_plan.ab_report](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_report_plan) | resource | +| [aws_backup_selection.ab_selection](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_selection) | resource | +| [aws_backup_selection.ab_selections](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_selection) | resource | +| [aws_backup_vault.ab_vault](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault) | resource | +| [aws_backup_vault_lock_configuration.ab_vault_lock_configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault_lock_configuration) | resource | +| [aws_backup_vault_notifications.backup_events](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault_notifications) | resource | +| [aws_iam_policy.ab_tag_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.ab_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.ab_backup_s3_policy_attach](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.ab_policy_attach](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.ab_restores_policy_attach](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.ab_restores_s3_policy_attach](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.ab_tag_policy_attach](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_organizations_policy.backup_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/organizations_policy) | resource | +| [aws_organizations_policy_attachment.backup_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/organizations_policy_attachment) | resource | +| [aws_sns_topic_policy.backup_events](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_policy) | resource | +| [aws_iam_policy_document.ab_role_assume_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.ab_tag_policy_document](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.backup_events](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [advanced\_backup\_settings](#input\_advanced\_backup\_settings) | Advanced backup settings by resource type | `map(map(string))` | `{}` | no | +| [audit\_framework](#input\_audit\_framework) | Configuration for AWS Backup Audit Manager framework |
object({
create = bool
name = string
description = optional(string)
controls = list(object({
name = string
parameter_name = optional(string)
parameter_value = optional(string)
}))
})
|
{
"controls": [],
"create": false,
"description": null,
"name": null
}
| no | +| [backup\_policies](#input\_backup\_policies) | Map of backup policies to create |
map(object({
target_vault_name = string
schedule = string
start_window = number
completion_window = number
lifecycle = object({
delete_after = number
cold_storage_after = optional(number)
})
recovery_point_tags = optional(map(string))
copy_actions = optional(list(map(string)))
enable_continuous_backup = optional(bool)
}))
| `{}` | no | +| [backup\_regions](#input\_backup\_regions) | List of regions where backups should be created | `list(string)` | `[]` | no | +| [backup\_selections](#input\_backup\_selections) | Map of backup selections |
map(object({
resources = optional(list(string))
not_resources = optional(list(string))
conditions = optional(map(any))
tags = optional(map(string))
}))
| `{}` | no | +| [changeable\_for\_days](#input\_changeable\_for\_days) | The number of days before the lock date. If omitted creates a vault lock in governance mode, otherwise it will create a vault lock in compliance mode | `number` | `null` | no | +| [enable\_org\_policy](#input\_enable\_org\_policy) | Enable AWS Organizations backup policy | `bool` | `false` | no | +| [enabled](#input\_enabled) | Change to false to avoid deploying any AWS Backup resources | `bool` | `true` | no | +| [iam\_role\_arn](#input\_iam\_role\_arn) | If configured, the module will attach this role to selections, instead of creating IAM resources by itself | `string` | `null` | no | +| [iam\_role\_name](#input\_iam\_role\_name) | Allow to set IAM role name, otherwise use predefined default | `string` | `""` | no | +| [locked](#input\_locked) | Change to true to add a lock configuration for the backup vault | `bool` | `false` | no | +| [max\_retention\_days](#input\_max\_retention\_days) | The maximum retention period that the vault retains its recovery points | `number` | `null` | no | +| [min\_retention\_days](#input\_min\_retention\_days) | The minimum retention period that the vault retains its recovery points | `number` | `null` | no | +| [notifications](#input\_notifications) | Notification block which defines backup vault events and the SNS Topic ARN to send AWS Backup notifications to. Leave it empty to disable notifications | `any` | `{}` | no | +| [notifications\_disable\_sns\_policy](#input\_notifications\_disable\_sns\_policy) | Disable the creation of the SNS policy. Enable if you need to manage the policy elsewhere. | `bool` | `false` | no | +| [org\_policy\_description](#input\_org\_policy\_description) | Description of the AWS Organizations backup policy | `string` | `"AWS Organizations backup policy"` | no | +| [org\_policy\_name](#input\_org\_policy\_name) | Name of the AWS Organizations backup policy | `string` | `"backup-policy"` | no | +| [org\_policy\_target\_id](#input\_org\_policy\_target\_id) | Target ID (Root/OU/Account) for the backup policy | `string` | `null` | no | +| [plan\_name](#input\_plan\_name) | The display name of a backup plan | `string` | `null` | no | +| [reports](#input\_reports) | The default cache behavior for this distribution. |
list(object({
name = string
description = optional(string, null)
formats = optional(list(string), null)
s3_bucket_name = string
s3_key_prefix = optional(string, null)
report_template = string
accounts = optional(list(string), null)
organization_units = optional(list(string), null)
regions = optional(list(string), null)
framework_arns = optional(list(string), [])
}))
| `[]` | no | +| [rule\_completion\_window](#input\_rule\_completion\_window) | The amount of time AWS Backup attempts a backup before canceling the job and returning an error | `number` | `null` | no | +| [rule\_enable\_continuous\_backup](#input\_rule\_enable\_continuous\_backup) | Enable continuous backups for supported resources. | `bool` | `false` | no | +| [rule\_lifecycle\_cold\_storage\_after](#input\_rule\_lifecycle\_cold\_storage\_after) | Specifies the number of days after creation that a recovery point is moved to cold storage | `number` | `null` | no | +| [rule\_lifecycle\_delete\_after](#input\_rule\_lifecycle\_delete\_after) | Specifies the number of days after creation that a recovery point is deleted. Must be 90 days greater than `cold_storage_after` | `number` | `null` | no | +| [rule\_name](#input\_rule\_name) | An display name for a backup rule | `string` | `null` | no | +| [rule\_recovery\_point\_tags](#input\_rule\_recovery\_point\_tags) | Metadata that you can assign to help organize the resources that you create | `map(string)` | `{}` | no | +| [rule\_schedule](#input\_rule\_schedule) | A CRON expression specifying when AWS Backup initiates a backup job | `string` | `null` | no | +| [rule\_start\_window](#input\_rule\_start\_window) | The amount of time in minutes before beginning a backup | `number` | `null` | no | +| [rules](#input\_rules) | A list of rule maps |
list(object({
name = string
target_vault_name = optional(string)
schedule = optional(string)
start_window = optional(number)
completion_window = optional(number)
enable_continuous_backup = optional(bool)
lifecycle = optional(object({
cold_storage_after = optional(number)
delete_after = number
}))
recovery_point_tags = optional(map(string))
copy_actions = optional(list(object({
destination_vault_arn = string
lifecycle = optional(object({
cold_storage_after = optional(number)
delete_after = number
}))
})))
}))
| `[]` | no | +| [selection\_conditions](#input\_selection\_conditions) | A map of conditions that you define to assign resources to your backup plans using tags. | `map(any)` | `{}` | no | +| [selection\_name](#input\_selection\_name) | The display name of a resource selection document | `string` | `null` | no | +| [selection\_not\_resources](#input\_selection\_not\_resources) | An array of strings that either contain Amazon Resource Names (ARNs) or match patterns of resources to exclude from a backup plan. | `list(any)` | `[]` | no | +| [selection\_resources](#input\_selection\_resources) | An array of strings that either contain Amazon Resource Names (ARNs) or match patterns of resources to assign to a backup plan | `list(any)` | `[]` | no | +| [selection\_tags](#input\_selection\_tags) | List of tags for `selection_name` var, when using variable definition. | `list(any)` | `[]` | no | +| [selections](#input\_selections) | A list or map of backup selections. If passing a list, each selection must have a name attribute. | `any` | `[]` | no | +| [tags](#input\_tags) | A mapping of tags to assign to the resource | `map(string)` | `{}` | no | +| [vault\_force\_destroy](#input\_vault\_force\_destroy) | A boolean that indicates that all recovery points stored in the vault are deleted so that the vault can be destroyed without error | `bool` | `false` | no | +| [vault\_kms\_key\_arn](#input\_vault\_kms\_key\_arn) | The server-side encryption key that is used to protect your backups | `string` | `null` | no | +| [vault\_name](#input\_vault\_name) | Name of the backup vault to create. If not given, AWS use default | `string` | `null` | no | +| [windows\_vss\_backup](#input\_windows\_vss\_backup) | Enable Windows VSS backup option and create a VSS Windows backup | `bool` | `false` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [framework\_arn](#output\_framework\_arn) | The ARN of the backup framework | +| [framework\_creation\_time](#output\_framework\_creation\_time) | The date and time that the backup framework was created | +| [framework\_id](#output\_framework\_id) | The unique identifier of the backup framework | +| [framework\_status](#output\_framework\_status) | The deployment status of the backup framework | +| [plan\_arn](#output\_plan\_arn) | The ARN of the backup plan | +| [plan\_id](#output\_plan\_id) | The id of the backup plan | +| [plan\_role](#output\_plan\_role) | The service role of the backup plan | +| [plan\_version](#output\_plan\_version) | Unique, randomly generated, Unicode, UTF-8 encoded string that serves as the version ID of the backup plan | +| [vault\_arn](#output\_vault\_arn) | The ARN of the vault | +| [vault\_id](#output\_vault\_id) | The name of the vault | + + +## Known Issues + +During the development of the module, the following issues were found: + +### Error creating Backup Vault + +In case you get an error message similar to this one: + +``` +error creating Backup Vault (): AccessDeniedException: status code: 403, request id: 8e7e577e-5b74-4d4d-95d0-bf63e0b2cc2e, +``` + +Add the [required IAM permissions mentioned in the CreateBackupVault row](https://docs.aws.amazon.com/aws-backup/latest/devguide/access-control.html#backup-api-permissions-ref) to the role or user creating the Vault (the one running Terraform CLI). In particular make sure `kms` and `backup-storage` permissions are added. + + +## Known Issues + +During the development of the module, the following issues were found: + +### Error creating Backup Vault + +In case you get an error message similar to this one: + +``` +error creating Backup Vault (): AccessDeniedException: status code: 403, request id: 8e7e577e-5b74-4d4d-95d0-bf63e0b2cc2e, +``` + +Add the [required IAM permissions mentioned in the CreateBackupVault row](https://docs.aws.amazon.com/aws-backup/latest/devguide/access-control.html#backup-api-permissions-ref) to the role or user creating the Vault (the one running Terraform CLI). In particular make sure `kms` and `backup-storage` permissions are added. +