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
21 changes: 21 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,27 @@ days-old graph cannot look confident. `risk:plan` states `plan-time` coverage
with its own caveat: it reads the plan, not the graph. Failures degrade the
statement, never the prediction.

## Event-triggered recompute

Escalations used to appear only on the policy evaluator's schedule, so a new
admin grant or trust could sit unevaluated for hours. The incremental
collector now signals when a batch could have moved one: any IAM node
(`IamRole`, `IamUser`, `IamGroup`, `IamPolicy*`, `AccessKey`) or IAM edge
(`TRUSTS`, `HAS_IAM_ROLE`, `ATTACHED_TO`, `MEMBER_OF`, `HAS_ACCESS_KEY`) makes
the batch recompute-worthy, and the collector sends one message to the
`recompute` FIFO queue (`RECOMPUTE_QUEUE_URL`; best-effort — a missing queue or
send failure never fails collection). Two bounds keep the heaviest job in the
system from running constantly: the producer's FIFO deduplication id collapses
a five-minute burst to one message, and the recompute worker — a Deployment
that long-polls the queue — refuses to evaluate more often than
`RECOMPUTE_MIN_INTERVAL_SECONDS` (default 900), deleting messages that arrive
inside the interval as covered by the run that just happened. It runs the
**policy evaluator** only, so escalation paths (and the likelihood on any path
through them) refresh within one incremental cycle; the hourly rule run is what
raises the corresponding issues. A failed evaluation leaves its messages for
SQS redelivery and the queue's DLQ rather than deleting them. The CDK path is
not wired (the scheduled evaluator still covers it).

## Compliance reconciliation and the trust center

The compliance job evaluates the CIS AWS Foundations v3.0.0 and Khalifa
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

All notable changes to Khalifa.

## [Unreleased] - Event-triggered recompute

### Added

- **Event-triggered policy evaluation**: the incremental collector now signals when a batch could have moved an escalation — any IAM node (`IamRole`, `IamUser`, `IamGroup`, `IamPolicy*`, `AccessKey`) or IAM edge (`TRUSTS`, `HAS_IAM_ROLE`, `ATTACHED_TO`, `MEMBER_OF`, `HAS_ACCESS_KEY`) marks the batch recompute-worthy — sending one best-effort message to a new `recompute` FIFO queue (`RECOMPUTE_QUEUE_URL`); a missing queue or a send failure never fails collection. The `recompute-worker` runtime command runs as a Deployment (rendered only when the queue is wired; policy-evaluator IRSA role plus receive/delete on the queue) that long-polls it and re-runs **the policy evaluator**, so escalation paths refresh within one incremental cycle instead of at the evaluator's scheduled hour; the hourly rule run still raises the corresponding issues. Two bounds keep the heavy job in check: the producer's FIFO deduplication id collapses a five-minute burst to one message, and the worker refuses to evaluate more often than `RECOMPUTE_MIN_INTERVAL_SECONDS` (default 900, chart `recomputeWorker.minIntervalSeconds`), deleting covered messages. A failed evaluation leaves messages for SQS redelivery and the queue's DLQ; the scheduled evaluator remains the backstop. Terraform creates the queue + DLQ, grants the incremental collector `SendMessage` and the policy-evaluator role receive/delete, and flows `recomputeQueueUrl` into the chart. The CDK path is not wired (documented).

### Notes

- Still open from the re-review (`~/Documents/Testing/plans/2026-10-27-predictive-completion.md`): freshness tempering inside scores (G9), observed-reachability v2 (remote endpoints, per-hop citation), shift-left v2 (IAM diff/Helm, CI wiring), and the long tail (UEBA breadth, EPSS/KEV, trust-center projections, per-issue risk window/SLO).

## [Unreleased] - Outcome labels and coverage statements

### Added
Expand Down
22 changes: 19 additions & 3 deletions OPERATIONAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
8. **cloudtrail-analyzer** - CloudTrail usage CronJob (Athena → AccessAnalyzerCache; optional anomaly nodes via graph-writer)
9. **flow-log-analyzer** - VPC flow-log CronJob (Athena → TrafficObservations; **suspended by default**, reuses the cloudtrail-analyzer IRSA role)
10. **threat-intel-import** - CronJob: imports customer threat-intel lists from S3 into ThreatIndicators (**suspended by default**, runs as the rule-runner service account)
11. **dspm-scanner** - CronJob: secret scanning of S3 objects and Lambda environment variables (Macie primary, regex fallback; findings written via graph-writer)
12. **policy-evaluator / least-privilege-sweep** - Lambdas: effective permissions, escalation paths, rightsizing
13. **IRSA roles** - IAM roles for pod authentication to Neptune/DynamoDB/SQS
11. **recompute-worker** - Deployment (rendered only when the recompute queue is wired): drains IAM-change signals and re-runs the policy evaluator, at most once per `RECOMPUTE_MIN_INTERVAL_SECONDS`; uses the policy-evaluator IRSA role
12. **dspm-scanner** - CronJob: secret scanning of S3 objects and Lambda environment variables (Macie primary, regex fallback; findings written via graph-writer)
13. **policy-evaluator / least-privilege-sweep** - Lambdas: effective permissions, escalation paths, rightsizing
14. **IRSA roles** - IAM roles for pod authentication to Neptune/DynamoDB/SQS

### Network Flow

Expand Down Expand Up @@ -225,6 +226,21 @@ kubectl create job --from=cronjob/khalifa-rule-runner rule-runner-manual -n secu
already consume the findings). Auto-ticketing stays off for matched issues
until `/rules/quality` shows measured precision.

### Runbook: Event-triggered recompute

The recompute worker is rendered only when `collection.recomputeQueueUrl` is set
(the complete example/Terraform wires it). It is always-on by design:
- Producer: the incremental collector sends one message per five-minute window
when a batch touched IAM. Watch `Queued an event-triggered recompute` in the
incremental collector logs.
- Worker: watch `Event-triggered policy evaluation complete` and
`Recompute skipped inside the minimum interval` in the recompute-worker pod.
Tune `recomputeWorker.minIntervalSeconds` (default 900) if the evaluator runs
too often for the graph's size.
- Failures: an evaluation error leaves messages for SQS redelivery; three
receives land them in the recompute DLQ. The scheduled evaluator CronJob
remains the backstop if the worker is down.

## I. Graph Query Profiling

Neptune serves id lookups from the index; a property filter on a whole label
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ One member role. Evaluation stays in-account.
| **CIEM** | IAM decomposition, effective permissions, escalation paths (assume chains and `iam:PassRole` to compute, RULE-034), CloudTrail-grounded rightsizing. |
| **Least privilege** | Sweep of roles and users. Deployable policy recommendations. |
| **Secrets** | Secrets Manager and SSM parameter inventory with who-can-read analysis from identity and resource policies, rotation and access age, sharing detection (RULE-010, RULE-032, RULE-033), and admin users holding active access keys (RULE-031). |
| **Risk** | Scheduled rules and attack-path traversals on the live graph (34 rules), with an exploitability factor that raises scores for paths carrying Amazon Inspector findings, an explainable likelihood on every path, a per-fix simulation that counts what each edge removal would clear, matching against customer-supplied threat-intel lists (additive score bonus, TTL-fresh), curated MITRE ATT&CK techniques on every finding, graph-only adversary scenarios that re-run technique chains over the live graph, and a per-rule quality report (suppression vs resolution) that calibrates which rules to trust. |
| **Risk** | Scheduled rules and attack-path traversals on the live graph (34 rules), with an exploitability factor that raises scores for paths carrying Amazon Inspector findings, an explainable likelihood on every path, escalation paths recomputed within minutes of an IAM change (event-triggered and rate-bounded), a per-fix simulation that counts what each edge removal would clear, matching against customer-supplied threat-intel lists (additive score bonus, TTL-fresh), curated MITRE ATT&CK techniques on every finding, graph-only adversary scenarios that re-run technique chains over the live graph, and a per-rule quality report (suppression vs resolution) that calibrates which rules to trust. |
| **Detection** | GuardDuty detector coverage per region (including `MISSING`), Security Hub findings joined to internet-exposed resources (RULE-017, RULE-018), CloudTrail anomaly signals: root activity, secret reads from IPs outside each principal's own 30-day baseline, AccessDenied deviations from each principal's own daily baseline (RULE-020 to RULE-022), canary credential tripwires (RULE-023), secret scanning of Lambda environment variables plus IMDSv1-with-role detection (RULE-024), and Amazon Inspector CVEs on internet-exposed workloads (RULE-003). |
| **Exposure** | Public EKS control planes (RULE-019) plus public S3/RDS and world-open security groups, derived from collected endpoint and network config, with optional observed traffic from VPC flow logs confirming where traffic actually goes. |
| **Compliance** | CIS AWS Foundations v3.0 and the Khalifa baseline, per-control evidence, mapped SOC 2 / ISO 27001 / PCI DSS / NIST 800-53 requirements, and CIS reconciliation against Security Hub before anything is publishable. |
Expand Down Expand Up @@ -163,6 +163,8 @@ EKS uses [`deploy/helm/khalifa/values.yaml`](deploy/helm/khalifa/values.yaml). T
| `THREAT_INTEL_IP_LIST_S3_URI` | s3://bucket/key of the newline-separated IP indicator list (empty disables) | — |
| `THREAT_INTEL_ACCOUNT_LIST_S3_URI`| s3://bucket/key of the newline-separated account-id list (empty disables) | — |
| `THREAT_INTEL_TTL_DAYS` | Days an imported indicator stays valid (DynamoDB TTL) | `90` |
| `RECOMPUTE_QUEUE_URL` | FIFO queue the incremental collector signals and the recompute worker drains | — |
| `RECOMPUTE_MIN_INTERVAL_SECONDS` | Minimum seconds between event-triggered policy evaluations (recompute worker) | `900` |
| `ATHENA_DATABASE` | Glue database for CloudTrail | `khalifa_cloudtrail_db` |
| `ATHENA_WORKGROUP` | Athena workgroup | `khalifa-cloudtrail-analysis` |
| `CLOUDTRAIL_S3_LOCATION` | CloudTrail S3 prefix | `s3://cloudtrail-logs/AWSLogs/` |
Expand Down Expand Up @@ -277,7 +279,7 @@ reported as certified.

| Path | Contents |
| ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| [`lambdas/`](lambdas) | Collector, incremental, graph-writer, policy-evaluator, CloudTrail, flow-log, threat-intel import, least-privilege, DSPM, runtime |
| [`lambdas/`](lambdas) | Collector, incremental, graph-writer, policy-evaluator, CloudTrail, flow-log, threat-intel import, recompute, least-privilege, DSPM, runtime |
| [`packages/risk-engine`](packages/risk-engine) | Rules, scoring, compliance |
| [`api-service/`](api-service) · [`ui/`](ui) | REST API and Next.js UI |
| [`deploy/`](deploy) | Helm chart, Terraform modules, fixtures |
Expand Down
1 change: 1 addition & 0 deletions deploy/helm/khalifa/templates/configmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,6 @@ data:
GRAPH_WRITER_TIMEOUT_MS: {{ .Values.collection.graphWriterTimeoutMs | quote }}
GRAPH_WRITER_URL: {{ printf "http://%s-graph-writer:%v" (include "khalifa.fullname" .) .Values.graphWriter.service.port | quote }}
SQS_QUEUE_URL: {{ .Values.collection.incrementalQueueUrl | quote }}
RECOMPUTE_QUEUE_URL: {{ .Values.collection.recomputeQueueUrl | quote }}
GUARDDUTY_ADMINISTRATOR_ACCOUNT_ID: {{ .Values.collection.guardDutyAdministratorAccountId | default "" | quote }}
GUARDDUTY_FINDING_REGIONS: {{ join "," (.Values.collection.guardDutyFindingRegions | default list) | quote }}
2 changes: 1 addition & 1 deletion deploy/helm/khalifa/templates/networkpolicies.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{{- if .Values.networkPolicies.enabled }}
{{- range $component := list "api" "ui" "rule-runner" "org-collector" "incremental" "graph-writer" "compliance" "cloudtrail-analyzer" "flow-log-analyzer" "threat-intel-import" "dspm-scanner" "policy-evaluator" "least-privilege-sweep" }}
{{- range $component := list "api" "ui" "rule-runner" "org-collector" "incremental" "graph-writer" "compliance" "cloudtrail-analyzer" "flow-log-analyzer" "threat-intel-import" "recompute-worker" "dspm-scanner" "policy-evaluator" "least-privilege-sweep" }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
Expand Down
63 changes: 63 additions & 0 deletions deploy/helm/khalifa/templates/recompute-worker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{{- if .Values.collection.recomputeQueueUrl }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "khalifa.fullname" . }}-recompute-worker
labels:
{{- include "khalifa.labels" . | nindent 4 }}
app.kubernetes.io/component: recompute-worker
spec:
replicas: {{ .Values.recomputeWorker.replicas }}
selector:
matchLabels:
{{- include "khalifa.selectorLabels" (dict "root" . "component" "recompute-worker") | nindent 6 }}
template:
metadata:
labels:
{{- include "khalifa.selectorLabels" (dict "root" . "component" "recompute-worker") | nindent 8 }}
{{- with .Values.recomputeWorker.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.recomputeWorker.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
# Reuses the policy-evaluator IRSA role: the worker only runs that
# evaluation, and the role gains receive/delete rights on the queue.
serviceAccountName: {{ include "khalifa.serviceAccountName" (dict "root" . "key" "policyEvaluator" "suffix" "policy-evaluator") }}
automountServiceAccountToken: {{ .Values.serviceAccounts.policyEvaluator.automountServiceAccountToken }}
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
securityContext:
{{- include "khalifa.podSecurityContext" . | nindent 8 }}
{{- if .Values.topologySpread.enabled }}
topologySpreadConstraints:
{{- include "khalifa.topologySpreadConstraints" (dict "root" . "component" "recompute-worker") | nindent 8 }}
{{- end }}
containers:
- name: recompute-worker
image: {{ required "recomputeWorker.image must be an immutable repository@sha256 digest" .Values.recomputeWorker.image | quote }}
imagePullPolicy: IfNotPresent
args: ["recompute-worker"]
securityContext:
{{- include "khalifa.containerSecurityContext" . | nindent 12 }}
env:
- name: RECOMPUTE_MIN_INTERVAL_SECONDS
value: {{ .Values.recomputeWorker.minIntervalSeconds | quote }}
envFrom:
- configMapRef:
name: {{ include "khalifa.fullname" . }}-config
- configMapRef:
name: {{ include "khalifa.fullname" . }}-collection
resources:
{{- toYaml .Values.recomputeWorker.resources | nindent 12 }}
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
{{- end }}
30 changes: 29 additions & 1 deletion deploy/helm/khalifa/values.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"graphWriter",
"compliance",
"reconciliation",
"recomputeWorker",
"threatIntelImport",
"flowLogAnalyzer",
"cloudtrailAnalyzer",
Expand Down Expand Up @@ -198,6 +199,7 @@
"trustCenterSyncClientId": { "type": "string", "pattern": "^(|[0-9a-f]{32}\\.access)$" }
}
},
"recomputeWorker": { "$ref": "#/definitions/recomputeWorker" },
"threatIntelImport": { "$ref": "#/definitions/cronJob" },
"flowLogAnalyzer": { "$ref": "#/definitions/cronJob" },
"cloudtrailAnalyzer": { "$ref": "#/definitions/cronJob" },
Expand Down Expand Up @@ -519,6 +521,26 @@
"days": { "type": "integer", "minimum": 1, "maximum": 31 }
}
},
"recomputeWorker": {
"type": "object",
"additionalProperties": false,
"required": [
"image",
"minIntervalSeconds",
"replicas",
"podAnnotations",
"podLabels",
"resources"
],
"properties": {
"image": { "$ref": "#/definitions/image" },
"minIntervalSeconds": { "type": "integer", "minimum": 0, "maximum": 86400 },
"replicas": { "type": "integer", "minimum": 1 },
"podAnnotations": { "$ref": "#/definitions/stringMap" },
"podLabels": { "$ref": "#/definitions/stringMap" },
"resources": { "$ref": "#/definitions/resources" }
}
},
"collection": {
"type": "object",
"additionalProperties": false,
Expand All @@ -531,7 +553,8 @@
"maxAttempts",
"retryDelayMs",
"graphWriterTimeoutMs",
"incrementalQueueUrl"
"incrementalQueueUrl",
"recomputeQueueUrl"
],
"properties": {
"homeAccountId": { "type": "string", "pattern": "^$|^[0-9]{12}$" },
Expand Down Expand Up @@ -566,6 +589,10 @@
"type": "string",
"pattern": "^$|^https://sqs\\.[a-z0-9-]+\\.amazonaws\\.com/[0-9]{12}/[A-Za-z0-9_.-]+$"
},
"recomputeQueueUrl": {
"type": "string",
"pattern": "^$|^https://sqs\\.[a-z0-9-]+\\.amazonaws\\.com/[0-9]{12}/[A-Za-z0-9_.-]+$"
},
"guardDutyAdministratorAccountId": { "type": "string", "pattern": "^$|^[0-9]{12}$" },
"guardDutyFindingRegions": {
"type": "array",
Expand Down Expand Up @@ -807,6 +834,7 @@
"cloudtrail-analyzer",
"flow-log-analyzer",
"threat-intel-import",
"recompute-worker",
"dspm-scanner",
"policy-evaluator",
"least-privilege-sweep"
Expand Down
Loading
Loading