Get in Touch
Close

Your Cloud Story,
Engineered for Success

Contacts

US Office: Obsium, 6200,
Stoneridge Mall Rd, Pleasanton CA 94588 USA

Kochi Office: GB4, Ground Floor, Athulya, Infopark Phase 1, Infopark Campus Kakkanad, Kochi 682042

+91 9895941969

hello@obsium.io

Kubernetes security best practices for production clusters

Kubernetes security best practices for production clusters

In 2025, attackers robbed a cryptocurrency exchange without using a single exploit. They phished a developer, borrowed their open cloud session to slip one ordinary-looking pod into production, and had it read the identity token Kubernetes clips inside almost every container by default. That token belonged to a CI/CD account with sweeping permissions — enough to read every secret in the cluster, plant a backdoor, and walk out through the cloud account into the exchange’s financial systems (Unit 42, April 2026).

No zero-day. Just a sequence of doors nobody had closed.

And that’s the norm now, not the exception. Unit 42 logged a 282% jump in Kubernetes attacks last year, almost all of them rooted in “misconfigured environments and overprivileged identities” (Unit 42). The upside: a misconfiguration is yours to fix this week, no vendor patch required. So this guide closes those doors in priority order, most impact, least disruption first, and, unlike most write-ups, tells you what breaks when you do.

Why Kubernetes clusters get compromised

Kubernetes ships permissively. Pods can talk to every other pod by default. Service account tokens mount automatically. Secrets in etcd are base64-encoded, which is an encoding, not encryption. Containers can run as root unless you stop them.

None of this is a bug. The defaults optimize for workloads starting successfully, because a platform that breaks every deployment on day one doesn’t get adopted. The security burden shifts to you.

Unit 42’s documented attack chain runs in three steps:

  1. Exploit a misconfiguration or vulnerability to get remote code execution inside a container
  2. Steal the Kubernetes identity mounted in that container
  3. Use it to escalate across clusters and into connected cloud services

The OWASP Kubernetes Top 10

OWASP revised this list in 2025, and the ordering changed meaningfully. Most articles still cite the 2022 version.

Rank2025 riskChanged from 2022?
K01Insecure workload configurationsUnchanged at #1
K02Overly permissive authorization configurationsUp from K03 (was “Overly Permissive RBAC”)
K03Secrets management failuresUp from K08
K04Lack of cluster-level policy enforcementUnchanged
K05Missing network segmentation controlsUp from K07
K06Overly exposed Kubernetes componentsNew framing
K07Misconfigured and vulnerable cluster componentsMerged K09/K10
K08Cluster to cloud lateral movementNew entry
K09Broken authentication mechanismsDown from K06
K10Inadequate logging and monitoringDown from K05

Source: OWASP Kubernetes Top 10

Key insight: K08, cluster-to-cloud lateral movement, is new in 2025 and it’s the one most hardening guides skip entirely. It’s also the highest-severity path, because it converts a container compromise into a cloud account compromise. Supply chain vulnerabilities dropped off the list as a standalone item.

Start here: controls ranked by what actually stops attacks

Do all of it, and you’ll have a well-hardened cluster. Most teams can’t do all of it this quarter. Here’s the honest priority order, weighted by how often each control appears in real attack paths versus how painful it is to implement.

PriorityControlBlocksEffortBreaks things?
1Disable unused service account token automountLateral movement, token theftLowRarely
2Scope workload identity (IRSA / Workload Identity)Cluster-to-cloud pivotMediumSometimes
3Remove wildcard and cluster-admin RBACPrivilege escalationMediumSometimes
4Pod Security Standards (baseline → restricted)Container escape, root execMediumYes, frequently
5Default-deny network policiesLateral movement, exfiltrationHighYes, frequently
6Enable audit loggingNothing directly, enables everythingLowNo
7Encrypt secrets at rest / external storeSecret disclosureMediumRarely
8Image scanning + signature verificationSupply chainMediumSometimes
9Runtime detection (Falco or equivalent)Post-compromise detectionMediumNo

Items 1, 6, and 7 are the highest value per unit of disruption. Do those first. Items 4 and 5 deliver the most security but need a staged rollout to avoid taking workloads down.

Lock down RBAC and service account tokens

RBAC principles that survive contact with production

Kubernetes RBAC is additive. There are no deny rules. A subject’s permissions are the union of every binding that applies to it, which means a single overly broad ClusterRoleBinding quietly undoes careful work everywhere else.

RoleClusterRole
ScopeSingle namespaceEntire cluster
Bound byRoleBindingClusterRoleBinding (cluster-wide) or RoleBinding (namespace-scoped)
Use forApp permissions, team boundariesNode/PV access, CRDs, operators
Risk if over-scopedContained to namespaceFull cluster compromise

A ClusterRole bound with a RoleBinding grants those permissions only inside that namespace. That’s the pattern you want for reusable permission sets without cluster-wide blast radius.

Things that should trigger a review:

  • Any use of system:masters — it bypasses RBAC entirely and isn’t subject to authorization webhooks
  • Wildcards in verbs, resources, or apiGroups
  • cluster-admin bound to anything other than a small, named group of humans
  • create on pods combined with a privileged service account, which is a direct escalation path
  • escalate, bind, and impersonate verbs
  • Permission to read secrets cluster-wide

Audit what you actually have:

# What can this service account do?
kubectl auth can-i --list \
  --as=system:serviceaccount:default:my-app

# Find every ClusterRoleBinding to cluster-admin
kubectl get clusterrolebindings -o json | jq -r '
  .items[] | select(.roleRef.name=="cluster-admin") |
  "\(.metadata.name): \(.subjects // [] | map(.kind+"/"+.name) | join(", "))"'

The Kubernetes project maintains RBAC good practices documentation that goes deeper on escalation paths. It’s worth reading in full before a hardening push.

Service account tokens are the lateral movement path

This is the control most teams miss, and it’s the one attackers rely on.

In plain terms: a service account token is an identity badge Kubernetes clips onto almost every running container automatically. Most containers never use it. But if an attacker breaks into one container, that badge is what lets them move from a single compromised app to the rest of the cluster, and often into the cloud account behind it. Taking the badge away from containers that don’t need it is the cheapest, highest-value fix in this whole guide.

By default, every pod gets a service account token mounted at /var/run/secrets/kubernetes.io/serviceaccount/token. Unit 42 documented attackers specifically targeting that path, then embedding the stolen token in an HTTP header so exfiltration traffic resembles normal authenticated API calls.

Most application pods never call the Kubernetes API. They don’t need the token. Turn it off.

# On the ServiceAccount (applies to all pods using it)
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app
  namespace: production
automountServiceAccountToken: false
---
# Or per-pod, which overrides the ServiceAccount setting
apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  serviceAccountName: my-app
  automountServiceAccountToken: false
  containers:
  - name: app
    image: my-app:1.4.2

For workloads that genuinely need API access, use projected tokens bound to the pod lifetime with a short expiry. These rotate automatically and become useless quickly if stolen:

spec:
  volumes:
  - name: sa-token
    projected:
      sources:
      - serviceAccountToken:
          path: token
          expirationSeconds: 3600   # 1 hour
          audience: my-api

Unit 42’s recommendation is direct on this point: bind tokens to a pod’s lifetime and limit the validity window so that “threat actors who steal projected tokens gain only brief, narrowly-scoped access.”

Production tip: Audit which pods actually use their token before disabling it broadly. Enable audit logging first, then filter API server logs by service account over a week. Anything that never appears is safe to switch off. Doing this in the reverse order is how you find out in production that your logging sidecar needed API access.

Cluster to cloud: when a pod compromise becomes an account compromise

This is OWASP’s new K08, and it’s the path with the worst outcomes.

Your pods assume cloud IAM roles through IRSA on EKS, Workload Identity on GKE, or Managed Identity on AKS. If those roles are broadly scoped, an attacker with code execution in one pod inherits whatever that role can do in your cloud account.

This is the last door in the exchange breach we opened with. Once the attackers held that CI/CD token, the Kubernetes cluster stopped being the target and became the on-ramp: the token’s cloud permissions were what let them step off the cluster and into the money. That step — cluster to cloud — is the one with no undo button, because now they’re in systems Kubernetes doesn’t even manage.

The controls that close it are unglamorous and specific:

  • Scope IAM roles per service account, never per node or per cluster
  • Block pod access to the cloud metadata endpoint (169.254.169.254) unless explicitly required. On EKS this means setting the IMDS hop limit to 1, which is not the default for standard managed node groups (they ship with 2). Get IRSA or EKS Pod Identity working first, or you’ll break any pod currently relying on the node role.
  • Avoid attaching broad permissions to node instance roles, since every pod on the node inherits them
  • Separate CI/CD identities from workload identities, and never let a workload assume a deploy role
  • Audit cloud IAM and Kubernetes RBAC together — reviewing them separately hides the combined blast radius

Enforce Pod Security Standards without breaking production

In plain terms: this is Kubernetes’ built-in guardrail for what a container is allowed to do — run as the all-powerful root user, reach into the host machine, and so on. You pick a strictness level for each team, and the platform refuses to run anything that crosses the line. The catch is that switching the strict setting on across an existing system will block workloads that were quietly relying on those permissions. Sequencing the rollout carefully is what separates a hardening win from an outage.

Pod Security Admission replaced the deprecated PodSecurityPolicy. It works through namespace labels and enforces three profiles.

ProfileWhat it allowsUse for
PrivilegedEverything, unrestrictedSystem namespaces, CNI, storage drivers
BaselineBlocks known privilege escalation (hostNetwork, hostPID, hostIPC, privileged containers, adding dangerous capabilities, host ports)Realistic minimum for application namespaces
RestrictedBaseline plus: non-root required, all capabilities dropped, seccomp RuntimeDefault, no privilege escalationProduction apps, especially handling sensitive data

Source: Kubernetes Pod Security Standards

The rollout path that doesn’t cause an outage

Every guide tells you to enforce restricted. Almost none tell you that doing so across existing namespaces will reject a meaningful fraction of your workloads immediately.

PSA supports three modes per profile, and you should use all three in sequence:

apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    # Step 1: log violations only, nothing is blocked
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: latest

    # Step 2: add warnings to kubectl output for humans
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: latest

    # Step 3: only after violations are fixed
    pod-security.kubernetes.io/enforce: baseline
    pod-security.kubernetes.io/enforce-version: latest

A practical sequence:

  1. Label every namespace with audit: restricted and warn: restricted. Nothing breaks. You get data.
  2. Collect violations from audit logs for two to four weeks, covering at least one full deploy cycle for every service.
  3. Fix workloads. This is the slow part and it’s mostly application work, not platform work.
  4. Set enforce: baseline first. It blocks the dangerous cases without requiring every image to run as non-root.
  5. Move to enforce: restricted namespace by namespace, starting with newer services.

Warning: Applying enforce: restricted cluster-wide without an audit period is the most common way a Kubernetes hardening project causes an incident. Existing deployments won’t be evicted, but the next rollout, scale-up, or node replacement will fail to schedule. The failure appears hours or days after the change, which makes it harder to correlate.

What typically breaks under restricted:

  • Legacy images with no non-root user defined in the Dockerfile
  • Anything needing a Linux capability other than NET_BIND_SERVICE. Restricted forces drop: ["ALL"] and permits adding back only NET_BIND_SERVICE — so a non-root process can still bind a low port if you grant that one capability, but sidecars wanting NET_ADMIN, NET_RAW, or SYS_PTRACE are rejected outright
  • hostPath volume mounts, common in monitoring agents and log shippers. Restricted allows only a safe list of volume types (configMap, secret, emptyDir, projected, PVC, and similar), and hostPath isn’t on it
  • Init containers doing filesystem work that assumes root

A compliant pod spec looks like this:

spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 10001
    fsGroup: 10001
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: my-app:1.4.2
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop: ["ALL"]

readOnlyRootFilesystem: true is worth the effort but usually requires mounting an emptyDir at /tmp for anything that writes scratch files.

Network policies and default-deny

So far we’ve controlled who a container is and what it’s allowed to do. This next door is about where it can go — and by default, the answer is everywhere.

Every pod in a Kubernetes cluster can reach every other pod, across all namespaces, with nothing in between. There is no segmentation until you create it.

Picture your marketing site’s contact form running two namespaces away from the payments database. Out of the box, the pod serving that form can open a connection straight to that database. Nobody intended it; nobody has to. That means a compromised frontend pod can talk directly to your database, your internal APIs, and the metadata service. Network policies are how you stop that, and they’re commonly skipped because retrofitting them into a running cluster is genuinely disruptive.

Start with default-deny in one namespace:

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

Then allow what’s needed. Don’t forget DNS, which is the single most common cause of “everything broke when I applied network policies”:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: production
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

Rollout approach that works: apply default-deny in a staging namespace, watch what fails, write allow rules, then promote. Some CNIs (Cilium, Calico) support policy audit or logging modes that show what would be dropped, which is much safer than discovering it through failed requests.

Warning: NetworkPolicy is enforced by your CNI plugin, not by Kubernetes itself. Flannel doesn’t support it at all. If you apply these manifests on an unsupported CNI, they’re accepted by the API server and silently do nothing, which is worse than not applying them because you now believe you’re segmented.

Retrofitting policies into a busy cluster is slow work. Budget a quarter for a large namespace, not an afternoon.

Secrets management

Every door we’ve closed so far assumes the attacker is trying to get somewhere. This one is about what they grab when they arrive: your database passwords, API keys, and cloud credentials. In the exchange breach, “enumerated secrets across every namespace” was a single sentence in the report. In practice, it was the moment the attackers got the keys to everything else.

Here’s the uncomfortable part. Kubernetes Secrets are base64-encoded in etcd by default, and base64 is just an encoding — a five-character command turns it back into plaintext. Anyone with etcd access or broad secret-read RBAC can read your credentials as easily as you can.

Minimum bar: enable encryption at rest with a KMS provider (AWS KMS, Google Cloud KMS, Azure Key Vault). On managed control planes, this is often a single flag, and there’s no reason to skip it.

ApproachEncryptionRotationAudit trailComplexity
Native Secrets, no encryptionNone (base64)ManualWeakNone
Native + KMS encryption at restAt restManualModerateLow
External Secrets Operator + cloud storeFullAutomaticStrongMedium
HashiCorp Vault + agent injectionFull, dynamic credsAutomaticStrongHigh
Sealed SecretsAt rest, in GitManualModerateLow

External Secrets Operator hits the best balance for most teams: secrets live in your cloud provider’s secret manager, sync into Kubernetes automatically, and rotation happens outside the cluster.

Don’t do these:

  • Secrets in environment variables — they leak into crash dumps, logs, and kubectl describe, and Unit 42 specifically documents attackers harvesting credentials from pod environment variables
  • Secrets in ConfigMaps
  • Secrets baked into images
  • Secrets in Git, unencrypted, which happens more often than anyone admits

Supply chain and image security

Scanning and signing get conflated a lot, and they solve different problems. Scanning tells you what’s vulnerable. Signing tells you the image is the one your pipeline built. You want both.

Scanning in CI catches known CVEs before deployment. Trivy and Grype are solid open-source options. Fail builds on critical severity with a documented exception process, because a scanner that blocks everything gets disabled within a month.

Signature verification at admission is what stops a tampered or unapproved image from running. Sign in CI with cosign, verify at admission with a policy engine.

# Kyverno policy: only run signed images from our registry
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signature
spec:
  validationFailureAction: Enforce
  rules:
  - name: verify-signature
    match:
      any:
      - resources:
          kinds: ["Pod"]
    verifyImages:
    - imageReferences:
      - "registry.example.com/*"
      attestors:
      - entries:
        - keys:
            publicKeys: |-
              -----BEGIN PUBLIC KEY-----
              ...
              -----END PUBLIC KEY-----

Base image discipline matters more than most scanning: distroless or minimal base images remove entire categories of vulnerability by not shipping a shell, a package manager, or curl. Unit 42 documented attackers using curl and wget to exfiltrate tokens. Those binaries don’t exist in a distroless image.

We covered CI-stage scanning in more depth in DevOps security best practices and prioritization in cloud vulnerability management.


Getting the controls right is one problem. Knowing whether they’re working is another. Most teams we work with have policies defined and no reliable way to tell whether enforcement is actually happening in every namespace, or whether a violation last Tuesday went unnoticed. Obsium builds the observability layer that makes Kubernetes security measurable — audit log pipelines, policy violation dashboards, and runtime alerting that routes to the team that owns the workload rather than a shared inbox.


Admission control: Gatekeeper vs Kyverno vs built-in PSA

Admission controllers are where policy becomes enforcement. Requests hit the API server, admission webhooks evaluate them, and non-compliant resources get rejected.

Pod Security AdmissionKyvernoOPA Gatekeeper
Policy languageNone (3 fixed profiles)YAMLRego
Learning curveMinimalLowHigh
ScopePod security onlyAny resource, any ruleAny resource, any rule
Mutation supportNoYesLimited
Image verificationNoBuilt-inVia external tooling
Runs whereBuilt into API serverWebhookWebhook
Best forBaseline pod hardeningMost teams needing custom policyOrgs already using OPA/Rego elsewhere

How to choose:

  • Start with built-in PSA. It covers pod security, requires no extra components, and can’t fail open because it’s part of the API server.
  • Add Kyverno when you need policy beyond pod security: required labels, registry allowlists, image signature verification, resource limit enforcement. YAML policies mean your platform team can actually maintain them.
  • Choose Gatekeeper if your organization already writes Rego for other OPA deployments and wants one policy language across the stack. Otherwise the Rego learning curve is a real cost.

Production tip: Run any admission webhook in audit mode before enforce mode, same as PSA. Also configure failurePolicy deliberately. Fail is more secure but means a webhook outage blocks all deployments, including the one that would fix the webhook. Plenty of teams have locked themselves out this way.

Runtime security and audit logging

Prevention fails eventually, and what limits the damage at that point is how quickly you notice.

Audit logging

Unit 42 notes that many environments run with audit logging disabled entirely, which turns incident response into guesswork. There’s no way to reconstruct what an attacker did if nothing recorded it.

What to alert on, based on the escalation signals Unit 42 flags:

  • Modifications to ClusterRoleBindings and ClusterRoles
  • Service account tokens used from unusual IP addresses or outside expected time windows
  • exec and attach into production pods
  • Pods created in sensitive namespaces like kube-system
  • Admission webhook creation or modification
  • CoreDNS configuration changes
  • Secret enumeration across namespaces
  • Anything using the escalate, bind, or impersonate verbs

Runtime detection

Falco is the CNCF-graduated standard here and detects behavior rather than signatures: unexpected shells in containers, writes to sensitive paths, outbound connections to unknown destinations, reads of the service account token path.

The industry has moved decisively in this direction. Sysdig’s 2026 report found more than 70% of security teams now use behavior-based detections, protecting 91% of cloud environments with high-fidelity runtime alerts, and 140% more organizations year over year automatically terminate suspicious processes when a detection fires (Sysdig 2026 Cloud-Native Security and Usage Report).

The same report found human users now account for just 2.8% of managed identities in cloud environments. Machine identity is the attack surface.

Loris Degioanni, Sysdig’s founder and CTO, framed the shift this way:

“Security teams have optimized human workflows, but they’ve reached their limit. AI-assisted threats move too fast for dashboards, alerts, and manual triage.” — Sysdig 2026 Cloud-Native Security and Usage Report

There’s a practical trap here. Runtime tools generate high alert volume, and a Falco deployment with default rules and no tuning produces noise that teams learn to ignore within weeks. We wrote about that failure mode in Why Your Kubernetes Alerts Are Useless. Tune rules to your workloads, route alerts to owning teams, and delete rules that have never once indicated a real problem.

Kubernetes security tools compared

CategoryOpen sourceCommercialWhat it does
Image scanningTrivy, Grype, ClairSnyk, Prisma CloudCVEs in images and dependencies
Policy/admissionKyverno, OPA GatekeeperVarious CNAPPEnforce rules at admission
Runtime detectionFalco, TetragonSysdig Secure, AquaDetect malicious behavior live
Posture managementkube-bench, kubescapeWiz, Orca, PrismaConfig drift, CIS compliance
SecretsExternal Secrets Operator, Sealed SecretsVault Enterprise, cloud KMSSecure storage and rotation
Network policyCilium, CalicoCalico EnterpriseSegmentation and enforcement
Image signingcosign / SigstoreVariousProvenance and authenticity
RBAC auditkubectl-who-can, rbac-tool, kraneIncluded in CNAPPFind over-permissioned subjects

A reasonable open-source baseline: Trivy in CI, Kyverno at admission, Falco at runtime, kube-bench for CIS compliance, External Secrets Operator for secrets. That combination covers most of what commercial platforms bundle, at the cost of integrating and maintaining it yourself.

Common mistakes

MistakeWhy it’s dangerousFix
Running enforce: restricted cluster-wide on day oneBlocks scheduling on next rollout, appears days laterAudit → warn → enforce, per namespace
Leaving automountServiceAccountToken on everywhereHands attackers an identity in every podDisable by default, enable per workload
cluster-admin Bound to service accountsOne pod compromise equals cluster compromiseScoped Roles, review all bindings
Applying NetworkPolicy on FlannelSilently does nothing, creates false confidenceVerify CNI support first
Forgetting DNS egress rulesEverything breaks in confusing waysAlways allow kube-dns egress
Broad node instance IAM rolesEvery pod on the node inherits cloud permissionsPer-service-account identity (IRSA/Workload Identity)
Secrets in environment variablesLeak into logs, crash dumps, describe outputMounted files or external secret injection
Image scanning without admission verificationScanning is advisory; nothing stops an unscanned imageVerify signatures at admission
Falco with default rules, untunedAlert fatigue, then everyone ignores itTune to your workloads, route to owners
Audit logging disabledNo incident reconstruction possibleEnable, ship off-cluster, retain 90+ days
Webhook failurePolicy: Fail with no escape hatchWebhook outage blocks all deploys including the fixNamespace exclusions for system namespaces

Production security checklist

Cluster setup

  • [ ] API server not publicly exposed, or restricted by IP allowlist
  • [ ] Audit logging enabled, shipped off-cluster, 90+ day retention
  • [ ] Secrets encryption at rest with a KMS provider
  • [ ] etcd encrypted and network-restricted to control plane
  • [ ] CNI supports NetworkPolicy (verified, not assumed)
  • [ ] kubelet anonymous auth disabled, authorization mode set to Webhook
  • [ ] Node metadata endpoint blocked from pods (hop limit 1 on EKS)
  • [ ] Managed control plane on a supported version, patch cadence defined
  • [ ] kube-bench run against CIS Benchmark, findings triaged

Identity and access

  • [ ] No wildcards in production RBAC rules
  • [ ] cluster-admin bound only to a named, reviewed group
  • [ ] system:masters usage eliminated
  • [ ] automountServiceAccountToken: false default, exceptions documented
  • [ ] Projected tokens with bounded expiry for API-consuming workloads
  • [ ] Cloud IAM scoped per service account, not per node
  • [ ] CI/CD identities separated from workload identities
  • [ ] RBAC reviewed quarterly, with a diff process for changes

Workloads

  • [ ] Every namespace labeled with PSA audit and warn
  • [ ] enforce: baseline minimum on application namespaces
  • [ ] enforce: restricted on namespaces handling sensitive data
  • [ ] Containers run as non-root with all capabilities dropped
  • [ ] readOnlyRootFilesystem where feasible
  • [ ] Resource requests and limits set (DoS protection, not just cost)
  • [ ] Default-deny NetworkPolicy per namespace with explicit allows
  • [ ] No secrets in environment variables

Supply chain

  • [ ] Image scanning in CI, builds fail on critical findings
  • [ ] Signature verification enforced at admission
  • [ ] Registry allowlist enforced by policy
  • [ ] Base images minimal or distroless
  • [ ] No :latest tags in production manifests

Detection and response

  • [ ] Runtime detection deployed and tuned
  • [ ] Alerts on ClusterRoleBinding changes, pod exec, secret enumeration
  • [ ] Alerts route to owning teams, not a shared inbox
  • [ ] Incident runbook exists for compromised pod and stolen token
  • [ ] Audit log queries tested before you need them

Conclusion

Kubernetes security comes down to configuration discipline far more than tooling budget. The 282% rise in threat activity Unit 42 recorded came mostly from attackers working through the same handful of misconfigurations: mounted tokens nobody disabled, RBAC nobody scoped, network policies nobody wrote, audit logs nobody switched on.

What to take away:

  • Defaults are permissive on purpose. Every unhardened cluster is running as designed.
  • Token automount is the cheapest high-value fix. Most pods don’t need API access.
  • The cluster-to-cloud pivot is the worst-case path. Scope workload identity per service account.
  • Stage your Pod Security Standards rollout. Audit, then warn, then enforce, or you will cause an incident.
  • Verify your CNI supports NetworkPolicy before trusting manifests that may be doing nothing.
  • Enable audit logging today. It blocks no attacks and makes every investigation possible.
  • Untuned detection is the same as no detection. Alert fatigue is a security failure.

Work down the priority table. Do the low-disruption controls this week, stage the disruptive ones across a quarter, and check the results rather than assuming enforcement is happening.

Go back to the exchange we opened with. Every step the attackers took was a door left open by default, and every one had a fix in this guide that a team could have shipped in an afternoon. The clusters that don’t end up as someone’s case study aren’t the ones with the biggest security budget. They’re the ones where somebody closed the doors, and keeps checking they’re still shut.

Where Obsium fits

Hardening a cluster is a project. Knowing it stayed hardened is an operating capability, and that’s where most teams have a gap. Policies drift, new namespaces launch without labels, and a violation on Tuesday goes unnoticed until an audit in March.

Obsium builds the observability and governance layer around Kubernetes security: audit log pipelines that retain what investigators need, policy compliance dashboards showing enforcement per namespace, and runtime alerting tuned to your workloads rather than shipped defaults.

If you’re hardening production clusters and want a second set of eyes on the plan, book a free 30-minute consultation. An engineer will review your setup and tell you which three controls to prioritize.

FAQs

What are the most important Kubernetes security best practices?

In priority order: disable unused service account token automount, scope cloud workload identity per service account, remove wildcard RBAC permissions, enforce Pod Security Standards through a staged rollout, apply default-deny network policies, and enable audit logging. The first and last are low-effort and high-value, so start there.

Is Kubernetes secure by default?

No, and deliberately so. Defaults favor workload compatibility: pods can reach all other pods, service account tokens mount automatically, secrets are base64-encoded rather than encrypted, and containers can run as root. Hardening is your responsibility.

What is the difference between Pod Security Standards and Pod Security Policies?

PodSecurityPolicy was removed in Kubernetes 1.25. Pod Security Admission replaced it, using three fixed profiles (privileged, baseline, restricted) applied via namespace labels. PSA is simpler but less flexible, so teams needing custom rules typically add Kyverno or Gatekeeper alongside it.

Should you disable automountServiceAccountToken?

Yes, as a default, with exceptions for workloads that genuinely call the Kubernetes API. Most application pods never do. The mounted token is the primary lateral movement path in documented attacks, and Unit 42 observed token-theft-related activity in 22% of cloud environments in 2025.

What is the difference between OPA Gatekeeper and Kyverno?

Kyverno uses YAML for policies and is Kubernetes-native, making it far easier for platform teams to maintain. Gatekeeper uses Rego, which is more powerful and more expensive to learn. Pick Kyverno unless your organization already runs OPA elsewhere and wants one policy language across systems.

How do you secure Kubernetes secrets?

Enable encryption at rest with a KMS provider as a minimum. Better: keep secrets in an external store (cloud secret manager or Vault) and sync them in with External Secrets Operator, so rotation happens outside the cluster. Mount secrets as files rather than environment variables, and restrict secret-read RBAC tightly.

What is a Kubernetes admission controller?

A component that intercepts requests to the API server after authentication and authorization but before persistence. Validating controllers accept or reject; mutating controllers modify resources in flight. This is where policies like “no privileged containers” and “only signed images” get enforced.

How do you monitor Kubernetes security in production?

Three layers: audit logs for API server activity, runtime detection (Falco or equivalent) for container behavior, and posture scanning (kube-bench, kubescape) for configuration drift. All three need somewhere to go — a log pipeline, alert routing, and a dashboard — or they produce data nobody sees.

What is the CIS Kubernetes Benchmark?

A prescriptive configuration standard from the Center for Internet Security covering control plane, etcd, worker node, and policy settings. kube-bench automates checking against it. Managed clusters (EKS, GKE, AKS) have provider-specific versions since you don’t control the control plane.

How often should you review Kubernetes RBAC?

Quarterly at minimum, plus whenever someone leaves a team or a service is decommissioned. RBAC permissions accumulate. Nobody removes access, because removing access risks breaking something and nobody is rewarded for it.

Leave a Comment

Your email address will not be published. Required fields are marked *