Kubernetes security is not a tool. It is a set of patterns that prevent the next incident — almost always caused by a forgotten cluster-admin or a namespace without isolated networking.
This chapter builds the minimum that every production cluster should have.
Course seal: tested on Kubernetes 1.35 with PSA and Calico/Cilium.
Practical threat model
Questions that are worth more than a generic checklist:
- If a pod is compromised, what can it reach? (network, API, secrets)
- Who can create pods? Pods create node access.
- Where are the secrets and who reads them?
- What runs with
privileged: trueand why?
RBAC without cluster-admin
Never give cluster-admin to an application. Create a Role in the namespace:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: portal-backend
namespace: portal
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: portal-backend
namespace: portal
subjects:
- kind: ServiceAccount
name: portal-backend
namespace: portal
roleRef:
kind: Role
name: portal-backend
apiGroup: rbac.authorization.k8s.io
In the Deployment:
spec:
template:
spec:
serviceAccountName: portal-backend
automountServiceAccountToken: false
automountServiceAccountToken: false when the app doesn't talk to the API is the cheapest win of the chapter: without a mounted token, a leaked credential in the container is useless.
Audit:
kubectl auth can-i --list --as=system:serviceaccount:portal:portal-backend -n portal
kubectl get clusterrolebindings -o json | jq -r '.items[] | select(.roleRef.name=="cluster-admin") | .metadata.name'
People: each with their own user (OIDC), not a shared kubeconfig.
Pod Security Admission
PSA replaced PSP. Three levels:
| Level | What it requires |
|---|---|
| privileged | nothing |
| baseline | blocks the worst (privileged, hostNetwork, hostPath) |
| restricted | runs as non-root, no privilege escalation, seccomp, dropped capabilities |
In the namespace:
apiVersion: v1
kind: Namespace
metadata:
name: portal
labels:
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
Start with warn + audit, fix the workloads, then enforce.
Pod compatible with restricted:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem breaks apps that write to /tmp. Mount an emptyDir at /tmp — and discover how many apps write where they shouldn't.
NetworkPolicy: default-deny
Without policy, every pod talks to every pod. Start by denying:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: portal
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
Then allow what is necessary:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-allow
namespace: portal
spec:
podSelector:
matchLabels:
app: portal-backend
policyTypes: ["Ingress", "Egress"]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: portal-postgres
ports:
- protocol: TCP
port: 5432
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
Two classic mistakes:
- Forgetting DNS in egress: everything becomes a timeout.
- Trusting IP: pods change IPs; use selectors.
Test:
kubectl -n portal run tmp --rm -it --image=busybox -- sh
# dentro: nc -zv portal-postgres 5432 (deve falhar se não liberado)
Secrets
What not to do:
- Secret in
values.yamlor in Git. - Secret in an environment variable printed in logs.
- The same secret in all environments.
Options:
- External Secrets Operator + Vault/AWS SM.
- Sealed Secrets (encrypted in Git).
- SOPS + age.
Regardless of the choice:
- rotate credentials;
- limit Secret read access per Role (Secret is namespaced, but read is power);
- enable
encryption at restin etcd; - avoid
kubectl get secret -o yamlin pipelines.
And the alert that already appeared in this course: minioadmin, changeme, admin/admin in manifests is an incident waiting to happen.
Runtime and images
- Images from a trusted registry, by digest.
imagePullPolicy: IfNotPresentwith immutable tag.- Admission policy (Kyverno) blocking
:latest,privileged, and hostPath. - Vulnerability scanner in the pipeline (chapter 24).
- Runtime security (Falco) to detect shells in containers.
Lab
- Apply
default-denyin the namespace and observe what breaks. - Allow DNS and real dependencies; validate with
nc/curl. - Enable PSA in
warnmode and list non-compliant pods. - Fix the
securityContextof a Deployment and move toenforce. - Create a minimal ServiceAccount + Role and test with
kubectl auth can-i. - Try
kubectl execas non-root and write to/(should fail).
Common mistakes
| Symptom | Cause |
|---|---|
| Timeout after NetworkPolicy | DNS not allowed in egress |
| Pod rejected by PSA | missing runAsNonRoot/seccomp |
| App breaks with readOnlyRoot | writing to /tmp without emptyDir |
| Cluster-admin everywhere | RBAC copied from tutorial |
| Secret readable by any pod | missing RBAC + isolated namespace |
| API token mounted without use | automountServiceAccountToken default |
| Policy does not apply | CNI without NetworkPolicy support |
Checklist
- [ ] ServiceAccount per application, token unmounted when possible.
- [ ] Minimal Roles and audit of
cluster-admin. - [ ] PSA
restricted(or justifiedbaseline) per namespace. - [ ]
securityContextwith non-root, seccomp, and dropped capabilities. - [ ] Default-deny NetworkPolicy + explicit allows.
- [ ] DNS allowed in egress.
- [ ] Secrets outside Git and defined rotation.
- [ ] etcd with encryption at rest.
- [ ] Admission policy for images and privileges.
- [ ] Runtime detection (Falco) evaluated.
Glossary
- RBAC: role-based access control.
- ServiceAccount: pod identity.
- PSA: Pod Security Admission.
- NetworkPolicy: firewall between pods.
- default-deny: denies everything and allows what is necessary.
- seccomp: syscall filter.
- External Secrets: operator that syncs external secrets.
Next
Hardened cluster. Now the question that brings down an environment: how much load can it handle? Capacity, k6, rate limit, and what happens when the database becomes the bottleneck.
Next: Kubernetes Course 26 — Capacity and Load: HPA, k6, and Rate Limit
Keep reading
Read also

StorCLI - Complete Cheat Sheet
StorCLI is the official and modern tool for managing Broadcom/LSI RAID controllers. This guide covers essential commands, syntax and use cases.
12 min de leitura
TutoriaisBusiness Services in Nyverra Portal: Translating IT into Business
The business doesn't feel a server go down — it feels email stop. The Business Services (Service Mapping) module of Nyverra ITSM Portal connects services like Email, ERP or PACS to CMDB assets, revealing the real impact of each failure.
5 min de leitura
SegurançaRPO vs RTO: How to Define Before Buying Backup (and Why Most Companies Get It Wrong)
Most companies buy backup asking the wrong question: "how much does it cost?". The right question is: how much data can I lose (RPO) and how long can I be down (RTO). Discover how to define these numbers per system and avoid purchasing protection based on price, not on real need.
9 min de leitura
Comments
No comments yet
Be the first to share your thoughts on this article.

Leave a comment