jan-karel.nl
Home / Securitymaatregelen / Cloudbeveiliging / Kubernetes Hardening

Kubernetes Hardening

Kubernetes Hardening

Cloud Snel, Cloud Strak

Cloudomgevingen veranderen snel. Daarom moet beveiliging hier standaard en geautomatiseerd meebewegen.

Voor Kubernetes Hardening is automatisering leidend: guardrails in code, least privilege en continue driftcontrole.

Zo houd je snelheid in de cloud, zonder dat veiligheid afhankelijk wordt van handmatig geluk.

Directe maatregelen (15 minuten)

Waarom dit telt

De kern van Kubernetes Hardening is risicoreductie in de praktijk. Technische context ondersteunt de maatregelkeuze, maar implementatie en borging staan centraal.

RBAC (Role-Based Access Control)

Kubernetes zonder RBAC is als een gebouw waar elke sleutel op elk slot past. RBAC bepaalt wie wat mag doen in het cluster. Het probleem: de meeste organisaties configureren RBAC met de subtiliteit van een voorhamer – cluster-admin aan iedereen, en klaar.

Type Scope Gebruik
Role Namespace-gebonden Toegang tot resources binnen een specifieke namespace
ClusterRole Cluster-breed Toegang tot cluster-brede resources (nodes, PVs) of herbruikbaar over namespaces
RoleBinding Namespace-gebonden Koppelt een Role/ClusterRole aan een subject binnen een namespace
ClusterRoleBinding Cluster-breed Koppelt een ClusterRole aan een subject voor het hele cluster

Vuistregel: gebruik Role en RoleBinding tenzij je expliciet cluster-brede toegang nodig hebt.

# Wat mag de huidige gebruiker?
kubectl auth can-i --list

# Wat mag een specifiek service account?
kubectl auth can-i --list --as=system:serviceaccount:default:my-app

# Alle ClusterRoleBindings met cluster-admin
kubectl get clusterrolebindings -o json | \
  jq '.items[] | select(.roleRef.name=="cluster-admin") |
    {name: .metadata.name, subjects: .subjects}'
# Restrictieve Role + RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods-production
  namespace: production
subjects:
- kind: Group
  name: "developers"
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

Veelgemaakte RBAC-fouten

Fout Risico Oplossing
Wildcard verbs ("*") Volledige controle inclusief delete Specificeer exact welke verbs nodig zijn
Wildcard resources ("*") Toegang tot secrets, configmaps, alles Specificeer exact welke resources
cluster-admin aan service accounts Container compromise = cluster compromise Minimale ClusterRole per applicatie
Default service account gebruiken Elke pod in de namespace deelt dezelfde rechten Dedicated service account per applicatie
Geen automountServiceAccountToken: false Token automatisch in elke pod Zet op false tenzij de pod de API nodig heeft

Pod Security Standards

Pod Security Standards (PSS) vervangen de verouderde PodSecurityPolicies. Afdwinging via Pod Security Admission (PSA).

Niveau Wat het toestaat Wanneer gebruiken
Privileged Alles – geen restricties Systeemcomponenten (kube-system)
Baseline Blokkeert bekende privilege escalations Algemene workloads
Restricted Maximale lockdown Productie-workloads
# PSA labels op namespace
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

SecurityContext: de juiste instellingen

apiVersion: v1
kind: Pod
metadata:
  name: hardened-app
  namespace: production
spec:
  automountServiceAccountToken: false
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: registry.company.nl/app:1.4.2@sha256:abc123...
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop: ["ALL"]
    resources:
      limits:
        memory: "256Mi"
        cpu: "500m"
    volumeMounts:
    - name: tmp
      mountPath: /tmp
  volumes:
  - name: tmp
    emptyDir: {}
Setting Waarom
runAsNonRoot: true Voorkomt dat de container als root draait
readOnlyRootFilesystem: true Geen schrijftoegang tot container-filesysteem
allowPrivilegeEscalation: false Blokkeert setuid/setgid en ptrace-escalatie
capabilities.drop: ALL Verwijdert alle Linux capabilities
seccompProfile: RuntimeDefault Blokkeert gevaarlijke syscalls

Network Policies

Zonder Network Policies kan elke pod met elke andere pod praten. Dat is het standaardgedrag.

# Default deny: blokkeer ALLE ingress en egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
---
# Allow: webapp mag naar de database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-webapp-to-db
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: database
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: webapp
    ports:
    - protocol: TCP
      port: 5432
---
# Blokkeer cloud metadata service
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-metadata-service
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 169.254.169.254/32
CNI Plugin Network Policies Egress FQDN Policies
Calico Ja Ja Ja (Enterprise)
Cilium Ja Ja Ja
Weave Net Ja Ja Nee
Flannel Nee Nee Nee
AWS VPC CNI Via Calico add-on Via Calico Nee

Let op: Flannel negeert Network Policies stilzwijgend. Geen foutmelding. De policies bestaan in de API maar worden niet afgedwongen.

Secrets management

Kubernetes Secrets zijn base64-gecodeerd. Niet versleuteld. Base64 is geen encryptie.

kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d
# Resultaat: P@ssw0rd123   <-- zo "veilig" zijn Kubernetes Secrets

Encryption at rest

# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
    - secrets
    providers:
    - aescbc:
        keys:
        - name: key1
          secret: <base64-encoded-32-byte-key>
    - identity: {}

External Secrets

Oplossing Voordeel Nadeel
HashiCorp Vault Volledige lifecycle, audit trail Complexe setup
AWS Secrets Manager Native integratie, automatische rotatie Vendor lock-in
Azure Key Vault Native Azure integratie Vendor lock-in
Sealed Secrets (Bitnami) Secrets veilig in git Geen rotatie, cluster-gebonden
# External Secrets Operator: secret uit Vault
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: db-credentials
  data:
  - secretKey: password
    remoteRef:
      key: secret/data/production/database
      property: password

Image security

# Kyverno: blokkeer images van niet-goedgekeurde registries
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: restrict-image-registries
spec:
  validationFailureAction: Enforce
  rules:
  - name: validate-registries
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "Images mogen alleen van goedgekeurde registries komen."
      pattern:
        spec:
          containers:
          - image: "registry.company.nl/* | europe-docker.pkg.dev/company/*"
# Trivy: scan en blokkeer bij CRITICAL findings
trivy image --exit-code 1 --severity CRITICAL registry.company.nl/app:1.4.2

# Grype: alternatieve scanner
grype registry.company.nl/app:1.4.2 --fail-on critical
Praktijk Waarom
Nooit latest gebruiken Reproduceerbaarheid, audit trail
Digest pinning (@sha256:...) Voorkomt tag overwriting (supply chain)
imagePullPolicy: Always Voorkomt stale cached images
Signed images (Cosign) Garandeert herkomst en integriteit

Admission Controllers

Feature OPA Gatekeeper Kyverno
Taal Rego (eigen taal) YAML (native K8s)
Leercurve Steil Laag
Mutatie Beperkt Ja
Generatie Nee Ja
# Kyverno: vereis resource limits + blokkeer privileged
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-resource-limits
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-limits
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "Containers moeten CPU en memory limits hebben."
      pattern:
        spec:
          containers:
          - resources:
              limits:
                memory: "?*"
                cpu: "?*"
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-privileged
spec:
  validationFailureAction: Enforce
  rules:
  - name: no-privileged
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "Privileged containers zijn niet toegestaan."
      deny:
        conditions:
          any:
          - key: "{{ request.object.spec.containers[].securityContext.privileged }}"
            operator: AnyIn
            value: [true]

etcd-beveiliging

etcd is de database van Kubernetes. Wie toegang heeft tot etcd, heeft alles: secrets, RBAC, de volledige cluster state.

Maatregel Implementatie Waarom
TLS client certs --client-cert-auth=true, --cert-file, --key-file Alleen geauthenticeerde clients
Peer TLS --peer-client-cert-auth=true etcd nodes authenticeren elkaar
Firewall Alleen TCP 2379/2380 van API server Niemand anders hoeft bij etcd
Backup-encryptie etcdctl snapshot save + GPG/AES Backups bevatten secrets in plaintext
Aparte nodes etcd op dedicated machines Reduceer aanvalsoppervlak

Audit logging

# /etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  - level: None
    nonResourceURLs: ["/healthz*", "/readyz*", "/livez*"]
  - level: Metadata
    resources:
    - group: ""
      resources: ["secrets", "configmaps"]
  - level: RequestResponse
    resources:
    - group: "rbac.authorization.k8s.io"
      resources: ["clusterroles", "clusterrolebindings", "roles", "rolebindings"]
  - level: RequestResponse
    resources:
    - group: ""
      resources: ["pods/exec", "pods/attach", "pods/portforward"]
  - level: Metadata
    omitStages: ["RequestReceived"]
# API server flags
# --audit-policy-file=/etc/kubernetes/audit-policy.yaml
# --audit-log-path=/var/log/kubernetes/audit.log
# --audit-log-maxage=30
# --audit-webhook-config-file=/etc/kubernetes/audit-webhook.yaml  (SIEM)
Audit Event Betekenis Prioriteit
kubectl exec in productie Interactieve shell in pod Hoog
Secret GET door onbekend SA Credential theft Kritiek
ClusterRoleBinding aangemaakt Privilege escalation Hoog
Pod met hostPID: true Container escape Kritiek

Veelvoorkomende fouten

# Fout Impact Oplossing
1 cluster-admin voor applicatie SA’s Cluster takeover bij pod compromise Minimale Role per applicatie
2 Geen Network Policies Laterale beweging tussen alle pods Default deny + expliciete allow
3 Containers als root Privilege escalation runAsNonRoot: true
4 Secrets in env vars Zichtbaar via kubectl describe pod Volume mounts of External Secrets
5 latest tag op images Supply chain risico Versie + digest pinning
6 Geen resource limits DoS op het cluster Verplichten via admission controller
7 etcd zonder TLS Cluster data leesbaar op netwerk TLS met client certificaten
8 Dashboard exposed Cluster controle via browser Verwijderen of achter VPN
9 Kubelet API open Node-level command execution --anonymous-auth=false
10 Geen audit logging Blind vliegen Audit policy + SIEM

Checklist

# Maatregel Prioriteit
1 RBAC: geen wildcard verbs/resources, geen cluster-admin voor apps Kritiek
2 RBAC: dedicated service account, automountServiceAccountToken: false Hoog
3 PSA: restricted op productie-namespaces Kritiek
4 SecurityContext: runAsNonRoot, readOnlyRootFilesystem, drop ALL caps Kritiek
5 Network Policies: default deny ingress + egress Kritiek
6 Network Policies: metadata service geblokkeerd, CNI enforcement geverifieerd Kritiek
7 Secrets: encryption at rest + external secrets operator Hoog
8 Images: goedgekeurde registries, geen latest, scanning in CI/CD Hoog
9 Admission controller: Kyverno of OPA Gatekeeper actief Hoog
10 etcd: TLS + firewall + versleutelde backups Kritiek
11 Audit logging: policy geconfigureerd + SIEM integratie Hoog
12 Kubelet: anonymous auth uitgeschakeld Kritiek

Kubernetes is het systeem dat zo complex is dat er een hele industrie is ontstaan om het te beheren. Denk daar even over na: het platform dat bedoeld was om deployments te vereenvoudigen, is zo ingewikkeld dat je er consultants, certificeringen en gespecialiseerde teams voor nodig hebt. En dat zijn de organisaties die het serieus nemen.

De rest – en dat is het merendeel – heeft een Kubernetes-cluster omdat iemand op een conferentie heeft gehoord dat “iedereen het doet.” Ze hebben vijftig microservices, drie mensen die weten wat een Pod is, en nul Network Policies. De containers draaien als root, het dashboard hangt aan het internet, en de enige RBAC-configuratie is dat iedereen cluster-admin is “zodat het werkt.”

Het mooiste is nog de illusie van container-isolatie. “We draaien in containers, dus we zijn veilig.” Ja, containers die als root draaien. Met hostNetwork. Met hostPID. Met het volledige Linux capability-set. Dat is geen isolatie – dat is een root shell met extra stappen. Maar het staat mooi op de architectuurslide.

Elk startupje heeft tegenwoordig een Kubernetes-cluster. Niet omdat ze het nodig hebben – een monoliet op een VM was prima geweest – maar omdat het op het CV staat. En die cluster? Geen security policy. Geen audit logging. Geen Network Policies. Maar wel een helm chart met elf dependencies en een Slack-integratie die elke deployment viert met een feesttoeter-emoji. Prioriteiten.

Samenvatting

Kubernetes hardening draait om lagen. RBAC beperkt wie wat mag doen. Pod Security Standards beperken wat containers mogen. Network Policies beperken met wie ze mogen praten. Secrets management beschermt gevoelige data. Image security garandeert dat je vertrouwde code draait. Admission controllers dwingen al deze regels af. etcd-beveiliging beschermt de kroonjuwelen van het cluster. En audit logging zorgt dat je ziet wat er gebeurt. Geen van deze maatregelen is optioneel. Begin met de kritieke items uit de checklist, en test regelmatig of de policies daadwerkelijk worden afgedwongen.

In het volgende hoofdstuk kijken we naar Infrastructure as Code security – hoe je ervoor zorgt dat de Terraform, Pulumi en CloudFormation templates waarmee je deze clusters bouwt, niet zelf de bron zijn van misconfiguraties en kwetsbaarheden.

Op de hoogte blijven?

Ontvang maandelijks cybersecurity-inzichten in je inbox.

← Cloudbeveiliging ← Home