Kubernetes RBAC a fondo: Entendiendo autorización con kubectl y curl
Introducción
En este artículo vamos a explorar cómo funciona RBAC (Control de Acceso Basado en Roles) en Kubernetes, no solo desde la perspectiva de kubectl, sino profundizando en las llamadas HTTP reales que suceden detrás de escena. Vamos a ver qué hace realmente kubectl cuando creás roles, role bindings y verificás permisos.
La idea de este articulo es desmitificar la API de Kubernetes mientras vemos como funciona RBAC, esto no solo nos va a dar la posibilidad de entender y manejar mejor los permisos en el cluster si no que construir cualquier herramienta o interaccion que necesitemos en Kubernetes usando sus APIs.
Si leíste mi artículo anterior sobre autenticación y autorización en Kubernetes, ya sabés que la autenticación es sobre probar quién sos, mientras que la autorización es sobre qué se te permite hacer. RBAC es el mecanismo principal de autorización de Kubernetes, y entender cómo funciona a nivel de API te va a hacer mucho más efectivo para debugear problemas de permisos.
RBAC Básico:
RBAC en Kubernetes consiste de tres componentes principales:
- Roles/ClusterRoles: Definen qué acciones se pueden realizar en qué recursos
- Subjects: Usuarios, grupos o service accounts que necesitan permisos
- RoleBindings/ClusterRoleBindings: Conectan roles con subjects
La diferencia clave entre Role/RoleBinding y ClusterRole/ClusterRoleBinding es el alcance:
- Role/RoleBinding: Con namespace (permisos dentro de un namespace específico)
- ClusterRole/ClusterRoleBinding: A nivel de cluster (permisos en todos los namespaces o recursos de nivel cluster)
Así que vamos a crear algunos recursos RBAC, probarlos con kubectl, y después ver exactamente qué requests HTTP se están haciendo al servidor de API de Kubernetes. Esto te va a dar un entendimiento mucho más profundo de cómo realmente funciona RBAC.
Vamos al grano
Empecemos configurando nuestro entorno de testing. Voy a crear un namespace, algunos roles y usuarios, después te muestro tanto los comandos kubectl como las llamadas curl equivalentes.
Configurando nuestro entorno:
Primero necesitamos encontrar la direccion publica de nuestro API server:
kubectl get ep -A
NAMESPACE NAME ENDPOINTS AGE
default kubernetes 172.19.0.2:6443 78m
kube-system kube-dns 10.244.0.3:53,10.244.0.4:53,10.244.0.3:53 + 3 more... 78m
Si en vez usamos kubectl proxy, va a usar las credenciales que usa kubectl e ignorar las que le pasemos con curl, asi que para que todos los ejemplos funcionen como se muestra hay que usar la direccion directa del API server.
Primero, vamos a crear un namespace para nuestros experimentos:
kubectl create namespace rbac-demo
Ahora veamos qué hace esto realmente a nivel de API. Habilitá el modo verbose de kubectl para ver las llamadas HTTP:
kubectl create namespace rbac-demo -v=8
Vas a ver output como este (truncado para legibilidad):
I0110 10:30:15.123456 POST https://127.0.0.1:6443/api/v1/namespaces
I0110 10:30:15.123456 Request Body: {"apiVersion":"v1","kind":"Namespace","metadata":{"name":"rbac-demo"}}
I0110 10:30:15.123456 Response Status: 201 Created
Ahora hagamos lo mismo con curl para entender la llamada de API cruda:
# Obtener info del cluster
kubectl cluster-info
# Obtener tu token (esto va a variar según tu configuración)
TOKEN=$(kubectl get secret $(kubectl get serviceaccount default -o jsonpath='{.secrets[0].name}') -o jsonpath='{.data.token}' | base64 -d)
# Hacer la llamada de API
curl -k -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-X POST \
https://172.19.0.2:6443/api/v1/namespaces \
-d '{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": {
"name": "rbac-demo-curl"
}
}'
La respuesta va a ser una representación JSON del namespace creado. ¡Esto es exactamente lo que hace kubectl por detrás!
Creando Recursos RBAC
Ahora vamos a crear un Role que permita leer pods en nuestro namespace:
kubectl create role pod-reader \
--namespace=rbac-demo \
--verb=get,list,watch \
--resource=pods
Veamos el YAML real que se creó:
kubectl get role pod-reader -n rbac-demo -o yaml
El output debería verse así:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: rbac-demo
resourceVersion: "12345"
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
Ahora hagamos la misma llamada con curl para ver el request HTTP crudo:
# Primero, veamos qué enviaría kubectl
kubectl create role pod-reader-curl \
--namespace=rbac-demo \
--verb=get,list,watch \
--resource=pods \
--dry-run=client -o json
Esto nos muestra el payload JSON exacto. Ahora enviémoslo vía curl:
curl -k -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-X POST \
https://172.19.0.2:6443/apis/rbac.authorization.k8s.io/v1/namespaces/rbac-demo/roles \
-d '{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "Role",
"metadata": {
"name": "pod-reader-curl",
"namespace": "rbac-demo"
},
"rules": [
{
"apiGroups": [""],
"resources": ["pods"],
"verbs": ["get", "list", "watch"]
}
]
}'
Notá el path de API: /apis/rbac.authorization.k8s.io/v1/namespaces/rbac-demo/roles
. Esto nos dice:
-
Estamos usando el API group de RBAC (
rbac.authorization.k8s.io
) - Versión v1
-
Está namespacead (incluye
/namespaces/rbac-demo
) - Estamos trabajando con roles
Creando un RoleBinding
Ahora vamos a crear un RoleBinding para darle a un usuario el role pod-reader:
kubectl create rolebinding pod-reader-binding \
--namespace=rbac-demo \
--role=pod-reader \
--user=john.doe@example.com
NOTA: Crear usuarios es un poco complicado por que necesitamos un certificado, etc, podes leer mas aca, hay muchos ejemplos de como crear un usuario manualmente, esto tambien funciona un poco distinto en los proveedores cloud pero las mecanicas de Kubernetes son basicamente las mismas.
Inspeccionemos qué se creó:
kubectl get rolebinding pod-reader-binding -n rbac-demo -o yaml
El output muestra:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-reader-binding
namespace: rbac-demo
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: pod-reader
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: john.doe@example.com
Ahora el equivalente en curl:
curl -k -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-X POST \
https://172.19.0.2:6443/apis/rbac.authorization.k8s.io/v1/namespaces/rbac-demo/rolebindings \
-d '{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "RoleBinding",
"metadata": {
"name": "pod-reader-binding-curl",
"namespace": "rbac-demo"
},
"roleRef": {
"apiGroup": "rbac.authorization.k8s.io",
"kind": "Role",
"name": "pod-reader"
},
"subjects": [
{
"apiGroup": "rbac.authorization.k8s.io",
"kind": "User",
"name": "[email protected]"
}
]
}'
Verificando Permisos
Ahora testeemos permisos. Kubernetes proporciona una API muy útil para esto - el SubjectAccessReview:
kubectl auth can-i get pods --namespace=rbac-demo --as=john.doe@example.com
Esto debería retornar yes
ya que recién le dimos a [email protected] el role pod-reader. ¿Pero qué está pasando por detrás? Usemos curl para hacer la misma verificación:
curl -k -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-X POST \
https://172.19.0.2:6443/apis/authorization.k8s.io/v1/subjectaccessreviews \
-d '{
"apiVersion": "authorization.k8s.io/v1",
"kind": "SubjectAccessReview",
"spec": {
"resourceAttributes": {
"namespace": "rbac-demo",
"verb": "get",
"resource": "pods"
},
"user": "[email protected]"
}
}'
La respuesta se va a ver así:
{
"apiVersion": "authorization.k8s.io/v1",
"kind": "SubjectAccessReview",
"metadata": {
},
"spec": {
"resourceAttributes": {
"namespace": "rbac-demo",
"resource": "pods",
"verb": "get"
},
"user": "[email protected]"
},
"status": {
"allowed": true,
"reason": "RBAC: allowed by RoleBinding \"pod-reader-binding/rbac-demo\" of Role \"pod-reader\" to User \"[email protected]\""
}
}
¡Esto es increíblemente útil! La respuesta no solo nos dice si la acción está permitida ("allowed": true
) sino que también explica exactamente por qué (campo reason
).
Testeemos un permiso que debería ser denegado:
curl -k -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-X POST \
https://172.19.0.2:6443/apis/authorization.k8s.io/v1/subjectaccessreviews \
-d '{
"apiVersion": "authorization.k8s.io/v1",
"kind": "SubjectAccessReview",
"spec": {
"resourceAttributes": {
"namespace": "rbac-demo",
"verb": "delete",
"resource": "pods"
},
"user": "[email protected]"
}
}'
La respuesta va a mostrar:
{
"status": {
"allowed": false,
"reason": "RBAC: access denied"
}
}
Ejemplo de ClusterRole y ClusterRoleBinding
Vamos a crear un ClusterRole que pueda leer nodes (un recurso de nivel cluster):
kubectl create clusterrole node-reader --verb=get,list,watch --resource=nodes
El equivalente en curl (notá que no hay namespace en la URL ya que es de nivel cluster):
curl -k -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-X POST \
https://172.19.0.2:6443/apis/rbac.authorization.k8s.io/v1/clusterroles \
-d '{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "ClusterRole",
"metadata": {
"name": "node-reader-curl"
},
"rules": [
{
"apiGroups": [""],
"resources": ["nodes"],
"verbs": ["get", "list", "watch"]
}
]
}'
Ahora vinculémoslo a un usuario:
curl -k -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-X POST \
https://172.19.0.2:6443/apis/rbac.authorization.k8s.io/v1/clusterrolebindings \
-d '{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "ClusterRoleBinding",
"metadata": {
"name": "node-reader-binding-curl"
},
"roleRef": {
"apiGroup": "rbac.authorization.k8s.io",
"kind": "ClusterRole",
"name": "node-reader-curl"
},
"subjects": [
{
"apiGroup": "rbac.authorization.k8s.io",
"kind": "User",
"name": "[email protected]"
}
]
}'
Debugeando Problemas de RBAC
Una de las características más poderosas para debugear RBAC es la habilidad de verificar permisos para cualquier usuario. Creemos un script comprensivo para auditar permisos:
#!/bin/bash
# rbac-check.sh
USER=$1
NAMESPACE=${2:-"default"}
if [ -z "$USER" ]; then
echo "Usage: $0 <user> [namespace]"
exit 1
fi
echo "Verificando permisos para usuario: $USER en namespace: $NAMESPACE"
echo "=================================================="
# Recursos comunes para verificar
RESOURCES=("pods" "services" "deployments" "configmaps" "secrets")
VERBS=("get" "list" "watch" "create" "update" "patch" "delete")
for resource in "${RESOURCES[@]}"; do
echo "Recurso: $resource"
for verb in "${VERBS[@]}"; do
result=$(kubectl auth can-i $verb $resource --namespace=$NAMESPACE --as=$USER)
printf " %-8s: %s\n" "$verb" "$result"
done
echo ""
done
Características Avanzadas de RBAC
Exploremos algunas características avanzadas de RBAC usando tanto kubectl como curl:
Resource Names: Podés restringir acceso a recursos nombrados específicos:
curl -k -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-X POST \
https://172.19.0.2:6443/apis/rbac.authorization.k8s.io/v1/namespaces/rbac-demo/roles \
-d '{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "Role",
"metadata": {
"name": "specific-pod-reader",
"namespace": "rbac-demo"
},
"rules": [
{
"apiGroups": [""],
"resources": ["pods"],
"resourceNames": ["my-specific-pod"],
"verbs": ["get", "list", "watch"]
}
]
}'
API Groups: Diferentes recursos pertenecen a diferentes API groups:
# Core API group (string vacío) - pods, services, etc.
# apps API group - deployments, replicasets, etc.
# extensions API group - ingresses, etc.
curl -k -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-X POST \
https://172.19.0.2:6443/apis/rbac.authorization.k8s.io/v1/namespaces/rbac-demo/roles \
-d '{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "Role",
"metadata": {
"name": "deployment-manager",
"namespace": "rbac-demo"
},
"rules": [
{
"apiGroups": ["apps"],
"resources": ["deployments"],
"verbs": ["get", "list", "watch", "create", "update", "patch", "delete"]
}
]
}'
Inspeccionando RBAC Existente
Para entender qué permisos existen en tu cluster, podés consultar la API directamente:
# Listar todos los roles en un namespace
curl -k -H "Authorization: Bearer $TOKEN" \
https://172.19.0.2:6443/apis/rbac.authorization.k8s.io/v1/namespaces/rbac-demo/roles
# Listar todos los rolebindings en un namespace
curl -k -H "Authorization: Bearer $TOKEN" \
https://172.19.0.2:6443/apis/rbac.authorization.k8s.io/v1/namespaces/rbac-demo/rolebindings
# Listar todos los clusterroles
curl -k -H "Authorization: Bearer $TOKEN" \
https://172.19.0.2:6443/apis/rbac.authorization.k8s.io/v1/clusterroles
# Listar todos los clusterrolebindings
curl -k -H "Authorization: Bearer $TOKEN" \
https://172.19.0.2:6443/apis/rbac.authorization.k8s.io/v1/clusterrolebindings
También podés usar jq para filtrar y formatear el output:
# Obtener todos los rolebindings y mostrar qué usuarios tienen qué roles
curl -s -k -H "Authorization: Bearer $TOKEN" \
https://172.19.0.2:6443/apis/rbac.authorization.k8s.io/v1/namespaces/rbac-demo/rolebindings | \
jq -r '.items[] | "\(.metadata.name): \(.subjects[]?.name) -> \(.roleRef.name)"'
Testeando con un Usuario Real
Vamos a crear un service account y testear nuestras reglas RBAC:
kubectl create serviceaccount test-user -n rbac-demo
# Vincular nuestro role pod-reader a este service account
kubectl create rolebinding test-user-binding \
--namespace=rbac-demo \
--role=pod-reader \
--serviceaccount=rbac-demo:test-user
# Secreto especial que se usa para crear el token de este service account
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: test-user-sa-secret
namespace: rbac-demo
annotations:
kubernetes.io/service-account.name: test-user
type: kubernetes.io/service-account-token
EOF
Ahora obtengamos el token del service account y testeemos permisos:
# Obtener el token del service account
SA_TOKEN=$(kubectl get secret $(kubectl get serviceaccount test-user -n rbac-demo -o jsonpath='{.secrets[0].name}') -n rbac-demo -o jsonpath='{.data.token}' | base64 -d)
# Testear si podemos listar pods usando el token del service account
curl -k -H "Authorization: Bearer $SA_TOKEN" \
https://172.19.0.2:6443/api/v1/namespaces/rbac-demo/pods
Esto debería funcionar ya que le dimos al service account el role pod-reader. Ahora probemos algo que no debería poder hacer:
# Intentar crear un pod (debería fallar)
curl -k -H "Authorization: Bearer $SA_TOKEN" \
-H "Content-Type: application/json" \
-X POST \
https://172.19.0.2:6443/api/v1/namespaces/rbac-demo/pods \
-d '{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "test-pod"
},
"spec": {
"containers": [
{
"name": "test",
"image": "nginx"
}
]
}
}'
Esto debería retornar un error 403 Forbidden con un mensaje como:
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "pods is forbidden: User \"system:serviceaccount:rbac-demo:test-user\" cannot create resource \"pods\" in API group \"\" in the namespace \"rbac-demo\"",
"reason": "Forbidden",
"details": {
"kind": "pods"
},
"code": 403
}
¡Perfecto! El RBAC está funcionando como esperábamos.
Patrones Comunes de RBAC
Acá hay algunos patrones comunes de RBAC que vas a encontrar:
Acceso de solo lectura a todo en un namespace:
curl -k -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-X POST \
https://172.19.0.2:6443/apis/rbac.authorization.k8s.io/v1/namespaces/rbac-demo/roles \
-d '{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "Role",
"metadata": {
"name": "namespace-reader",
"namespace": "rbac-demo"
},
"rules": [
{
"apiGroups": ["*"],
"resources": ["*"],
"verbs": ["get", "list", "watch"]
}
]
}'
Acceso para crear y manejar deployments:
curl -k -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-X POST \
https://172.19.0.2:6443/apis/rbac.authorization.k8s.io/v1/namespaces/rbac-demo/roles \
-d '{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "Role",
"metadata": {
"name": "deployment-manager",
"namespace": "rbac-demo"
},
"rules": [
{
"apiGroups": ["apps"],
"resources": ["deployments"],
"verbs": ["*"]
},
{
"apiGroups": [""],
"resources": ["pods"],
"verbs": ["get", "list", "watch"]
}
]
}'
Troubleshooting RBAC
Cuando RBAC no está funcionando como esperás, acá hay un enfoque sistemático para debugear:
-
Verificar si el usuario/service account existe:
kubectl get serviceaccount test-user -n rbac-demo
-
Verificar qué roles están vinculados al usuario:
kubectl get rolebindings -n rbac-demo -o wide kubectl get clusterrolebindings -o wide
-
Usar SubjectAccessReview para testear permisos específicos:
kubectl auth can-i create pods --namespace=rbac-demo --as=system:serviceaccount:rbac-demo:test-user
-
Verificar el mensaje de error exacto de la API: Los mensajes de error usualmente son muy específicos sobre qué está faltando.
-
Verificar las reglas del role:
kubectl describe role pod-reader -n rbac-demo
Clean up
Siempre recordá limpiar tus recursos de testing:
kubectl delete namespace rbac-demo
O con curl:
curl -k -H "Authorization: Bearer $TOKEN" \
-X DELETE \
https://172.19.0.2:6443/api/v1/namespaces/rbac-demo
Errata
Si encontrás algún error o tenés alguna sugerencia, mandame un mensaje para que lo corrija.
Podés leer más sobre RBAC acá y sobre la API de Kubernetes acá.
No tienes cuenta? Regístrate aqui
Ya registrado? Iniciar sesión a tu cuenta ahora.
-
Comentarios
Online: 0
Por favor inicie sesión para poder escribir comentarios.