¿Por qué firmar comandos remotos en el navegador? Autorización y límites de confianza en Fleetd
Firmas Ed25519, destinos, protección contra repeticiones, permisos y revocación de claves en Fleetd v0.3.0, con los límites de la interfaz y la terminal.
Cuando una máquina ya aparece en el panel, ejecutar comandos desde el navegador parece el siguiente paso natural. Consultar el disco, reiniciar un servicio o abrir una terminal facilita administrar servidores y routers. Pero también permite al panel modificar esas máquinas: un fallo de monitorización y una intrusión en el sistema de ejecución tienen consecuencias muy distintas.
En el artículo anterior expliqué la conexión saliente, la aprobación de dispositivos y su identidad. Ahora toca otra pregunta: si el agente ya reconoce al servidor, ¿por qué verifica además una firma del navegador antes de ejecutar un comando? Aquí describo Fleetd v0.3.0, incluyendo sus límites: de dónde procede el código del navegador y qué protección queda después de abrir una terminal.
Iniciar sesión no concede autoridad ilimitada
En un diseño habitual, el administrador inicia sesión, el servidor autoriza una operación y el agente ejecuta el mensaje recibido. La autoridad queda concentrada en el servidor.
Fleetd añade una comprobación local: ¿ha autorizado esta operación una clave que esta máquina considera fiable? El navegador construye y firma la solicitud; el servidor la retransmite; el agente consulta su propia configuración. El inicio de sesión y la autenticación de la conexión siguen siendo necesarios, porque responden a preguntas diferentes.
Confirmar destino y comando → Firmar en el navegador
→ Retransmitir → Verificar en el agente → Ejecutar
Recibir un mensaje por una conexión autenticada no basta para ejecutarlo.
La firma debe cubrir toda la operación
Firmar únicamente uptime no aclara qué máquina puede ejecutarlo, cuándo ni cuántas veces puede reutilizarse la solicitud. Fleetd firma estos datos:
| Campo | Función |
|---|---|
v |
Versión del protocolo |
id |
Identificar la autorización y detectar duplicados |
kind |
Ejecutar un comando o abrir una terminal |
targets |
Huellas de las máquinas autorizadas |
argv o action |
Programa y argumentos, o acción configurada localmente |
issued_at, expires_at |
Intervalo de aceptación |
timeout |
Tiempo de ejecución, limitado también por el agente |
El formulario actual envuelve el texto como ['sh', '-c', texto]. La firma abarca la solicitud que contiene esos argumentos. Las tuberías y && funcionan porque se invoca explícitamente un shell.
El navegador serializa la solicitud a bytes JSON y los firma con Ed25519. El agente verifica exactamente esos bytes antes de interpretar el JSON. Cambiar uptime por reboot o añadir un destino invalida la firma existente. Una solicitud que originalmente incluía varias máquinas sí autoriza su ejecución en cada una de ellas: vincular destinos limita el alcance, no obliga a elegir un único equipo.
El agente decide si la autorización es válida
Cada agente conserva una lista trusted_signers. La clave pública adjunta a la solicitud debe estar previamente en esa lista; aceptar cualquier clave permitiría que cualquiera autorizase sus propios comandos.
Además de verificar la firma, el agente comprueba el tipo, su huella en los destinos, las fechas y los permisos locales. Rechaza campos JSON desconocidos para evitar interpretaciones divergentes.
El navegador genera solicitudes válidas durante cinco minutos. El agente tolera dos minutos de desfase de reloj y rechaza intervalos declarados superiores a diez minutos. Es una ventana para aceptar la autorización, no la duración máxima del comando: el tiempo de ejecución se controla por separado. La tolerancia tampoco permite afirmar que toda solicitud se rechaza exactamente al quinto minuto. Ambos extremos necesitan relojes razonablemente correctos.
Estas condiciones determinan si una autorización fiable se aplica ahora a esta máquina.
Evitar repeticiones no garantiza ejecutar exactamente una vez
Una solicitud intacta y bien firmada puede reenviarse. Fleetd genera su ID a partir de 16 bytes aleatorios. Tras autorizarla, el agente registra ese ID y rechaza su reutilización.
El registro se guarda en seen.json, dentro del directorio de estado, por defecto /var/lib/fleetd. Si ese estado se conserva, reiniciar normalmente el proceso no vuelve reutilizables las solicitudes todavía vigentes. Si falla la persistencia, la ejecución no continúa.
Esto no garantiza que el efecto de negocio ocurra exactamente una vez. El ID se registra antes de comprobar el límite de concurrencia y arrancar el proceso: una solicitud puede consumir su ID sin llegar a ejecutarse. También puede ocurrir lo contrario desde la perspectiva del panel: el comando ya modificó la máquina, pero una desconexión impidió recibir el resultado.
Volver a pulsar Ejecutar crea otro ID y otra autorización. El agente no sabe si se está recuperando un intento fallido o repitiendo una operación terminada. En migraciones, cambios de configuración y actualizaciones conviene inspeccionar el estado real y diseñar operaciones idempotentes. La pérdida del archivo de estado o una restauración del disco tampoco equivalen a un reinicio ordinario del proceso.
Los permisos locales siguen mandando
| Permiso | Solicitudes admitidas |
|---|---|
monitor |
Monitorización; sin comandos ni terminales |
allowlist |
Acciones locales con firma fiable |
full |
Comandos arbitrarios y terminales con firma fiable |
Una máquina destinada a monitorización puede quedarse en monitor. Para un conjunto pequeño de tareas, una lista local reduce el alcance. En un sistema con systemd y nginx instalado, por ejemplo:
permission: allowlist
allowlist:
nginx-status: [systemctl, is-active, nginx]
Es un fragmento: siguen haciendo falta la conexión y las claves públicas fiables. La solicitud firmada incluye action: nginx-status; el agente obtiene el programa y sus argumentos de su configuración local. El emisor no puede añadir argumentos arbitrarios ni combinar action con argv.
El formulario de v0.3.0 envía comandos de shell y no incluye un selector de acciones. Escribir nginx-status en él no invoca esta acción en una máquina restringida a allowlist. La capacidad del protocolo y la interfaz todavía no coinciden por completo.
En el despliegue actual, el agente se ejecuta como root, y los comandos con permiso full tienen esa autoridad. Una firma no detecta si el comando es erróneo o destructivo. Tampoco vuelve inmutable la configuración local: un comando root ya autorizado puede modificarla. La política limita solicitudes no autorizadas, no el poder de un proceso root autorizado.
Las claves del navegador tienen su propio ciclo de vida
WebCrypto genera una pareja Ed25519 con clave privada no exportable, almacenada en IndexedDB. El flujo normal registra la clave pública en el servidor, no la privada.
Cambiar de ordenador o borrar los datos del sitio puede impedir firmar solicitudes reconocidas por los agentes, aunque siga funcionando el inicio de sesión. El nuevo navegador necesita otra clave y los agentes deben confiar en su parte pública. Registrarla en el servidor no actualiza automáticamente las instalaciones existentes.
La revocación también debe llegar a las máquinas. Eliminar una clave del panel no revoca la confianza de los agentes ya configurados. Hay que retirarla de sus listas trusted_signers y reiniciar los clientes mediante el gestor de servicios correspondiente.
La clave de firma de versiones es distinta: una firma autoriza operaciones; la otra autentica paquetes de actualización del agente. Ese segundo ciclo merece su propio artículo.
Dos límites importantes
El servidor también entrega la interfaz
«No exportable» impide extraer el material privado mediante las API normales de WebCrypto. No impide que código malicioso del mismo origen use la clave para firmar.
Fleetd sirve la interfaz desde el servidor. Si este queda comprometido, puede entregar JavaScript modificado en una visita posterior. La página podría mostrar un comando y firmar otro, o inducir un uso indebido de la clave. Un diálogo de confirmación proporcionado por la misma página no constituye una verificación independiente.
El agente rechaza solicitudes alteradas que carezcan de una firma válida, pero la confianza en el código de la interfaz sigue siendo una premisa. Distribuir un firmador por separado podría reducir esta dependencia. El documento de diseño menciona un firmador CLI, todavía no implementado en v0.3.0.
Solo se firma la apertura de la terminal
Abrir una terminal requiere una solicitud firmada kind: shell y permiso local full. Las pulsaciones posteriores circulan por la sesión sin firmas individuales del navegador. Un servidor comprometido podría inyectar entrada en una terminal abierta.
Conviene abrir sesiones cuando sean necesarias y cerrarlas al terminar. Una operación puntual y explícita suele ser más fácil de revisar que una sesión interactiva. La firma inicial no protege cada entrada posterior de la misma manera.
Firmar tampoco oculta comandos ni resultados. El cifrado entre agente y servidor protege el trayecto de red, pero el servidor es un extremo de esa comunicación. No es confidencialidad entre navegador y agente que excluya al servidor.
Importan también los casos rechazados
Ver aparecer resultados es una demostración útil, pero una autorización necesita fallos bien definidos. Las pruebas existentes cubren contenido alterado, claves desconocidas, destinos incorrectos, solicitudes vencidas o futuras, permisos insuficientes e IDs repetidos después de recargar el estado persistido. No constituyen una auditoría integral ni prueban automáticamente interfaces maliciosas, la entrada de terminal o todos los entornos de despliegue.
Lo valioso para quien se autohospeda es poder explicar qué claves se aceptan, qué máquinas están autorizadas, cuándo vale la solicitud, qué permite la política local y qué ocurre con los duplicados. La interfaz de acciones, un firmador independiente y la protección de terminales son mejoras concretas pendientes.
La próxima entrega tratará el despliegue: cómo ejecutar el mismo agente Go en servidores Linux y routers OpenWrt.
Discussion
Share a question or your experience. Comments appear after moderation. Your email is never displayed publicly. Comment privacy
Loading comments…