Saltar a contenido

El viaje de una petición

Sigue una petición de kubectl desde que sale de tu máquina hasta que vuelve la respuesta. Así entiendes qué comprueba kubelatch, qué ve el cluster y qué queda en la auditoría.

De un vistazo

sequenceDiagram
    participant K as kubectl
    participant P as Proxy de kubelatch
    participant DB as Postgres
    participant A as API server
    K->>P: GET /clusters/prod/api/v1/namespaces/apps/pods<br/>Authorization: Bearer klt_…
    P->>P: Ruta y cabeceras Impersonate-*
    P->>DB: Busca el hash del token
    P->>DB: Sujeto, cluster y permisos activos
    P->>DB: Inserta la fila de auditoría (inicio)
    P->>A: Misma petición con Impersonate-User,<br/>Impersonate-Group, Impersonate-Uid, Audit-ID
    A-->>P: Respuesta (RBAC nativo del cluster)
    P-->>K: Respuesta + Audit-ID
    P->>DB: Completa la fila (estado, duración)

1. La petición llega al proxy

Tu kubeconfig apunta a https://<kubelatch>/clusters/<id> y lleva un token klt_… como Bearer. Todo lo que cuelga de /clusters/ va al proxy antes que a cualquier otra ruta.

Toda respuesta del proxy lleva una cabecera Audit-ID, también los errores. Es lo que buscas en los logs o en la auditoría para localizar una petición concreta.

2. Primer filtro: ruta y cabeceras

Antes de mirar el token, el proxy rechaza con 400:

  • Rutas con segmentos vacíos, . o .., caracteres escapados o un %.
  • Un identificador de cluster que no puede existir.
  • Cualquier cabecera Impersonate-* que venga del cliente. Por eso kubectl --as no funciona a través de kubelatch: el proxy ya usa la impersonación, y dejar pasar una impersonación anidada permitiría pedir ser otra persona.

Estos rechazos no dejan fila de auditoría: la petición no llega a identificar a nadie.

3. ¿Quién eres? El token

El proxy solo acepta Authorization: Bearer klt_… con el formato exacto de un token de kubelatch. Ignora las cookies. Después:

  1. Calcula el SHA-256 del token y busca la credencial por ese hash. No hay caché: cada petición consulta Postgres, así que una revocación vale en la petición siguiente.
  2. Comprueba que la credencial no esté revocada ni caducada. Si falla algo de esto, o el token no existe, responde 401.
  3. Carga el sujeto dueño de la credencial. Si la cuenta está deshabilitada, responde 403.
  4. Si la credencial está restringida a un cluster y no es este, responde 403.

4. ¿Qué puedes hacer aquí? Cluster y permisos

El proxy carga el cluster. Si no existe responde 404; si todavía no tiene tokens configurados, 503.

Luego busca los permisos activos del sujeto en ese cluster: no revocados y sin caducar en este instante. Si no hay ninguno responde 403 con un mensaje claro. Cada permiso se traduce en un grupo:

Ámbito del permiso Grupo que se impersona
Un namespace kubelatch:ns:<namespace>:<nivel>
Todo el cluster (*) kubelatch:cluster:<nivel>

Una caducidad no necesita que nadie haga nada: en cuanto un permiso caduca, el proxy deja de enviar su grupo.

5. La fila de auditoría, antes de reenviar

Con la identidad y los grupos resueltos, el proxy clasifica la petición como lo haría el API server: verbo, grupo de API, recurso, subrecurso, namespace y nombre. Inserta la fila de auditoría antes de reenviar. Si no puede escribirla, responde 503 y no reenvía nada: ninguna petición pasa sin su registro.

También actualiza el «último uso» de la credencial, como mucho una vez por minuto.

6. Lo que recibe el API server

El proxy reescribe la petición antes de reenviarla:

Cabecera Qué hace el proxy
Authorization La sustituye por el token de la ServiceAccount kubelatch-proxy de ese cluster. Tu token nunca llega al cluster.
Impersonate-User user:<login> para personas, bot:<nombre> para bots.
Impersonate-Uid El identificador interno del sujeto.
Impersonate-Group Una por cada grupo del paso 4.
Audit-ID Un UUIDv7 generado por kubelatch. Nunca respeta el del cliente.
X-Forwarded-For La IP real del cliente (ver proxies de confianza).
Cookie, X-Real-IP, Forwarded, X-Forwarded-* Las elimina.

El API server autentica a la ServiceAccount del proxy, comprueba que puede impersonar a ese usuario y a esos grupos, y aplica su RBAC nativo a la identidad impersonada. kubelatch no añade system:authenticated: lo pone el propio API server.

El API server usa la cabecera Audit-ID como auditID de su propio registro. Así una fila de kubelatch y el evento de la auditoría nativa del cluster se correlacionan por el mismo identificador. En la auditoría nativa, user es la ServiceAccount del proxy e impersonatedUser es user:<login>.

7. La respuesta vuelve

El proxy devuelve la respuesta del API server tal cual, con su propio Audit-ID. Si no consigue hablar con el API server, responde 502 con un resumen del problema (tiempo agotado, certificado que no coincide, API server inalcanzable), sin filtrar direcciones internas.

Al terminar, completa la fila de auditoría con el código de estado, la duración, la hora de fin y el error si lo hubo. Una fila solo se puede completar una vez.

Streams largos: exec, port-forward, watch y logs

kubectl exec, attach, port-forward y cp abren una conexión que se actualiza a WebSocket o SPDY. El proxy las envía por un transporte HTTP/1.1 dedicado, porque el API server no hace esa actualización sobre HTTP/2. El resto de peticiones van por HTTP/2. watch y logs -f se reenvían sin búfer, byte a byte.

La fila de auditoría de un stream se abre al empezar (101 para las conexiones actualizadas) y se completa al cerrarse, con su duración real.

Revocar corta lo que ya está abierto

Revocar una credencial rechaza la siguiente petición con 401. Pero un exec o un watch abiertos no hacen peticiones nuevas. Por eso cada réplica lleva un registro de sus peticiones en curso y cada 10 s revalida cada una:

  • ¿Sigue la credencial sin revocar y sin caducar?
  • ¿Sigue la cuenta habilitada?
  • ¿Siguen siendo exactamente los mismos grupos? Añadir o quitar un permiso en ese cluster también corta el stream, y el cliente se reconecta con los grupos nuevos.

Lo que ya no vale se corta en 10 s como máximo. Si el corte llega antes de que el API server responda, el cliente recibe un 403; si el stream ya estaba abierto, se cierra la conexión. La fila de auditoría queda con el error «stream cortado: la credencial ya no es válida».

Si Postgres falla durante esa revalidación, los streams abiertos siguen abiertos. Las peticiones nuevas sí fallan cerradas.

Qué queda registrado cuando algo falla

Rechazo ¿Deja fila de auditoría?
400 (ruta, cluster no válido, Impersonate-*) No
401 (token ausente, desconocido, revocado o caducado) Sí, sin sujeto, con el prefijo visible del token
403 (cuenta deshabilitada, otro cluster, sin permisos), 404 (cluster desconocido), 503 (cluster sin tokens) Sí, con credencial y sujeto
503 porque kubelatch no puede usar Postgres No: no hay dónde escribirla

Las filas de rechazo se limitan a una por IP cada 10 s, para que nadie llene la tabla probando tokens. La respuesta al cliente no cambia por ese límite. La lista completa de códigos está en Errores.