1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
# Variables ===================================================================
variable "name_prefix" {
description = "Prefix prepended to resource names created by this module"
type = string
validation {
condition = length(trimspace(var.name_prefix)) > 0
error_message = "name_prefix must not be empty."
}
}
variable "tags" {
description = "Tags applied to all resources in this module."
type = map(string)
default = {}
}
variable "vaults" {
description = <<-EOT
Map of centralized backup vaults to create in the hub account.
Each vault carries its own list of trusted spoke role ARNs (principals
allowed to backup:CopyIntoBackupVault) so that different vaults can
enforce different trust boundaries — e.g. a compliance-tier vault
restricted to a subset of spoke accounts.
EOT
type = map(object({
name_suffix = string
kms_key_arn = optional(string, null)
# Principals (spoke account backup service roles) allowed to copy
# recovery points into this vault. Required — a vault with an empty
# list accepts no cross-account copies.
trusted_spoke_role_arns = list(string)
# Vault lock (WORM compliance mode)
lock_enabled = optional(bool, false)
lock_changeable_for_days = optional(number, 3)
lock_min_retention_days = optional(number, null)
lock_max_retention_days = optional(number, null)
# Notifications — fully opt-in, no default events fire unless listed.
# Set sns_topic_arn to reuse an existing topic; leave null and set
# create_sns_topic = true to have the module provision one.
notifications = optional(object({
sns_topic_arn = optional(string, null)
create_sns_topic = optional(bool, false)
kms_key_arn = optional(string, null)
backup_vault_events = list(string)
}), null)
}))
default = {}
validation {
condition = alltrue([
for k, v in var.vaults : can(regex("^[a-zA-Z0-9_-]+$", v.name_suffix))
])
error_message = <<-EOT
Vault name_suffix values must contain only alphanumerics, hyphens,
and underscores (no dots, spaces, or other characters).
EOT
}
validation {
condition = alltrue([
for k, v in var.vaults :
v.lock_enabled ? (v.lock_min_retention_days != null) : true
])
error_message = <<-EOT
lock_min_retention_days is required when lock_enabled is true.
Set a retention floor, or leave lock_enabled = false for this vault.
EOT
}
validation {
condition = alltrue([
for k, v in var.vaults :
v.lock_enabled && v.lock_min_retention_days != null ?
v.lock_min_retention_days >= 1 :
true
])
error_message = "lock_min_retention_days must be at least 1 day."
}
validation {
condition = alltrue([
for k, v in var.vaults :
v.lock_enabled && v.lock_max_retention_days != null ?
v.lock_max_retention_days >= 1 :
true
])
error_message = "lock_max_retention_days must be at least 1 day."
}
validation {
condition = alltrue([
for k, v in var.vaults :
(v.lock_enabled && v.lock_min_retention_days != null && v.lock_max_retention_days != null) ?
v.lock_max_retention_days >= v.lock_min_retention_days :
true
])
error_message = <<-EOT
lock_max_retention_days must be greater than or equal to
lock_min_retention_days when both are set.
EOT
}
validation {
condition = alltrue([
for k, v in var.vaults :
v.lock_enabled ? v.lock_changeable_for_days >= 3 : true
])
error_message = <<-EOT
lock_changeable_for_days must be at least 3 — AWS Backup enforces a
minimum 3-day cooling-off period before compliance mode locks in
permanently.
EOT
}
validation {
condition = alltrue([
for k, v in var.vaults : length(v.trusted_spoke_role_arns) > 0
])
error_message = <<-EOT
Every hub vault must define at least one trusted_spoke_role_arns entry.
A vault with an empty list can never receive cross-account copies.
EOT
}
validation {
condition = alltrue([
for k, v in var.vaults :
v.notifications == null ? true : (
v.notifications.sns_topic_arn != null || v.notifications.create_sns_topic
)
])
error_message = <<-EOT
When notifications is set, you must either:
- provide an existing sns_topic_arn, or
- set create_sns_topic = true to have the module provision one.
EOT
}
validation {
condition = alltrue([
for k, v in var.vaults :
v.notifications == null ? true : length(v.notifications.backup_vault_events) > 0
])
error_message = <<-EOT
notifications.backup_vault_events must list at least one event
when notifications is configured for a vault.
EOT
}
validation {
condition = alltrue(flatten([
for k, v in var.vaults : [
for event in(v.notifications == null ? [] : v.notifications.backup_vault_events) :
contains(local.valid_vault_events_for_notifications, event)
]
]))
error_message = <<-EOT
notifications.backup_vault_events contains an event not recognized
by AWS Backup.
Valid Events:
${join("\n ", [for chunk in chunklist(local.valid_vault_events_for_notifications, 3) : join(", ", chunk)])}
EOT
}
}
|