Skip to main content

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.

MethodGroupWhat you provideNotes
dumpExternalA kubectl get ... -o json snapshotNo connectivity needed - ideal for airgap/bastion. Nothing leaves your network.
kubeconfigExternalA full kubeconfig (read-only context recommended)apPosture reads the cluster API directly via the kubernetes client.
apiserverExternalAPI server URL + a read-only ServiceAccount bearer token (+ optional CA bundle)Lighter than a full kubeconfig; supports an insecure skip-TLS toggle for labs.
inclusterInternalAn apPosture API tokenYou 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.

Live methods need the cluster client

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)

ControlSevTitleCWE · MITRE · PSS
CIS-5.2.1criticalPrivileged containerCWE-250 · T1610 · Baseline
CIS-5.2.4highPod shares host network namespaceCWE-668 · T1610 · Baseline
CIS-5.2.2highPod shares host PID namespaceCWE-668 · T1610 · Baseline
CIS-5.2.3highPod shares host IPC namespaceCWE-668 · T1610 · Baseline
CIS-5.2.5highallowPrivilegeEscalation is trueCWE-250 · T1611 · Restricted
CIS-5.2.6mediumContainer may run as rootCWE-250 · T1610 · Restricted
CIS-5.2.8highDangerous Linux capability addedCWE-250 · T1610 · Restricted
CIS-5.2.9lowCapabilities not dropped (drop: ALL missing)CWE-250 · T1610 · Restricted
CIS-5.2.11mediumhostPath volume mounts the node filesystemCWE-668 · T1610 · Baseline
K8S-RO-ROOTFSlowRoot filesystem is writableCWE-732 · T1222 · Restricted
K8S-NO-LIMITSlowContainer has no resource limitsCWE-400 · T1499
K8S-SECCOMPmediumNo seccomp profile (RuntimeDefault)CWE-693 · T1610 · Restricted
K8S-LATEST-IMGlowMutable image tag (:latest)CWE-1104 · T1525
K8S-NO-DIGESTinfoImage not pinned by digestCWE-494 · T1525
K8S-SA-AUTOMOUNTmediumDefault ServiceAccount token auto-mountedCWE-522 · T1528
K8S-DEFAULT-NSlowWorkload runs in the default namespaceCWE-668 · T1610

Secrets

ControlSevTitleCWE · MITRE
K8S-ENV-SECREThighHardcoded secret in container envCWE-798 · T1552

RBAC (CIS 5.1)

ControlSevTitleCWE · MITRE
CIS-5.1.1criticalcluster-admin granted via RoleBindingCWE-269 · T1078
CIS-5.1.2highRole allows reading SecretsCWE-522 · T1552
CIS-5.1.3highWildcard (*) used in a RoleCWE-269 · T1078
CIS-5.1.4highRole can create Pods (privilege-escalation path)CWE-269 · T1611
CIS-5.1.13highRole can escalate/bind privilegesCWE-269 · T1078

Network (CIS 5.3)

ControlSevTitleCWE · MITRE
CIS-5.3.2mediumNamespace has no NetworkPolicy (default-allow)CWE-668 · T1046

Pod Security admission (PSS)

ControlSevTitleCWE · MITRE
K8S-PSS-LABELmediumNamespace missing Pod Security admission labelCWE-693 · T1610
System namespaces are tagged, not hidden by the engine

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 fieldPopulated from
name<control title>: <resource>
plugin_id / file_pathcontrol_id + resource (keeps each (control, resource) a distinct vuln under the unified fingerprint)
severityControl severity
cweControl CWE
attackMITRE ATT&CK for Containers technique
solutionControl remediation note
referenceCIS Kubernetes Benchmark; NSA/CISA Kubernetes Hardening; Pod Security Standards
evidenceinternet-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.

MethodPathPurpose
GET/k8s-posture/rulesControl count, per-category breakdown, and frameworks.
GET/k8s-posture/methodsAvailable scan methods + whether the live cluster client is installed.
GET/k8s-posture/agent-manifestRender the in-cluster read-only CronJob YAML for the internal method.
GET/k8s-posture/findingsLatest persisted posture for a cluster (no re-scan) for the dashboard.
GET/k8s-posture/trendCompliance score + issue count per past scan (chart data).
GET/k8s-posture/diffNew / resolved / unchanged findings between the two most recent scans.
POST/k8s-posture/scanEvaluate a snapshot against the rule pack, score it, and (optionally) persist.
POST/k8s-posture/driftGitOps 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 optional kubernetes client 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 kubectl dump are reported as not assessed, not as passing.