Kubernetes RBAC a fondo: Entendiendo autorización con kubectl y curl


kubernetes

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:

  1. Verificar si el usuario/service account existe:

    kubectl get serviceaccount test-user -n rbac-demo
  2. Verificar qué roles están vinculados al usuario:

    kubectl get rolebindings -n rbac-demo -o wide
    kubectl get clusterrolebindings -o wide
  3. Usar SubjectAccessReview para testear permisos específicos:

    kubectl auth can-i create pods --namespace=rbac-demo --as=system:serviceaccount:rbac-demo:test-user
  4. Verificar el mensaje de error exacto de la API: Los mensajes de error usualmente son muy específicos sobre qué está faltando.

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

Iniciar session con GitHub
Iniciar sesion con Google
  • Comentarios

    Online: 0

Por favor inicie sesión para poder escribir comentarios.

by Gabriel Garrido