Skip to content

chore(monitoring): tune GuardDuty and Security Hub to verified-benign patterns - #164

Open
Alexanderamiri wants to merge 2 commits into
mainfrom
chore/sec-detection-tuning
Open

Alexanderamiri wants to merge 2 commits into
mainfrom
chore/sec-detection-tuning

Conversation

@Alexanderamiri

@Alexanderamiri Alexanderamiri commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Why

Security Hub and GuardDuty keep sending findings to javabin-security that the audit verified as benign or as accepted risk. They drown out the real signal:

  • GuardDuty Impact:IAMUser/AnomalousBehavior (sev 8): javabin-team-provisioner calls cognito-idp:UpdateGroup during its scheduled group sync. CloudTrail confirms the caller is the Lambda.
  • GuardDuty CredentialAccess:IAMUser/AnomalousBehavior (sev 5) on team SSO ssm:GetParameter is deliberately NOT filtered. The team permission set is Action "*" scoped by StringEqualsIfExists aws:ResourceTag/team, so it does not limit reads to the team's path, and this is the finding a stolen SSO session produces. One of the two live findings (jzwatch) came from Hungary. A comment in detection-tuning.tf and the docs records why.
  • CIS 1.2.0 was enabled by accident: aws_securityhub_account defaults enable_default_standards to true. Its 14 CloudWatch.* metric-filter controls need CloudTrail to send logs to CloudWatch Logs, which the platform does not do (cost).
  • FSBP controls that do not apply: EKS (none), Macie, interface endpoints, VPC block-public-access, and Inspector EC2/Lambda code scanning.
  • Accepted findings: dbmoresleep is public on purpose (RDS.2, RDS.46). The platform ALB's port-80 listener only redirects to HTTPS (ELB.18).

What

New monitoring/detection-tuning.tf. Every entry states its reason in code.

GuardDuty ARCHIVE filters

  • team-provisioner-cognito-sync: matches that finding type, that principal and cognito-idp only.
  • Other anomaly types for this principal still alert.

Security Hub controls disabled, each with updated_reason

  • CIS 1.2.0: CloudWatch.1-14 and CloudTrail.5.
  • FSBP: CloudTrail.5, GuardDuty.7, Macie.1, EC2.10/55/56/57/58/60, EC2.172, Inspector.1 and Inspector.3.

Security Hub automation rules (set to SUPPRESSED with a note)

  • RDS.2 and RDS.46 on dbmoresleep. EC2.18 and EC2.19 on its security group stay open on purpose, because narrowing that group is still an open audit item.
  • ELB.18 on the platform ALB's port-80 listener. The new ingress output http_listener_arn is passed into monitoring for this.

Docs: a new "Detection tuning" section in docs/platform-modules.md lists every suppression and its reason.

The RDS MaliciousIPCaller findings on dbmoresleep are not suppressed. They are real signal.

How verified

  • terraform fmt -recursive -check and terraform validate pass. Pytest passes (107).
  • Every control ID exists in its standard: checked with list-security-control-definitions for both enabled standards.
  • There are no existing automation rules or GuardDuty filters to conflict with.
  • The live GuardDuty findings match the filter fields exactly:
    • resource.accessKeyDetails.userName = javabin-team-provisioner, service cognito-idp.amazonaws.com, api UpdateGroup.
  • Expected plan:
    • Creates: 1 GuardDuty filter, 27 control associations and 2 automation rules.
    • No changes to existing resources beyond the new output and variable wiring.

Review fixes

  • Dropped the team-sso-own-ssm-params filter: its justification about the permission set was false, and it would archive the stolen-SSO-session signal regardless of source country.

Follow-ups

  • Merge after feat(monitoring): enable account security guardrails #163 and rebase: both edit the monitoring section of docs/platform-modules.md.

  • GuardDuty filters only apply to new findings. After apply, archive the existing provisioner finding once in the console. Confirm the two team-SSO GetParameter findings with their users (jzwatch: Hungary, 2026-10-24 10:19 UTC) before archiving them.

  • Re-enable the matching controls if CloudTrail later goes to CloudWatch Logs, or if VPC endpoints or Macie are adopted.

🤖 Generated with Claude Code

… patterns

Archive filters for the team-provisioner Cognito sync and team SSO roles
reading their own SSM params; disable inapplicable CIS/FSBP controls with
reasons; suppress accepted RDS.2/RDS.46 (dbmoresleep) and ELB.18
(redirect-only listener).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@Alexanderamiri
Alexanderamiri requested a review from a team as a code owner October 24, 2026 13:38
@github-actions

Copy link
Copy Markdown

Terraform Plan

🚧 Changes detected — Plan: 31 to add, 0 to change, 0 to destroy.

Plan output
Acquiring state lock. This may take a few moments...
aws_ssm_parameter.atlassian_org_id: Refreshing state... [id=/javabin/platform/atlassian-org-id]
aws_ssm_parameter.atlassian_api_key: Refreshing state... [id=/javabin/platform/atlassian-api-key]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.monitoring.aws_guardduty_filter.team_provisioner_cognito_sync will be created
  + resource "aws_guardduty_filter" "team_provisioner_cognito_sync" {
      + action      = "ARCHIVE"
      + arn         = (known after apply)
      + description = "Benign: team-provisioner Lambda syncs Cognito groups"
      + detector_id = "f1df02cf279e4b5986ce1e9bcb3af9c5"
      + id          = (known after apply)
      + name        = "team-provisioner-cognito-sync"
      + rank        = 1
      + tags_all    = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/platform"
          + "service"     = "platform"
          + "team"        = "platform"
        }

      + finding_criteria {
          + criterion {
              + equals     = [
                  + "Impact:IAMUser/AnomalousBehavior",
                ]
              + field      = "type"
              + not_equals = []
            }
          + criterion {
              + equals     = [
                  + "cognito-idp.amazonaws.com",
                ]
              + field      = "service.action.awsApiCallAction.serviceName"
              + not_equals = []
            }
          + criterion {
              + equals     = [
                  + "javabin-team-provisioner",
                ]
              + field      = "resource.accessKeyDetails.userName"
              + not_equals = []
            }
        }
    }

  # module.monitoring.aws_guardduty_filter.team_sso_own_ssm_params[0] will be created
  + resource "aws_guardduty_filter" "team_sso_own_ssm_params" {
      + action      = "ARCHIVE"
      + arn         = (known after apply)
      + description = "Benign: team SSO roles read SSM params under their own team path"
      + detector_id = "f1df02cf279e4b5986ce1e9bcb3af9c5"
      + id          = (known after apply)
      + name        = "team-sso-own-ssm-params"
      + rank        = 2
      + tags_all    = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/platform"
          + "service"     = "platform"
          + "team"        = "platform"
        }

      + finding_criteria {
          + criterion {
              + equals     = [
                  + "AWSReservedSSO_javabin-team-cupcake_86d8ba0713e502cf",
                  + "AWSReservedSSO_javabin-team-jzwatch_9f6d3f1931f4d26d",
                  + "AWSReservedSSO_javabin-team-platform_8a181f2e7ee4d33d",
                  + "AWSReservedSSO_javabin-team-testteam_e064ebd49a2bd630",
                ]
              + field      = "resource.accessKeyDetails.userName"
              + not_equals = []
            }
          + criterion {
              + equals     = [
                  + "CredentialAccess:IAMUser/AnomalousBehavior",
                ]
              + field      = "type"
              + not_equals = []
            }
          + criterion {
              + equals     = [
                  + "GetParameter",
                ]
              + field      = "service.action.awsApiCallAction.api"
              + not_equals = []
            }
        }
    }

  # module.monitoring.aws_securityhub_automation_rule.alb_http_redirect will be created
  + resource "aws_securityhub_automation_rule" "alb_http_redirect" {
      + arn         = (known after apply)
      + description = "The platform ALB port-80 listener only redirects to HTTPS."
      + id          = (known after apply)
      + is_terminal = false
      + rule_name   = "javabin-accepted-alb-http-redirect"
      + rule_order  = 2
      + rule_status = "ENABLED"
      + tags_all    = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/platform"
          + "service"     = "platform"
          + "team"        = "platform"
        }

      + actions {
          + type = "FINDING_FIELDS_UPDATE"

          + finding_fields_update {
              + note {
                  + text       = "False positive: redirect-only listener (HTTP 301 to HTTPS)."
                  + updated_by = "javabin-platform-terraform"
                }
              + workflow {
                  + status = "SUPPRESSED"
                }
            }
        }

      + criteria {
          + compliance_security_control_id {
              + comparison = "EQUALS"
              + value      = "ELB.18"
            }
          + resource_id {
              + comparison = "EQUALS"
              + value      = "arn:aws:elasticloadbalancing:eu-central-1:553637109631:listener/app/javabin-platform-alb/bec1dd43ab8341b9/1d92e19ae75aa59b"
            }
        }
    }

  # module.monitoring.aws_securityhub_automation_rule.moresleep_public_rds will be created
  + resource "aws_securityhub_automation_rule" "moresleep_public_rds" {
      + arn         = (known after apply)
      + description = "dbmoresleep is intentionally publicly accessible (accepted risk)."
      + id          = (known after apply)
      + is_terminal = false
      + rule_name   = "javabin-accepted-moresleep-public-rds"
      + rule_order  = 1
      + rule_status = "ENABLED"
      + tags_all    = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/platform"
          + "service"     = "platform"
          + "team"        = "platform"
        }

      + actions {
          + type = "FINDING_FIELDS_UPDATE"

          + finding_fields_update {
              + note {
                  + text       = "Accepted risk: dbmoresleep is a public endpoint by design. Security group narrowing is tracked in the security audit (H3)."
                  + updated_by = "javabin-platform-terraform"
                }
              + workflow {
                  + status = "SUPPRESSED"
                }
            }
        }

      + criteria {
          + compliance_security_control_id {
              + comparison = "EQUALS"
              + value      = "RDS.2"
            }
          + compliance_security_control_id {
              + comparison = "EQUALS"
              + value      = "RDS.46"
            }
          + resource_id {
              + comparison = "EQUALS"
              + value      = "arn:aws:rds:eu-central-1:553637109631:db:dbmoresleep"
            }
        }
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudTrail.5"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudTrail.5"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.1"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.1"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.10"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.10"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.11"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.11"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.12"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.12"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.13"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.13"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.14"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.14"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.2"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.2"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.3"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.3"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.4"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.4"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.5"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.5"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.6"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.6"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.7"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.7"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.8"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.8"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.9"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.9"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/CloudTrail.5"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudTrail.5"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/EC2.10"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "EC2.10"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "No VPC interface endpoints (~$15/month each). AWS API traffic uses the NAT gateway over TLS."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/EC2.172"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "EC2.172"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "VPC block-public-access cannot be on: the public ALB and NAT gateway need the internet gateway."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/EC2.55"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "EC2.55"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "No VPC interface endpoints (~$15/month each). AWS API traffic uses the NAT gateway over TLS."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/EC2.56"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "EC2.56"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "No VPC interface endpoints (~$15/month each). AWS API traffic uses the NAT gateway over TLS."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/EC2.57"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "EC2.57"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "No VPC interface endpoints (~$15/month each). AWS API traffic uses the NAT gateway over TLS."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/EC2.58"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "EC2.58"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "No VPC interface endpoints (~$15/month each). AWS API traffic uses the NAT gateway over TLS."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/EC2.60"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "EC2.60"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "No VPC interface endpoints (~$15/month each). AWS API traffic uses the NAT gateway over TLS."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/GuardDuty.7"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "GuardDuty.7"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "No EKS clusters in the account."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/Inspector.1"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "Inspector.1"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "Inspector EC2 scanning off: only legacy Elastic Beanstalk instances (not SSM-managed), being migrated to ECS."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/Inspector.3"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "Inspector.3"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "Inspector Lambda code scanning off (cost). Lambda package scanning (Inspector.4) is on."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/Macie.1"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "Macie.1"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "Macie not adopted: cost not justified for current S3 data. Revisit if PII is stored in S3."
    }

Plan: 31 to add, 0 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan"

LLM Review

Risk: 🟢 LOW

Plan creates 31 monitoring/Security Hub/GuardDuty configuration resources in module.monitoring with no changes or destroys; it suppresses some findings and disables certain security controls, but none of the changes are dangerous on their own.

  • ✅ [routine] Plan is purely additive: 31 to add, 0 to change, 0 to destroy. No existing infrastructure is modified or replaced.
  • 🔒 [security] Four Security Hub automation rules suppress findings: RDS.2/RDS.46 for dbmoresleep (public RDS endpoint, documented as accepted risk pending security audit item H3), ELB.18 on the ALB port-80 listener (redirect-only, so a reasonable false positive). Suppressing the public RDS finding hides a real exposure, so the accepted-risk decision should be tracked and revisited.
  • 🔒 [security] About 30 Security Hub controls are disabled across CIS 1.2.0 and FSBP (e.g., CloudTrail.5, CloudWatch.1-14, EC2.10/55-58/60/172, Inspector.1/3, Macie.1, GuardDuty.7). Each has a documented rationale (cost, no EKS, no VPC endpoints), and the CloudWatch/CloudTrail rationale relies on EventBridge and GuardDuty coverage, so it's worth confirming those rules actually exist and work.
  • 🔒 [security] Two GuardDuty filters are set to ARCHIVE findings: the team-provisioner Cognito sync and team SSO roles reading their own SSM parameter paths. Both are narrowly scoped (specific user/type/API), so they're low risk, but archiving hides findings and the SSO filter's CredentialAccess:IAMUser/AnomalousBehavior match should be watched.
  • 💰 [cost] No new billable resources are created by this plan. The Security Hub control disables and GuardDuty filters reduce or avoid spend (the notes cite avoided VPC endpoint and CloudTrail-to-CloudWatch Logs costs).

The team permission set does not limit SSM reads to the team's path, and
the archived finding type is what stolen SSO credentials produce. One of
the two live findings came from Hungary. Leave it alerting.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@github-actions

Copy link
Copy Markdown

Terraform Plan

🚧 Changes detected — Plan: 30 to add, 0 to change, 0 to destroy.

Plan output
aws_ssm_parameter.atlassian_api_key: Refreshing state... [id=/javabin/platform/atlassian-api-key]
aws_ssm_parameter.atlassian_org_id: Refreshing state... [id=/javabin/platform/atlassian-org-id]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # module.monitoring.aws_guardduty_filter.team_provisioner_cognito_sync will be created
  + resource "aws_guardduty_filter" "team_provisioner_cognito_sync" {
      + action      = "ARCHIVE"
      + arn         = (known after apply)
      + description = "Benign: team-provisioner Lambda syncs Cognito groups"
      + detector_id = "f1df02cf279e4b5986ce1e9bcb3af9c5"
      + id          = (known after apply)
      + name        = "team-provisioner-cognito-sync"
      + rank        = 1
      + tags_all    = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/platform"
          + "service"     = "platform"
          + "team"        = "platform"
        }

      + finding_criteria {
          + criterion {
              + equals     = [
                  + "Impact:IAMUser/AnomalousBehavior",
                ]
              + field      = "type"
              + not_equals = []
            }
          + criterion {
              + equals     = [
                  + "cognito-idp.amazonaws.com",
                ]
              + field      = "service.action.awsApiCallAction.serviceName"
              + not_equals = []
            }
          + criterion {
              + equals     = [
                  + "javabin-team-provisioner",
                ]
              + field      = "resource.accessKeyDetails.userName"
              + not_equals = []
            }
        }
    }

  # module.monitoring.aws_securityhub_automation_rule.alb_http_redirect will be created
  + resource "aws_securityhub_automation_rule" "alb_http_redirect" {
      + arn         = (known after apply)
      + description = "The platform ALB port-80 listener only redirects to HTTPS."
      + id          = (known after apply)
      + is_terminal = false
      + rule_name   = "javabin-accepted-alb-http-redirect"
      + rule_order  = 2
      + rule_status = "ENABLED"
      + tags_all    = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/platform"
          + "service"     = "platform"
          + "team"        = "platform"
        }

      + actions {
          + type = "FINDING_FIELDS_UPDATE"

          + finding_fields_update {
              + note {
                  + text       = "False positive: redirect-only listener (HTTP 301 to HTTPS)."
                  + updated_by = "javabin-platform-terraform"
                }
              + workflow {
                  + status = "SUPPRESSED"
                }
            }
        }

      + criteria {
          + compliance_security_control_id {
              + comparison = "EQUALS"
              + value      = "ELB.18"
            }
          + resource_id {
              + comparison = "EQUALS"
              + value      = "arn:aws:elasticloadbalancing:eu-central-1:553637109631:listener/app/javabin-platform-alb/bec1dd43ab8341b9/1d92e19ae75aa59b"
            }
        }
    }

  # module.monitoring.aws_securityhub_automation_rule.moresleep_public_rds will be created
  + resource "aws_securityhub_automation_rule" "moresleep_public_rds" {
      + arn         = (known after apply)
      + description = "dbmoresleep is intentionally publicly accessible (accepted risk)."
      + id          = (known after apply)
      + is_terminal = false
      + rule_name   = "javabin-accepted-moresleep-public-rds"
      + rule_order  = 1
      + rule_status = "ENABLED"
      + tags_all    = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/platform"
          + "service"     = "platform"
          + "team"        = "platform"
        }

      + actions {
          + type = "FINDING_FIELDS_UPDATE"

          + finding_fields_update {
              + note {
                  + text       = "Accepted risk: dbmoresleep is a public endpoint by design. Security group narrowing is tracked in the security audit (H3)."
                  + updated_by = "javabin-platform-terraform"
                }
              + workflow {
                  + status = "SUPPRESSED"
                }
            }
        }

      + criteria {
          + compliance_security_control_id {
              + comparison = "EQUALS"
              + value      = "RDS.2"
            }
          + compliance_security_control_id {
              + comparison = "EQUALS"
              + value      = "RDS.46"
            }
          + resource_id {
              + comparison = "EQUALS"
              + value      = "arn:aws:rds:eu-central-1:553637109631:db:dbmoresleep"
            }
        }
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudTrail.5"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudTrail.5"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.1"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.1"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.10"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.10"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.11"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.11"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.12"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.12"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.13"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.13"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.14"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.14"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.2"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.2"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.3"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.3"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.4"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.4"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.5"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.5"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.6"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.6"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.7"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.7"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.8"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.8"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["cis/CloudWatch.9"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudWatch.9"
      + standards_arn       = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/CloudTrail.5"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "CloudTrail.5"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "CloudTrail is not sent to CloudWatch Logs (ingestion cost). Equivalent alerts come from EventBridge rules (iam-changes, console-login, resource-creation/modification, security-tampering) to javabin-security, and GuardDuty covers unauthorized API calls and root use."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/EC2.10"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "EC2.10"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "No VPC interface endpoints (~$15/month each). AWS API traffic uses the NAT gateway over TLS."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/EC2.172"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "EC2.172"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "VPC block-public-access cannot be on: the public ALB and NAT gateway need the internet gateway."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/EC2.55"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "EC2.55"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "No VPC interface endpoints (~$15/month each). AWS API traffic uses the NAT gateway over TLS."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/EC2.56"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "EC2.56"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "No VPC interface endpoints (~$15/month each). AWS API traffic uses the NAT gateway over TLS."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/EC2.57"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "EC2.57"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "No VPC interface endpoints (~$15/month each). AWS API traffic uses the NAT gateway over TLS."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/EC2.58"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "EC2.58"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "No VPC interface endpoints (~$15/month each). AWS API traffic uses the NAT gateway over TLS."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/EC2.60"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "EC2.60"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "No VPC interface endpoints (~$15/month each). AWS API traffic uses the NAT gateway over TLS."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/GuardDuty.7"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "GuardDuty.7"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "No EKS clusters in the account."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/Inspector.1"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "Inspector.1"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "Inspector EC2 scanning off: only legacy Elastic Beanstalk instances (not SSM-managed), being migrated to ECS."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/Inspector.3"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "Inspector.3"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "Inspector Lambda code scanning off (cost). Lambda package scanning (Inspector.4) is on."
    }

  # module.monitoring.aws_securityhub_standards_control_association.disabled["fsbp/Macie.1"] will be created
  + resource "aws_securityhub_standards_control_association" "disabled" {
      + association_status  = "DISABLED"
      + id                  = (known after apply)
      + security_control_id = "Macie.1"
      + standards_arn       = "arn:aws:securityhub:eu-central-1::standards/aws-foundational-security-best-practices/v/1.0.0"
      + updated_reason      = "Macie not adopted: cost not justified for current S3 data. Revisit if PII is stored in S3."
    }

Plan: 30 to add, 0 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan"

LLM Review

Risk: 🟢 LOW

Plan creates 30 Security Hub/GuardDuty monitoring resources (suppression rules, control disablements, and a GuardDuty archive filter) with no changes or destruction to existing infrastructure.

  • 🔒 [security] Security Hub automation rule 'javabin-accepted-moresleep-public-rds' suppresses RDS.2 and RDS.46 findings for dbmoresleep, which is publicly accessible. The suppression is documented as accepted risk, and the note says security group narrowing is tracked in audit item H3. Worth confirming H3 is actively scheduled, since this rule will hide the exposure from Security Hub.
  • 🔒 [security] GuardDuty filter 'team-provisioner-cognito-sync' archives Impact:IAMUser/AnomalousBehavior findings for the javabin-team-provisioner user calling cognito-idp. This is scoped narrowly to one IAM user and one service, but archiving IAM anomaly findings suppresses detection for that principal, so it should be kept tightly scoped.
  • 🔒 [security] Around 20 Security Hub CIS and FSBP controls are being disabled, including CloudTrail.5, CloudWatch.1-14, EC2.10, EC2.172, and Inspector.1/3, plus Macie.1. Each has a documented rationale (cost, legacy EB instances, equivalent EventBridge alerts), but the combined effect is a notable reduction in compliance coverage. The CloudTrail/CloudWatch rationale depends on the EventBridge rules actually existing, so confirm they do.
  • 💰 [cost] No billable resources are created. Security Hub control disablement and automation rules are free. The stated motivations are avoiding CloudWatch Logs ingestion and VPC endpoint costs, which this plan does not add.
  • 💥 [destruction] Plan is 30 to add, 0 to change, 0 to destroy. No existing resources are modified or deleted.
  • ✅ [routine] Resources are tagged consistently (environment, managed-by, repo, service, team). The automation rules and the GuardDuty filter are all in the same monitoring module.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant