Salta al contenuto principale

06 - Sicurezza Kubernetes: RBAC, Pod Security Standards e OPA Gatekeeper

Sicurezza Kubernetes: RBAC, Pod Security Standards e OPA Gatekeeper Un cluster Kubernetes di default e sorprendentemente insicuro: i container possono...

Cosa ne pensi di questo articolo?

06 - Sicurezza Kubernetes: RBAC, Pod Security Standards e OPA Gatekeeper
Kubernetes AvanzatoFoto:Brett SaylesPexels

In Breve

Sicurezza Kubernetes: RBAC, Pod Security Standards e OPA Gatekeeper Un cluster Kubernetes di default e sorprendentemente insicuro: i container possono...

Sicurezza Kubernetes: RBAC, Pod Security Standards e OPA Gatekeeper

Un cluster Kubernetes di default e sorprendentemente insicuro: i container possono girare come root, montare il filesystem del nodo host, accedere all'API server con token di ServiceAccount con permessi ampi. Nel 2025, il 68% degli incidenti di sicurezza in ambienti containerizzati era attribuibile a errori di configurazione invece che a vulnerabilita software (fonte: Sysdig Container Security Report 2025). La buona notizia: Kubernetes offre strumenti potenti per eliminare queste configurazioni rischiose.

Questo articolo copre un hardening completo del cluster: RBAC per controllare chi puo fare cosa con l'API Kubernetes, Pod Security Standards per prevenire container privilegiati e configurazioni rischiose, e OPA Gatekeeper per implementare policy aziendali personalizzate come "tutte le immagini devono provenire dal registro interno" o "nessun deployment senza resource limits".

Cosa Imparerai

  • Modello di autorizzazione Kubernetes: RBAC, verbi, risorse, scope
  • Principio del minimo privilegio: Role, ClusterRole, RoleBinding applicati
  • ServiceAccount: come limitare i token auto-mounted e usare IRSA/Workload Identity
  • Pod Security Standards: Privileged, Baseline, Restricted e enforcement per namespace
  • OPA Gatekeeper: installazione, ConstraintTemplate in Rego, Constraint
  • Policy comuni: immagini da registry approvate, resource limits obbligatori, no root
  • Audit logging: monitorare chi fa cosa nel cluster
  • Checklist di hardening completa

RBAC: Controllo degli Accessi Basato sui Ruoli

RBAC (Role-Based Access Control) e il meccanismo di autorizzazione principale di Kubernetes. Definisce chi (Subject: User, Group, ServiceAccount) puo eseguire quale azione (verb: get, list, watch, create, update, patch, delete) su quale risorsa (Resource: pods, deployments, secrets) in quale scope (namespace con Role/RoleBinding, o cluster intero con ClusterRole/ClusterRoleBinding).

Role e RoleBinding per un Team di Sviluppo

# rbac-developer-role.yaml
# Un ruolo per i developer: possono vedere e modificare
# deployment/pod/service nel loro namespace, ma non secrets
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer
  namespace: team-alpha
rules:
  # Deployment: lettura + scaling
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  # Pod: lettura + exec + logs
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["pods/log"]
    verbs: ["get", "list"]
  - apiGroups: [""]
    resources: ["pods/exec"]
    verbs: ["create"]
  # Service e ConfigMap: accesso completo
  - apiGroups: [""]
    resources: ["services", "configmaps", "endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  # HPA
  - apiGroups: ["autoscaling"]
    resources: ["horizontalpodautoscalers"]
    verbs: ["get", "list", "watch"]
  # Ingress
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: team-alpha-developers
  namespace: team-alpha
subjects:
  # Gruppo di utenti (gestito da OIDC/SSO)
  - kind: Group
    name: "team-alpha-devs"
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: developer
  apiGroup: rbac.authorization.k8s.io

ClusterRole per SRE/Admin con Scope Limitato

# rbac-sre-clusterrole.yaml
# SRE: accesso in lettura a tutto il cluster, write solo su namespace specifici
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-reader
rules:
  - apiGroups: ["*"]
    resources: ["*"]
    verbs: ["get", "list", "watch"]
  # Nega esplicita: non puo leggere i secrets (sovrascritta da DENY)
  # NOTA: in RBAC Kubernetes non esiste DENY esplicita - usa Gatekeeper per questo
---
# SRE: read su tutto + write limitato ai namespace di produzione
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: sre-cluster-reader
subjects:
  - kind: Group
    name: "platform-sre"
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-reader
  apiGroup: rbac.authorization.k8s.io

# Verifica i permessi di un utente/serviceaccount
kubectl auth can-i create pods --as=system:serviceaccount:production:api-server-sa
kubectl auth can-i delete secrets --as=developer-user -n production
kubectl auth can-i '*' '*' --as=alice  # lista tutto quello che alice puo fare

ServiceAccount: Limitare i Token Automatici

Per default, ogni Pod monta automaticamente un token ServiceAccount valido. Nei Pod che non chiamano l'API Kubernetes, questo token e inutile ma aumenta la superficie di attacco:

# serviceaccount-minimal.yaml
# ServiceAccount dedicata per ogni applicazione (mai usare default)
apiVersion: v1
kind: ServiceAccount
metadata:
  name: api-server-sa
  namespace: production
  annotations:
    # Su AWS EKS: delega i permessi IAM tramite IRSA
    eks.amazonaws.com/role-arn: "arn:aws:iam::123456789:role/ApiServerRole"
automountServiceAccountToken: false  # non montare il token automaticamente
---
# Deployment che usa la ServiceAccount
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
  namespace: production
spec:
  template:
    spec:
      serviceAccountName: api-server-sa
      automountServiceAccountToken: false  # ridondante ma esplicito
      containers:
        - name: api-server
          image: my-registry/api-server:1.2.0
---
# Se il Pod deve chiamare l'API K8s, usa un token proiettato con scadenza
# invece del token auto-mounted (che non scade mai)
apiVersion: v1
kind: Pod
spec:
  serviceAccountName: api-server-sa
  volumes:
    - name: api-token
      projected:
        sources:
          - serviceAccountToken:
              path: token
              expirationSeconds: 3600  # scade ogni ora
              audience: kubernetes.default.svc
  containers:
    - name: api-server
      volumeMounts:
        - name: api-token
          mountPath: /var/run/secrets/kubernetes.io/serviceaccount
          readOnly: true

Pod Security Standards (PSS)

I Pod Security Standards sostituiscono le deprecate PodSecurityPolicies (rimosse in K8s 1.25). Definiscono tre livelli di sicurezza applicabili a livello di namespace tramite label:

  • Privileged: nessuna restrizione (solo per namespace di sistema come kube-system)
  • Baseline: previene le configurazioni piu note e rischiose (privileged containers, hostPath, hostNetwork)
  • Restricted: best practice attuali per sicurezza massima (non-root, seccomp, capabilities drop)

Ogni livello ha tre modalita: enforce (blocca il Pod), audit (registra nel log ma non blocca), warn (mostra un warning all'utente).

Applicare Pod Security Standards ai Namespace

# Applica PSS al namespace tramite label
kubectl label namespace production \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=latest \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/audit-version=latest \
  pod-security.kubernetes.io/warn=restricted \
  pod-security.kubernetes.io/warn-version=latest

# Per namespace di sistema che richiedono pod privilegiati
kubectl label namespace kube-system \
  pod-security.kubernetes.io/enforce=privileged

# Testa cosa succederebbe se applicassi restricted a un namespace esistente
kubectl label --dry-run=server --overwrite namespace production \
  pod-security.kubernetes.io/enforce=restricted

Pod Conforme al Livello Restricted

# pod-restricted-compliant.yaml
# Un Pod che rispetta tutte le regole del livello Restricted
apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
  namespace: production
spec:
  securityContext:
    runAsNonRoot: true          # non girare come root
    runAsUser: 1000             # UID non-root
    runAsGroup: 3000
    fsGroup: 2000
    seccompProfile:
      type: RuntimeDefault      # profilo seccomp di default
    supplementalGroups: [1000]
  containers:
    - name: app
      image: my-registry/api-server:1.2.0
      securityContext:
        allowPrivilegeEscalation: false  # fondamentale: previene sudo
        readOnlyRootFilesystem: true     # filesystem immutabile
        capabilities:
          drop:
            - ALL                # rimuovi tutte le Linux capabilities
          # add: [] - non aggiungere nessuna capability
        runAsNonRoot: true
        seccompProfile:
          type: RuntimeDefault
      resources:
        requests:
          cpu: "100m"
          memory: "128Mi"
        limits:
          cpu: "500m"
          memory: "512Mi"
      volumeMounts:
        - name: tmp
          mountPath: /tmp      # se l'app scrive su /tmp, monta un volume
        - name: cache
          mountPath: /app/cache
  volumes:
    - name: tmp
      emptyDir: {}
    - name: cache
      emptyDir: {}
  automountServiceAccountToken: false

OPA Gatekeeper: Policy Engine per Kubernetes

I Pod Security Standards coprono un insieme fisso di controlli. OPA Gatekeeper permette di definire policy personalizzate usando Rego, il linguaggio di OPA. E implementato come un Admission Webhook che intercetta ogni richiesta all'API server e la valida contro le policy definite.

Installazione OPA Gatekeeper

# Installa Gatekeeper con Helm
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update

helm install gatekeeper gatekeeper/gatekeeper \
  --namespace gatekeeper-system \
  --create-namespace \
  --version 3.17.0 \
  --set auditInterval=30 \
  --set constraintViolationsLimit=100 \
  --set logLevel=INFO

# Verifica installazione
kubectl get pods -n gatekeeper-system

ConstraintTemplate: Immagini Solo da Registry Approvati

# constraint-template-registry.yaml
# Il template definisce lo schema e la logica in Rego
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sallowedrepos
  annotations:
    description: "Richiede che le immagini provengano solo da registry approvati"
spec:
  crd:
    spec:
      names:
        kind: K8sAllowedRepos
      validation:
        openAPIV3Schema:
          type: object
          properties:
            repos:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sallowedrepos

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not starts_with_allowed(container.image)
          msg := sprintf("Il container '%v' usa l'immagine '%v' che non proviene da un registry approvato", [container.name, container.image])
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.initContainers[_]
          not starts_with_allowed(container.image)
          msg := sprintf("L'initContainer '%v' usa l'immagine '%v' non approvata", [container.name, container.image])
        }

        starts_with_allowed(image) {
          repo := input.parameters.repos[_]
          startswith(image, repo)
        }
---
# Constraint: applica la policy con i parametri specifici
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
  name: require-approved-registry
spec:
  enforcementAction: deny  # deny|warn|dryrun
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    excludedNamespaces:
      - kube-system
      - gatekeeper-system
      - monitoring
  parameters:
    repos:
      - "registry.company.internal/"
      - "gcr.io/company-project/"
      - "public.ecr.aws/company/"

ConstraintTemplate: Resource Limits Obbligatori

# constraint-template-resource-limits.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredresources
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredResources
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredresources

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not container.resources.limits.cpu
          msg := sprintf("Container '%v': cpu limit obbligatorio ma mancante", [container.name])
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not container.resources.limits.memory
          msg := sprintf("Container '%v': memory limit obbligatorio ma mancante", [container.name])
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not container.resources.requests.cpu
          msg := sprintf("Container '%v': cpu request obbligatoria ma mancante", [container.name])
        }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredResources
metadata:
  name: require-resource-limits
spec:
  enforcementAction: deny
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    excludedNamespaces:
      - kube-system
      - gatekeeper-system

ConstraintTemplate: No Container Root

# constraint-template-no-root.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8spsphostnamespace
spec:
  crd:
    spec:
      names:
        kind: K8sPSPHostNamespace
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8spsphostnamespace

        violation[{"msg": msg}] {
          input.review.object.spec.hostPID == true
          msg := "hostPID non e consentito"
        }

        violation[{"msg": msg}] {
          input.review.object.spec.hostIPC == true
          msg := "hostIPC non e consentito"
        }

        violation[{"msg": msg}] {
          input.review.object.spec.hostNetwork == true
          not input.review.object.metadata.annotations["policy.company.internal/exempt-hostnetwork"]
          msg := "hostNetwork non e consentito senza annotation di esenzione"
        }

# Verifica violazioni esistenti nel cluster
kubectl get constraints
kubectl describe k8srequiredresources require-resource-limits
# Nella sezione Status.Violations vedrai tutti i Pod non conformi

Audit Logging

Kubernetes puo registrare ogni richiesta all'API server in un audit log strutturato. E fondamentale per compliance e investigazione di incidenti.

# audit-policy.yaml - configurazione dell'audit log
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  # Registra tutto sui Secrets a livello RequestResponse (incluso il contenuto)
  - level: RequestResponse
    resources:
      - group: ""
        resources: ["secrets"]

  # Registra le modifiche a RBAC
  - level: RequestResponse
    resources:
      - group: "rbac.authorization.k8s.io"
        resources: ["roles", "clusterroles", "rolebindings", "clusterrolebindings"]

  # Registra exec nei Pod (potenziale accesso malevolo)
  - level: Request
    resources:
      - group: ""
        resources: ["pods/exec", "pods/portforward", "pods/proxy"]

  # Per tutto il resto: registra solo i metadata (chi ha fatto cosa, quando)
  - level: Metadata
    omitStages:
      - RequestReceived

# Kube-apiserver config (aggiungi all'avvio di kube-apiserver):
# --audit-log-path=/var/log/kubernetes/audit.log
# --audit-log-maxage=30
# --audit-log-maxbackup=10
# --audit-log-maxsize=100
# --audit-policy-file=/etc/kubernetes/audit-policy.yaml

Checklist di Hardening Kubernetes

Security Hardening Checklist

  • RBAC: Principio del minimo privilegio per ogni ServiceAccount e gruppo utenti
  • ServiceAccount: automountServiceAccountToken: false su tutti i Pod che non chiamano l'API server
  • Token scadenza: usa token proiettati con expirationSeconds invece dei token permanenti
  • Pod Security Standards: restricted su tutti i namespace di produzione, baseline su staging
  • No root: runAsNonRoot: true e allowPrivilegeEscalation: false su tutti i container
  • Filesystem immutabile: readOnlyRootFilesystem: true + volume emptyDir per /tmp e /cache
  • Resource limits: CPU e memory limits su tutti i container (Gatekeeper per enforcement)
  • Registry approvati: Gatekeeper constraint che blocca immagini da registry non autorizzati
  • Network Policy: default-deny su tutti i namespace di produzione (vedi Articolo 1)
  • Secrets: usa external secret manager (AWS Secrets Manager, Vault) invece di Secret Kubernetes plain
  • Audit log: abilitato e centralizzato in un SIEM o log aggregator
  • etcd: cifrato at-rest con encryption config
  • API server: accesso anonimo disabilitato, admission controllers abilitati

Errori di Sicurezza Comuni

  • Namespace default per i workload: il namespace default ha RBAC permissivo per default; usa sempre namespace dedicati per i tuoi workload
  • ClusterAdmin per i CI/CD pipeline: le pipeline GitOps non hanno bisogno di cluster-admin; crea un ServiceAccount con i permessi minimi necessari per il namespace target
  • Secret come variabili di ambiente: le variabili di ambiente sono visibili in kubectl describe pod e nei log; usa file montati da Secrets o external secret managers
  • Tag latest nelle immagini: il tag latest puo cambiare; usa sempre digest SHA256 o tag immutabili per garantire riproducibilita e sicurezza
  • Ignorare le CVE del base image: scansiona regolarmente le immagini con Trivy, Snyk o Grype; un base image Ubuntu con CVE critiche annulla il lavoro di hardening del cluster

Conclusioni e Prossimi Passi

La sicurezza di un cluster Kubernetes e un processo a strati: RBAC controlla l'accesso all'API, Pod Security Standards previene configurazioni rischiose a livello di Pod, e OPA Gatekeeper permette di codificare le policy aziendali come codice versionabile e auditabile. Nessuno di questi strumenti da solo e sufficiente; insieme formano una difesa in profondita (defense-in-depth) che riduce drasticamente la superficie di attacco.

Il passo successivo per rafforzare ulteriormente la sicurezza e implementare un sistema di runtime security come Falco, che monitora le system call dei container in tempo reale e allerta su comportamenti anomali (shell aperta in un container in produzione, lettura di file di credenziali, connessioni di rete inattese).

Serie Correlate

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti