# storage/backup-hub

Provisions the centralized AWS Backup vault(s) for a hub/spoke backup
architecture. Deployed once, in the designated hub/backup account.
Spoke accounts never create resources via this module. They only
reference the vault ARNs this module outputs, as `copy_action`
destinations in their own local backup plans (see `storage/backup-spoke`).

## Usage

```hcl
module "backup_hub" {
  source = "hcassc.jfrog.io/iac-tf-modules-virtual__storage/backup-hub/aws"
  version = "0.1.0"

  name_prefix = "hca-prod-shared"

  vaults = {
    standard = {
      name_suffix = "backup-vault"
      trusted_spoke_role_arns = local.vault_trusted_arns["standard"]
    }
  }

  tags = local.tags
}
```

## Prerequisites, read before deploying

This module does not create or verify the IAM role AWS Backup uses in
each spoke account. `trusted_spoke_role_arns` on every vault is a list of
ARN strings, and the module trusts them blindly. AWS IAM permits a
resource policy to reference a principal ARN for a role that does not
exist, so the policy applies successfully either way. The failure only
surfaces later, silently, as a failed cross-account copy job in the
spoke account, not as a Terraform error here.

Before a vault's trust list means anything, the referenced role
(`hca-backup-service-role`, or whatever your platform standardizes on)
must already exist in that spoke account. This is currently a deliberate,
manual, per-account bootstrap step, created via IAM console/CLI, not
Terraform. It falls in the same category as the Bitbucket runner OIDC
bootstrap. See `iac-governance` for the exact trust policy and managed
policy attachments required.

## How trust lists should be built

`trusted_spoke_role_arns` is a flat `list(string)` per vault, deliberately
unopinionated. The module has no idea how you arrived at that list. Do
not hand-type ARNs directly in the module call. Build them in the
calling stack instead, from a small, reviewable, spoke-centric table,
then invert it:

```hcl
# in the calling stack, not this module

locals {
  backup_spokes = {
    shared_services = {
      account_id     = "635649352288"
      trusted_role   = "hca-backup-service-role"
      trusted_vaults = ["standard"]
    }
  }

  vault_trusted_arns = {
    for vault_key in distinct(flatten([
      for spoke in values(local.backup_spokes) : spoke.trusted_vaults
    ])) :
    vault_key => [
      for spoke_key, spoke in local.backup_spokes :
      "arn:aws:iam::${spoke.account_id}:role/${spoke.trusted_role}"
      if contains(spoke.trusted_vaults, vault_key)
    ]
  }
}
```

Keeping this in the stack, not the module, means adding a new spoke, or
granting an existing spoke access to a new vault, is a one-line edit to
`backup_spokes`. It stays reviewable in one place, with zero module
changes required.

## Naming

Vault and SNS topic names are fully derived by the module, not
caller-supplied as a complete string:

​`<name_prefix>-<region_abbr>-<name_suffix>`

`region_abbr` is derived internally from the deploying region (for
example `ap-south-1` becomes `aps1`) via `data.aws_availability_zones`.
`name_prefix` and `name_suffix` are the only naming inputs a caller
provides. The `Name` tag mirrors the resource name exactly. Do not
expect them to diverge.

## Design notes and known limitations

- **Single-region only.** Vaults are created in whichever region the
  provider passed to this module resolves to. Cross-region DR (a second
  hub vault in another region, with hub-to-hub replication) is out of
  scope for this module. This is deliberately deferred pending a
  confirmed RTO/RPO requirement. If added later, expect either a
  dual-provider version of this module or a second `backup-hub`
  invocation per region, not a toggle on the existing one.
- **`prevent_destroy` and `force_destroy = false` are hardcoded and
  non-configurable on every vault this module creates.** There is no
  variable to disable this. A hub vault holds the org's centralized
  compliance copies, and there is no legitimate everyday reason it
  should be destroyable. Decommissioning is a deliberate, reviewed
  action: temporarily remove the `lifecycle` block in its own PR.
- **Multiple hub vaults are supported and independently trusted.** Each
  entry in `vaults` gets its own access policy, built only from that
  vault's own `trusted_spoke_role_arns`. This is intentional. A
  stricter compliance-tier vault can trust a smaller set of spokes than
  a standard-tier vault, in the same hub account.
- **Vault lock (WORM) is opt-in per vault** (`lock_enabled`), not a
  module default. AWS enforces `min_retention_days >= 1` and
  `changeable_for_days >= 3`. This module validates both, plus
  `max_retention_days >= min_retention_days` when both are set, at plan
  time rather than surfacing as an apply-time AWS API error.
- **Notifications are opt-in per vault.** Either supply an existing
  `sns_topic_arn`, or set `create_sns_topic = true` to have the module
  provision one. `backup_vault_events` is validated at plan time against
  AWS Backup's actual event enum, so a typo'd event name fails fast
  rather than at apply.
- **No cross-region validation on a supplied `sns_topic_arn`.** If you
  reuse an existing topic, it must be in the same region as the vault.
  The module does not check this; AWS will reject it at apply if
  mismatched.
- **No format validation on `kms_key_arn`.** Both the vault's and the
  notification topic's KMS key ARNs are passed through as plain strings.
  A malformed ARN fails at apply with AWS's own error, not a Terraform
  validation.

## Related modules

- `storage/backup-spoke`: deployed once per spoke account, creates the
  local vault, plan, and selection for that account's actual resources,
  with an optional `copy_action` targeting this module's vault ARN
  output.

<!-- BEGIN_TF_DOCS — DO NOT EDIT BELOW THIS LINE -->

## Requirements

| Name                                                                     | Version            |
| ------------------------------------------------------------------------ | ------------------ |
| <a name="requirement_terraform"></a> [terraform](#requirement_terraform) | >= 1.15.0, < 2.0.0 |
| <a name="requirement_aws"></a> [aws](#requirement_aws)                   | >= 6.0, < 7.0      |

## Providers

| Name                                             | Version       |
| ------------------------------------------------ | ------------- |
| <a name="provider_aws"></a> [aws](#provider_aws) | >= 6.0, < 7.0 |

## Resources

| Name                                                                                                                                                    | Type     |
| ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| [aws_backup_vault.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault)                                       | resource |
| [aws_backup_vault_lock_configuration.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault_lock_configuration) | resource |
| [aws_backup_vault_notifications.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault_notifications)           | resource |
| [aws_backup_vault_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/backup_vault_policy)                         | resource |
| [aws_sns_topic.backup_notifications](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic)                             | resource |

## Inputs

| Name                                                               | Description                                                                                                                                                                                                                                                                                                                           | Type                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | Default | Required |
| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | :------: |
| <a name="input_name_prefix"></a> [name_prefix](#input_name_prefix) | Prefix prepended to resource names created by this module                                                                                                                                                                                                                                                                             | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | n/a     |   yes    |
| <a name="input_tags"></a> [tags](#input_tags)                      | Tags applied to all resources in this module.                                                                                                                                                                                                                                                                                         | `map(string)`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `{}`    |    no    |
| <a name="input_vaults"></a> [vaults](#input_vaults)                | Map of centralized backup vaults to create in the hub account.<br/>Each vault carries its own list of trusted spoke role ARNs (principals<br/>allowed to backup:CopyIntoBackupVault) so that different vaults can<br/>enforce different trust boundaries — e.g. a compliance-tier vault<br/>restricted to a subset of spoke accounts. | <pre>map(object({<br/> name_suffix = string<br/> kms_key_arn = optional(string, null)<br/><br/> # Principals (spoke account backup service roles) allowed to copy<br/> # recovery points into this vault. Required — a vault with an empty<br/> # list accepts no cross-account copies.<br/> trusted_spoke_role_arns = list(string)<br/><br/> # Vault lock (WORM compliance mode)<br/> lock_enabled = optional(bool, false)<br/> lock_changeable_for_days = optional(number, 3)<br/> lock_min_retention_days = optional(number, null)<br/> lock_max_retention_days = optional(number, null)<br/><br/> # Notifications — fully opt-in, no default events fire unless listed.<br/> # Set sns_topic_arn to reuse an existing topic; leave null and set<br/> # create_sns_topic = true to have the module provision one.<br/> notifications = optional(object({<br/> sns_topic_arn = optional(string, null)<br/> create_sns_topic = optional(bool, false)<br/> kms_key_arn = optional(string, null)<br/> backup_vault_events = list(string)<br/> }), null)<br/> }))</pre> | `{}`    |    no    |

## Outputs

| Name                                                                                                     | Description                                                                                                                                                                                                                                    |
| -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <a name="output_notification_topic_arns"></a> [notification_topic_arns](#output_notification_topic_arns) | Map of hub vault logical key to SNS topic ARN, for vaults where the module created the topic (notifications.create_sns_topic = true). Vaults using an existing topic (sns_topic_arn provided) or with no notifications configured are omitted. |
| <a name="output_vault_arns"></a> [vault_arns](#output_vault_arns)                                        | Map of hub vault logical key to ARN. Feed these into spoke backup-spoke module copy_actions.                                                                                                                                                   |
| <a name="output_vault_ids"></a> [vault_ids](#output_vault_ids)                                           | Map of hub vault logical key to vault name.                                                                                                                                                                                                    |
| <a name="output_vault_recovery_points"></a> [vault_recovery_points](#output_vault_recovery_points)       | Map of hub vault logical key to current recovery point count.                                                                                                                                                                                  |

<!-- END_TF_DOCS — DO NOT EDIT ABOVE THIS LINE -->

```

```
