Cloudflare Access
Login con mi correo en el borde de Cloudflare, antes de que el tráfico entre al túnel. La sesión dura 24 horas.
Sin sesión: redirige al login y nunca aparece el banner de SSH.Acceso remoto seguro a agentes de IA
Tengo varios agentes de IA trabajando en paralelo en mi computador con Linux (Pop!_OS). Frente al computador los manejo con herdr, al menos por ahora. Cuando me alejo, los veo y les respondo desde el celular con Collie. Todo viaja dentro de una conexión SSH que pasa por Cloudflare, y el computador no tiene ningún puerto abierto hacia la red.
Publicado el
Parte 1 · La motivación
Un agente de IA para programar rinde más cuando lo dejas trabajar solo un buen rato. El problema aparece cuando tienes cinco o seis agentes abiertos en proyectos distintos: alguno termina, otro se queda esperando un permiso, y tú no te enteras hasta que revisas terminal por terminal.
herdr es un multiplexor de terminal, de la familia de tmux, pensado para agentes de IA. Un servidor en segundo plano es dueño de las terminales reales. Tú te conectas y desconectas como cliente, y los agentes siguen trabajando.
Cuando estoy frente al computador, herdr es lo que uso, al menos por el momento: lo abro en la terminal y manejo a los agentes desde ahí. Lejos del computador uso Collie, que además sirve en el escritorio como alternativa gráfica.
La idea en una línea: herdr reúne a los agentes, y Collie, el perro pastor, los vigila por ti cuando no estás frente al computador.
herdr.blocked es el que me hizo buscar acceso remoto: el agente está detenido esperando una respuesta mía. Si estoy lejos del computador, ese agente pierde horas.
Parte 2 · La interfaz
Collie es una aplicación web progresiva (PWA) que sirve mi propio computador. Lee los paneles de herdr y los convierte en una interfaz pensada para el dedo: la lista de agentes con su estado, la salida de cada uno y un cuadro para responder.
Podría entrar por SSH desde Termux y abrir herdr directamente en el teléfono. Funciona, pero con un teclado de celular, Esc, Ctrl y las flechas son un suplicio, y una interfaz de terminal no está pensada para una pantalla de seis pulgadas.
Collie no reemplaza a herdr: lo usa por debajo. Lo que escribo en Collie llega al panel de herdr, y lo que pasa en el panel se ve en Collie. Funciona con cualquier agente que corra en un panel.
collie push-keys.Instalarla, recibir notificaciones y dictar por voz requieren un contexto seguro: HTTPS o localhost. Como entro por http://localhost:8787 a través del túnel SSH, el navegador lo trata como seguro.




Capturas del repositorio oficial de Collie (licencia MIT). Muestran agentes de distintos proveedores en el mismo tablero.
Collie no es solo para el celular. En el computador lo abro en el navegador, en http://127.0.0.1:8787, y funciona como una alternativa gráfica a la interfaz de terminal de herdr: la misma lista de agentes, con el mouse, y los cambios de código a un clic.
Hoy, frente al computador, sigo usando herdr en la terminal. Collie queda como opción cuando prefiero una vista más visual, o cuando quiero revisar qué cambió un agente sin abrir otro panel.
| Aspecto | herdr en la terminal | Collie en el navegador |
|---|---|---|
| Dónde | Frente al computador | En el computador o en el celular |
| Control | Teclado y mouse en la terminal | Toque o mouse, con teclas especiales en una bandeja |
| Agentes que esperan | Estado en la barra lateral | Marca roja y contador en el tablero |
| Cambios de código | git en otro panel | Pantalla Files con diff |
| Para entrar | Abrir la terminal | Navegador y dispositivo emparejado |
Parte 3 · El requisito
El atajo habitual es abrir el puerto 22 en el router, o publicar la web de Collie. Cualquier puerto abierto en internet recibe escaneos automáticos a todas horas. Y un notebook cambia de red: lo que escucha en todas las interfaces queda expuesto en cada Wi-Fi donde lo conectes.
Además, Collie corre con mis permisos. Quien controla un panel puede escribir comandos en mi máquina. Exponerlo mal equivale a entregar una terminal.
Parte 4 · La arquitectura
Así llega un toque en la pantalla del celular hasta un agente de IA. El orden importa: cada paso solo ocurre si el anterior dejó pasar la petición.
SSH con la llave privada; la web viaja dentro de SSH.
Pide login antes de dejar pasar. El túnel lo abre el Linux hacia afuera.
/etc/cloudflared y solo root puede leerla.Parte 5 · Defensa en profundidad
Cada barrera usa una credencial distinta. Para controlar un agente, alguien tendría que tener las tres al mismo tiempo.
Login con mi correo en el borde de Cloudflare, antes de que el tráfico entre al túnel. La sesión dura 24 horas.
Sin sesión: redirige al login y nunca aparece el banner de SSH.Solo autenticación por llave pública. Una llave Ed25519 autorizada, un usuario permitido y root bloqueado.
Sin la llave: Permission denied (publickey).Un código de un solo uso, generado en el computador, deja un token en el navegador del celular.
Sin emparejar: la API responde "device not paired".Parte 6 · Paso a paso
Los hostnames, el usuario y los identificadores son ejemplos. Reemplázalos por los tuyos.
cloudflared corre como servicio de systemd y abre una conexión saliente hacia Cloudflare. El ingress publica un solo hostname hacia SSH. Cualquier otra dirección responde 404.
La credencial del túnel vive en /etc/cloudflared con permisos 600 y dueño root. El cert.pem que deja cloudflared tunnel login no se necesita para que el túnel funcione, así que lo saqué del equipo.
tunnel: <id-del-túnel>
credentials-file: /etc/cloudflared/<id-del-túnel>.json # root:root 600
ingress:
- hostname: pc.tu-dominio.dev
service: ssh://localhost:22
- service: http_status:404Es el paso que convierte el túnel en algo seguro. Sin Access, cualquiera con cloudflared llega a tu sshd: solo cambiaste el camino, no quién puede entrar.
curl -sI https://pc.tu-dominio.dev | grep -i location
# location: https://<equipo>.cloudflareaccess.com/cdn-cgi/access/login/...El túnel entrega en localhost:22, así que sshd no necesita escuchar hacia afuera. Las contraseñas ya estaban desactivadas en sshd_config. El resto va en un archivo aparte para que una actualización del paquete no lo pise.
En Ubuntu 24.04, y por lo tanto en Pop!_OS, SSH arranca por activación de socket: ListenAddress recién se aplica después de regenerar el socket.
ListenAddress 127.0.0.1
ListenAddress ::1
PermitRootLogin no
AllowUsers tu-usuario
MaxAuthTries 3
LoginGraceTime 20
X11Forwarding no
AllowAgentForwarding nosudo systemctl daemon-reload
sudo systemctl restart ssh.socket
ss -tln | grep ':22 ' # solo 127.0.0.1:22 y [::1]:22| Directiva | Valor | Por qué |
|---|---|---|
PasswordAuthentication | no | Solo llaves. Una contraseña se puede adivinar; una llave Ed25519, no. |
KbdInteractiveAuthentication | no | Cierra la otra vía por la que el servidor puede pedir una contraseña. |
ListenAddress | 127.0.0.1, ::1 | Solo el túnel llega a SSH. Nadie en la LAN o en el Wi-Fi del café. |
PermitRootLogin | no | root nunca entra directo, ni con llave. |
AllowUsers | tu-usuario | Cualquier otra cuenta del sistema queda fuera, aunque tenga llaves. |
MaxAuthTries | 3 | Corta antes a quien prueba llaves una tras otra. |
LoginGraceTime | 20 | Una conexión que no se autentica se cierra a los 20 segundos. |
X11Forwarding | no | No hay interfaz gráfica que reenviar desde el celular. |
AllowAgentForwarding | no | Nadie puede usar el agente SSH del cliente a través de esta máquina. |
AllowTcpForwarding | yes (por defecto) | Se mantiene: el reenvío hacia Collie depende de esto. |
Al revisar ss -tln aparecieron servicios escuchando en 0.0.0.0, entre ellos el correo local (Postfix), y el firewall estaba apagado. Pasé todo a loopback y encendí UFW con política de rechazo para lo entrante.
Ojo: los puertos que publica Docker con -p se saltan UFW. Si usas contenedores, publícalos en 127.0.0.1:puerto.
sudo postconf -e 'inet_interfaces = loopback-only'
sudo systemctl restart postfix
sudo ufw default deny incoming
sudo ufw enablePor defecto, Collie se publica con Tailscale (tailscale serve). COLLIE_SKIP_SERVE=1 le indica que la entrada la manejas tú, en este caso con SSH, y que no publique nada.
La lista de hosts permitidos viene activa: localhost pasa, y un Host ajeno recibe host not allowed, lo que protege contra DNS rebinding. El emparejamiento se activa en cuanto hay un dispositivo emparejado.
COLLIE_HOST=127.0.0.1
COLLIE_PORT=8787
COLLIE_MUX=herdr
COLLIE_SKIP_SERVE=1systemctl --user restart collie
collie doctor # bind 127.0.0.1 · front-door: SKIP_SERVE
collie pair # código de un solo uso para el celular
collie devices listEn Termux, la terminal para Android, instalé openssh y cloudflared. El bloque de configuración hace todo: pasa por Access, usa la llave y abre el reenvío hacia Collie.
ssh pc. La primera vez del día, cloudflared muestra una URL para iniciar sesión.http://localhost:8787 en el navegador del teléfono.collie pair dentro de esa sesión SSH y escribo el código en la configuración de Collie.Host pc
HostName pc.tu-dominio.dev
User tu-usuario
ProxyCommand cloudflared access ssh --hostname %h
LocalForward 8787 127.0.0.1:8787
ServerAliveInterval 30Parte 7 · Más cosas a considerar
Antes de configurar Access, cualquier computador con cloudflared access ssh --hostname … llegaba al banner de OpenSSH de mi máquina. El túnel evita abrir puertos, pero no pregunta quién eres.
Regla: túnel y Access van siempre juntos.
Por el túnel, sshd ve cada conexión como local. fail2ban no puede distinguir a un atacante de ti, y si bloqueara, bloquearía localhost.
Regla: la identidad y los límites van en el borde, no en el host.
Con activación de socket, ListenAddress solo se aplica con daemon-reload y reiniciando ssh.socket. Además, sudo sshd -t falla con "Missing privilege separation directory: /run/sshd" cuando sshd no está corriendo, y eso corta una cadena con && sin que lo notes.
Regla: comprueba con ss -tln, no leyendo la configuración.
La aplicación de Access protegía pc-ssh.tu-dominio.dev y el túnel publicaba pc.tu-dominio.dev. En el panel todo se veía configurado, pero nada estaba protegido.
Regla: curl -sI tiene que devolver 302 hacia cloudflareaccess.com.
sshd, el correo local y un proxy de base de datos levantado con --address 0.0.0.0 escuchaban en todas las interfaces, con el firewall apagado. En cualquier red pública eran alcanzables sin pasar por Cloudflare.
Regla: loopback por defecto, UFW encendido y ss -tln de vez en cuando.
El archivo que deja cloudflared tunnel login contiene un token de API de la cuenta, con el que se pueden administrar túneles del dominio. El túnel no lo necesita para correr. Borrar el archivo no revoca el token.
Regla: sácalo del equipo y revoca el token si no lo vas a usar.
Parte 8 · Confiar, pero comprobar
Esto es lo que quedó escuchando en el computador. Nada sale de loopback, salvo el DNS de las máquinas virtuales de libvirt, en una interfaz que está apagada.
LISTEN 127.0.0.1:22 sshd (vía ssh.socket)
LISTEN [::1]:22 sshd
LISTEN 127.0.0.1:8787 collie
LISTEN 127.0.0.1:20241 cloudflared (métricas)
LISTEN 127.0.0.1:25 postfix
LISTEN 127.0.0.1:631 cups| Prueba | Resultado esperado |
|---|---|
curl -sI https://pc.tu-dominio.dev | 302 hacia el login de Access |
ssh por el túnel, sin sesión | Pide login en el navegador; no aparece el banner de OpenSSH |
ssh sin llave | Solo ofrece publickey |
sudo ufw status verbose | deny (incoming), sin reglas abiertas |
collie doctor | bind 127.0.0.1 y emparejamiento activo |
curl -H 'Host: otro.com' 127.0.0.1:8787/api/config | host not allowed |
| La misma llamada, sin token | device not paired |
Parte 9 · Sin vender humo
sudo, ~/.ssh, mis repositorios..env. Abro workspaces por repositorio, nunca en ~.PermitOpen 127.0.0.1:8787, para que SSH solo pueda reenviar hacia Collie.collie devices set-expiry <dispositivo> 30d.collie devices revoke <dispositivo>~/.ssh/authorized_keys.Cada paso cierra una barrera distinta, así que conviene hacer los tres.
Parte 10 · Para seguir leyendo