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"
},