Ir al contenido

Identidad y enrollment

El WhyStack Agent se autentica por mTLS. Esta página trata de la credencial que usa para eso: cómo la obtiene, cómo esa credencial se mantiene viva y (la pregunta que sale cara cuando nadie la hace primero) qué pasa cuando se reemplaza el contenedor.

Conviene separar las dos cosas desde ya, porque se confunden:

  • Cómo nace la identidad: tres caminos, y eliges uno.
  • Cómo sobrevive: renovación (dentro del proceso) y persistencia (a través del reemplazo del contenedor).

La segunda mitad es la que decide si un deploy necesita a alguien frente a un navegador.

Camino Cuándo usarlo Qué configuras
Atestación de nube Donde la plataforma prueba la identidad por sí sola: GKE, EC2/EKS RUNNER_ATTESTATION_MODE + RUNNER_ENROLL_URL
Bootstrap token Donde no hay identidad de plataforma: VPS, tu propio host, un primer piloto RUNNER_BOOTSTRAP_TOKEN + RUNNER_ENROLL_URL
Certificado aprovisionado Un puente: nosotros lo emitimos, tú lo montas RUNNER_CERT_PEM + RUNNER_KEY_PEM

La atestación de nube es la mejor dondequiera que exista, y la razón es concreta: no hay ningún secreto que generar, entregar ni rotar. Los modos disponibles y lo que exige cada uno están en Atestación.

El boot es una máquina de estados: liveness → config → secrets → enroll → connect → ready. El paso enroll hace esto:

  1. Genera un par de claves EC P-256. La clave privada existe solo en memoria, como PKCS#8.
  2. Genera un nonce y arma un CSR con él en el campo challengePassword, que es la prueba de posesión.
  3. Recopila la atestación de la plataforma, según RUNNER_ATTESTATION_MODE.
  4. Hace un POST a /api/enroll con { version, attestation, csr, challengeNonce }.
  5. El control-plane valida la atestación, firma el CSR y devuelve un certificado con el SPIFFE id del tenant adentro: spiffe://rootpilot/tenant/<slug>/runner. El trust domain pasa a whystack más adelante; el Agent acepta los dos, y los dos nombran a la misma organización.
  6. El Agent abre el túnel WSS con mTLS usando ese certificado.

El tenant sale del certificado, no de la configuración

Sección titulada «El tenant sale del certificado, no de la configuración»

Vale la pena insistir en esto, porque es la garantía que sostiene todo lo demás: el material de atestación determina el tenant, no una variable de entorno. El enrollment devuelve un certificado con el SPIFFE id adentro, y eso es lo que el control-plane lee en cada llamada.

RUNNER_TENANT, que aparece en los scripts de la flota, no elige ningún tenant: elige la carpeta de la bóveda de la que se leen las credenciales. Equivocarte en ese slug no te va a conectar a la organización equivocada, y si el slug declarado no coincide con aquel con el que la credencial hizo el enrollment, el Agent se niega a arrancar en lugar de servir a la organización equivocada.

El certificado es de vida corta, 15 minutos por defecto. Es deliberado: un certificado que vale poco tiempo vale poco para quien lo robe.

RUNNER_CERT_RENEWAL=on es el valor por defecto, y la renovación se dispara a los 2/3 del tiempo restante, no cerca del vencimiento. La razón es operativa: renovar justo en notAfter convertiría cualquier inestabilidad transitoria de la red en pérdida de identidad. Renovar a los 2/3 deja un tercio de la vida útil como ventana de reintentos.

Los reintentos hacen backoff con un techo de 5 minutos y jitter de ±25%. Sin el jitter, toda la flota golpearía el control-plane en el mismo segundo. Si el servidor responde Retry-After, eso prevalece sobre el cálculo local.

La renovación requiere RUNNER_ENROLL_URL: es la dirección con la que habla.

Cada 30 segundos el Agent verifica si ya es hora de renovar, comparando el reloj del sistema con el vencimiento del certificado. No pone una alarma para dentro de 40 minutos.

La diferencia se nota cuando el host deja de contar el tiempo: una máquina suspendida, una VM restaurada desde un snapshot, un contenedor sin CPU por una cuota. Una alarma larga cuenta tiempo de proceso y no avanza mientras el host duerme. Despertaría creyendo que todavía quedan minutos para renovar un certificado que murió horas antes. Al preguntarle al reloj del sistema cada 30 segundos, el Agent descubre la situación real justo después de volver:

  • Si vuelve antes del vencimiento: renueva de inmediato. El certificado se salva.
  • Si vuelve después: declara la pérdida y sale, para que el supervisor levante un reemplazo (consulta Solución de problemas), en lugar de pasar media hora intentando reconectarse con una identidad muerta.

La renovación no es un RUNNER_ATTESTATION_MODE. Renovar no es una opción de configuración: es lo que hace el Agent cuando ya tiene un certificado.

Firma el nonce del nuevo CSR con la clave privada actual y envía el certificado actual junto con él. El control-plane valida la cadena contra la CA, verifica la firma con la clave pública del certificado y extrae el tenant del certificado, nunca del cuerpo de la solicitud. Leerlo del cuerpo convertiría la renovación en una forma de cambiar de tenant.

Esta es la diferencia que más cuesta si nadie la configura.

La renovación vive dentro del proceso. Reemplaza el contenedor y la identidad muere con él. La alternativa es el enrollment desde cero. Donde la identidad nació de un bootstrap token, eso significa un token nuevo, generado a mano, en cada deploy (el token es de un solo uso).

RUNNER_IDENTITY_DIR resuelve eso: apunta a un directorio donde el Agent escribe la identidad (identity.json, modo 600). En el siguiente boot renueva a partir de ella en lugar de hacer el enrollment desde cero.

volumes:
- whystack-identity:/identity # named volume: inherits the right ownership from the image
environment:
RUNNER_IDENTITY_DIR: /identity

Eso es todo: la variable alcanza por sí sola. La primera vez que el Agent usa el directorio, escribe ahí un id de dueño (instance-id, modo 600), así que la identidad se sigue reconociendo como suya después de que se recrea el contenedor, que es justamente el objetivo de la funcionalidad.

RUNNER_INSTANCE_ID sigue valiendo la pena, pero por otro motivo: identifica la instancia en los logs y en la Flota. Ya no tiene nada que ver con la persistencia.

Tres cosas que conviene saber antes de activarlo:

  • El directorio tiene que tener permiso de escritura para el uid del Agent (la imagen corre como no-root, uid 1000). Un volumen con nombre lo resuelve solo: la imagen trae /identity con la propiedad correcta, y el volumen la hereda al crearse. Un bind mount no hereda: la propiedad es la del host. Si no se puede escribir, el boot no falla: avisa y sigue, y solo te enteras en el siguiente deploy, con could not persist identity — next boot will re-enroll from scratch.
  • Un directorio por Agent. Dos contenedores en el mismo volumen compartirían una identidad.
  • La ventana tiene un límite. Lo que sobrevive es la renovación, y vale hasta 6 minutos después del vencimiento del certificado (5 de gracia + 1 de margen de reloj). Un deploy normal cabe con holgura; un host apagado toda la noche, no. Ese caso es enrollment desde cero.

Pasados esos 6 minutos, el control-plane responde presented cert expired beyond the renewal grace window, y lo va a responder para siempre: la atestación de renovación prueba la posesión con el certificado que murió.

El comportamiento entonces es salir del proceso, para que el supervisor levante un Agent nuevo que haga el enrollment desde cero. Sin esa salida, el proceso se volvería un zombi: arriba, sin servir nada, invisible para cualquier probe.

identity expired beyond the renewal grace window — runner needs re-enrollment

WhyStack emite un certificado para tu tenant y tú lo montas en /certs (o lo pasas inline en RUNNER_CERT_PEM/RUNNER_KEY_PEM/RUNNER_CA_PEM). No requiere atestación de plataforma ni egress hacia el endpoint de enrollment, y es el puente para entornos donde todavía no aplica ninguno de los otros dos.

El costo se declara de entrada: no se renueva. Cuando vence, la flota deja de conectarse, y el Agent ni sale del proceso ni avisa. Sigue intentando reconectarse indefinidamente, con un error de TLS que nunca dice que eso es lo que pasó. La fecha está en el notAfter de la línea de boot; anótala en el calendario el día en que recibas el certificado.

El esquema exige al menos un camino de identidad, y falla rápido cuando no hay ninguno:

either provide RUNNER_CERT_PEM+RUNNER_KEY_PEM (provisioned cert) or RUNNER_ENROLL_URL (enrollment)