Orchestration

Kubernetes Security

Controls that keep a Kubernetes cluster predictable when many teams and workloads share it.

Kubernetes clusters are high-value targets because they often control many services, hold sensitive credentials, and run with broad cloud permissions. By default, Kubernetes is built for trust within a cluster , workloads can communicate freely, identities are implicit, and many defaults are permissive. Security in Kubernetes means making that implicit trust explicit: defining who can call the API, which Pods can talk to which, what runtime capabilities are allowed, and what evidence exists when something unexpected happens.

Learning objectives

What you should be able to do after reading.
  • Explain the main security controls that protect a Kubernetes cluster.
  • Describe how identity, network policy, and workload settings reduce blast radius.
  • Recognize the controls that support detection and auditability.

At a glance

Fast mental model before you dive in.
Identity
  • RBAC
  • Service accounts
  • Scoped permissions
Workload control
  • Security context
  • Admission control
  • Network policies
Visibility
  • Audit logs
  • Cluster hardening
  • Policy review

Core idea

The 4 Cs of cloud-native security, Code, Container, Cluster, Cloud, describe the layers that need to be addressed. Kubernetes cluster security is only one layer. A secure cluster running insecure application code or misconfigured containers does not produce a secure system. Each layer must be addressed, and weaknesses in one layer are not fully compensated by strength in another.

The primary security goal in Kubernetes is to reduce blast radius. If a container is compromised, how far can the attacker reach? Without controls, the answer is 'very far'. They can reach other workloads across all namespaces, call the Kubernetes API with the container's service account permissions, and potentially move to the cloud control plane. With controls, network policies, scoped service accounts, security contexts, and admission policies, the answer becomes 'not very far.'

Namespaces are an organizational boundary, not a security boundary by themselves. Two Pods in different namespaces can communicate freely unless NetworkPolicies prevent it. A user with cluster-admin can read Secrets from any namespace. Actual security isolation between tenants requires network policies, RBAC scoped per namespace, and potentially separate clusters for workloads with different trust levels.

The operational habit that matters most is treating security configuration as code. RBAC policies, NetworkPolicies, admission rules, and security contexts should all be in version control, reviewed like application changes, and applied through a controlled deployment process, not applied ad hoc by individuals with direct cluster access.

Control layers

  • Use RBAC and service accounts to control which humans and workloads can perform which actions against the Kubernetes API.
  • Use NetworkPolicies to define which Pods can communicate with which other Pods and on which ports, defaulting to deny.
  • Use security contexts and admission controllers to enforce runtime constraints that prevent dangerous workload configurations from being deployed.
  • Enable and ship audit logs to a system where they will actually be reviewed and alerted on.

Baseline

  • Treat audit logs as a primary security control, not just troubleshooting data. Alert on suspicious API calls, RBAC changes, and access to Secrets.
  • Keep privileged exceptions narrow, documented, and regularly reviewed, one team's temporary workaround has a habit of becoming permanent.
  • Review cluster-wide defaults regularly, network exposure, default service account permissions, and admission policies, because silent drift widens access without any single deliberate decision.

Signals to watch for

Patterns worth investigating further.
  • Pods running with more privilege than the application requires.
  • Changes to access policy or admission policy without a clear review trail.
  • Unexpected API activity that is only visible after the fact.

DEEP DIVE

RBAC

Role-based access control (RBAC) in Kubernetes governs who can perform which actions on which resources in the API. Roles define a set of allowed verbs (get, list, create, update, delete, watch, patch) on specific API groups and resource types, scoped to a single namespace. ClusterRoles are identical but apply cluster-wide. RoleBindings attach a Role to a user, group, or service account within a namespace. ClusterRoleBindings attach a ClusterRole at the cluster level.

The principle of least privilege applies strictly to RBAC. A developer who needs to read logs and exec into Pods in their team's namespace does not need list access to Secrets or create access to deployments in other namespaces. Well-designed RBAC maps to actual job functions, what a person or system genuinely needs to do their work, rather than to org chart titles or convenience groupings.

Common RBAC mistakes include binding subjects to the cluster-admin ClusterRole (which grants unrestricted access to everything), using wildcard verbs ('*') or wildcard resources ('*') in Role definitions, and granting 'create' or 'update' on ClusterRoleBindings to workloads (which allows privilege escalation by creating new bindings). These mistakes are easy to make and difficult to detect without regular audits.

Auditing RBAC is possible with 'kubectl auth can-i --as=system:serviceaccount:namespace:name verb resource', which shows what a specific service account is allowed to do. Tools like rbac-lookup, rakkess, and kubectl-who-can make it easier to visualize what permissions exist and who has them, which is essential for periodic RBAC reviews in clusters that have grown over time.

Service accounts

Service accounts are the identity used by workloads to authenticate to the Kubernetes API. Every Pod runs under a service account, and by default that service account's token is automatically mounted into the Pod's filesystem at /var/run/secrets/kubernetes.io/serviceaccount. A container can use that token to authenticate to the API server and perform any action that the service account's RBAC permissions allow.

The default service account in each namespace has minimal permissions, but many operators grant broader permissions without realizing how widely those permissions are applied. If a service account with 'list secrets' permission is bound to the default service account in a namespace, every Pod in that namespace, including newly created ones that have nothing to do with the original use case, inherits that permission.

Workloads that do not need to call the Kubernetes API should disable token auto-mounting. This is done by setting automountServiceAccountToken: false either in the ServiceAccount spec (which applies to all Pods using that service account) or in individual Pod/Deployment specs. Disabling auto-mounting means a compromised container cannot use its token to probe the cluster's API.

For workloads that genuinely need to interact with cloud services, not the Kubernetes API but AWS, GCP, or Azure, the modern pattern is to use cloud provider identity integration rather than long-lived credentials. AWS IRSA (IAM Roles for Service Accounts) and GKE Workload Identity allow a Kubernetes service account to assume a cloud IAM role without storing cloud credentials in the cluster. This eliminates an entire class of credential exposure and enables fine-grained, per-workload cloud access control.

Network policies

Without NetworkPolicies, all Pods in a Kubernetes cluster can communicate with all other Pods on any port. This flat network model is convenient for development but catastrophic for security in production, a compromised application can connect to any database, queue, or internal API in the cluster regardless of namespace or intended function. NetworkPolicies restrict this communication by specifying which Pods may send or receive traffic from which other Pods.

NetworkPolicies are enforced by the CNI (Container Network Interface) plugin, not by Kubernetes itself. Not all CNI plugins support NetworkPolicy enforcement. Common plugins that do include Calico, Cilium, and Antrea. If a cluster uses a CNI plugin that does not support NetworkPolicies (such as Flannel in its default configuration), NetworkPolicy objects can be created but will have no effect, a dangerous false sense of security.

The most effective pattern is default-deny. Create a NetworkPolicy that selects all Pods in a namespace and denies all ingress and all egress. Then add specific allow policies for the traffic that should be permitted. This inverts the default from 'everything is allowed unless explicitly denied' to 'everything is denied unless explicitly permitted.' A new service added to the namespace has no network access until a NetworkPolicy explicitly grants it, which prevents accidentally over-permissive configurations.

A concrete NetworkPolicy example, a frontend Pod should be able to receive HTTP traffic from the Ingress controller and send traffic to a backend API Pod. It should not be able to connect directly to the database (only the backend should). Without NetworkPolicies, all three can freely communicate. With targeted NetworkPolicies in place, a compromised frontend cannot reach the database directly. Reducing the blast radius of a frontend compromise significantly.

Security context

A security context defines operating system-level security settings for a Pod or a specific container. These settings are independent of the container image. The image defines what code runs, but the security context defines how that code is allowed to run. Key fields include runAsUser (the UID the container process runs as), runAsNonRoot (rejects the container if it would run as root), readOnlyRootFilesystem (prevents writing to the container filesystem), allowPrivilegeEscalation (prevents processes from gaining more privileges than the parent), and capabilities (add or drop specific Linux capabilities).

Pod Security Admission (PSA) is the built-in Kubernetes mechanism for enforcing security context requirements at namespace scope. It replaced the deprecated PodSecurityPolicy in Kubernetes 1.25. PSA defines three policy levels. Privileged (no restrictions), Baseline (prevents known privilege escalation), and Restricted (strongest hardening, requires non-root, read-only filesystem, dropped capabilities). Namespaces are labeled to specify which policy level applies and whether violations are enforced, audited, or warned.

The Restricted PSA level reflects what a well-hardened Pod looks like. Running as a non-root user, with an immutable root filesystem, no privilege escalation allowed, all capabilities dropped, and a seccomp profile applied. Many legitimate applications can meet these requirements with minimal changes, those that cannot should have a documented exception explaining why the relaxation is necessary and what compensating controls are in place.

A common mistake is setting privileged: true as a quick fix for a container that fails to start due to permission issues. This grants the container near-host-level capabilities and bypasses almost all isolation. The right approach is to identify the specific permission or capability the container actually needs (often CAP_NET_BIND_SERVICE for low port binding, or a specific file permission) and grant only that, not blanket privilege.

Admission control

Admission controllers intercept requests to the Kubernetes API before objects are persisted to etcd. They can validate (accept or reject a request based on its content), mutate (modify a request before it is stored, for example to inject default security settings), or both. Kubernetes ships with many built-in admission controllers, the most extensible are the ValidatingAdmissionWebhook and MutatingAdmissionWebhook, which allow external policy engines to handle admission decisions.

OPA/Gatekeeper and Kyverno are the two dominant policy engines for Kubernetes admission control. Gatekeeper uses OPA's Rego language to express policies. Kyverno uses a YAML-based policy language that is closer to Kubernetes resource syntax. Both can enforce requirements like 'all containers must have resource limits', 'images must come from the approved registry', 'Pods must not run as root', and 'all objects must have required labels.

Admission control solves a category of problems that RBAC alone cannot. RBAC controls who can create or modify objects, admission control controls what those objects are allowed to contain. A developer with permission to create Deployments can create one with privileged: true unless an admission controller prevents it. This separation of concerns, RBAC for authorization, admission for content validation, is the right model for large clusters.

Mutating admission controllers are used for safe defaults enforcement. Injecting sidecar containers, setting default resource limits, adding required labels, or automatically mounting secrets. This reduces the chance of a missing required configuration causing a policy violation, because the mutation fills in the gap automatically. However, mutations should be transparent, engineers should know what the cluster is adding to their workloads, and should be documented.

Audit logs

Kubernetes audit logs record every request to the API server. Who made the request, what action was requested, on which resource, and what the outcome was. The audit policy defines which requests are logged and at what level of detail (None, Metadata, Request, RequestResponse). A complete audit policy balances capturing security-relevant events against the log volume that would make analysis impractical.

Security-relevant events to watch for include access to Secrets (especially from unexpected service accounts), creation or modification of ClusterRoleBindings (privilege escalation), exec into Pods (indicates hands-on access to a running container), changes to admission webhooks (could disable security controls), and API calls from the system:anonymous or system:unauthenticated subjects (should never occur in a properly configured cluster).

Audit logs are only valuable if they are collected and analyzed. The cluster should ship audit logs to an external log management system (Elasticsearch, Splunk, CloudWatch Logs) rather than storing them only on the API server nodes, where they can be lost or tampered with. Alerts on high-priority events should be configured so that security-relevant API activity triggers immediate review rather than being discovered days later during a manual log review.

A common failure mode is enabling audit logging but never defining what 'interesting' looks like. Without alert rules and a process for reviewing suspicious events, audit logs become a large archive that is only consulted after an incident has already been identified through other means. The value of audit logs is proactive detection, not just post-incident reconstruction.

Cluster hardening

Cluster hardening is the process of closing gaps between the cluster's default configuration and a secure operational baseline. The CIS Kubernetes Benchmark is the most widely used reference, covering the API server, controller manager, scheduler, etcd, kubelet, and the worker node configuration. Running the kube-bench tool against a cluster reports which CIS checks pass and fail, giving a concrete starting point for hardening.

etcd is the most sensitive component in the Kubernetes control plane. It stores all cluster state, including Secrets in base64-encoded form. Access to etcd should be restricted to the API server, direct access by humans or other systems should require strong authentication and be audited separately. etcd should use TLS for all communication and should be encrypted at rest. Backing up etcd regularly is critical. Losing the etcd data means losing the entire cluster state.

The API server exposes several options that reduce the attack surface. Disabling anonymous authentication, requiring client certificate authentication for internal component communication, enabling audit logging, restricting the set of allowed admission plugins, and, for older clusters, ensuring the insecure port (which accepted HTTP without authentication) is disabled. Many managed Kubernetes services (EKS, GKE, AKS) configure some of these correctly by default, but self-managed clusters require explicit attention.

At the node level, AppArmor and seccomp profiles add mandatory access control and system call filtering to container processes. Seccomp profiles restrict which system calls a container can make. The RuntimeDefault profile blocks many syscalls that are rarely needed by applications but are frequently used by exploits. AppArmor profiles limit file access, network access, and capability use at the kernel level. Both require explicit configuration in the Pod's security context to be applied, but the investment pays off by making many exploit techniques significantly harder to execute.