Kubernetes security: what I actually enforce

Best-practice lists are easy to write and cheap to believe. This is the list I actually enforce on the clusters I operate, with the manifests I use.

1. Network policies first

Default-deny is non-negotiable. Everything else is allowlisting traffic that should never have existed:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: app
spec:
  podSelector: {}
  policyTypes: [Ingress, Egress]

Then allow only what the workload needs — namespace to namespace, port to port. CNI choice matters here; I use Cilium or Calico, both with NetworkPolicy support.

Lesson: the first time you enable default-deny on a running namespace, you discover exactly which implicit traffic your app was secretly relying on. Budget a rollback window for that.

2. RBAC with real least privilege

Rules of thumb I apply:

  • No cluster-admin outside the platform team, and even then, never in CI tokens.
  • CI gets per-namespace service accounts, not shared credentials.
  • Regular reviews: kubectl auth can-i --list per team, and revoke what’s unused.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: app
  name: deployer
rules:
  - apiGroups: ['apps', '']
    resources: ['deployments', 'services', 'configmaps']
    verbs: ['get', 'list', 'create', 'update', 'patch']

Lesson: an audit of can-i --list outputs is the cheapest RBAC cleanup you will ever do — most clusters accumulate rights nobody still needs.

3. Secrets: no plaintext, no ConfigMaps

ConfigMaps are for configuration, not credentials. Depending on the platform:

  • ExternalSecrets pulling from a vault on the cluster side,
  • SealedSecrets when the cluster must be self-contained,
  • direct Vault integration for the sensitive ones.

Whatever the mechanism, the rule is the same: the manifest repository must never contain a real secret, and git history must never have contained one.

4. Image security: scan before deploy, not after

Every build pushes to a registry where the scan runs at rest, and promotion is gated on it:

# .gitlab-ci.yml — extract
scan_image:
  stage: security
  image: aquasec/trivy
  script:
    - trivy image --exit-code 1 --severity HIGH,CRITICAL "$IMAGE_TAG"

--exit-code 1 is the important part: a failing scan must fail the pipeline. Add admission-time enforcement (Kyverno or similar) as the second gate, so images can’t bypass the pipeline path entirely.

What this buys you

Network segmentation, least privilege, secrets hygiene and image scanning cover the majority of what actually gets clusters compromised. None of it is exciting — and that is precisely the point. Security work that makes news is usually the work you skipped.


Written by Rulx Philomé Alexis · ← All notes