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

  1. Les tres portes: autenticació, autorització i admissió
  2. Els subjectes: usuaris, grups i ServiceAccounts
  3. Els quatre objectes de RBAC
  4. Anatomia d'una regla: apiGroups, resources, verbs i resourceNames
  5. Subrecursos: el poder ocult de pods/exec i pods/log
  6. Els rols per defecte i els rols agregats
  7. Verificar permisos amb kubectl auth can-i
  8. El disseny de RBAC de Rutas Norte
  9. Per què list sobre secrets equival a llegir-los
  10. Escalada de privilegis i les proteccions de Kubernetes
  11. Auditoria i revisió periòdica de permisos
  12. Errors comuns i consells
  13. Exercicis
  14. Conclusió

  1. 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).

  1. 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 fer kubectl 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 del kubeconfig d'administrador que genera kubeadm. 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.io

Resum 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

  1. 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:

  • Role i ClusterRole descriuen què es pot fer. Són llistes de permisos, i no esmenten ningú.
  • RoleBinding i ClusterRoleBinding descriuen 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.io

Amb 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.

  1. 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-resources
NAME                  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        StorageClass

Quan 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 amb list, watch, create ni deletecollection. 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.

  1. Subrecursos: el poder ocult de pods/exec i pods/log

Alguns 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 pods i get pods/log són permisos diferents. Poder veure que un pod existeix no dona dret a llegir-ne els logs. I pods/exec no el concedeix el verb get sobre pods: 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.

  1. 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:

  1. edit pot llegir Secrets. Molta gent concedeix edit a 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 servir edit tal qual a rutas-norte-pro.
  2. view no veu Secrets (va ser una decisió explícita del projecte), però 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 secrets

Rols 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.

kubectl get clusterrole view -o yaml
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 controlador

El 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.

  1. Verificar permisos amb kubectl auth can-i

Escriure 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 deployments
yes
# Puc llegir secrets a producció?
kubectl auth can-i get secrets --namespace rutas-norte-pro
no

El 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
no
# 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-pro
yes

També 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-pro
no

I 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-pro
Resources                                       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ó.

kubectl auth whoami
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 $fallades

Executar aquest guió en cada canvi converteix el RBAC en una cosa verificable. Sense ell, un roleRef mal copiat pot passar mesos inadvertit.

  1. 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.io

I 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ó RoleBindingClusterRole.

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.io

cluster-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.io

La 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.io

Analitzem les decisions:

  • Sense create: els objectes nous els crea una persona de plataforma després de revisió. La CI només actualitza el que ja existeix i ha estat revisat.
  • Sense delete ni deletecollection: 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.

  1. Per què list sobre secrets equival a llegir-los

Aquest é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 yaml
apiVersion: 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: List

Com vam dir a 03-02, base64 no és xifratge: és codificació. Una sola ordre ho reverteix.

Regla que no admet excepcions: concedir list sobre secrets é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é list sobre secrets a rutas-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

# MAI facis això fora d'un laboratori
rules:
  - apiGroups: ["*"]
    resources: ["*"]
    verbs: ["*"]

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.

  1. 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-dev
Error 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.2

Crear 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:

  1. create pods a rutas-norte-pro és un permís d'alt privilegi. Per això ci-rutasnorte no el té i desenvolupament tampoc el té a producció.
  2. 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.
  3. Els verbs que creen pods indirectament compten igual: crear Deployments, StatefulSets, DaemonSets, Jobs o CronJobs és crear pods per delegació. edit els 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:plataforma

Aquesta 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.

  1. 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            no

Nomé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"
  done

Eines 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)
kubectl krew install who-can
kubectl who-can get secrets -n rutas-norte-pro
ROLEBINDING          NAMESPACE          SUBJECT      TYPE   SA-NAMESPACE
plataforma-admin     rutas-norte-pro    plataforma   Group
operador-postgres    rutas-norte-pro    op-postgres  ServiceAccount  rutas-norte-pro

El 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 exec ni kubectl 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.

  1. Enumera tots els problemes de seguretat d'aquest rol.
  2. Reescriu-lo amb el mínim privilegi.
  3. 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.io

Verificació:

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)
yes
yes
no
no
no
no

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.io

Verificació:

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-pro
yes
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.

  1. nodes és un recurs sense namespace. Un RoleBinding mai no pot concedir accés a recursos globals, ni tan sols referenciant un ClusterRole que els esmenti.
  2. L'agent ha de llistar pods de tots els namespaces. Un RoleBinding acotaria 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.io

Fixa'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[@]}"
--- Ha de continuar funcionant ---
yes
yes
yes
--- Ja no ha de funcionar ---
no
no
no
no
no
no

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/ClusterRole diuen què, RoleBinding/ClusterRoleBinding diuen qui. La combinació RoleBindingClusterRole é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 com pods/log i pods/exec), verbs i opcionalment resourceNames (que no s'aplica a list).
  • list sobre 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-group i kubectl auth whoami converteixen 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

Mòdul 2: Components Principals de Kubernetes

Mòdul 3: Gestió de Configuració i Secrets

Mòdul 4: Xarxes a Kubernetes

Mòdul 5: Emmagatzematge a Kubernetes

Mòdul 6: Conceptes Avançats de Kubernetes

Mòdul 7: Monitoratge i Registre

Mòdul 8: Seguretat a Kubernetes

Mòdul 9: Escalat i Rendiment

Mòdul 10: Ecosistema i Eines de Kubernetes

Mòdul 11: Estudis de Cas i Aplicacions del Món Real

Mòdul 12: Preparació per a la Certificació de Kubernetes

© Copyright 2026. Tots els drets reservats