NTC Backup
Description​
NTC Backup provisions a dedicated, central backup account that orchestrates AWS Backup across your AWS Organization through AWS Organizations BACKUP_POLICY documents, instead of configuring AWS Backup separately in every member account.
Member accounts back up resources locally first for fast operational recovery, then optionally copy the same recovery point into this account's central vault for durability and isolation from the source account. Backup Vault Lock can additionally give a central vault WORM-style immutability once its grace period expires.
Running backup centrally from its own account keeps recovery points out of reach of a compromised or accidentally deleted workload account: the central vault policy grants member accounts exactly one permission, backup:CopyIntoBackupVault, so even a fully compromised workload account has no path to read, restore, or delete a recovery point once it has landed here. This separation is also what frameworks like ISO 27001 and FINMA expect from backup and recovery: demonstrably independent of the systems it protects.
NTC Backup doesn't work standalone - see Architecture Review below for how it depends on NTC Account Factory and NTC Organizations, and the Guide to Configure NTC Backup for the full setup, step by step.
See Backup Policies for how policy attachment, tag-based resource selection, and cross-account copy behave once configured, Member Account Requirements for what individual resources additionally need, Restore for how to bring a recovery point back, Malware Protection for scanning recovery points with Amazon GuardDuty, and Troubleshooting for known issues.
Architecture Review​
NTC Backup spans three separate Terraform stacks, each owned by a different account, each doing a distinct job. Understanding how they relate is the fastest way to understand the whole system - and the most common source of confusion when something doesn't work.
Management Account: Grants the Right to Govern​
The Organizations management account doesn't touch backups directly. It delegates AWS Backup administration to the backup account - registering it as delegated admin, granting it rights to manage BACKUP_POLICY documents, and turning on org-wide cross-account backup. This is largely a one-time setup step; it doesn't need to change as backup_definitions evolves.
Backup Account: Authors the Policy​
The backup account is the delegated administrator granted above. For each backup_definitions entry it owns a central vault and KMS key, holding recovery points copied in from member accounts, isolated from the accounts that produced them. It also authors the actual BACKUP_POLICY document - what gets backed up, on what schedule, with what retention - and attaches it to target OUs/accounts via the Organizations API.
Member Accounts: Run the Backups​
The backup baseline template, rolled out via NTC Account Factory, provisions a local vault and the IAM roles AWS Backup and GuardDuty need in every account you want backed up. This is where backup jobs actually run: each job backs up a resource into the local vault first, then optionally copies that same recovery point into the backup account's central vault. This is also the only place actual backup plans exist as real AWS resources.
How the Three Fit Together​
The management account grants the right to govern. The backup account decides what gets backed up and where centrally. AWS Backup then carries out that decision in each member account, using whatever the baseline template already set up there.
If the baseline template is missing, or its vault/role names don't match what the backup account expects, jobs fail with AccessDenied - even though the other two pieces are correct. This is why the Guide to Configure NTC Backup sets the three stacks up in that exact order.

Usage​
| Latest Release | 1.0.1 |
|---|
Requirements​
The following requirements are needed by this module:
-
terraform (>= 1.5.7)
-
aws (>= 6.0)
Providers​
The following providers are used by this module:
- aws (>= 6.0)
Modules​
The following Modules are called:
lambda_notification_poller​
Source: ./modules/lambda
Version:
lambda_pretty_notifications​
Source: ./modules/lambda
Version:
Resources​
The following resources are used by this module:
- aws_backup_vault.ntc_central_backup_vault (resource)
- aws_backup_vault_lock_configuration.ntc_central_backup_vault_lock (resource)
- aws_backup_vault_policy.ntc_central_backup_vault (resource)
- aws_cloudwatch_event_rule.ntc_backup_notification (resource)
- aws_cloudwatch_event_rule.ntc_backup_notification_poller_schedule (resource)
- aws_cloudwatch_event_target.ntc_backup_notification (resource)
- aws_cloudwatch_event_target.ntc_backup_notification_poller_schedule (resource)
- aws_iam_role.ntc_backup_notification_lambda (resource)
- aws_iam_role.ntc_backup_notification_poller_lambda (resource)
- aws_iam_role.ntc_central_backup_malware_scanner (resource)
- aws_iam_role.ntc_central_backup_operator (resource)
- aws_iam_role_policy.ntc_backup_notification_lambda (resource)
- aws_iam_role_policy.ntc_backup_notification_poller_lambda (resource)
- aws_iam_role_policy_attachments_exclusive.ntc_central_backup_malware_scanner (resource)
- aws_iam_role_policy_attachments_exclusive.ntc_central_backup_operator (resource)
- aws_iam_service_linked_role.ntc_backup_service_linked_role (resource)
- aws_iam_service_linked_role.ntc_rds_service_linked_role (resource)
- aws_kms_alias.ntc_backup_notification (resource)
- aws_kms_alias.ntc_central_backup_encryption (resource)
- aws_kms_key.ntc_backup_notification (resource)
- aws_kms_key.ntc_central_backup_encryption (resource)
- aws_kms_key_policy.ntc_backup_notification (resource)
- aws_organizations_policy.ntc_central_backup_policy (resource)
- aws_organizations_policy_attachment.ntc_central_backup_policy (resource)
- aws_sns_topic.ntc_backup_notification (resource)
- aws_sns_topic_policy.ntc_backup_notification (resource)
- aws_sns_topic_subscription.ntc_backup_notification (resource)
- aws_caller_identity.current (data source)
- aws_iam_policy_document.ntc_backup_notification_kms (data source)
- aws_iam_policy_document.ntc_backup_notification_lambda (data source)
- aws_iam_policy_document.ntc_backup_notification_lambda_trust (data source)
- aws_iam_policy_document.ntc_backup_notification_poller_lambda (data source)
- aws_iam_policy_document.ntc_backup_notification_poller_lambda_trust (data source)
- aws_iam_policy_document.ntc_backup_notification_sns (data source)
- aws_iam_policy_document.ntc_central_backup_encryption_policy (data source)
- aws_iam_policy_document.ntc_central_backup_vault_policy (data source)
- aws_organizations_organization.current (data source)
- aws_partition.current (data source)
Required Inputs​
No required inputs.
Optional Inputs​
The following input variables are optional (have default values):
backup_definitions​
Description: One entry per central backup vault (+ KMS key, tag-driven backup plan pair, and its own AWS
Organizations BACKUP_POLICY document). Entries are keyed by name, which is used to generate all resource names and must be unique.
Each entry's backup plan is attached to the specified OUs and/or account IDs
- name: unique identifier - drives every resource name this entry creates (vault, KMS alias,
plan/selection names). Renaming recreates them all. - region: region for this entry's central vault + KMS key.
- resource_types: AWS services eligible for backup. Valid values are:
EC2, EBS, RDS, Aurora, Aurora DSQL, Neptune, DocumentDB, DynamoDB, EFS, S3, FSx,
CloudFormation, Redshift, Redshift Serverless, SAP HANA on Amazon EC2,
Storage Gateway, Timestream, EKS - copy_to_backup_definition_by_name: other entries' names to ALSO copy this entry's
local-and-central backups to, beyond its own vault. - local_backup_vault_retention_days / central_backup_vault_retention_days: retention (days) in
the local (member account) vault / this entry's central vault. - cron_schedule: backup schedule for both the local-only and local-and-central plans - cron(...)
only, hourly-or-coarser (AWS Backup rejects finer). Defaults to every 4 hours. - backup_start_tolerance_minutes: minutes AWS Backup waits for a scheduled job to start before
marking it missed. - central_vault_lock_config: Backup Vault Lock for the CENTRAL vault only (the local vault is
never locked by this module).- enabled: turns the lock on.
- min_retention_days / max_retention_days: the range every rule's retention must fall within
once locked - central_backup_vault_retention_days must fall inside it too, or AWS rejects
the plan. - changeable_for_days: grace period before the lock becomes PERMANENT and these bounds can
never be adjusted again.
- member_account_backup_role_name: IAM role name in member accounts that AWS Backup assumes
to back up resources. - backup_target_ou_path_ids: OU path IDs (no trailing "/*") to attach this entry's policy to and
trust for copy-in. - backup_target_account_ids: account IDs to attach + trust directly, independent of OU
membership. At least one of this or backup_target_ou_path_ids is required. - tag_key_to_enable_backup / tag_key_to_define_backup_scope: tag keys driving whether a resource
is backed up at all, and whether it's also copied centrally. - backup_enabled_if_untagged: whether an untagged resource is backed up by default.
- default_backup_scope: default scope ("local-only" or "local-and-central") for untagged
resources while tag_based_selection_enabled is true; the STATIC scope for every resource once
it's false. - tag_based_selection_enabled: whether tag-based per-resource selection is active. false turns
default_backup_scope into a static, entry-wide switch instead of a default. - member_account_malware_scanner_role_name: IAM role name in member accounts that AWS Backup
passes to GuardDuty when malware_scanning.enabled is true - must match the local scanner role
name from the account baseline template's "backup" template. - malware_scanning: GuardDuty Malware Protection for this entry's recovery points, off by default.
This only controls AUTOMATIC scanning on every scheduled backup job (scan_actions/scan_settings
in the generated backup policy) - the scanner IAM roles are always created regardless (see
malware_scan_scanner_iam_role_name / member_account_malware_scanner_role_name), so an on-demand
scan against any existing recovery point still works via the console/API even while this is false.- enabled: turns AUTOMATIC scanning on for this entry.
- resource_types: which of this entry's resource_types to scan - only "EC2", "EBS", and "S3"
support scanning today (or "ALL" to mean whichever of those three this entry actually backs
up); anything else here has no effect since AWS Backup has nothing to scan. - scan_mode: "INCREMENTAL_SCAN" (only changed files since the last scan) or "FULL_SCAN".
Type:
list(object({
name = string
region = string
resource_types = optional(list(string), ["EC2", "EBS", "RDS", "Aurora", "Neptune", "DocumentDB", "DynamoDB", "EFS", "S3"])
copy_to_backup_definition_by_name = optional(list(string), [])
local_backup_vault_retention_days = optional(number, 7)
central_backup_vault_retention_days = optional(number, 30)
cron_schedule = optional(string, "cron(30 0/4 ? * * *)")
backup_start_tolerance_minutes = optional(number, 60)
central_vault_lock_config = optional(object({
enabled = optional(bool, false)
min_retention_days = optional(number, 10)
max_retention_days = optional(number, 90)
changeable_for_days = optional(number, 30) # grace period before the lock becomes PERMANENT
}), {})
member_account_backup_role_name = optional(string, "ntc-local-backup-operator-role")
member_account_malware_scanner_role_name = optional(string, "ntc-local-backup-malware-scanner-role")
backup_target_ou_path_ids = optional(list(string), []) # full OU path IDs, no trailing "/*"
backup_target_account_ids = optional(list(string), []) # attach + trust directly, independent of OU membership
tag_key_to_enable_backup = optional(string, "ntc:backup")
tag_key_to_define_backup_scope = optional(string, "ntc:backup-scope")
backup_enabled_if_untagged = optional(bool, false)
default_backup_scope = optional(string, "local-and-central")
tag_based_selection_enabled = optional(bool, true)
malware_scanning = optional(object({
enabled = optional(bool, false)
resource_types = optional(list(string), ["EC2", "EBS", "S3"])
scan_mode = optional(string, "INCREMENTAL_SCAN")
}), {})
}))
Default: []
backup_notification_settings​
Description: SNS topic settings to send AWS Backup job failure notifications.
Type:
object({
enable_notifications = optional(bool, true)
org_identifier = optional(string, "")
sns_topic_name = optional(string, "ntc-backup-notification-topic")
sns_topic_region = optional(string, null) # defaults to the first backup_definitions entry's region
event_rule_prefix = optional(string, "ntc-backup-notification")
notification_lambda_name = optional(string, "ntc-backup-notification-lambda")
notify_on_states = optional(list(string), ["FAILED", "ABORTED", "EXPIRED"])
poller_lambda_name = optional(string, "ntc-backup-notification-poller-lambda")
poller_schedule_expression = optional(string, "cron(3 0 * * ? *)") # once daily at 00:03 UTC
subscriptions = optional(list(object({
protocol = optional(string, "email")
endpoints = optional(list(string), [])
subscription_role_arn = optional(string, null)
})), [])
})
Default: {}
central_backup_operator_iam_role_name​
Description: Name of the IAM role in THIS (central backup) account that AWS Backup assumes to manage backups
within the central vaults and receive copies from member accounts. Shared across every
backup_definitions entry.
Type: string
Default: "ntc-central-backup-operator-role"
create_backup_service_linked_role​
Description: Whether to create AWSServiceRoleForBackup, the role AWS Backup itself uses as caller identity for
native-service copy/read and KMS grants in this account - the central vaults' KMS key policies
trust it explicitly. Set to false if it already exists (EntityAlreadyExists otherwise) - import it
instead: tofu import aws_iam_service_linked_role.ntc_backup_service_linked_role arn:aws:iam::<ACCOUNT_ID>:role/aws-service-role/backup.amazonaws.com/AWSServiceRoleForBackup.
Type: bool
Default: true
create_rds_service_linked_role​
Description: Whether to create AWSServiceRoleForRDS, required before AWS Backup can copy an RDS-family
recovery point into a vault here. Set to false if it already exists (EntityAlreadyExists
otherwise) - import it instead: tofu import aws_iam_service_linked_role.ntc_rds_service_linked_role arn:aws:iam::<ACCOUNT_ID>:role/aws-service-role/rds.amazonaws.com/AWSServiceRoleForRDS.
Type: bool
Default: true
malware_scan_scanner_iam_role_name​
Description: Name of the IAM role in THIS (central backup) account that AWS Backup passes to GuardDuty when
initiating a malware scan. Trusted exclusively by malware-protection.guardduty.amazonaws.com - no
other principal can assume it.
Type: string
Default: "ntc-central-backup-malware-scanner-role"
Outputs​
The following outputs are exported:
backup_notifications_sns_topic_arn​
Description: ARN of the SNS topic which notifies about AWS Backup job, copy, and restore failures.
backup_vault_arns​
Description: ARNs of the central backup vaults, keyed by backup_definitions entry name.
backup_vault_kms_key_arns​
Description: ARNs of the KMS keys used for backup encryption, keyed by backup_definitions entry name.
backup_vault_names​
Description: Names of the central backup vaults, keyed by backup_definitions entry name.
malware_scan_scanner_iam_role_arn​
Description: ARN of the scanner role GuardDuty uses to read recovery points during a malware scan. Pass this
explicitly when starting an on-demand scan (console or StartMalwareScan/backup start-scan-job
API) - it is not inferred automatically.