Securitate Kubernetes: RBAC, Pod Security Standards și OPA Gatekeeper
Un cluster Kubernetes implicit și surprinzător de nesigur: containerele pot rula ca root, montați sistemul de fișiere nod gazdă, accesați API-ul serverului cu simbolul de ServiceAccount cu permisiuni largi. În 2025, 68% din incidentele de securitate în medii containerized a fost atribuită mai degrabă erorilor de configurare decât vulnerabilităților software (sursa: Sysdig Container Security Report 2025). Vestea bună: Kubernetes oferă instrumente puternice pentru a elimina aceste setări riscante.
Acest articol acoperă o întărire completă a clusterului: RBAC a verifica cine poate face ce cu API-ul Kubernetes, Standarde de securitate a podului a preveni containere privilegiate și configurații riscante, de ex OPA Gatekeeper pentru implementați politici personalizate ale companiei, cum ar fi „toate imaginile trebuie să provină din registru intern” sau „fără implementare fără limite de resurse”.
Ce vei învăța
- Model de autorizare Kubernetes: RBAC, verbe, resurse, domeniu
- Principiul celui mai mic privilegiu: Rol, ClusterRole, RoleBinding aplicat
- ServiceAccount: Cum să limitați jetoanele montate automat și să utilizați IRSA/Workload Identity
- Standarde de securitate a podului: privilegiat, de bază, restricționat și aplicare prin spațiu de nume
- OPA Gatekeeper: Instalare, ConstraintTemplate în Rego, Constraint
- Politici comune: imagini de registru aprobate, limite de resurse obligatorii, fără root
- Înregistrare de audit: monitorizați cine ce face în cluster
- Lista completă de verificare a întăririi
RBAC: Controlul accesului bazat pe roluri
RBAC (Role-Based Access Control) este mecanismul principal de autorizare al Kubernetes. Definește OMS (Subiect: utilizator, grup, cont de serviciu) se poate executa ce actiune (verb: obține, listează, urmărește, creează, actualizează, corecește, șterge) sus ce resursă (Resursa: pod-uri, implementări, secrete) în care domeniu de aplicare (spațiu de nume cu Role/RoleBinding sau întreg cluster cu ClusterRole/ClusterRoleBinding).
Role și RoleBinding pentru o echipă de dezvoltare
# 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 pentru SRE/Admin cu domeniu limitat
# 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: Limitați jetoanele automate
În mod implicit, fiecare Pod montează automat un jeton ServiceAccount valid. În Podurile care nu cheamă API-ul Kubernetes, acest token este inutil, dar mărește suprafața de atac:
# 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
Standarde de securitate pentru pod (PSS)
Standardele de securitate pod înlocuiesc PodSecurityPolicies depreciate (eliminate în K8s 1.25). Acestea definesc trei niveluri de securitate aplicabile la nivel de spațiu de nume prin intermediul etichetelor:
- Privilegiat: fără restricții (doar pentru spații de nume de sistem, cum ar fi kube-system)
- Linii de bază: previne cele mai notorii și riscante configurații (containere privilegiate, hostPath, hostNetwork)
- Restricţionat: Cele mai bune practici actuale pentru securitate maximă (non-root, seccomp, scăderea capacităților)
Fiecare nivel are trei moduri: enforce (blochează Podul), audit
(înregistrează, dar nu blochează), warn (afișează un avertisment utilizatorului).
Aplicați standardele de securitate pentru pod la spațiile de nume
# 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 compatibil cu nivelul restricționat
# 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: Motor de politici pentru Kubernetes
Standardele de securitate Pod acoperă un set fix de controale. OPA Gatekeeper permite pentru a defini politici personalizate folosind Rego, limba OPA. Este implementat ca un Admission Webhook care interceptează fiecare cerere către serverul API și valabile împotriva politicilor definite.
Instalare 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: Numai imagini din registrul aprobat
# 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: Limite de resurse obligatorii
# 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: Fără rădăcină de container
# 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
Înregistrare de audit
Kubernetes poate înregistra fiecare cerere pe serverul API într-un jurnal de audit structurat. Este esențial pentru conformitate și investigarea incidentelor.
# 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
Lista de verificare a întăririi Kubernetes
Lista de verificare a securității
- RBAC: Principiul privilegiului minim pentru fiecare ServiceAccount și grup de utilizatori
- Cont de serviciu:
automountServiceAccountToken: falsepe toate podurile care nu apelează API-ul serverului - Expirarea simbolului: utilizați jetoane proiectate cu
expirationSecondsîn loc de jetoane permanente - Standarde de securitate a podului:
restrictedîn toate spațiile de nume de producție,baselinepe scenă - Fără rădăcină:
runAsNonRoot: trueeallowPrivilegeEscalation: falsepe toate containerele - Sistem de fișiere imuabil:
readOnlyRootFilesystem: true+ volum emptyDir pentru /tmp și /cache - Limitele resurselor: Limite CPU și memorie pentru toate containerele (Gatekeeper pentru aplicare)
- Registre aprobate: Constrângere Gatekeeper care blochează imaginile din registrele neautorizate
- Politica de rețea: default-deny pe toate spațiile de nume de producție (a se vedea articolul 1)
- Secrete: utilizați un manager secret extern (AWS Secrets Manager, Vault) în loc de Secret Kubernetes simplu
- Jurnalele de audit: activat și centralizat într-un SIEM sau un agregator de jurnal
- etcd: criptat în repaus cu configurație de criptare
- API-uri de server: acces anonim dezactivat, controlori de admitere activate
Greșeli comune de securitate
- Spațiu de nume implicit pentru încărcături de lucru: spațiul de nume implicit are RBAC permisiv în mod implicit; Utilizați întotdeauna spații de nume dedicate pentru sarcinile dvs. de lucru
- ClusterAdmin pentru conductele CI/CD: Conductele GitOps nu au nevoie de cluster-admin; Creați un ServiceAccount cu permisiunile minime necesare pentru spațiul de nume țintă
- Secretul ca variabile de mediu: variabilele de mediu sunt vizibile în
kubectl describe podși în jurnalele; utilizați fișiere montate de Secrets sau manageri externi de secrete - Ultimele etichete din imagini: cea mai recentă etichetă se poate modifica; Utilizați întotdeauna SHA256 digest sau etichete imuabile pentru a asigura reproductibilitatea și securitatea
- Ignorați CVE-urile imaginii de bază: scanați imaginile în mod regulat cu Trivy, Snyk sau Grype; o imagine de bază Ubuntu cu CVE-uri critice anulează munca de consolidare a clusterului
Concluzii și pașii următori
Securitatea unui cluster Kubernetes este un proces stratificat: RBAC controlează accesul la API, Standardele de securitate pentru pod previne configurațiile riscante la nivel de pod, iar OPA Gatekeeper vă permite să codificați politicile companiei ca cod versionat și auditabil. Niciunul dintre aceste instrumente nu este suficient; împreună formează una apărare în profunzime care reduce drastic suprafața de atac.
Următorul pas pentru a consolida și mai mult securitatea și a implementa un sistem instrumente de securitate de execuție, cum ar fi Falco, care monitorizează apelurile sistemului container în timp real și alertă asupra comportamentului anormal (cochilie deschisă într-un container în producție, citire de fișiere de acreditări, conexiuni neașteptate la rețea).
Articole viitoare din seria Kubernetes la scară
- Multi-Tenancy în Kubernetes: spațiu de nume, cotă de resurse și HNC
- Sarcini de lucru AI și GPU pe Kubernetes: Plugin de dispozitiv și joburi de instruire
- FinOps pentru Kubernetes: dimensionare drepturi, instanțe spot și reducere a costurilor
- GitOps cu ArgoCD: implementare declarativă și lansări progresive
Articole anterioare
Serii înrudite
- DevSecOps — politică ca cod, scanare de securitate în conducte CI/CD
- Rețea Kubernetes — Politica de rețea pentru izolarea sarcinilor de lucru
- Ingineria platformei — securitate precum balustrade automate pentru echipe







