Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions infra/accounts/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,13 @@ module "auth_github_actions" {
github_repository = module.project_config.code_repository
allowed_actions = [for aws_service in module.project_config.aws_services : "${aws_service}:*"]
}

# GuardDuty module - account-wide security detector
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is account-wide, but region specific. template-infra only really supports project_config.default_region + us-east-1 (for things that require existing there), but in the future we will have more greater support for multi-region configs.

We could lay some ground work for that with a local var like utilized_regions = distinct([local.region, 'us-east-1']) and create instances of the module for each region? Or have the module accept a list of regions.

Thoughts?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree. thoughts on adding utilized_regions as a list type variable, in project-config defaulting to default_region? I just added it, and tried to use it within the threatdection module - had to revert back the threatdetection work - the current Terraform AWS Provider we are using ("5.6.0") does not support specifying region on the aws_guardduty_detector. This was added on latest version of the AWS Terraform provider. Added TODO comments on the infra/modules/threatdetection.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please create issues in template-infra (if they don't exist) for:

  • AWS provider update
  • Multi-region support for GuardDuty setup

Then link the latter here.

module "threatdetection" {
source = "../modules/threatdetection"

enable_detector = var.enable_threatdetection
finding_publishing_frequency = var.threatdetection_finding_publishing_frequency
# TODO: When upgrading to AWS provider >= 5.7.0, uncomment for multi-region support:
# utilized_regions = module.project_config.utilized_regions
}
5 changes: 5 additions & 0 deletions infra/accounts/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ output "tf_locks_table_name" {
output "tf_state_bucket_name" {
value = module.backend.tf_state_bucket_name
}

output "threatdetection_detector_id" {
description = "ThreatDetection detector ID for account-wide security monitoring"
value = module.threatdetection.detector_id
}
20 changes: 20 additions & 0 deletions infra/accounts/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
variable "enable_threatdetection" {
description = "Whether to enable the ThreatDetection detector for the account"
type = bool
default = true
}

variable "threatdetection_finding_publishing_frequency" {
description = "The frequency of notifications sent for subsequent ThreatDetection finding occurrences"
type = string
default = "FIFTEEN_MINUTES"

validation {
condition = contains([
"FIFTEEN_MINUTES",
"ONE_HOUR",
"SIX_HOURS"
], var.threatdetection_finding_publishing_frequency)
error_message = "Finding publishing frequency must be one of: FIFTEEN_MINUTES, ONE_HOUR, SIX_HOURS."
}
}
27 changes: 24 additions & 3 deletions infra/modules/storage/access_control.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ resource "aws_s3_bucket_policy" "storage" {
}

data "aws_iam_policy_document" "storage" {
# Require HTTPS connections
statement {
sid = "RestrictToTLSRequestsOnly"
effect = "Deny"
actions = ["s3:*"]
sid = "RestrictToTLSRequestsOnly"
effect = "Deny"
actions = ["s3:*"]
Comment on lines +20 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These formatting changes seem unrelated, remove?

resources = [aws_s3_bucket.storage.arn]
principals {
type = "*"
Expand All @@ -30,6 +31,26 @@ data "aws_iam_policy_document" "storage" {
values = ["false"]
}
}

# Block access to objects tagged with malware threats
statement {
sid = "BlockMalwareThreats"
effect = "Deny"
actions = [
"s3:GetObject",
"s3:GetObjectVersion"
]
resources = ["${aws_s3_bucket.storage.arn}/*"]
principals {
type = "*"
identifiers = ["*"]
}
condition {
test = "StringEquals"
variable = "s3:ExistingObjectTag/GuardDutyMalwareScanStatus"
values = ["THREATS_FOUND"]
}
}
}

# Create policy for read/write access
Expand Down
139 changes: 139 additions & 0 deletions infra/modules/storage/malwaredetection.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# S3-specific GuardDuty malware protection
# This configures malware scanning for this specific S3 bucket

# Get current account ID
data "aws_caller_identity" "identity" {}

# Get current region
data "aws_region" "region" {}

# Configure S3 bucket for malware scanning
resource "aws_guardduty_malware_protection_plan" "s3_malware_protection" {
role = aws_iam_role.guardduty_malware_protection.arn

protected_resource {
s3_bucket {
bucket_name = aws_s3_bucket.storage.bucket
object_prefixes = [] # Scan all objects in the bucket
}
}

actions {
tagging {
status = "ENABLED"
}
}

depends_on = [
aws_iam_role.guardduty_malware_protection,
aws_iam_role_policy.guardduty_malware_protection,
aws_s3_bucket.storage
]
}

# IAM role for GuardDuty Malware Protection service
resource "aws_iam_role" "guardduty_malware_protection" {
name = "${var.name}-guardduty-malware-protection"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "malware-protection-plan.guardduty.amazonaws.com"
}
Condition = {
StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.identity.account_id
}
}
}
]
})
}

# IAM policy for GuardDuty Malware Protection
resource "aws_iam_role_policy" "guardduty_malware_protection" {
name = "${var.name}-guardduty-malware-protection-policy"
role = aws_iam_role.guardduty_malware_protection.id

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:GetObjectTagging",
"s3:GetObjectVersion",
"s3:GetObjectVersionTagging",
"s3:PutObjectTagging",
"s3:PutObjectVersionTagging"
]
Resource = [
"${aws_s3_bucket.storage.arn}/*"
]
},
{
Effect = "Allow"
Action = [
"s3:GetBucketLocation",
"s3:GetBucketVersioning",
"s3:ListBucket",
"s3:GetBucketOwnershipControls",
"s3:GetBucketPublicAccessBlock",
"s3:GetBucketPolicy",
"s3:GetBucketPolicyStatus",
"s3:GetBucketAcl",
"s3:GetBucketNotification",
"s3:GetBucketTagging"
]
Resource = [
aws_s3_bucket.storage.arn
]
},
{
Effect = "Allow"
Action = [
"kms:Decrypt",
"kms:DescribeKey",
"kms:GenerateDataKey"
]
Resource = [
aws_kms_key.storage.arn
]
Condition = {
StringEquals = {
"kms:ViaService" = "s3.${data.aws_region.region.name}.amazonaws.com"
}
}
},
{
Effect = "Allow"
Action = [
"iam:GetRole",
"sts:AssumeRole"
]
Resource = [
aws_iam_role.guardduty_malware_protection.arn
]
},
{
Effect = "Allow"
Action = [
"events:PutRule",
"events:PutTargets",
"events:DeleteRule",
"events:RemoveTargets",
"events:DescribeRule",
"events:ListTargetsByRule"
]
Resource = [
"arn:aws:events:${data.aws_region.region.name}:${data.aws_caller_identity.identity.account_id}:rule/DO-NOT-DELETE-AmazonGuardDutyMalwareProtectionS3-*"
]
}
]
})
}
18 changes: 18 additions & 0 deletions infra/modules/threatdetection/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# GuardDuty centralized module
# This module manages the account-wide GuardDuty detector for malware protection

# Create and enable GuardDuty detector (single detector for current region)
resource "aws_guardduty_detector" "main" {
# checkov:skip=CKV2_AWS_3:GuardDuty is enabled for this specific region/account - org-level management not required for single-account setup
enable = var.enable_detector
finding_publishing_frequency = var.finding_publishing_frequency
}

# TODO: When upgrading to AWS provider >= 5.7.0, uncomment the following for multi-region support:
# resource "aws_guardduty_detector" "main" {
# for_each = toset(var.utilized_regions)
# # checkov:skip=CKV2_AWS_3:GuardDuty is enabled for this specific region/account - org-level management not required for single-account setup
# enable = var.enable_detector
# region = each.value
# finding_publishing_frequency = var.finding_publishing_frequency
# }
22 changes: 22 additions & 0 deletions infra/modules/threatdetection/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Outputs for GuardDuty module

output "detector_id" {
description = "GuardDuty detector ID"
value = aws_guardduty_detector.main.id
}

output "detector_arn" {
description = "GuardDuty detector ARN"
value = aws_guardduty_detector.main.arn
}

# TODO: When upgrading to AWS provider >= 5.7.0, uncomment for multi-region support:
# output "detector_id" {
# description = "GuardDuty detector IDs by region"
# value = { for k, v in aws_guardduty_detector.main : k => v.id }
# }
#
# output "detector_arn" {
# description = "GuardDuty detector ARNs by region"
# value = { for k, v in aws_guardduty_detector.main : k => v.arn }
# }
31 changes: 31 additions & 0 deletions infra/modules/threatdetection/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
variable "enable_detector" {
description = "Whether to enable the GuardDuty detector"
type = bool
default = true
}

variable "finding_publishing_frequency" {
description = "The frequency of notifications sent for subsequent finding occurrences"
type = string
default = "FIFTEEN_MINUTES"

validation {
condition = contains([
"FIFTEEN_MINUTES",
"ONE_HOUR",
"SIX_HOURS"
], var.finding_publishing_frequency)
error_message = "Finding publishing frequency must be one of: FIFTEEN_MINUTES, ONE_HOUR, SIX_HOURS."
}
}

# TODO: When upgrading to AWS provider >= 5.7.0, uncomment for multi-region support:
# variable "utilized_regions" {
# description = <<-EOF
# List of AWS regions that GuardDuty should be enabled in.
# This should typically include all regions that are being used in the project,
# especially if there are resources in those regions that GuardDuty can monitor for security threats.
# EOF
# type = list(string)
# default = []
# }
3 changes: 3 additions & 0 deletions infra/project-config/aws_services.tf
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ locals {
// Amazon EventBridge – Serverless event bus for event-driven applications and AWS service integrations. Used for event-based jobs.
"events",

// Amazon GuardDuty – Threat detection service that monitors for malicious activity and unauthorized behavior. Used for S3 malware protection.
"guardduty",

// AWS Identity and Access Management – Manages users, roles, and permissions for AWS services.
"iam",

Expand Down
4 changes: 4 additions & 0 deletions infra/project-config/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@ locals {
# otherwise us-east-1 is a good default
default_region = "us-east-1"

# List of AWS regions that may be used for this project.
# Used for multi-region deployments or to scope down other resources that need to interact with multiple regions.
utilized_regions = [local.default_region]

github_actions_role_name = "${local.project_name}-github-actions"
}
4 changes: 4 additions & 0 deletions infra/project-config/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,7 @@ output "project_name" {
output "system_notifications_config" {
value = local.system_notifications_config
}

output "utilized_regions" {
value = local.utilized_regions
}
Loading