Modelo de seguridad¶
Qué garantiza kubelatch, qué no, y en qué se apoya cada garantía. Léelo antes de conceder permisos o de poner kubelatch en producción.
Qué protege y qué no¶
kubelatch controla quién llega a los API servers a través de él, con qué identidad y qué queda registrado. En concreto:
- Cada petición lleva una credencial propia, revocable al instante y con caducidad.
- El cluster ve a la persona real (
user:<login>) y solo los grupos de sus permisos activos. - Cada petición queda registrada antes de llegar al cluster, con un identificador que la correlaciona con la auditoría nativa del cluster.
- La ServiceAccount del proxy no puede impersonar a nadie fuera de lo que kubelatch concede.
kubelatch no protege:
- Lo que ocurre dentro de un namespace que ya está concedido: la frontera real es el namespace (ver Niveles).
- El acceso nativo a los clusters (identidades cloud, kubeconfigs de administrador, tokens de ServiceAccount sacados de un pod). Eso queda en la auditoría nativa del cluster, no en la de kubelatch.
- La base de datos frente a quien tiene su rol de Postgres, ni la clave de cifrado frente a quien lee los Secrets del cluster de gestión.
- Las cargas de trabajo: kubelatch no está en su camino.
Credenciales: tokens klt_¶
Una credencial es un token opaco: klt_ seguido de 32 bytes aleatorios en base64url.
- kubelatch guarda solo su SHA-256 y los 12 primeros caracteres visibles (
klt_+ 8), que sirven para reconocerla en el inventario y en la auditoría sin poder usarla. - El token y el kubeconfig se muestran una sola vez, en una respuesta con
Cache-Control: no-store. La interfaz no los guarda en el almacenamiento del navegador ni en la URL. - No hay caché de autorización: cada petición al proxy consulta la credencial, así que revocar vale en la petición siguiente. Los streams abiertos se cortan en 10 s como máximo.
- La caducidad es obligatoria: 30 días como máximo para personas y 90 para bots por defecto (
KUBELATCH_MAX_TTL_USER,KUBELATCH_MAX_TTL_BOT). Las de CI duran 1 h por defecto (KUBELATCH_CI_TOKEN_TTL).
Un hash rápido basta porque el token tiene 256 bits de entropía: no hay diccionario que probar. Los enlaces de invitación y vinculación (kli_…) siguen el mismo esquema, con un solo uso y caducidad.
El proxy y su ServiceAccount¶
- Sin cookies. El proxy solo acepta
Authorization: Bearer klt_…e ignora las cookies, así que una página web no puede usar tu sesión contra un cluster. - Sin
Impersonate-*del cliente. Cualquier cabeceraImpersonate-*entrante se rechaza con400. kubelatch ya usa la impersonación con la ServiceAccountkubelatch-proxy; dejar pasar una impersonación anidada permitiría a cualquiera pedir ser otro. Por esokubectl --asno está soportado. Para probar «qué puede hacer X», usa el acceso nativo de administrador:kubectl auth can-i --as user:x --as-group kubelatch:ns:apps:viewer. Audit-IDpropio. El proxy genera el suyo y nunca respeta el del cliente.- Rutas estrictas. Rechaza segmentos
.,.., vacíos y cualquier escape, para que nada se decodifique dos veces por el camino. - ServiceAccount acotada.
kubelatch-proxysolo puede impersonar a los usuarios y grupos de la lista que mantiene el reconciliador (resourceNames): los sujetos con permisos activos y los grupos de esos permisos. Un fallo en el proxy no puede llegar asystem:mastersni a nadie a quien kubelatch no haya concedido algo. - Nombres con prefijo.
user:ybot:evitan colisiones con identidades nativas del cluster.
La ServiceAccount kubelatch-reconciler sí es equivalente a cluster-admin por diseño: tiene que poder crear el binding de cluster-admin. Sus escrituras están limitadas a los nombres fijos que kubelatch gestiona, lo que acota errores, no el privilegio. Nunca está en el camino de una petición de cliente.
La frontera de namespace¶
Dentro de un namespace concedido, kubelatch no puede separar nada: quien crea pods alcanza sus Secrets y sus ServiceAccounts. Por eso developer, debugger y admin exigen Pod Security Admission (baseline o restricted), y kube-system, kube-public, kube-node-lease, kubelatch-system y los de KUBELATCH_PROTECTED_NAMESPACES no admiten ningún permiso. viewer con ámbito * lee los logs de todos los namespaces, y los logs pueden contener secretos. Todo esto está explicado en Niveles de permiso.
Endurecimiento opcional: bindings a kubelatch-*¶
Un admin de namespace, concedido por kubelatch o nativo, puede crear un RoleBinding a kubelatch-developer para el sujeto que quiera. Ese binding no da acceso a través de kubelatch, porque el proxy solo impersona a quien tiene permisos vigentes. Pero sí se lo daría a una credencial nativa de ese sujeto, y confunde al reconciliador, que registra en el log los bindings ajenos con nombres propios.
deploy/k8s/hardening/vap-kubelatch-bindings.yaml es un ValidatingAdmissionPolicy (Kubernetes 1.30+) que solo deja a la ServiceAccount kubelatch-system/kubelatch-reconciler crear o cambiar bindings cuyo roleRef empiece por kubelatch-. Se aplica en cada cluster gestionado, con el kubeconfig de ese cluster, no el del cluster de gestión. make e2e-install comprueba que deniega un binding manual y que el reconciliador sigue funcionando. La política también deniega a un administrador los bindings del propio bootstrap: mover el cluster a otra instancia o registrarlo de nuevo exige quitarla un momento (Endurecer el cluster).
Tokens de los clusters, cifrados¶
Los tokens de las dos ServiceAccounts de cada cluster son las llaves del reino. kubelatch los guarda cifrados con AES-256-GCM, con una clave derivada por HKDF de KUBELATCH_ENCRYPTION_KEY, y nunca los muestra. Cada token va ligado a su cluster y a su papel (proxy o reconciliador), así que no se puede mover el cifrado de un cluster a otro.
La misma clave, con otras etiquetas de derivación, firma las cookies de sesión y las cookies de estado del login con GitHub. Si se pierde o se rota, todo el mundo vuelve a iniciar sesión y hay que volver a pegar los tokens de cada cluster. Las contraseñas y las credenciales klt_ no dependen de ella.
Guarda la clave aparte
La clave de cifrado no está en Postgres. Un backup de la base de datos sin la clave no permite usar los tokens de los clusters; con la clave, sí. Guárdalas por separado.
Proxies de confianza y la IP de origen¶
La IP de origen de la auditoría y los tres limitadores por IP (login, intercambio de CI y fallos de autenticación del proxy) usan la dirección de la conexión TCP. Detrás de un Ingress u otro proxy L7, esa dirección es la del proxy.
KUBELATCH_TRUSTED_PROXIES dice de qué redes se cree X-Forwarded-For, leído de derecha a izquierda hasta el primer salto que no es de confianza. Solo admite CIDR: una IP suelta impide el arranque, y un prefijo más amplio que /8 en IPv4 o /16 en IPv6 también.
- No pongas rangos que alcance un cliente: podría elegir la IP con la que aparece en la auditoría y esquivar los límites.
- Detrás de un Ingress, confía solo en las IP de los pods del controlador. Un CIDR de pods de todo el cluster convierte cada pod en un proxy de confianza. Por eso el overlay
ingresstrae unaNetworkPolicyobligatoria que solo admite tráfico desde el namespace del controlador, y necesita un CNI que apliqueNetworkPolicy. - Detrás de un
LoadBalancerL4 conexternalTrafficPolicy: Local, déjalo vacío.
Los limitadores agrupan IPv6 por /64, para que rotar direcciones dentro del prefijo no multiplique los intentos. Con dos réplicas, cada una cuenta sus límites por IP en memoria; el bloqueo de cuenta tras 5 fallos sí es global.
Contraseñas y sesiones¶
- Contraseñas de 12 a 128 caracteres, distintas del login, sin reglas de composición. Hash argon2id con los parámetros de OWASP, como mucho 4 cálculos a la vez.
- 5 fallos seguidos bloquean la cuenta 15 minutos, y cada IP tiene 10 intentos por minuto. La respuesta es la misma para login inexistente, contraseña errónea o cuenta deshabilitada, y tarda lo mismo.
- Todo intento de login, bueno o malo, queda en el registro del plano de control con la IP. La excepción es un login de más de 64 caracteres: recibe el mismo
401sin llegar a la base de datos ni quedar registrado. - Cambiar la propia contraseña en «Mi cuenta» tiene su propio límite: 10 intentos por minuto por cuenta, y cada fallo queda registrado.
- Sesiones de 12 h en una cookie firmada, revocables todas a la vez. Las mutaciones de la API tienen protección contra peticiones de otro origen.
Tras 5 fallos, el 429 confirma que el login existe, y cualquiera puede bloquear una cuenta 15 minutos. Se acepta para una herramienta interna; el límite por IP lo encarece.
Identidad con GitHub¶
Con la GitHub App configurada, la identidad de las personas es la de GitHub. Los detalles del flujo están en Identidad; aquí, lo que importa para la seguridad:
- Doble factor. kubelatch no lo implementa: se apoya en el que impone la organización. Solo puede comprobarlo si la App tiene Organization → Administration: read; sin ese permiso, avisa y confía. Activa el 2FA obligatorio en GitHub antes que nada.
- Contraseñas. Con GitHub activo, solo entran con contraseña las cuentas de emergencia, que no tienen doble factor. Que sean pocas, con contraseñas largas en un gestor, y para usar solo cuando GitHub no está. Los hashes de las demás cuentas siguen en la base de datos pero no sirven para entrar.
- Bajas. Quien sale de la organización se deshabilita solo (webhook firmado con HMAC o sincronización horaria). La sincronización nunca deshabilita ante un error de GitHub y nunca rehabilita. El TTL máximo de las credenciales acota lo que un fallo prolongado de ambas vías podría dejar abierto.
- El flujo OAuth.
statealeatorio en una cookie firmada de 10 minutos,HttpOnly,SameSite=Laxy limitada a la ruta del callback. El código se canjea en el servidor con el client secret y el token de la persona se descarta tras el login. No se usa PKCE: GitHub no lo documenta para GitHub Apps y kubelatch es un cliente confidencial, para el questatemás client secret es lo que exige la BCP de OAuth 2.0. Los tokens de instalación de la App viven solo en memoria. - Carreras. Vincular es una comparación e intercambio sobre el id de GitHub: un login que compite con una desvinculación o revinculación de la misma cuenta no obtiene sesión. Un error interno del callback redirige con
?error=server, sin detalles. - Webhook sin deduplicación. Una entrega firmada y repetida puede deshabilitar a alguien que un admin rehabilitó tras volver a la organización. La cuenta queda deshabilitada hasta que un admin la rehabilite otra vez, con credenciales nuevas porque las anteriores quedaron revocadas: la sincronización horaria nunca rehabilita, así que no corrige esa baja. Guarda
GITHUB_WEBHOOK_SECRETcomo cualquier secreto y rótalo si se filtra. Si alguien que volvió aparece deshabilitado, revisa «Usuarios» y losuser.disableconvia: github-webhookdel plano de control.
Las cookies de sesión y de estado no llevan el prefijo __Host-; están limitadas a su ruta y son HttpOnly.
Sin GitHub¶
Sin las variables GITHUB_*, el login local no tiene doble factor ni bajas automáticas. Una contraseña robada da acceso a los clusters con los permisos de esa persona. Mitigaciones activas: sesiones de 12 h, bloqueo por intentos y límite por IP, credenciales con caducidad, revocación inmediata y auditoría de cada login. Es aceptable para una prueba o un entorno sin GitHub. En producción, configura la GitHub App y, mientras tanto, deshabilita el mismo día las cuentas de quien se va.
Credenciales de CI sin secretos¶
Un workflow de GitHub Actions no guarda ningún secreto de kubelatch: canjea un id_token OIDC que GitHub firma para ese run (CI: trust rules). La verificación es estricta:
- Firma. Solo RS256, con las claves públicas de
<issuer>/.well-known/jwks(KUBELATCH_GITHUB_ACTIONS_ISSUER). kubelatch las guarda 1 h; una clave desconocida fuerza un refresco, como mucho uno por minuto. Si GitHub no responde, sigue usando las guardadas hasta 24 h después del último refresco correcto. Pasado ese plazo, todos los intercambios fallan con401. - Claims.
issexacto,audigual aKUBELATCH_BASE_URL, yexp,nbfeiatcon 30 s de margen para el desfase de relojes. - Un solo uso. El
jtise guarda al canjearlo; un token repetido se rechaza con401. - Ids, no nombres. Las trust rules comparan
repository_owner_id,repository_id,refyenvironment. kubelatch no interpretasub: acepta tanto el formato clásicorepo:owner/name:…como el de ids inmutables.
Cualquier fallo del token responde el mismo 401 token no válido; el motivo solo queda en el log y en el evento ci.exchange.failure.
Auditoría inmutable¶
kubelatch escribe dos registros en Postgres:
audit_events: una fila por petición al proxy, insertada antes de reenviarla y completada al terminar.control_events: cada acción del plano de control (altas, permisos, credenciales, revocaciones, cambios de cuentas, trust rules) en la misma transacción que la acción, y cada intento de login.
Triggers de Postgres impiden modificarlas: una fila de audit_events solo se completa una vez, control_events nunca se modifica, y TRUNCATE está prohibido en ambas. Borrar solo se permite a la tarea periódica de retención: borra por lotes de 5 000 filas y activa en cada lote el interruptor SET LOCAL kubelatch.audit_retention = 'on' que los triggers exigen para un DELETE. Las filas no tienen claves foráneas: borrar un cluster o un sujeto no borra su historia.
La base de datos es tan segura como su acceso
Quien tenga el rol de Postgres de kubelatch puede desactivar los triggers o activar el interruptor de retención. kubelatch usa el mismo rol para las migraciones y para la ejecución. Si necesitas evidencia a prueba de manipulación, exporta la auditoría a un almacenamiento inmutable.
La retención se fija con KUBELATCH_AUDIT_RETENTION, 90 días por defecto (ver Auditoría y retención).
La auditoría nativa del cluster, segunda fuente¶
Mantén activa la auditoría nativa de cada cluster. Registra lo que no pasa por kubelatch (accesos nativos, tokens de ServiceAccount usados desde pods) y es una segunda fuente independiente. El API server usa el Audit-ID de kubelatch como su auditID, así que una fila de kubelatch y su evento nativo se encuentran por el mismo identificador.
Datos personales¶
audit_events guarda por petición la IP, el login, el User-Agent y la query string. En un exec, la query contiene command=, es decir, lo que la persona ejecutó. control_events guarda la IP y el login de cada intento de login.
- Finalidad: seguridad y trazabilidad del acceso a los clusters.
- Plazo:
KUBELATCH_AUDIT_RETENTION(90 días por defecto).
Lo que kubelatch no cubre¶
Grabación de sesiones, ingesta de la auditoría nativa del cluster, relay para clusters privados, gestión de namespaces y cuotas, kubectl --as, proveedores de identidad distintos de GitHub y límite de peticiones por credencial. Tampoco sustituye al acceso nativo de emergencia a cada cluster, que debe existir y estar probado (ver Cuentas y recuperación).