Skip to content

Commit 58a918a

Browse files
authored
Merge pull request #67
2 parents 51580de + 048ebc5 commit 58a918a

File tree

16 files changed

+393
-146
lines changed

16 files changed

+393
-146
lines changed

main.tf

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ module "deployment" {
4646
}
4747
central_account_resource_name_prefix = var.central_account_resource_name_prefix
4848
central_backup_service_linked_role_arn = local.backup_service_linked_role_arn
49-
central_backup_service_role_arn = module.backup_service_role.role.arn
5049
central_deployment_helper_role_arn = module.deployment_helper.lambda_role.arn
5150
central_deployment_helper_topic_name = module.deployment_helper.sns_topic.name
5251
deployment_regions = local.deployment_regions

modules/service-deployment-regional/backup-vaults.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ locals {
1212
Resource : "*",
1313
Condition : {
1414
ArnLike : {
15-
"aws:PrincipalArn" : "arn:${var.current_aws_partition}:iam::*:role/${var.member_account_backup_service_role_name}"
15+
"aws:PrincipalArn" : "arn:${var.current.partition}:iam::*:role/${var.deployment.member_account_backup_service_role_name}"
1616
},
1717
"ForAnyValue:StringLike" : {
1818
"aws:PrincipalOrgPaths" : var.deployment.ou_paths_including_children

modules/service-deployment-regional/eventbridge.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ resource "aws_cloudwatch_event_bus_policy" "event_bus" {
2222
Resource : "*",
2323
Condition : {
2424
ArnLike : {
25-
"aws:PrincipalArn" : "arn:${var.current_aws_partition}:iam::*:role/${var.member_account_eventbridge_rule_name}",
25+
"aws:PrincipalArn" : "arn:${var.current.partition}:iam::*:role/${var.deployment.member_account_eventbridge_rule_name}",
2626
},
2727
"ForAnyValue:StringLike" : {
2828
"aws:PrincipalOrgPaths" : var.deployment.ou_paths_including_children
@@ -93,7 +93,7 @@ resource "aws_cloudwatch_event_rule" "default_to_event_bus" {
9393
"detail-type" : ["Backup Job State Change", "Copy Job State Change"],
9494
"detail" : {
9595
"$or" : [
96-
{ "backupVaultName" : [var.member_account_backup_vault_name] },
96+
{ "backupVaultName" : [var.deployment.member_account_backup_vault_name] },
9797
{ "sourceBackupVaultArn" : local.central_backup_vault_arns },
9898
{ "destinationBackupVaultArn" : local.central_backup_vault_arns }
9999
]

modules/service-deployment-regional/kms.tf

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
resource "aws_kms_replica_key" "key" {
2-
count = var.region != var.current_aws_region ? 1 : 0
2+
count = var.region != var.current.region ? 1 : 0
33

44
region = var.region
55
primary_key_arn = var.kms.primary_key_arn
66
policy = var.kms.kms_key_policy
77
}
88

99
resource "aws_kms_alias" "key" {
10-
count = var.region != var.current_aws_region ? 1 : 0
10+
count = var.region != var.current.region ? 1 : 0
1111

1212
region = var.region
1313
name = var.kms.kms_key_alias

modules/service-deployment-regional/sfn_backup_ingest.tf

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ resource "aws_cloudwatch_event_rule" "backup_ingest" {
1515
"$or" : [
1616
{
1717
# Member -> Intermediate
18-
"sourceBackupVaultArn" : [{ "wildcard" : "arn:*:backup:*:*:backup-vault:${var.member_account_backup_vault_name}" }],
18+
"sourceBackupVaultArn" : [{ "wildcard" : "arn:*:backup:*:*:backup-vault:${var.deployment.member_account_backup_vault_name}" }],
1919
"destinationBackupVaultArn" : [aws_backup_vault.intermediate.arn]
2020
},
2121
{
@@ -25,7 +25,7 @@ resource "aws_cloudwatch_event_rule" "backup_ingest" {
2525
},
2626
{
2727
# Member -> LAG
28-
"sourceBackupVaultArn" : [{ "wildcard" : "arn:*:backup:*:*:backup-vault:${var.member_account_backup_vault_name}" }],
28+
"sourceBackupVaultArn" : [{ "wildcard" : "arn:*:backup:*:*:backup-vault:${var.deployment.member_account_backup_vault_name}" }],
2929
"destinationBackupVaultArn" : concat([1], values(aws_backup_logically_air_gapped_vault.lag)[*].arn)
3030
}
3131
]
@@ -70,16 +70,16 @@ resource "aws_sfn_state_machine" "backup_ingest" {
7070
"Type" : "Pass",
7171
"Output" : "", # Don't output anything to reduce CloudWatch Logs ingest
7272
"Assign" : {
73-
"accountId" : var.current_aws_account_id,
73+
"accountId" : var.current.account_id,
7474
"backupIngestSfnStateRoleArn" : var.stepfunctions.ingest_state_role_arn,
7575
"centralBackupServiceRoleArn" : var.deployment.backup_service_role_arn,
7676
"destinationBackupVaultArn" : "{% $states.input.detail.destinationBackupVaultArn %}",
7777
"destinationRecoveryPointArn" : "{% $states.input.detail.destinationRecoveryPointArn %}",
7878
"intermediateBackupVaultArn" : aws_backup_vault.intermediate.arn,
7979
"jobStatus" : "{% $states.input.detail.state %}",
8080
"lagBackupVaultNamePrefix" : var.backup_vaults.lag_vault_prefix,
81-
"memberAccountBackupServiceRoleName" : var.member_account_backup_service_role_name,
82-
"partitionId" : var.current_aws_partition,
81+
"memberAccountBackupServiceRoleName" : var.deployment.member_account_backup_service_role_name,
82+
"partitionId" : var.current.partition,
8383
"retentionTags" : {
8484
"member" : var.backup_policies.local_retention_days_tag,
8585
"intermediate" : var.backup_policies.intermediate_retention_days_tag,
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
#
2+
# Step Function to copy backups to member account restore vaults (...-default)
3+
#
4+
resource "aws_cloudwatch_log_group" "backup_restore" {
5+
region = var.region
6+
name = "/aws/vendedlogs/states/${var.stepfunctions.restore_state_machine_name}"
7+
retention_in_days = 90
8+
}
9+
10+
resource "aws_sfn_state_machine" "backup_restore" {
11+
name = var.stepfunctions.restore_state_machine_name
12+
role_arn = var.stepfunctions.restore_state_machine_role_arn
13+
14+
logging_configuration {
15+
level = "ALL"
16+
include_execution_data = true
17+
log_destination = "${aws_cloudwatch_log_group.backup_restore.arn}:*"
18+
}
19+
20+
/*
21+
Step Function input:
22+
{
23+
"destinationAccount": "222222222222",
24+
"recoveryPointArn": "arn:aws:backup:eu-west-2:111111111111:recovery-point:website-logs-20250708044140-61ebc5da",
25+
"sourceBackupVaultName": "central-account-backup-vault"
26+
}
27+
*/
28+
definition = jsonencode({
29+
"QueryLanguage" : "JSONata"
30+
"StartAt" : "SetVars",
31+
"States" : {
32+
"SetVars" : {
33+
"Type" : "Pass",
34+
"Assign" : {
35+
"accountId" : var.current.account_id,
36+
"backupVaultArnPrefix" : "arn:${var.current.partition}:backup:${var.current.region}:<accountId>:backup-vault:",
37+
"centralBackupServiceRoleArn" : var.deployment.backup_service_role_arn,
38+
"iamRoleArnPrefix" : "arn:${var.current.partition}:iam::<accountId>:role/",
39+
"intermediateBackupVaultArn" : aws_backup_vault.intermediate.arn,
40+
"memberAccountBackupServiceRoleName" : var.deployment.member_account_backup_service_role_name,
41+
"memberAccountBackupVaultName" : var.deployment.member_account_backup_vault_name,
42+
"memberAccountRestoreVaultName" : var.deployment.member_account_restore_vault_name,
43+
"standardBackupVaultArns" : values(aws_backup_vault.standard)[*].arn,
44+
"waitSeconds" : 180
45+
},
46+
"Output" : "{% $states.input %}"
47+
"Next" : "SourceVault?"
48+
}
49+
"SourceVault?" : {
50+
"Type" : "Choice",
51+
"Choices" : [
52+
{
53+
"Condition" : "{% ($replace($backupVaultArnPrefix, '<accountId>', $accountId) & $states.input.sourceBackupVaultName) in $standardBackupVaultArns %}",
54+
"Next" : "StartCopyToIntermediateVault"
55+
},
56+
{
57+
"Condition" : "{% ($replace($backupVaultArnPrefix, '<accountId>', $accountId) & $states.input.sourceBackupVaultName) = $intermediateBackupVaultArn %}",
58+
"Next" : "StartCopyToDestinationAccountBackupVault"
59+
}
60+
]
61+
},
62+
# Copy Standard -> Intermediate vault
63+
"StartCopyToIntermediateVault" : {
64+
"Type" : "Task",
65+
"Resource" : "arn:aws:states:::aws-sdk:backup:startCopyJob",
66+
"Arguments" : {
67+
"DestinationBackupVaultArn" : "{% $intermediateBackupVaultArn %}",
68+
"IamRoleArn" : "{% $centralBackupServiceRoleArn %}",
69+
"RecoveryPointArn" : "{% $states.input.recoveryPointArn %}",
70+
"SourceBackupVaultName" : "{% $states.input.sourceBackupVaultName %}",
71+
},
72+
"Output" : "{% $merge([$states.input, $states.result ]) %}",
73+
"Next" : "WaitForCopyToIntermediateVault"
74+
},
75+
"WaitForCopyToIntermediateVault" : {
76+
"Type" : "Wait",
77+
"Seconds" : "{% $waitSeconds %}",
78+
"Next" : "DescribeCopyToIntermediateVault"
79+
}
80+
"DescribeCopyToIntermediateVault" : {
81+
"Type" : "Task",
82+
"Resource" : "arn:aws:states:::aws-sdk:backup:describeCopyJob",
83+
"Arguments" : {
84+
"CopyJobId" : "{% $states.input.CopyJobId %}"
85+
},
86+
"Output" : "{% $merge([$states.input, $states.result ]) %}",
87+
"Next" : "CopiedToIntermediateVault?"
88+
},
89+
"CopiedToIntermediateVault?" : {
90+
"Type" : "Choice",
91+
"Choices" : [
92+
{
93+
"Condition" : "{% $states.input.CopyJob.State = 'COMPLETED' %}",
94+
"Next" : "StartCopyToDestinationAccountBackupVault"
95+
},
96+
{
97+
"Condition" : "{% $states.input.CopyJob.State in ['CREATED', 'RUNNING'] %}",
98+
"Next" : "WaitForCopyToIntermediateVault"
99+
}
100+
],
101+
"Default" : "Fail"
102+
},
103+
# Copy Intermediate -> Destination Account Backup vault
104+
"StartCopyToDestinationAccountBackupVault" : {
105+
"Type" : "Task",
106+
"Resource" : "arn:aws:states:::aws-sdk:backup:startCopyJob",
107+
"Arguments" : {
108+
"DestinationBackupVaultArn" : "{% $replace($backupVaultArnPrefix, '<accountId>', $states.input.destinationAccount) & $memberAccountBackupVaultName %}",
109+
"IamRoleArn" : "{% $centralBackupServiceRoleArn %}",
110+
"RecoveryPointArn" : "{% $states.input.CopyJob ? $states.input.CopyJob.DestinationRecoveryPointArn : $states.input.recoveryPointArn %}",
111+
"SourceBackupVaultName" : "{% $match($intermediateBackupVaultArn, /backup-vault:([^:]*)/).groups[0] %}",
112+
},
113+
"Output" : "{% $merge([$states.input, $states.result ]) %}",
114+
"Next" : "WaitForCopyToDestinationAccountBackupVault"
115+
},
116+
"WaitForCopyToDestinationAccountBackupVault" : {
117+
"Type" : "Wait",
118+
"Seconds" : "{% $waitSeconds %}",
119+
"Next" : "DescribeCopyToDestinationAccountBackupVault"
120+
}
121+
"DescribeCopyToDestinationAccountBackupVault" : {
122+
"Type" : "Task",
123+
"Resource" : "arn:aws:states:::aws-sdk:backup:describeCopyJob",
124+
"Arguments" : {
125+
"CopyJobId" : "{% $states.input.CopyJobId %}"
126+
},
127+
"Output" : "{% $merge([$states.input, $states.result ]) %}",
128+
"Next" : "CopiedToDestinationAccountBackupVault?"
129+
},
130+
"CopiedToDestinationAccountBackupVault?" : {
131+
"Type" : "Choice",
132+
"Choices" : [
133+
{
134+
"Condition" : "{% $states.input.CopyJob.State = 'COMPLETED' %}",
135+
"Next" : "StartCopyToDestinationAccountRestoreVault"
136+
},
137+
{
138+
"Condition" : "{% $states.input.CopyJob.State in ['CREATED', 'RUNNING'] %}",
139+
"Next" : "WaitForCopyToDestinationAccountBackupVault"
140+
}
141+
],
142+
"Default" : "Fail"
143+
},
144+
# Copy Destination Account Backup vault -> Destination Account Restore vault
145+
"StartCopyToDestinationAccountRestoreVault" : {
146+
"Type" : "Task",
147+
"Resource" : "arn:aws:states:::aws-sdk:backup:startCopyJob",
148+
"Credentials" : { "RoleArn" : "{% $replace($iamRoleArnPrefix, '<accountId>', $states.input.destinationAccount) & $memberAccountBackupServiceRoleName %}" },
149+
"Arguments" : {
150+
"DestinationBackupVaultArn" : "{% $replace($backupVaultArnPrefix, '<accountId>', $states.input.destinationAccount) & $memberAccountRestoreVaultName %}",
151+
"IamRoleArn" : "{% $replace($iamRoleArnPrefix, '<accountId>', $states.input.destinationAccount) & $memberAccountBackupServiceRoleName %}",
152+
"RecoveryPointArn" : "{% $states.input.CopyJob ? $states.input.CopyJob.DestinationRecoveryPointArn : $states.input.recoveryPointArn %}",
153+
"SourceBackupVaultName" : "{% $memberAccountBackupVaultName %}",
154+
},
155+
"Output" : "{% $merge([$states.input, $states.result ]) %}",
156+
"Next" : "WaitForCopyToDestinationAccountRestoreVault"
157+
},
158+
"WaitForCopyToDestinationAccountRestoreVault" : {
159+
"Type" : "Wait",
160+
"Seconds" : "{% $waitSeconds %}",
161+
"Next" : "DescribeCopyToDestinationAccountRestoreVault"
162+
}
163+
"DescribeCopyToDestinationAccountRestoreVault" : {
164+
"Type" : "Task",
165+
"Resource" : "arn:aws:states:::aws-sdk:backup:describeCopyJob",
166+
"Arguments" : {
167+
"CopyJobId" : "{% $states.input.CopyJobId %}"
168+
},
169+
"Output" : "{% $merge([$states.input, $states.result ]) %}",
170+
"Next" : "CopiedToDestinationAccountRestoreVault?"
171+
},
172+
"CopiedToDestinationAccountRestoreVault?" : {
173+
"Type" : "Choice",
174+
"Choices" : [
175+
{
176+
"Condition" : "{% $states.input.CopyJob.State = 'COMPLETED' %}",
177+
"Next" : "Succeed"
178+
},
179+
{
180+
"Condition" : "{% $states.input.CopyJob.State in ['CREATED', 'RUNNING'] %}",
181+
"Next" : "WaitForCopyToDestinationAccountRestoreVault"
182+
}
183+
],
184+
"Default" : "Fail"
185+
},
186+
"Succeed" : {
187+
"Type" : "Succeed"
188+
},
189+
"Fail" : {
190+
"Type" : "Fail",
191+
},
192+
}
193+
})
194+
}

modules/service-deployment-regional/variables.tf

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,23 @@ variable "backup_vaults" {
1616
})
1717
}
1818

19-
variable "current_aws_account_id" {
20-
description = "The AWS account ID where Terraform is being executed."
21-
type = string
22-
}
23-
24-
variable "current_aws_partition" {
25-
description = "The current AWS partition (e.g., aws, aws-cn, aws-us-gov) where Terraform is being executed."
26-
type = string
27-
}
28-
29-
variable "current_aws_region" {
30-
description = "The current AWS region where Terraform is being executed."
31-
type = string
19+
variable "current" {
20+
description = "The current AWS account ID, organization, partition, and region."
21+
type = object({
22+
account_id : string
23+
partition : string
24+
region : string
25+
})
3226
}
3327

3428
variable "deployment" {
3529
type = object({
36-
backup_service_role_arn = string
37-
ou_paths_including_children = list(string),
30+
backup_service_role_arn = string
31+
member_account_backup_service_role_name = string
32+
member_account_backup_vault_name = string
33+
member_account_eventbridge_rule_name = string
34+
member_account_restore_vault_name = string
35+
ou_paths_including_children = list(string)
3836
})
3937
}
4038

@@ -54,21 +52,6 @@ variable "kms" {
5452
})
5553
}
5654

57-
variable "member_account_backup_service_role_name" {
58-
description = "The name of the backup service role in member accounts."
59-
type = string
60-
}
61-
62-
variable "member_account_eventbridge_rule_name" {
63-
description = "The name of the EventBridge rule in member accounts."
64-
type = string
65-
}
66-
67-
variable "member_account_backup_vault_name" {
68-
description = "The name of the backup vault in member accounts."
69-
type = string
70-
}
71-
7255
variable "ram" {
7356
type = object({
7457
create_lag_shares = bool
@@ -88,6 +71,8 @@ variable "stepfunctions" {
8871
ingest_state_machine_name = string
8972
ingest_state_machine_role_arn = string
9073
ingest_state_role_arn = string
74+
restore_state_machine_name = string
75+
restore_state_machine_role_arn = string
9176
})
9277
}
9378

modules/service-deployment/cloudformation.tf

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ locals {
33
{ "Fn::GetAtt" : ["DeploymentHelperRole", "Arn"] },
44
{ "Fn::Sub" : "arn:$${AWS::Partition}:iam::$${AWS::AccountId}:role/$${BackupServiceRoleName}" },
55
[for i in var.admin_role_names : { "Fn::Sub" : "arn:$${AWS::Partition}:iam::$${AWS::AccountId}:role/${i}" }],
6+
{ "Ref" : "CentralBackupServiceRoleArn" }
67
])
78
}
89

@@ -15,19 +16,20 @@ resource "aws_cloudformation_stack_set" "member_account_deployments" {
1516

1617
# Try to do as much as possible in native CloudFormation, but some things, like dynamic lists, are only possible in Terraform.
1718
# jsonencode(jsondecode(...)) used to minify the file.
18-
template_body = jsonencode(jsondecode(templatefile("${path.module}/templates/stackset.json.tftpl", {
19+
template_body = templatefile("${path.module}/templates/stackset.json.tftpl", {
1920
central_backup_vault_arn_templates = [for i in local.central_backup_vault_arns_template : { "Fn::Sub" : replace(replace(i, "<REGION>", "$${AWS::Region}"), var.current.account_id, "$${CentralAccountId}") }],
2021
member_eventbridge_rule_arn_templates = [for i in var.deployment_regions : { "Fn::Sub" : "arn:${var.current.partition}:events:${i}:$${AWS::AccountId}:rule/${local.member_account_eventbridge_rule_name}" }],
2122
backup_vault_admin_arn_templates = local.cfn_backup_vault_admin_arn_templates
22-
})))
23+
})
2324

2425
parameters = {
2526
BackupServiceLinkedRoleArn = var.central_backup_service_linked_role_arn
2627
BackupServiceRoleName = local.member_account_backup_service_role_name
2728
BackupServiceRestoreRoleName = local.member_account_backup_service_restore_role_name
28-
BackupServiceRolePrincipals = join(", ", [module.backup_ingest_sfn_role.role.arn])
29+
BackupServiceRolePrincipals = join(", ", [module.backup_ingest_sfn_role.role.arn, module.backup_restore_sfn_role.role.arn])
2930
BackupVaultName = local.member_account_backup_vault_name
3031
CentralAccountId = var.current.account_id
32+
CentralBackupServiceRoleArn = module.backup_service_role.role.arn
3133
DeploymentHelperRoleArn = var.central_deployment_helper_role_arn
3234
DeploymentHelperRoleNamePrefix = replace(var.member_account_deployment_helper_role_name_template, "<REGION>", "")
3335
DeploymentHelperTopicName = var.central_deployment_helper_topic_name

0 commit comments

Comments
 (0)