Skip to content

Deny audit-trail tampering (CloudTrail, Config, GuardDuty, SecurityHub, S3, KMS)#1

Merged
royosherove merged 1 commit into
mainfrom
fix/deny-audit-tampering
May 13, 2026
Merged

Deny audit-trail tampering (CloudTrail, Config, GuardDuty, SecurityHub, S3, KMS)#1
royosherove merged 1 commit into
mainfrom
fix/deny-audit-tampering

Conversation

@royosherove
Copy link
Copy Markdown
Member

Closes the audit-trail tampering gap: under PowerUserAccess the agent could call cloudtrail:StopLogging, DeleteTrail, etc., blinding the very audit mechanism the design relies on.

What changed

Policy expansion — 11 deny statements, 132 deny actions:

  • DenyCloudTrailTampering (14) — Stop/Delete/Update trails, event-data-stores, channels, selectors, resource policies; also Create* to block "stand up a competing trail" evasion
  • DenyAuditServiceTampering (30) — Config/GuardDuty/SecurityHub recorders, members, filters, finding triage. Documented as intentional broad deny (triage = human/SOC task)
  • DenyTrailStorageTampering (21) — trail S3 bucket: delete, policy/ACL/PAB/ownership, encryption, versioning, logging, lifecycle, replication, object-lock, notification redirect, website, object retention/legal-hold, governance bypass, PutObject (corrupt existing logs)
  • DenyTrailKmsTampering (12) — trail CMK: ScheduleKeyDeletion, DisableKey, PutKeyPolicy, grants, alias retargeting, PutResourcePolicy, key-material import
  • DenyRoleManagementOutsideAgentPath extended (+4) — iam:TagRole/UntagRole/UpdateRole/UpdateRoleDescription for defense-in-depth parity with DenySelfEscalation
  • DenySelfEscalation extended (+6) — closes the self-mutation hole opened by adding path = var.iam_path to the agent role

Terraform module mirrors all 4 new statements via concat() over named locals; conditional inclusion gated on trail_bucket_name / trail_kms_key_arn vars.

Quality gates

  • Fail-closed precondition — if both trail vars are null and trail_protection_acknowledged != true, plan/apply errors with a clear message
  • Variable validation — full KMS ARN (UUID or MRK) regex; bare S3 bucket-name regex (rejects ARNs, uppercase, underscores, double-dots, IP-format); IAM role/policy name regex; IAM path regex (rejects empty, / root, double-slash); strict KMS ARN format with multi-region key support
  • CI workflow (.github/workflows/lint.yml) — JSON parse, substitution round-trip lint, terraform fmt/validate, per-Sid Action/NotAction parity check across all 3 policies (deny-guardrails, iam-scoped, permissions-boundary). Caught real Sid drift during development (AllowInstanceProfileManagement vs AllowInstanceProfileManagementUnderAgentPath).

Repo cleanup

  • Moved terraform/example.tfterraform/examples/downstream-consumer.tf (was causing Duplicate variable declaration and breaking terraform validate)
  • Added .gitignore (was missing entirely)
  • LICENSE → Apache-2.0 in README (was MIT, mismatch with LICENSE file)
  • Replaced 200+ lines of stale inline JSON in policy-design.md with Sid-summary tables linking to canonical sources
  • README repo-structure tree updated; placeholder table simplified; substitution helper hardened against ordering footguns

Breaking changes

  • aws_iam_role.agent and aws_iam_instance_profile.agent now set path = var.iam_path (default /loki/). Existing deployments will recreate role + profile on apply, changing the role ARN. New deployments are unaffected. Required so the JSON template's role/IAM_PATHAGENT_ROLE_NAME placeholder matches the actual role ARN.

Partition support

Hardcoded to aws (commercial regions). GovCloud / China not supported. TODO marker in terraform/main.tf documents the 3 lock-in sites if/when partition support is added.

Review trail

8 automated review loops + 2 architect reviews + 7 amendments. Final state: zero findings on both reviewers' last pass.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e18b70de79

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread terraform/main.tf
Comment on lines +300 to +303
"guardduty:DisassociateFromMasterAccount", "guardduty:StopMonitoringMembers",
"guardduty:DeletePublishingDestination", "guardduty:UpdatePublishingDestination",
"guardduty:DisassociateMembers", "guardduty:DeleteMembers",
"guardduty:UpdateMemberDetectors",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Deny administrator-account disassociation APIs

DenyAuditServiceTampering blocks only the deprecated *DisassociateFromMasterAccount actions, but not guardduty:DisassociateFromAdministratorAccount / securityhub:DisassociateFromAdministratorAccount. With PowerUserAccess still attached, the agent can use the newer APIs to detach from delegated administrators and bypass the intended centralized GuardDuty/Security Hub audit controls.

Useful? React with 👍 / 👎.

Comment thread terraform/main.tf
Comment on lines +292 to +295
"config:DeleteConfigurationRecorder", "config:StopConfigurationRecorder",
"config:PutConfigurationRecorder",
"config:DeleteDeliveryChannel", "config:PutDeliveryChannel",
"config:DeleteConfigRule",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Cover service-linked AWS Config recorder tampering

The Config deny list blocks only customer-managed recorder mutations (DeleteConfigurationRecorder, PutConfigurationRecorder), but omits service-linked recorder APIs like config:DeleteServiceLinkedConfigurationRecorder and config:PutServiceLinkedConfigurationRecorder. In accounts that use service-linked recorders, the agent can still disable or replace recording despite this guardrail.

Useful? React with 👍 / 👎.

@royosherove royosherove force-pushed the fix/deny-audit-tampering branch 2 times, most recently from cd4fc29 to 4d05d4d Compare May 13, 2026 21:29
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4d05d4d713

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread terraform/main.tf
Comment on lines +355 to +356
"kms:PutResourcePolicy", "kms:DeleteResourcePolicy",
"kms:ImportKeyMaterial", "kms:DeleteImportedKeyMaterial"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use valid KMS actions in DenyTrailKmsTampering

DenyTrailKmsTampering includes kms:PutResourcePolicy and kms:DeleteResourcePolicy, but AWS KMS does not define those IAM actions (KMS policy management is kms:PutKeyPolicy). When trail_kms_key_arn is set, this makes the inline IAM policy invalid and aws_iam_role_policy.deny_guardrails creation/update fails with a malformed/invalid action error, so the guardrail cannot be deployed at all.

Useful? React with 👍 / 👎.

…b, S3, KMS)

PowerUserAccess permits the agent to blind the audit trail through
multiple paths. Adds explicit denies covering all of them.

policies/deny-guardrails.json — 4 new statements:

DenyCloudTrailTampering
  StopLogging, DeleteTrail, UpdateTrail, PutEventSelectors,
  PutInsightSelectors, Delete/UpdateEventDataStore.

DenyAuditServiceTampering
  Config: Delete/Stop/Put ConfigurationRecorder, Delete/Put
    DeliveryChannel (Put covers overwrite-as-delete: pointing the
    recorder at a black-hole bucket), DeleteConfigRule.
  GuardDuty: DeleteDetector, UpdateDetector (covers flipping a
    detector to disabled / low-frequency without deleting it),
    DisassociateFromMasterAccount, StopMonitoringMembers.
  SecurityHub: DisableSecurityHub, DisassociateFromMasterAccount,
    BatchDisableStandards, UpdateStandardsControl (covers
    control-level disablement that bypasses DisableSecurityHub).

DenyTrailStorageTampering (scoped to TRAIL_BUCKET_NAME)
  s3:DeleteBucket, Delete/PutBucketPolicy, PutBucketAcl,
  PutBucketVersioning, PutLifecycleConfiguration, DeleteObject,
  DeleteObjectVersion, PutObjectRetention, BypassGovernanceRetention.
  Scoped to the trail bucket ARN so the agent can still operate on
  other buckets normally.

DenyTrailKmsTampering (scoped to TRAIL_KMS_KEY_ARN)
  kms:ScheduleKeyDeletion, DisableKey, PutKeyPolicy, CreateGrant,
  RevokeGrant, CancelKeyDeletion. Scoped to the trail's CMK so the
  agent can still manage other keys.

README.md
  Adds TRAIL_BUCKET_NAME and TRAIL_KMS_KEY_ARN to the placeholder
  table, with guidance for the unencrypted-trail case.

docs/policy-design.md
  Expands security-property #4 from a one-liner to an itemized list
  covering all four threat surfaces; line-wraps for diff/readability.
@royosherove royosherove force-pushed the fix/deny-audit-tampering branch from 4d05d4d to 63d9f88 Compare May 13, 2026 21:40
@royosherove royosherove merged commit 866023a into main May 13, 2026
1 check passed
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