diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
index 2484993..dfe1f01 100644
--- a/.github/workflows/pre-commit.yml
+++ b/.github/workflows/pre-commit.yml
@@ -10,7 +10,7 @@ on:
env:
PYTHON_VERSION: "3.10"
TERRAFORM_DOCS_VERSION: "v0.16.0"
- TFLINT_VERSION: "v0.40.1"
+ TFLINT_VERSION: "v0.45.0"
permissions:
contents: read
diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml
index e396e4b..46f6e7a 100644
--- a/.github/workflows/validate.yaml
+++ b/.github/workflows/validate.yaml
@@ -49,4 +49,4 @@ jobs:
- name: Terraform Validate
id: validate
- run: terraform validate
+ run: terraform -chdir=examples validate
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 2f4c9dd..d4a85b6 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -15,6 +15,7 @@ repos:
- id: terraform_fmt
- id: terraform_tflint
- id: terraform_validate
+ exclude: '^[^/]+$'
- id: terraform_checkov
- id: terraform_docs
args:
diff --git a/.tflint.hcl b/.tflint.hcl
index 372282e..00fa5c5 100644
--- a/.tflint.hcl
+++ b/.tflint.hcl
@@ -1,6 +1,6 @@
plugin "terraform" {
enabled = true
- version = "0.1.1"
+ version = "0.2.2"
source = "github.com/terraform-linters/tflint-ruleset-terraform"
preset = "recommended"
}
diff --git a/README.md b/README.md
index d75996d..5e0f62d 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# AWS <$module-name> Terraform module
+# AWS Backup Terraform module
[](https://lablabs.io/)
@@ -6,44 +6,82 @@ We help companies build, run, deploy and scale software and infrastructure by em
---
-[![Terraform validate](https://github.com/lablabs/terraform-aws-<$module-name>/actions/workflows/validate.yaml/badge.svg)](https://github.com/lablabs/terraform-aws-<$module-name>/actions/workflows/validate.yaml)
-[![pre-commit](https://github.com/lablabs/terraform-aws-<$module-name>/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/lablabs/terraform-aws-<$module-name>/actions/workflows/pre-commit.yml)
+[![Terraform validate](https://github.com/lablabs/terraform-aws-backup/actions/workflows/validate.yaml/badge.svg)](https://github.com/lablabs/terraform-aws-backup/actions/workflows/validate.yaml)
+[![pre-commit](https://github.com/lablabs/terraform-aws-backup/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/lablabs/terraform-aws-backup/actions/workflows/pre-commit.yml)
## Description
-A Terraform module to provision <$module-name>
+A Terraform module to provision AWS Backup
## Related Projects
Check out other [terraform modules](https://github.com/orgs/lablabs/repositories?q=terraform-aws&type=public&language=&sort=).
## Examples
-
-See [Basic example](examples/basic/README.md) for further information.
+- [Single account example](examples/single-account/README.md)
+- [Cross account example](examples/cross-account/README.md)
+ - Backup vault in other account will be created
## Requirements
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.0 |
+| [terraform](#requirement\_terraform) | >= 1.3 |
| [aws](#requirement\_aws) | >= 4.19.0 |
## Modules
-No modules.
+| Name | Source | Version |
+|------|--------|---------|
+| [source\_kms\_key](#module\_source\_kms\_key) | cloudposse/kms-key/aws | 0.12.1 |
+| [source\_label](#module\_source\_label) | cloudposse/label/null | 0.25.0 |
+| [source\_role](#module\_source\_role) | cloudposse/iam-role/aws | 0.17.0 |
+| [target\_kms\_key](#module\_target\_kms\_key) | cloudposse/kms-key/aws | 0.12.1 |
+| [target\_label](#module\_target\_label) | cloudposse/label/null | 0.25.0 |
+| [target\_role](#module\_target\_role) | cloudposse/iam-role/aws | 0.17.0 |
## Resources
-No resources.
+| Name | Type |
+|------|------|
+| [aws_backup_plan.source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_plan) | resource |
+| [aws_backup_selection.source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_selection) | resource |
+| [aws_backup_selection.tag](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_selection) | resource |
+| [aws_backup_vault.source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault) | resource |
+| [aws_backup_vault.target](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault) | resource |
+| [aws_backup_vault_policy.source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault_policy) | resource |
+| [aws_backup_vault_policy.target](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault_policy) | resource |
+| [aws_caller_identity.source](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
+| [aws_caller_identity.target](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
+| [aws_iam_policy_document.kms_source_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.kms_target_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.source_vault](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.target_vault](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
## Inputs
-No inputs.
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no |
+| [backup\_plans](#input\_backup\_plans) | Backup plans config along with rule and resources setup |
list(object({
name = string
resources = optional(list(string), [])
selection_tags = optional(list(object({
type = string
key = string
value = string
})), [])
rules = list(object({
name = string
schedule = string
enable_continuous_backup = optional(bool)
start_window = optional(string, 60)
completion_window = optional(number, 180)
lifecycle = optional(object({
cold_storage_after = optional(number)
delete_after = optional(number)
}))
copy_action_lifecycle = optional(object({
cold_storage_after = optional(number)
delete_after = optional(number)
}))
recovery_point_tags = optional(map(string))
}))
advanced_backup_setting = optional(object({
WindowsVSS = optional(string, null)
resource_type = optional(string, null)
}), null)
}))
| `[]` | no |
+| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` | {
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no |
+| [enabled](#input\_enabled) | Variable indicating whether deployment is enabled | `bool` | `true` | no |
+| [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no |
+| [is\_cross\_account\_backup\_enabled](#input\_is\_cross\_account\_backup\_enabled) | Create backup vault on different account and turn on copy action to this vault (provider.target needs to be set) | `bool` | `false` | no |
+| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no |
+| [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no |
+| [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no |
+| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no |
## Outputs
-No outputs.
+| Name | Description |
+|------|-------------|
+| [source\_backup\_vault\_arn](#output\_source\_backup\_vault\_arn) | Backup Vault ARN of source backup vault |
+| [source\_backup\_vault\_id](#output\_source\_backup\_vault\_id) | Backup Vault ID of source backup vault |
+| [target\_backup\_vault\_arn](#output\_target\_backup\_vault\_arn) | Backup Vault ARN of target backup vault |
+| [target\_backup\_vault\_id](#output\_target\_backup\_vault\_id) | Backup Vault ID of target backup vault |
## Contributing and reporting issues
diff --git a/aws_backup.tf b/aws_backup.tf
new file mode 100644
index 0000000..8f071d9
--- /dev/null
+++ b/aws_backup.tf
@@ -0,0 +1,130 @@
+# Source vault
+resource "aws_backup_vault" "source" {
+ count = var.enabled ? 1 : 0
+ provider = aws.source
+ name = module.source_label.id
+ kms_key_arn = module.source_kms_key.key_arn
+ tags = module.source_label.tags
+ force_destroy = true
+}
+
+resource "aws_backup_vault_policy" "source" {
+ count = var.enabled ? 1 : 0
+ provider = aws.source
+ backup_vault_name = aws_backup_vault.source[0].name
+ policy = data.aws_iam_policy_document.source_vault.json
+}
+
+resource "aws_backup_plan" "source" {
+ provider = aws.source
+ for_each = { for bp in var.backup_plans : bp.name => bp if var.enabled }
+
+ name = each.value.name
+ tags = module.source_label.tags
+
+ dynamic "rule" {
+ for_each = each.value.rules
+ content {
+ rule_name = rule.value.name
+ target_vault_name = aws_backup_vault.source[0].name
+ schedule = rule.value.schedule
+ start_window = try(rule.value.start_window, 60)
+ completion_window = try(rule.value.completion_window, 180)
+ recovery_point_tags = try(rule.value.recovery_point_tags, null)
+ enable_continuous_backup = try(rule.value.enable_continuous_backup, null)
+
+
+ dynamic "lifecycle" {
+ for_each = try(rule.value.lifecycle, null) != null ? [true] : []
+ content {
+ cold_storage_after = try(rule.value.lifecycle.cold_storage_after, null)
+ delete_after = try(rule.value.lifecycle.delete_after, null)
+ }
+ }
+
+ dynamic "copy_action" {
+ for_each = var.is_cross_account_backup_enabled == true ? [true] : []
+ content {
+ dynamic "lifecycle" {
+ for_each = try(rule.value.copy_action_lifecycle, null) != null ? [true] : []
+ content {
+ cold_storage_after = try(rule.value.copy_action_lifecycle.cold_storage_after, null)
+ delete_after = try(rule.value.copy_action_lifecycle.delete_after, null)
+ }
+ }
+ destination_vault_arn = aws_backup_vault.target[0].arn
+ }
+
+ }
+ }
+ }
+
+ dynamic "advanced_backup_setting" {
+ for_each = try(each.value.advanced_backup_setting, null) != null ? [true] : []
+
+ content {
+ backup_options = {
+ WindowsVSS = try(each.value.advanced_backup_setting.WindowsVSS, null)
+ }
+ resource_type = try(each.value.advanced_backup_setting.resource_type, null)
+ }
+ }
+}
+
+# Resource selection by arn
+resource "aws_backup_selection" "source" {
+ for_each = { for bp in flatten([
+ for bp_plan in var.backup_plans : [
+ for resource in bp_plan.resources : {
+ backup_plan_key : bp_plan.name
+ resource_arn : resource
+ }
+ ]
+ ]) : md5("${bp.backup_plan_key}${bp.resource_arn}") => bp if var.enabled }
+
+ provider = aws.source
+ iam_role_arn = module.source_role.arn
+ plan_id = aws_backup_plan.source[each.value.backup_plan_key].id
+ name = substr("${module.source_label.id}-${each.key}", 0, 50)
+ resources = [each.value.resource_arn]
+}
+
+# Resource selection by tag
+resource "aws_backup_selection" "tag" {
+ for_each = { for bp in flatten([
+ for bp_plan in var.backup_plans : [
+ for selection_tag in bp_plan.selection_tags : {
+ backup_plan_key : bp_plan.name
+ selection_tag : selection_tag
+ }
+ ]
+ ]) : md5("${bp.backup_plan_key}${bp.selection_tag["type"]}${bp.selection_tag["key"]}${bp.selection_tag["value"]}") => bp if var.enabled }
+
+ provider = aws.source
+ iam_role_arn = module.source_role.arn
+ plan_id = aws_backup_plan.source[each.value.backup_plan_key].id
+ name = substr("${module.source_label.id}-${each.key}", 0, 50)
+ resources = ["*"]
+ selection_tag {
+ type = each.value.selection_tag["type"]
+ key = each.value.selection_tag["key"]
+ value = each.value.selection_tag["value"]
+ }
+}
+
+# Target vault
+resource "aws_backup_vault" "target" {
+ count = var.enabled && var.is_cross_account_backup_enabled ? 1 : 0
+ provider = aws.target
+ name = module.target_label.id
+ kms_key_arn = module.target_kms_key.key_arn
+ tags = module.source_label.tags
+ force_destroy = true
+}
+
+resource "aws_backup_vault_policy" "target" {
+ count = var.enabled && var.is_cross_account_backup_enabled ? 1 : 0
+ provider = aws.target
+ backup_vault_name = aws_backup_vault.target[0].name
+ policy = data.aws_iam_policy_document.target_vault[0].json
+}
diff --git a/context.tf b/context.tf
new file mode 100644
index 0000000..e6a5bb3
--- /dev/null
+++ b/context.tf
@@ -0,0 +1,94 @@
+variable "namespace" {
+ type = string
+ default = null
+ description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique"
+}
+
+variable "environment" {
+ type = string
+ default = null
+ description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'"
+}
+
+variable "stage" {
+ type = string
+ default = null
+ description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'"
+}
+
+variable "name" {
+ type = string
+ default = null
+ description = <<-EOT
+ ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
+ This is the only ID element not also included as a `tag`.
+ The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input.
+ EOT
+}
+
+variable "attributes" {
+ type = list(string)
+ default = []
+ description = <<-EOT
+ ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
+ in the order they appear in the list. New attributes are appended to the
+ end of the list. The elements of the list are joined by the `delimiter`
+ and treated as a single ID element.
+ EOT
+}
+
+variable "tags" {
+ type = map(string)
+ default = {}
+ description = <<-EOT
+ Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
+ Neither the tag keys nor the tag values will be modified by this module.
+ EOT
+}
+
+variable "context" {
+ type = any
+ default = {
+ enabled = true
+ namespace = null
+ tenant = null
+ environment = null
+ stage = null
+ name = null
+ delimiter = null
+ attributes = []
+ tags = {}
+ additional_tag_map = {}
+ regex_replace_chars = null
+ label_order = []
+ id_length_limit = null
+ label_key_case = null
+ label_value_case = null
+ descriptor_formats = {}
+ # Note: we have to use [] instead of null for unset lists due to
+ # https://github.com/hashicorp/terraform/issues/28137
+ # which was not fixed until Terraform 1.0.0,
+ # but we want the default to be all the labels in `label_order`
+ # and we want users to be able to prevent all tag generation
+ # by setting `labels_as_tags` to `[]`, so we need
+ # a different sentinel to indicate "default"
+ labels_as_tags = ["unset"]
+ }
+ description = <<-EOT
+ Single object for setting entire context at once.
+ See description of individual variables for details.
+ Leave string and numeric variables as `null` to use default value.
+ Individual variable settings (non-null) override settings in context object,
+ except for attributes, tags, and additional_tag_map, which are merged.
+ EOT
+
+ validation {
+ condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"])
+ error_message = "Allowed values: `lower`, `title`, `upper`."
+ }
+
+ validation {
+ condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"])
+ error_message = "Allowed values: `lower`, `title`, `upper`, `none`."
+ }
+}
diff --git a/examples/basic/main.tf b/examples/basic/main.tf
deleted file mode 100644
index 838b218..0000000
--- a/examples/basic/main.tf
+++ /dev/null
@@ -1,3 +0,0 @@
-module "example_module" {
- source = "../../"
-}
diff --git a/examples/basic/providers.tf b/examples/basic/providers.tf
deleted file mode 100644
index e1bf6a7..0000000
--- a/examples/basic/providers.tf
+++ /dev/null
@@ -1,3 +0,0 @@
-provider "aws" {
- region = "eu-central-1"
-}
diff --git a/examples/basic/README.md b/examples/cross-account/README.md
similarity index 56%
rename from examples/basic/README.md
rename to examples/cross-account/README.md
index b4eaa18..863c119 100644
--- a/examples/basic/README.md
+++ b/examples/cross-account/README.md
@@ -14,11 +14,14 @@ The code in this example shows how to use the module with basic configuration an
| Name | Source | Version |
|------|--------|---------|
-| [example\_module](#module\_example\_module) | ../../ | n/a |
+| [aws-backup-dev-audit](#module\_aws-backup-dev-audit) | ../../ | n/a |
## Resources
-No resources.
+| Name | Type |
+|------|------|
+| [aws_backup_global_settings.aws_backup](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_global_settings) | resource |
+| [aws_dynamodb_table.example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource |
## Inputs
diff --git a/examples/cross-account/base.tf b/examples/cross-account/base.tf
new file mode 100644
index 0000000..a40e44f
--- /dev/null
+++ b/examples/cross-account/base.tf
@@ -0,0 +1,30 @@
+# terraform apply --target aws_dynamodb_table.basic-dynamodb-table
+resource "aws_dynamodb_table" "example" {
+ #checkov:skip=CKV_AWS_28
+ #checkov:skip=CKV_AWS_119
+ #checkov:skip=CKV2_AWS_16
+ #checkov:skip=CKV_AWS_28
+
+
+ provider = aws.source
+ name = "TestAWSBackup"
+ read_capacity = 1
+ write_capacity = 1
+ hash_key = "UserId"
+ range_key = "Title"
+
+ attribute {
+ name = "UserId"
+ type = "S"
+ }
+
+ attribute {
+ name = "Title"
+ type = "S"
+ }
+
+ tags = {
+ Name = "TestAWSBackup"
+ Environment = "Dev"
+ }
+}
diff --git a/examples/cross-account/main.tf b/examples/cross-account/main.tf
new file mode 100644
index 0000000..764339f
--- /dev/null
+++ b/examples/cross-account/main.tf
@@ -0,0 +1,77 @@
+resource "aws_backup_global_settings" "aws_backup" {
+ global_settings = {
+ "isCrossAccountBackupEnabled" = "true"
+ }
+ provider = aws.management
+}
+
+module "aws-backup-dev-audit" {
+ source = "../../"
+
+ providers = {
+ aws.source = aws.source
+ aws.target = aws.target
+ }
+
+ enabled = true
+
+ name = "dynamod-db"
+
+ # namespace = "aws-backup"
+
+ is_cross_account_backup_enabled = true
+
+ backup_plans = [
+ {
+ name = "dynamodb-plan"
+ resources = [aws_dynamodb_table.example.arn]
+ selection_tags = [
+ {
+ type = "STRINGEQUALS"
+ key = "tag:stage"
+ value = "dev"
+ }
+ ]
+ rules = [{
+ name = "dynamodb-rule"
+ schedule = "cron(30 * * * ? *)"
+ start_window = 60
+ completion_window = 120
+ lifecycle = {
+ delete_after = 14
+ }
+ copy_action_lifecycle = {
+ delete_after = 14
+ }
+
+ recovery_point_tags = {
+ "Environment" = "dev"
+ }
+ },
+ {
+ name = "dynamodb-rule"
+ schedule = "cron(0 * * * ? *)"
+ start_window = 60
+ completion_window = 120
+ lifecycle = {
+ delete_after = 14
+ }
+ recovery_point_tags = {
+ "Environment" = "dev"
+ }
+ }]
+ },
+ {
+ name = "dynamodb-plan2"
+ resources = [aws_dynamodb_table.example.arn]
+ rules = [{
+ name = "dynamodb-rule2"
+ schedule = "cron(0 1 * * ? *)"
+ start_window = 120
+ completion_window = 360
+ }]
+ }
+ ]
+
+
+}
diff --git a/examples/cross-account/providers.tf b/examples/cross-account/providers.tf
new file mode 100644
index 0000000..670611d
--- /dev/null
+++ b/examples/cross-account/providers.tf
@@ -0,0 +1,17 @@
+provider "aws" {
+ alias = "management"
+ region = "eu-central-1"
+ profile = "management"
+}
+
+provider "aws" {
+ alias = "source"
+ region = "eu-central-1"
+ profile = "dev"
+}
+
+provider "aws" {
+ alias = "target"
+ region = "eu-central-1"
+ profile = "audit"
+}
diff --git a/examples/basic/versions.tf b/examples/cross-account/versions.tf
similarity index 99%
rename from examples/basic/versions.tf
rename to examples/cross-account/versions.tf
index 90a7444..3b89985 100644
--- a/examples/basic/versions.tf
+++ b/examples/cross-account/versions.tf
@@ -5,6 +5,7 @@ terraform {
aws = {
source = "hashicorp/aws"
version = ">= 4.19.0"
+
}
}
}
diff --git a/examples/single-account/README.md b/examples/single-account/README.md
new file mode 100644
index 0000000..863c119
--- /dev/null
+++ b/examples/single-account/README.md
@@ -0,0 +1,33 @@
+# Basic example
+
+The code in this example shows how to use the module with basic configuration and minimal set of other resources.
+
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | >= 1.0 |
+| [aws](#requirement\_aws) | >= 4.19.0 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [aws-backup-dev-audit](#module\_aws-backup-dev-audit) | ../../ | n/a |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_backup_global_settings.aws_backup](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_global_settings) | resource |
+| [aws_dynamodb_table.example](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table) | resource |
+
+## Inputs
+
+No inputs.
+
+## Outputs
+
+No outputs.
+
diff --git a/examples/single-account/base.tf b/examples/single-account/base.tf
new file mode 100644
index 0000000..a40e44f
--- /dev/null
+++ b/examples/single-account/base.tf
@@ -0,0 +1,30 @@
+# terraform apply --target aws_dynamodb_table.basic-dynamodb-table
+resource "aws_dynamodb_table" "example" {
+ #checkov:skip=CKV_AWS_28
+ #checkov:skip=CKV_AWS_119
+ #checkov:skip=CKV2_AWS_16
+ #checkov:skip=CKV_AWS_28
+
+
+ provider = aws.source
+ name = "TestAWSBackup"
+ read_capacity = 1
+ write_capacity = 1
+ hash_key = "UserId"
+ range_key = "Title"
+
+ attribute {
+ name = "UserId"
+ type = "S"
+ }
+
+ attribute {
+ name = "Title"
+ type = "S"
+ }
+
+ tags = {
+ Name = "TestAWSBackup"
+ Environment = "Dev"
+ }
+}
diff --git a/examples/single-account/main.tf b/examples/single-account/main.tf
new file mode 100644
index 0000000..1afdeec
--- /dev/null
+++ b/examples/single-account/main.tf
@@ -0,0 +1,52 @@
+resource "aws_backup_global_settings" "aws_backup" {
+ global_settings = {
+ "isCrossAccountBackupEnabled" = "true"
+ }
+ provider = aws.management
+}
+
+module "aws-backup-dev-audit" {
+ source = "../../"
+
+ providers = {
+ aws.source = aws.source
+ aws.target = aws.source
+ }
+
+ name = "dynamod-db"
+
+ namespace = "aws-backup"
+
+ is_cross_account_backup_enabled = false
+
+ backup_plans = [
+ {
+ name = "dynamodb-plan"
+ resources = [aws_dynamodb_table.example.arn]
+ rules = [{
+ name = "dynamodb-rule"
+ schedule = "cron(0 1 * * ? *)"
+ start_window = 120
+ completion_window = 360
+ lifecycle = {
+ delete_after = 14
+ }
+ recovery_point_tags = {
+ "Environment" = "dev"
+ }
+ }]
+ },
+ {
+ name = "dynamodb-plan2"
+ resources = [aws_dynamodb_table.example.arn]
+ rules = [{
+ name = "dynamodb-rule2"
+ schedule = "cron(0 1 * * ? *)"
+ start_window = 120
+ completion_window = 360
+ }]
+ }
+ ]
+
+
+}
diff --git a/examples/single-account/providers.tf b/examples/single-account/providers.tf
new file mode 100644
index 0000000..d537219
--- /dev/null
+++ b/examples/single-account/providers.tf
@@ -0,0 +1,11 @@
+provider "aws" {
+ alias = "management"
+ region = "eu-central-1"
+ profile = "management"
+}
+
+provider "aws" {
+ alias = "source"
+ region = "eu-central-1"
+ profile = "dev"
+}
diff --git a/examples/single-account/versions.tf b/examples/single-account/versions.tf
new file mode 100644
index 0000000..3b89985
--- /dev/null
+++ b/examples/single-account/versions.tf
@@ -0,0 +1,11 @@
+terraform {
+ required_version = ">= 1.0"
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 4.19.0"
+
+ }
+ }
+}
diff --git a/iam.tf b/iam.tf
new file mode 100644
index 0000000..577841d
--- /dev/null
+++ b/iam.tf
@@ -0,0 +1,106 @@
+locals {
+ aws_backup_managed_policy_arns = [
+ "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup",
+ "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForRestores"
+ ]
+ role_description = "Provides AWS Backup permission to create backups and perform restores on your behalf across AWS services"
+}
+
+module "source_role" {
+ source = "cloudposse/iam-role/aws"
+ version = "0.17.0"
+
+ enabled = var.enabled
+ providers = {
+ aws = aws.source
+ }
+
+ context = module.source_label.context
+
+ role_description = local.role_description
+
+ assume_role_actions = [
+ "sts:AssumeRole"
+ ]
+
+ principals = {
+ Service = ["backup.amazonaws.com"]
+ }
+
+ managed_policy_arns = local.aws_backup_managed_policy_arns
+
+ policy_document_count = 0
+}
+
+
+module "target_role" {
+ source = "cloudposse/iam-role/aws"
+ version = "0.17.0"
+
+ enabled = var.enabled && var.is_cross_account_backup_enabled
+ providers = {
+ aws = aws.target
+ }
+
+ context = module.target_label.context
+
+ role_description = local.role_description
+
+ assume_role_actions = [
+ "sts:AssumeRole"
+ ]
+
+ principals = {
+ Service = ["backup.amazonaws.com"]
+ }
+
+ policy_document_count = 0
+
+ managed_policy_arns = local.aws_backup_managed_policy_arns
+}
+
+
+# aws backup
+
+data "aws_iam_policy_document" "source_vault" {
+ provider = aws.source
+
+ statement {
+ sid = "Enable backup"
+ effect = "Allow"
+
+ actions = ["backup:CopyIntoBackupVault"]
+
+ #checkov:skip=CKV_AWS_109
+ resources = ["*"]
+
+ principals {
+ identifiers = [
+ "arn:aws:iam::${data.aws_caller_identity.target.account_id}:root",
+ ]
+ type = "AWS"
+ }
+ }
+}
+
+data "aws_iam_policy_document" "target_vault" {
+ count = var.is_cross_account_backup_enabled ? 1 : 0
+ provider = aws.target
+
+ statement {
+ sid = "Enable backup"
+ effect = "Allow"
+
+ actions = ["backup:CopyIntoBackupVault"]
+
+ #checkov:skip=CKV_AWS_109
+ resources = ["*"]
+
+ principals {
+ identifiers = [
+ "arn:aws:iam::${data.aws_caller_identity.source.account_id}:root",
+ ]
+ type = "AWS"
+ }
+ }
+}
diff --git a/kms.tf b/kms.tf
new file mode 100644
index 0000000..1513187
--- /dev/null
+++ b/kms.tf
@@ -0,0 +1,125 @@
+module "source_kms_key" {
+ source = "cloudposse/kms-key/aws"
+ version = "0.12.1"
+
+ enabled = var.enabled
+ providers = {
+ aws = aws.source
+ }
+
+ context = module.source_label.context
+ deletion_window_in_days = 7
+ enable_key_rotation = true
+ alias = "alias/${module.source_label.id}"
+ policy = data.aws_iam_policy_document.kms_source_policy.json
+}
+
+module "target_kms_key" {
+ source = "cloudposse/kms-key/aws"
+ version = "0.12.1"
+
+ enabled = var.enabled && var.is_cross_account_backup_enabled
+ providers = {
+ aws = aws.target
+ }
+
+ context = module.target_label.context
+ deletion_window_in_days = 7
+ enable_key_rotation = true
+ alias = "alias/${module.target_label.id}"
+ policy = data.aws_iam_policy_document.kms_target_policy.json
+}
+
+data "aws_caller_identity" "source" {
+ provider = aws.source
+}
+
+data "aws_caller_identity" "target" {
+ provider = aws.target
+}
+
+data "aws_iam_policy_document" "kms_source_policy" {
+ provider = aws.source
+ statement {
+ sid = "Enable IAM User Permissions"
+ effect = "Allow"
+
+ #checkov:skip=CKV_AWS_111
+ actions = ["kms:*"]
+
+ #checkov:skip=CKV_AWS_109
+ resources = ["*"]
+
+ principals {
+ identifiers = [
+ "arn:aws:iam::${data.aws_caller_identity.source.account_id}:root",
+ ]
+ type = "AWS"
+ }
+ }
+ statement {
+ sid = "Allow use of the key"
+ effect = "Allow"
+
+ #checkov:skip=CKV_AWS_111
+ actions = ["kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey"
+ ]
+
+ #checkov:skip=CKV_AWS_109
+ resources = ["*"]
+
+ principals {
+ identifiers = [
+ "arn:aws:iam::${data.aws_caller_identity.target.account_id}:root",
+ ]
+ type = "AWS"
+ }
+ }
+}
+
+data "aws_iam_policy_document" "kms_target_policy" {
+ provider = aws.target
+ statement {
+ sid = "Enable IAM User Permissions"
+ effect = "Allow"
+
+ #checkov:skip=CKV_AWS_111
+ actions = ["kms:*"]
+
+ #checkov:skip=CKV_AWS_109
+ resources = ["*"]
+
+ principals {
+ identifiers = [
+ "arn:aws:iam::${data.aws_caller_identity.target.account_id}:root"
+ ]
+ type = "AWS"
+ }
+ }
+
+ statement {
+ sid = "Allow use of the key"
+ effect = "Allow"
+
+ actions = ["kms:Encrypt",
+ "kms:Decrypt",
+ "kms:ReEncrypt*",
+ "kms:GenerateDataKey*",
+ "kms:DescribeKey"
+ ]
+
+ #checkov:skip=CKV_AWS_109
+ resources = ["*"]
+
+ principals {
+ identifiers = [
+ "arn:aws:iam::${data.aws_caller_identity.source.account_id}:root",
+ ]
+ type = "AWS"
+ }
+ }
+}
diff --git a/label.tf b/label.tf
new file mode 100644
index 0000000..15a352a
--- /dev/null
+++ b/label.tf
@@ -0,0 +1,23 @@
+module "source_label" {
+ source = "cloudposse/label/null"
+ version = "0.25.0"
+ name = var.name
+ namespace = var.namespace
+ stage = var.stage
+ attributes = concat(["source"], var.attributes)
+ environment = var.environment
+ tags = var.tags
+ context = var.context
+}
+
+module "target_label" {
+ source = "cloudposse/label/null"
+ version = "0.25.0"
+ name = var.name
+ namespace = var.namespace
+ stage = var.stage
+ attributes = concat(["target"], var.attributes)
+ environment = var.environment
+ tags = var.tags
+ context = var.context
+}
diff --git a/main.tf b/main.tf
deleted file mode 100644
index e69de29..0000000
diff --git a/outputs.tf b/outputs.tf
index e69de29..25fd1dd 100644
--- a/outputs.tf
+++ b/outputs.tf
@@ -0,0 +1,19 @@
+output "source_backup_vault_id" {
+ value = try(aws_backup_vault.source[0].id, "")
+ description = "Backup Vault ID of source backup vault"
+}
+
+output "source_backup_vault_arn" {
+ value = try(aws_backup_vault.source[0].arn, "")
+ description = "Backup Vault ARN of source backup vault"
+}
+
+output "target_backup_vault_id" {
+ value = try(aws_backup_vault.target[0].id, "")
+ description = "Backup Vault ID of target backup vault"
+}
+
+output "target_backup_vault_arn" {
+ value = try(aws_backup_vault.target[0].arn, "")
+ description = "Backup Vault ARN of target backup vault"
+}
diff --git a/variables.tf b/variables.tf
index e69de29..25fbb56 100644
--- a/variables.tf
+++ b/variables.tf
@@ -0,0 +1,45 @@
+variable "enabled" {
+ type = bool
+ default = true
+ description = "Variable indicating whether deployment is enabled"
+}
+
+variable "is_cross_account_backup_enabled" {
+ type = bool
+ default = false
+ description = "Create backup vault on different account and turn on copy action to this vault (provider.target needs to be set)"
+}
+
+variable "backup_plans" {
+ default = []
+ description = "Backup plans config along with rule and resources setup"
+ type = list(object({
+ name = string
+ resources = optional(list(string), [])
+ selection_tags = optional(list(object({
+ type = string
+ key = string
+ value = string
+ })), [])
+ rules = list(object({
+ name = string
+ schedule = string
+ enable_continuous_backup = optional(bool)
+ start_window = optional(string, 60)
+ completion_window = optional(number, 180)
+ lifecycle = optional(object({
+ cold_storage_after = optional(number)
+ delete_after = optional(number)
+ }))
+ copy_action_lifecycle = optional(object({
+ cold_storage_after = optional(number)
+ delete_after = optional(number)
+ }))
+ recovery_point_tags = optional(map(string))
+ }))
+ advanced_backup_setting = optional(object({
+ WindowsVSS = optional(string, null)
+ resource_type = optional(string, null)
+ }), null)
+ }))
+}
diff --git a/versions.tf b/versions.tf
index 90a7444..c395a0a 100644
--- a/versions.tf
+++ b/versions.tf
@@ -1,10 +1,11 @@
terraform {
- required_version = ">= 1.0"
+ required_version = ">= 1.3"
required_providers {
aws = {
- source = "hashicorp/aws"
- version = ">= 4.19.0"
+ source = "hashicorp/aws"
+ version = ">= 4.19.0"
+ configuration_aliases = [aws.source, aws.target]
}
}
}