Casi todos los proyectos que entregamos acaban necesitando lo mismo, algo poco glamuroso: llevar un artefacto de compilación desde CI/CD hasta un servidor y ejecutarlo. Nadie reserva tiempo para esto — «es solo el despliegue» —, así que suele resolverse una vez, bajo presión de plazos, con lo primero que se puede escribir rápido. Normalmente es una clave privada SSH metida en los secretos de CI y un script de shell que copia un archivo por SCP y reinicia un servicio. Funciona. También deja de escalar en cuanto hay un segundo proyecto, y nosotros reconstruimos una versión ligeramente distinta de lo mismo para cada cliente hasta que por fin nos sentamos a escribir la herramienta que de verdad queríamos: Orosu.
Del japonés 降ろす (órosu) — «descargar», «desembarcar».
Orosu es un pequeño servicio en Rust, orosu-server, que se instala una sola vez en una máquina. En lugar de que CI tenga una clave SSH capaz de abrir una shell, CI tiene una clave Ed25519 capaz de disparar exactamente uno de un conjunto fijo de scripts que el dueño de la máquina definió de antemano, a través de una conexión WebSocket autenticada. Este artículo trata sobre por qué esa diferencia justifica construir toda una herramienta alrededor, y cómo se ve en la práctica.
Ideas clave
- El problema no es SSH en sí — es lo que implica el acceso SSH. Una clave SSH que puede iniciar sesión para ejecutar un script de despliegue puede ejecutar cualquier cosa. Toda credencial que llega a una máquina de producción hereda el radio de impacto completo de una shell, la necesite o no.
- Orosu reduce eso a un conjunto cerrado de scripts predefinidos. CI elige un script por nombre y pasa argumentos; no puede enviar un comando arbitrario, sin importar en qué se convierta el pipeline si lo comprometen.
- La autenticación es una firma, no un secreto que viaja por la conexión. Cada tarea se firma con la clave Ed25519 del cliente. No hay contraseña ni secreto compartido que un atacante pueda interceptar en tránsito.
- Es un único binario en Rust sin dependencias en tiempo de ejecución — se instala vía apt o con un binario precompilado de GitHub Releases, detrás del proxy inverso que ya termina TLS en esa máquina.
- Una capa opcional de cifrado de extremo a extremo cubre el único hueco que deja el TLS terminado en el proxy: mantener confidenciales los argumentos del script y la salida frente a ese mismo proxy.
- Orosu es open source bajo licencia Apache-2.0 — lo usamos nosotros mismos, y preferiríamos que otros equipos dejaran de reinventar esto.
La forma del problema
Quítale el envoltorio a «desplegar desde CI» y siempre es la misma petición: ejecutar este script en esa máquina con estos archivos. La manera por defecto en que la mayoría de equipos resuelve esa petición acumula riesgo siguiendo un patrón concreto y predecible:
- Una clave privada SSH acaba en los secretos de CI. Normalmente no está limitada a un script — da acceso a una shell completa, porque restringir SSH a «solo este comando» es lo bastante engorroso como para que casi nadie lo haga de verdad.
- Esa misma clave, o una copia casi idéntica, se reutiliza en todos los repositorios y pipelines que necesitan llegar a ese servidor, porque generar y rotar una clave distinta por pipeline es más proceso del que nadie quiere asumir.
- El script de despliegue se copia y pega del proyecto anterior, se acumulan pequeñas diferencias, y ya nadie recuerda cuál es la versión de referencia.
- La producción queda directamente accesible desde los runners de CI — precisamente el tipo de host que un atacante busca, porque un runner comprometido con acceso SSH ya es un servidor comprometido.
- El día en que una clave sí se filtra, rotarla significa encontrar todos los pipelines que podrían referenciarla, porque nunca hubo un registro — solo secretos dispersos en tantos repositorios como hayan acumulado una copia.
Nada de esto es un problema de habilidad. Es la forma por defecto de «ejecutar un script en una máquina remota desde un pipeline» cuando la herramienta a la que se recurre es una shell remota de propósito general. SSH no está mal para iniciar sesión y depurar una máquina a mano. Es el primitivo equivocado para una tarea de CI que solo necesita disparar los mismos tres o cuatro scripts de despliegue.
Lo que realmente queríamos
Antes de escribir una línea de código, los requisitos tenían menos que ver con criptografía y más con lo que una credencial de despliegue debería poder hacer:
- Una credencial de CI no debería poder abrir una shell. Debería poder disparar un script, por nombre, de una lista que controla el dueño del servidor — y punto.
- La autenticación no debería depender de un secreto que viaja en cada petición. Una firma demuestra la posesión de una clave sin exponerla.
- Añadir un nuevo destino de despliegue no debería significar generar un nuevo par de claves SSH y encajarlo a mano en la configuración del servidor — debería ser una entrada de configuración y un comando
keygen. - El servidor debe ser la fuente de verdad sobre qué puede ejecutarse, no el pipeline. Una tarea de CI comprometida o descuidada nunca debería poder ampliar sus propios permisos.
Esa lista es un protocolo WebSocket, no un SSH disfrazado de otra cosa — que es exactamente en lo que se convirtió Orosu.
Cómo funciona
orosu-server se ejecuta en la máquina de destino. Escucha conexiones WebSocket, verifica una firma Ed25519 en cada tarea entrante, comprueba el nombre del script contra la lista permitida configurada para ese cliente y, solo si todo cuadra, ejecuta el script con los argumentos y archivos adjuntos que trajera la tarea.
listen:
tcp: "127.0.0.1:8081"
clients:
- name: my-ci-client
secret_file: /etc/orosu/my-ci-client.pub
scripts:
- name: deploy
command:
- "bash"
- "/etc/orosu/scripts/deploy.sh"
Esa lista scripts: es todo el modelo de seguridad en un solo lugar. my-ci-client puede disparar deploy y nada más — ni otro script en la misma máquina, ni un comando de shell arbitrario, ni un script de otro cliente. Si el pipeline de CI que tiene la clave de este cliente se compromete, el atacante hereda exactamente la capacidad de un script de despliegue, no un inicio de sesión.
El par de claves de un cliente se genera con orosu-keygen:
orosu-keygen --name my-ci-client --private-key-output my-ci-client.key --public-key-output my-ci-client.pub
La mitad pública va en la configuración del servidor de arriba; la privada se convierte en un secreto de CI, usado solo para firmar peticiones — nunca concede una sesión por sí sola. Disparar el script desde GitHub Actions es la Action complementaria:
- name: Deploy
uses: orosu-ci/orosu@v0
with:
address: ${{ secrets.OROSU_SERVER_URL }}
script: deploy
key: ${{ secrets.OROSU_CLIENT_KEY }}
arguments: ${{ github.sha }}
Ese es todo el cambio en el pipeline: un paso de SSH se convierte en un paso de Orosu, y la superficie del lado del servidor se reduce de «lo que sea que pueda hacer la shell de esta clave» a «este script concreto, con nombre».
orosu-server en sí normalmente escucha en localhost y está detrás de un proxy inverso que termina TLS y reenvía las actualizaciones de WebSocket — nginx, en el caso habitual, la misma máquina que probablemente ya hace esto para lo que sea que el script de despliegue reinicia.
Por qué no limitar una clave SSH con command=
SSH sí soporta técnicamente una restricción command= en authorized_keys, y nos preguntan a menudo por qué Orosu no es simplemente eso. Dos razones por las que se queda corto en la práctica, ambas sobre lo que pasa alrededor del camino feliz y no sobre el camino feliz en sí:
- Es configuración manual del servidor, por clave, invisible para quien escribe el pipeline. Nada garantiza que cada clave de despliegue en
authorized_keyslleve de verdad una restriccióncommand=— el archivo no tiene esquema, y una clave sin restringir junto a otras nueve sí restringidas se ve idéntica a simple vista. - Una sesión SSH restringida sigue hablando el idioma de una shell. Dependiendo del script y de cómo
command=interactúe con los argumentos que reenvía SSH, la frontera entre «ejecutar esto» y «ejecutar esto, con argumentos donde se puede inyectar algo» es fácil de cruzar sin darse cuenta. Los argumentos de script en Orosu son campos del protocolo, no tokens de shell montados a partir de una línea de comandos SSH — no hay ninguna shell de la que un argumento pueda escaparse.
Orosu no afirma que SSH sea inseguro. Afirma que la versión segura de «restringir SSH a un solo comando» exige suficiente ceremonia adicional como para que casi nadie la aplique de forma consistente, y que una herramienta cuyo único trabajo es ejecutar uno de unos pocos scripts predefinidos puede hacer de eso el comportamiento por defecto en lugar de una opción que hay que activar.
Confidencialidad más allá del proxy inverso
WSS cubre la conexión, pero cuando TLS se termina en un proxy inverso frente a orosu-server — la configuración estándar de arriba —, los argumentos del script, los archivos subidos y la salida quedan en texto plano en ese proxy. Para la mayoría de scripts de despliegue eso no es un problema; para algunos sí lo es (un argumento de despliegue puede ser una credencial que el script reenvía, un token de rollback, un identificador de cliente). La respuesta de Orosu es una capa opcional de cifrado de extremo a extremo — X25519 para el acuerdo de claves, HKDF-SHA256 para derivar las claves de sesión, ChaCha20-Poly1305 para cifrar — que se monta sobre el transporte WebSocket y se activa de forma independiente en cada extremo:
orosu-keygen --kind server --private-key-output server.key --public-key-output server.pub
La clave pública del servidor se añade a su configuración y al parámetro server_key de la Action de GitHub. Un servidor con el cifrado configurado sigue sirviendo exactamente igual que antes a los clientes que omiten server_key — no hace falta una migración coordinada, ni un día de corte, ni se rompe nada para un cliente que todavía no se ha actualizado.
Refuerzos de seguridad, con honestidad
Tratamos esto como parte de primer orden de la herramienta, no como una casilla que marcar. Algunos detalles concretos, porque «nos importa la seguridad» es una afirmación que preferimos respaldar con lo que realmente cambió: la versión 0.7.0 añadió verificaciones contra ataques de low-order-point en el intercambio X25519 (cerrando una clase conocida de ataque sobre curvas elípticas), reforzó la extracción de adjuntos para que un zip manipulado o desmesurado no pueda salirse de su directorio de extracción ni agotar el disco por descompresión, e hizo que orosu-keygen escriba los archivos de claves privadas con permisos 0600 sin importar el umask vigente. Las tres llegaron como una actualización directa — misma configuración, mismo protocolo, mismo CLI. El historial completo está en CHANGELOG.md.
Lo que Orosu no es
El alcance limitado aquí es una decisión de diseño, no una carencia, pero merece decirse con claridad. Orosu no orquesta despliegues progresivos, no gestiona rollbacks ni entiende la topología de tu despliegue — ejecuta un script que tú ya escribiste, y es ese script el que decide qué significa desplegar. No es una herramienta nativa de Kubernetes: si tu destino ya es un clúster con un controlador de despliegue de verdad, ese controlador es casi con toda seguridad la mejor opción, y Orosu estaría resolviendo un problema que ya no tienes. Donde encuentra su sitio es en la máquina que no es un nodo de clúster — el VPS suelto, el servidor físico, ese servidor «algún día lo containerizamos» sobre el que todavía corre parte del stack en muchas agencias y startups.
Primeros pasos
orosu-server y orosu-keygen se distribuyen como paquetes de Debian/Ubuntu:
curl -fsSL https://packages.nerdy.pro/NerdyPro.gpg | sudo gpg --dearmor -o /usr/share/keyrings/nerdy-pro.gpg
echo "deb [signed-by=/usr/share/keyrings/nerdy-pro.gpg] https://packages.nerdy.pro/ stable main" | sudo tee /etc/apt/sources.list.d/nerdy-pro.list
sudo apt update
sudo apt install orosu
GitHub Releases también publica binarios precompilados para máquinas donde no se puede añadir un repositorio apt. La guía completa de configuración — generar claves, escribir la configuración del servidor, conectar la GitHub Action — está en la página del proyecto.
Orosu está escrito en Rust, el mismo lenguaje detrás de dxpdf, nuestro motor de conversión de DOCX a PDF — es donde recurrimos cuando una herramienta tiene que ser rápida, predecible y segura frente a una entrada que no es del todo confiable, como un archivo adjuntado por un cliente que llega por la red.
Preguntas frecuentes
¿Quieres dejar de conectarte por SSH al servidor donde despliegas?
Para eso exactamente sirve Orosu. Lee la documentación completa del proyecto para la configuración detallada, echa un vistazo al resto de nuestro trabajo open source, o contáctanos si quieres que revisemos cómo tu equipo lleva código a producción — los pipelines de despliegue construidos bajo presión de plazos son un hallazgo concreto y habitual en una auditoría de código IA, y Orosu es la solución a la que recurrimos tanto como la recomendamos.
Ilya Nixan es fundador y lead developer en Nerdy Production, una agencia Flutter-first que además construye y mantiene herramientas de infraestructura — como Orosu y dxpdf — sobre las que corre su propia entrega de proyectos.

