Skip to content
Merged
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
9 changes: 6 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,13 +229,15 @@ terraform/state/
## Alert Routing

```
EventBridge ──► javabin-security SNS ──► slack-alert Lambda ──► #javabin-infra-alerts
GuardDuty ──► Security Hub ──► SNS ──► slack-alert Lambda ──► #javabin-infra-alerts
EventBridge ──► javabin-security SNS ──► slack-alert Lambda:
Security Hub findings (NEW only) ──► #platform-security-alerts
GuardDuty findings ──► #platform-security-alerts
IAM / resource / login events ──► #javabin-infra-alerts
Cost Anomaly ──► javabin-alerts SNS ──► slack-alert Lambda ──► #javabin-cost-alerts

Scheduled:
Monday 08:00 UTC ──► cost-report ──► #javabin-cost-alerts
Monday 08:00 UTC ──► securityhub-summary ──► #javabin-infra-alerts
Monday 08:00 UTC ──► securityhub-summary ──► #platform-security-alerts
Daily 08:00 UTC ──► daily-cost-check ──► #javabin-cost-alerts (only on spikes)

EventBridge (Create/Run) ──► compliance-reporter (report to Slack, no auto-fix)
Expand All @@ -252,6 +254,7 @@ All parameters are in `eu-central-1`. Use `--profile javabin --region eu-central
| Path | Type | Used By |
|------|------|---------|
| `/javabin/slack/platform-resource-alerts-webhook` | SecureString | slack-alert, compliance-reporter, platform-ci |
| `/javabin/slack/platform-security-alerts-webhook` | SecureString | slack-alert (Security Hub + GuardDuty), securityhub-summary |
| `/javabin/slack/platform-cost-alerts-webhook` | String | slack-alert (cost), cost-report, daily-cost-check |
| `/javabin/slack/platform-override-alerts-webhook` | SecureString | tf-apply (block notification), approve-override |
| `/javabin/platform/google-admin-sa` | SecureString | team-provisioner (GCP SA JSON key, domain-wide delegation) |
Expand Down
29 changes: 27 additions & 2 deletions terraform/lambda-src/slack_alert/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# SSM parameter names passed via environment
INFRA_WEBHOOK_PARAM = os.environ["INFRA_WEBHOOK_PARAM"]
COST_WEBHOOK_PARAM = os.environ["COST_WEBHOOK_PARAM"]
SECURITY_WEBHOOK_PARAM = os.environ.get("SECURITY_WEBHOOK_PARAM", "")
SECURITY_TOPIC_ARN = os.environ["SECURITY_TOPIC_ARN"]
PROJECT_PREFIX = os.environ.get("PROJECT_PREFIX", "javabin")
GITHUB_ORG_URL = os.environ.get("GITHUB_ORG_URL", "https://github.com/javaBin")
Expand Down Expand Up @@ -932,6 +933,16 @@ def format_guardduty_finding(parsed):
region = parsed.get("region", detail.get("region", "unknown"))
account = parsed.get("account", detail.get("accountId", "unknown"))

# Dedup root credential usage — GuardDuty fires per API call (ConsoleLogin,
# Search, GetIdentityMetadata, etc.). One alert per hour is enough.
if finding_type == "Policy:IAMUser/RootCredentialUsage":
hour_key = datetime.now(timezone.utc).strftime("%Y-%m-%d-%H")
dedup_key = f"guardduty:root:{hour_key}"
if is_finding_already_alerted(dedup_key):
logger.info("GuardDuty root credential finding suppressed (hourly dedup): %s", title)
return None
record_finding_alert(dedup_key)

# Suppress findings for resources recently managed by CI
resource = detail.get("resource", {})
for s3_detail in resource.get("S3BucketDetails", []):
Expand Down Expand Up @@ -1293,7 +1304,7 @@ def format_securityhub_summary():

def summary_handler(event, context):
"""Lambda handler for the weekly Security Hub summary."""
webhook_url = _get_webhook(INFRA_WEBHOOK_PARAM)
webhook_url = _get_webhook(SECURITY_WEBHOOK_PARAM if SECURITY_WEBHOOK_PARAM else INFRA_WEBHOOK_PARAM)
try:
result = format_securityhub_summary()
if result:
Expand All @@ -1310,21 +1321,35 @@ def summary_handler(event, context):
# ---------------------------------------------------------------------------
# Main handler
# ---------------------------------------------------------------------------
def _is_security_finding(parsed):
"""Check if the event is a Security Hub or GuardDuty finding."""
detail_type = parsed.get("detail-type", "")
return detail_type in (
"Security Hub Findings - Imported",
"GuardDuty Finding",
)


def handler(event, context):
for record in event["Records"]:
sns_message = record["Sns"]
topic_arn = sns_message.get("TopicArn", "")
subject = sns_message.get("Subject", "AWS Alert")
raw_message = sns_message["Message"]

# Route to correct webhook based on SNS topic
# Default webhook based on SNS topic
if topic_arn == SECURITY_TOPIC_ARN:
webhook_url = _get_webhook(INFRA_WEBHOOK_PARAM)
else:
webhook_url = _get_webhook(COST_WEBHOOK_PARAM)

try:
parsed = json.loads(raw_message)
# Route Security Hub + GuardDuty findings to dedicated security channel
if (topic_arn == SECURITY_TOPIC_ARN
and SECURITY_WEBHOOK_PARAM
and _is_security_finding(parsed)):
webhook_url = _get_webhook(SECURITY_WEBHOOK_PARAM)
result = format_structured_alert(subject, parsed)
except (json.JSONDecodeError, TypeError):
result = format_plain_alert(subject, raw_message)
Expand Down
30 changes: 16 additions & 14 deletions terraform/platform/lambdas/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -773,13 +773,14 @@ resource "aws_lambda_function" "slack_alert" {

environment {
variables = {
INFRA_WEBHOOK_PARAM = "/javabin/slack/platform-resource-alerts-webhook"
COST_WEBHOOK_PARAM = "/javabin/slack/platform-cost-alerts-webhook"
SECURITY_TOPIC_ARN = var.security_topic_arn
PROJECT_PREFIX = var.project
GITHUB_ORG_URL = local.github_org_url
DEPLOY_REGION = var.region
DEDUP_TABLE_NAME = var.alert_dedup_table_name
INFRA_WEBHOOK_PARAM = "/javabin/slack/platform-resource-alerts-webhook"
COST_WEBHOOK_PARAM = "/javabin/slack/platform-cost-alerts-webhook"
SECURITY_WEBHOOK_PARAM = "/javabin/slack/platform-security-alerts-webhook"
SECURITY_TOPIC_ARN = var.security_topic_arn
PROJECT_PREFIX = var.project
GITHUB_ORG_URL = local.github_org_url
DEPLOY_REGION = var.region
DEDUP_TABLE_NAME = var.alert_dedup_table_name
}
}
}
Expand Down Expand Up @@ -1349,13 +1350,14 @@ resource "aws_lambda_function" "securityhub_summary" {

environment {
variables = {
INFRA_WEBHOOK_PARAM = "/javabin/slack/platform-resource-alerts-webhook"
COST_WEBHOOK_PARAM = "/javabin/slack/platform-cost-alerts-webhook"
SECURITY_TOPIC_ARN = var.security_topic_arn
PROJECT_PREFIX = var.project
GITHUB_ORG_URL = local.github_org_url
DEPLOY_REGION = var.region
DEDUP_TABLE_NAME = var.alert_dedup_table_name
INFRA_WEBHOOK_PARAM = "/javabin/slack/platform-resource-alerts-webhook"
COST_WEBHOOK_PARAM = "/javabin/slack/platform-cost-alerts-webhook"
SECURITY_WEBHOOK_PARAM = "/javabin/slack/platform-security-alerts-webhook"
SECURITY_TOPIC_ARN = var.security_topic_arn
PROJECT_PREFIX = var.project
GITHUB_ORG_URL = local.github_org_url
DEPLOY_REGION = var.region
DEDUP_TABLE_NAME = var.alert_dedup_table_name
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions terraform/platform/monitoring/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,9 @@ resource "aws_cloudwatch_event_rule" "securityhub_findings" {
Severity = {
Label = ["HIGH", "CRITICAL"]
}
Workflow = {
Status = ["NEW"]
}
}
}
})
Expand Down