Kubernetes - secure runtime
A Kubernetes cluster is a distributed operating system: RBAC, admission controllers and policies govern what each pod can do, mount and expose.
What it is
Kubernetes is not just a container orchestrator, it is a distributed operating system with its own : API, etcd, pod network, secrets and volumes. Each layer brings controls: RBAC says who can ask what of the API, admission controllers say what can enter the cluster, NetworkPolicies say what each pod can talk to among the others, and the kubelet says what each container can do on the node.
Treating Kubernetes as 'just a deploy runner' is the mistake that leaks tokens in logs, opens ports to the internet and runs containers as root with writable filesystems. Defending the cluster requires all four controls combined, not just one.
RBAC: the first fence
RBAC (Role-Based Access Control) links a subject (ServiceAccount, User, Group) to a role, and the role to a set of verbs over resources. It works at two levels: Role + RoleBinding (namespace scope) and ClusterRole + ClusterRoleBinding (whole-cluster scope).
The classic mistake is granting cluster-admin to the CI pipeline or to an application service account. It works, the deploy passes, and any RCE in a pod becomes full control of the cluster, including reading all secrets in all namespaces.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: ci-deployer-all
subjects:
- kind: ServiceAccount
name: ci-runner
namespace: ci
roleRef:
kind: ClusterRole
name: cluster-admin # total power, across the whole cluster
apiGroup: rbac.authorization.k8s.ioapiVersion: v1
kind: Namespace
metadata:
name: payments
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployer
namespace: payments
rules:
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ci-deployer
namespace: payments
subjects:
- kind: ServiceAccount
name: ci-runner
namespace: ci
roleRef:
kind: Role
name: deployer
apiGroup: rbac.authorization.k8s.ioNetworkPolicies: the pods' firewall
By default, every pod talks to every pod in every namespace. NetworkPolicies fix this: they are pod selectors that say who can come in and go out, with allowlist semantics (what is not allowed is blocked).
The first policy is always deny-by-default in the namespace. Without it, opening a new port on a pod is opening a new port across the whole cluster. The second comes with explicit allows for legitimate traffic (ingress from the ingress controller, egress to external APIs, pod-to-pod within the same app).
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: payments
spec:
podSelector: {} # applies to all pods in the namespace
policyTypes:
- Ingress
- Egress
ingress: [] # nothing gets in
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53 # DNS to resolve external servicesAdmission control: policy before the pod starts
Admission controllers sit between accepting the request and persisting it in etcd. They can validate (reject) or mutate (rewrite) the manifest. Kubernetes ships Pod Security Admission (PSA) built in since 1.23, with privileged, baseline and restricted profiles. For finer logic, policy engines come in:
- Gatekeeper: uses Rego, policy-as-code syntax, ideal for teams with a policy engineering culture
- Kyverno: pure YAML, reads like a normal Kubernetes manifest, ideal for teams that want to write policies without learning another language
- Pod Security Admission: native, no extra dependency, baseline and restricted profiles cover 80% of cases
- ValidatingAdmissionPolicy (KEP-3485): declarative admission in CEL, stabilizing in recent versions
Secrets in etcd: encryption at rest
By default, secrets in etcd are stored in plain text (only base64 in the YAML). Anyone with read access to etcd, or to a backup of it, reads all tokens, passwords and keys. Encryption at rest solves this: each etcd key is encrypted with a key wrapped by a .
The price is managing a key (rotation, revocation), and what you gain is : even an etcd dump becomes inert data without the KMS.
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- kms:
name: aws-kms
endpoint: unix:///var/run/kms-provider/socket
cachesize: 100
keyid: arn:aws:kms:us-east-1:111122223333:key/abcd-...
- identity: {} # fallback for reading old dataWhere it fits
Kubernetes is the border between what the pipeline produced and what will run in production. Every defense that failed earlier (unscanned image, manifest without policy) lands here: admission control rejects, NetworkPolicy isolates, RBAC limits the damage. On the other hand, it is the last chance to fix what slipped through: PSP/PSA catches a loose manifest, OPA catches wrong labels, secrets in KMS defeat an etcd dump.
Strengths
- native Kubernetes controls already cover RBAC, NetworkPolicy, PSA, encryption at rest: nothing to buy
- (Kyverno, Gatekeeper) becomes a PR matter, with review and testing, not an urgent ticket
- namespaces become cheap isolation units, giving a limit without needing separate clusters
Limitations
- RBAC, NetworkPolicy and admission are default-deny controls: they must be configured before anything runs, and the migration path for an existing cluster is manual
- the whole cluster is only as secure as the node: an exposed kubelet or a privileged container breaks the guarantees of the layers above
- operating policies at scale (X clusters, Y namespaces) becomes platform work, not app work, and without someone accountable it turns into a mess
connections on the map
- CSPM - cloud postureaudits postureKubernetes - secure runtime
- CWPP - workload protectiondetects behaviorKubernetes - secure runtime
updated on 14/10/2026 · sources to go deeper · report an error
Study before
test what you learned
Loading questions…