Ingest NetworkPolicies & SecurityContexts (SCAM+SPAM) for threat modelling #27

Open
opened 2026-06-30 13:51:20 +02:00 by jonas · 0 comments
Owner

Goal

Expand SCAM (the in-cluster agent) and SPAM ingestion to collect Kubernetes NetworkPolicies and Deployment/Pod SecurityContexts (and related posture resources), so SPAM can reason about the threat model of a workload — not just what runs and whether it's exposed, but how well it's contained.

Note: this requires changes in two reposMotstandskraft/scam (new collectors) and Motstandskraft/spam (new models/views + correlation). SCAM is intentionally "dumb": it emits raw K8s objects; SPAM owns all enrichment/joins (scam/.docs/spam.md).

What exists today

  • SCAM collects: Containers, Services, Ingress/IngressClass, Gateway API (Gateway/HTTPRoute/GRPCRoute/TLSRoute/TCPRoute/GatewayClass), Traefik IngressRoute(/TCP/UDP), EndpointSlice; plus Snapshot reconciliation records. Schema: scam/.docs/records.md; collectors in scam/internal/collector/.
  • NOT collected: NetworkPolicy, security contexts, RBAC, PodSecurity. validKinds in spam/api/internal/scam/models.go:170 excludes them.
  • SPAM storage: cluster_record (JSONB blob, upsert by identity, tombstone via snapshots), clusters (identity = kube-system namespace UID + ROR binding), cluster_sessions (liveness). Rollups: cluster_summary, cluster_image_inventory (db/cluster_summary_view.go).
  • Exposure graph (pod→service→ingress/route→public IP) computed in db/host_exposure_view.go. Threat scoring exists at asset level (assetrisk/score.go) but has no containment/posture inputs.

Proposed scope

SCAM side (new collectors)

  • NetworkPolicy (networking.k8s.io/v1): pod selector, ingress/egress rules, namespace/pod/IPBlock peers, ports. Also detect "default-deny" presence per namespace.
  • Pod/Deployment SecurityContext — both pod-level and per-container: runAsNonRoot, runAsUser, privileged, allowPrivilegeEscalation, readOnlyRootFilesystem, capabilities add/drop, seccompProfile, host namespaces (hostNetwork/hostPID/hostIPC), hostPath volume mounts.
  • (Optional, later) RBAC (Roles/Bindings, ServiceAccount token automount), PodSecurity admission labels per namespace.
  • Emit as new record kinds following existing JSONB record conventions + add to validKinds.

SPAM side

  • Add kinds to validKinds; store in cluster_record (JSONB — no migration needed for new fields, per design philosophy).
  • New projections/views correlating workload → applicable NetworkPolicy (selector match) and container → effective security context, per namespace/cluster.
  • Derive posture signals and feed assetrisk.Signals (new fields): e.g. RunsPrivileged, RunsAsRoot, NoNetworkPolicy (no default-deny / no policy selecting the pod), HostNamespaceUse, WritableRootFS, OverbroadCapabilities. These become trust/containment inputs to the threat model — currently the score has none.
  • Surface in UI: a workload "containment / hardening" panel and cluster-level posture rollups; potentially adjust triage context (e.g. an internet-exposed, privileged, no-NetworkPolicy workload with a KEV CVE is materially worse).

Edge cases / decisions

  1. Selector matching is non-trivial. NetworkPolicy applies by label selector across namespaces; "is this pod covered by a default-deny?" requires evaluating all policies in the namespace. Decide whether SCAM pre-computes coverage or SPAM does it from raw objects (per philosophy: SPAM computes).
  2. Effective security context = pod-level ∪ container-level with precedence rules + namespace PodSecurity defaults. Define the resolution logic carefully.
  3. RBAC sensitivity. Collecting RBAC is sensitive (privilege map of the cluster) — confirm it's in scope/authorized; may warrant a separate phase + tighter ACL.
  4. Volume / churn. NetworkPolicies and security contexts are relatively stable vs. pods; should be low-churn, but confirm against the known cluster_record churn concerns (strip volatile fields, snapshot reconciliation).
  5. Does posture shift triage tiers or only context? Today tiers are purely vuln-driven; posture currently feeds TrustScore/context, not tiers. Decide whether containment can escalate (e.g. privileged + exposed + KEV → fix_now). Recommend: context first, opt-in escalation rule later.
  6. Agent RBAC. SCAM's ServiceAccount needs get/list/watch on netpol + (if RBAC) rbac resources — Helm chart update + least-privilege review.
  7. Backward/forward compat. Older SCAM agents won't send these kinds — SPAM must treat absence as "unknown", not "compliant".

Tasks

  • scam: NetworkPolicy + SecurityContext collectors; document record kinds in .docs/records.md; Helm RBAC update
  • spam: add kinds to validKinds; ingest/store (JSONB)
  • spam: netpol coverage projection (selector match, default-deny detection) + effective-security-context projection
  • spam: new posture fields on assetrisk.Signals; decide context-vs-escalation
  • spam: workload hardening panel + cluster posture rollup in UI
  • (Later) RBAC / PodSecurity admission collection as a separate, ACL-gated phase

Relates to: asset-risk threat scoring; cluster ACL (posture reads must respect clusterACLFilter).

## Goal Expand **SCAM** (the in-cluster agent) and **SPAM** ingestion to collect Kubernetes **NetworkPolicies** and **Deployment/Pod SecurityContexts** (and related posture resources), so SPAM can reason about the **threat model** of a workload — not just *what runs* and *whether it's exposed*, but *how well it's contained*. > Note: this requires changes in **two repos** — `Motstandskraft/scam` (new collectors) and `Motstandskraft/spam` (new models/views + correlation). SCAM is intentionally "dumb": it emits raw K8s objects; SPAM owns all enrichment/joins (`scam/.docs/spam.md`). ## What exists today - SCAM collects: Containers, Services, Ingress/IngressClass, Gateway API (Gateway/HTTPRoute/GRPCRoute/TLSRoute/TCPRoute/GatewayClass), Traefik IngressRoute(/TCP/UDP), EndpointSlice; plus `Snapshot` reconciliation records. Schema: `scam/.docs/records.md`; collectors in `scam/internal/collector/`. - **NOT collected:** NetworkPolicy, security contexts, RBAC, PodSecurity. `validKinds` in `spam/api/internal/scam/models.go:170` excludes them. - SPAM storage: `cluster_record` (JSONB blob, upsert by identity, tombstone via snapshots), `clusters` (identity = kube-system namespace UID + ROR binding), `cluster_sessions` (liveness). Rollups: `cluster_summary`, `cluster_image_inventory` (`db/cluster_summary_view.go`). - Exposure graph (pod→service→ingress/route→public IP) computed in `db/host_exposure_view.go`. Threat *scoring* exists at asset level (`assetrisk/score.go`) but has **no containment/posture inputs**. ## Proposed scope ### SCAM side (new collectors) - **NetworkPolicy** (`networking.k8s.io/v1`): pod selector, ingress/egress rules, namespace/pod/IPBlock peers, ports. Also detect "default-deny" presence per namespace. - **Pod/Deployment SecurityContext** — both pod-level and per-container: `runAsNonRoot`, `runAsUser`, `privileged`, `allowPrivilegeEscalation`, `readOnlyRootFilesystem`, `capabilities` add/drop, `seccompProfile`, host namespaces (`hostNetwork`/`hostPID`/`hostIPC`), `hostPath` volume mounts. - (Optional, later) **RBAC** (Roles/Bindings, ServiceAccount token automount), **PodSecurity admission** labels per namespace. - Emit as new record kinds following existing JSONB record conventions + add to `validKinds`. ### SPAM side - Add kinds to `validKinds`; store in `cluster_record` (JSONB — no migration needed for new fields, per design philosophy). - New projections/views correlating **workload → applicable NetworkPolicy** (selector match) and **container → effective security context**, per namespace/cluster. - Derive **posture signals** and feed `assetrisk.Signals` (new fields): e.g. `RunsPrivileged`, `RunsAsRoot`, `NoNetworkPolicy` (no default-deny / no policy selecting the pod), `HostNamespaceUse`, `WritableRootFS`, `OverbroadCapabilities`. These become **trust/containment inputs** to the threat model — currently the score has none. - Surface in UI: a workload "containment / hardening" panel and cluster-level posture rollups; potentially adjust triage context (e.g. an internet-exposed, privileged, no-NetworkPolicy workload with a KEV CVE is materially worse). ## Edge cases / decisions 1. **Selector matching is non-trivial.** NetworkPolicy applies by label selector across namespaces; "is this pod covered by a default-deny?" requires evaluating all policies in the namespace. Decide whether SCAM pre-computes coverage or SPAM does it from raw objects (per philosophy: **SPAM computes**). 2. **Effective security context** = pod-level ∪ container-level with precedence rules + namespace PodSecurity defaults. Define the resolution logic carefully. 3. **RBAC sensitivity.** Collecting RBAC is sensitive (privilege map of the cluster) — confirm it's in scope/authorized; may warrant a separate phase + tighter ACL. 4. **Volume / churn.** NetworkPolicies and security contexts are relatively stable vs. pods; should be low-churn, but confirm against the known `cluster_record` churn concerns (strip volatile fields, snapshot reconciliation). 5. **Does posture shift triage tiers or only context?** Today tiers are purely vuln-driven; posture currently feeds TrustScore/context, not tiers. Decide whether containment can *escalate* (e.g. privileged + exposed + KEV → fix_now). Recommend: context first, opt-in escalation rule later. 6. **Agent RBAC.** SCAM's ServiceAccount needs `get/list/watch` on netpol + (if RBAC) rbac resources — Helm chart update + least-privilege review. 7. **Backward/forward compat.** Older SCAM agents won't send these kinds — SPAM must treat absence as "unknown", not "compliant". ## Tasks - [ ] **scam:** NetworkPolicy + SecurityContext collectors; document record kinds in `.docs/records.md`; Helm RBAC update - [ ] **spam:** add kinds to `validKinds`; ingest/store (JSONB) - [ ] **spam:** netpol coverage projection (selector match, default-deny detection) + effective-security-context projection - [ ] **spam:** new posture fields on `assetrisk.Signals`; decide context-vs-escalation - [ ] **spam:** workload hardening panel + cluster posture rollup in UI - [ ] (Later) RBAC / PodSecurity admission collection as a separate, ACL-gated phase **Relates to:** asset-risk threat scoring; cluster ACL (posture reads must respect `clusterACLFilter`).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Motstandskraft/spam#27
No description provided.