Al cerrar el módulo 7 dejamos la plataforma Rutas Norte completamente observable: sondas que detectan un proceso enfermo, métricas en Prometheus, cuadros de mando en Grafana, alertas encaminadas por Alertmanager y todos los logs centralizados en Elasticsearch. Vemos absolutamente todo lo que ocurre. Y sin embargo, ahora mismo, cualquiera que tenga acceso al clúster puede leer el Secret con las credenciales de postgres-reservas —la base de datos que guarda el nombre, el DNI, el teléfono y el correo de cada cliente que ha comprado un billete—, puede desplegar un contenedor privilegiado que se escape al nodo, o puede subir una imagen que nadie ha revisado. Y no sabríamos quién lo hizo.
Este módulo entero se dedica a cerrar esas puertas. Y la primera, la más importante, es esta: decidir quién puede hacer qué. En Kubernetes eso se llama RBAC (Role-Based Access Control, control de acceso basado en roles) y es el mecanismo que convierte un clúster donde todo el mundo puede todo en un clúster donde cada persona y cada proceso tiene exactamente los permisos que necesita y ni uno más.
En el módulo 3 dejamos dos hilos sueltos que ahora recogemos. Al hablar de Secrets dijimos que quién puede leer un secreto lo decide RBAC. Al hablar de ServiceAccounts dijimos que la ServiceAccount responde a quién eres y RBAC a qué puedes hacer, y vimos un 403 Forbidden que demostraba que Kubernetes deniega por defecto. Esta lección explica exactamente por qué ocurrió ese 403 y cómo se concede, con precisión quirúrgica, solo lo imprescindible.
Advertencia importante. Esta lección enseña los mecanismos de RBAC y propone un diseño de ejemplo para una empresa ficticia. El diseño real de permisos de un clúster de producción debe revisarlo un profesional de seguridad, y si el clúster trata datos personales —como es el caso de
postgres-reservas— también el responsable de cumplimiento normativo de la organización. Un error de permisos no se nota hasta que alguien lo aprovecha.
Contenido
- Las tres puertas: autenticación, autorización y admisión
- Los sujetos: usuarios, grupos y ServiceAccounts
- Los cuatro objetos de RBAC
- Anatomía de una regla: apiGroups, resources, verbs y resourceNames
- Subrecursos: el poder oculto de
pods/execypods/log - Los roles por defecto y los roles agregados
- Verificar permisos con
kubectl auth can-i - El diseño de RBAC de Rutas Norte
- Por qué
listsobre secretos equivale a leerlos - Escalada de privilegios y las protecciones de Kubernetes
- Auditoría y revisión periódica de permisos
- Errores comunes y consejos
- Ejercicios
- Conclusión
- Las tres puertas: autenticación, autorización y admisión
Todo en Kubernetes pasa por el apiserver. Cuando escribes kubectl get pods, cuando el kubelet informa del estado de un nodo, cuando el controlador de Deployments crea un ReplicaSet, cuando informes-ocupacion consulta la lista de pods: todo son peticiones HTTP a la misma API. Y toda petición atraviesa tres puertas antes de llegar a etcd.
flowchart LR
A["Petición HTTP<br/>kubectl / SDK / curl"] --> B{"1. Autenticación<br/>¿quién eres?"}
B -->|401 Unauthorized| X1["Rechazada"]
B -->|Identidad válida| C{"2. Autorización<br/>¿puedes hacerlo?<br/>AQUÍ ACTÚA RBAC"}
C -->|403 Forbidden| X2["Rechazada"]
C -->|Permitido| D{"3. Control de admisión<br/>¿el objeto es aceptable?"}
D -->|Mutating: modifica| D
D -->|Validating: rechaza| X3["Rechazada (422 / 400)"]
D -->|Aceptado| E["Validación de esquema"]
E --> F[("etcd")]
Vamos puerta por puerta.
Puerta 1: autenticación (authentication)
Responde a ¿quién eres?. El apiserver examina las credenciales que acompañan a la petición y produce una identidad: un nombre de usuario, una lista de grupos y, opcionalmente, unos atributos extra. Los mecanismos habituales son:
| Mecanismo | Cómo llega la identidad | Uso típico |
|---|---|---|
| Certificado de cliente X.509 | El CN del certificado es el usuario; cada O es un grupo |
Administradores, clústeres pequeños, kubeadm |
| Token de ServiceAccount (JWT) | Firmado por el clúster; identifica system:serviceaccount:<ns>:<nombre> |
Procesos dentro del clúster |
| OIDC (proveedor externo) | Un token de un proveedor como Keycloak, Okta, Entra ID o Google | Personas en organizaciones medianas y grandes |
| Webhook de autenticación | El apiserver pregunta a un servicio externo | Integraciones a medida |
| Proveedores de nube (EKS, AKS, GKE) | El proveedor traduce su identidad IAM a usuario/grupos de Kubernetes | Kubernetes gestionado (ver 10-06) |
Si ninguno funciona, la petición se queda en system:anonymous y normalmente se rechaza con 401 Unauthorized.
Puerta 2: autorización (authorization)
Responde a ¿puedes hacer esto?. El apiserver toma la identidad de la puerta anterior y el detalle de la petición (verbo, grupo de API, recurso, namespace, nombre) y consulta a los autorizadores configurados. RBAC es el autorizador que usa prácticamente todo el mundo, aunque existen otros (Node, ABAC, Webhook, AlwaysAllow). Si ningún autorizador dice "sí", la respuesta es 403 Forbidden. No hay permiso implícito: Kubernetes deniega por defecto. Ese fue exactamente el 403 que vimos en 03-06.
Un detalle esencial: RBAC solo concede, nunca deniega. No existen reglas de denegación. El permiso efectivo de un sujeto es la unión de todo lo que le conceden todos sus bindings. Por eso quitar un permiso significa quitar el binding que lo otorga, no añadir una regla que lo prohíba.
Puerta 3: control de admisión (admission control)
Responde a ¿este objeto es aceptable tal y como viene?. Aquí actúan los controladores de admisión mutantes (que pueden modificar el objeto, como el que inyecta la ServiceAccount por defecto) y los validantes (que solo aceptan o rechazan). Es donde viven las ResourceQuota del módulo 3, el Pod Security Admission y las políticas de Kyverno.
Esta tercera puerta es el tema de 08-03; aquí solo la nombramos para que veas dónde encaja RBAC. Recuerda la diferencia clave:
RBAC decide si tienes derecho a hacer la operación. La admisión decide si el objeto que envías cumple las normas. Puedes tener permiso para crear pods (RBAC dice sí) y aun así ver rechazado un pod privilegiado (la admisión dice no).
- Los sujetos: usuarios, grupos y ServiceAccounts
RBAC concede permisos a sujetos. Hay exactamente tres tipos.
Usuarios (User)
Aquí viene la sorpresa que confunde a casi todo el mundo al empezar:
Kubernetes no almacena usuarios. No existe un objeto
User. No puedes hacerkubectl create user ana. No hay ninguna base de datos de personas dentro del clúster.
Un usuario es simplemente una cadena de texto que produce el autenticador. Si te autenticas con un certificado cuyo CN=ana.garcia, para RBAC eres el usuario ana.garcia. Si te autenticas con OIDC y el proveedor devuelve el email [email protected], ese será tu nombre de usuario. El clúster confía en el autenticador y punto.
La consecuencia práctica es importante: dar de alta y de baja a personas es responsabilidad del proveedor de identidad, no de Kubernetes. Si Ana deja la empresa y solo borras su RoleBinding, pero su certificado sigue siendo válido y le quedaban permisos por otro binding de grupo, sigue entrando.
Grupos (Group)
Igual que los usuarios: son cadenas que produce el autenticador. Con un certificado X.509, cada campo O (organización) se convierte en un grupo. Con OIDC, suele configurarse un claim como groups.
Casi siempre debes conceder permisos a grupos, no a usuarios. Si Rutas Norte contrata a alguien nuevo en el equipo de plataforma, lo añades al grupo plataforma en el proveedor de identidad y ya tiene los permisos. No hay que tocar un solo YAML del clúster.
Kubernetes reserva algunos grupos con significado propio:
| Grupo | Quién lo tiene |
|---|---|
system:authenticated |
Cualquiera que haya superado la autenticación |
system:unauthenticated |
Peticiones anónimas |
system:masters |
Acceso total, sin pasar por RBAC (puerta trasera de emergencia) |
system:serviceaccounts |
Todas las ServiceAccounts del clúster |
system:serviceaccounts:<ns> |
Todas las ServiceAccounts de un namespace |
Cuidado extremo con
system:masters. El apiserver le concede permiso total antes de consultar RBAC, así que no se puede limitar ni revocar con un manifiesto. Es la identidad delkubeconfigde administrador que generakubeadm. Ese fichero debe custodiarse como una llave maestra: guardado fuera del portátil de nadie, con acceso registrado, y usado solo para recuperar el clúster cuando RBAC está roto.
ServiceAccounts
Son las identidades de los procesos que corren en el clúster, y sí son objetos reales de Kubernetes (los creamos en 03-06). En RBAC se referencian de dos maneras equivalentes:
# Forma recomendada dentro de un binding
subjects:
- kind: ServiceAccount
name: informes-ocupacion
namespace: rutas-norte-pro# Forma equivalente, tratándola como usuario
subjects:
- kind: User
name: system:serviceaccount:rutas-norte-pro:informes-ocupacion
apiGroup: rbac.authorization.k8s.ioResumen comparativo:
| Usuarios y grupos | ServiceAccounts | |
|---|---|---|
| ¿Existen como objeto? | No | Sí (kubectl get sa) |
| ¿Quién los gestiona? | Proveedor de identidad externo o la CA del clúster | Kubernetes |
| ¿Con namespace? | No, son globales | Sí, pertenecen a un namespace |
| Para qué | Personas y sistemas externos | Pods y controladores |
| Credencial | Certificado, token OIDC | Token JWT proyectado |
- Los cuatro objetos de RBAC
RBAC tiene solo cuatro tipos de objeto, y todos viven en el grupo de API rbac.authorization.k8s.io/v1. La idea es de una simetría muy limpia:
RoleyClusterRoledescriben qué se puede hacer. Son listas de permisos, y no mencionan a nadie.RoleBindingyClusterRoleBindingdescriben quién lo puede hacer. Unen a unos sujetos con un rol.
flowchart LR
subgraph Qué se puede hacer
R["Role<br/>(un namespace)"]
CR["ClusterRole<br/>(todo el clúster)"]
end
subgraph Quién puede hacerlo
RB["RoleBinding<br/>(un namespace)"]
CRB["ClusterRoleBinding<br/>(todo el clúster)"]
end
S["Sujetos:<br/>usuarios, grupos,<br/>ServiceAccounts"]
R --> RB
CR --> RB
CR --> CRB
S --> RB
S --> CRB
La tabla de las cuatro combinaciones
Fíjate bien porque aquí está el 90 % de la confusión con RBAC:
| Binding | Rol referenciado | Alcance efectivo | Ejemplo en Rutas Norte |
|---|---|---|---|
RoleBinding |
Role (mismo namespace) |
Los permisos del rol, solo en el namespace del binding | desarrollo gestiona objetos en rutas-norte-dev |
RoleBinding |
ClusterRole |
Los permisos del rol, acotados al namespace del binding | soporte usa el rol view solo en rutas-norte-pro |
ClusterRoleBinding |
ClusterRole |
Los permisos en todos los namespaces y sobre recursos globales | plataforma administra el clúster |
ClusterRoleBinding |
Role |
No existe. Kubernetes lo rechaza | — |
La segunda fila es la que sorprende y a la vez la más útil. Un ClusterRole no concede nada por sí solo: es solo una plantilla de permisos. Cuando lo referencias desde un RoleBinding, sus reglas se aplican únicamente dentro del namespace de ese binding. Esto permite escribir el rol una vez y reutilizarlo en muchos namespaces.
# Un ÚNICO ClusterRole reutilizable: lectura de la aplicación
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rutasnorte-lectura-aplicacion
rules:
- apiGroups: [""]
resources: ["pods", "services", "configmaps", "events"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "replicasets", "daemonsets"]
verbs: ["get", "list", "watch"]
---
# Aplicado SOLO a rutas-norte-pro mediante un RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: soporte-lectura-pro
namespace: rutas-norte-pro # <-- aquí se acota el alcance
subjects:
- kind: Group
name: soporte
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole # referenciamos un ClusterRole...
name: rutasnorte-lectura-aplicacion
apiGroup: rbac.authorization.k8s.ioCon esos dos manifiestos, el grupo soporte puede listar pods en rutas-norte-pro y en ningún otro sitio. Si mañana quisiéramos darle también lectura en rutas-norte-pre, bastaría con otro RoleBinding idéntico cambiando el namespace: el ClusterRole no se toca.
Cuándo hace falta un ClusterRole de verdad
Hay recursos que no pertenecen a ningún namespace: Node, PersistentVolume, StorageClass, Namespace, ClusterRole, CustomResourceDefinition... Para conceder permisos sobre ellos es obligatorio un ClusterRole referenciado desde un ClusterRoleBinding. Un RoleBinding nunca puede dar acceso a un recurso global, por mucho que el ClusterRole lo mencione.
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 detalle de roleRef que conviene saber: es inmutable. No puedes cambiar a qué rol apunta un binding ya creado; hay que borrarlo y volver a crearlo. La lista de subjects, en cambio, sí se puede modificar.
- Anatomía de una regla: apiGroups, resources, verbs y resourceNames
Una regla de RBAC es la intersección de cuatro dimensiones. Todas deben coincidir para que el permiso aplique.
rules:
- apiGroups: ["apps"] # 1. ¿de qué grupo de API?
resources: ["deployments"] # 2. ¿qué tipo de recurso?
resourceNames: ["api-reservas"] # 3. ¿qué objeto concreto? (opcional)
verbs: ["get", "patch", "update"] # 4. ¿qué operaciones?apiGroups: de qué familia de API hablamos
Cada recurso de Kubernetes pertenece a un grupo de API. El grupo del núcleo (core, también llamado legacy) es histórico y se representa con la cadena vacía "", no con core. Ahí viven Pods, Services, ConfigMaps, Secrets, ServiceAccounts, Nodes, PersistentVolumeClaims y Namespaces.
| Grupo | Recursos habituales |
|---|---|
"" (núcleo) |
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ódulo 7) |
Escribir apiGroups: ["core"] es un error silencioso muy frecuente: no falla al aplicar el manifiesto, simplemente no concede nada porque no existe ningún grupo llamado core.
Para averiguar el grupo y el nombre plural exacto de cualquier recurso:
# Lista todos los recursos con su grupo (APIVERSION), si tienen namespace y su 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 StorageClassCuando APIVERSION es solo v1 (sin barra), el grupo es el vacío "". Cuando es apps/v1, el grupo es apps. La columna NAMESPACED te dice si necesitas Role o forzosamente ClusterRole.
En una regla se usa siempre el nombre plural en minúsculas de la columna NAME, nunca el KIND ni el nombre corto. resources: ["Pod"] o resources: ["po"] no conceden nada.
verbs: qué operaciones se permiten
| Verbo | Operación HTTP | Qué permite |
|---|---|---|
get |
GET a un objeto | Leer un objeto por su nombre |
list |
GET a la colección | Listar objetos, incluyendo su contenido completo |
watch |
GET con ?watch=true |
Recibir cambios en tiempo real |
create |
POST | Crear objetos nuevos |
update |
PUT | Reemplazar un objeto entero |
patch |
PATCH | Modificar parcialmente un objeto |
delete |
DELETE a un objeto | Borrar uno por nombre |
deletecollection |
DELETE a la colección | Borrar todos los que coincidan con un selector |
Y tres verbos especiales que no corresponden a una operación normal:
| Verbo especial | Sobre qué recurso | Qué significa |
|---|---|---|
bind |
roles, clusterroles |
Permite crear bindings a ese rol aunque no tengas sus permisos |
escalate |
roles, clusterroles |
Permite crear o editar roles con permisos que tú no tienes |
impersonate |
users, groups, serviceaccounts |
Permite actuar en nombre de otra identidad |
Los tres son, en la práctica, vías de escalada de privilegios. Volveremos a ellos en el apartado 10.
Un aviso importante sobre deletecollection: mucha gente lo pasa por alto al construir un rol de "poder borrar cosas concretas". Si concedes delete con resourceNames pero también deletecollection sin restricción, has abierto la puerta a borrarlo todo, porque resourceNames no aplica a las operaciones de colección.
resourceNames: limitar a objetos concretos
Permite acotar una regla a objetos con nombres concretos:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: lectura-config-tienda
namespace: rutas-norte-pro
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["tienda-web-config"] # SOLO este ConfigMap
verbs: ["get"]Con este rol se puede hacer kubectl get configmap tienda-web-config pero no kubectl get configmap otro-cualquiera.
La limitación fundamental de
resourceNames. No funciona conlist,watch,createnideletecollection. La razón es sencilla: cuando pides una lista, aún no sabes qué objetos hay, así que el autorizador no puede filtrar por nombre —solo puede permitir o denegar la petición entera—. RBAC no es un filtro de contenido.
Consecuencia práctica: resourceNames restringe la lectura individual, pero no impide un listado si has concedido list. Si tu intención es "que solo pueda ver este ConfigMap", tienes que conceder get con resourceNames y no conceder list. La contrapartida es que kubectl get configmaps (sin nombre) fallará con un 403, lo cual desconcierta a los usuarios; hay que documentarlo.
Aun con esa limitación, resourceNames es muy valioso en un caso concreto: dar a un proceso permiso para actualizar exactamente un objeto. Lo usaremos con ci-rutasnorte.
- Subrecursos: el poder oculto de
pods/exec y pods/log
pods/exec y pods/logAlgunos recursos tienen subrecursos: rutas hijas de la API con su propio control de acceso. Se escriben con una barra en el campo resources.
| Subrecurso | Qué permite | Riesgo |
|---|---|---|
pods/log |
Leer los logs del contenedor | Medio: los logs pueden contener datos sensibles |
pods/exec |
Abrir un proceso dentro del contenedor | Muy alto: acceso interactivo total al contenedor |
pods/attach |
Conectarse al proceso principal | Muy alto: equivalente en la práctica a exec |
pods/portforward |
Abrir un túnel a un puerto del pod | Alto: alcanza servicios internos desde el portátil |
pods/ephemeralcontainers |
Inyectar contenedores efímeros (kubectl debug, ver 07-06) |
Muy alto |
pods/status |
Actualizar el estado del pod | Bajo, uso de controladores |
deployments/scale |
Cambiar solo el número de réplicas | Bajo, muy útil |
serviceaccounts/token |
Emitir un token para una ServiceAccount | Crítico: suplantar ese proceso |
nodes/proxy |
Hablar con la API del kubelet | Crítico |
El punto clave que se olvida constantemente:
get podsyget pods/logson permisos distintos. Poder ver que un pod existe no da derecho a leer sus logs. Ypods/execno lo concede el verbogetsobrepods: hay que nombrarlo explícitamente. Esto es una buena noticia, porque permite dar visibilidad sin dar acceso.
Ejemplo directamente aplicable a Rutas Norte: el equipo de soporte necesita ver si un pod está caído y leer sus logs para responder a un cliente que dice que no le llega el correo de confirmación. No necesita entrar en el contenedor.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rutasnorte-soporte
rules:
# Ver el estado de los pods y de los objetos que los gobiernan
- apiGroups: [""]
resources: ["pods", "services", "events", "endpoints"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "replicasets"]
verbs: ["get", "list", "watch"]
# Leer logs: subrecurso explícito
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
# OJO: NO aparecen "secrets", ni "pods/exec", ni "pods/portforward",
# ni "pods/ephemeralcontainers". Es deliberado y es el núcleo del rol.Fíjate en lo que no está. Un rol de seguridad se juzga tanto por lo que omite como por lo que incluye. Si soporte tuviera pods/exec sobre api-reservas, podría abrir una shell, leer las variables de entorno del proceso y obtener la cadena de conexión a la base de datos de clientes. El límite entre "ver el estado" y "leer datos personales" es exactamente esa línea del YAML.
Y ojo con pods/log incluso así: en 07-05 prohibimos explícitamente registrar datos personales en los logs precisamente porque un permiso de lectura de logs es mucho más fácil de conceder que uno de lectura de base de datos. Las dos decisiones se refuerzan.
- Los roles por defecto y los roles agregados
Todo clúster viene con decenas de ClusterRole predefinidos. Muchos son internos (system:kube-scheduler, system:node...), pero cuatro están pensados para personas:
| ClusterRole | Alcance recomendado | Qué permite | Peligros |
|---|---|---|---|
cluster-admin |
Todo el clúster | Absolutamente todo, incluido modificar RBAC | Es * sobre *. Solo para emergencias |
admin |
Un namespace (vía RoleBinding) |
Gestionar todo en el namespace, incluidos Secrets, Roles y RoleBindings | Puede leer todos los Secrets del namespace |
edit |
Un namespace | Crear y modificar la mayoría de objetos; no puede tocar Roles ni RoleBindings | Sí puede leer y crear Secrets |
view |
Un namespace | Solo lectura de la mayoría de objetos; no ve Secrets | El más seguro de los cuatro |
Dos matices que deciden diseños enteros:
editpuede leer Secrets. Mucha gente concedeediten producción pensando "es que solo edita, no administra", y con eso ha entregado las credenciales de la base de datos de clientes. Si esto te importa —y en Rutas Norte nos importa mucho—, no puedes usaredittal cual enrutas-norte-pro.viewno ve Secrets (fue una decisión explícita del proyecto), pero sí ve ConfigMaps. Esto refuerza lo que dijimos en 03-01 y 03-02: los datos sensibles van en Secrets, nunca en ConfigMaps, aunque "solo sea una cadena de conexión sin contraseña".
Comprobar qué hace exactamente un rol antes de usarlo es un hábito obligatorio:
kubectl describe clusterrole view | head -30
kubectl get clusterrole edit -o yaml | grep -A3 secretsRoles agregados (aggregationRule)
Algunos roles por defecto no tienen reglas propias: se componen automáticamente con las reglas de todos los ClusterRole que llevan ciertas etiquetas. Eso es 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:
- ... # rellenado automáticamente por el controladorEl controlador de agregación vigila los ClusterRole con esa etiqueta y copia sus reglas dentro de view. Esto es enormemente útil cuando instalas CRDs: puedes hacer que quien tiene view vea también tus recursos personalizados sin editar el rol view (que se sobrescribiría en cada actualización del clúster).
Ejemplo real para Rutas Norte: en el módulo 7 instalamos el operador de Prometheus, que aporta ServiceMonitor y PrometheusRule. Queremos que soporte y desarrollo puedan verlos.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rutasnorte-agregado-monitorizacion-lectura
labels:
# Estas etiquetas hacen que las reglas se sumen a los roles por defecto
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"]Al aplicarlo, cualquiera que tenga view en cualquier namespace pasa a ver los ServiceMonitor de ese namespace, sin tocar ni un binding. Nota la jerarquía: admin agrega lo de edit, y edit agrega lo de view, así que si solo pones aggregate-to-view, los tres lo heredan igualmente. Ponerlas las tres es explícito y no hace daño.
- Verificar permisos con
kubectl auth can-i
kubectl auth can-iEscribir RBAC sin verificarlo es como escribir tests sin ejecutarlos. kubectl auth can-i responde a la pregunta exacta que el apiserver se hace.
# ¿Puedo yo, con mi kubeconfig actual, crear deployments aquí?
kubectl auth can-i create deploymentsLo verdaderamente potente es --as y --as-group, que preguntan en nombre de otra identidad (esto usa el mecanismo de impersonation, así que necesitas el verbo impersonate o ser administrador):
# ¿Puede el equipo de soporte entrar en un contenedor de producción?
kubectl auth can-i create pods/exec \
--as-group soporte \
--as [email protected] \
--namespace rutas-norte-pro# ¿Puede leer los logs? (esto sí debe funcionar)
kubectl auth can-i get pods/log \
--as-group soporte \
--as [email protected] \
--namespace rutas-norte-proTambién se pueden verificar ServiceAccounts, que es como se auditan los permisos de los procesos:
kubectl auth can-i list secrets \
--as system:serviceaccount:rutas-norte-pro:informes-ocupacion \
--namespace rutas-norte-proY una vista completa de todo lo que puede hacer un sujeto en un namespace:
kubectl auth can-i --list \
--as system:serviceaccount:rutas-norte-pro:informes-ocupacion \
--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]Esa salida es exactamente la "hoja de permisos" que hay que revisar en cada auditoría. Si aparece algo que no esperabas, tienes un binding de más.
kubectl auth whoami
Estable desde 1.28, responde a "¿quién cree el clúster que soy?". Es la herramienta para diagnosticar problemas de autenticación, que se confunden constantemente con problemas de autorización.
ATTRIBUTE VALUE
Username [email protected]
Groups [desarrollo system:authenticated]Un patrón de diagnóstico útil:
| Síntoma | Comprobación | Causa probable |
|---|---|---|
401 Unauthorized |
kubectl auth whoami falla |
Credencial caducada o mal configurada |
403 Forbidden y whoami muestra los grupos esperados |
kubectl auth can-i |
Falta un binding o el rol no cubre el verbo/recurso |
403 y whoami muestra grupos inesperados |
Configuración de OIDC | El proveedor no envía los grupos correctos |
403 solo en un namespace |
RoleBinding en el namespace |
El binding existe en otro namespace |
Un guion de verificación que merece la pena guardar en el repositorio junto a los manifiestos, para ejecutarlo tras cada cambio de RBAC:
#!/usr/bin/env bash
# k8s/rbac/verificar-rbac.sh
# Comprueba que el RBAC de Rutas Norte concede y deniega lo esperado.
set -euo pipefail
fallos=0
comprobar() {
local esperado="$1"; shift
local descripcion="$1"; shift
local resultado
resultado=$(kubectl auth can-i "$@" 2>/dev/null || true)
if [[ "$resultado" == "$esperado" ]]; then
echo "OK $descripcion (=$esperado)"
else
echo "FALLO $descripcion: esperado '$esperado', obtenido '$resultado'"
fallos=$((fallos + 1))
fi
}
# Lo que soporte SÍ debe poder hacer
comprobar yes "soporte lee logs en pro" \
get pods/log --as-group soporte --as revisor --namespace rutas-norte-pro
# Lo que soporte NUNCA debe poder hacer
comprobar no "soporte NO entra en contenedores de pro" \
create pods/exec --as-group soporte --as revisor --namespace rutas-norte-pro
comprobar no "soporte NO lee secretos de pro" \
get secrets --as-group soporte --as revisor --namespace rutas-norte-pro
# Lo que desarrollo NO debe poder hacer en producción
comprobar no "desarrollo NO borra deployments en pro" \
delete deployments --as-group desarrollo --as revisor --namespace rutas-norte-pro
comprobar no "desarrollo NO lee secretos de pro" \
get secrets --as-group desarrollo --as revisor --namespace rutas-norte-pro
# Lo que la CI SÍ y NO debe poder hacer
comprobar yes "CI actualiza deployments en pro" \
patch deployments --as system:serviceaccount:rutas-norte-pro:ci-rutasnorte \
--namespace rutas-norte-pro
comprobar no "CI NO borra deployments en pro" \
delete deployments --as system:serviceaccount:rutas-norte-pro:ci-rutasnorte \
--namespace rutas-norte-pro
exit $fallosEjecutar este guion en cada cambio convierte el RBAC en algo verificable. Sin él, un roleRef mal copiado puede pasar meses inadvertido.
- El diseño de RBAC de Rutas Norte
Ahora aplicamos todo a los cuatro colectivos de la empresa y a las ServiceAccounts de los procesos. El principio rector es el de mínimo privilegio: cada sujeto recibe lo justo para su trabajo.
Resumen del diseño:
| Sujeto | rutas-norte-dev |
rutas-norte-pre |
rutas-norte-pro |
Recursos globales |
|---|---|---|---|---|
desarrollo |
Amplio (edit propio, con Secrets) |
Lectura + logs | Solo lectura, sin Secrets, sin exec | Ninguno |
plataforma |
Administración | Administración | Administración | Lectura de nodos, PV, SC |
soporte |
— | — | Lectura de pods y logs, sin Secrets ni exec | Ninguno |
ci-rutasnorte |
Desplegar | Desplegar | Solo actualizar Deployments/StatefulSets | Ninguno |
El equipo de desarrollo
En rutas-norte-dev necesitan trabajar con libertad. Usamos el ClusterRole edit acotado con un RoleBinding:
# k8s/rbac/desarrollo-dev.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: desarrollo-edit-dev
namespace: rutas-norte-dev
labels:
app.kubernetes.io/part-of: rutas-norte
subjects:
- kind: Group
name: desarrollo
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: edit # incluye leer y crear Secrets: aceptable en dev
apiGroup: rbac.authorization.k8s.ioEs aceptable porque en rutas-norte-dev no hay datos reales de clientes: solo datos sintéticos generados para pruebas. Esa afirmación es una decisión de diseño que debe estar documentada y verificada, no una suposición. Si algún día alguien copiase un volcado de producción a desarrollo, este binding pasaría a ser un incidente grave. Es exactamente el tipo de regla que el responsable de cumplimiento debe conocer.
En rutas-norte-pro el planteamiento cambia por completo. Los desarrolladores necesitan investigar —ver el estado, leer logs, consultar eventos— pero no tocar nada ni leer credenciales.
# k8s/rbac/desarrollo-pro.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rutasnorte-diagnostico-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 y contenedores efímeros NO.
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
# "secrets" no aparece en ninguna regla. Deliberado.
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: desarrollo-lectura-pro
namespace: rutas-norte-pro
subjects:
- kind: Group
name: desarrollo
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: rutasnorte-diagnostico-lectura
apiGroup: rbac.authorization.k8s.ioY el mismo ClusterRole, más el permiso de logs, se reutiliza en rutas-norte-pre sin escribir reglas nuevas. Esa es la ventaja de la combinación RoleBinding → ClusterRole.
El equipo de plataforma (SRE)
Administra los tres namespaces. Podríamos darles cluster-admin, pero es excesivo y hace imposible distinguir en el registro de auditoría (08-06) una operación rutinaria de una anómala. Preferimos admin por namespace más un ClusterRole de lectura de 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ón completa del namespace, incluidos Secrets y RBAC local
apiGroup: rbac.authorization.k8s.io
---
# (mismo RoleBinding replicado en rutas-norte-dev y 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 # nodos, PV, StorageClasses: solo lectura
apiGroup: rbac.authorization.k8s.iocluster-admin queda reservado para un procedimiento de emergencia con acceso registrado y aprobación previa. En muchas organizaciones esto se implementa con acceso temporal (just-in-time): un sistema externo crea el ClusterRoleBinding, avisa por un canal público y lo borra automáticamente a las dos horas.
Atención al cliente (soporte)
Ya escribimos su ClusterRole en el apartado 5. Solo falta el binding, exclusivamente en producción:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: soporte-pro
namespace: rutas-norte-pro
subjects:
- kind: Group
name: soporte
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: rutasnorte-soporte
apiGroup: rbac.authorization.k8s.ioLa canalización de integración continua (ci-rutasnorte)
Este es el caso más interesante, porque la CI es un objetivo muy goloso: si alguien compromete el sistema de CI y este tiene cluster-admin, tiene el clúster. Le damos exactamente lo que necesita para desplegar: actualizar la imagen de los Deployments y StatefulSets existentes. Nada más.
# k8s/rbac/ci.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: ci-rutasnorte
namespace: rutas-norte-pro
automountServiceAccountToken: false # nadie monta este token en un pod (ver 03-06)
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ci-despliegue
namespace: rutas-norte-pro
rules:
# Actualizar cargas de trabajo existentes: sí. Crearlas o borrarlas: no.
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets"]
verbs: ["get", "list", "patch", "update"]
# Consultar el resultado del despliegue (kubectl rollout status)
- apiGroups: ["apps"]
resources: ["replicasets"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["pods", "events"]
verbs: ["get", "list"]
# NO hay: create, delete, deletecollection, secrets, pods/exec,
# roles, rolebindings, serviceaccounts.
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ci-despliegue
namespace: rutas-norte-pro
subjects:
- kind: ServiceAccount
name: ci-rutasnorte
namespace: rutas-norte-pro
roleRef:
kind: Role
name: ci-despliegue
apiGroup: rbac.authorization.k8s.ioAnalicemos las decisiones:
- Sin
create: los objetos nuevos los crea una persona deplataformatras revisión. La CI solo actualiza lo que ya existe y ha sido revisado. - Sin
deletenideletecollection: una CI comprometida no puede borrar la plataforma. - Sin
secrets: la CI no lee credenciales del clúster. Las suyas están en el gestor de secretos de la canalización. - Sin
pods/exec: no hay manera de que la CI abra una shell en producción.
Si quisiéramos ser todavía más estrictos, podríamos acotar con resourceNames a los nombres exactos de las cargas:
- apiGroups: ["apps"]
resources: ["deployments"]
resourceNames: ["tienda-web", "api-reservas", "worker-notificaciones"]
verbs: ["get", "patch", "update"]
# ...y una regla aparte con solo "list" (sin resourceNames), porque
# resourceNames no aplica a list, para que la CI pueda listar.
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["list"]Ese es el patrón correcto cuando hace falta el listado: dos reglas, una restrictiva para las operaciones por nombre y otra con solo list.
ServiceAccounts de los procesos
El CronJob informes-ocupacion genera un informe nocturno de ocupación de plazas. No necesita hablar con la API de Kubernetes en absoluto, así que lo correcto es que ni siquiera monte el token:
apiVersion: v1
kind: ServiceAccount
metadata:
name: informes-ocupacion
namespace: rutas-norte-pro
automountServiceAccountToken: false
# Sin ningún Role ni RoleBinding: no necesita permisos.Que un proceso no tenga ningún binding es la situación ideal y más frecuente de lo que la gente cree. tienda-web, worker-notificaciones y redis-cache están en el mismo caso.
api-reservas sí necesita leer un ConfigMap concreto para recargar su configuración de rutas en caliente:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: api-reservas-config
namespace: rutas-norte-pro
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["api-reservas-rutas"] # exactamente ese, ningún otro
verbs: ["get", "watch"]Y el operador de PostgreSQL del módulo 6, que sí necesita permisos amplios porque su trabajo es gestionar StatefulSets, Services y PVCs. Recuerda lo que dijimos en 06-07: antes de adoptar un operador hay que leer qué RBAC pide. Este es el mínimo razonable, acotado a un namespace:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: operador-postgres
namespace: rutas-norte-pro
rules:
- apiGroups: ["basededatos.rutasnorte.example"]
resources: ["clusterespostgres", "clusterespostgres/status", "clusterespostgres/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"]
# El operador crea las credenciales de la base de datos: necesita secretos.
# Está acotado a ESTE namespace, no a todo el clúster. Es la diferencia
# entre "puede leer las credenciales de la BD de clientes" y
# "puede leer TODAS las credenciales del clúster".
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]Si el operador que estás evaluando exige un ClusterRole con secrets en todo el clúster y su documentación no explica por qué, esa es una razón legítima para rechazarlo o para desplegarlo con el alcance restringido a un namespace, si lo soporta.
- Por qué
list sobre secretos equivale a leerlos
list sobre secretos equivale a leerlosEste es uno de los puntos que más gente entiende mal, y tiene consecuencias directas sobre los datos de clientes de Rutas Norte.
Cuando ejecutas kubectl get secrets, kubectl hace un GET /api/v1/namespaces/<ns>/secrets. El apiserver devuelve una SecretList con los objetos completos, incluido el campo data con todos los valores. Que kubectl te muestre una tabla resumida sin los valores es solo una decisión de presentación del cliente. Los datos ya viajaron por la red hasta tu máquina.
# Esto usa "list", no "get", y devuelve los valores de TODOS los secretos
kubectl get secrets -n rutas-norte-pro -o yamlapiVersion: v1
items:
- apiVersion: v1
kind: Secret
metadata:
name: postgres-reservas-credenciales
namespace: rutas-norte-pro
data:
POSTGRES_PASSWORD: <valor en base64, perfectamente legible>
POSTGRES_USER: <valor en base64>
type: Opaque
kind: ListComo dijimos en 03-02, base64 no es cifrado: es codificación. Una sola orden lo revierte.
Regla que no admite excepciones: conceder
listsobresecretses conceder lectura de todos los secretos de ese ámbito. No existe forma de listar secretos "sin ver su contenido". Si un rol tienelistsobresecretsenrutas-norte-pro, quien lo tenga puede leer las credenciales de la base de datos de clientes. Trátalo con la misma seriedad que entregar la contraseña.
Lo mismo vale para watch, que entrega los objetos completos en cada cambio.
Por qué los comodines son casi siempre un error
Eso es cluster-admin. Pero incluso los comodines "pequeños" son peligrosos:
| Regla con comodín | Lo que la gente cree que concede | Lo que concede de verdad |
|---|---|---|
resources: ["*"], verbs: ["get","list"] |
"Leer cosas" | Todos los Secrets, tokens, y cualquier recurso futuro |
apiGroups: ["*"], resources: ["deployments"] |
Deployments | Deployments de cualquier grupo, presente y futuro |
verbs: ["*"] sobre pods |
Gestionar pods | Incluye pods/exec, pods/attach, pods/portforward |
La última fila es especialmente traicionera: un comodín en verbs sobre pods no incluye los subrecursos (hay que nombrarlos), pero resources: ["pods/*"] sí incluye exec, attach y portforward. Merece la pena memorizar la diferencia.
Y hay un problema temporal: un comodín concede permisos sobre recursos que todavía no existen. Si instalas un CRD nuevo el mes que viene, cualquiera con resources: ["*"] lo controlará desde el primer día, sin que nadie haya decidido nada.
La alternativa correcta es siempre enumerar. Es más largo de escribir y muchísimo más fácil de auditar.
- Escalada de privilegios y las protecciones de Kubernetes
La protección contra la escalada por privilegios
Kubernetes impide, por defecto, que crees o modifiques un rol con permisos que tú no tienes. Si pudieras, cualquiera con permiso sobre roles sería cluster-admin de facto: se escribiría un rol con * y se lo asignaría.
# Alguien de "desarrollo" (que solo tiene "edit" en dev) intenta esto
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=["desarrollo" "system:authenticated"]) is attempting
to grant RBAC permissions not currently held:
{APIGroups:["*"], Resources:["*"], Verbs:["*"]}La regla exacta es: para crear o modificar un rol, debes poseer ya todos los permisos que ese rol concede (en el ámbito correspondiente). Hay dos excepciones deliberadas:
| Verbo | Efecto | Cuándo se usa legítimamente |
|---|---|---|
escalate sobre roles/clusterroles |
Permite crear roles con permisos que no tienes | Controladores que gestionan RBAC (Argo CD, operadores) |
bind sobre un ClusterRole concreto |
Permite crear bindings a ese rol sin tener sus permisos | Delegar "puedes dar el rol X" sin dar X |
bind, acotado con resourceNames, es el mecanismo elegante para delegar. En Rutas Norte podríamos permitir que los jefes de equipo concedan el rol de soporte sin ser administradores:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: delegar-rol-soporte
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-soporte"] # SOLO puede vincular este rol
verbs: ["bind"]Quien tenga este rol puede crear bindings al rol rutasnorte-soporte y a ningún otro. No puede vincular admin, ni cluster-admin. Es una delegación segura.
Poder crear pods equivale a usar cualquier ServiceAccount del namespace
Esta es probablemente la implicación más subestimada de todo RBAC:
Si puedes crear pods en un namespace, puedes ejecutar código con la identidad de cualquier ServiceAccount de ese namespace, simplemente poniendo su nombre en
spec.serviceAccountName.
# Un pod puede declarar cualquier ServiceAccount de SU namespace
apiVersion: v1
kind: Pod
metadata:
name: cualquier-cosa
namespace: rutas-norte-pro
spec:
serviceAccountName: operador-postgres # ¡hereda TODOS sus permisos!
containers:
- name: app
image: registry.rutasnorte.example/utilidades:1.4.2Crear pods no está protegido por la regla de "no puedes conceder lo que no tienes", porque técnicamente no estás creando un rol: estás creando un pod. Pero el efecto práctico es idéntico a heredar los permisos de esa ServiceAccount.
Consecuencias concretas para nuestro diseño:
create podsenrutas-norte-proes un permiso de alto privilegio. Por esoci-rutasnorteno lo tiene ydesarrollotampoco lo tiene en producción.- Nunca metas en el mismo namespace una ServiceAccount muy privilegiada y cargas de trabajo normales. El operador de PostgreSQL, que puede leer secretos, comparte namespace con
tienda-web. Quien pueda crear un pod ahí, hereda los permisos del operador. Lo correcto es aislar los operadores en su propio namespace. - Los verbos que crean pods indirectamente cuentan igual: crear Deployments, StatefulSets, DaemonSets, Jobs o CronJobs es crear pods por delegación.
editincluye todos ellos.
Otras rutas de escalada que conviene conocer para poder cerrarlas:
| Permiso | Por qué es de alto privilegio |
|---|---|
create pods |
Suplantar cualquier SA del namespace (arriba) |
create pods/exec |
Entrar en un pod que ya usa una SA privilegiada |
create serviceaccounts/token |
Emitir un token para cualquier SA del namespace |
impersonate sobre users/groups |
Actuar como otra persona, incluido system:masters |
escalate sobre roles |
Autoconcederse cualquier permiso |
create sobre certificatesigningrequests/approval |
Emitir certificados de cliente con el grupo que quiera |
get nodes/proxy |
Hablar con el kubelet y ejecutar en cualquier pod del nodo |
patch sobre validatingwebhookconfigurations |
Desactivar los controles de admisión (08-03) |
Auditar estos permisos concretos es una tarea rutinaria muy rentable:
# ¿Quién puede crear pods en producción?
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/soporte-pro -> ClusterRole/rutasnorte-soporte : Group:soporte
RoleBinding/desarrollo-lectura-pro -> ClusterRole/rutasnorte-diagnostico-lectura : Group:desarrollo
RoleBinding/ci-despliegue -> Role/ci-despliegue : ServiceAccount:ci-rutasnorte
ClusterRoleBinding/plataforma-infraestructura -> ClusterRole/rutasnorte-lectura-infraestructura : Group:plataformaEsa lista debe caber en una pantalla y cada línea debe tener una justificación conocida. El día que no quepa, empieza el problema.
- Auditoría y revisión periódica de permisos
RBAC se degrada con el tiempo. Un permiso temporal que nadie retiró, un binding para depurar una incidencia que se quedó, un grupo que creció. La única defensa es la revisión sistemática.
Quién tiene 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 segunda línea es exactamente lo que hay que cazar: un acceso de emergencia que debía durar dos horas y sigue ahí. La convención de poner la fecha en el nombre del binding hace que salte a la vista.
Quién puede leer los secretos de producción
for verbo in get list watch; do
echo "=== verbo: $verbo ==="
for grupo in desarrollo plataforma soporte; do
printf " %-12s %s\n" "$grupo" \
"$(kubectl auth can-i "$verbo" secrets --as-group "$grupo" --as revisor -n rutas-norte-pro)"
done
done=== verbo: get ===
desarrollo no
plataforma yes
soporte no
=== verbo: list ===
desarrollo no
plataforma yes
soporte no
=== verbo: watch ===
desarrollo no
plataforma yes
soporte noSolo plataforma. Es el resultado que buscábamos y hay que poder demostrarlo en cualquier momento: esta salida, guardada con fecha, es una evidencia válida para una auditoría de cumplimiento.
Detectar bindings huérfanos
Cuando una persona deja la empresa, su binding se queda. Cuando una ServiceAccount se borra, sus bindings también. Un chequeo sencillo:
# ServiceAccounts referenciadas en bindings que ya no existen
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 "HUÉRFANO: $binding -> sa $ns/$sa"
doneHerramientas de análisis
Existen utilidades específicas que hacen este trabajo mucho más cómodo:
| Herramienta | Qué aporta |
|---|---|
kubectl who-can (krew) |
"¿Quién puede hacer X sobre Y?" en una orden |
rbac-tool (krew) |
Visualiza el grafo de permisos, detecta reglas redundantes |
kubectl-rbac-lookup (krew) |
Lista los permisos de un sujeto en todo el clúster |
| Kubescape / Trivy | Incluyen comprobaciones de RBAC excesivo (ver 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 proceso de revisión
Una cadencia razonable para una plataforma como Rutas Norte:
| Frecuencia | Revisión | Responsable |
|---|---|---|
| En cada cambio | El guion verificar-rbac.sh en la canalización |
Automático |
| Semanal | ClusterRoleBinding nuevos o modificados |
plataforma |
| Trimestral | Quién puede leer Secrets de rutas-norte-pro y quién tiene cluster-admin |
plataforma + seguridad |
| Trimestral | Bindings huérfanos y accesos de emergencia caducados | plataforma |
| Anual | Revisión completa del diseño con seguridad y cumplimiento | Dirección |
Todos los manifiestos de RBAC deben vivir en el repositorio, en k8s/base/rbac/, y aplicarse solo desde ahí. Un binding creado a mano con kubectl create rolebinding es invisible para la revisión de código y es exactamente por donde entran los problemas. En el módulo 10 veremos con GitOps cómo hacer que el clúster rechace de facto cualquier cosa que no esté en Git.
Errores Comunes y Consejos
Escribir apiGroups: ["core"]. No existe. El grupo del núcleo es la cadena vacía "". El manifiesto se aplica sin error y no concede nada; luego pasas una hora buscando por qué hay un 403.
Usar el Kind o el nombre corto en resources. Hay que usar el plural en minúsculas: pods, no Pod ni po; deployments, no Deployment. Consúltalo con kubectl api-resources.
Creer que un RoleBinding a un ClusterRole da permisos globales. No: los acota al namespace del binding. Es justo lo contrario de lo que sugiere el nombre y es la combinación más útil de las cuatro.
Intentar cambiar el roleRef de un binding existente. Es inmutable. Hay que borrar y recrear. Un kubectl apply que cambia el roleRef falla con un error poco descriptivo.
Conceder edit en producción "porque solo edita". edit lee y crea Secrets, y crea pods (con lo que puede suplantar cualquier ServiceAccount del namespace). En un namespace con datos personales es un permiso de administración disfrazado.
Suponer que resourceNames protege el listado. No aplica a list, watch, create ni deletecollection. Si concedes list sin nombres, se ve todo.
Olvidar que list sobre Secrets es leerlos. No hay forma de listarlos sin ver el contenido.
Añadir comodines "para salir del paso". El permiso temporal se queda, y encima concede acceso a recursos que todavía no existen. Si necesitas desbloquear a alguien rápido, concede el permiso exacto que le falta y ábrele una tarea para revisarlo.
Conceder permisos a usuarios en vez de a grupos. Cada alta y cada baja se convierte en un cambio de manifiesto, y las bajas se olvidan siempre.
Confundir 401 con 403. 401 es autenticación (no sé quién eres), 403 es autorización (sé quién eres y no puedes). kubectl auth whoami distingue los dos casos en un segundo.
No verificar. Escribe siempre las comprobaciones negativas: no basta con confirmar que soporte puede leer logs, hay que confirmar que no puede hacer exec ni leer Secrets. Los fallos de seguridad están en los permisos de más, no en los de menos.
Poner un operador privilegiado en el mismo namespace que las aplicaciones. Quien pueda crear un pod ahí hereda los permisos del operador. Namespace aparte.
Consejo de oro: empieza siempre por cero permisos y añade solo lo que falla. Es más lento el primer día y muchísimo más seguro todos los demás. Con kubectl auth can-i --list sabrás en cada momento exactamente dónde estás.
Ejercicios
Ejercicio 1: rol de solo lectura con logs, sin exec ni secretos
Rutas Norte incorpora un becario en el equipo de soporte. Necesitas un ClusterRole llamado rutasnorte-soporte-junior y un binding que lo aplique solo a rutas-norte-pre. Debe poder:
- Listar y ver pods, servicios y deployments.
- Leer los logs de los pods.
Y no debe poder:
- Leer Secrets.
- Ejecutar
kubectl execnikubectl port-forward. - Modificar nada.
Escribe los manifiestos y las órdenes de verificación que demuestren las tres prohibiciones.
Ejercicio 2: RBAC mínimo para un recolector de métricas
Un nuevo agente de métricas, agente-inventario, corre como Deployment en rutas-norte-pro con su propia ServiceAccount. Necesita listar pods y nodos de todo el clúster para elaborar un inventario de recursos, y nada más. Escribe la ServiceAccount, el ClusterRole, el ClusterRoleBinding y una comprobación que demuestre que no puede leer Secrets ni crear nada.
Pregunta adicional: ¿por qué aquí sí hace falta un ClusterRoleBinding y no basta con un RoleBinding?
Ejercicio 3: auditar y corregir un rol peligroso
Encuentras este Role aplicado en rutas-norte-pro:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: equipo-analitica
namespace: rutas-norte-pro
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list", "watch"]Está vinculado al grupo analitica, cuyo único cometido es consultar las métricas de ocupación mediante los ServiceMonitor y ver el estado de los pods.
- Enumera todos los problemas de seguridad de este rol.
- Reescríbelo con el mínimo privilegio.
- Escribe las comprobaciones que demuestren que la versión nueva sigue sirviendo para su cometido y ya no expone datos personales.
Soluciones
Solución 1
# k8s/rbac/soporte-junior.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rutasnorte-soporte-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"]
# Ausentes deliberadamente: secrets, pods/exec, pods/attach,
# pods/portforward, pods/ephemeralcontainers y todo verbo de escritura.
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: soporte-junior-pre
namespace: rutas-norte-pre # el ClusterRole queda acotado a este namespace
subjects:
- kind: Group
name: soporte-junior
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: rutasnorte-soporte-junior
apiGroup: rbac.authorization.k8s.ioVerificación:
kubectl apply -f k8s/rbac/soporte-junior.yaml
SUJETO=(--as becario --as-group soporte-junior)
# Debe poder
kubectl auth can-i get pods/log "${SUJETO[@]}" -n rutas-norte-pre # yes
kubectl auth can-i list deployments "${SUJETO[@]}" -n rutas-norte-pre # yes
# NO debe poder
kubectl auth can-i get secrets "${SUJETO[@]}" -n rutas-norte-pre # no
kubectl auth can-i create pods/exec "${SUJETO[@]}" -n rutas-norte-pre # no
kubectl auth can-i create pods/portforward "${SUJETO[@]}" -n rutas-norte-pre # no
kubectl auth can-i list pods "${SUJETO[@]}" -n rutas-norte-pro # no (otro namespace)La última comprobación es la que demuestra que el RoleBinding acota el ClusterRole: en rutas-norte-pro no tiene absolutamente nada.
Solución 2
# k8s/rbac/agente-inventario.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: agente-inventario
namespace: rutas-norte-pro
labels:
app.kubernetes.io/part-of: rutas-norte
# Aquí SÍ se monta el token: el proceso necesita hablar con la API.
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rutasnorte-inventario
rules:
- apiGroups: [""]
resources: ["pods", "nodes"]
verbs: ["get", "list", "watch"]
# Nada más. Ni secrets, ni configmaps, ni ningún verbo de escritura.
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: agente-inventario
subjects:
- kind: ServiceAccount
name: agente-inventario
namespace: rutas-norte-pro
roleRef:
kind: ClusterRole
name: rutasnorte-inventario
apiGroup: rbac.authorization.k8s.ioVerificación:
SA=system:serviceaccount:rutas-norte-pro:agente-inventario
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]
...Por qué hace falta un ClusterRoleBinding: por dos razones independientes.
nodeses un recurso sin namespace. UnRoleBindingnunca puede conceder acceso a recursos globales, ni siquiera referenciando unClusterRoleque los mencione.- El agente debe listar pods de todos los namespaces. Un
RoleBindingacotaría la lectura al namespace del binding, y habría que replicarlo en cada namespace existente y futuro.
Si el requisito hubiera sido solo "pods de rutas-norte-pro", un RoleBinding habría sido la opción correcta y más segura.
Solución 3
1. Problemas del rol original:
| Problema | Consecuencia |
|---|---|
resources: ["*"] incluye secrets |
El grupo analitica puede leer las credenciales de postgres-reservas, y con ellas acceder a los datos personales de todos los clientes. Incidente de protección de datos. |
list sobre secrets |
Con una sola orden ve el contenido de todos los secretos del namespace. |
apiGroups: ["*"] |
Cubre recursos que aún no existen: cualquier CRD que se instale mañana quedará expuesto sin decisión de nadie. |
resources: ["*"] incluye serviceaccounts |
Facilita el reconocimiento de qué identidades privilegiadas hay en el namespace. |
| No cumple el cometido declarado | Para ver métricas y estado de pods no hace falta nada de esto. |
| Regla imposible de auditar | Nadie puede responder de un vistazo a "¿qué puede ver este grupo?". |
2. Versión con mínimo privilegio:
# 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:
# Estado de las cargas de trabajo
- apiGroups: [""]
resources: ["pods", "services"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets", "replicasets"]
verbs: ["get", "list", "watch"]
# Objetos de monitorización del módulo 7
- apiGroups: ["monitoring.coreos.com"]
resources: ["servicemonitors", "podmonitors", "prometheusrules"]
verbs: ["get", "list", "watch"]
# Métricas de uso (metrics-server, 07-02)
- apiGroups: ["metrics.k8s.io"]
resources: ["pods", "nodes"]
verbs: ["get", "list"]
# Ausentes deliberadamente: secrets, configmaps, serviceaccounts,
# pods/log, pods/exec y todo verbo de escritura.
---
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.ioFíjate en que también hemos quitado pods/log: el cometido declarado es consultar métricas, y los logs podrían contener información de clientes (aunque en 07-05 lo prohibimos, el permiso mínimo no depende de que esa prohibición se cumpla siempre).
3. Verificación:
kubectl delete role equipo-analitica -n rutas-norte-pro
kubectl apply -f k8s/rbac/analitica.yaml
A=(--as analista --as-group analitica -n rutas-norte-pro)
echo "--- Debe seguir funcionando ---"
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 "--- Ya no debe 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[@]}"Como el rol viejo concedía acceso a las credenciales de la base de datos de clientes, no basta con corregir el rol: hay que revisar el registro de auditoría para saber si alguien llegó a leer ese Secret, y si es así, rotar las credenciales y notificarlo al responsable de cumplimiento. Cómo se hace esa consulta es el tema de 08-06.
Conclusión
RBAC es la primera y más importante línea de defensa de un clúster de Kubernetes, y ahora tienes el modelo mental completo:
- Toda petición atraviesa tres puertas: autenticación (quién eres), autorización (qué puedes hacer, aquí actúa RBAC) y control de admisión (si el objeto es aceptable).
- Los sujetos son usuarios y grupos, que Kubernetes no almacena y llegan del autenticador, y ServiceAccounts, que sí son objetos del clúster.
- Cuatro objetos:
Role/ClusterRoledicen qué,RoleBinding/ClusterRoleBindingdicen quién. La combinaciónRoleBinding→ClusterRolees la más útil: escribe el rol una vez, acótalo donde quieras. - Una regla es la intersección de
apiGroups(con el grupo vacío""del núcleo),resources(incluidos subrecursos comopods/logypods/exec),verbsy opcionalmenteresourceNames(que no aplica alist). listsobre Secrets es leerlos. Los comodines conceden más de lo que crees, incluido sobre recursos que aún no existen.- Crear pods equivale a poder usar cualquier ServiceAccount del namespace; por eso los operadores privilegiados van en su propio namespace.
kubectl auth can-i --as/--as-groupykubectl auth whoamiconvierten el RBAC en algo verificable, y esas verificaciones deben ejecutarse automáticamente en cada cambio.
En Rutas Norte hemos pasado de "cualquiera puede leer las credenciales de la base de datos de clientes" a un diseño donde solo plataforma puede hacerlo, soporte ve el estado y los logs sin poder entrar en los contenedores, desarrollo investiga producción sin tocarla, y la canalización de integración continua solo puede actualizar la imagen de las cargas que ya existen.
Pero acabamos de descubrir un límite incómodo: RBAC decide si tienes derecho a crear un pod, no cómo es ese pod. Alguien de plataforma, con permisos perfectamente legítimos, puede desplegar hoy mismo un contenedor con privileged: true, que monte / del nodo con hostPath y corra como root. RBAC dirá que sí, porque tiene permiso para crear pods. Y desde ahí, el nodo entero —y todos los pods que corran en él, incluido postgres-reservas— queda al descubierto.
La siguiente lección, 08-02, Contextos de Seguridad y Endurecimiento del Contenedor, ataca exactamente ese problema: qué aísla realmente un contenedor, por qué no es una máquina virtual, y cómo se configura el securityContext de cada componente de Rutas Norte para que, aunque alguien consiga ejecutar código dentro de un contenedor, no pueda salir de él.
Curso de Kubernetes
Módulo 1: Introducción a Kubernetes
- ¿Qué es Kubernetes?
- Arquitectura de Kubernetes
- Conceptos y Terminología Clave
- Configuración de un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objetos, Manifiestos YAML y el Modelo Declarativo
- El Proyecto del Curso: la Plataforma Rutas Norte
Módulo 2: Componentes Principales de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualizaciones, Rollbacks y Estrategias de Despliegue
- Servicios
- Namespaces
- Etiquetas, Selectores y Anotaciones
Módulo 3: Gestión de Configuración y Secretos
- ConfigMaps
- Secrets
- Variables de Entorno
- Cuotas y Límites de Recursos
- LimitRanges y Clases de Calidad de Servicio (QoS)
- ServiceAccounts y Acceso a la API desde los Pods
Módulo 4: Redes en Kubernetes
- Redes de Clúster
- Tipos de Servicios
- DNS Interno y Descubrimiento de Servicios
- Controladores de Ingress
- TLS y Gestión de Certificados con cert-manager
- Políticas de Red
Módulo 5: Almacenamiento en Kubernetes
- Volúmenes
- Volúmenes Persistentes
- Reclamaciones de Volúmenes Persistentes
- Clases de Almacenamiento
- Aprovisionamiento Dinámico, Expansión y Snapshots
- Copias de Seguridad y Restauración de Datos
Módulo 6: Conceptos Avanzados de Kubernetes
- StatefulSets
- DaemonSets
- Trabajos y CronJobs
- Init Containers, Sidecars y Patrones Multi-Contenedor
- Planificación: Afinidad, Taints y Tolerations
- Definiciones de Recursos Personalizados (CRDs)
- Operadores y el Patrón Controlador
Módulo 7: Monitoreo y Registro
- Verificaciones de Salud y Sondas
- Servidor de Métricas y kubectl top
- Monitoreo con Prometheus
- Visualización y Alertas con Grafana y Alertmanager
- Registro Centralizado con Elasticsearch, Fluentd y Kibana (EFK)
- Depuración de Aplicaciones y Eventos del Clúster
Módulo 8: Seguridad en Kubernetes
- Control de Acceso Basado en Roles (RBAC)
- Contextos de Seguridad y Endurecimiento del Contenedor
- Políticas de Seguridad de Pods y Pod Security Standards
- Seguridad de Red
- Seguridad de Imágenes
- Auditoría, Escaneo y Gestión de Vulnerabilidades
Módulo 9: Escalado y Rendimiento
- Autoescalado Horizontal de Pods
- Autoescalado Vertical de Pods
- Autoescalado de Clúster
- Escalado por Eventos y Métricas Personalizadas con KEDA
- Alta Disponibilidad: PodDisruptionBudgets y Topología
- Ajuste de Rendimiento
Módulo 10: Ecosistema y Herramientas de Kubernetes
- Minikube y Entornos Locales con kind
- Kubeadm
- Helm
- Kustomize
- GitOps con Argo CD y Flux
- Kubernetes Gestionado: EKS, AKS y GKE
Módulo 11: Estudios de Caso y Aplicaciones del Mundo Real
- Despliegue de una Aplicación Web
- Ejecución de Aplicaciones con Estado
- CI/CD con Kubernetes
- Estrategias de Despliegue: Blue-Green y Canary
- Gestión Multi-Clúster
- Operación en Producción: Incidencias, Runbooks y Costes
