Kubernetes defaults are permissive: pods can usually talk to every other pod, service accounts get mounted whether or not they are used, and RBAC accumulates breadth over time. The controls with the best return are a pod security baseline enforced at admission, default-deny network policy in sensitive namespaces, tight RBAC reviewed against actual usage, and admission control on image provenance.
How do you secure a Kubernetes cluster?
Secure a Kubernetes cluster by enforcing a pod security baseline at admission so privileged workloads cannot be created, applying default-deny network policies so pods cannot reach services they do not need, keeping RBAC scoped and reviewed against actual usage, avoiding long-lived Secret objects in favour of runtime-injected credentials, and restricting which images may be deployed.
What the defaults actually give you
It is worth being explicit about the starting position, because several defaults surprise people.
- All pods can reach all other pods across all namespaces. Namespaces are not a network boundary without network policies.
- A service account token is mounted into every pod by default, whether the workload uses the API or not.
- Containers run as whatever user the image specifies, which for many public images is root.
- Secret objects are base64-encoded, not encrypted, and stored in etcd.
- Any image from any registry can be deployed unless admission policy restricts it.
Pod security standards, enforced at admission
Pod Security Admission provides three levels (privileged, baseline and restricted) applied per namespace. Restricted is where you want production to end up: non-root, no privilege escalation, dropped capabilities, seccomp enabled.
apiVersion: v1
kind: Namespace
metadata:
name: payments
labels:
# Enforce restricted; also warn and audit so violations are visible
# in tooling that has not caught up yet.
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restrictedNetwork policy: default deny, then allow
Flat pod networking means a single compromised pod can reach every service in the cluster. Network policy is the control that limits lateral movement, and it is the one most often skipped because it requires knowing what actually talks to what.
# Deny all ingress in the namespace, then add explicit allows.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: payments
spec:
podSelector: {}
policyTypes: [Ingress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-api-from-gateway
namespace: payments
spec:
podSelector:
matchLabels: { app: payments-api }
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector:
matchLabels: { name: edge }
podSelector:
matchLabels: { app: gateway }
ports:
- protocol: TCP
port: 8080The practical route is to derive the allow list from observed traffic rather than from an architecture diagram. Observed dependencies reflect what the system actually does; diagrams reflect what it was supposed to do two refactors ago.
RBAC that stays tight
RBAC accumulates. A binding granted for an incident stays. A role widened to unblock a deployment is never narrowed. Within a year, nobody can answer who can delete a production namespace.
- Prefer Role over ClusterRole. Most workloads need namespace scope, not cluster scope.
- Avoid wildcard verbs and resources. A role with * on * is an administrator whatever it is called.
- Set automountServiceAccountToken: false on workloads that never call the API, which is most of them.
- Review against usage, not against the list of grants. A permission never exercised in six months is a straightforward revocation.
- Watch for the escalation paths: create pod plus a privileged service account, or the ability to create bindings, are effectively cluster admin.
Secrets and supply chain at admission
Kubernetes Secrets are base64-encoded objects in etcd. With encryption at rest and tight RBAC they can be acceptable, but they are long-lived, unrotated and produce no read audit. Projecting short-lived credentials at runtime from a dedicated store avoids all three problems.
On the supply chain side, admission control is where policy becomes enforcement: restrict which registries may be pulled from, require image signatures, and require a digest rather than a tag. That last one alone prevents an entire class of substitution.
A prioritised order
- Pod security baseline in warn mode; fix violations; then enforce.
- Disable service account token automounting where it is not needed.
- Default-deny network policy in the most sensitive namespaces, with allows derived from observed traffic.
- RBAC review against usage; remove wildcards and unused bindings.
- Move secrets out of Secret objects into runtime injection.
- Registry restriction and signature verification at admission.
- Continuous posture checking so drift from all of the above is reported.
Key takeaways
- Kubernetes defaults are permissive: flat networking, mounted tokens, unrestricted images.
- Enforce pod security standards at admission, but start in warn mode.
- Default-deny network policy is the main control against lateral movement.
- Derive network allow lists from observed traffic, not from architecture diagrams.
- Review RBAC against actual usage; unused grants are easy revocations.
- Admission control on registries, signatures and digests closes the supply chain.
Frequently asked questions
Three predefined policy levels (privileged, baseline and restricted) that Pod Security Admission enforces per namespace. Restricted requires non-root execution, no privilege escalation, dropped capabilities and a seccomp profile.
A mesh gives you mutual TLS and Layer 7 authorisation, which is stronger for traffic it handles. Network policies still matter as a Layer 3 and 4 control for traffic outside the mesh and as defence in depth if mesh configuration is wrong.
Not by default: they are base64-encoded and stored in etcd. Encryption at rest can be enabled, which protects the etcd data but does not address the absence of rotation or of a read audit trail.
Over-permissive RBAC, closely followed by running containers as root and having no network policies. None is exotic; all three are the result of defaults being permissive and nobody revisiting them.
Resolve Roles, ClusterRoles and their bindings into effective permissions per subject. Doing this per cluster by hand does not scale past a few clusters, which is why DevOpsArk resolves it across the fleet into one model.
It is a reasonable choice for mutual TLS and fine-grained authorisation between services, but it adds significant operational complexity. Get pod security, network policy and RBAC right first: they cover more risk for far less overhead.