Scheduling, RBAC e Sicurezza
Playground per questo incontro: usa il playground Kubernetes multi-nodo su iximiuz Labs: https://labs.iximiuz.com/playgrounds/k8s-omni
Obiettivi dell'incontro
Al termine di questo incontro i partecipanti saranno in grado di:
- Progettare un sistema RBAC con Role, ClusterRole, RoleBinding e ServiceAccount
- Applicare il principio del minimo privilegio e verificarlo con
kubectl auth can-i - Distribuire workload in più zone con TopologySpreadConstraints
- Configurare Taints e Tolerations per nodi dedicati
- Usare PriorityClass per gestire la preemption dei Pod
- Applicare ResourceQuota e LimitRange per la governance multi-tenant
- Fare hardening di un Pod usando SecurityContext (runAsNonRoot, readOnlyRootFilesystem, capabilities)
- Applicare Pod Security Standards a un namespace
Teoria (50 min)
RBAC — Role-Based Access Control
Kubernetes RBAC risponde alla domanda: "chi può fare cosa su quali risorse?"
I 4 Oggetti RBAC
| Oggetto | Scope | Funzione |
|---|---|---|
| Role | Namespace | Definisce permessi su risorse in un namespace |
| ClusterRole | Cluster | Permessi cluster-wide (nodi, PV, namespace, CRD) |
| RoleBinding | Namespace | Associa Role o ClusterRole a Subject nel namespace |
| ClusterRoleBinding | Cluster | Associa ClusterRole a Subject a livello cluster |
Subjects (chi ha i permessi):
User— utente umano (da certificato o OIDC token)Group— gruppo di utentiServiceAccount— identità per i Pod
Role e ClusterRole
# Role: permessi limitati a un namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: team-a
name: pod-reader
rules:
- apiGroups: [""] # "" = core API group (Pod, Service, ConfigMap...)
resources: ["pods", "pods/log", "pods/exec"]
# NOTA: "pods/log" e "pods/exec" sono sub-resource — richiedono permesso esplicito
# separato dal permesso su "pods". Avere "get" su "pods" NON dà automaticamente
# accesso a "pods/log" o "pods/exec". Devono essere dichiarati esplicitamente.
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list"]
---
# ClusterRole: per risorse cluster-wide o da riusare in più namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-reader
rules:
- apiGroups: [""]
resources: ["pods", "nodes", "endpoints", "services"]
verbs: ["get", "list", "watch"]
- apiGroups: ["metrics.k8s.io"]
resources: ["pods", "nodes"]
verbs: ["get", "list"]
Verbs disponibili:
| Verb | Operazione HTTP | Equivalente kubectl |
|---|---|---|
get | GET /resource/:name | kubectl get pod myapp |
list | GET /resource | kubectl get pods |
watch | GET /resource?watch=true | kubectl get pods -w |
create | POST /resource | kubectl create / apply |
update | PUT /resource/:name | kubectl apply (modifica) |
patch | PATCH /resource/:name | kubectl patch |
delete | DELETE /resource/:name | kubectl delete |
deletecollection | DELETE /resource | kubectl delete pods --all |
escalate | speciale | Creare Role con permessi superiori ai propri |
bind | speciale | Creare RoleBinding per un Role |
RoleBinding e ClusterRoleBinding
# RoleBinding: assegna un Role (o ClusterRole) in un namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: team-a
name: read-pods-binding
subjects:
# Associa a un ServiceAccount
- kind: ServiceAccount
name: monitoring-sa
namespace: monitoring
# Associa a un utente (da certificato/OIDC)
- kind: User
name: alice@example.com
apiGroup: rbac.authorization.k8s.io
# Associa a un gruppo
- kind: Group
name: system:masters
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role # o ClusterRole
name: pod-reader
apiGroup: rbac.authorization.k8s.io
ServiceAccount per i Pod
Ogni Pod ha un ServiceAccount associato (default se non specificato).
Il token viene automontato in /var/run/secrets/kubernetes.io/serviceaccount/.
# Crea un ServiceAccount dedicato
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp-sa
namespace: production
automountServiceAccountToken: false # Non montare il token se non serve
---
# Deployment che usa il ServiceAccount
spec:
serviceAccountName: myapp-sa
automountServiceAccountToken: true # Sovrascrive il default del SA
# Dentro un Pod, usa il token per chiamare l'apiserver
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
curl -s --cacert $CACERT \
-H "Authorization: Bearer $TOKEN" \
https://kubernetes.default.svc.cluster.local/api/v1/namespaces/default/pods
Verifica RBAC con kubectl auth can-i
# Verifica le proprie autorizzazioni
kubectl auth can-i create pods
kubectl auth can-i delete secrets -n production
# Impersona un ServiceAccount (richiede --as)
kubectl auth can-i create pods \
--as=system:serviceaccount:team-a:team-a-sa \
-n team-a
# → yes
kubectl auth can-i create pods \
--as=system:serviceaccount:team-b:team-b-sa \
-n team-a
# → no
# Vedi TUTTO ciò che può fare un ServiceAccount
kubectl auth can-i --list \
--as=system:serviceaccount:production:myapp-sa \
-n production
Principio del minimo privilegio: ogni ServiceAccount deve avere solo i verbi
e le risorse strettamente necessarie per il funzionamento dell'applicazione.
cluster-admin è quasi sempre sbagliato — e pericoloso.
Cordon, Drain e Uncordon: Manutenzione dei Nodi
Quando serve fare manutenzione su un nodo (upgrade OS, sostituzione hardware, patching kernel), Kubernetes offre tre comandi per gestire i workload in modo sicuro:
cordon → Marca il nodo come non schedulabile (SchedulingDisabled)
I Pod esistenti continuano a girare, nessun nuovo Pod viene schedulato
drain → Cordon + evict di tutti i Pod (tranne DaemonSet)
I Pod vengono ricreati su altri nodi dai controller (Deployment, StatefulSet)
uncordon → Rimuove il flag SchedulingDisabled
Il nodo torna disponibile per nuovi Pod
Cordon: Blocca Nuovi Pod
# Marca il nodo come non schedulabile
kubectl cordon node-02
# → node/node-02 cordoned
# Verifica lo stato
kubectl get nodes
# NAME STATUS ROLES AGE
# node-01 Ready worker 5d
# node-02 Ready,SchedulingDisabled worker 5d ← cordoned
# I Pod esistenti su node-02 continuano a funzionare normalmente!
Drain: Evacua il Nodo
# Drain base: evict tutti i Pod (tranne DaemonSet)
kubectl drain node-02 --ignore-daemonsets
# Drain con Pod che usano emptyDir (i dati in emptyDir vengono persi)
kubectl drain node-02 --ignore-daemonsets --delete-emptydir-data
# Drain forzato: evict anche Pod non gestiti da controller (standalone Pod)
# ⚠️ ATTENZIONE: i Pod standalone vengono ELIMINATI senza ricrearsi!
kubectl drain node-02 --ignore-daemonsets --force
# Drain con timeout (utile in automazione)
kubectl drain node-02 --ignore-daemonsets --timeout=120s
# Drain con grace period ridotto (default: usa il terminationGracePeriodSeconds del Pod)
kubectl drain node-02 --ignore-daemonsets --grace-period=30
Cosa succede durante il drain:
1. kubectl cordon node-02 → nodo diventa SchedulingDisabled
2. Per ogni Pod sul nodo:
a. Rispetta PodDisruptionBudget (se presente)
b. Invia SIGTERM al container
c. Attende terminationGracePeriodSeconds
d. Se non termina → SIGKILL
3. Il controller (Deployment/ReplicaSet) crea il Pod sostitutivo su un altro nodo
4. StatefulSet: ricrea il Pod con lo stesso nome e PVC (ordinato)
5. DaemonSet: i Pod DaemonSet vengono ignorati (--ignore-daemonsets)
⚠️
kubectl draine dati emptyDir: il flag--delete-emptydir-datadistrugge permanentemente i dati nei volumiemptyDir. Prima di fare drain, verifica quali Pod usanoemptyDir:kubectl get pods --field-selector spec.nodeName=node-02 -o json | \ jq '.items[] | select(.spec.volumes[]?.emptyDir) | .metadata.name'
Uncordon: Rimetti il Nodo in Servizio
# Dopo la manutenzione, riabilita lo scheduling
kubectl uncordon node-02
# → node/node-02 uncordoned
# Verifica
kubectl get nodes
# NAME STATUS ROLES AGE
# node-02 Ready worker 5d ← tornato Ready
Nota: dopo uncordon i Pod evictati non tornano automaticamente sul nodo. Restano dove sono stati rischedulati. Il nodo riceverà solo nuovi Pod.
PodDisruptionBudget (PDB): Protezione durante il Drain
I PDB garantiscono che un certo numero di Pod resti sempre disponibile durante operazioni di disruption volontaria (drain, upgrade, scaling down):
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb
namespace: production
spec:
minAvailable: 2 # Almeno 2 Pod devono restare attivi
# oppure:
# maxUnavailable: 1 # Al massimo 1 Pod può essere down
selector:
matchLabels:
app: api
# Se fai drain e il PDB non è soddisfatto:
# → "Cannot evict pod as it would violate the pod's disruption budget"
# Il drain si blocca finché il PDB non è rispettato (es. un nuovo Pod diventa Ready altrove)
# Controlla i PDB
kubectl get pdb -A
kubectl describe pdb api-pdb -n production
Workflow tipico di manutenzione nodo:
# 1. Verifica cosa gira sul nodo
kubectl get pods --field-selector spec.nodeName=node-02 -A
# 2. Controlla i PDB
kubectl get pdb -A
# 3. Drain (con pazienza)
kubectl drain node-02 --ignore-daemonsets --delete-emptydir-data --timeout=300s
# 4. Esegui la manutenzione (upgrade, patch, reboot...)
ssh node-02 'sudo apt update && sudo apt upgrade -y && sudo reboot'
# 5. Verifica che il nodo è tornato Ready
kubectl get nodes -w # -w = watch
# 6. Riabilita lo scheduling
kubectl uncordon node-02
Scheduling Avanzato
TopologySpreadConstraints
Distribuisce Pod uniformemente su zone, nodi, o qualsiasi topology key.
spec:
topologySpreadConstraints:
# Constraint 1: max 1 Pod di differenza tra zone
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone # Label del nodo
whenUnsatisfiable: DoNotSchedule # o ScheduleAnyway
labelSelector:
matchLabels:
app: myapp
# Constraint 2: max 2 Pod di differenza tra nodi
- maxSkew: 2
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: myapp
# Simula zone sui nodi del playground
kubectl label node node1 topology.kubernetes.io/zone=zone-a
kubectl label node node2 topology.kubernetes.io/zone=zone-b
kubectl label node node3 topology.kubernetes.io/zone=zone-c
# Crea Deployment con TopologySpreadConstraints
kubectl apply -f deployment-multizone.yaml
# Verifica la distribuzione
kubectl get pods -o wide
kubectl get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName | \
sort -k2 | uniq -f1 -c
whenUnsatisfiable values:
DoNotSchedule— il Pod rimanePendingfinché il constraint non può essere soddisfattoScheduleAnyway— schedula comunque ma minimizza lo skew (best-effort)
Taints e Tolerations
I Taints su un nodo respingono i Pod che non hanno la toleration corrispondente.
# Aggiungi taint su un nodo (3 effetti possibili)
kubectl taint node node1 dedicated=gpu:NoSchedule # Non schedula nuovi Pod
kubectl taint node node1 dedicated=gpu:NoExecute # Evict i Pod esistenti senza toleration
kubectl taint node node1 dedicated=gpu:PreferNoSchedule # Preferibilmente non schedulare
# Rimuovi un taint
kubectl taint node node1 dedicated=gpu:NoSchedule-
# Pod che può girare su nodi GPU (toleration corrispondente)
spec:
tolerations:
- key: dedicated
operator: Equal
value: gpu
effect: NoSchedule
# Toleration wildcard: tollera qualsiasi taint con questa key
- key: dedicated
operator: Exists
effect: NoSchedule
# Toleration per nodo non-ready (evict dopo 60s invece del default 300s)
- key: node.kubernetes.io/not-ready
operator: Exists
effect: NoExecute
tolerationSeconds: 60
Node Affinity
spec:
affinity:
nodeAffinity:
# HARD: se non soddisfatto, Pod rimane Pending
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: node-type
operator: In
values: ["compute", "high-memory"]
# SOFT: preferisce certi nodi ma accetta altri
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["zone-a"]
PriorityClass e Preemption
Le PriorityClass assegnano una priorità numerica ai Pod. Quando il cluster è saturo, lo scheduler può fare preemption: evictare Pod a bassa priorità per far posto a Pod a priorità più alta.
# Definizione di PriorityClass
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority # o Never per disabilitare preemption
description: "Per workload critici di produzione"
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: low-priority
value: 100
globalDefault: false
preemptionPolicy: Never
description: "Per batch job e workload non critici"
# Pod che usa una PriorityClass
apiVersion: v1
kind: Pod
metadata:
name: critical-api
spec:
priorityClassName: high-priority
containers:
- name: api
image: myapp:v1
resources:
requests:
cpu: "500m"
memory: "512Mi"
PriorityClass di sistema (built-in):
| PriorityClass | Valore | Uso |
|---|---|---|
system-cluster-critical | 2000000000 | Componenti del cluster (CoreDNS, kube-proxy) |
system-node-critical | 2000001000 | Componenti del nodo (kubelet, CSI driver) |
| (default se nessuna PriorityClass) | 0 | Tutti i Pod senza priorityClassName |
Come funziona la preemption:
1. Pod "critical-api" (priority=1000000) arriva ma il nodo è pieno
2. Lo scheduler cerca Pod con priorità < 1000000 sul nodo
3. Se trova Pod a bassa priorità sufficiente per liberare risorse:
- Invia SIGTERM ai Pod a bassa priorità (graceful termination)
- Dopo terminationGracePeriodSeconds → SIGKILL
- Schedula il Pod ad alta priorità
4. Se preemptionPolicy: Never → Pod rimane Pending senza evictare nessuno
Best practice:
- Definisci 3-4 livelli (es. critical=1M, standard=1000, batch=100, preemptible=10)
- Non usare
globalDefault: truesu più di una PriorityClass - Imposta
preemptionPolicy: Neversu batch job per evitare che evictino altri batch - Monitora preemption con
kubectl get events | grep Preempted - Combina con ResourceQuota per limitare quanti Pod high-priority un team può creare
# Lista PriorityClass nel cluster
kubectl get priorityclasses
# Vedi quali Pod usano una specifica PriorityClass
kubectl get pods -A -o custom-columns=\
NS:.metadata.namespace,\
NAME:.metadata.name,\
PRIORITY:.spec.priorityClassName,\
PRIO-VALUE:.spec.priority
# Eventi di preemption
kubectl get events -A --field-selector reason=Preempted
ResourceQuota e LimitRange: Governance Multi-Tenant
Questa sezione approfondisce i concetti introdotti nell'Incontro 4 (Workload e Configurazione).
ResourceQuota limita le risorse totali che un namespace può consumare. È lo strumento fondamentale per il multi-tenancy:
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-backend
namespace: backend
spec:
hard:
# Limiti di compute
requests.cpu: "8"
requests.memory: 16Gi
limits.cpu: "16"
limits.memory: 32Gi
# Limiti di oggetti
pods: "50"
services: "20"
services.loadbalancers: "2"
persistentvolumeclaims: "10"
secrets: "30"
configmaps: "30"
# Limiti per PriorityClass (K8s 1.12+)
# → limita quante risorse high-priority il team può usare
scopeSelector:
matchExpressions:
- operator: In
scopeName: PriorityClass
values: ["high-priority"]
LimitRange — imposta default e limiti per singolo container/Pod:
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: backend
spec:
limits:
- type: Container
default: # Limits di default se non specificati
cpu: "500m"
memory: "512Mi"
defaultRequest: # Requests di default se non specificati
cpu: "100m"
memory: "128Mi"
min: # Minimo permesso
cpu: "50m"
memory: "64Mi"
max: # Massimo permesso
cpu: "2"
memory: "4Gi"
- type: Pod
max:
cpu: "4" # Nessun Pod può chiedere più di 4 CPU totali
memory: "8Gi"
- type: PersistentVolumeClaim
min:
storage: 1Gi
max:
storage: 50Gi
Interazione tra ResourceQuota e LimitRange:
LimitRange (per container) ResourceQuota (per namespace)
├─ min: 50m CPU ├─ requests.cpu: 8 (totale)
├─ max: 2 CPU ├─ limits.cpu: 16 (totale)
├─ default: 500m CPU └─ pods: 50 (conteggio)
└─ defaultRequest: 100m CPU
Quando un Pod senza requests/limits viene creato:
1. LimitRange inietta i default (100m request, 500m limit)
2. ResourceQuota verifica che il totale namespace non superi 8 CPU request
3. Se la quota è esaurita → errore "exceeded quota"
# Stato corrente della quota
kubectl describe resourcequota team-backend -n backend
# → mostra used/hard per ogni risorsa
# Limiti attivi
kubectl describe limitrange default-limits -n backend
# Chi sta consumando cosa
kubectl top pods -n backend --sort-by=cpu
Best practice multi-tenant: ogni namespace di team dovrebbe avere:
- ResourceQuota per il cap totale
- LimitRange per i default (evita Pod senza limiti)
- NetworkPolicy per l'isolamento di rete
- RBAC per i permessi
Pod Security Standards e SecurityContext
Linux Capabilities
I container Docker ereditano un sottoinsieme di capabilities Linux:
# Vedi le capabilities di un container
kubectl exec myapp -- cat /proc/1/status | grep Cap
# CapPrm: 00000000a80425fb (bitmap delle capabilities permesse)
# Decodifica (su Linux)
capsh --decode=00000000a80425fb
Docker include di default: CHOWN, DAC_OVERRIDE, FSETID, FOWNER, MKNOD, NET_RAW,
SETGID, SETUID, SETFCAP, SETPCAP, NET_BIND_SERVICE, SYS_CHROOT, KILL, AUDIT_WRITE.
Best practice: drop ALL, aggiungi solo quello che serve:
spec:
containers:
- name: app
securityContext:
# Non permettere escalation a root
allowPrivilegeEscalation: false
# Richiede che l'immagine abbia USER non-root
runAsNonRoot: true
runAsUser: 65534 # UID nobody
runAsGroup: 65534
# Filesystem root in sola lettura
readOnlyRootFilesystem: true
# Capabilities
capabilities:
drop: ["ALL"] # Rimuovi tutto
add: ["NET_BIND_SERVICE"] # Solo se il processo deve bindare porte < 1024
# Profilo seccomp: filtra le syscall permesse
seccompProfile:
type: RuntimeDefault # Profilo predefinito del container runtime
# SecurityContext a livello Pod (applicato a tutti i container)
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000 # Proprietario del volume montato
fsGroupChangePolicy: OnRootMismatch # Cambia owner solo se necessario
seccompProfile:
type: RuntimeDefault
sysctls: # Parametri kernel (richiedono privilegio)
- name: net.core.somaxconn
value: "1024"
Volume Writable con readOnlyRootFilesystem
# Con readOnlyRootFilesystem: true, devi montare emptyDir per file temporanei
spec:
containers:
- name: app
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: tmp
mountPath: /tmp
- name: cache
mountPath: /app/cache
- name: logs
mountPath: /var/log/app
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
- name: logs
emptyDir: {}
Pod Security Standards (PSS)
PSS sostituisce PodSecurityPolicy (rimossa in K8s 1.25) con tre livelli predefiniti:
| Livello | Cosa blocca |
|---|---|
| Privileged | Nessuna restrizione (per componenti di sistema) |
| Baseline | Vieta: privileged container, hostNetwork, hostPID, hostPath, porte < 1024... |
| Restricted | Aggiunge: runAsNonRoot, readOnlyRootFilesystem, drop ALL capabilities, seccomp |
# Abilita PSS su un namespace (audit = log solo, enforce = blocca)
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
# Testa se un Pod passerebbe la validazione
kubectl apply --dry-run=server -f pod.yaml -n production
CRD e Operators: Estendere l'API Kubernetes
Le Custom Resource Definitions (CRD) permettono di aggiungere nuovi tipi di risorse all'API Kubernetes — senza modificare il codice del cluster.
Hai già incontrato diverse CRD in questo corso:
ServiceMonitor(Prometheus Operator) — per configurare il monitoringApplication(ArgoCD) — per il GitOpsPrometheusRule— per gli alert custom
# Lista tutte le CRD installate nel cluster
kubectl get crd
# Filtra CRD di un operator specifico
kubectl get crd | grep monitoring.coreos.com
# → servicemonitors.monitoring.coreos.com
# → prometheusrules.monitoring.coreos.com
# → podmonitors.monitoring.coreos.com
# Ispeziona una CRD
kubectl describe crd servicemonitors.monitoring.coreos.com
# Usa le risorse custom come qualsiasi risorsa nativa
kubectl get servicemonitors -A
kubectl get prometheusrules -n monitoring
Un Operator è un controller che gestisce il ciclo di vita di un'applicazione complessa usando CRD. L'operator osserva le risorse custom e reagisce (reconciliation loop):
| Operator | CRD principali | Cosa automatizza |
|---|---|---|
| Prometheus Operator | ServiceMonitor, PrometheusRule | Scraping, alerting, retention |
| cert-manager | Certificate, ClusterIssuer | Emissione e rinnovo certificati TLS |
| Strimzi | Kafka, KafkaTopic | Cluster Kafka, topic, utenti |
| CloudNativePG | Cluster (PostgreSQL) | HA, backup, failover PostgreSQL |
Risorse: esplora gli operator disponibili su OperatorHub.io — catalogo curato dalla community CNCF.
Admission Webhooks: Validazione e Mutazione
Gli Admission Webhooks sono il meccanismo con cui Kubernetes intercetta e modifica le richieste API dopo l'autenticazione e l'autorizzazione RBAC, ma prima della persistenza su etcd.
Richiesta API → Autenticazione → Autorizzazione (RBAC) → Admission Webhooks → etcd
│
├─ Mutating → modifica il manifest
└─ Validating → accetta o rifiuta
| Tipo | Cosa fa | Esempio |
|---|---|---|
| Mutating | Modifica il manifest in volo | Iniettare sidecar, aggiungere label di default |
| Validating | Accetta o rifiuta il manifest | Bloccare immagini non firmate, imporre naming convention |
Pod Security Standards (PSS) che hai appena studiato è implementato come admission controller built-in. I policy engine esterni usano webhook:
| Policy Engine | Tipo | Linguaggio Policy |
|---|---|---|
| OPA Gatekeeper | Validating webhook | Rego (linguaggio dichiarativo) |
| Kyverno | Validating + Mutating | YAML nativo (no nuovo linguaggio) |
# Vedi i webhook configurati nel cluster
kubectl get validatingwebhookconfigurations
kubectl get mutatingwebhookconfigurations
# Esempio: Kyverno installa webhook per le sue policy
kubectl get validatingwebhookconfigurations | grep kyverno
Quando usare i webhook: PSS copre i casi base (non-root, no privileged). Per policy aziendali personalizzate (es. "tutte le immagini devono provenire dal registry interno", "ogni Deployment deve avere un PDB"), usa Kyverno o Gatekeeper.
NetworkPolicy e Sicurezza di Rete
Il controllo dell'accesso tramite RBAC e SecurityContext protegge l'accesso all'API Kubernetes e le syscall dei container, ma non protegge la comunicazione di rete tra i Pod. Per prevenire il movimento laterale (un Pod compromesso che attacca altri servizi), bisogna usare le NetworkPolicy.
Di default Kubernetes è default-allow: ogni Pod può comunicare con qualsiasi altro Pod nel cluster, indipendentemente dal namespace. In un'architettura multi-tenant o con dati sensibili, questo è un rischio significativo.
Cross-reference: Le NetworkPolicy sono trattate in dettaglio nell'Incontro 5 (Networking). Per la security posture completa di un namespace di produzione, combina:
- RBAC (chi può interagire con l'API)
- SecurityContext (cosa può fare il container a livello kernel)
- NetworkPolicy (quale traffico di rete è permesso)
Una policy di base per un namespace di produzione:
# default-deny tutto il traffico in ingresso e uscita apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny-all namespace: production spec: podSelector: {} policyTypes: - Ingress - Egress # Nessuna regola → tutto bloccato (aggiungi allowlist selettive per ogni servizio)
Hands-on Guidato (90 min — su iximiuz Labs)
Esercizio 1 — RBAC per Team Separati
# Setup: tre namespace, tre team
kubectl create namespace team-a
kubectl create namespace team-b
kubectl create namespace monitoring
# ServiceAccount per ogni team
kubectl create serviceaccount team-a-sa -n team-a
kubectl create serviceaccount team-b-sa -n team-b
kubectl create serviceaccount monitoring-sa -n monitoring
# Team A: accesso completo nel proprio namespace
kubectl apply -f - <<'EOF'
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: team-a
name: full-access
rules:
- apiGroups: ["", "apps", "batch"]
resources: ["*"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: team-a
name: team-a-full-access
subjects:
- kind: ServiceAccount
name: team-a-sa
namespace: team-a
roleRef:
kind: Role
name: full-access
apiGroup: rbac.authorization.k8s.io
EOF
# Team B: sola lettura
kubectl apply -f - <<'EOF'
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: team-b
name: read-only
rules:
- apiGroups: ["", "apps"]
resources: ["pods", "deployments", "services", "configmaps"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: team-b
name: team-b-read-only
subjects:
- kind: ServiceAccount
name: team-b-sa
namespace: team-b
roleRef:
kind: Role
name: read-only
apiGroup: rbac.authorization.k8s.io
EOF
# Monitoring: ClusterRole per leggere da tutti i namespace
kubectl create clusterrole monitoring-reader \
--verb=get,list,watch \
--resource=pods,nodes,endpoints,services
kubectl create clusterrolebinding monitoring-reader \
--clusterrole=monitoring-reader \
--serviceaccount=monitoring:monitoring-sa
# Verifica
kubectl auth can-i create pods \
--as=system:serviceaccount:team-a:team-a-sa -n team-a
# → yes
kubectl auth can-i create pods \
--as=system:serviceaccount:team-b:team-b-sa -n team-b
# → no
kubectl auth can-i list pods \
--as=system:serviceaccount:monitoring:monitoring-sa -n team-a
# → yes
Esercizio 2 — TopologySpreadConstraints: 6 Repliche in 3 Zone
# Label dei nodi con zona
kubectl label node node1 topology.kubernetes.io/zone=zone-a
kubectl label node node2 topology.kubernetes.io/zone=zone-b
kubectl label node node3 topology.kubernetes.io/zone=zone-c
kubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: multizone-app
spec:
replicas: 6
selector:
matchLabels:
app: multizone-app
template:
metadata:
labels:
app: multizone-app
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: multizone-app
containers:
- name: app
image: nginx:alpine
resources:
requests:
memory: "16Mi"
cpu: "10m"
EOF
# Verifica la distribuzione (2 Pod per zona)
kubectl get pods -o wide
kubectl get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName \
| awk '{print $2}' | sort | uniq -c
Esercizio 3 — Hardening di un Pod Insicuro
# Pod insicuro (root, filesystem scrivibile, capabilities)
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: insecure-pod
spec:
containers:
- name: app
image: nginx:alpine
EOF
kubectl exec insecure-pod -- id
# uid=0(root) gid=0(root) → pericoloso!
kubectl exec insecure-pod -- touch /root/evil-file
# → Funziona! Root può scrivere ovunque
# Pod hardened
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: hardened-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx:unprivileged # Versione di nginx che supporta non-root
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: tmp
mountPath: /tmp
- name: nginx-cache
mountPath: /var/cache/nginx
- name: nginx-run
mountPath: /var/run
resources:
requests:
memory: "32Mi"
cpu: "50m"
volumes:
- name: tmp
emptyDir: {}
- name: nginx-cache
emptyDir: {}
- name: nginx-run
emptyDir: {}
EOF
kubectl exec hardened-pod -- id
# uid=1000 gid=3000 → non root
kubectl exec hardened-pod -- touch /tmp/test
# → FAIL! readOnlyRootFilesystem: solo /tmp (emptyDir) è scrivibile
kubectl exec hardened-pod -- touch /root/evil-file
# → Permission denied!
Capstone Challenge — su Proxmox (ultime 2h)
"L'Attacco RBAC"
Un token di ServiceAccount è stato compromesso. Il ServiceAccount
compromised-saha permessi eccessivi: accesso a tutti i Secret del cluster.Il tuo compito:
1. Analisi: identifica i permessi attuali del SA compromesso
kubectl auth can-i --list \ --as=system:serviceaccount:production:compromised-sa -n production2. Minimo privilegio: analizza cosa l'applicazione usa realmente
# Guarda i log dell'app per capire quali API chiama kubectl logs deploy/myapp -n production | grep -E "GET|POST|PATCH" | sort | uniq -c3. Riduzione permessi: crea un nuovo Role con solo le azioni necessarie
# Crea nuovo ServiceAccount con permessi minimi kubectl create serviceaccount minimal-sa -n production # Crea Role con solo get/list/watch su pods (niente secrets!)4. Rotazione token: aggiorna il Deployment per usare il nuovo SA
kubectl patch deployment myapp -n production \ -p '{"spec":{"template":{"spec":{"serviceAccountName":"minimal-sa"}}}}'5. Verifica: l'app funziona ancora e non può più accedere ai Secret
kubectl auth can-i get secrets \ --as=system:serviceaccount:production:minimal-sa -n production # → no ✓Svolgere su cluster Proxmox reale. Portare le credenziali SSH.
Self-Study Assignment
Completa il seguente tutorial su iximiuz Labs:
Loading tutorial...
Challenge consigliate su iximiuz Labs (cerca nella sezione Challenges):
- Taints & Tolerations
- Pod Affinity/Anti-Affinity
- "Kube Mysteries" series
Letture consigliate:
- RBAC Authorization — kubernetes.io
- Pod Security Standards — kubernetes.io
- SecurityContext — kubernetes.io
- Scheduling Framework — kubernetes.io
Risorse Aggiuntive
Documentazione Ufficiale Kubernetes
- RBAC Authorization — kubernetes.io — guida completa: Role, ClusterRole, RoleBinding, soggetti, aggregazione
- Pod Security Standards — kubernetes.io — Privileged, Baseline, Restricted: cosa consente ogni livello con tabella comparativa
- Security Context — kubernetes.io — tutti i campi securityContext per Pod e container con esempi pratici
- Node Affinity — kubernetes.io — nodeSelector, affinity, anti-affinity, topology spread constraints
- Taints and Tolerations — kubernetes.io — effetti NoSchedule, NoExecute, PreferNoSchedule con esempi
- Resource Management — kubernetes.io — requests, limits, LimitRange, ResourceQuota per namespace
Guide e Benchmark di Sicurezza
- NSA/CISA Kubernetes Hardening Guide — guida governativa alla sicurezza K8s: threat model, container hardening, network policies, audit logging
- CIS Kubernetes Benchmark — standard di sicurezza per la configurazione del cluster (free download dopo registrazione)
- CNCF Cloud Native Security Whitepaper — framework di sicurezza per ambienti cloud-native: supply chain, runtime, accesso
- AWS EKS Security Best Practices — guida completa di sicurezza EKS, ampiamente applicabile a qualsiasi cluster K8s
Policy Engine e Compliance
- OPA Gatekeeper — Open Policy Agent — policy engine: scrivi policy in Rego, enforcement via admission webhook, violation reports
- Kyverno — policy engine nativo K8s: YAML-based, validate/mutate/generate, Kyverno CLI per CI
- kube-bench — Aqua Security — verifica il cluster contro CIS Benchmark: identifica misconfigurazioni in minuti
- Kubescape — ARMO — scansione del cluster contro NSA, MITRE ATT&CK framework for Kubernetes, CIS
Runtime Security
- Falco — CNCF — runtime security: rileva syscall anomale e comportamenti sospetti nei container in produzione
- Trivy — Aqua Security — scanner all-in-one: vulnerabilità immagini, misconfigurazioni K8s YAML, secrets, SBOM
- Starboard — Aqua Security — integra risultati di sicurezza (Trivy, Polaris, Conftest) come CRD native in Kubernetes
Blog e Articoli Tecnici
- Ahmet Alp Balkan — Kubernetes RBAC tips and tricks — consigli pratici per un RBAC sicuro, minimale e manutenibile
- Ivan Velichko — Kubernetes Pod Security Context (iximiuz.com) — securityContext esplorato dal punto di vista del kernel Linux: capabilities, seccomp, namespaces
- NCC Group — Understanding Kubernetes RBAC — analisi delle vulnerabilità RBAC più comuni: privilege escalation, token theft, bind escalation
- Learnk8s — Scheduling in Kubernetes — come funziona lo scheduler: filter plugins, score plugins, binding
Strumenti di Audit e Visualizzazione RBAC
- rbac-tool — analizza e visualizza le policy RBAC del cluster: chi può fare cosa, policy graph
- kubectl-who-can — mostra chi può eseguire una specifica azione su una risorsa K8s
- rakkess — matrice di accesso: mostra tutti i verbi che un utente può eseguire su ogni risorsa
- Previous lesson
- Networking — Services, DNS e Ingress