En tancar el mòdul 7 vam deixar la plataforma Rutas Norte completament observable: sondes que detecten un procés malalt, mètriques a Prometheus, quadres de comandament a Grafana, alertes encaminades per Alertmanager i tots els logs centralitzats a Elasticsearch. Veiem absolutament tot el que passa. I tanmateix, ara mateix, qualsevol que tingui accés al clúster pot llegir el Secret amb les credencials de postgres-reserves —la base de dades que guarda el nom, el DNI, el telèfon i el correu de cada client que ha comprat un bitllet—, pot desplegar un contenidor privilegiat que s'escapi al node, o pot pujar una imatge que ningú ha revisat. I no sabríem qui ho va fer.
Aquest mòdul sencer es dedica a tancar aquestes portes. I la primera, la més important, és aquesta: decidir qui pot fer què. A Kubernetes això s'anomena RBAC (Role-Based Access Control, control d'accés basat en rols) i és el mecanisme que converteix un clúster on tothom pot fer-ho tot en un clúster on cada persona i cada procés té exactament els permisos que necessita i ni un més.
Al mòdul 3 vam deixar dos fils solts que ara recollim. En parlar de Secrets vam dir que qui pot llegir un secret ho decideix RBAC. En parlar de ServiceAccounts vam dir que la ServiceAccount respon a qui ets i RBAC a què pots fer, i vam veure un 403 Forbidden que demostrava que Kubernetes denega per defecte. Aquesta lliçó explica exactament per què va passar aquell 403 i com es concedeix, amb precisió quirúrgica, només l'imprescindible.
Advertiment important. Aquesta lliçó ensenya els mecanismes de RBAC i proposa un disseny d'exemple per a una empresa fictícia. El disseny real de permisos d'un clúster de producció l'ha de revisar un professional de seguretat, i si el clúster tracta dades personals —com és el cas de
postgres-reserves— també el responsable de compliment normatiu de l'organització. Un error de permisos no es nota fins que algú l'aprofita.
Contingut
- Les tres portes: autenticació, autorització i admissió
- Els subjectes: usuaris, grups i ServiceAccounts
- Els quatre objectes de RBAC
- Anatomia d'una regla: apiGroups, resources, verbs i resourceNames
- Subrecursos: el poder ocult de
pods/execipods/log - Els rols per defecte i els rols agregats
- Verificar permisos amb
kubectl auth can-i - El disseny de RBAC de Rutas Norte
- Per què
listsobre secrets equival a llegir-los - Escalada de privilegis i les proteccions de Kubernetes
- Auditoria i revisió periòdica de permisos
- Errors comuns i consells
- Exercicis
- Conclusió
- Les tres portes: autenticació, autorització i admissió
Tot a Kubernetes passa per l'apiserver. Quan escrius kubectl get pods, quan el kubelet informa de l'estat d'un node, quan el controlador de Deployments crea un ReplicaSet, quan informes-ocupacio consulta la llista de pods: tot són peticions HTTP a la mateixa API. I tota petició travessa tres portes abans d'arribar a etcd.
flowchart LR
A["Petició HTTP<br/>kubectl / SDK / curl"] --> B{"1. Autenticació<br/>qui ets?"}
B -->|401 Unauthorized| X1["Rebutjada"]
B -->|Identitat vàlida| C{"2. Autorització<br/>ho pots fer?<br/>AQUÍ ACTUA RBAC"}
C -->|403 Forbidden| X2["Rebutjada"]
C -->|Permès| D{"3. Control d'admissió<br/>l'objecte és acceptable?"}
D -->|Mutating: modifica| D
D -->|Validating: rebutja| X3["Rebutjada (422 / 400)"]
D -->|Acceptat| E["Validació d'esquema"]
E --> F[("etcd")]
Anem porta per porta.
Porta 1: autenticació (authentication)
Respon a qui ets?. L'apiserver examina les credencials que acompanyen la petició i produeix una identitat: un nom d'usuari, una llista de grups i, opcionalment, uns atributs extra. Els mecanismes habituals són:
| Mecanisme | Com arriba la identitat | Ús típic |
|---|---|---|
| Certificat de client X.509 | El CN del certificat és l'usuari; cada O és un grup |
Administradors, clústers petits, kubeadm |
| Token de ServiceAccount (JWT) | Signat pel clúster; identifica system:serviceaccount:<ns>:<nom> |
Processos dins del clúster |
| OIDC (proveïdor extern) | Un token d'un proveïdor com Keycloak, Okta, Entra ID o Google | Persones en organitzacions mitjanes i grans |
| Webhook d'autenticació | L'apiserver pregunta a un servei extern | Integracions a mida |
| Proveïdors de núvol (EKS, AKS, GKE) | El proveïdor tradueix la seva identitat IAM a usuari/grups de Kubernetes | Kubernetes gestionat (veure 10-06) |
Si cap funciona, la petició es queda a system:anonymous i normalment es rebutja amb 401 Unauthorized.
Porta 2: autorització (authorization)
Respon a pots fer això?. L'apiserver pren la identitat de la porta anterior i el detall de la petició (verb, grup d'API, recurs, namespace, nom) i consulta els autoritzadors configurats. RBAC és l'autoritzador que fa servir pràcticament tothom, tot i que n'existeixen d'altres (Node, ABAC, Webhook, AlwaysAllow). Si cap autoritzador diu "sí", la resposta és 403 Forbidden. No hi ha permís implícit: Kubernetes denega per defecte. Aquell va ser exactament el 403 que vam veure a 03-06.
Un detall essencial: RBAC només concedeix, mai denega. No existeixen regles de denegació. El permís efectiu d'un subjecte és la unió de tot el que li concedeixen tots els seus bindings. Per això treure un permís vol dir treure el binding que l'atorga, no afegir una regla que el prohibeixi.
Porta 3: control d'admissió (admission control)
Respon a aquest objecte és acceptable tal com ve?. Aquí actuen els controladors d'admissió mutants (que poden modificar l'objecte, com el que injecta la ServiceAccount per defecte) i els validants (que només accepten o rebutgen). És on viuen les ResourceQuota del mòdul 3, el Pod Security Admission i les polítiques de Kyverno.
Aquesta tercera porta és el tema de 08-03; aquí només l'anomenem perquè vegis on encaixa RBAC. Recorda la diferència clau:
RBAC decideix si tens dret a fer l'operació. L'admissió decideix si l'objecte que envies compleix les normes. Pots tenir permís per crear pods (RBAC diu sí) i tot i així veure rebutjat un pod privilegiat (l'admissió diu no).
- Els subjectes: usuaris, grups i ServiceAccounts
RBAC concedeix permisos a subjectes. N'hi ha exactament tres tipus.
Usuaris (User)
Aquí ve la sorpresa que confon gairebé tothom en començar:
Kubernetes no emmagatzema usuaris. No existeix un objecte
User. No pots ferkubectl create user anna. No hi ha cap base de dades de persones dins del clúster.
Un usuari és simplement una cadena de text que produeix l'autenticador. Si t'autentiques amb un certificat el CN del qual és anna.garcia, per a RBAC ets l'usuari anna.garcia. Si t'autentiques amb OIDC i el proveïdor retorna l'email [email protected], aquest serà el teu nom d'usuari. El clúster confia en l'autenticador i prou.
La conseqüència pràctica és important: donar d'alta i de baixa persones és responsabilitat del proveïdor d'identitat, no de Kubernetes. Si l'Anna deixa l'empresa i només esborres el seu RoleBinding, però el seu certificat continua sent vàlid i li quedaven permisos per un altre binding de grup, continua entrant.
Grups (Group)
Igual que els usuaris: són cadenes que produeix l'autenticador. Amb un certificat X.509, cada camp O (organització) es converteix en un grup. Amb OIDC, sol configurar-se un claim com ara groups.
Gairebé sempre has de concedir permisos a grups, no a usuaris. Si Rutas Norte contracta algú nou a l'equip de plataforma, l'afegeixes al grup plataforma al proveïdor d'identitat i ja té els permisos. No cal tocar ni un sol YAML del clúster.
Kubernetes reserva alguns grups amb significat propi:
| Grup | Qui el té |
|---|---|
system:authenticated |
Qualsevol que hagi superat l'autenticació |
system:unauthenticated |
Peticions anònimes |
system:masters |
Accés total, sense passar per RBAC (porta del darrere d'emergència) |
system:serviceaccounts |
Totes les ServiceAccounts del clúster |
system:serviceaccounts:<ns> |
Totes les ServiceAccounts d'un namespace |
Compte extrem amb
system:masters. L'apiserver li concedeix permís total abans de consultar RBAC, així que no es pot limitar ni revocar amb un manifest. És la identitat delkubeconfigd'administrador que generakubeadm. Aquest fitxer s'ha de custodiar com una clau mestra: guardat fora del portàtil de ningú, amb accés registrat, i utilitzat només per recuperar el clúster quan RBAC està trencat.
ServiceAccounts
Són les identitats dels processos que corren al clúster, i sí que són objectes reals de Kubernetes (els vam crear a 03-06). A RBAC es referencien de dues maneres equivalents:
# Forma recomanada dins d'un binding
subjects:
- kind: ServiceAccount
name: informes-ocupacio
namespace: rutas-norte-pro# Forma equivalent, tractant-la com a usuari
subjects:
- kind: User
name: system:serviceaccount:rutas-norte-pro:informes-ocupacio
apiGroup: rbac.authorization.k8s.ioResum comparatiu:
| Usuaris i grups | ServiceAccounts | |
|---|---|---|
| Existeixen com a objecte? | No | Sí (kubectl get sa) |
| Qui els gestiona? | Proveïdor d'identitat extern o la CA del clúster | Kubernetes |
| Amb namespace? | No, són globals | Sí, pertanyen a un namespace |
| Per a què | Persones i sistemes externs | Pods i controladors |
| Credencial | Certificat, token OIDC | Token JWT projectat |
- Els quatre objectes de RBAC
RBAC té només quatre tipus d'objecte, i tots viuen al grup d'API rbac.authorization.k8s.io/v1. La idea és d'una simetria molt neta:
RoleiClusterRoledescriuen què es pot fer. Són llistes de permisos, i no esmenten ningú.RoleBindingiClusterRoleBindingdescriuen qui ho pot fer. Uneixen uns subjectes amb un rol.
flowchart LR
subgraph Què es pot fer
R["Role<br/>(un namespace)"]
CR["ClusterRole<br/>(tot el clúster)"]
end
subgraph Qui ho pot fer
RB["RoleBinding<br/>(un namespace)"]
CRB["ClusterRoleBinding<br/>(tot el clúster)"]
end
S["Subjectes:<br/>usuaris, grups,<br/>ServiceAccounts"]
R --> RB
CR --> RB
CR --> CRB
S --> RB
S --> CRB
La taula de les quatre combinacions
Fixa-t'hi bé perquè aquí hi ha el 90 % de la confusió amb RBAC:
| Binding | Rol referenciat | Abast efectiu | Exemple a Rutas Norte |
|---|---|---|---|
RoleBinding |
Role (mateix namespace) |
Els permisos del rol, només al namespace del binding | desenvolupament gestiona objectes a rutas-norte-dev |
RoleBinding |
ClusterRole |
Els permisos del rol, acotats al namespace del binding | suport usa el rol view només a rutas-norte-pro |
ClusterRoleBinding |
ClusterRole |
Els permisos a tots els namespaces i sobre recursos globals | plataforma administra el clúster |
ClusterRoleBinding |
Role |
No existeix. Kubernetes ho rebutja | — |
La segona fila és la que sorprèn i alhora la més útil. Un ClusterRole no concedeix res per si sol: és només una plantilla de permisos. Quan el referencies des d'un RoleBinding, les seves regles s'apliquen únicament dins del namespace d'aquell binding. Això permet escriure el rol una vegada i reutilitzar-lo en molts namespaces.
# Un ÚNIC ClusterRole reutilitzable: lectura de l'aplicació
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rutasnorte-lectura-aplicacio
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "events"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "replicasets", "daemonsets"]
verbs: ["get", "list", "watch"]
---
# Aplicat NOMÉS a rutas-norte-pro mitjançant un RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: suport-lectura-pro
namespace: rutas-norte-pro # <-- aquí s'acota l'abast
subjects:
- kind: Group
name: suport
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole # referenciem un ClusterRole...
name: rutasnorte-lectura-aplicacio
apiGroup: rbac.authorization.k8s.ioAmb aquests dos manifests, el grup suport pot llistar pods a rutas-norte-pro i enlloc més. Si demà volguéssim donar-li també lectura a rutas-norte-pre, n'hi hauria prou amb un altre RoleBinding idèntic canviant el namespace: el ClusterRole no es toca.
Quan cal un ClusterRole de debò
Hi ha recursos que no pertanyen a cap namespace: Node, PersistentVolume, StorageClass, Namespace, ClusterRole, CustomResourceDefinition... Per concedir permisos sobre ells és obligatori un ClusterRole referenciat des d'un ClusterRoleBinding. Un RoleBinding mai no pot donar accés a un recurs global, per molt que el ClusterRole l'esmenti.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rutasnorte-lectura-infraestructura
rules:
- apiGroups: [""]
resources: ["nodes", "persistentvolumes", "namespaces"]
verbs: ["get", "list", "watch"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses", "volumeattachments"]
verbs: ["get", "list", "watch"]Un detall de roleRef que convé saber: és immutable. No pots canviar a quin rol apunta un binding ja creat; cal esborrar-lo i tornar-lo a crear. La llista de subjects, en canvi, sí que es pot modificar.
- Anatomia d'una regla: apiGroups, resources, verbs i resourceNames
Una regla de RBAC és la intersecció de quatre dimensions. Totes han de coincidir perquè el permís s'apliqui.
rules:
- apiGroups: ["apps"] # 1. de quin grup d'API?
resources: ["deployments"] # 2. quin tipus de recurs?
resourceNames: ["api-reserves"] # 3. quin objecte concret? (opcional)
verbs: ["get", "patch", "update"] # 4. quines operacions?apiGroups: de quina família d'API parlem
Cada recurs de Kubernetes pertany a un grup d'API. El grup del nucli (core, també anomenat legacy) és històric i es representa amb la cadena buida "", no amb core. Allà viuen Pods, Services, ConfigMaps, Secrets, ServiceAccounts, Nodes, PersistentVolumeClaims i Namespaces.
| Grup | Recursos habituals |
|---|---|
"" (nucli) |
pods, services, configmaps, secrets, serviceaccounts, persistentvolumeclaims, events, nodes, namespaces |
apps |
deployments, statefulsets, daemonsets, replicasets |
batch |
jobs, cronjobs |
networking.k8s.io |
ingresses, networkpolicies, ingressclasses |
rbac.authorization.k8s.io |
roles, rolebindings, clusterroles, clusterrolebindings |
storage.k8s.io |
storageclasses, volumeattachments, csidrivers |
autoscaling |
horizontalpodautoscalers |
policy |
poddisruptionbudgets |
monitoring.coreos.com |
servicemonitors, prometheusrules (CRDs del mòdul 7) |
Escriure apiGroups: ["core"] és un error silenciós molt freqüent: no falla en aplicar el manifest, simplement no concedeix res perquè no existeix cap grup anomenat core.
Per esbrinar el grup i el nom plural exacte de qualsevol recurs:
# Llista tots els recursos amb el seu grup (APIVERSION), si tenen namespace i el seu KIND
kubectl api-resourcesNAME SHORTNAMES APIVERSION NAMESPACED KIND
configmaps cm v1 true ConfigMap
pods po v1 true Pod
secrets v1 true Secret
deployments deploy apps/v1 true Deployment
statefulsets sts apps/v1 true StatefulSet
cronjobs cj batch/v1 true CronJob
ingresses ing networking.k8s.io/v1 true Ingress
networkpolicies netpol networking.k8s.io/v1 true NetworkPolicy
nodes no v1 false Node
storageclasses sc storage.k8s.io/v1 false StorageClassQuan APIVERSION és només v1 (sense barra), el grup és el buit "". Quan és apps/v1, el grup és apps. La columna NAMESPACED et diu si necessites Role o forçosament ClusterRole.
En una regla s'utilitza sempre el nom plural en minúscules de la columna NAME, mai el KIND ni el nom curt. resources: ["Pod"] o resources: ["po"] no concedeixen res.
verbs: quines operacions es permeten
| Verb | Operació HTTP | Què permet |
|---|---|---|
get |
GET a un objecte | Llegir un objecte pel seu nom |
list |
GET a la col·lecció | Llistar objectes, incloent-hi el seu contingut complet |
watch |
GET amb ?watch=true |
Rebre canvis en temps real |
create |
POST | Crear objectes nous |
update |
PUT | Reemplaçar un objecte sencer |
patch |
PATCH | Modificar parcialment un objecte |
delete |
DELETE a un objecte | Esborrar-ne un pel nom |
deletecollection |
DELETE a la col·lecció | Esborrar tots els que coincideixin amb un selector |
I tres verbs especials que no corresponen a una operació normal:
| Verb especial | Sobre quin recurs | Què significa |
|---|---|---|
bind |
roles, clusterroles |
Permet crear bindings a aquell rol encara que no en tinguis els permisos |
escalate |
roles, clusterroles |
Permet crear o editar rols amb permisos que tu no tens |
impersonate |
users, groups, serviceaccounts |
Permet actuar en nom d'una altra identitat |
Els tres són, a la pràctica, vies d'escalada de privilegis. Hi tornarem a l'apartat 10.
Un avís important sobre deletecollection: molta gent el passa per alt en construir un rol de "poder esborrar coses concretes". Si concedeixes delete amb resourceNames però també deletecollection sense restricció, has obert la porta a esborrar-ho tot, perquè resourceNames no s'aplica a les operacions de col·lecció.
resourceNames: limitar a objectes concrets
Permet acotar una regla a objectes amb noms concrets:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: lectura-config-botiga
namespace: rutas-norte-pro
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["botiga-web-config"] # NOMÉS aquest ConfigMap
verbs: ["get"]Amb aquest rol es pot fer kubectl get configmap botiga-web-config però no kubectl get configmap qualsevol-altre.
La limitació fonamental de
resourceNames. No funciona amblist,watch,createnideletecollection. La raó és senzilla: quan demanes una llista, encara no saps quins objectes hi ha, així que l'autoritzador no pot filtrar per nom —només pot permetre o denegar la petició sencera—. RBAC no és un filtre de contingut.
Conseqüència pràctica: resourceNames restringeix la lectura individual, però no impedeix un llistat si has concedit list. Si la teva intenció és "que només pugui veure aquest ConfigMap", has de concedir get amb resourceNames i no concedir list. La contrapartida és que kubectl get configmaps (sense nom) fallarà amb un 403, cosa que desconcerta els usuaris; cal documentar-ho.
Fins i tot amb aquesta limitació, resourceNames és molt valuós en un cas concret: donar a un procés permís per actualitzar exactament un objecte. El farem servir amb ci-rutasnorte.
- Subrecursos: el poder ocult de
pods/exec i pods/log
pods/exec i pods/logAlguns recursos tenen subrecursos: rutes filles de l'API amb el seu propi control d'accés. S'escriuen amb una barra al camp resources.
| Subrecurs | Què permet | Risc |
|---|---|---|
pods/log |
Llegir els logs del contenidor | Mitjà: els logs poden contenir dades sensibles |
pods/exec |
Obrir un procés dins del contenidor | Molt alt: accés interactiu total al contenidor |
pods/attach |
Connectar-se al procés principal | Molt alt: equivalent a la pràctica a exec |
pods/portforward |
Obrir un túnel a un port del pod | Alt: arriba a serveis interns des del portàtil |
pods/ephemeralcontainers |
Injectar contenidors efímers (kubectl debug, veure 07-06) |
Molt alt |
pods/status |
Actualitzar l'estat del pod | Baix, ús de controladors |
deployments/scale |
Canviar només el nombre de rèpliques | Baix, molt útil |
serviceaccounts/token |
Emetre un token per a una ServiceAccount | Crític: suplantar aquell procés |
nodes/proxy |
Parlar amb l'API del kubelet | Crític |
El punt clau que s'oblida constantment:
get podsiget pods/logsón permisos diferents. Poder veure que un pod existeix no dona dret a llegir-ne els logs. Ipods/execno el concedeix el verbgetsobrepods: cal anomenar-lo explícitament. Això és una bona notícia, perquè permet donar visibilitat sense donar accés.
Exemple directament aplicable a Rutas Norte: l'equip de suport necessita veure si un pod ha caigut i llegir-ne els logs per respondre a un client que diu que no li arriba el correu de confirmació. No necessita entrar al contenidor.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rutasnorte-suport
rules:
# Veure l'estat dels pods i dels objectes que els governen
- apiGroups: [""]
resources: ["pods", "services", "events", "endpoints"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "replicasets"]
verbs: ["get", "list", "watch"]
# Llegir logs: subrecurs explícit
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
# ATENCIÓ: NO hi apareixen "secrets", ni "pods/exec", ni "pods/portforward",
# ni "pods/ephemeralcontainers". És deliberat i és el nucli del rol.Fixa't en el que no hi és. Un rol de seguretat es jutja tant pel que omet com pel que inclou. Si suport tingués pods/exec sobre api-reserves, podria obrir una shell, llegir les variables d'entorn del procés i obtenir la cadena de connexió a la base de dades de clients. El límit entre "veure l'estat" i "llegir dades personals" és exactament aquella línia del YAML.
I compte amb pods/log fins i tot així: a 07-05 vam prohibir explícitament registrar dades personals als logs precisament perquè un permís de lectura de logs és molt més fàcil de concedir que un de lectura de base de dades. Les dues decisions es reforcen.
- Els rols per defecte i els rols agregats
Tot clúster ve amb desenes de ClusterRole predefinits. Molts són interns (system:kube-scheduler, system:node...), però quatre estan pensats per a persones:
| ClusterRole | Abast recomanat | Què permet | Perills |
|---|---|---|---|
cluster-admin |
Tot el clúster | Absolutament tot, inclòs modificar RBAC | És * sobre *. Només per a emergències |
admin |
Un namespace (via RoleBinding) |
Gestionar-ho tot al namespace, inclosos Secrets, Roles i RoleBindings | Pot llegir tots els Secrets del namespace |
edit |
Un namespace | Crear i modificar la majoria d'objectes; no pot tocar Roles ni RoleBindings | Sí que pot llegir i crear Secrets |
view |
Un namespace | Només lectura de la majoria d'objectes; no veu Secrets | El més segur dels quatre |
Dos matisos que decideixen dissenys sencers:
editpot llegir Secrets. Molta gent concedeixedita producció pensant "és que només edita, no administra", i amb això ha lliurat les credencials de la base de dades de clients. Si això t'importa —i a Rutas Norte ens importa molt—, no pots fer serviredittal qual arutas-norte-pro.viewno veu Secrets (va ser una decisió explícita del projecte), però sí que veu ConfigMaps. Això reforça el que vam dir a 03-01 i 03-02: les dades sensibles van en Secrets, mai en ConfigMaps, encara que "només sigui una cadena de connexió sense contrasenya".
Comprovar què fa exactament un rol abans de fer-lo servir és un hàbit obligatori:
kubectl describe clusterrole view | head -30
kubectl get clusterrole edit -o yaml | grep -A3 secretsRols agregats (aggregationRule)
Alguns rols per defecte no tenen regles pròpies: es componen automàticament amb les regles de tots els ClusterRole que porten certes etiquetes. Això és la aggregationRule.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: view
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.authorization.k8s.io/aggregate-to-view: "true"
rules:
- ... # emplenat automàticament pel controladorEl controlador d'agregació vigila els ClusterRole amb aquella etiqueta i copia les seves regles dins de view. Això és enormement útil quan instal·les CRDs: pots fer que qui té view vegi també els teus recursos personalitzats sense editar el rol view (que se sobreescriuria en cada actualització del clúster).
Exemple real per a Rutas Norte: al mòdul 7 vam instal·lar l'operador de Prometheus, que aporta ServiceMonitor i PrometheusRule. Volem que suport i desenvolupament els puguin veure.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rutasnorte-agregat-monitoratge-lectura
labels:
# Aquestes etiquetes fan que les regles se sumin als rols per defecte
rbac.authorization.k8s.io/aggregate-to-view: "true"
rbac.authorization.k8s.io/aggregate-to-edit: "true"
rbac.authorization.k8s.io/aggregate-to-admin: "true"
rules:
- apiGroups: ["monitoring.coreos.com"]
resources: ["servicemonitors", "prometheusrules", "podmonitors"]
verbs: ["get", "list", "watch"]En aplicar-lo, qualsevol que tingui view a qualsevol namespace passa a veure els ServiceMonitor d'aquell namespace, sense tocar cap binding. Fixa't en la jerarquia: admin agrega el d'edit, i edit agrega el de view, així que si només poses aggregate-to-view, tots tres l'hereten igualment. Posar-les totes tres és explícit i no fa cap mal.
- Verificar permisos amb
kubectl auth can-i
kubectl auth can-iEscriure RBAC sense verificar-lo és com escriure tests sense executar-los. kubectl auth can-i respon a la pregunta exacta que l'apiserver es fa.
# Puc jo, amb el meu kubeconfig actual, crear deployments aquí?
kubectl auth can-i create deploymentsEl veritablement potent és --as i --as-group, que pregunten en nom d'una altra identitat (això utilitza el mecanisme d'impersonation, així que necessites el verb impersonate o ser administrador):
# Pot l'equip de suport entrar en un contenidor de producció?
kubectl auth can-i create pods/exec \
--as-group suport \
--as [email protected] \
--namespace rutas-norte-pro# Pot llegir els logs? (això sí que ha de funcionar)
kubectl auth can-i get pods/log \
--as-group suport \
--as [email protected] \
--namespace rutas-norte-proTambé es poden verificar ServiceAccounts, que és com s'auditen els permisos dels processos:
kubectl auth can-i list secrets \
--as system:serviceaccount:rutas-norte-pro:informes-ocupacio \
--namespace rutas-norte-proI una vista completa de tot el que pot fer un subjecte en un namespace:
kubectl auth can-i --list \
--as system:serviceaccount:rutas-norte-pro:informes-ocupacio \
--namespace rutas-norte-proResources Non-Resource URLs Resource Names Verbs
selfsubjectreviews.authentication.k8s.io [] [] [create]
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
pods [] [] [get list]
configmaps [] [informes-config] [get]
[/api/*] [] [get]Aquesta sortida és exactament el "full de permisos" que cal revisar en cada auditoria. Si hi apareix res que no esperaves, tens un binding de més.
kubectl auth whoami
Estable des de 1.28, respon a "qui es pensa el clúster que soc?". És l'eina per diagnosticar problemes d'autenticació, que es confonen constantment amb problemes d'autorització.
ATTRIBUTE VALUE
Username [email protected]
Groups [desenvolupament system:authenticated]Un patró de diagnòstic útil:
| Símptoma | Comprovació | Causa probable |
|---|---|---|
401 Unauthorized |
kubectl auth whoami falla |
Credencial caducada o mal configurada |
403 Forbidden i whoami mostra els grups esperats |
kubectl auth can-i |
Falta un binding o el rol no cobreix el verb/recurs |
403 i whoami mostra grups inesperats |
Configuració d'OIDC | El proveïdor no envia els grups correctes |
403 només en un namespace |
RoleBinding al namespace |
El binding existeix en un altre namespace |
Un guió de verificació que val la pena guardar al repositori al costat dels manifests, per executar-lo després de cada canvi de RBAC:
#!/usr/bin/env bash
# k8s/rbac/verificar-rbac.sh
# Comprova que el RBAC de Rutas Norte concedeix i denega el que s'espera.
set -euo pipefail
fallades=0
comprovar() {
local esperat="$1"; shift
local descripcio="$1"; shift
local resultat
resultat=$(kubectl auth can-i "$@" 2>/dev/null || true)
if [[ "$resultat" == "$esperat" ]]; then
echo "OK $descripcio (=$esperat)"
else
echo "FALLA $descripcio: esperat '$esperat', obtingut '$resultat'"
fallades=$((fallades + 1))
fi
}
# El que suport SÍ ha de poder fer
comprovar yes "suport llegeix logs a pro" \
get pods/log --as-group suport --as revisor --namespace rutas-norte-pro
# El que suport MAI ha de poder fer
comprovar no "suport NO entra en contenidors de pro" \
create pods/exec --as-group suport --as revisor --namespace rutas-norte-pro
comprovar no "suport NO llegeix secrets de pro" \
get secrets --as-group suport --as revisor --namespace rutas-norte-pro
# El que desenvolupament NO ha de poder fer a producció
comprovar no "desenvolupament NO esborra deployments a pro" \
delete deployments --as-group desenvolupament --as revisor --namespace rutas-norte-pro
comprovar no "desenvolupament NO llegeix secrets de pro" \
get secrets --as-group desenvolupament --as revisor --namespace rutas-norte-pro
# El que la CI SÍ i NO ha de poder fer
comprovar yes "CI actualitza deployments a pro" \
patch deployments --as system:serviceaccount:rutas-norte-pro:ci-rutasnorte \
--namespace rutas-norte-pro
comprovar no "CI NO esborra deployments a pro" \
delete deployments --as system:serviceaccount:rutas-norte-pro:ci-rutasnorte \
--namespace rutas-norte-pro
exit $falladesExecutar aquest guió en cada canvi converteix el RBAC en una cosa verificable. Sense ell, un roleRef mal copiat pot passar mesos inadvertit.
- El disseny de RBAC de Rutas Norte
Ara apliquem tot això als quatre col·lectius de l'empresa i a les ServiceAccounts dels processos. El principi rector és el de mínim privilegi: cada subjecte rep el just per a la seva feina.
Resum del disseny:
| Subjecte | rutas-norte-dev |
rutas-norte-pre |
rutas-norte-pro |
Recursos globals |
|---|---|---|---|---|
desenvolupament |
Ampli (edit propi, amb Secrets) |
Lectura + logs | Només lectura, sense Secrets, sense exec | Cap |
plataforma |
Administració | Administració | Administració | Lectura de nodes, PV, SC |
suport |
— | — | Lectura de pods i logs, sense Secrets ni exec | Cap |
ci-rutasnorte |
Desplegar | Desplegar | Només actualitzar Deployments/StatefulSets | Cap |
L'equip de desenvolupament
A rutas-norte-dev necessiten treballar amb llibertat. Fem servir el ClusterRole edit acotat amb un RoleBinding:
# k8s/rbac/desenvolupament-dev.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: desenvolupament-edit-dev
namespace: rutas-norte-dev
labels:
app.kubernetes.io/part-of: rutas-norte
subjects:
- kind: Group
name: desenvolupament
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: edit # inclou llegir i crear Secrets: acceptable a dev
apiGroup: rbac.authorization.k8s.ioÉs acceptable perquè a rutas-norte-dev no hi ha dades reals de clients: només dades sintètiques generades per a proves. Aquesta afirmació és una decisió de disseny que ha d'estar documentada i verificada, no una suposició. Si algun dia algú copiés un abocament de producció a desenvolupament, aquest binding passaria a ser un incident greu. És exactament el tipus de regla que el responsable de compliment ha de conèixer.
A rutas-norte-pro el plantejament canvia completament. Els desenvolupadors necessiten investigar —veure l'estat, llegir logs, consultar esdeveniments— però no tocar res ni llegir credencials.
# k8s/rbac/desenvolupament-pro.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rutasnorte-diagnostic-lectura
rules:
- apiGroups: [""]
resources:
["pods", "services", "configmaps", "events", "endpoints",
"persistentvolumeclaims", "serviceaccounts"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
resources: ["jobs", "cronjobs"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses", "networkpolicies"]
verbs: ["get", "list", "watch"]
- apiGroups: ["autoscaling"]
resources: ["horizontalpodautoscalers"]
verbs: ["get", "list", "watch"]
# Logs sí; exec, attach, portforward i contenidors efímers NO.
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
# "secrets" no apareix en cap regla. Deliberat.
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: desenvolupament-lectura-pro
namespace: rutas-norte-pro
subjects:
- kind: Group
name: desenvolupament
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: rutasnorte-diagnostic-lectura
apiGroup: rbac.authorization.k8s.ioI el mateix ClusterRole, més el permís de logs, es reutilitza a rutas-norte-pre sense escriure regles noves. Aquest és l'avantatge de la combinació RoleBinding → ClusterRole.
L'equip de plataforma (SRE)
Administra els tres namespaces. Podríem donar-los cluster-admin, però és excessiu i fa impossible distingir al registre d'auditoria (08-06) una operació rutinària d'una d'anòmala. Preferim admin per namespace més un ClusterRole de lectura d'infraestructura:
# k8s/rbac/plataforma.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: plataforma-admin
namespace: rutas-norte-pro
subjects:
- kind: Group
name: plataforma
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: admin # gestió completa del namespace, inclosos Secrets i RBAC local
apiGroup: rbac.authorization.k8s.io
---
# (mateix RoleBinding replicat a rutas-norte-dev i rutas-norte-pre)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: plataforma-infraestructura
subjects:
- kind: Group
name: plataforma
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: rutasnorte-lectura-infraestructura # nodes, PV, StorageClasses: només lectura
apiGroup: rbac.authorization.k8s.iocluster-admin queda reservat per a un procediment d'emergència amb accés registrat i aprovació prèvia. En moltes organitzacions això s'implementa amb accés temporal (just-in-time): un sistema extern crea el ClusterRoleBinding, avisa per un canal públic i l'esborra automàticament al cap de dues hores.
Atenció al client (suport)
Ja vam escriure el seu ClusterRole a l'apartat 5. Només falta el binding, exclusivament a producció:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: suport-pro
namespace: rutas-norte-pro
subjects:
- kind: Group
name: suport
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: rutasnorte-suport
apiGroup: rbac.authorization.k8s.ioLa canalització d'integració contínua (ci-rutasnorte)
Aquest és el cas més interessant, perquè la CI és un objectiu molt llaminer: si algú compromet el sistema de CI i aquest té cluster-admin, té el clúster. Li donem exactament el que necessita per desplegar: actualitzar la imatge dels Deployments i StatefulSets existents. Res més.
# k8s/rbac/ci.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: ci-rutasnorte
namespace: rutas-norte-pro
automountServiceAccountToken: false # ningú munta aquest token en un pod (veure 03-06)
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ci-desplegament
namespace: rutas-norte-pro
rules:
# Actualitzar càrregues de treball existents: sí. Crear-les o esborrar-les: no.
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets"]
verbs: ["get", "list", "patch", "update"]
# Consultar el resultat del desplegament (kubectl rollout status)
- apiGroups: ["apps"]
resources: ["replicasets"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["pods", "events"]
verbs: ["get", "list"]
# NO hi ha: create, delete, deletecollection, secrets, pods/exec,
# roles, rolebindings, serviceaccounts.
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ci-desplegament
namespace: rutas-norte-pro
subjects:
- kind: ServiceAccount
name: ci-rutasnorte
namespace: rutas-norte-pro
roleRef:
kind: Role
name: ci-desplegament
apiGroup: rbac.authorization.k8s.ioAnalitzem les decisions:
- Sense
create: els objectes nous els crea una persona deplataformadesprés de revisió. La CI només actualitza el que ja existeix i ha estat revisat. - Sense
deletenideletecollection: una CI compromesa no pot esborrar la plataforma. - Sense
secrets: la CI no llegeix credencials del clúster. Les seves són al gestor de secrets de la canalització. - Sense
pods/exec: no hi ha manera que la CI obri una shell a producció.
Si volguéssim ser encara més estrictes, podríem acotar amb resourceNames als noms exactes de les càrregues:
- apiGroups: ["apps"]
resources: ["deployments"]
resourceNames: ["botiga-web", "api-reserves", "worker-notificacions"]
verbs: ["get", "patch", "update"]
# ...i una regla a part amb només "list" (sense resourceNames), perquè
# resourceNames no s'aplica a list, per tal que la CI pugui llistar.
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["list"]Aquest és el patró correcte quan cal el llistat: dues regles, una de restrictiva per a les operacions per nom i una altra amb només list.
ServiceAccounts dels processos
El CronJob informes-ocupacio genera un informe nocturn d'ocupació de places. No necessita parlar amb l'API de Kubernetes en absolut, així que el correcte és que ni tan sols munti el token:
apiVersion: v1
kind: ServiceAccount
metadata:
name: informes-ocupacio
namespace: rutas-norte-pro
automountServiceAccountToken: false
# Sense cap Role ni RoleBinding: no necessita permisos.Que un procés no tingui cap binding és la situació ideal i més freqüent del que la gent es pensa. botiga-web, worker-notificacions i redis-cache són en el mateix cas.
api-reserves sí que necessita llegir un ConfigMap concret per recarregar la seva configuració de rutes en calent:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: api-reserves-config
namespace: rutas-norte-pro
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["api-reserves-rutes"] # exactament aquest, cap altre
verbs: ["get", "watch"]I l'operador de PostgreSQL del mòdul 6, que sí que necessita permisos amplis perquè la seva feina és gestionar StatefulSets, Services i PVCs. Recorda el que vam dir a 06-07: abans d'adoptar un operador cal llegir quin RBAC demana. Aquest és el mínim raonable, acotat a un namespace:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: operador-postgres
namespace: rutas-norte-pro
rules:
- apiGroups: ["basededades.rutasnorte.example"]
resources: ["clusterspostgres", "clusterspostgres/status", "clusterspostgres/finalizers"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["apps"]
resources: ["statefulsets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["services", "persistentvolumeclaims", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# L'operador crea les credencials de la base de dades: necessita secrets.
# Està acotat a AQUEST namespace, no a tot el clúster. És la diferència
# entre "pot llegir les credencials de la BD de clients" i
# "pot llegir TOTES les credencials del clúster".
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]Si l'operador que estàs avaluant exigeix un ClusterRole amb secrets a tot el clúster i la seva documentació no explica per què, aquesta és una raó legítima per rebutjar-lo o per desplegar-lo amb l'abast restringit a un namespace, si ho admet.
- Per què
list sobre secrets equival a llegir-los
list sobre secrets equival a llegir-losAquest és un dels punts que més gent entén malament, i té conseqüències directes sobre les dades de clients de Rutas Norte.
Quan executes kubectl get secrets, kubectl fa un GET /api/v1/namespaces/<ns>/secrets. L'apiserver retorna una SecretList amb els objectes complets, inclòs el camp data amb tots els valors. Que kubectl et mostri una taula resumida sense els valors és només una decisió de presentació del client. Les dades ja han viatjat per la xarxa fins a la teva màquina.
# Això utilitza "list", no "get", i retorna els valors de TOTS els secrets
kubectl get secrets -n rutas-norte-pro -o yamlapiVersion: v1
items:
- apiVersion: v1
kind: Secret
metadata:
name: postgres-reserves-credencials
namespace: rutas-norte-pro
data:
POSTGRES_PASSWORD: <valor en base64, perfectament llegible>
POSTGRES_USER: <valor en base64>
type: Opaque
kind: ListCom vam dir a 03-02, base64 no és xifratge: és codificació. Una sola ordre ho reverteix.
Regla que no admet excepcions: concedir
listsobresecretsés concedir lectura de tots els secrets d'aquell àmbit. No hi ha manera de llistar secrets "sense veure'n el contingut". Si un rol télistsobresecretsarutas-norte-pro, qui el tingui pot llegir les credencials de la base de dades de clients. Tracta-ho amb la mateixa serietat que lliurar la contrasenya.
El mateix val per a watch, que lliura els objectes complets en cada canvi.
Per què els comodins són gairebé sempre un error
Això és cluster-admin. Però fins i tot els comodins "petits" són perillosos:
| Regla amb comodí | El que la gent es pensa que concedeix | El que concedeix de debò |
|---|---|---|
resources: ["*"], verbs: ["get","list"] |
"Llegir coses" | Tots els Secrets, tokens, i qualsevol recurs futur |
apiGroups: ["*"], resources: ["deployments"] |
Deployments | Deployments de qualsevol grup, present i futur |
verbs: ["*"] sobre pods |
Gestionar pods | Inclou pods/exec, pods/attach, pods/portforward |
L'última fila és especialment traïdora: un comodí a verbs sobre pods no inclou els subrecursos (cal anomenar-los), però resources: ["pods/*"] sí que inclou exec, attach i portforward. Val la pena memoritzar la diferència.
I hi ha un problema temporal: un comodí concedeix permisos sobre recursos que encara no existeixen. Si instal·les un CRD nou el mes que ve, qualsevol amb resources: ["*"] el controlarà des del primer dia, sense que ningú hagi decidit res.
L'alternativa correcta és sempre enumerar. És més llarg d'escriure i moltíssim més fàcil d'auditar.
- Escalada de privilegis i les proteccions de Kubernetes
La protecció contra l'escalada per privilegis
Kubernetes impedeix, per defecte, que creïs o modifiquis un rol amb permisos que tu no tens. Si poguessis, qualsevol amb permís sobre roles seria cluster-admin de facto: s'escriuria un rol amb * i se l'assignaria.
# Algú de "desenvolupament" (que només té "edit" a dev) intenta això
kubectl create role escalada --verb='*' --resource='*' -n rutas-norte-devError from server (Forbidden): roles.rbac.authorization.k8s.io "escalada" is forbidden:
user "[email protected]" (groups=["desenvolupament" "system:authenticated"]) is attempting
to grant RBAC permissions not currently held:
{APIGroups:["*"], Resources:["*"], Verbs:["*"]}La regla exacta és: per crear o modificar un rol, has de posseir ja tots els permisos que aquell rol concedeix (en l'àmbit corresponent). Hi ha dues excepcions deliberades:
| Verb | Efecte | Quan es fa servir legítimament |
|---|---|---|
escalate sobre roles/clusterroles |
Permet crear rols amb permisos que no tens | Controladors que gestionen RBAC (Argo CD, operadors) |
bind sobre un ClusterRole concret |
Permet crear bindings a aquell rol sense tenir-ne els permisos | Delegar "pots donar el rol X" sense donar X |
bind, acotat amb resourceNames, és el mecanisme elegant per delegar. A Rutas Norte podríem permetre que els caps d'equip concedissin el rol de suport sense ser administradors:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: delegar-rol-suport
namespace: rutas-norte-pro
rules:
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["rolebindings"]
verbs: ["create", "get", "list", "delete"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["clusterroles"]
resourceNames: ["rutasnorte-suport"] # NOMÉS pot vincular aquest rol
verbs: ["bind"]Qui tingui aquest rol pot crear bindings al rol rutasnorte-suport i a cap altre. No pot vincular admin, ni cluster-admin. És una delegació segura.
Poder crear pods equival a fer servir qualsevol ServiceAccount del namespace
Aquesta és probablement la implicació més subestimada de tot RBAC:
Si pots crear pods en un namespace, pots executar codi amb la identitat de qualsevol ServiceAccount d'aquell namespace, simplement posant el seu nom a
spec.serviceAccountName.
# Un pod pot declarar qualsevol ServiceAccount del SEU namespace
apiVersion: v1
kind: Pod
metadata:
name: qualsevol-cosa
namespace: rutas-norte-pro
spec:
serviceAccountName: operador-postgres # hereta TOTS els seus permisos!
containers:
- name: app
image: registry.rutasnorte.example/utilitats:1.4.2Crear pods no està protegit per la regla de "no pots concedir el que no tens", perquè tècnicament no estàs creant un rol: estàs creant un pod. Però l'efecte pràctic és idèntic a heretar els permisos d'aquella ServiceAccount.
Conseqüències concretes per al nostre disseny:
create podsarutas-norte-proés un permís d'alt privilegi. Per aixòci-rutasnorteno el té idesenvolupamenttampoc el té a producció.- Mai posis al mateix namespace una ServiceAccount molt privilegiada i càrregues de treball normals. L'operador de PostgreSQL, que pot llegir secrets, comparteix namespace amb
botiga-web. Qui pugui crear un pod allà, hereta els permisos de l'operador. El correcte és aïllar els operadors al seu propi namespace. - Els verbs que creen pods indirectament compten igual: crear Deployments, StatefulSets, DaemonSets, Jobs o CronJobs és crear pods per delegació.
editels inclou tots.
Altres rutes d'escalada que convé conèixer per poder tancar-les:
| Permís | Per què és d'alt privilegi |
|---|---|
create pods |
Suplantar qualsevol SA del namespace (a dalt) |
create pods/exec |
Entrar en un pod que ja fa servir una SA privilegiada |
create serviceaccounts/token |
Emetre un token per a qualsevol SA del namespace |
impersonate sobre users/groups |
Actuar com una altra persona, inclòs system:masters |
escalate sobre rols |
Autoconcedir-se qualsevol permís |
create sobre certificatesigningrequests/approval |
Emetre certificats de client amb el grup que vulgui |
get nodes/proxy |
Parlar amb el kubelet i executar en qualsevol pod del node |
patch sobre validatingwebhookconfigurations |
Desactivar els controls d'admissió (08-03) |
Auditar aquests permisos concrets és una tasca rutinària molt rendible:
# Qui pot crear pods a producció?
kubectl get rolebindings,clusterrolebindings -A -o json | jq -r '
.items[]
| select(.metadata.namespace == "rutas-norte-pro" or .kind == "ClusterRoleBinding")
| "\(.kind)/\(.metadata.name) -> \(.roleRef.kind)/\(.roleRef.name) : " +
([.subjects[]? | "\(.kind):\(.name)"] | join(", "))'RoleBinding/plataforma-admin -> ClusterRole/admin : Group:plataforma
RoleBinding/suport-pro -> ClusterRole/rutasnorte-suport : Group:suport
RoleBinding/desenvolupament-lectura-pro -> ClusterRole/rutasnorte-diagnostic-lectura : Group:desenvolupament
RoleBinding/ci-desplegament -> Role/ci-desplegament : ServiceAccount:ci-rutasnorte
ClusterRoleBinding/plataforma-infraestructura -> ClusterRole/rutasnorte-lectura-infraestructura : Group:plataformaAquesta llista ha de cabre en una pantalla i cada línia ha de tenir una justificació coneguda. El dia que no hi càpiga, comença el problema.
- Auditoria i revisió periòdica de permisos
RBAC es degrada amb el temps. Un permís temporal que ningú va retirar, un binding per depurar una incidència que s'hi va quedar, un grup que va créixer. L'única defensa és la revisió sistemàtica.
Qui té cluster-admin
kubectl get clusterrolebindings -o json | jq -r '
.items[]
| select(.roleRef.name == "cluster-admin")
| "\(.metadata.name): " + ([.subjects[]? | "\(.kind)/\(.name)"] | join(", "))'cluster-admin: Group/system:masters
plataforma-emergencia-2026-08: User/[email protected]La segona línia és exactament el que cal caçar: un accés d'emergència que havia de durar dues hores i encara hi és. La convenció de posar la data al nom del binding fa que salti a la vista.
Qui pot llegir els secrets de producció
for verb in get list watch; do
echo "=== verb: $verb ==="
for grup in desenvolupament plataforma suport; do
printf " %-16s %s\n" "$grup" \
"$(kubectl auth can-i "$verb" secrets --as-group "$grup" --as revisor -n rutas-norte-pro)"
done
done=== verb: get ===
desenvolupament no
plataforma yes
suport no
=== verb: list ===
desenvolupament no
plataforma yes
suport no
=== verb: watch ===
desenvolupament no
plataforma yes
suport noNomés plataforma. És el resultat que buscàvem i cal poder demostrar-ho en qualsevol moment: aquesta sortida, guardada amb data, és una evidència vàlida per a una auditoria de compliment.
Detectar bindings orfes
Quan una persona deixa l'empresa, el seu binding s'hi queda. Quan una ServiceAccount s'esborra, els seus bindings també. Una comprovació senzilla:
# ServiceAccounts referenciades en bindings que ja no existeixen
kubectl get rolebindings -A -o json | jq -r '
.items[]
| . as $rb
| .subjects[]?
| select(.kind == "ServiceAccount")
| "\(.namespace // $rb.metadata.namespace) \(.name) \($rb.metadata.namespace)/\($rb.metadata.name)"' \
| while read -r ns sa binding; do
kubectl get sa "$sa" -n "$ns" >/dev/null 2>&1 || echo "ORFE: $binding -> sa $ns/$sa"
doneEines d'anàlisi
Existeixen utilitats específiques que fan aquesta feina molt més còmoda:
| Eina | Què aporta |
|---|---|
kubectl who-can (krew) |
"Qui pot fer X sobre Y?" en una ordre |
rbac-tool (krew) |
Visualitza el graf de permisos, detecta regles redundants |
kubectl-rbac-lookup (krew) |
Llista els permisos d'un subjecte a tot el clúster |
| Kubescape / Trivy | Inclouen comprovacions de RBAC excessiu (veure 08-06) |
ROLEBINDING NAMESPACE SUBJECT TYPE SA-NAMESPACE
plataforma-admin rutas-norte-pro plataforma Group
operador-postgres rutas-norte-pro op-postgres ServiceAccount rutas-norte-proEl procés de revisió
Una cadència raonable per a una plataforma com Rutas Norte:
| Freqüència | Revisió | Responsable |
|---|---|---|
| En cada canvi | El guió verificar-rbac.sh a la canalització |
Automàtic |
| Setmanal | ClusterRoleBinding nous o modificats |
plataforma |
| Trimestral | Qui pot llegir Secrets de rutas-norte-pro i qui té cluster-admin |
plataforma + seguretat |
| Trimestral | Bindings orfes i accessos d'emergència caducats | plataforma |
| Anual | Revisió completa del disseny amb seguretat i compliment | Direcció |
Tots els manifests de RBAC han de viure al repositori, a k8s/base/rbac/, i aplicar-se només des d'allà. Un binding creat a mà amb kubectl create rolebinding és invisible per a la revisió de codi i és exactament per on entren els problemes. Al mòdul 10 veurem amb GitOps com fer que el clúster rebutgi de facto qualsevol cosa que no sigui a Git.
Errors Comuns i Consells
Escriure apiGroups: ["core"]. No existeix. El grup del nucli és la cadena buida "". El manifest s'aplica sense error i no concedeix res; després passes una hora buscant per què hi ha un 403.
Fer servir el Kind o el nom curt a resources. Cal fer servir el plural en minúscules: pods, no Pod ni po; deployments, no Deployment. Consulta-ho amb kubectl api-resources.
Creure que un RoleBinding a un ClusterRole dona permisos globals. No: els acota al namespace del binding. És just al contrari del que suggereix el nom i és la combinació més útil de les quatre.
Intentar canviar el roleRef d'un binding existent. És immutable. Cal esborrar i recrear. Un kubectl apply que canvia el roleRef falla amb un error poc descriptiu.
Concedir edit a producció "perquè només edita". edit llegeix i crea Secrets, i crea pods (amb la qual cosa pot suplantar qualsevol ServiceAccount del namespace). En un namespace amb dades personals és un permís d'administració disfressat.
Suposar que resourceNames protegeix el llistat. No s'aplica a list, watch, create ni deletecollection. Si concedeixes list sense noms, es veu tot.
Oblidar que list sobre Secrets és llegir-los. No hi ha manera de llistar-los sense veure'n el contingut.
Afegir comodins "per sortir del pas". El permís temporal s'hi queda, i a sobre concedeix accés a recursos que encara no existeixen. Si necessites desbloquejar algú de pressa, concedeix el permís exacte que li falta i obre-li una tasca per revisar-ho.
Concedir permisos a usuaris en comptes de a grups. Cada alta i cada baixa es converteix en un canvi de manifest, i les baixes s'obliden sempre.
Confondre 401 amb 403. 401 és autenticació (no sé qui ets), 403 és autorització (sé qui ets i no pots). kubectl auth whoami distingeix els dos casos en un segon.
No verificar. Escriu sempre les comprovacions negatives: no n'hi ha prou amb confirmar que suport pot llegir logs, cal confirmar que no pot fer exec ni llegir Secrets. Les fallades de seguretat són als permisos de més, no als de menys.
Posar un operador privilegiat al mateix namespace que les aplicacions. Qui pugui crear un pod allà hereta els permisos de l'operador. Namespace a part.
Consell d'or: comença sempre per zero permisos i afegeix només el que falla. És més lent el primer dia i moltíssim més segur tots els altres. Amb kubectl auth can-i --list sabràs en cada moment exactament on ets.
Exercicis
Exercici 1: rol de només lectura amb logs, sense exec ni secrets
Rutas Norte incorpora un becari a l'equip de suport. Necessites un ClusterRole anomenat rutasnorte-suport-junior i un binding que l'apliqui només a rutas-norte-pre. Ha de poder:
- Llistar i veure pods, serveis i deployments.
- Llegir els logs dels pods.
I no ha de poder:
- Llegir Secrets.
- Executar
kubectl execnikubectl port-forward. - Modificar res.
Escriu els manifests i les ordres de verificació que demostrin les tres prohibicions.
Exercici 2: RBAC mínim per a un recol·lector de mètriques
Un nou agent de mètriques, agent-inventari, corre com a Deployment a rutas-norte-pro amb la seva pròpia ServiceAccount. Necessita llistar pods i nodes de tot el clúster per elaborar un inventari de recursos, i res més. Escriu la ServiceAccount, el ClusterRole, el ClusterRoleBinding i una comprovació que demostri que no pot llegir Secrets ni crear res.
Pregunta addicional: per què aquí sí que cal un ClusterRoleBinding i no n'hi ha prou amb un RoleBinding?
Exercici 3: auditar i corregir un rol perillós
Trobes aquest Role aplicat a rutas-norte-pro:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: equip-analitica
namespace: rutas-norte-pro
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list", "watch"]Està vinculat al grup analitica, l'únic comès del qual és consultar les mètriques d'ocupació mitjançant els ServiceMonitor i veure l'estat dels pods.
- Enumera tots els problemes de seguretat d'aquest rol.
- Reescriu-lo amb el mínim privilegi.
- Escriu les comprovacions que demostrin que la versió nova continua servint per al seu comès i ja no exposa dades personals.
Solucions
Solució 1
# k8s/rbac/suport-junior.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rutasnorte-suport-junior
labels:
app.kubernetes.io/part-of: rutas-norte
rules:
- apiGroups: [""]
resources: ["pods", "services", "events"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
# Absents deliberadament: secrets, pods/exec, pods/attach,
# pods/portforward, pods/ephemeralcontainers i tot verb d'escriptura.
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: suport-junior-pre
namespace: rutas-norte-pre # el ClusterRole queda acotat a aquest namespace
subjects:
- kind: Group
name: suport-junior
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: rutasnorte-suport-junior
apiGroup: rbac.authorization.k8s.ioVerificació:
kubectl apply -f k8s/rbac/suport-junior.yaml
SUBJECTE=(--as becari --as-group suport-junior)
# Ha de poder
kubectl auth can-i get pods/log "${SUBJECTE[@]}" -n rutas-norte-pre # yes
kubectl auth can-i list deployments "${SUBJECTE[@]}" -n rutas-norte-pre # yes
# NO ha de poder
kubectl auth can-i get secrets "${SUBJECTE[@]}" -n rutas-norte-pre # no
kubectl auth can-i create pods/exec "${SUBJECTE[@]}" -n rutas-norte-pre # no
kubectl auth can-i create pods/portforward "${SUBJECTE[@]}" -n rutas-norte-pre # no
kubectl auth can-i list pods "${SUBJECTE[@]}" -n rutas-norte-pro # no (altre namespace)L'última comprovació és la que demostra que el RoleBinding acota el ClusterRole: a rutas-norte-pro no té absolutament res.
Solució 2
# k8s/rbac/agent-inventari.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: agent-inventari
namespace: rutas-norte-pro
labels:
app.kubernetes.io/part-of: rutas-norte
# Aquí SÍ que es munta el token: el procés necessita parlar amb l'API.
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rutasnorte-inventari
rules:
- apiGroups: [""]
resources: ["pods", "nodes"]
verbs: ["get", "list", "watch"]
# Res més. Ni secrets, ni configmaps, ni cap verb d'escriptura.
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: agent-inventari
subjects:
- kind: ServiceAccount
name: agent-inventari
namespace: rutas-norte-pro
roleRef:
kind: ClusterRole
name: rutasnorte-inventari
apiGroup: rbac.authorization.k8s.ioVerificació:
SA=system:serviceaccount:rutas-norte-pro:agent-inventari
kubectl auth can-i list pods --as "$SA" -A # yes
kubectl auth can-i list nodes --as "$SA" # yes
kubectl auth can-i get secrets --as "$SA" -n rutas-norte-pro # no
kubectl auth can-i create pods --as "$SA" -n rutas-norte-pro # no
kubectl auth can-i delete pods --as "$SA" -A # no
kubectl auth can-i --list --as "$SA" -n rutas-norte-proyes
yes
no
no
no
Resources Non-Resource URLs Resource Names Verbs
pods [] [] [get list watch]
nodes [] [] [get list watch]
...Per què cal un ClusterRoleBinding: per dues raons independents.
nodesés un recurs sense namespace. UnRoleBindingmai no pot concedir accés a recursos globals, ni tan sols referenciant unClusterRoleque els esmenti.- L'agent ha de llistar pods de tots els namespaces. Un
RoleBindingacotaria la lectura al namespace del binding, i caldria replicar-lo a cada namespace existent i futur.
Si el requisit hagués estat només "pods de rutas-norte-pro", un RoleBinding hauria estat l'opció correcta i més segura.
Solució 3
1. Problemes del rol original:
| Problema | Conseqüència |
|---|---|
resources: ["*"] inclou secrets |
El grup analitica pot llegir les credencials de postgres-reserves, i amb elles accedir a les dades personals de tots els clients. Incident de protecció de dades. |
list sobre secrets |
Amb una sola ordre veu el contingut de tots els secrets del namespace. |
apiGroups: ["*"] |
Cobreix recursos que encara no existeixen: qualsevol CRD que s'instal·li demà quedarà exposat sense decisió de ningú. |
resources: ["*"] inclou serviceaccounts |
Facilita el reconeixement de quines identitats privilegiades hi ha al namespace. |
| No compleix el comès declarat | Per veure mètriques i estat de pods no cal res de tot això. |
| Regla impossible d'auditar | Ningú pot respondre d'un cop d'ull a "què pot veure aquest grup?". |
2. Versió amb mínim privilegi:
# k8s/rbac/analitica.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rutasnorte-analitica
labels:
app.kubernetes.io/part-of: rutas-norte
rules:
# Estat de les càrregues de treball
- apiGroups: [""]
resources: ["pods", "services"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "replicasets"]
verbs: ["get", "list", "watch"]
# Objectes de monitoratge del mòdul 7
- apiGroups: ["monitoring.coreos.com"]
resources: ["servicemonitors", "podmonitors", "prometheusrules"]
verbs: ["get", "list", "watch"]
# Mètriques d'ús (metrics-server, 07-02)
- apiGroups: ["metrics.k8s.io"]
resources: ["pods", "nodes"]
verbs: ["get", "list"]
# Absents deliberadament: secrets, configmaps, serviceaccounts,
# pods/log, pods/exec i tot verb d'escriptura.
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: analitica-pro
namespace: rutas-norte-pro
subjects:
- kind: Group
name: analitica
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: rutasnorte-analitica
apiGroup: rbac.authorization.k8s.ioFixa't que també hem tret pods/log: el comès declarat és consultar mètriques, i els logs podrien contenir informació de clients (encara que a 07-05 ho vam prohibir, el permís mínim no depèn que aquella prohibició es compleixi sempre).
3. Verificació:
kubectl delete role equip-analitica -n rutas-norte-pro
kubectl apply -f k8s/rbac/analitica.yaml
A=(--as analista --as-group analitica -n rutas-norte-pro)
echo "--- Ha de continuar funcionant ---"
kubectl auth can-i list pods "${A[@]}"
kubectl auth can-i list servicemonitors.monitoring.coreos.com "${A[@]}"
kubectl auth can-i list deployments "${A[@]}"
echo "--- Ja no ha de funcionar ---"
kubectl auth can-i get secrets "${A[@]}"
kubectl auth can-i list secrets "${A[@]}"
kubectl auth can-i watch secrets "${A[@]}"
kubectl auth can-i get pods/log "${A[@]}"
kubectl auth can-i list configmaps "${A[@]}"
kubectl auth can-i create pods "${A[@]}"Com que el rol vell concedia accés a les credencials de la base de dades de clients, no n'hi ha prou amb corregir el rol: cal revisar el registre d'auditoria per saber si algú va arribar a llegir aquell Secret, i si és així, rotar les credencials i notificar-ho al responsable de compliment. Com es fa aquella consulta és el tema de 08-06.
Conclusió
RBAC és la primera i més important línia de defensa d'un clúster de Kubernetes, i ara tens el model mental complet:
- Tota petició travessa tres portes: autenticació (qui ets), autorització (què pots fer, aquí actua RBAC) i control d'admissió (si l'objecte és acceptable).
- Els subjectes són usuaris i grups, que Kubernetes no emmagatzema i arriben de l'autenticador, i ServiceAccounts, que sí que són objectes del clúster.
- Quatre objectes:
Role/ClusterRolediuen què,RoleBinding/ClusterRoleBindingdiuen qui. La combinacióRoleBinding→ClusterRoleés la més útil: escriu el rol una vegada, acota'l on vulguis. - Una regla és la intersecció d'
apiGroups(amb el grup buit""del nucli),resources(inclosos subrecursos compods/logipods/exec),verbsi opcionalmentresourceNames(que no s'aplica alist). listsobre Secrets és llegir-los. Els comodins concedeixen més del que et penses, inclòs sobre recursos que encara no existeixen.- Crear pods equival a poder fer servir qualsevol ServiceAccount del namespace; per això els operadors privilegiats van al seu propi namespace.
kubectl auth can-i --as/--as-groupikubectl auth whoamiconverteixen el RBAC en una cosa verificable, i aquestes verificacions s'han d'executar automàticament en cada canvi.
A Rutas Norte hem passat de "qualsevol pot llegir les credencials de la base de dades de clients" a un disseny on només plataforma ho pot fer, suport veu l'estat i els logs sense poder entrar als contenidors, desenvolupament investiga producció sense tocar-la, i la canalització d'integració contínua només pot actualitzar la imatge de les càrregues que ja existeixen.
Però acabem de descobrir un límit incòmode: RBAC decideix si tens dret a crear un pod, no com és aquell pod. Algú de plataforma, amb permisos perfectament legítims, pot desplegar avui mateix un contenidor amb privileged: true, que munti / del node amb hostPath i corri com a root. RBAC dirà que sí, perquè té permís per crear pods. I des d'allà, el node sencer —i tots els pods que hi corrin, inclòs postgres-reserves— queda al descobert.
La lliçó següent, 08-02, Contextos de Seguretat i Enduriment del Contenidor, ataca exactament aquest problema: què aïlla realment un contenidor, per què no és una màquina virtual, i com es configura el securityContext de cada component de Rutas Norte perquè, encara que algú aconsegueixi executar codi dins d'un contenidor, no en pugui sortir.
Curs de Kubernetes
Mòdul 1: Introducció a Kubernetes
- Què és Kubernetes?
- Arquitectura de Kubernetes
- Conceptes i Terminologia Clau
- Configuració d'un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objectes, Manifests YAML i el Model Declaratiu
- El Projecte del Curs: la Plataforma Rutas Norte
Mòdul 2: Components Principals de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualitzacions, Rollbacks i Estratègies de Desplegament
- Serveis
- Namespaces
- Etiquetes, Selectors i Anotacions
Mòdul 3: Gestió de Configuració i Secrets
- ConfigMaps
- Secrets
- Variables d'Entorn
- Quotes i Límits de Recursos
- LimitRanges i Classes de Qualitat de Servei (QoS)
- ServiceAccounts i Accés a l'API des dels Pods
Mòdul 4: Xarxes a Kubernetes
- Xarxes de Clúster
- Tipus de Serveis
- DNS Intern i Descobriment de Serveis
- Controladors d'Ingress
- TLS i Gestió de Certificats amb cert-manager
- Polítiques de Xarxa
Mòdul 5: Emmagatzematge a Kubernetes
- Volums
- Volums Persistents
- Reclamacions de Volums Persistents
- Classes d'Emmagatzematge
- Aprovisionament Dinàmic, Expansió i Snapshots
- Còpies de Seguretat i Restauració de Dades
Mòdul 6: Conceptes Avançats de Kubernetes
- StatefulSets
- DaemonSets
- Treballs i CronJobs
- Init Containers, Sidecars i Patrons Multicontenidor
- Planificació: Afinitat, Taints i Toleracions
- Definicions de Recursos Personalitzats (CRDs)
- Operadors i el Patró Controlador
Mòdul 7: Monitoratge i Registre
- Verificacions de Salut i Sondes
- Servidor de Mètriques i kubectl top
- Monitoratge amb Prometheus
- Visualització i Alertes amb Grafana i Alertmanager
- Registre Centralitzat amb Elasticsearch, Fluentd i Kibana (EFK)
- Depuració d'Aplicacions i Esdeveniments del Clúster
Mòdul 8: Seguretat a Kubernetes
- Control d'Accés Basat en Rols (RBAC)
- Contextos de Seguretat i Enduriment del Contenidor
- Polítiques de Seguretat de Pods i Pod Security Standards
- Seguretat de Xarxa
- Seguretat d'Imatges
- Auditoria, Escaneig i Gestió de Vulnerabilitats
Mòdul 9: Escalat i Rendiment
- Autoescalat Horitzontal de Pods
- Autoescalat Vertical de Pods
- Autoescalat de Clúster
- Escalat per Esdeveniments i Mètriques Personalitzades amb KEDA
- Alta Disponibilitat: PodDisruptionBudgets i Topologia
- Ajust de Rendiment
Mòdul 10: Ecosistema i Eines de Kubernetes
- Minikube i Entorns Locals amb kind
- Kubeadm
- Helm
- Kustomize
- GitOps amb Argo CD i Flux
- Kubernetes Gestionat: EKS, AKS i GKE
Mòdul 11: Estudis de Cas i Aplicacions del Món Real
- Desplegament d'una Aplicació Web
- Execució d'Aplicacions amb Estat
- CI/CD amb Kubernetes
- Estratègies de Desplegament: Blue-Green i Canary
- Gestió Multi-Clúster
- Operació en Producció: Incidències, Runbooks i Costos
