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
52 changes: 52 additions & 0 deletions docs/adr/0014-cis-metric-filters-for-global-service-detections.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Global-service detections as CIS metric-filter alarms, not us-east-1 EventBridge rules

Detection rules 2 (console sign-in without MFA), 3 (root usage), and 4 (IAM policy changes)
are implemented as **CloudWatch Logs metric-filter alarms in ca-central-1**, using the CIS AWS
Foundations Benchmark filter patterns verbatim — not as EventBridge rules. This ADR records why,
because the original design (EventBridge rules for all single-occurrence signals) was wrong in a
way that only surfaced when two other controls collided.

**The collision.** EventBridge delivers events from global services — IAM, STS, and console
sign-in — **only to the us-east-1 event bus** (AWS EventBridge troubleshooting guide: "events
from API calls from global services are only available in that region"). Rules 2/3/4 as
originally deployed on the ca-central-1 bus could never match (issue 007). The AWS-documented
fix is to create the rules in us-east-1. But the region-lock SCP (ADR-0009) explicitly denies
non-`ca-central-1` actions for workloads-account principals, and `events:*` is not — and should
not be — in its exemption list. Our own guardrail forbids the vendor-recommended fix.

**Options considered.**
- *(a) us-east-1 EventBridge rules in the workloads account* — requires punching an `events:*`
hole in the region-lock SCP. Rejected: weakens the headline guardrail to serve a secondary
control.
- *(b) us-east-1 rules in the SCP-exempt management account, cross-account/cross-region routed*
— works, but adds cross-account event-bus permissions, an IAM routing role, and puts workload
alerting infrastructure in the account that is supposed to stay near-empty (ADR-0009).
Rejected as disproportionate.
- *(c) CloudWatch Logs metric-filter alarms in ca-central-1* — **chosen.** The member trail is
multi-region with `include_global_service_events`, so IAM/sign-in events already land in the
ca-central-1 log group that rule 6 reads. Three metric filters + alarms replicate an existing
in-repo pattern with zero new plumbing.

**Why (c) is canonical, not a workaround.** The CIS AWS Foundations Benchmark specifies exactly
these three detections as metric filters + alarms on a CloudTrail log group (CIS v1.4.0 controls
4.2, 4.3, 4.4), and Security Hub ships controls (CloudWatch.1, CloudWatch.3, CloudWatch.4) that
check for those **exact** filter patterns — they fail if terms are added or removed, so the
patterns are used verbatim and must not be "improved". A side benefit: Security Hub only credits
trails owned by the evaluated account in the evaluated region, which the member trail satisfies;
enabling Security Hub's CIS standard would score these controls PASS as-built.

**Consequences.**
- Alert latency on rules 2/3/4 is **~5–10 minutes** (CloudTrail → CW Logs delivery plus alarm
evaluation) versus seconds on the EventBridge rules. Accepted for low-volume,
any-occurrence-is-suspicious signals; recorded as Honest Residual Risk #9 in `plan.md`.
Production fix: a delegated-admin security account outside the SCP's scope.
- Alarm emails carry the alarm name, not the event payload; triage requires a CloudWatch Logs
Insights query against the log group (runbook).
- Rule 2 is **expected-but-logged** for IAM Identity Center sign-ins: federated `ConsoleLogin`
events record `MFAUsed: No` (MFA happens at the IdP), so every SSO console login fires it.
Same triage posture as rule 8; runbook "Expected alerts" documents it.
- Rule 9's tamper watch was extended to the new transport's suppression surface:
`DeleteMetricFilter`, `DeleteAlarms`, `DisableAlarmActions` alongside the EventBridge
operations.
- The detection pipeline is now 5 EventBridge rules + 4 metric-filter alarms; the "nine rules"
framing and the per-rule table in `plan.md` §Detection Pipeline reflect this.
30 changes: 21 additions & 9 deletions docs/runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,18 @@ terraform -chdir=infra/loonvault output api_endpoint

## Expected alerts on first apply

Two detection rules can fire legitimately during or immediately after `loonvault-apply`.
Both are expected and do not indicate a breach.
Three detection rules can fire legitimately during or immediately after `loonvault-apply`.
All are expected and do not indicate a breach.

**Rule 2 -- Console sign-in without MFA (fires on every SSO console login)**

The CIS 4.2 filter pattern (used verbatim — Security Hub checks it) alerts when a
`ConsoleLogin` event records `MFAUsed: No`. IAM Identity Center sign-ins always record
`MFAUsed: No` because MFA is enforced at the IdP, invisible to the CloudTrail event. So
every SSO console login — including your own — produces this alert ~5–10 minutes later
(metric-filter path, ADR-0014). Same triage posture as rule 8: acknowledge sign-ins you
performed; treat any `ConsoleLogin` you did not perform as a compromise indicator (it
would be an IAM-user or root sign-in path that bypassed Identity Center).

**Rule 8 -- Anomalous GetSecretValue (fires on every apply)**

Expand All @@ -183,13 +193,15 @@ When this alert arrives:

**Rule 9 -- Detection rule tampered (fires on every apply)**

`loonvault-apply` calls `events:PutRule` on every EventBridge rule it manages. The
tamper detection rule (rule 9) watches `DeleteRule`, `DisableRule`, and `RemoveTargets`
-- not `PutRule` -- so Terraform applies do not trigger it. Note `loonvault-destroy`
*does* call `DeleteRule` on every rule; whether that delivers an alert before the SNS
topic is torn down is timing-dependent (CloudTrail to EventBridge latency vs. teardown
speed) and is being validated (see issue 010). Treat a rule-9 alert as a real
suppression attempt only if it fires **outside an apply or destroy window**.
`loonvault-apply` calls `events:PutRule` / `logs:PutMetricFilter` on every detection
resource it manages. The tamper detection rule (rule 9) watches suppression-class
operations only (`DeleteRule`, `DisableRule`, `RemoveTargets`, `DeleteMetricFilter`,
`DeleteAlarms`, `DisableAlarmActions`) -- not the Put* calls -- so Terraform applies do
not trigger it. Note `loonvault-destroy` *does* call the delete operations on every rule,
filter, and alarm; whether that delivers an alert before the SNS topic is torn down is
timing-dependent (CloudTrail to EventBridge latency vs. teardown speed) and is being
validated (see issue 010). Treat a rule-9 alert as a real suppression attempt only if it
fires **outside an apply or destroy window**.

## Destroy (after the interview)

Expand Down
6 changes: 3 additions & 3 deletions docs/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,11 +247,11 @@ The **AI** actor is a force-multiplier on OE and TE: published code that would r
| T-038 | E | EventBridge detection rules modified or deleted to suppress specific alerts | TE MI | 8 | 3 | 4 | 5 | 7 | **5.4** | Medium |
| T-039 | R | AccessDenied spike CloudWatch alarm misconfigured or metric filter targeting wrong log group | MI | 7 | 4 | 5 | 5 | 4 | **5.0** | Medium |

**Mitigating controls:** EventBridge rule fires on `StopLogging`/`DeleteTrail`/`UpdateTrail` (1 occurrence → SNS); CloudTrail log bucket has deletion-deny policy (`s3:DeleteObject` denied, including root); CloudTrail log file validation detects tampering; CloudTrail management events capture EventBridge rule changes; nine detection rules cover the highest-signal events across all six B-13 Cyber Security principles; SNS alerts have confirmed subscriber (email); EventBridge rule fires on `DeleteRule`, `DisableRule`, `RemoveTargets` (detection rule 9, G-05).
**Mitigating controls:** EventBridge rule fires on `StopLogging`/`DeleteTrail`/`UpdateTrail` (1 occurrence → SNS); CloudTrail log bucket has deletion-deny policy (`s3:DeleteObject` denied, including root); CloudTrail log file validation detects tampering; CloudTrail management events capture detection-infrastructure changes; nine detection rules — five EventBridge rules plus four CIS metric-filter alarms (ADR-0014) — cover the highest-signal events across all six B-13 Cyber Security principles; SNS alerts have confirmed subscriber (email); rule 9 fires on suppression of either transport: `DeleteRule`/`DisableRule`/`RemoveTargets` (EventBridge) and `DeleteMetricFilter`/`DeleteAlarms`/`DisableAlarmActions` (metric filters/alarms) (G-05).

**Gap, T-036:** The EventBridge alert fires *after* CloudTrail is disabled; an attacker who disables CloudTrail, acts, then re-enables it has an unlogged window of seconds-to-minutes. This is inherent to detection-based audit trail protection.

**Gap: T-038 -- CLOSED (G-05).** EventBridge rule 9 fires on `DeleteRule`, `DisableRule`, and `RemoveTargets` (`source: aws.events`, `eventSource: events.amazonaws.com`) → SNS (`infra/loonvault/detection.tf`). `PutRule` is deliberately excluded: every `loonvault-apply` fires it, making it too noisy for a single-developer workflow. Residual limitation: rule 9 cannot detect its own deletion (self-referential detection problem). Production fix: cross-account CloudTrail delivery to a dedicated monitoring account whose rules cannot be modified by a compromised workloads-account principal.
**Gap: T-038 -- CLOSED (G-05).** EventBridge rule 9 fires on suppression-class operations against **both detection transports**: `DeleteRule`, `DisableRule`, `RemoveTargets` (`eventSource: events.amazonaws.com`) and — since rules 2/3/4 moved to CIS metric-filter alarms (ADR-0014) — `DeleteMetricFilter` (`logs.amazonaws.com`) and `DeleteAlarms`/`DisableAlarmActions` (`monitoring.amazonaws.com`) → SNS (`infra/loonvault/detection.tf`). `PutRule`/`PutMetricFilter` are deliberately excluded: every `loonvault-apply` fires them, making them too noisy for a single-developer workflow. Residual limitation: rule 9 cannot detect its own deletion (self-referential detection problem). Production fix: cross-account CloudTrail delivery to a dedicated monitoring account whose rules cannot be modified by a compromised workloads-account principal.

---

Expand Down Expand Up @@ -307,7 +307,7 @@ These threats arise specifically from the public posture: full code publication

**Mitigating controls (T-048):** Lambda authorizer correctness is the sole control, unit tests covering all path-normalisation edge cases (URL encoding, trailing slash, case variants, double slashes) must be comprehensive and form part of the CI gate. Security by design: the authorizer's dual-validation (origin secret + CF Access JWT) means finding one edge case is insufficient, both checks must be bypassed simultaneously for admin access.

**Mitigating controls (T-049):** The published detection thresholds are a known and accepted tradeoff of the public posture. Eight of the nine detection rules are single-occurrence for high-signal events (root usage, CloudTrail disabled, IAM policy widened, SG changed, no-MFA sign-in, KMS key lifecycle, anomalous GetSecretValue, detection rule tampering): these cannot be evaded by staying below a threshold. Only the AccessDenied spike (>5 in 5 min) is threshold-based. Mitigation: consider lowering the threshold post-deploy or adding a secondary metric for slower enumeration (e.g., >20 AccessDenied in 30 min). The detection rule configuration intentionally errs toward false positives over false negatives for high-signal events.
**Mitigating controls (T-049):** The published detection thresholds are a known and accepted tradeoff of the public posture. Eight of the nine detection rules are single-occurrence for high-signal events (root usage, CloudTrail disabled, IAM policy changed, SG changed, no-MFA sign-in, KMS key lifecycle, anomalous GetSecretValue, detection rule tampering): these cannot be evaded by staying below a threshold — the three that run as CIS metric-filter alarms (rules 2/3/4, ADR-0014) alarm at ≥ 1 event in 5 minutes, which any single event trips. Only the AccessDenied spike (>5 in 5 min) is threshold-based. Mitigation: consider lowering the threshold post-deploy or adding a secondary metric for slower enumeration (e.g., >20 AccessDenied in 30 min). The detection rule configuration intentionally errs toward false positives over false negatives for high-signal events.

**Mitigating controls (T-050):** SSM parameter *paths* are published; the *values* are not. The X-Origin-Secret value is set at runtime and not stored in code or documentation. Mitigation: enforce SecureString type for all SSM parameters containing security-sensitive values (see Gap G-01). Even with the path known, reading a SecureString requires both IAM `ssm:GetParameter` permission and KMS `kms:Decrypt` permission on the parameter's CMK.

Expand Down
Loading