Skip to content

Commit 35bc1ca

Browse files
Merge pull request #41 from NHSDigital/CCM-7890_ExpandBackupModule
CCM-7890 expand backup module
2 parents d1e89cf + f971b0d commit 35bc1ca

23 files changed

Lines changed: 351 additions & 263 deletions

.github/actions/tfsec/action.yaml

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@ runs:
66
- name: "TFSec Scan - Components"
77
shell: bash
88
run: |
9-
for component in $(find infrastructure/terraform/components -mindepth 1 -type d); do
10-
scripts/terraform/tfsec.sh $component
11-
done
12-
- name: "TFSec Scan - Modules"
13-
shell: bash
14-
run: |
15-
for module in $(find infrastructure/terraform/modules -mindepth 1 -type d); do
16-
scripts/terraform/tfsec.sh $module
17-
done
9+
modules_exit_code=0
10+
11+
./scripts/terraform/tfsec.sh ./infrastructure/modules || modules_exit_code=$?
12+
13+
if [ $modules_exit_code -ne 0 ]; then
14+
echo "One or more TFSec scans failed."
15+
exit 1
16+
fi

infrastructure/modules/aws-backup-source/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See [terraform-aws-backup](https://github.com/NHSDigital/terraform-aws-backup.gi
1414
| <a name="input_backup_copy_vault_arn"></a> [backup\_copy\_vault\_arn](#input\_backup\_copy\_vault\_arn) | The ARN of the destination backup vault for cross-account backup copies. | `string` | `""` | no |
1515
| <a name="input_backup_plan_config"></a> [backup\_plan\_config](#input\_backup\_plan\_config) | Configuration for backup plans | <pre>object({<br> selection_tag = string<br> compliance_resource_types = list(string)<br> rules = list(object({<br> name = string<br> schedule = string<br> enable_continuous_backup = optional(bool)<br> lifecycle = object({<br> delete_after = optional(number)<br> cold_storage_after = optional(number)<br> })<br> copy_action = optional(object({<br> delete_after = optional(number)<br> }))<br> }))<br> })</pre> | <pre>{<br> "compliance_resource_types": [<br> "S3"<br> ],<br> "rules": [<br> {<br> "copy_action": {<br> "delete_after": 365<br> },<br> "lifecycle": {<br> "delete_after": 35<br> },<br> "name": "daily_kept_5_weeks",<br> "schedule": "cron(0 0 * * ? *)"<br> },<br> {<br> "copy_action": {<br> "delete_after": 365<br> },<br> "lifecycle": {<br> "delete_after": 90<br> },<br> "name": "weekly_kept_3_months",<br> "schedule": "cron(0 1 ? * SUN *)"<br> },<br> {<br> "copy_action": {<br> "delete_after": 365<br> },<br> "lifecycle": {<br> "cold_storage_after": 30,<br> "delete_after": 2555<br> },<br> "name": "monthly_kept_7_years",<br> "schedule": "cron(0 2 1 * ? *)"<br> },<br> {<br> "copy_action": {<br> "delete_after": 365<br> },<br> "enable_continuous_backup": true,<br> "lifecycle": {<br> "delete_after": 35<br> },<br> "name": "point_in_time_recovery",<br> "schedule": "cron(0 5 * * ? *)"<br> }<br> ],<br> "selection_tag": "BackupLocal"<br>}</pre> | no |
1616
| <a name="input_backup_plan_config_dynamodb"></a> [backup\_plan\_config\_dynamodb](#input\_backup\_plan\_config\_dynamodb) | Configuration for backup plans with dynamodb | <pre>object({<br> enable = bool<br> selection_tag = string<br> compliance_resource_types = list(string)<br> rules = optional(list(object({<br> name = string<br> schedule = string<br> enable_continuous_backup = optional(bool)<br> lifecycle = object({<br> delete_after = number<br> cold_storage_after = optional(number)<br> })<br> copy_action = optional(object({<br> delete_after = optional(number)<br> }))<br> })))<br> })</pre> | <pre>{<br> "compliance_resource_types": [<br> "DynamoDB"<br> ],<br> "enable": true,<br> "rules": [<br> {<br> "copy_action": {<br> "delete_after": 365<br> },<br> "lifecycle": {<br> "delete_after": 35<br> },<br> "name": "dynamodb_daily_kept_5_weeks",<br> "schedule": "cron(0 0 * * ? *)"<br> },<br> {<br> "copy_action": {<br> "delete_after": 365<br> },<br> "lifecycle": {<br> "delete_after": 90<br> },<br> "name": "dynamodb_weekly_kept_3_months",<br> "schedule": "cron(0 1 ? * SUN *)"<br> },<br> {<br> "copy_action": {<br> "delete_after": 365<br> },<br> "lifecycle": {<br> "cold_storage_after": 30,<br> "delete_after": 2555<br> },<br> "name": "dynamodb_monthly_kept_7_years",<br> "schedule": "cron(0 2 1 * ? *)"<br> }<br> ],<br> "selection_tag": "BackupDynamoDB"<br>}</pre> | no |
17-
| <a name="input_bootstrap_kms_key_arn"></a> [bootstrap\_kms\_key\_arn](#input\_bootstrap\_kms\_key\_arn) | The ARN of the bootstrap KMS key used for encryption at rest of the SNS topic. | `string` | n/a | yes |
17+
| <a name="input_notification_kms_key"></a> [bootstrap\_kms\_key\_arn](#input\_bootstrap\_kms\_key\_arn) | The ARN of the bootstrap KMS key used for encryption at rest of the SNS topic. | `string` | n/a | yes |
1818
| <a name="input_environment_name"></a> [environment\_name](#input\_environment\_name) | The name of the environment where AWS Backup is configured. | `string` | n/a | yes |
1919
| <a name="input_notifications_target_email_address"></a> [notifications\_target\_email\_address](#input\_notifications\_target\_email\_address) | The email address to which backup notifications will be sent via SNS. | `string` | `""` | no |
2020
| <a name="input_project_name"></a> [project\_name](#input\_project\_name) | The name of the project this relates to. | `string` | n/a | yes |
@@ -24,7 +24,7 @@ See [terraform-aws-backup](https://github.com/NHSDigital/terraform-aws-backup.gi
2424
| <a name="input_restore_testing_plan_scheduled_expression"></a> [restore\_testing\_plan\_scheduled\_expression](#input\_restore\_testing\_plan\_scheduled\_expression) | Scheduled Expression of Recovery Selection Point | `string` | `"cron(0 1 ? * SUN *)"` | no |
2525
| <a name="input_restore_testing_plan_selection_window_days"></a> [restore\_testing\_plan\_selection\_window\_days](#input\_restore\_testing\_plan\_selection\_window\_days) | Selection window days | `number` | `7` | no |
2626
| <a name="input_restore_testing_plan_start_window"></a> [restore\_testing\_plan\_start\_window](#input\_restore\_testing\_plan\_start\_window) | Start window from the scheduled time during which the test should start | `number` | `1` | no |
27-
| <a name="input_terraform_role_arn"></a> [terraform\_role\_arn](#input\_terraform\_role\_arn) | ARN of Terraform role used to deploy to account | `string` | n/a | yes |
27+
| <a name="input_management_ci_role_arn"></a> [terraform\_role\_arn](#input\_terraform\_role\_arn) | ARN of Terraform role used to deploy to account | `string` | n/a | yes |
2828
<!-- vale on -->
2929

3030
## Example
@@ -34,9 +34,9 @@ module "test_aws_backup" {
3434
source = "./modules/aws-backup"
3535
3636
environment_name = "environment_name"
37-
bootstrap_kms_key_arn = kms_key[0].arn
37+
notification_kms_key = kms_key[0].arn
3838
project_name = "testproject"
3939
reports_bucket = "compliance-reports"
40-
terraform_role_arn = data.aws_iam_role.terraform_role.arn
40+
management_ci_role_arn = data.aws_iam_role.terraform_role.arn
4141
}
4242
```

infrastructure/modules/aws-backup-source/backup_framework.tf renamed to infrastructure/modules/aws-backup-source/backup_framework_dynamodb.tf

Lines changed: 10 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
1-
resource "aws_backup_framework" "main" {
1+
resource "aws_backup_framework" "dynamodb" {
2+
count = var.backup_plan_config_dynamodb.enable ? 1 : 0
3+
24
# must be underscores instead of dashes
3-
name = replace("${local.resource_name_prefix}-framework", "-", "_")
4-
description = "${var.project_name} Backup Framework"
5+
name = replace("${local.resource_name_prefix}-dynamodb-framework", "-", "_")
6+
description = "${var.project_name} DynamoDB Backup Framework"
57

68
# Evaluates if recovery points are encrypted.
79
control {
810
name = "BACKUP_RECOVERY_POINT_ENCRYPTED"
911

1012
scope {
1113
tags = {
12-
"environment_name" = var.environment_name
14+
Environment = var.environment_name
1315
}
1416
}
1517
}
@@ -20,13 +22,13 @@ resource "aws_backup_framework" "main" {
2022

2123
scope {
2224
tags = {
23-
"environment_name" = var.environment_name
25+
Environment = var.environment_name
2426
}
2527
}
2628

2729
input_parameter {
2830
name = "principalArnList"
29-
value = var.terraform_role_arn
31+
value = var.management_ci_role_arn
3032
}
3133
}
3234

@@ -36,7 +38,7 @@ resource "aws_backup_framework" "main" {
3638

3739
scope {
3840
tags = {
39-
"environment_name" = var.environment_name
41+
Environment = var.environment_name
4042
}
4143
}
4244

@@ -52,7 +54,7 @@ resource "aws_backup_framework" "main" {
5254

5355
scope {
5456
tags = {
55-
"environment_name" = var.environment_name
57+
Environment = var.environment_name
5658
}
5759
}
5860

@@ -72,47 +74,6 @@ resource "aws_backup_framework" "main" {
7274
}
7375
}
7476

75-
# Evaluates if resources are protected by a backup plan.
76-
control {
77-
name = "BACKUP_RESOURCES_PROTECTED_BY_BACKUP_PLAN"
78-
79-
scope {
80-
compliance_resource_types = var.backup_plan_config.compliance_resource_types
81-
tags = {
82-
(var.backup_plan_config.selection_tag) = "True"
83-
}
84-
}
85-
}
86-
87-
# Evaluates if resources have at least one recovery point created within the past 1 day.
88-
control {
89-
name = "BACKUP_LAST_RECOVERY_POINT_CREATED"
90-
91-
input_parameter {
92-
name = "recoveryPointAgeUnit"
93-
value = "days"
94-
}
95-
96-
input_parameter {
97-
name = "recoveryPointAgeValue"
98-
value = "1"
99-
}
100-
101-
scope {
102-
compliance_resource_types = var.backup_plan_config.compliance_resource_types
103-
tags = {
104-
(var.backup_plan_config.selection_tag) = "True"
105-
}
106-
}
107-
}
108-
}
109-
110-
resource "aws_backup_framework" "dynamodb" {
111-
count = var.backup_plan_config_dynamodb.enable ? 1 : 0
112-
# must be underscores instead of dashes
113-
name = replace("${local.resource_name_prefix}-dynamodb-framework", "-", "_")
114-
description = "${var.project_name} DynamoDB Backup Framework"
115-
11677
# Evaluates if resources are protected by a backup plan.
11778
control {
11879
name = "BACKUP_RESOURCES_PROTECTED_BY_BACKUP_PLAN"
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
resource "aws_backup_framework" "s3" {
2+
count = var.backup_plan_config_s3.enable ? 1 : 0
3+
4+
# must be underscores instead of dashes
5+
name = replace("${local.resource_name_prefix}-framework", "-", "_")
6+
description = "${var.project_name} Backup Framework"
7+
8+
# Evaluates if recovery points are encrypted.
9+
control {
10+
name = "BACKUP_RECOVERY_POINT_ENCRYPTED"
11+
12+
scope {
13+
tags = {
14+
Environment = var.environment_name
15+
}
16+
}
17+
}
18+
19+
# Evaluates if backup vaults do not allow manual deletion of recovery points with the exception of certain IAM roles.
20+
control {
21+
name = "BACKUP_RECOVERY_POINT_MANUAL_DELETION_DISABLED"
22+
23+
scope {
24+
tags = {
25+
Environment = var.environment_name
26+
}
27+
}
28+
29+
input_parameter {
30+
name = "principalArnList"
31+
value = var.management_ci_role_arn
32+
}
33+
}
34+
35+
# Evaluates if recovery point retention period is at least 1 month.
36+
control {
37+
name = "BACKUP_RECOVERY_POINT_MINIMUM_RETENTION_CHECK"
38+
39+
scope {
40+
tags = {
41+
Environment = var.environment_name
42+
}
43+
}
44+
45+
input_parameter {
46+
name = "requiredRetentionDays"
47+
value = "35"
48+
}
49+
}
50+
51+
# Evaluates if backup plan creates backups at least every 1 day and retains them for at least 1 month before deleting them.
52+
control {
53+
name = "BACKUP_PLAN_MIN_FREQUENCY_AND_MIN_RETENTION_CHECK"
54+
55+
scope {
56+
tags = {
57+
Environment = var.environment_name
58+
}
59+
}
60+
61+
input_parameter {
62+
name = "requiredFrequencyUnit"
63+
value = "days"
64+
}
65+
66+
input_parameter {
67+
name = "requiredRetentionDays"
68+
value = "35"
69+
}
70+
71+
input_parameter {
72+
name = "requiredFrequencyValue"
73+
value = "1"
74+
}
75+
}
76+
77+
# Evaluates if resources are protected by a backup plan.
78+
control {
79+
name = "BACKUP_RESOURCES_PROTECTED_BY_BACKUP_PLAN"
80+
81+
scope {
82+
compliance_resource_types = var.backup_plan_config_s3.compliance_resource_types
83+
tags = {
84+
(var.backup_plan_config_s3.selection_tag) = "True"
85+
}
86+
}
87+
}
88+
89+
# Evaluates if resources have at least one recovery point created within the past 1 day.
90+
control {
91+
name = "BACKUP_LAST_RECOVERY_POINT_CREATED"
92+
93+
input_parameter {
94+
name = "recoveryPointAgeUnit"
95+
value = "days"
96+
}
97+
98+
input_parameter {
99+
name = "recoveryPointAgeValue"
100+
value = "1"
101+
}
102+
103+
scope {
104+
compliance_resource_types = var.backup_plan_config_s3.compliance_resource_types
105+
tags = {
106+
(var.backup_plan_config_s3.selection_tag) = "True"
107+
}
108+
}
109+
}
110+
}

infrastructure/modules/aws-backup-source/backup_plan.tf

Lines changed: 0 additions & 85 deletions
This file was deleted.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# this backup plan shouldn't include a continous backup rule as it isn't supported for DynamoDB
2+
resource "aws_backup_plan" "dynamodb" {
3+
count = var.backup_plan_config_dynamodb.enable ? 1 : 0
4+
5+
name = "${local.resource_name_prefix}-dynamodb-plan"
6+
7+
dynamic "rule" {
8+
for_each = var.backup_plan_config_dynamodb.rules
9+
content {
10+
recovery_point_tags = {
11+
backup_rule_name = rule.value.name
12+
}
13+
rule_name = rule.value.name
14+
target_vault_name = aws_backup_vault.main.name
15+
schedule = rule.value.schedule
16+
lifecycle {
17+
delete_after = rule.value.lifecycle.delete_after != null ? rule.value.lifecycle.delete_after : null
18+
cold_storage_after = rule.value.lifecycle.cold_storage_after != null ? rule.value.lifecycle.cold_storage_after : null
19+
}
20+
dynamic "copy_action" {
21+
for_each = var.backup_copy_vault_arn != "" && var.backup_copy_vault_account_id != "" && rule.value.copy_action != null ? rule.value.copy_action : {}
22+
content {
23+
lifecycle {
24+
delete_after = copy_action.value
25+
}
26+
destination_vault_arn = var.backup_copy_vault_arn
27+
}
28+
}
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)