Kubernetes Posture
Sidebar: Testing → K8s Posture (/k8s-posture)
Kubernetes Posture audits a running cluster for security misconfigurations. Where IaC scanning checks static manifests in a repository, this module takes a read-only snapshot of the cluster's live resources (workloads, RBAC, NetworkPolicies, namespaces, Services, Ingresses) and evaluates them against a curated, CIS-aligned rule pack. Every failure flows into the platform's unified Vulnerabilities hub, so your cluster posture sits beside DAST, SAST, SCA, and IaC results - with cross-tool dedup, attack-chain inference, AI triage, and compliance scoring.
The engine is fully offline. No rule set is copied from any tool; the checks are authored from public hardening guidance - the CIS Kubernetes Benchmark, the NSA/CISA Kubernetes Hardening Guide, and the Pod Security Standards - and tagged with MITRE ATT&CK for Containers techniques and CWE IDs.
How a scan works
You feed the engine a cluster snapshot. It builds a normalized in-memory model,
runs every control against the relevant resource population, emits one finding per
(control, resource) failure, rolls the results up into a compliance score and
grade, and (optionally) persists them as Finding atoms.
Connecting a cluster
Four input methods are supported, in two groups. The right method is inferred
from the body fields if you omit method.
| Method | Group | What you provide | Notes |
|---|---|---|---|
dump | External | A kubectl get ... -o json snapshot | No connectivity needed - ideal for airgap/bastion. Nothing leaves your network. |
kubeconfig | External | A full kubeconfig (read-only context recommended) | apPosture reads the cluster API directly via the kubernetes client. |
apiserver | External | API server URL + a read-only ServiceAccount bearer token (+ optional CA bundle) | Lighter than a full kubeconfig; supports an insecure skip-TLS toggle for labs. |
incluster | Internal | An apPosture API token | You apply a read-only CronJob in the cluster; it dumps every 6h and POSTs the snapshot back. No inbound access to the cluster is required. |
The recommended read-only dump command (shown in the UI) is:
kubectl get pods,deployments,statefulsets,daemonsets,cronjobs,jobs,\
roles,clusterroles,rolebindings,clusterrolebindings,networkpolicies,\
namespaces,serviceaccounts,services,ingresses -A -o json > cluster.json
The snapshot builder accepts a single resource, a JSON array, a List
(items[]), or a dict of Lists, and recursively collects and de-duplicates the
Kubernetes objects it finds - so almost any kubectl dump shape works.
kubeconfig, apiserver, and the agent's POST path require the optional
kubernetes Python client on the server. If it is unavailable, use Offline
dump or the in-cluster agent, which work without it. The /methods
endpoint reports live_available.
Controls catalog
The engine ships 24 controls across five categories. CIS Kubernetes Benchmark
IDs are preserved verbatim; apPosture-original checks use a K8S-* prefix. Each
control carries a severity, a CWE, a MITRE ATT&CK for Containers technique, a Pod
Security Standards level where applicable, a remediation note, and (for most) a
copy-paste hardened YAML snippet.
Workload hardening / Pod Security Standards (CIS 5.2)
| Control | Sev | Title | CWE · MITRE · PSS |
|---|---|---|---|
CIS-5.2.1 | critical | Privileged container | CWE-250 · T1610 · Baseline |
CIS-5.2.4 | high | Pod shares host network namespace | CWE-668 · T1610 · Baseline |
CIS-5.2.2 | high | Pod shares host PID namespace | CWE-668 · T1610 · Baseline |
CIS-5.2.3 | high | Pod shares host IPC namespace | CWE-668 · T1610 · Baseline |
CIS-5.2.5 | high | allowPrivilegeEscalation is true | CWE-250 · T1611 · Restricted |
CIS-5.2.6 | medium | Container may run as root | CWE-250 · T1610 · Restricted |
CIS-5.2.8 | high | Dangerous Linux capability added | CWE-250 · T1610 · Restricted |
CIS-5.2.9 | low | Capabilities not dropped (drop: ALL missing) | CWE-250 · T1610 · Restricted |
CIS-5.2.11 | medium | hostPath volume mounts the node filesystem | CWE-668 · T1610 · Baseline |
K8S-RO-ROOTFS | low | Root filesystem is writable | CWE-732 · T1222 · Restricted |
K8S-NO-LIMITS | low | Container has no resource limits | CWE-400 · T1499 |
K8S-SECCOMP | medium | No seccomp profile (RuntimeDefault) | CWE-693 · T1610 · Restricted |
K8S-LATEST-IMG | low | Mutable image tag (:latest) | CWE-1104 · T1525 |
K8S-NO-DIGEST | info | Image not pinned by digest | CWE-494 · T1525 |
K8S-SA-AUTOMOUNT | medium | Default ServiceAccount token auto-mounted | CWE-522 · T1528 |
K8S-DEFAULT-NS | low | Workload runs in the default namespace | CWE-668 · T1610 |
Secrets
| Control | Sev | Title | CWE · MITRE |
|---|---|---|---|
K8S-ENV-SECRET | high | Hardcoded secret in container env | CWE-798 · T1552 |
RBAC (CIS 5.1)
| Control | Sev | Title | CWE · MITRE |
|---|---|---|---|
CIS-5.1.1 | critical | cluster-admin granted via RoleBinding | CWE-269 · T1078 |
CIS-5.1.2 | high | Role allows reading Secrets | CWE-522 · T1552 |
CIS-5.1.3 | high | Wildcard (*) used in a Role | CWE-269 · T1078 |
CIS-5.1.4 | high | Role can create Pods (privilege-escalation path) | CWE-269 · T1611 |
CIS-5.1.13 | high | Role can escalate/bind privileges | CWE-269 · T1078 |
Network (CIS 5.3)
| Control | Sev | Title | CWE · MITRE |
|---|---|---|---|
CIS-5.3.2 | medium | Namespace has no NetworkPolicy (default-allow) | CWE-668 · T1046 |
Pod Security admission (PSS)
| Control | Sev | Title | CWE · MITRE |
|---|---|---|---|
K8S-PSS-LABEL | medium | Namespace missing Pod Security admission label | CWE-693 · T1610 |
Findings in platform namespaces (kube-system, kube-public, kube-node-lease,
local-path-storage) are real but expected-by-design, so they are tagged
system. The Findings tab hides them by default; toggle System shown to see
them. Built-in RBAC (cluster-admin, system:*, default admin/edit/view
ClusterRoles) is skipped as noise - what matters is which subjects those roles are
bound to, surfaced in the RBAC tab.
Scoring and grading
A control is only assessed when its target population exists in the snapshot (for example, RBAC controls need Roles/ClusterRoles present). Controls with no population are honestly reported as not assessed rather than auto-passing - no false greens. The cluster compliance score is the ratio of passed to assessed controls:
Alongside the cluster score, the engine computes a per-namespace scoreboard
using a severity-weighted penalty model (critical 40 · high 20 · medium 8 · low 3 · info 1, with a ×1.5 multiplier for internet-facing objects). Each
namespace gets score = max(0, 100 - penalty) and its own A-F grade. The five
riskiest user objects (platform namespaces excluded) are surfaced as Top 5
riskiest objects. Workloads reachable from outside the cluster - behind a
LoadBalancer/NodePort Service or targeted by an Ingress - are flagged
internet-facing, weighting their blast radius.
How findings map into the unified model
When persist is true, each finding is ingested as a Finding atom with
source = k8s_posture through the shared ingest_service, creating an
Assessment scoped to the cluster name. This means
cluster posture participates in the same dedup, lifecycle, SLA, and reporting
machinery as every other source.
| Finding field | Populated from |
|---|---|
name | <control title>: <resource> |
plugin_id / file_path | control_id + resource (keeps each (control, resource) a distinct vuln under the unified fingerprint) |
severity | Control severity |
cwe | Control CWE |
attack | MITRE ATT&CK for Containers technique |
solution | Control remediation note |
reference | CIS Kubernetes Benchmark; NSA/CISA Kubernetes Hardening; Pod Security Standards |
evidence | internet-facing when the object is externally reachable |
The Assessment's score is set to the cluster compliance score, which powers the
Trend chart. After persistence, the engine compares the new score to the
previous scan for the same cluster; if it dropped, a posture_regression
notification is emitted (best-effort, never breaks a
scan).
API endpoints
All endpoints are mounted under /api/v1/k8s-posture and require the
view_findings permission.
| Method | Path | Purpose |
|---|---|---|
GET | /k8s-posture/rules | Control count, per-category breakdown, and frameworks. |
GET | /k8s-posture/methods | Available scan methods + whether the live cluster client is installed. |
GET | /k8s-posture/agent-manifest | Render the in-cluster read-only CronJob YAML for the internal method. |
GET | /k8s-posture/findings | Latest persisted posture for a cluster (no re-scan) for the dashboard. |
GET | /k8s-posture/trend | Compliance score + issue count per past scan (chart data). |
GET | /k8s-posture/diff | New / resolved / unchanged findings between the two most recent scans. |
POST | /k8s-posture/scan | Evaluate a snapshot against the rule pack, score it, and (optionally) persist. |
POST | /k8s-posture/drift | GitOps drift: runtime snapshot vs declared manifests in a repo. |
The /scan response includes summary (score, grade, counts, category
breakdown, exposed counts), controls (per-control pass/fail/assessed),
findings, scoreboard, topology, and rbac_report. The scan is recorded in
the audit log.
In the UI
The page header shows the live control count and frameworks. After a scan, a score row (compliance grade, controls passed, open issues, workload/namespace counts) sits above seven tabs:
- Overview - namespace scoreboard + Top 5 riskiest objects.
- Findings - searchable, filterable list (severity, category, namespace, internet-facing, hide-system) with per-finding remediation and copy-paste hardened YAML.
- RBAC - the RBAC analyzer: each subject's effective risky permissions
(
cluster-admin,wildcard,read-secrets,create-workloads,escalate), resolved through bindings, with an "excessive" flag. - Topology - Namespace → Workload → Container tree, color-coded by worst severity and issue count (the blast radius).
- Compliance - a control × pass/fail heatmap grouped by category, including not assessed cells.
- Drift - GitOps drift: compares the runtime dump against the Kubernetes manifests declared in a Git repo, flagging settings weakened at runtime (runtime more permissive than Git) plus runtime-only and Git-only workloads.
- Trend - compliance score over time + a diff (new / resolved / unchanged) versus the previous scan.
Remediation
Most controls ship a copy-paste hardened YAML snippet (the copy button in a
finding's detail), for example a fully hardened securityContext for privileged
containers, a default-deny NetworkPolicy, a Pod Security admission label, or a
secretKeyRef rewrite for a hardcoded env secret. Apply the snippet, re-scan, and
watch the Trend chart improve.
Limitations / not yet covered
- Cloud / CNAPP posture (cloud IAM, control-plane node config, kubelet flags, managed-service settings) is not in scope here - this module audits cluster-resident objects. Cloud/CNAPP posture is planned as a separate module.
- Live scan methods (
kubeconfig,apiserver, in-cluster POST) depend on the optionalkubernetesclient being present on the server; the offline dump and agent paths always work. - A control is scored only when its target population exists in the snapshot;
resources you did not include in your
kubectldump are reported as not assessed, not as passing.
Related
- Vulnerabilities hub - where persisted cluster findings live alongside every other source.
- Assessments - the scan-run records that carry the cluster score and power Trend/Diff.
- IaC scanning - the static, pre-deploy counterpart to live posture.
- Compliance & maturity - how posture rolls into framework coverage.
- ASPM Coverage - portfolio-wide testing coverage.