Kubernetes is powerful and flexible — and those same qualities make it easy to misconfigure in ways that have serious security consequences. Publicly exposed dashboards, wildcard RBAC permissions, secrets in plaintext, and pods running with host-level access are routinely found in production Kubernetes clusters. This checklist covers the twelve misconfigurations that matter most, with kubectl commands to audit each one.

1. Privileged Pods

A privileged pod runs with essentially host-level access. If an attacker compromises a privileged pod, they have the equivalent of root on the underlying node and a clear path to cluster takeover.

# Find privileged pods in all namespaces
kubectl get pods --all-namespaces -o json |   jq '.items[] | select(.spec.containers[].securityContext.privileged==true) |
      {name: .metadata.name, ns: .metadata.namespace}'

# Also check initContainers
kubectl get pods --all-namespaces -o json |   jq '.items[] | select(.spec.initContainers[]?.securityContext.privileged==true) |
      {name: .metadata.name, ns: .metadata.namespace}'

Fix: Remove privileged: true from pod security contexts. If an application genuinely needs elevated Linux capabilities, add specific capabilities with capabilities.add rather than enabling full privilege.

2. Containers Running as Root

Containers that run as root (UID 0) provide attackers with maximum privileges inside the container, and root container escapes are more likely to succeed than non-root escapes.

# Find pods that do not enforce runAsNonRoot
kubectl get pods --all-namespaces -o json |   jq '.items[] | select(
        .spec.securityContext.runAsNonRoot != true and
        .spec.containers[].securityContext.runAsNonRoot != true
      ) | {name: .metadata.name, ns: .metadata.namespace}'

Fix: Add to pod spec:

securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  runAsGroup: 1000

3. hostPath Volume Mounts

Mounting host directories into containers bypasses container isolation. A pod with hostPath: / mounted can read and modify the entire host filesystem. Even more limited hostPath mounts (like /etc or /var/run/docker.sock) are high-risk.

# Find pods using hostPath volumes
kubectl get pods --all-namespaces -o json |   jq '.items[] | select(.spec.volumes[]?.hostPath != null) |
      {name: .metadata.name, ns: .metadata.namespace,
       path: [.spec.volumes[].hostPath.path // empty]}'

The Docker socket (/var/run/docker.sock) is particularly dangerous — any container that can access it can spawn new privileged containers on the host.

4. Exposed Kubernetes Dashboard

The Kubernetes Dashboard is a legitimate admin tool, but it has been exploited in multiple high-profile breaches (including Tesla's 2018 cryptomining incident) when exposed to the internet without authentication. Check whether your dashboard is externally accessible:

# Find dashboard services with external IPs or LoadBalancer type
kubectl get svc --all-namespaces | grep -i dashboard

# Check if a NodePort or LoadBalancer type is assigned
kubectl get svc kubernetes-dashboard -n kubernetes-dashboard -o json |   jq '{type: .spec.type, ports: .spec.ports}'

The Dashboard should only be accessible via kubectl proxy or through a VPN/bastion host. Never expose it via a LoadBalancer or NodePort service.

5. RBAC Over-Permission (ClusterAdmin Abuse)

RBAC (Role-Based Access Control) governs what subjects (users, service accounts) can do in the cluster. A common shortcut is assigning the cluster-admin ClusterRole to service accounts that do not need it, or creating wildcard role bindings.

# Find all ClusterRoleBindings to cluster-admin
kubectl get clusterrolebindings -o json |   jq '.items[] | select(.roleRef.name=="cluster-admin") |
      {name: .metadata.name, subjects: .subjects}'

# Find roles with wildcard resource or verb permissions
kubectl get roles --all-namespaces -o json |   jq '.items[] | select(.rules[].resources[]=="*" or .rules[].verbs[]=="*") |
      {name: .metadata.name, ns: .metadata.namespace}'

Apply least-privilege: grant only the specific resources and verbs each service account needs. A service account for a deployment controller needs deployments and replicasets — not * on all resources.

6. Secrets in Plaintext YAML

Kubernetes Secrets are base64-encoded, not encrypted. Storing Secret YAML files in git repositories — even private ones — exposes your credentials to anyone with repository access. Worse, base64 decoding is trivial:

# This is NOT secure — base64 is not encryption
kubectl get secret my-secret -o json | jq '.data | map_values(@base64d)'

Solutions:

# Check if etcd encryption at rest is configured
kubectl get apiserver -o json | jq '.spec.encryption'
# Should show AES-CBC or AES-GCM encryption configuration

7. Missing NetworkPolicy (Default Allow-All)

By default, Kubernetes allows all pods in a cluster to communicate with each other. Without NetworkPolicy objects, a compromised pod can reach any other pod, including databases, internal APIs, and the Kubernetes API server.

# Find namespaces with no NetworkPolicy
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)
    if [ "$count" -eq 0 ]; then
        echo "No NetworkPolicy in namespace: $ns"
    fi
done

Start with a default-deny policy for each namespace, then add explicit allow rules for required traffic:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}       # Applies to all pods in namespace
  policyTypes:
    - Ingress
    - Egress

8. Missing Resource Limits (DoS Risk)

Pods without CPU and memory limits can consume all node resources, causing a denial of service for other workloads on the same node. A compromised pod with no limits can also use the resource exhaustion as a lateral movement enabler.

# Find pods without resource limits
kubectl get pods --all-namespaces -o json |   jq '.items[] | select(.spec.containers[].resources.limits == null) |
      {name: .metadata.name, ns: .metadata.namespace}'

Enforce limits cluster-wide with a LimitRange object in each namespace, and use ResourceQuotas to cap total namespace resource consumption.

9. etcd Encryption at Rest

etcd stores all Kubernetes cluster state, including Secrets. Without encryption at rest, anyone with filesystem access to the etcd data directory can read all secrets in plaintext. Enable encryption at rest via the kube-apiserver configuration:

# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: BASE64_ENCODED_32_BYTE_KEY
      - identity: {}   # Fallback for reading unencrypted data

Managed Kubernetes services (EKS, GKE, AKS) offer encryption at rest for etcd — enable it in the cluster configuration if it is not the default.

10. API Server Exposure

The Kubernetes API server should never be directly accessible from the public internet. Check what your API server endpoint resolves to:

# Check your current kube config endpoint
kubectl config view --minify | grep server

# If the IP is a public IP, your API server is internet-facing
# Should only be accessible from your corporate network or VPN

For managed clusters: enable private endpoint access and disable public endpoint access in your cloud provider's cluster settings. For self-managed clusters: place the API server behind a VPN or bastion host, and restrict access with security groups/firewall rules.

11. Pod Security Standards

Kubernetes 1.25+ includes Pod Security Standards (PSS) as a built-in replacement for the deprecated PodSecurityPolicy. Enforce the restricted profile to prevent the most dangerous pod configurations:

# Apply restricted PSS to a namespace via namespace label
kubectl label namespace production   pod-security.kubernetes.io/enforce=restricted   pod-security.kubernetes.io/enforce-version=latest   pod-security.kubernetes.io/warn=restricted   pod-security.kubernetes.io/audit=restricted

The restricted profile requires non-root users, drops all capabilities, disables privilege escalation, and mandates seccomp profiles. Run in warn mode first to understand what would be rejected before enforcing.

12. Image Pull Policy and Unverified Images

Using imagePullPolicy: Always for production images ensures you are not running stale cached versions. More importantly, ensure all production images come from trusted registries — not docker.io public images without a verified publisher.

# Enforce OPA/Gatekeeper policy to restrict allowed registries
# Example constraint: only allow images from your private registry
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: AllowedRepos
metadata:
  name: require-private-registry
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    repos:
      - "registry.company.com/"
      - "gcr.io/company-project/"

Use image signing (Cosign, Notary) and admission controllers to verify that every image deployed to your cluster has been signed by your build pipeline.

Running a Full Audit

Tools like kube-bench (runs the CIS Kubernetes Benchmark against your cluster) and kube-hunter (actively probes your cluster for vulnerabilities) automate much of this checklist. Run kube-bench as a Job inside your cluster for accurate results:

kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
kubectl logs job/kube-bench

Combine cluster-level hardening with application-level security scanning. Shieldome scans the web applications running inside your cluster — checking their security headers, TLS configuration, exposed paths, and vulnerability indicators from the outside, exactly as an attacker would.