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: falsesu tutti i Pod che non chiamano l'API server - Token scadenza: usa token proiettati con
expirationSecondsinvece dei token permanenti - Pod Security Standards:
restrictedsu tutti i namespace di produzione,baselinesu staging - No root:
runAsNonRoot: trueeallowPrivilegeEscalation: falsesu 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 pode 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).
Prossimi Articoli della Serie Kubernetes at Scale
Articoli Precedenti
Serie Correlate
- DevSecOps - policy-as-code, security scanning nelle pipeline CI/CD
- Kubernetes Networking - Network Policy per isolare i workload
- Platform Engineering - sicurezza come guardrail automatici per i team

Commenti
Caricamento commenti...
Accedi per commentare