mirror of
https://github.com/ghndrx/terraform-foundation.git
synced 2026-02-10 06:45:06 +00:00
feat(security): add guardduty and security-hub modules
- guardduty: Full-featured threat detection with SNS alerts, EventBridge, S3 export, IPSet/ThreatIntelSet, organization support - security-hub: Centralized security posture with standards (CIS, PCI, NIST), cross-region aggregation, custom actions, built-in insights Both modules are opt-in via variables with sensible defaults.
This commit is contained in:
140
terraform/modules/guardduty/README.md
Normal file
140
terraform/modules/guardduty/README.md
Normal file
@@ -0,0 +1,140 @@
|
||||
# GuardDuty Module
|
||||
|
||||
AWS GuardDuty threat detection with alerting, S3 export, and threat intelligence integration.
|
||||
|
||||
## Features
|
||||
|
||||
- **All Protection Types**: S3, Kubernetes, malware, RDS, Lambda, runtime monitoring
|
||||
- **SNS Alerts**: EventBridge-based alerts with severity filtering
|
||||
- **S3 Export**: Archive findings with lifecycle policies
|
||||
- **Threat Intelligence**: Custom IP sets and threat intel feeds
|
||||
- **Organization Support**: Delegated admin configuration
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic
|
||||
|
||||
```hcl
|
||||
module "guardduty" {
|
||||
source = "../modules/guardduty"
|
||||
name = "main"
|
||||
}
|
||||
```
|
||||
|
||||
### With Email Alerts
|
||||
|
||||
```hcl
|
||||
module "guardduty" {
|
||||
source = "../modules/guardduty"
|
||||
name = "main"
|
||||
|
||||
enable_sns_alerts = true
|
||||
alert_email = "security@example.com"
|
||||
alert_severity_threshold = "HIGH" # Only HIGH and CRITICAL
|
||||
}
|
||||
```
|
||||
|
||||
### Full Security Stack
|
||||
|
||||
```hcl
|
||||
module "guardduty" {
|
||||
source = "../modules/guardduty"
|
||||
name = "security-prod"
|
||||
|
||||
# All protections enabled
|
||||
enable_s3_protection = true
|
||||
enable_kubernetes_audit = true
|
||||
enable_malware_protection = true
|
||||
enable_rds_login_events = true
|
||||
enable_lambda_network_logs = true
|
||||
enable_runtime_monitoring = true # Additional cost
|
||||
|
||||
# Alerting
|
||||
enable_sns_alerts = true
|
||||
alert_email = "security@example.com"
|
||||
alert_severity_threshold = "MEDIUM"
|
||||
|
||||
# Export for compliance
|
||||
enable_s3_export = true
|
||||
|
||||
# Trusted IPs (won't generate findings)
|
||||
ipset_cidrs = [
|
||||
"10.0.0.0/8",
|
||||
"192.168.1.0/24",
|
||||
]
|
||||
|
||||
tags = {
|
||||
Environment = "production"
|
||||
Team = "security"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Organization Admin
|
||||
|
||||
```hcl
|
||||
module "guardduty" {
|
||||
source = "../modules/guardduty"
|
||||
name = "org-guardduty"
|
||||
|
||||
is_organization_admin = true
|
||||
auto_enable_organization_members = true
|
||||
|
||||
enable_sns_alerts = true
|
||||
alert_email = "soc@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
## Inputs
|
||||
|
||||
| Name | Description | Type | Default |
|
||||
|------|-------------|------|---------|
|
||||
| name | Name prefix for resources | string | - |
|
||||
| enable | Enable GuardDuty detector | bool | true |
|
||||
| finding_publishing_frequency | Publishing frequency | string | "FIFTEEN_MINUTES" |
|
||||
| enable_s3_protection | S3 data events monitoring | bool | true |
|
||||
| enable_kubernetes_audit | EKS audit logs | bool | true |
|
||||
| enable_malware_protection | EC2/EBS malware scanning | bool | true |
|
||||
| enable_rds_login_events | RDS login monitoring | bool | true |
|
||||
| enable_lambda_network_logs | Lambda network activity | bool | true |
|
||||
| enable_runtime_monitoring | Runtime monitoring ($$) | bool | false |
|
||||
| enable_sns_alerts | Enable SNS alerts | bool | false |
|
||||
| alert_email | Email for alerts | string | "" |
|
||||
| alert_sns_topic_arn | Existing SNS topic | string | "" |
|
||||
| alert_severity_threshold | Min severity: LOW/MEDIUM/HIGH/CRITICAL | string | "MEDIUM" |
|
||||
| enable_s3_export | Export findings to S3 | bool | false |
|
||||
| export_s3_bucket | S3 bucket for export | string | "" |
|
||||
| ipset_cidrs | Trusted IP CIDRs | list(string) | [] |
|
||||
| threat_intel_feed_urls | Threat intel feed URLs | list(string) | [] |
|
||||
| is_organization_admin | Delegated admin account | bool | false |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| detector_id | GuardDuty detector ID |
|
||||
| detector_arn | GuardDuty detector ARN |
|
||||
| sns_topic_arn | SNS topic for alerts |
|
||||
| export_bucket | S3 bucket for findings |
|
||||
| eventbridge_rule_arn | EventBridge rule ARN |
|
||||
| enabled_features | Map of enabled features |
|
||||
|
||||
## Severity Levels
|
||||
|
||||
| Level | Numeric Range | Example Finding Types |
|
||||
|-------|--------------|----------------------|
|
||||
| LOW | 1.0 - 3.9 | Info gathering, unusual activity |
|
||||
| MEDIUM | 4.0 - 6.9 | Potentially malicious activity |
|
||||
| HIGH | 7.0 - 8.9 | Compromised resources, active threats |
|
||||
| CRITICAL | 9.0+ | Confirmed breaches, exfiltration |
|
||||
|
||||
## Cost Considerations
|
||||
|
||||
- **Base**: Charged per GB of VPC Flow Logs, DNS logs, CloudTrail events
|
||||
- **S3 Protection**: Per S3 event analyzed
|
||||
- **EKS Audit Logs**: Per EKS audit log event
|
||||
- **Malware Protection**: Per GB scanned
|
||||
- **Runtime Monitoring**: Per vCPU-hour monitored
|
||||
- **S3 Export**: Standard S3 storage costs
|
||||
|
||||
See [GuardDuty Pricing](https://aws.amazon.com/guardduty/pricing/) for current rates.
|
||||
643
terraform/modules/guardduty/main.tf
Normal file
643
terraform/modules/guardduty/main.tf
Normal file
@@ -0,0 +1,643 @@
|
||||
################################################################################
|
||||
# GuardDuty Module
|
||||
#
|
||||
# Threat detection with alerting:
|
||||
# - GuardDuty detector with all protection features
|
||||
# - EventBridge rules for finding notifications
|
||||
# - SNS alerts with severity filtering
|
||||
# - S3 export for findings (optional)
|
||||
# - IPSet / ThreatIntelSet integration (optional)
|
||||
# - Lambda-based auto-remediation (optional)
|
||||
#
|
||||
# Usage:
|
||||
# module "guardduty" {
|
||||
# source = "../modules/guardduty"
|
||||
# name = "main-detector"
|
||||
#
|
||||
# enable_sns_alerts = true
|
||||
# alert_email = "security@example.com"
|
||||
#
|
||||
# # Only alert on HIGH and CRITICAL findings
|
||||
# alert_severity_threshold = "HIGH"
|
||||
# }
|
||||
################################################################################
|
||||
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Variables
|
||||
################################################################################
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
description = "Name prefix for GuardDuty resources"
|
||||
}
|
||||
|
||||
variable "enable" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Enable GuardDuty detector"
|
||||
}
|
||||
|
||||
variable "finding_publishing_frequency" {
|
||||
type = string
|
||||
default = "FIFTEEN_MINUTES"
|
||||
description = "Finding publishing frequency"
|
||||
validation {
|
||||
condition = contains(["FIFTEEN_MINUTES", "ONE_HOUR", "SIX_HOURS"], var.finding_publishing_frequency)
|
||||
error_message = "Must be FIFTEEN_MINUTES, ONE_HOUR, or SIX_HOURS."
|
||||
}
|
||||
}
|
||||
|
||||
# Protection Features
|
||||
variable "enable_s3_protection" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Enable S3 data events monitoring"
|
||||
}
|
||||
|
||||
variable "enable_kubernetes_audit" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Enable EKS Kubernetes audit logs"
|
||||
}
|
||||
|
||||
variable "enable_malware_protection" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Enable malware protection for EC2/EBS"
|
||||
}
|
||||
|
||||
variable "enable_rds_login_events" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Enable RDS login activity monitoring"
|
||||
}
|
||||
|
||||
variable "enable_lambda_network_logs" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Enable Lambda network activity monitoring"
|
||||
}
|
||||
|
||||
variable "enable_runtime_monitoring" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Enable runtime monitoring for EC2/ECS/EKS (additional cost)"
|
||||
}
|
||||
|
||||
# SNS Alerting
|
||||
variable "enable_sns_alerts" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Enable SNS alerts for findings"
|
||||
}
|
||||
|
||||
variable "alert_email" {
|
||||
type = string
|
||||
default = ""
|
||||
description = "Email address for finding alerts (creates subscription)"
|
||||
}
|
||||
|
||||
variable "alert_sns_topic_arn" {
|
||||
type = string
|
||||
default = ""
|
||||
description = "Existing SNS topic ARN (created if empty and alerts enabled)"
|
||||
}
|
||||
|
||||
variable "alert_severity_threshold" {
|
||||
type = string
|
||||
default = "MEDIUM"
|
||||
description = "Minimum severity for alerts: LOW, MEDIUM, HIGH, CRITICAL"
|
||||
validation {
|
||||
condition = contains(["LOW", "MEDIUM", "HIGH", "CRITICAL"], var.alert_severity_threshold)
|
||||
error_message = "Must be LOW, MEDIUM, HIGH, or CRITICAL."
|
||||
}
|
||||
}
|
||||
|
||||
# S3 Export
|
||||
variable "enable_s3_export" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Export findings to S3 bucket"
|
||||
}
|
||||
|
||||
variable "export_s3_bucket" {
|
||||
type = string
|
||||
default = ""
|
||||
description = "S3 bucket for findings export (created if empty and export enabled)"
|
||||
}
|
||||
|
||||
variable "export_kms_key_arn" {
|
||||
type = string
|
||||
default = ""
|
||||
description = "KMS key for findings encryption"
|
||||
}
|
||||
|
||||
# Threat Intelligence
|
||||
variable "ipset_cidrs" {
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "Trusted IP CIDRs to whitelist from findings"
|
||||
}
|
||||
|
||||
variable "threat_intel_feed_urls" {
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "URLs of threat intel feeds (must be accessible)"
|
||||
}
|
||||
|
||||
# Organization
|
||||
variable "is_organization_admin" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "This account is the delegated admin for GuardDuty"
|
||||
}
|
||||
|
||||
variable "auto_enable_organization_members" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Auto-enable GuardDuty for new org accounts"
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
type = map(string)
|
||||
default = {}
|
||||
description = "Resource tags"
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Data Sources
|
||||
################################################################################
|
||||
|
||||
data "aws_caller_identity" "current" {}
|
||||
data "aws_region" "current" {}
|
||||
|
||||
locals {
|
||||
# Severity numeric mapping for EventBridge filter
|
||||
severity_map = {
|
||||
LOW = 1.0
|
||||
MEDIUM = 4.0
|
||||
HIGH = 7.0
|
||||
CRITICAL = 8.0
|
||||
}
|
||||
severity_threshold = local.severity_map[var.alert_severity_threshold]
|
||||
|
||||
create_sns_topic = var.enable_sns_alerts && var.alert_sns_topic_arn == ""
|
||||
sns_topic_arn = local.create_sns_topic ? aws_sns_topic.alerts[0].arn : var.alert_sns_topic_arn
|
||||
|
||||
create_export_bucket = var.enable_s3_export && var.export_s3_bucket == ""
|
||||
export_bucket_name = local.create_export_bucket ? aws_s3_bucket.export[0].id : var.export_s3_bucket
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# GuardDuty Detector
|
||||
################################################################################
|
||||
|
||||
resource "aws_guardduty_detector" "main" {
|
||||
count = var.enable ? 1 : 0
|
||||
|
||||
enable = true
|
||||
finding_publishing_frequency = var.finding_publishing_frequency
|
||||
|
||||
datasources {
|
||||
s3_logs {
|
||||
enable = var.enable_s3_protection
|
||||
}
|
||||
kubernetes {
|
||||
audit_logs {
|
||||
enable = var.enable_kubernetes_audit
|
||||
}
|
||||
}
|
||||
malware_protection {
|
||||
scan_ec2_instance_with_findings {
|
||||
ebs_volumes {
|
||||
enable = var.enable_malware_protection
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tags = merge(var.tags, { Name = var.name })
|
||||
}
|
||||
|
||||
# Additional feature configurations (added in AWS provider 5.x)
|
||||
resource "aws_guardduty_detector_feature" "rds_login" {
|
||||
count = var.enable && var.enable_rds_login_events ? 1 : 0
|
||||
|
||||
detector_id = aws_guardduty_detector.main[0].id
|
||||
name = "RDS_LOGIN_EVENTS"
|
||||
status = "ENABLED"
|
||||
}
|
||||
|
||||
resource "aws_guardduty_detector_feature" "lambda_network" {
|
||||
count = var.enable && var.enable_lambda_network_logs ? 1 : 0
|
||||
|
||||
detector_id = aws_guardduty_detector.main[0].id
|
||||
name = "LAMBDA_NETWORK_LOGS"
|
||||
status = "ENABLED"
|
||||
}
|
||||
|
||||
resource "aws_guardduty_detector_feature" "runtime_monitoring" {
|
||||
count = var.enable && var.enable_runtime_monitoring ? 1 : 0
|
||||
|
||||
detector_id = aws_guardduty_detector.main[0].id
|
||||
name = "RUNTIME_MONITORING"
|
||||
status = "ENABLED"
|
||||
|
||||
additional_configuration {
|
||||
name = "EKS_ADDON_MANAGEMENT"
|
||||
status = "ENABLED"
|
||||
}
|
||||
additional_configuration {
|
||||
name = "ECS_FARGATE_AGENT_MANAGEMENT"
|
||||
status = "ENABLED"
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# SNS Topic for Alerts
|
||||
################################################################################
|
||||
|
||||
resource "aws_sns_topic" "alerts" {
|
||||
count = local.create_sns_topic ? 1 : 0
|
||||
|
||||
name = "${var.name}-guardduty-alerts"
|
||||
kms_master_key_id = "alias/aws/sns"
|
||||
|
||||
tags = merge(var.tags, { Name = "${var.name}-guardduty-alerts" })
|
||||
}
|
||||
|
||||
resource "aws_sns_topic_policy" "alerts" {
|
||||
count = local.create_sns_topic ? 1 : 0
|
||||
|
||||
arn = aws_sns_topic.alerts[0].arn
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Sid = "AllowEventBridge"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "events.amazonaws.com"
|
||||
}
|
||||
Action = "sns:Publish"
|
||||
Resource = aws_sns_topic.alerts[0].arn
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_sns_topic_subscription" "email" {
|
||||
count = var.enable_sns_alerts && var.alert_email != "" ? 1 : 0
|
||||
|
||||
topic_arn = local.sns_topic_arn
|
||||
protocol = "email"
|
||||
endpoint = var.alert_email
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# EventBridge Rule for Finding Alerts
|
||||
################################################################################
|
||||
|
||||
resource "aws_cloudwatch_event_rule" "findings" {
|
||||
count = var.enable && var.enable_sns_alerts ? 1 : 0
|
||||
|
||||
name = "${var.name}-guardduty-findings"
|
||||
description = "Route GuardDuty findings to SNS"
|
||||
|
||||
event_pattern = jsonencode({
|
||||
source = ["aws.guardduty"]
|
||||
detail-type = ["GuardDuty Finding"]
|
||||
detail = {
|
||||
severity = [
|
||||
{ numeric = [">=", local.severity_threshold] }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
tags = merge(var.tags, { Name = "${var.name}-guardduty-findings" })
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_event_target" "sns" {
|
||||
count = var.enable && var.enable_sns_alerts ? 1 : 0
|
||||
|
||||
rule = aws_cloudwatch_event_rule.findings[0].name
|
||||
target_id = "sns"
|
||||
arn = local.sns_topic_arn
|
||||
|
||||
input_transformer {
|
||||
input_paths = {
|
||||
severity = "$.detail.severity"
|
||||
region = "$.detail.region"
|
||||
type = "$.detail.type"
|
||||
title = "$.detail.title"
|
||||
description = "$.detail.description"
|
||||
accountId = "$.detail.accountId"
|
||||
findingId = "$.detail.id"
|
||||
}
|
||||
input_template = <<EOF
|
||||
{
|
||||
"subject": "GuardDuty Alert: <type>",
|
||||
"message": "Severity: <severity>\nRegion: <region>\nAccount: <accountId>\n\nTitle: <title>\n\nDescription: <description>\n\nFinding ID: <findingId>\n\nView in console: https://<region>.console.aws.amazon.com/guardduty/home?region=<region>#/findings"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# S3 Export
|
||||
################################################################################
|
||||
|
||||
resource "aws_s3_bucket" "export" {
|
||||
count = local.create_export_bucket ? 1 : 0
|
||||
|
||||
bucket = "${var.name}-guardduty-findings-${data.aws_caller_identity.current.account_id}"
|
||||
force_destroy = false
|
||||
|
||||
tags = merge(var.tags, { Name = "${var.name}-guardduty-findings" })
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_versioning" "export" {
|
||||
count = local.create_export_bucket ? 1 : 0
|
||||
|
||||
bucket = aws_s3_bucket.export[0].id
|
||||
versioning_configuration {
|
||||
status = "Enabled"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_server_side_encryption_configuration" "export" {
|
||||
count = local.create_export_bucket ? 1 : 0
|
||||
|
||||
bucket = aws_s3_bucket.export[0].id
|
||||
|
||||
rule {
|
||||
apply_server_side_encryption_by_default {
|
||||
sse_algorithm = var.export_kms_key_arn != "" ? "aws:kms" : "AES256"
|
||||
kms_master_key_id = var.export_kms_key_arn != "" ? var.export_kms_key_arn : null
|
||||
}
|
||||
bucket_key_enabled = var.export_kms_key_arn != "" ? true : false
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_public_access_block" "export" {
|
||||
count = local.create_export_bucket ? 1 : 0
|
||||
|
||||
bucket = aws_s3_bucket.export[0].id
|
||||
|
||||
block_public_acls = true
|
||||
block_public_policy = true
|
||||
ignore_public_acls = true
|
||||
restrict_public_buckets = true
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_lifecycle_configuration" "export" {
|
||||
count = local.create_export_bucket ? 1 : 0
|
||||
|
||||
bucket = aws_s3_bucket.export[0].id
|
||||
|
||||
rule {
|
||||
id = "archive-findings"
|
||||
status = "Enabled"
|
||||
|
||||
transition {
|
||||
days = 90
|
||||
storage_class = "STANDARD_IA"
|
||||
}
|
||||
|
||||
transition {
|
||||
days = 365
|
||||
storage_class = "GLACIER"
|
||||
}
|
||||
|
||||
expiration {
|
||||
days = 2555 # 7 years for compliance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_s3_bucket_policy" "export" {
|
||||
count = var.enable_s3_export ? 1 : 0
|
||||
|
||||
bucket = local.export_bucket_name
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Sid = "AllowGuardDutyExport"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "guardduty.amazonaws.com"
|
||||
}
|
||||
Action = "s3:PutObject"
|
||||
Resource = "arn:aws:s3:::${local.export_bucket_name}/*"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
Sid = "AllowGuardDutyBucketRead"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "guardduty.amazonaws.com"
|
||||
}
|
||||
Action = "s3:GetBucketLocation"
|
||||
Resource = "arn:aws:s3:::${local.export_bucket_name}"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
# KMS key for GuardDuty export (required)
|
||||
resource "aws_kms_key" "export" {
|
||||
count = var.enable && var.enable_s3_export && var.export_kms_key_arn == "" ? 1 : 0
|
||||
|
||||
description = "${var.name}-guardduty-export"
|
||||
deletion_window_in_days = 7
|
||||
enable_key_rotation = true
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Sid = "AllowRoot"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
|
||||
}
|
||||
Action = "kms:*"
|
||||
Resource = "*"
|
||||
},
|
||||
{
|
||||
Sid = "AllowGuardDuty"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "guardduty.amazonaws.com"
|
||||
}
|
||||
Action = "kms:GenerateDataKey*"
|
||||
Resource = "*"
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
tags = merge(var.tags, { Name = "${var.name}-guardduty-export" })
|
||||
}
|
||||
|
||||
resource "aws_kms_alias" "export" {
|
||||
count = var.enable && var.enable_s3_export && var.export_kms_key_arn == "" ? 1 : 0
|
||||
|
||||
name = "alias/${var.name}-guardduty-export"
|
||||
target_key_id = aws_kms_key.export[0].key_id
|
||||
}
|
||||
|
||||
locals {
|
||||
export_kms_key_arn = var.export_kms_key_arn != "" ? var.export_kms_key_arn : (
|
||||
var.enable_s3_export ? aws_kms_key.export[0].arn : ""
|
||||
)
|
||||
}
|
||||
|
||||
resource "aws_guardduty_publishing_destination" "s3" {
|
||||
count = var.enable && var.enable_s3_export ? 1 : 0
|
||||
|
||||
detector_id = aws_guardduty_detector.main[0].id
|
||||
destination_arn = "arn:aws:s3:::${local.export_bucket_name}"
|
||||
destination_type = "S3"
|
||||
kms_key_arn = local.export_kms_key_arn
|
||||
|
||||
depends_on = [aws_s3_bucket_policy.export, aws_kms_key.export]
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# IP Set (Trusted IPs)
|
||||
################################################################################
|
||||
|
||||
resource "aws_s3_object" "ipset" {
|
||||
count = var.enable && length(var.ipset_cidrs) > 0 ? 1 : 0
|
||||
|
||||
bucket = local.create_export_bucket ? aws_s3_bucket.export[0].id : var.export_s3_bucket
|
||||
key = "guardduty-ipset.txt"
|
||||
content = join("\n", var.ipset_cidrs)
|
||||
}
|
||||
|
||||
resource "aws_guardduty_ipset" "trusted" {
|
||||
count = var.enable && length(var.ipset_cidrs) > 0 ? 1 : 0
|
||||
|
||||
activate = true
|
||||
detector_id = aws_guardduty_detector.main[0].id
|
||||
format = "TXT"
|
||||
location = "s3://${aws_s3_object.ipset[0].bucket}/${aws_s3_object.ipset[0].key}"
|
||||
name = "${var.name}-trusted-ips"
|
||||
|
||||
tags = merge(var.tags, { Name = "${var.name}-trusted-ips" })
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Threat Intel Set
|
||||
################################################################################
|
||||
|
||||
resource "aws_guardduty_threatintelset" "feeds" {
|
||||
for_each = var.enable ? toset(var.threat_intel_feed_urls) : []
|
||||
|
||||
activate = true
|
||||
detector_id = aws_guardduty_detector.main[0].id
|
||||
format = "TXT"
|
||||
location = each.value
|
||||
name = "${var.name}-threat-intel-${md5(each.value)}"
|
||||
|
||||
tags = merge(var.tags, { Name = "${var.name}-threat-intel" })
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Organization Configuration (Delegated Admin)
|
||||
################################################################################
|
||||
|
||||
resource "aws_guardduty_organization_configuration" "main" {
|
||||
count = var.enable && var.is_organization_admin ? 1 : 0
|
||||
|
||||
auto_enable_organization_members = var.auto_enable_organization_members ? "ALL" : "NONE"
|
||||
detector_id = aws_guardduty_detector.main[0].id
|
||||
|
||||
datasources {
|
||||
s3_logs {
|
||||
auto_enable = var.enable_s3_protection
|
||||
}
|
||||
kubernetes {
|
||||
audit_logs {
|
||||
enable = var.enable_kubernetes_audit
|
||||
}
|
||||
}
|
||||
malware_protection {
|
||||
scan_ec2_instance_with_findings {
|
||||
ebs_volumes {
|
||||
auto_enable = var.enable_malware_protection
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Outputs
|
||||
################################################################################
|
||||
|
||||
output "detector_id" {
|
||||
value = var.enable ? aws_guardduty_detector.main[0].id : null
|
||||
description = "GuardDuty detector ID"
|
||||
}
|
||||
|
||||
output "detector_arn" {
|
||||
value = var.enable ? aws_guardduty_detector.main[0].arn : null
|
||||
description = "GuardDuty detector ARN"
|
||||
}
|
||||
|
||||
output "sns_topic_arn" {
|
||||
value = var.enable_sns_alerts ? local.sns_topic_arn : null
|
||||
description = "SNS topic ARN for alerts"
|
||||
}
|
||||
|
||||
output "export_bucket" {
|
||||
value = var.enable_s3_export ? local.export_bucket_name : null
|
||||
description = "S3 bucket for findings export"
|
||||
}
|
||||
|
||||
output "eventbridge_rule_arn" {
|
||||
value = var.enable && var.enable_sns_alerts ? aws_cloudwatch_event_rule.findings[0].arn : null
|
||||
description = "EventBridge rule ARN for findings"
|
||||
}
|
||||
|
||||
output "enabled_features" {
|
||||
value = var.enable ? {
|
||||
s3_protection = var.enable_s3_protection
|
||||
kubernetes_audit = var.enable_kubernetes_audit
|
||||
malware_protection = var.enable_malware_protection
|
||||
rds_login_events = var.enable_rds_login_events
|
||||
lambda_network_logs = var.enable_lambda_network_logs
|
||||
runtime_monitoring = var.enable_runtime_monitoring
|
||||
sns_alerts = var.enable_sns_alerts
|
||||
s3_export = var.enable_s3_export
|
||||
alert_threshold = var.alert_severity_threshold
|
||||
} : null
|
||||
description = "Enabled GuardDuty features"
|
||||
}
|
||||
190
terraform/modules/security-hub/README.md
Normal file
190
terraform/modules/security-hub/README.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# Security Hub Module
|
||||
|
||||
AWS Security Hub for centralized security posture management with alerting and cross-region aggregation.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multiple Standards**: AWS Foundational, CIS v1.4/v3.0, PCI DSS, NIST 800-53
|
||||
- **SNS Alerts**: EventBridge-based alerts with severity filtering
|
||||
- **Cross-Region Aggregation**: Aggregate findings across regions
|
||||
- **Custom Actions**: Define remediation workflow triggers
|
||||
- **Built-in Insights**: Pre-configured finding queries
|
||||
- **Product Integrations**: Inspector, Macie, Detective
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic
|
||||
|
||||
```hcl
|
||||
module "security_hub" {
|
||||
source = "../modules/security-hub"
|
||||
name = "main"
|
||||
|
||||
enable_aws_foundational = true
|
||||
}
|
||||
```
|
||||
|
||||
### Compliance-Focused
|
||||
|
||||
```hcl
|
||||
module "security_hub" {
|
||||
source = "../modules/security-hub"
|
||||
name = "compliance"
|
||||
|
||||
# Standards
|
||||
enable_aws_foundational = true
|
||||
enable_cis_benchmark = true
|
||||
enable_pci_dss = true
|
||||
enable_nist_800_53 = true
|
||||
|
||||
# Disable noisy controls
|
||||
disabled_controls = [
|
||||
"EC2.19", # Default security group
|
||||
"IAM.6", # MFA hardware
|
||||
]
|
||||
|
||||
# Alerting
|
||||
enable_sns_alerts = true
|
||||
alert_email = "security@example.com"
|
||||
alert_severity = ["CRITICAL", "HIGH"]
|
||||
|
||||
tags = {
|
||||
Environment = "production"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cross-Region Aggregator
|
||||
|
||||
```hcl
|
||||
# Deploy in your primary region (e.g., us-east-1)
|
||||
module "security_hub" {
|
||||
source = "../modules/security-hub"
|
||||
name = "aggregator"
|
||||
|
||||
enable_finding_aggregator = true
|
||||
aggregation_regions = [] # All regions
|
||||
|
||||
enable_sns_alerts = true
|
||||
alert_email = "soc@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
### Organization Admin
|
||||
|
||||
```hcl
|
||||
module "security_hub" {
|
||||
source = "../modules/security-hub"
|
||||
name = "org-hub"
|
||||
|
||||
is_organization_admin = true
|
||||
auto_enable_organization_members = true
|
||||
|
||||
enable_aws_foundational = true
|
||||
enable_cis_benchmark = true
|
||||
|
||||
enable_sns_alerts = true
|
||||
alert_email = "security@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
### With Custom Actions
|
||||
|
||||
```hcl
|
||||
module "security_hub" {
|
||||
source = "../modules/security-hub"
|
||||
name = "main"
|
||||
|
||||
custom_actions = [
|
||||
{
|
||||
name = "NotifySlack"
|
||||
identifier = "NotifySlack"
|
||||
description = "Send finding to Slack"
|
||||
},
|
||||
{
|
||||
name = "CreateJiraTicket"
|
||||
identifier = "CreateJira"
|
||||
description = "Create Jira ticket for finding"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Inputs
|
||||
|
||||
| Name | Description | Type | Default |
|
||||
|------|-------------|------|---------|
|
||||
| name | Name prefix for resources | string | - |
|
||||
| enable | Enable Security Hub | bool | true |
|
||||
| auto_enable_controls | Auto-enable new controls | bool | true |
|
||||
| control_finding_generator | SECURITY_CONTROL or STANDARD_CONTROL | string | "SECURITY_CONTROL" |
|
||||
| enable_aws_foundational | AWS Foundational Best Practices | bool | true |
|
||||
| enable_cis_benchmark | CIS Benchmark v1.4 | bool | false |
|
||||
| enable_cis_benchmark_v3 | CIS Benchmark v3.0 | bool | false |
|
||||
| enable_pci_dss | PCI DSS v3.2.1 | bool | false |
|
||||
| enable_nist_800_53 | NIST 800-53 Rev. 5 | bool | false |
|
||||
| disabled_controls | Control IDs to disable | list(string) | [] |
|
||||
| enable_sns_alerts | Enable SNS alerts | bool | false |
|
||||
| alert_email | Email for alerts | string | "" |
|
||||
| alert_severity | Severities to alert | list(string) | ["CRITICAL", "HIGH"] |
|
||||
| enable_finding_aggregator | Cross-region aggregation | bool | false |
|
||||
| aggregation_regions | Regions to aggregate | list(string) | [] |
|
||||
| is_organization_admin | Org admin account | bool | false |
|
||||
| custom_actions | Custom action definitions | list(object) | [] |
|
||||
| enable_inspector | Inspector integration | bool | false |
|
||||
| enable_macie | Macie integration | bool | false |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| hub_arn | Security Hub account ARN |
|
||||
| sns_topic_arn | SNS topic for alerts |
|
||||
| enabled_standards | List of enabled standards |
|
||||
| finding_aggregator_arn | Aggregator ARN |
|
||||
| custom_action_arns | Map of custom action ARNs |
|
||||
| insight_arns | Map of insight ARNs |
|
||||
|
||||
## Built-in Insights
|
||||
|
||||
The module creates these pre-configured insights:
|
||||
|
||||
1. **Critical Findings** - All critical findings grouped by resource type
|
||||
2. **Failed Resources** - Resources with compliance failures
|
||||
3. **Findings by Account** - Finding counts per AWS account
|
||||
|
||||
## Severity Levels
|
||||
|
||||
| Level | Description |
|
||||
|-------|-------------|
|
||||
| CRITICAL | Requires immediate action |
|
||||
| HIGH | High-priority security issue |
|
||||
| MEDIUM | Moderate security concern |
|
||||
| LOW | Minor security issue |
|
||||
| INFORMATIONAL | No security impact |
|
||||
|
||||
## Custom Actions Workflow
|
||||
|
||||
1. Define custom action in Terraform
|
||||
2. Create EventBridge rule targeting the action
|
||||
3. Route to Lambda/Step Functions for remediation
|
||||
|
||||
```hcl
|
||||
resource "aws_cloudwatch_event_rule" "custom_action" {
|
||||
name = "securityhub-notify-slack"
|
||||
|
||||
event_pattern = jsonencode({
|
||||
source = ["aws.securityhub"]
|
||||
detail-type = ["Security Hub Findings - Custom Action"]
|
||||
resources = [module.security_hub.custom_action_arns["NotifySlack"]]
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Cost Considerations
|
||||
|
||||
- **Base**: Per finding ingested
|
||||
- **Standards**: No additional cost beyond base
|
||||
- **Aggregation**: Cross-region data transfer costs
|
||||
|
||||
See [Security Hub Pricing](https://aws.amazon.com/security-hub/pricing/) for current rates.
|
||||
552
terraform/modules/security-hub/main.tf
Normal file
552
terraform/modules/security-hub/main.tf
Normal file
@@ -0,0 +1,552 @@
|
||||
################################################################################
|
||||
# Security Hub Module
|
||||
#
|
||||
# Centralized security posture management:
|
||||
# - Security Hub with standards subscriptions
|
||||
# - Finding aggregation (cross-region)
|
||||
# - SNS alerts for critical findings
|
||||
# - Custom actions for remediation workflows
|
||||
# - Product integrations
|
||||
# - Insight configuration
|
||||
#
|
||||
# Usage:
|
||||
# module "security_hub" {
|
||||
# source = "../modules/security-hub"
|
||||
# name = "main"
|
||||
#
|
||||
# enable_cis_benchmark = true
|
||||
# enable_aws_foundational = true
|
||||
# enable_pci_dss = true
|
||||
#
|
||||
# enable_sns_alerts = true
|
||||
# alert_email = "security@example.com"
|
||||
# }
|
||||
################################################################################
|
||||
|
||||
terraform {
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = ">= 5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Variables
|
||||
################################################################################
|
||||
|
||||
variable "name" {
|
||||
type = string
|
||||
description = "Name prefix for Security Hub resources"
|
||||
}
|
||||
|
||||
variable "enable" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Enable Security Hub"
|
||||
}
|
||||
|
||||
variable "auto_enable_controls" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Auto-enable new controls in standards"
|
||||
}
|
||||
|
||||
variable "control_finding_generator" {
|
||||
type = string
|
||||
default = "SECURITY_CONTROL"
|
||||
description = "Control finding generator: SECURITY_CONTROL or STANDARD_CONTROL"
|
||||
validation {
|
||||
condition = contains(["SECURITY_CONTROL", "STANDARD_CONTROL"], var.control_finding_generator)
|
||||
error_message = "Must be SECURITY_CONTROL or STANDARD_CONTROL."
|
||||
}
|
||||
}
|
||||
|
||||
# Standards
|
||||
variable "enable_aws_foundational" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Enable AWS Foundational Security Best Practices"
|
||||
}
|
||||
|
||||
variable "enable_cis_benchmark" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Enable CIS AWS Foundations Benchmark v1.4"
|
||||
}
|
||||
|
||||
variable "enable_cis_benchmark_v3" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Enable CIS AWS Foundations Benchmark v3.0"
|
||||
}
|
||||
|
||||
variable "enable_pci_dss" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Enable PCI DSS v3.2.1"
|
||||
}
|
||||
|
||||
variable "enable_nist_800_53" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Enable NIST 800-53 Rev. 5"
|
||||
}
|
||||
|
||||
# Disabled Controls
|
||||
variable "disabled_controls" {
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "Control IDs to disable (e.g., 'EC2.19', 'IAM.6')"
|
||||
}
|
||||
|
||||
# SNS Alerting
|
||||
variable "enable_sns_alerts" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Enable SNS alerts for findings"
|
||||
}
|
||||
|
||||
variable "alert_email" {
|
||||
type = string
|
||||
default = ""
|
||||
description = "Email for finding alerts"
|
||||
}
|
||||
|
||||
variable "alert_sns_topic_arn" {
|
||||
type = string
|
||||
default = ""
|
||||
description = "Existing SNS topic ARN (created if empty)"
|
||||
}
|
||||
|
||||
variable "alert_severity" {
|
||||
type = list(string)
|
||||
default = ["CRITICAL", "HIGH"]
|
||||
description = "Severities to alert on"
|
||||
}
|
||||
|
||||
# Cross-Region Aggregation
|
||||
variable "enable_finding_aggregator" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Enable cross-region finding aggregation (run in aggregation region)"
|
||||
}
|
||||
|
||||
variable "aggregation_regions" {
|
||||
type = list(string)
|
||||
default = []
|
||||
description = "Regions to aggregate (empty = all linked regions)"
|
||||
}
|
||||
|
||||
# Organization
|
||||
variable "is_organization_admin" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "This account is the delegated admin"
|
||||
}
|
||||
|
||||
variable "auto_enable_organization_members" {
|
||||
type = bool
|
||||
default = true
|
||||
description = "Auto-enable Security Hub for new org accounts"
|
||||
}
|
||||
|
||||
# Custom Actions
|
||||
variable "custom_actions" {
|
||||
type = list(object({
|
||||
name = string
|
||||
description = string
|
||||
identifier = string
|
||||
}))
|
||||
default = []
|
||||
description = "Custom actions for finding workflows"
|
||||
}
|
||||
|
||||
# Product Integrations
|
||||
variable "enable_inspector" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Enable Amazon Inspector integration"
|
||||
}
|
||||
|
||||
variable "enable_macie" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Enable Amazon Macie integration"
|
||||
}
|
||||
|
||||
variable "enable_detective" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Enable Amazon Detective integration"
|
||||
}
|
||||
|
||||
variable "tags" {
|
||||
type = map(string)
|
||||
default = {}
|
||||
description = "Resource tags"
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Data Sources
|
||||
################################################################################
|
||||
|
||||
data "aws_caller_identity" "current" {}
|
||||
data "aws_region" "current" {}
|
||||
|
||||
locals {
|
||||
create_sns_topic = var.enable_sns_alerts && var.alert_sns_topic_arn == ""
|
||||
sns_topic_arn = local.create_sns_topic ? aws_sns_topic.alerts[0].arn : var.alert_sns_topic_arn
|
||||
|
||||
# Standard ARNs
|
||||
standards = {
|
||||
aws_foundational = "arn:aws:securityhub:${data.aws_region.current.id}::standards/aws-foundational-security-best-practices/v/1.0.0"
|
||||
cis_benchmark = "arn:aws:securityhub:${data.aws_region.current.id}::standards/cis-aws-foundations-benchmark/v/1.4.0"
|
||||
cis_benchmark_v3 = "arn:aws:securityhub:${data.aws_region.current.id}::standards/cis-aws-foundations-benchmark/v/3.0.0"
|
||||
pci_dss = "arn:aws:securityhub:${data.aws_region.current.id}::standards/pci-dss/v/3.2.1"
|
||||
nist_800_53 = "arn:aws:securityhub:${data.aws_region.current.id}::standards/nist-800-53/v/5.0.0"
|
||||
}
|
||||
|
||||
enabled_standards = compact([
|
||||
var.enable_aws_foundational ? local.standards.aws_foundational : "",
|
||||
var.enable_cis_benchmark ? local.standards.cis_benchmark : "",
|
||||
var.enable_cis_benchmark_v3 ? local.standards.cis_benchmark_v3 : "",
|
||||
var.enable_pci_dss ? local.standards.pci_dss : "",
|
||||
var.enable_nist_800_53 ? local.standards.nist_800_53 : "",
|
||||
])
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Security Hub Account
|
||||
################################################################################
|
||||
|
||||
resource "aws_securityhub_account" "main" {
|
||||
count = var.enable ? 1 : 0
|
||||
|
||||
enable_default_standards = false
|
||||
auto_enable_controls = var.auto_enable_controls
|
||||
control_finding_generator = var.control_finding_generator
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Standards Subscriptions
|
||||
################################################################################
|
||||
|
||||
resource "aws_securityhub_standards_subscription" "standards" {
|
||||
for_each = var.enable ? toset(local.enabled_standards) : []
|
||||
|
||||
standards_arn = each.value
|
||||
|
||||
depends_on = [aws_securityhub_account.main]
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Disabled Controls
|
||||
################################################################################
|
||||
|
||||
resource "aws_securityhub_standards_control" "disabled" {
|
||||
for_each = var.enable ? toset(var.disabled_controls) : []
|
||||
|
||||
standards_control_arn = "arn:aws:securityhub:${data.aws_region.current.id}:${data.aws_caller_identity.current.account_id}:control/${each.value}"
|
||||
control_status = "DISABLED"
|
||||
disabled_reason = "Disabled via Terraform"
|
||||
|
||||
depends_on = [aws_securityhub_standards_subscription.standards]
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# SNS Topic for Alerts
|
||||
################################################################################
|
||||
|
||||
resource "aws_sns_topic" "alerts" {
|
||||
count = local.create_sns_topic ? 1 : 0
|
||||
|
||||
name = "${var.name}-securityhub-alerts"
|
||||
kms_master_key_id = "alias/aws/sns"
|
||||
|
||||
tags = merge(var.tags, { Name = "${var.name}-securityhub-alerts" })
|
||||
}
|
||||
|
||||
resource "aws_sns_topic_policy" "alerts" {
|
||||
count = local.create_sns_topic ? 1 : 0
|
||||
|
||||
arn = aws_sns_topic.alerts[0].arn
|
||||
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Sid = "AllowEventBridge"
|
||||
Effect = "Allow"
|
||||
Principal = {
|
||||
Service = "events.amazonaws.com"
|
||||
}
|
||||
Action = "sns:Publish"
|
||||
Resource = aws_sns_topic.alerts[0].arn
|
||||
}
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_sns_topic_subscription" "email" {
|
||||
count = var.enable_sns_alerts && var.alert_email != "" ? 1 : 0
|
||||
|
||||
topic_arn = local.sns_topic_arn
|
||||
protocol = "email"
|
||||
endpoint = var.alert_email
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# EventBridge Rule for Finding Alerts
|
||||
################################################################################
|
||||
|
||||
resource "aws_cloudwatch_event_rule" "findings" {
|
||||
count = var.enable && var.enable_sns_alerts ? 1 : 0
|
||||
|
||||
name = "${var.name}-securityhub-findings"
|
||||
description = "Route Security Hub findings to SNS"
|
||||
|
||||
event_pattern = jsonencode({
|
||||
source = ["aws.securityhub"]
|
||||
detail-type = ["Security Hub Findings - Imported"]
|
||||
detail = {
|
||||
findings = {
|
||||
Severity = {
|
||||
Label = var.alert_severity
|
||||
}
|
||||
Workflow = {
|
||||
Status = ["NEW"]
|
||||
}
|
||||
RecordState = ["ACTIVE"]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
tags = merge(var.tags, { Name = "${var.name}-securityhub-findings" })
|
||||
}
|
||||
|
||||
resource "aws_cloudwatch_event_target" "sns" {
|
||||
count = var.enable && var.enable_sns_alerts ? 1 : 0
|
||||
|
||||
rule = aws_cloudwatch_event_rule.findings[0].name
|
||||
target_id = "sns"
|
||||
arn = local.sns_topic_arn
|
||||
|
||||
input_transformer {
|
||||
input_paths = {
|
||||
severity = "$.detail.findings[0].Severity.Label"
|
||||
title = "$.detail.findings[0].Title"
|
||||
description = "$.detail.findings[0].Description"
|
||||
accountId = "$.detail.findings[0].AwsAccountId"
|
||||
region = "$.detail.findings[0].Region"
|
||||
resourceId = "$.detail.findings[0].Resources[0].Id"
|
||||
standard = "$.detail.findings[0].GeneratorId"
|
||||
}
|
||||
input_template = <<EOF
|
||||
{
|
||||
"subject": "Security Hub [<severity>]: <title>",
|
||||
"message": "Severity: <severity>\nAccount: <accountId>\nRegion: <region>\n\nTitle: <title>\n\nDescription: <description>\n\nResource: <resourceId>\n\nStandard: <standard>\n\nView in console: https://<region>.console.aws.amazon.com/securityhub/home?region=<region>#/findings"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Finding Aggregator (Cross-Region)
|
||||
################################################################################
|
||||
|
||||
resource "aws_securityhub_finding_aggregator" "main" {
|
||||
count = var.enable && var.enable_finding_aggregator ? 1 : 0
|
||||
|
||||
linking_mode = length(var.aggregation_regions) > 0 ? "SPECIFIED_REGIONS" : "ALL_REGIONS"
|
||||
specified_regions = length(var.aggregation_regions) > 0 ? var.aggregation_regions : null
|
||||
|
||||
depends_on = [aws_securityhub_account.main]
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Organization Configuration
|
||||
################################################################################
|
||||
|
||||
resource "aws_securityhub_organization_configuration" "main" {
|
||||
count = var.enable && var.is_organization_admin ? 1 : 0
|
||||
|
||||
auto_enable = var.auto_enable_organization_members
|
||||
auto_enable_standards = var.auto_enable_organization_members ? "DEFAULT" : "NONE"
|
||||
|
||||
depends_on = [aws_securityhub_account.main]
|
||||
}
|
||||
|
||||
resource "aws_securityhub_organization_admin_account" "main" {
|
||||
count = var.enable && var.is_organization_admin ? 1 : 0
|
||||
|
||||
admin_account_id = data.aws_caller_identity.current.account_id
|
||||
|
||||
depends_on = [aws_securityhub_account.main]
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Custom Actions
|
||||
################################################################################
|
||||
|
||||
resource "aws_securityhub_action_target" "custom" {
|
||||
for_each = var.enable ? { for a in var.custom_actions : a.identifier => a } : {}
|
||||
|
||||
name = each.value.name
|
||||
identifier = each.value.identifier
|
||||
description = each.value.description
|
||||
|
||||
depends_on = [aws_securityhub_account.main]
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Product Integrations
|
||||
################################################################################
|
||||
|
||||
resource "aws_securityhub_product_subscription" "inspector" {
|
||||
count = var.enable && var.enable_inspector ? 1 : 0
|
||||
|
||||
product_arn = "arn:aws:securityhub:${data.aws_region.current.id}::product/aws/inspector"
|
||||
|
||||
depends_on = [aws_securityhub_account.main]
|
||||
}
|
||||
|
||||
resource "aws_securityhub_product_subscription" "macie" {
|
||||
count = var.enable && var.enable_macie ? 1 : 0
|
||||
|
||||
product_arn = "arn:aws:securityhub:${data.aws_region.current.id}::product/aws/macie"
|
||||
|
||||
depends_on = [aws_securityhub_account.main]
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Insights (Custom Finding Queries)
|
||||
################################################################################
|
||||
|
||||
resource "aws_securityhub_insight" "critical_findings" {
|
||||
count = var.enable ? 1 : 0
|
||||
|
||||
name = "${var.name}-critical-findings"
|
||||
|
||||
filters {
|
||||
severity_label {
|
||||
comparison = "EQUALS"
|
||||
value = "CRITICAL"
|
||||
}
|
||||
workflow_status {
|
||||
comparison = "EQUALS"
|
||||
value = "NEW"
|
||||
}
|
||||
record_state {
|
||||
comparison = "EQUALS"
|
||||
value = "ACTIVE"
|
||||
}
|
||||
}
|
||||
|
||||
group_by_attribute = "ResourceType"
|
||||
|
||||
depends_on = [aws_securityhub_account.main]
|
||||
}
|
||||
|
||||
resource "aws_securityhub_insight" "failed_resources" {
|
||||
count = var.enable ? 1 : 0
|
||||
|
||||
name = "${var.name}-failed-resources"
|
||||
|
||||
filters {
|
||||
compliance_status {
|
||||
comparison = "EQUALS"
|
||||
value = "FAILED"
|
||||
}
|
||||
record_state {
|
||||
comparison = "EQUALS"
|
||||
value = "ACTIVE"
|
||||
}
|
||||
}
|
||||
|
||||
group_by_attribute = "ResourceId"
|
||||
|
||||
depends_on = [aws_securityhub_account.main]
|
||||
}
|
||||
|
||||
resource "aws_securityhub_insight" "findings_by_account" {
|
||||
count = var.enable ? 1 : 0
|
||||
|
||||
name = "${var.name}-findings-by-account"
|
||||
|
||||
filters {
|
||||
severity_label {
|
||||
comparison = "NOT_EQUALS"
|
||||
value = "INFORMATIONAL"
|
||||
}
|
||||
workflow_status {
|
||||
comparison = "EQUALS"
|
||||
value = "NEW"
|
||||
}
|
||||
record_state {
|
||||
comparison = "EQUALS"
|
||||
value = "ACTIVE"
|
||||
}
|
||||
}
|
||||
|
||||
group_by_attribute = "AwsAccountId"
|
||||
|
||||
depends_on = [aws_securityhub_account.main]
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Outputs
|
||||
################################################################################
|
||||
|
||||
output "hub_arn" {
|
||||
value = var.enable ? aws_securityhub_account.main[0].arn : null
|
||||
description = "Security Hub account ARN"
|
||||
}
|
||||
|
||||
output "sns_topic_arn" {
|
||||
value = var.enable_sns_alerts ? local.sns_topic_arn : null
|
||||
description = "SNS topic for alerts"
|
||||
}
|
||||
|
||||
output "enabled_standards" {
|
||||
value = local.enabled_standards
|
||||
description = "List of enabled standards ARNs"
|
||||
}
|
||||
|
||||
output "finding_aggregator_id" {
|
||||
value = var.enable && var.enable_finding_aggregator ? aws_securityhub_finding_aggregator.main[0].id : null
|
||||
description = "Finding aggregator ID"
|
||||
}
|
||||
|
||||
output "custom_action_arns" {
|
||||
value = var.enable ? {
|
||||
for k, v in aws_securityhub_action_target.custom : k => v.arn
|
||||
} : {}
|
||||
description = "Custom action ARNs"
|
||||
}
|
||||
|
||||
output "insight_arns" {
|
||||
value = var.enable ? {
|
||||
critical_findings = aws_securityhub_insight.critical_findings[0].arn
|
||||
failed_resources = aws_securityhub_insight.failed_resources[0].arn
|
||||
by_account = aws_securityhub_insight.findings_by_account[0].arn
|
||||
} : null
|
||||
description = "Security Hub insight ARNs"
|
||||
}
|
||||
|
||||
output "enabled_features" {
|
||||
value = var.enable ? {
|
||||
aws_foundational = var.enable_aws_foundational
|
||||
cis_benchmark = var.enable_cis_benchmark
|
||||
cis_benchmark_v3 = var.enable_cis_benchmark_v3
|
||||
pci_dss = var.enable_pci_dss
|
||||
nist_800_53 = var.enable_nist_800_53
|
||||
sns_alerts = var.enable_sns_alerts
|
||||
finding_aggregator = var.enable_finding_aggregator
|
||||
inspector = var.enable_inspector
|
||||
macie = var.enable_macie
|
||||
detective = var.enable_detective
|
||||
} : null
|
||||
description = "Enabled Security Hub features"
|
||||
}
|
||||
Reference in New Issue
Block a user