diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ec4b4fe71..2ece22ffe 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,6 +1,7 @@ { "modules/azure-disks-backup": "1.2.3", "modules/aws-sso": "0.6.1", + "modules/aws-backup": "0.0.0", "modules/azure-resource-group": "1.5.1", "modules/azure-oidc": "1.4.1", "modules/azure-flexible-server-postgresql": "3.1.2", diff --git a/modules/aws-backup/.terraform-docs.yml b/modules/aws-backup/.terraform-docs.yml new file mode 100644 index 000000000..49be8b854 --- /dev/null +++ b/modules/aws-backup/.terraform-docs.yml @@ -0,0 +1,48 @@ +formatter: "markdown" # this is required + +version: "" + +header-from: docs/header.md +footer-from: docs/footer.md + +recursive: + enabled: false + path: modules + include-main: true + +sections: + hide: [] + show: [] + +content: "" + +output: + file: "README.md" + mode: inject + template: |- + + {{ .Content }} + + +output-values: + enabled: false + from: "" + +sort: + enabled: true + by: name + +settings: + anchor: true + color: true + default: true + description: false + escape: true + hide-empty: false + html: true + indent: 2 + lockfile: true + read-comments: true + required: true + sensitive: true + type: true diff --git a/modules/aws-backup/README.md b/modules/aws-backup/README.md new file mode 100644 index 000000000..23c36dd8e --- /dev/null +++ b/modules/aws-backup/README.md @@ -0,0 +1,206 @@ + +# **AWS BACKUP Terraform Module** + +## Overview + +This module provides configuration for AWS Backup, including vault creation, backup plans, and resource selection. + +## Key Features + +- **Vault**: Creates a vault to store backups. +- **Plan**: Creates backup plans with options to replicate backups to other vaults, including cross-account and cross-region replication. +- **Selections**: Allows selection of resources for backup using tags or specifying the resource ARN. + +## Basic Usage + +### Minimal Example (Creates only a vault to store backups; this option does not perform backups!) + +```hcl +module "backup" { + source = "github.com/prefapp/tfm/modules/aws-backup" + aws_backup_vault = [{ + vault_name = "my-vault" + }] +} +``` + +### Example with plan and tag selection + +```hcl +module "backup" { + source = "github.com/prefapp/tfm/modules/aws-backup" + aws_backup_vault = [{ + vault_name = "only-rds-component-tags-backup" + # vault_region = "eu-west-1" + # vault_tags = { + # "one" = "two" + # "three" = "four" + # } + plan = [{ + name = "only-rds-daily-backup" + rule_name = "my-rule" + schedule = "cron(0 12 * * ? *)" + backup_selection_conditions = { + string_equals = [ + { key = "aws:ResourceTag/Component", value = "rds" } + ] + } + }] + } + ] +} +``` + +### With alias, replication to other regions, and access from other AWS accounts + +/!\ Important: Only works with aws organizations, you need to enable cross\_account\_backup in organization main account + +This only works in organization main account +```hcl +module "backup" { + source = "github.com/prefapp/tfm/modules/aws-backup" + + enable_cross_account_backup = true +} +``` + +For the accounts in your organization + +In the account that only receives backups: + +```hcl +module "backup" { + source = "github.com/prefapp/tfm/modules/aws-backup" + aws_backup_vault = [{ + vault_name = "only-rds-component-tags-backup" + # vault_region = "eu-west-1" + # vault_tags = { + # "one" = "two" + # "three" = "four" + # } + } + ] +} +``` + +In the account that will make backups and send them to another account + +```hcl +module "backup" { + source = "github.com/prefapp/tfm/modules/aws-backup" + aws_backup_vault = [{ + vault_name = "only-rds-component-tags-backup" + # vault_region = "eu-west-1" + # vault_tags = { + # "one" = "two" + # "three" = "four" + # } + plan = [{ + name = "only-rds-daily-backup" + rule_name = "my-rule" + schedule = "cron(0 12 * * ? *)" + backup_selection_conditions = { + string_equals = [ + { key = "aws:ResourceTag/Component", value = "rds" } + ] + } + }] + } + ] + copy_action_default_values = { + destination_account_id = "098765432109" + destination_region = "eu-west-1" + delete_after = 7 + } +} +``` + +## File Structure + +The module is organized with the following directory and file structure: + +``` +├── backup-global-configuration.tf +├── docs +│   ├── footer.md +│   └── header.md +├── _examples +│   ├── minimal +│   │   └── main.tf +│   ├── vault_with_plan_and_selection +│   │   └── main.tf +│   └── vault_with_plan_selection_with_replication +│   └── main.tf +├── iam-policy-roles.tf +├── main.tf +└── variables.tf +``` + +- **main.tf**: Entry point that wires together all module components, here they create vaults, plans and selections. +- **iam-policy-roles.tf**: Policy document for aws vaults. +- **backup-global-configuration.tf**: Configuration for enable cross account backup in organizations. + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5 | +| [aws](#requirement\_aws) | ~> 6.3 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | ~> 6.3 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_backup_global_settings.global](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_global_settings) | resource | +| [aws_backup_plan.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_plan) | resource | +| [aws_backup_selection.resource_selection](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_selection) | resource | +| [aws_backup_selection.tag_selection](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_selection) | resource | +| [aws_backup_vault.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault) | resource | +| [aws_backup_vault_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault_policy) | resource | +| [aws_iam_role.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [aws\_backup\_vault](#input\_aws\_backup\_vault) | List of objects defining the backup vault configuration, including backup plans and replication rules. |
list(object({
vault_name = string
vault_region = optional(string)
vault_tags = optional(map(string))
vault_kms_key_arn = optional(string)

plan = optional(list(object({
name = string
rule_name = string
schedule = string
schedule_expression_timezone = optional(string)
start_window = optional(number)
completion_window = optional(number)
# Structure for dynamic conditions in aws_backup_selection
# Example usage:
# backup_selection_conditions = {
# string_equals = [
# { key = "aws:ResourceTag/Component", value = "rds" }
# ]
# string_like = [
# { key = "aws:ResourceTag/Application", value = "app*" }
# ]
# string_not_equals = [
# { key = "aws:ResourceTag/Backup", value = "false" }
# ]
# string_not_like = [
# { key = "aws:ResourceTag/Environment", value = "test*" }
# ]
# }
backup_selection_conditions = optional(object({
string_equals = optional(list(object({ key = string, value = string })))
string_like = optional(list(object({ key = string, value = string })))
string_not_equals = optional(list(object({ key = string, value = string })))
string_not_like = optional(list(object({ key = string, value = string })))
}))
backup_selection_arn_resources = optional(list(string))
lifecycle = optional(object({
cold_storage_after = number
delete_after = number
}))
advanced_backup_setting = optional(list(object({
backup_options = map(string)
resource_type = string
})))
scan_action = optional(list(object({
malware_scanner = string
scan_action_type = string
})))
recovery_point_tags = optional(map(string))
tags = optional(map(string))
copy_action = optional(list(object({
destination_vault_arn = string
delete_after = optional(number)
})))
})
))
})
)
| `[]` | no | +| [aws\_kms\_key\_vault\_arn](#input\_aws\_kms\_key\_vault\_arn) | ARN of the KMS key used to encrypt the backup vault. If not provided, the default AWS Backup vault encryption will be used. | `string` | `null` | no | +| [copy\_action\_default\_values](#input\_copy\_action\_default\_values) | Default values for the copy action configuration in backup plan rules. If not provided, the copy action will not be created. |
object({
destination_account_id = string
destination_region = string
delete_after = number
})
|
{
"delete_after": 14,
"destination_account_id": null,
"destination_region": null
}
| no | +| [enable\_cross\_account\_backup](#input\_enable\_cross\_account\_backup) | Enable cross-account backup in AWS Backup global settings. If set to true, the module will manage the global settings resource to enable cross-account backup. If set to false, you can configure it separately if needed. | `bool` | `false` | no | +| [tags](#input\_tags) | Default tags to apply to all resources. | `map(string)` | `{}` | no | + +## Outputs + +No outputs. + +## Examples + +For detailed examples, refer to the [module examples](https://github.com/prefapp/tfm/tree/main/modules/aws-backup/_examples): + +- [Minimal](https://github.com/prefapp/tfm/tree/main/modules/aws-backup/_examples/minimal) – Minimal vault creation +- [Vault with plan and selection](https://github.com/prefapp/tfm/tree/main/modules/aws-backup/_examples/vault\_with\_plan\_and\_selection) – Backup vault creation with configuration of plans and backup selections +- [Vault with plan, selection, and replication](https://github.com/prefapp/tfm/tree/main/modules/aws-backup/_examples/vault\_with\_plan\_selection\_with\_replication) – KMS key creation with alias, cross-region replication, and additional account access + +## Remote Resources +- Terraform: https://www.terraform.io/ +- Amazon AWS Backup: [https://aws.amazon.com/es/backup/](https://aws.amazon.com/es/backup/) +- Terraform AWS Provider: [https://registry.terraform.io/providers/hashicorp/aws/latest](https://registry.terraform.io/providers/hashicorp/aws/latest) + +## Support + +For issues, questions, or contributions related to this module, please visit the repository’s issue tracker: [https://github.com/prefapp/tfm/issues](https://github.com/prefapp/tfm/issues) + \ No newline at end of file diff --git a/modules/aws-backup/_examples/minimal/main.tf b/modules/aws-backup/_examples/minimal/main.tf new file mode 100644 index 000000000..d0e689766 --- /dev/null +++ b/modules/aws-backup/_examples/minimal/main.tf @@ -0,0 +1,29 @@ +# Example: Minimal AWS Backup vault creation + +terraform { + required_version = ">= 1.5" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.3" + } + } +} + +provider "aws" { + region = "eu-west-1" +} + +module "backup" { + source = "./../.." + + aws_backup_vault = [{ + vault_name = "my-vault" + # vault_region = "eu-west-1" + # vault_tags = { + # "one" = "two" + # "tree" = "four" + # } + } + ] +} diff --git a/modules/aws-backup/_examples/vault_with_plan_and_selection/main.tf b/modules/aws-backup/_examples/vault_with_plan_and_selection/main.tf new file mode 100644 index 000000000..6e2621249 --- /dev/null +++ b/modules/aws-backup/_examples/vault_with_plan_and_selection/main.tf @@ -0,0 +1,39 @@ +# Example: AWS Backup vault with plan and selection + +terraform { + required_version = ">= 1.5" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.3" + } + } +} + +provider "aws" { + region = "eu-west-1" +} + +module "backup" { + source = "./../.." + + aws_backup_vault = [{ + vault_name = "only-rds-backup" + # vault_region = "eu-west-1" + # vault_tags = { + # "one" = "two" + # "tree" = "four" + # } + plan = [{ + name = "only-rds-daily-backup" + rule_name = "my-rule" + schedule = "cron(0 12 * * ? *)" + backup_selection_conditions = { + string_equals = [ + { key = "aws:ResourceTag/Component", value = "rds" } + ] + } + }] + } + ] +} diff --git a/modules/aws-backup/_examples/vault_with_plan_selection_with_replication/main.tf b/modules/aws-backup/_examples/vault_with_plan_selection_with_replication/main.tf new file mode 100644 index 000000000..08cfbbbde --- /dev/null +++ b/modules/aws-backup/_examples/vault_with_plan_selection_with_replication/main.tf @@ -0,0 +1,53 @@ +# Example: AWS Backup vault with plan, selection, and cross-region replication + +terraform { + required_version = ">= 1.5" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.3" + } + } +} + +provider "aws" { + region = "eu-west-1" +} + +module "backup-cross-region" { + source = "./../.." + aws_backup_vault = [{ + vault_name = "only-rds-backup" + vault_region = "us-east-1" + }] + +} +module "backup" { + source = "./../.." + + aws_backup_vault = [{ + vault_name = "only-rds-backup" + # vault_region = "eu-west-1" + # vault_tags = { + # "one" = "two" + # "tree" = "four" + # } + plan = [{ + name = "only-rds-daily-backup" + rule_name = "my-rule" + schedule = "cron(0 12 * * ? *)" + backup_selection_conditions = { + string_equals = [ + { key = "aws:ResourceTag/Component", value = "rds" } + ] + } + + }] + } + ] + copy_action_default_values = { + destination_account_id = "123456789012" # Same account id for cross-region copy, different account id for cross-account copy + destination_region = "us-east-1" + delete_after = 8 + } +} diff --git a/modules/aws-backup/backup-global-configuration.tf b/modules/aws-backup/backup-global-configuration.tf new file mode 100644 index 000000000..9e3b18b7a --- /dev/null +++ b/modules/aws-backup/backup-global-configuration.tf @@ -0,0 +1,6 @@ +resource "aws_backup_global_settings" "global" { + for_each = var.enable_cross_account_backup ? { "global" : "global" } : {} + global_settings = { + "isCrossAccountBackupEnabled" = "true" + } +} \ No newline at end of file diff --git a/modules/aws-backup/docs/footer.md b/modules/aws-backup/docs/footer.md new file mode 100644 index 000000000..b2d91e472 --- /dev/null +++ b/modules/aws-backup/docs/footer.md @@ -0,0 +1,16 @@ +## Examples + +For detailed examples, refer to the [module examples](https://github.com/prefapp/tfm/tree/main/modules/aws-backup/_examples): + +- [Minimal](https://github.com/prefapp/tfm/tree/main/modules/aws-backup/_examples/minimal) – Minimal vault creation +- [Vault with plan and selection](https://github.com/prefapp/tfm/tree/main/modules/aws-backup/_examples/vault_with_plan_and_selection) – Backup vault creation with configuration of plans and backup selections +- [Vault with plan, selection, and replication](https://github.com/prefapp/tfm/tree/main/modules/aws-backup/_examples/vault_with_plan_selection_with_replication) – KMS key creation with alias, cross-region replication, and additional account access + +## Remote Resources +- Terraform: https://www.terraform.io/ +- Amazon AWS Backup: [https://aws.amazon.com/es/backup/](https://aws.amazon.com/es/backup/) +- Terraform AWS Provider: [https://registry.terraform.io/providers/hashicorp/aws/latest](https://registry.terraform.io/providers/hashicorp/aws/latest) + +## Support + +For issues, questions, or contributions related to this module, please visit the repository’s issue tracker: [https://github.com/prefapp/tfm/issues](https://github.com/prefapp/tfm/issues) \ No newline at end of file diff --git a/modules/aws-backup/docs/header.md b/modules/aws-backup/docs/header.md new file mode 100644 index 000000000..3dad8fef5 --- /dev/null +++ b/modules/aws-backup/docs/header.md @@ -0,0 +1,143 @@ +# **AWS BACKUP Terraform Module** + +## Overview + +This module provides configuration for AWS Backup, including vault creation, backup plans, and resource selection. + +## Key Features + +- **Vault**: Creates a vault to store backups. +- **Plan**: Creates backup plans with options to replicate backups to other vaults, including cross-account and cross-region replication. +- **Selections**: Allows selection of resources for backup using tags or specifying the resource ARN. + +## Basic Usage + +### Minimal Example (Creates only a vault to store backups; this option does not perform backups!) + +```hcl +module "backup" { + source = "github.com/prefapp/tfm/modules/aws-backup" + aws_backup_vault = [{ + vault_name = "my-vault" + }] +} +``` + +### Example with plan and tag selection + +```hcl +module "backup" { + source = "github.com/prefapp/tfm/modules/aws-backup" + aws_backup_vault = [{ + vault_name = "only-rds-component-tags-backup" + # vault_region = "eu-west-1" + # vault_tags = { + # "one" = "two" + # "three" = "four" + # } + plan = [{ + name = "only-rds-daily-backup" + rule_name = "my-rule" + schedule = "cron(0 12 * * ? *)" + backup_selection_conditions = { + string_equals = [ + { key = "aws:ResourceTag/Component", value = "rds" } + ] + } + }] + } + ] +} +``` + +### With alias, replication to other regions, and access from other AWS accounts + +/!\ Important: Only works with aws organizations, you need to enable cross_account_backup in organization main account + + +This only works in organization main account +```hcl +module "backup" { + source = "github.com/prefapp/tfm/modules/aws-backup" + + enable_cross_account_backup = true +} +``` + +For the accounts in your organization + +In the account that only receives backups: + +```hcl +module "backup" { + source = "github.com/prefapp/tfm/modules/aws-backup" + aws_backup_vault = [{ + vault_name = "only-rds-component-tags-backup" + # vault_region = "eu-west-1" + # vault_tags = { + # "one" = "two" + # "three" = "four" + # } + } + ] +} +``` + +In the account that will make backups and send them to another account + +```hcl +module "backup" { + source = "github.com/prefapp/tfm/modules/aws-backup" + aws_backup_vault = [{ + vault_name = "only-rds-component-tags-backup" + # vault_region = "eu-west-1" + # vault_tags = { + # "one" = "two" + # "three" = "four" + # } + plan = [{ + name = "only-rds-daily-backup" + rule_name = "my-rule" + schedule = "cron(0 12 * * ? *)" + backup_selection_conditions = { + string_equals = [ + { key = "aws:ResourceTag/Component", value = "rds" } + ] + } + }] + } + ] + copy_action_default_values = { + destination_account_id = "098765432109" + destination_region = "eu-west-1" + delete_after = 7 + } +} +``` + +## File Structure + +The module is organized with the following directory and file structure: + +``` +├── backup-global-configuration.tf +├── docs +│   ├── footer.md +│   └── header.md +├── _examples +│   ├── minimal +│   │   └── main.tf +│   ├── vault_with_plan_and_selection +│   │   └── main.tf +│   └── vault_with_plan_selection_with_replication +│   └── main.tf +├── iam-policy-roles.tf +├── main.tf +└── variables.tf +``` + +- **main.tf**: Entry point that wires together all module components, here they create vaults, plans and selections. +- **iam-policy-roles.tf**: Policy document for aws vaults. +- **backup-global-configuration.tf**: Configuration for enable cross account backup in organizations. + + diff --git a/modules/aws-backup/iam-policy-roles.tf b/modules/aws-backup/iam-policy-roles.tf new file mode 100644 index 000000000..38830593f --- /dev/null +++ b/modules/aws-backup/iam-policy-roles.tf @@ -0,0 +1,66 @@ +data "aws_caller_identity" "current" {} + +data "aws_iam_policy_document" "this" { + for_each = var.aws_backup_vault != [] ? { + for vault in var.aws_backup_vault : vault.vault_name => vault + if vault.vault_name != null + } : {} + statement { + effect = "Allow" + + principals { + type = "AWS" + identifiers = [data.aws_caller_identity.current.account_id] + } + + actions = [ + "backup:DescribeBackupVault", + "backup:DeleteBackupVault", + "backup:PutBackupVaultAccessPolicy", + "backup:DeleteBackupVaultAccessPolicy", + "backup:GetBackupVaultAccessPolicy", + "backup:StartBackupJob", + "backup:GetBackupVaultNotifications", + "backup:PutBackupVaultNotifications", + "backup:CopyIntoBackupVault" + ] + + resources = [aws_backup_vault.this[each.key].arn] + } +} + +resource "aws_backup_vault_policy" "this" { + for_each = length(var.aws_backup_vault) > 0 ? { + for vault in var.aws_backup_vault : vault.vault_name => vault + if vault.vault_name != null + } : {} + backup_vault_name = aws_backup_vault.this[each.key].name + policy = data.aws_iam_policy_document.this[each.key].json + region = try(each.value.vault_region, null) +} + +## Role for selection of backups + +data "aws_iam_policy_document" "assume_role" { + statement { + effect = "Allow" + + principals { + type = "Service" + identifiers = ["backup.amazonaws.com"] + } + + actions = ["sts:AssumeRole"] + } +} +resource "aws_iam_role" "this" { + count = var.aws_backup_vault != [] ? 1 : 0 + name_prefix = "backupselection-role-" + assume_role_policy = data.aws_iam_policy_document.assume_role.json +} + +resource "aws_iam_role_policy_attachment" "this" { + count = var.aws_backup_vault != [] ? 1 : 0 + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup" + role = aws_iam_role.this[0].name +} \ No newline at end of file diff --git a/modules/aws-backup/main.tf b/modules/aws-backup/main.tf new file mode 100644 index 000000000..c7231b079 --- /dev/null +++ b/modules/aws-backup/main.tf @@ -0,0 +1,163 @@ +## Vault to store backups + +resource "aws_backup_vault" "this" { + for_each = length(var.aws_backup_vault) > 0 ? { + for vault in var.aws_backup_vault : vault.vault_name => vault + if vault.vault_name != null + } : {} + name = each.value.vault_name + region = try(each.value.vault_region, null) + kms_key_arn = try(each.value.vault_kms_key_arn, null) != null ? each.value.vault_kms_key_arn : var.aws_kms_key_vault_arn + tags = merge(var.tags, try(each.value.vault_tags, {})) +} + + +resource "aws_backup_plan" "this" { + for_each = { + for obj in flatten([ + for vault in var.aws_backup_vault : ( + vault.vault_name != null && vault.plan != null ? [ + for plan in vault.plan : { + key = "${vault.vault_name}.${plan.name}" + vault = vault + plan = plan + } + if plan.name != null + ] : [] + ) + ]) : obj.key => { + vault = obj.vault + plan = obj.plan + } + } + name = each.value.plan.name + + rule { + rule_name = try(each.value.plan.rule_name, "tf_example_backup_rule") + target_vault_name = aws_backup_vault.this[each.value.vault.vault_name].name + schedule = try(each.value.plan.schedule, "cron(0 12 * * ? *)") + schedule_expression_timezone = try(each.value.plan.schedule_expression_timezone, null) + start_window = try(each.value.plan.start_window, null) != null ? each.value.plan.start_window : 60 + completion_window = try(each.value.plan.completion_window, null) != null ? each.value.plan.completion_window : 11520 + recovery_point_tags = try(each.value.plan.recovery_point_tags, {}) != {} ? each.value.plan.recovery_point_tags : null + dynamic "scan_action" { + for_each = each.value.plan.scan_action != null ? [1] : [] + content { + malware_scanner = try(scan_action.value.malware_scanner, null) != null ? scan_action.value.malware_scanner : null + scan_mode = try(scan_action.value.scan_action_type, null) != null ? scan_action.value.scan_action_type : null + } + } + + dynamic "copy_action" { + for_each = try(each.value.plan.copy_action, null) != null ? [1] : var.copy_action_default_values.destination_account_id != null && var.copy_action_default_values.destination_region != null ? [1] : [] + # for_each = try(each.value.plan.copy_action, null) != null ? each.value.plan.copy_action : [] + content { + destination_vault_arn = try(each.value.plan.copy_action[0].destination_vault_arn, "arn:aws:backup:${var.copy_action_default_values.destination_region}:${var.copy_action_default_values.destination_account_id}:backup-vault:${aws_backup_vault.this[each.value.vault.vault_name].name}") + lifecycle { + delete_after = try(each.value.plan.copy_action[0].delete_after, var.copy_action_default_values.delete_after) != null ? try(each.value.plan.copy_action[0].delete_after, var.copy_action_default_values.delete_after) : null + } + } + } + + lifecycle { + cold_storage_after = try(each.value.plan.lifecycle.cold_storage_after, null) + delete_after = try(each.value.plan.lifecycle.delete_after, 14) + } + } + + dynamic "advanced_backup_setting" { + for_each = try(each.value.plan.advanced_backup_setting, null) != null ? each.value.plan.advanced_backup_setting : [] + content { + backup_options = try(advanced_backup_setting.value.backup_options, { WindowsVSS = "enabled" }) + resource_type = try(advanced_backup_setting.value.resource_type, "EC2") + } + } + + tags = merge(var.tags, try(each.value.plan.tags, {})) +} + + +resource "aws_backup_selection" "tag_selection" { + for_each = { + for obj in flatten([ + for vault in var.aws_backup_vault : ( + vault.vault_name != null && vault.plan != null ? [ + for plan in vault.plan : { + key = "${vault.vault_name}.${plan.name}" + vault = vault + plan = plan + } + if plan.name != null && plan.backup_selection_conditions != null + ] : [] + ) + ]) : obj.key => { + vault = obj.vault + plan = obj.plan + } + } + region = try(each.value.vault.vault_region, null) + iam_role_arn = aws_iam_role.this[0].arn + name = substr(each.key, 0, 50) # Backup selection name must be 50 characters or fewer + plan_id = aws_backup_plan.this[each.key].id + resources = ["*"] + + dynamic "condition" { + for_each = try(each.value.plan.backup_selection_conditions, null) != null ? [each.value.plan.backup_selection_conditions] : [] + content { + dynamic "string_equals" { + for_each = try(condition.value.string_equals, null) != null ? condition.value.string_equals : [] + content { + key = try(string_equals.value.key, null) + value = try(string_equals.value.value, null) + } + } + dynamic "string_like" { + for_each = try(condition.value.string_like, null) != null ? condition.value.string_like : [] + content { + key = try(string_like.value.key, null) + value = try(string_like.value.value, null) + } + } + dynamic "string_not_equals" { + for_each = try(condition.value.string_not_equals, null) != null ? condition.value.string_not_equals : [] + content { + key = try(string_not_equals.value.key, null) + value = try(string_not_equals.value.value, null) + } + } + dynamic "string_not_like" { + for_each = try(condition.value.string_not_like, null) != null ? condition.value.string_not_like : [] + content { + key = try(string_not_like.value.key, null) + value = try(string_not_like.value.value, null) + } + } + } + } +} + +resource "aws_backup_selection" "resource_selection" { + for_each = { + for obj in flatten([ + for vault in var.aws_backup_vault : ( + vault.vault_name != null && vault.plan != null ? [ + for plan in vault.plan : { + key = "${vault.vault_name}.${plan.name}" + vault = vault + plan = plan + } + if plan.name != null && plan.backup_selection_arn_resources != null + ] : [] + ) + ]) : obj.key => { + vault = obj.vault + plan = obj.plan + } + } + region = try(each.value.vault.vault_region, null) + iam_role_arn = aws_iam_role.this[0].arn + name = substr(each.key, 0, 50) # Backup selection name must be 50 characters or fewer + plan_id = aws_backup_plan.this[each.key].id + + resources = each.value.plan.backup_selection_arn_resources +} \ No newline at end of file diff --git a/modules/aws-backup/variables.tf b/modules/aws-backup/variables.tf new file mode 100644 index 000000000..eda2a5231 --- /dev/null +++ b/modules/aws-backup/variables.tf @@ -0,0 +1,95 @@ +variable "aws_kms_key_vault_arn" { + description = "ARN of the KMS key used to encrypt the backup vault. If not provided, the default AWS Backup vault encryption will be used." + type = string + default = null +} + +variable "aws_backup_vault" { + description = "List of objects defining the backup vault configuration, including backup plans and replication rules." + type = list(object({ + vault_name = string + vault_region = optional(string) + vault_tags = optional(map(string)) + vault_kms_key_arn = optional(string) + + plan = optional(list(object({ + name = string + rule_name = string + schedule = string + schedule_expression_timezone = optional(string) + start_window = optional(number) + completion_window = optional(number) + # Structure for dynamic conditions in aws_backup_selection + # Example usage: + # backup_selection_conditions = { + # string_equals = [ + # { key = "aws:ResourceTag/Component", value = "rds" } + # ] + # string_like = [ + # { key = "aws:ResourceTag/Application", value = "app*" } + # ] + # string_not_equals = [ + # { key = "aws:ResourceTag/Backup", value = "false" } + # ] + # string_not_like = [ + # { key = "aws:ResourceTag/Environment", value = "test*" } + # ] + # } + backup_selection_conditions = optional(object({ + string_equals = optional(list(object({ key = string, value = string }))) + string_like = optional(list(object({ key = string, value = string }))) + string_not_equals = optional(list(object({ key = string, value = string }))) + string_not_like = optional(list(object({ key = string, value = string }))) + })) + backup_selection_arn_resources = optional(list(string)) + lifecycle = optional(object({ + cold_storage_after = number + delete_after = number + })) + advanced_backup_setting = optional(list(object({ + backup_options = map(string) + resource_type = string + }))) + scan_action = optional(list(object({ + malware_scanner = string + scan_action_type = string + }))) + recovery_point_tags = optional(map(string)) + tags = optional(map(string)) + copy_action = optional(list(object({ + destination_vault_arn = string + delete_after = optional(number) + }))) + }) + )) + }) + ) + default = [] + +} + +variable "tags" { + description = "Default tags to apply to all resources." + type = map(string) + default = {} +} + +variable "copy_action_default_values" { + description = "Default values for the copy action configuration in backup plan rules. If not provided, the copy action will not be created." + type = object({ + destination_account_id = string + destination_region = string + delete_after = number + }) + default = { + destination_account_id = null + destination_region = null + delete_after = 14 + } +} + +variable "enable_cross_account_backup" { + description = "Enable cross-account backup in AWS Backup global settings. If set to true, the module will manage the global settings resource to enable cross-account backup. If set to false, you can configure it separately if needed." + type = bool + default = false +} \ No newline at end of file diff --git a/modules/aws-backup/versions.tf b/modules/aws-backup/versions.tf new file mode 100644 index 000000000..1197ec56d --- /dev/null +++ b/modules/aws-backup/versions.tf @@ -0,0 +1,12 @@ +terraform { + + required_version = ">= 1.5" + + required_providers { + + aws = { + source = "hashicorp/aws" + version = "~> 6.3" + } + } +} diff --git a/release-please-config.json b/release-please-config.json index 1791e861c..b84d43507 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -4,6 +4,9 @@ "release-type": "terraform-module", "commit-search-depth": 5, "packages": { + "modules/aws-backup": { + "package-name": "aws-backup" + }, "modules/aws-parameter-store": { "package-name": "aws-parameter-store" },