Skip to content

Commit 7a710e7

Browse files
claude[bot]lgallard
andcommitted
fix: address security vulnerabilities and monitoring issues in secure backup configuration
- **SECURITY FIX**: Remove wildcard KMS permissions from root account policies - Replace ''kms:*'' with specific required permissions for backup operations - Add ViaService conditions to restrict KMS usage to backup service only - This fixes high-risk security gap that violated principle of least privilege - **MONITORING FIX**: Update CloudWatch patterns for actual AWS Backup events - Replace invalid ''VAULT_ACCESS'' pattern with real CloudTrail backup events - Update CloudWatch dashboard queries to use correct CloudTrail log format - Fix log group naming to reflect CloudTrail integration purpose - **CONFIGURATION**: Make alarm thresholds configurable - Add vault_access_alarm_threshold variable for customizable monitoring - Add SNS topic configuration variables for notification management - Optimize region references using local values for better performance - **VARIABLE FIXES**: Resolve variable naming inconsistencies - Fix cross_region_name -> cross_region variable references - Fix retention_days -> backup_retention_days variable references - Update long_term_retention_days -> weekly_backup_retention_days All changes follow CLAUDE.md security guidelines and maintain backward compatibility. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Luis M. Gallardo D. <lgallard@users.noreply.github.com>
1 parent 50b92aa commit 7a710e7

File tree

4 files changed

+103
-22
lines changed

4 files changed

+103
-22
lines changed

examples/secure_backup_configuration/kms.tf

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,28 @@ resource "aws_kms_key" "backup_key" {
3434
Principal = {
3535
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
3636
}
37-
Action = "kms:*"
37+
Action = [
38+
"kms:Create*",
39+
"kms:Describe*",
40+
"kms:Enable*",
41+
"kms:List*",
42+
"kms:Put*",
43+
"kms:Update*",
44+
"kms:Revoke*",
45+
"kms:Disable*",
46+
"kms:Get*",
47+
"kms:Delete*",
48+
"kms:TagResource",
49+
"kms:UntagResource",
50+
"kms:ScheduleKeyDeletion",
51+
"kms:CancelKeyDeletion"
52+
]
3853
Resource = "*"
54+
Condition = {
55+
StringEquals = {
56+
"kms:ViaService" = "backup.${data.aws_region.current.id}.amazonaws.com"
57+
}
58+
}
3959
},
4060
{
4161
Sid = "AllowCloudWatchLogsAccess"
@@ -108,7 +128,7 @@ resource "aws_kms_key" "cross_region_backup_key" {
108128
Resource = "*"
109129
Condition = {
110130
StringEquals = {
111-
"kms:ViaService" = "backup.${var.cross_region_name}.amazonaws.com"
131+
"kms:ViaService" = "backup.${var.cross_region}.amazonaws.com"
112132
}
113133
}
114134
},
@@ -118,8 +138,31 @@ resource "aws_kms_key" "cross_region_backup_key" {
118138
Principal = {
119139
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
120140
}
121-
Action = "kms:*"
141+
Action = [
142+
"kms:Create*",
143+
"kms:Describe*",
144+
"kms:Enable*",
145+
"kms:List*",
146+
"kms:Put*",
147+
"kms:Update*",
148+
"kms:Revoke*",
149+
"kms:Disable*",
150+
"kms:Get*",
151+
"kms:Delete*",
152+
"kms:TagResource",
153+
"kms:UntagResource",
154+
"kms:ScheduleKeyDeletion",
155+
"kms:CancelKeyDeletion"
156+
]
122157
Resource = "*"
158+
Condition = {
159+
StringEquals = {
160+
"kms:ViaService" = [
161+
"backup.${data.aws_region.current.id}.amazonaws.com",
162+
"backup.${var.cross_region}.amazonaws.com"
163+
]
164+
}
165+
}
123166
},
124167
{
125168
Sid = "AllowCrossRegionBackupAccess"
@@ -137,7 +180,7 @@ resource "aws_kms_key" "cross_region_backup_key" {
137180
StringEquals = {
138181
"kms:ViaService" = [
139182
"backup.${data.aws_region.current.id}.amazonaws.com",
140-
"backup.${var.cross_region_name}.amazonaws.com"
183+
"backup.${var.cross_region}.amazonaws.com"
141184
]
142185
}
143186
}
@@ -152,7 +195,7 @@ resource "aws_kms_key" "cross_region_backup_key" {
152195
Name = "${var.project_name}-${var.environment}-backup-cross-region-key"
153196
Purpose = "cross-region-backup-encryption"
154197
KeyType = "cross-region"
155-
Region = var.cross_region_name
198+
Region = var.cross_region
156199
Compliance = "required"
157200
})
158201
}

examples/secure_backup_configuration/main.tf

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ locals {
4343
completion_window = 300
4444
lifecycle = {
4545
cold_storage_after = 30
46-
delete_after = var.retention_days
46+
delete_after = var.backup_retention_days
4747
}
4848
copy_actions = var.enable_cross_region_backup ? [{
49-
destination_backup_vault_arn = "arn:aws:backup:${var.cross_region_name}:${data.aws_caller_identity.current.account_id}:backup-vault:${local.vault_name}-cross-region"
49+
destination_backup_vault_arn = "arn:aws:backup:${var.cross_region}:${data.aws_caller_identity.current.account_id}:backup-vault:${local.vault_name}-cross-region"
5050
lifecycle = {
5151
cold_storage_after = 30
52-
delete_after = var.retention_days
52+
delete_after = var.backup_retention_days
5353
}
5454
}] : []
5555
recovery_point_tags = {
@@ -67,13 +67,13 @@ locals {
6767
completion_window = 480
6868
lifecycle = {
6969
cold_storage_after = 7
70-
delete_after = var.long_term_retention_days
70+
delete_after = var.weekly_backup_retention_days
7171
}
7272
copy_actions = var.enable_cross_region_backup ? [{
73-
destination_backup_vault_arn = "arn:aws:backup:${var.cross_region_name}:${data.aws_caller_identity.current.account_id}:backup-vault:${local.vault_name}-cross-region"
73+
destination_backup_vault_arn = "arn:aws:backup:${var.cross_region}:${data.aws_caller_identity.current.account_id}:backup-vault:${local.vault_name}-cross-region"
7474
lifecycle = {
7575
cold_storage_after = 7
76-
delete_after = var.long_term_retention_days
76+
delete_after = var.weekly_backup_retention_days
7777
}
7878
}] : []
7979
recovery_point_tags = {
@@ -187,7 +187,7 @@ resource "aws_backup_vault" "cross_region_vault" {
187187
tags = merge(local.common_tags, {
188188
Name = "${local.vault_name}-cross-region"
189189
Type = "cross-region"
190-
Region = var.cross_region_name
190+
Region = var.cross_region
191191
})
192192
}
193193

examples/secure_backup_configuration/monitoring.tf

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
# CloudWatch monitoring and alerting for backup security
22

3-
# CloudWatch Log Group for backup events
3+
# Local values for monitoring optimization
4+
locals {
5+
current_region = local.current_region
6+
}
7+
8+
# CloudWatch Log Group for CloudTrail backup events (optional, used if CloudTrail sends logs here)
9+
# NOTE: This log group is created for potential CloudTrail integration
10+
# If not using CloudTrail for backup audit logging, this can be omitted
411
resource "aws_cloudwatch_log_group" "backup_logs" {
5-
name = "/aws/backup/${var.project_name}-${var.environment}"
12+
name = "/aws/cloudtrail/${var.project_name}-${var.environment}-backup"
613
retention_in_days = var.log_retention_days
714
kms_key_id = aws_kms_key.backup_key.arn
815

916
tags = merge(local.common_tags, {
10-
Name = "backup-logs"
17+
Name = "backup-cloudtrail-logs"
1118
Purpose = "audit-logging"
19+
Service = "cloudtrail"
1220
})
1321
}
1422

@@ -78,11 +86,12 @@ resource "aws_cloudwatch_metric_alarm" "backup_job_success" {
7886
})
7987
}
8088

81-
# CloudWatch Metric Filter for backup vault access
89+
# CloudWatch Metric Filter for backup vault events (using CloudTrail patterns)
8290
resource "aws_logs_metric_filter" "vault_access" {
8391
name = "${var.project_name}-${var.environment}-vault-access"
8492
log_group_name = aws_cloudwatch_log_group.backup_logs.name
85-
pattern = "[timestamp, request_id, event_type=\"VAULT_ACCESS\", ...]"
93+
# Updated pattern to match actual AWS Backup CloudTrail events
94+
pattern = "{ $.eventSource = \"backup.amazonaws.com\" && ($.eventName = \"GetBackupVault*\" || $.eventName = \"DeleteBackupVault*\" || $.eventName = \"PutBackupVault*\") }"
8695

8796
metric_transformation {
8897
name = "VaultAccess"
@@ -103,7 +112,7 @@ resource "aws_cloudwatch_metric_alarm" "backup_vault_access" {
103112
namespace = "BackupSecurity/${var.project_name}"
104113
period = "900" # 15 minutes
105114
statistic = "Sum"
106-
threshold = "10" # Adjust based on normal access patterns
115+
threshold = var.vault_access_alarm_threshold
107116
alarm_description = "This metric monitors unusual backup vault access patterns for security"
108117
alarm_actions = var.sns_topic_arn != null ? [var.sns_topic_arn] : []
109118
treat_missing_data = "notBreaching"
@@ -136,7 +145,7 @@ resource "aws_cloudwatch_dashboard" "backup_dashboard" {
136145
]
137146
view = "timeSeries"
138147
stacked = false
139-
region = data.aws_region.current.id
148+
region = local.current_region
140149
title = "Backup Job Status"
141150
period = 300
142151
stat = "Sum"
@@ -151,7 +160,7 @@ resource "aws_cloudwatch_dashboard" "backup_dashboard" {
151160
["BackupSecurity/${var.project_name}", "VaultAccess", "BackupVaultName", module.backup.vault_id]
152161
]
153162
view = "timeSeries"
154-
region = data.aws_region.current.id
163+
region = local.current_region
155164
title = "Vault Access Patterns"
156165
period = 300
157166
stat = "Sum"
@@ -162,8 +171,8 @@ resource "aws_cloudwatch_dashboard" "backup_dashboard" {
162171
width = 24
163172
height = 6
164173
properties = {
165-
query = "SOURCE '${aws_cloudwatch_log_group.backup_logs.name}' | fields @timestamp, event_type, source_ip, user_identity\n| filter event_type = \"VAULT_ACCESS\"\n| sort @timestamp desc\n| limit 100"
166-
region = data.aws_region.current.id
174+
query = "SOURCE '${aws_cloudwatch_log_group.backup_logs.name}' | fields @timestamp, eventSource, eventName, sourceIPAddress, userIdentity.type\n| filter eventSource = \"backup.amazonaws.com\"\n| sort @timestamp desc\n| limit 100"
175+
region = local.current_region
167176
title = "Recent Vault Access Events"
168177
}
169178
}

examples/secure_backup_configuration/variables.tf

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,33 @@ variable "compliance_framework" {
174174
condition = contains(["SOC2", "HIPAA", "PCI-DSS", "ISO27001", "GDPR"], var.compliance_framework)
175175
error_message = "Compliance framework must be one of: SOC2, HIPAA, PCI-DSS, ISO27001, GDPR."
176176
}
177+
}
178+
179+
# CloudWatch monitoring configuration
180+
variable "vault_access_alarm_threshold" {
181+
description = "Threshold for unusual vault access alarm (adjust based on normal access patterns)"
182+
type = number
183+
default = 10
184+
185+
validation {
186+
condition = var.vault_access_alarm_threshold > 0
187+
error_message = "Vault access alarm threshold must be greater than 0."
188+
}
189+
}
190+
191+
variable "sns_topic_arn" {
192+
description = "SNS topic ARN for alarm notifications (optional)"
193+
type = string
194+
default = null
195+
196+
validation {
197+
condition = var.sns_topic_arn == null || can(regex("^arn:aws:sns:[a-z0-9-]+:[0-9]{12}:.*", var.sns_topic_arn))
198+
error_message = "SNS topic ARN must be a valid SNS topic ARN format."
199+
}
200+
}
201+
202+
variable "create_sns_topic" {
203+
description = "Whether to create an SNS topic for backup security alerts"
204+
type = bool
205+
default = false
177206
}

0 commit comments

Comments
 (0)