Foundations and Audit Boundaries

A Kubernetes cluster can run reliably and still expose unnecessary attack surface. A defensive security audit therefore reviews not only known CVEs, but also permissions, workload configuration, network segmentation, secret handling, platform boundaries and repeatable baselines. The goal is not to run as many commands as possible, but to turn evidence into context, risk, findings, remediation and verification.

Use this article only for your own lab cluster or for clusters where you have explicit authorization. Almost all checks are read-only. YAML examples describe target states and patterns; do not apply them blindly to production. Changes belong into a reviewed change process with backup, testing and rollback planning.

  • Control node: the system where kubectl, trivy or kube-bench runs.

  • Target cluster: the Kubernetes, OpenShift or OKD cluster being reviewed.

  • Managed node: a worker node that runs workloads. In managed Kubernetes, some node or control-plane details may not be directly accessible.

  • Scope: the technical and organizational boundary of the audit. Missing access is not automatically a finding; first it is a scope question.

Check Cluster Context and Inventory

The first audit step is context. An administrator may believe they are working against staging while the kubeconfig still points to production. `current-context` typically combines cluster, user and optionally namespace. Only after this context is confirmed are later findings reliable.

bash
kubectl config current-context
kubectl config view --minify
kubectl cluster-info
kubectl version
kubectl get nodes -o wide
kubectl get namespaces

What to look for: record cluster name, API endpoint, Kubernetes version, node operating systems, container runtime, namespace structure and recognizable platform components. Without inventory, findings are easy to misjudge: a missing control-plane check in a managed cluster means something different than the same missing check in a self-operated cluster.

Examples of suspicious signals include an unexpected context, a namespace scope that does not match the engagement, very old node versions, unclear system namespaces or missing information about the distribution. The risk is not the command itself, but a wrong basis for decisions. Verification means documenting context, scope and inventory in the audit record and checking them again before changes.

Review RBAC

RBAC answers which identity may perform which API action. The model consists of Subject, Binding and Role. A Subject is a User, Group or ServiceAccount. A Role applies in one namespace. A ClusterRole is defined cluster-wide. RoleBinding and ClusterRoleBinding connect Subjects with Roles or ClusterRoles. The flow is: Subject -> RoleBinding or ClusterRoleBinding -> Role or ClusterRole -> apiGroups, resources and verbs.

bash
kubectl auth can-i --list
kubectl get roles -A
kubectl get clusterroles
kubectl get rolebindings -A
kubectl get clusterrolebindings

The output is not a finished risk assessment. Scope and combination matter: `get` on ConfigMaps in one namespace is very different from `*` on `*` cluster-wide. Review wildcards, `cluster-admin`, access to `secrets`, `pods/exec`, `pods/attach`, `impersonate`, `bind`, `escalate`, `serviceaccounts/token` and permissions to create or modify workloads carefully. These permissions are not always wrong, but depending on scope and workload they can have high impact.

bash
NS=default
SA=default
kubectl auth can-i get secrets -A
kubectl auth can-i create pods -A
kubectl auth can-i create pods/exec -A
kubectl auth can-i impersonate users -A
kubectl auth can-i get secrets --as="system:serviceaccount:${NS}:${SA}" -n "$NS"

`kubectl auth can-i --list` shows the effective permissions of the currently used identity. Targeted `can-i` queries help verify concrete findings. A `yes` is not automatically critical and a `no` is not automatically sufficient. Namespace, resource, data class, workload purpose and possible permission combinations matter.

Concrete finding: a ServiceAccount for a web application is bound to `cluster-admin` through a ClusterRoleBinding. Evidence is the binding with Subject and ClusterRole. Risk: the workload receives far more API permissions than it needs. Remediation: create a minimal Role or ClusterRole and restrict the Binding to the required namespace and verbs. Verification: run `kubectl auth can-i` again for exactly this ServiceAccount.

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-config-reader
namespace: app
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-config-reader
namespace: app
subjects:
- kind: ServiceAccount
name: app
namespace: app
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: app-config-reader

Understand Service Accounts and Token Mounting

Service Accounts are Kubernetes identities for workloads. If a Pod needs to talk to the Kubernetes API, it should use an application-specific ServiceAccount with minimal permissions. If it does not need API access, no token should be mounted unnecessarily. Kubernetes creates a `default` ServiceAccount in every namespace; a Pod without explicit `serviceAccountName` uses that default.

bash
kubectl get serviceaccounts -A
kubectl get rolebindings -A -o wide | grep system:serviceaccount || true
kubectl get clusterrolebindings -o wide | grep system:serviceaccount || true
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.serviceAccountName}{"\t"}{.spec.automountServiceAccountToken}{"\n"}{end}'

What to look for: which Pods use the `default` ServiceAccount, which ServiceAccounts have cluster-wide bindings and whether `automountServiceAccountToken` is set deliberately. Since Kubernetes v1.22, Pods receive short-lived, automatically rotating tokens through the TokenRequest API as projected volumes. Do not reuse outdated blanket statements about automatically created long-lived Secret-based tokens.

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: web
namespace: app
automountServiceAccountToken: false
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: app
spec:
template:
spec:
serviceAccountName: web
automountServiceAccountToken: false
containers:
- name: web
image: registry.example.com/web:1.2.3

This YAML changes cluster state if applied. In the audit, it is a remediation pattern. Verification: let the Pod be recreated and inspect the resulting Pod spec. If the application then reports API errors, API access was actually required and must be modeled with minimal RBAC.

Review Pod Security and Workload Hardening

Pod Security Standards describe three profiles. `Privileged` is effectively unrestricted and is not a target state for normal applications. `Baseline` prevents known problematic privilege-escalation patterns while keeping broad compatibility. `Restricted` is the stronger hardening model for modern workloads and requires more care around images, volumes and runtime behavior. Pod Security Admission applies these profiles with namespace labels in `enforce`, `audit` and `warn` modes.

bash
kubectl get namespaces --show-labels
kubectl get namespaces -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.labels.pod-security\.kubernetes\.io/enforce}{"\t"}{.metadata.labels.pod-security\.kubernetes\.io/audit}{"\t"}{.metadata.labels.pod-security\.kubernetes\.io/warn}{"\n"}{end}'

`enforce` rejects Pods that do not meet the selected profile. `audit` writes audit events. `warn` warns users when creating or changing workloads. A namespace without labels does not automatically mean that no controls exist; admission controllers, policies, platform defaults or OpenShift SCCs can also apply. Platform context is required.

bash
kubectl get pods -A -o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,HOSTNETWORK:.spec.hostNetwork,HOSTPID:.spec.hostPID,HOSTIPC:.spec.hostIPC'
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{range .spec.containers[*]}{.name}{": privileged="}{.securityContext.privileged}{", ape="}{.securityContext.allowPrivilegeEscalation}{", rootfs="}{.securityContext.readOnlyRootFilesystem}{", uid="}{.securityContext.runAsUser}{"\n"}{end}{end}'

Suspicious settings include `privileged`, `hostNetwork`, `hostPID`, `hostIPC`, `hostPath`, root processes, additional capabilities, allowed privilege escalation and writable root filesystems. This is not a pure Boolean test: CNI plugins, storage CSI drivers, monitoring agents or security components can legitimately need host access. Evaluate purpose, namespace, image, ServiceAccount, scope, vendor requirement and compensating controls.

yaml
apiVersion: v1
kind: Pod
metadata:
name: hardened-example
namespace: app
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: registry.example.com/app:1.2.3
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: true

`runAsNonRoot` reduces the risk of root processes in the container. `allowPrivilegeEscalation: false` prevents a process from gaining additional privileges. `capabilities.drop: ["ALL"]` removes Linux capabilities and makes required exceptions visible. `readOnlyRootFilesystem` reduces write options inside the image, but can break applications that write temporary files to the root filesystem. `RuntimeDefault` uses the runtime default seccomp profile. Verification means inspecting the deployment again, checking logs and running application tests.

Review NetworkPolicies

NetworkPolicy describes allowed network flows for selected Pods. It is not a firewall for everything; it only takes effect when the network plugin implements the required policy semantics. Without suitable policies, workload-to-workload communication is often broader than necessary, depending on the CNI.

bash
kubectl get networkpolicy -A
for ns in $(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}'); do
count=$(kubectl get networkpolicy -n "$ns" --no-headers 2>/dev/null | wc -l)
printf "%s\t%s\n" "$ns" "$count"
done

A namespace with workloads but without NetworkPolicy can be a finding if the application needs segmentation and the CNI supports NetworkPolicy. Risk: unnecessarily broad east-west communication and weaker containment during an incident. Remediation: default deny as a starting point, followed by explicit required flows. Verification: inspect policies and test real application communication.

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: app
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress

`podSelector: {}` selects all Pods in the namespace. `policyTypes: Ingress` and `Egress` means incoming and outgoing communication for those Pods is only allowed if further policies explicitly permit it. This is not a complete security strategy: DNS, monitoring, ingress controllers, databases and external APIs need deliberate flows.

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: app
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53

This DNS example is a pattern, not a universal copy/paste truth. Namespace names and labels vary by distribution. Verification: check labels in your own cluster, validate DNS resolution from a test Pod and avoid unnecessary egress destinations.

Review Secrets and Images

A Kubernetes Secret is an API object for sensitive data. Base64 is encoding, not encryption. A defensive audit does not dump Secret values. It checks which Secret objects exist, which workloads reference them, which Subjects can read them, how rotation and lifecycle are handled and whether encryption at rest is enabled.

bash
kubectl get secrets -A --field-selector type!=kubernetes.io/service-account-token
kubectl auth can-i get secrets -A
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{range .spec.containers[*]}{range .env[*]}{.valueFrom.secretKeyRef.name}{","}{end}{end}{"\n"}{end}'

Secret read permissions are sensitive because they can expose database passwords, API keys or pull credentials. Secrets can be provided to workloads through environment variables or volumes. Neither method is always secure by itself: process environment, logs, debugging, filesystem behavior, rotation and application design influence risk. Verification: document RBAC checks for concrete ServiceAccounts, workload references and the rotation process.

Image scanners such as Trivy find known vulnerabilities in packages and artifacts. They do not automatically answer whether a CVE is reachable in the concrete code path, whether the workload has special privileges or whether a realistic attack path exists. Image scanning is therefore one component, not the whole audit.

bash
IMAGE="registry.example.com/app:1.2.3"
trivy image --severity HIGH,CRITICAL --ignore-unfixed "$IMAGE"
trivy k8s --report summary cluster

For `trivy image`, Severity, Installed Version and Fixed Version are the key fields. Not every CVE is a critical incident, but every relevant High/Critical finding needs assessment. Trivy marks Kubernetes scanning as experimental in its documentation; use it for orientation, not as a full replacement for manual review.

CIS, kube-bench and Platform Variants

The CIS Kubernetes Benchmark is a structured security baseline. kube-bench automates many of its controls. Benchmark compliance is not the same as security: a cluster can be close to the benchmark and still have architectural risk. Conversely, controlled deviations can be technically justified if they are documented, limited and compensated.

bash
kube-bench --version
kube-bench

What to look for: `PASS` means a check passed. `FAIL` requires context, owner and a remediation decision. `WARN` is a signal, not an automatic risk. Depending on the operating model, kube-bench may require host or node access; run it only in a suitable, authorized environment.

With managed Kubernetes, responsibility is shared. Providers often operate the control plane or selected components; customers typically remain responsible for RBAC, workloads, namespaces, NetworkPolicies, Secrets, images, identity integration and platform configuration. The exact boundary differs by provider and must be documented.

OpenShift and OKD add Security Context Constraints to Kubernetes. SCCs are an OpenShift-specific control layer and must not be mixed up with Pod Security Admission or Kubernetes Pod Security Standards. They control privileged containers, capabilities, host directories, SELinux context, user IDs, host namespaces, FSGroup, seccomp and volume types, among other settings.

bash
oc get scc
oc adm policy who-can use scc privileged

A suspicious result is not only that a workload can use a broad SCC, but why: is it a CNI plugin, CSI driver, operator or ordinary application? Remediation may be a custom narrowly scoped SCC, a different deployment model or removal of unnecessary rights. Verification: run `oc adm policy who-can` again and reassess affected workloads.

Prioritize and Remediate Findings

Not every suspicious result is automatically a vulnerability. A privileged CNI component, storage CSI driver with hostPath, operator with cluster-wide watch or monitoring agent with host access can be technically necessary. Professional assessment combines technical necessity, scope, exposure, compensating controls and operational ownership.

  • Excessive RBAC: evidence is a binding to `cluster-admin` or wildcards. Risk is excessive API access. Context is namespace, workload, data class and ServiceAccount. Remediation is a minimal Role. Verification uses `kubectl auth can-i` for exactly that identity.

  • Missing or overly broad network segmentation: evidence is a namespace with workloads and no suitable NetworkPolicies. Risk is unnecessary communication. Context is CNI capability and required flows. Remediation is default deny plus explicit allow rules. Verification uses policy review and functional testing.

  • Privileged workload: evidence is `privileged`, host namespace use or hostPath. Risk is a changed trust boundary to the node. Context is platform role, vendor requirement and compensating controls. Remediation is SecurityContext hardening or architectural change. Verification uses another spec review and application test.

Prioritization should consider exposure, privilege, exploitability, blast radius, data sensitivity, business impact and compensating controls. An audit without remediation remains an inventory. A reliable flow is: finding -> root cause -> change design -> test -> deployment -> verification -> close or explicit risk acceptance. In internal audits, the follow-up is usually verification or re-assessment; in formal penetration tests, a retest can be a separately defined step.

Audit Checklist

  • Cluster context and scope confirmed.

  • API, version, nodes, namespaces and platform boundaries recorded.

  • ClusterRoleBindings and broad permissions reviewed.

  • ServiceAccount permissions assessed.

  • Token mounting and default ServiceAccounts checked.

  • Pod Security Admission, namespace labels and platform policies reviewed.

  • Privileged workloads and host access contextualized.

  • NetworkPolicy coverage and default-deny readiness checked.

  • Secret access and Secret usage reviewed without value dumps.

  • Images scanned and results contextualized.

  • CIS/kube-bench baseline checked and limits documented.

  • Findings prioritized, remediation planned and changes verified.

Audit vs. Penetration Test

A Kubernetes security audit is not a penetration test. It defensively assesses configuration, permissions, workload hardening, network flows, Secrets, images and baselines. A penetration test checks, under an agreed scope, whether weaknesses are practically exploitable. The decision guide helps distinguish security assessment, vulnerability scan and penetration testing.

Decision Guide for Security Assessment, Vulnerability Scan and Penetration Testing Penetration test

Professional Support

Review Kubernetes security posture professionally

If you need more than a self-check and want a structured security assessment, hardening support or independent validation, ForgeOne helps with analysis, prioritization, remediation and verification.