Problema
Los administradores de servidores suelen necesitar acceso rápido a shells y a transferencia de archivos desde cualquier dispositivo, sin instalar clientes locales. Una solución basada en navegador simplifica la gestión, pero al mismo tiempo introduce una superficie de ataque: el servicio maneja credenciales SSH, mantiene sesiones activas y, si se expone sin protección, puede convertirse en un punto de entrada para atacantes. El reto es lanzar un workspace WebSSH que sea fácil de desplegar en un homelab y, al mismo tiempo, cumplir con buenas prácticas de seguridad para entornos internos y para instalaciones accesibles desde Internet.
Causa
- Credenciales en texto plano – Los contenedores suelen montar volúmenes con claves privadas sin cifrar o almacenar contraseñas en variables de entorno.
- Verificación de host‑key débil – Si el servicio acepta cualquier clave del servidor remoto, se pierde la protección contra ataques MITM.
- Exposición directa del puerto – Publicar el contenedor en
0.0.0.0:80permite escaneos automáticos y fuerza bruta. - Configuración por defecto del proxy – Un reverse proxy mal configurado puede omitir cabeceras de seguridad (HSTS, CSP) y dejar cookies sin
Secure/HttpOnly. - Falta de aislamiento de red – Ejecutar el contenedor con privilegios de root o en la red del host brinda al proceso acceso a recursos críticos.
- Auditoría insuficiente – Sin logs estructurados ni limitación de velocidad, los intentos de acceso no se detectan ni se bloquean.
Solución
1. Despliegue aislado con Docker Compose
Utiliza un docker-compose.yml que:
- Ejecute el contenedor como usuario no root (
user: 1000:1000). - Monte únicamente los directorios necesarios (
/datapara backups,/configpara claves cifradas). - Defina una red interna (
webssh_net) y exponga solo el puerto del proxy.
2. Terminación TLS con un reverse proxy
Coloca Nginx (o Caddy) delante del contenedor:
- Fuerza HTTPS con certificados de Let’s Encrypt o autosignados para pruebas.
- Habilita
Strict-Transport-Security,X-Content-Type-Options,X-Frame-OptionsyContent-Security-Policy. - Configura
proxy_set_headerpara pasarHost,X-Real-IPyX-Forwarded-Proto. - Aplica
limit_req_zoneylimit_reqpara limitar la tasa de peticiones a la ruta de login.
3. Cifrado de credenciales
WebSSH permite almacenar claves privadas en una base de datos cifrada. Configura:
- Variable
WEBSSH_ENCRYPTION_KEYcon una clave aleatoria de 32 bytes (generada conopenssl rand -hex 32). - Almacena la variable en un Docker secret o en un archivo montado bajo
/run/secrets.
4. Verificación estricta de host‑key
Activa la opción known_hosts por usuario y desactiva la aceptación automática de nuevas claves (--no-auto-accept-hostkey). Esto obliga a que el administrador añada manualmente la huella del servidor antes de la primera conexión.
5. Autenticación externa
Cuando el entorno lo permite, integra OIDC, LDAP o Active Directory. De lo contrario, habilita:
- Passkeys (WebAuthn) para MFA.
- TOTP como segundo factor.
- Códigos de recuperación almacenados cifrados.
6. Auditoría y limitación
- Configura
audit_log_pathpara que WebSSH escriba en/var/log/webssh/audit.log. - Usa
fail2bancon una regla que monitoree el archivo de log y bloquee IPs después de 5 intentos fallidos. - Habilita
rate_limitinterno (por ejemplo, 10 intentos por minuto por IP).
7. Copias de seguridad y restauración
- Programa un
crondentro del contenedor o en el host que copie/configy la base de datos a un volumen externo. - Documenta el proceso de restauración: detener el contenedor, reemplazar los archivos y volver a iniciar.
Cuándo aplicar esta solución
- Entornos internos (homelab): basta con Docker Compose + Nginx con TLS interno. La verificación de host‑key y el cifrado de claves siguen siendo críticos.
- Instalaciones expuestas a Internet: agrega OIDC/LDAP, fuerza HSTS y habilita
fail2ban. Limita el acceso a rangos IP conocidos mediante reglas de firewall (iptablesoufw). - Equipos con requisitos de cumplimiento: usa certificados de autoridad interna, almacena la clave de cifrado en un secret manager (HashiCorp Vault, AWS Secrets Manager) y habilita auditoría centralizada.
No es necesario aplicar todas las capas en un entorno de pruebas rápido; sin embargo, omitir TLS o la verificación de host‑key deja el servicio vulnerable a ataques triviales.
Código
cat > docker-compose.yml <<'EOF'
version: "3.8"
services:
webssh:
image: bifrost0x/webssh:latest
restart: unless-stopped
user: "1000:1000"
environment:
- WEBSSH_ENCRYPTION_KEY_FILE=/run/secrets/webssh_key
- WEBSSH_HOSTKEY_VERIFICATION=strict
secrets:
- webssh_key
volumes:
- webssh_data:/app/data
- webssh_config:/app/config
networks:
- webssh_net
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- webssh
networks:
- webssh_net
secrets:
webssh_key:
file: ./secrets/webssh_enc_key.txt
volumes:
webssh_data:
webssh_config:
networks:
webssh_net:
driver: bridge
EOF
cat > nginx.conf <<'EOF'
events { worker_connections 1024; }
http {
server {
listen 443 ssl;
server_name webssh.example.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header Content-Security-Policy "default-src 'self'";
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
location / {
proxy_pass http://webssh:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /login {
limit_req zone=login burst=10 nodelay;
proxy_pass http://webssh:8080/login;
}
}
}
EOF
Verificación
- Conexión TLS – Usa
curl -v https://webssh.example.comy verifica que el certificado sea el esperado y que la cabeceraStrict-Transport-Securityesté presente. - Login exitoso – Accede desde el navegador, ingresa credenciales válidas y confirma que la sesión se abre en una pestaña.
- Host‑key – Conecta a un servidor nuevo; la UI debe solicitar la huella y rechazar la conexión si no se aprueba.
- Rate limiting – Desde una terminal, ejecuta 10 peticiones POST a
/loginen menos de un minuto; la última debe devolver429 Too Many Requests. - Fail2ban – Simula 6 intentos fallidos; revisa
/var/log/fail2ban.logy confirma que la IP quedó en la listawebssh‑ssh.
Notas adicionales
- Rotación de claves: cambia
WEBSSH_ENCRYPTION_KEYcada 90 días y vuelve a encriptar la base de datos con la herramienta incluida (webssh-cli rekey). - Actualizaciones: antes de actualizar la imagen, exporta la base de datos (
docker exec webssh pg_dump) y prueba la nueva versión en un entorno de staging. - Monitorización: integra los logs de Nginx y WebSSH en un stack ELK o Loki para detectar patrones de acceso inusuales.
- Redundancia: en entornos críticos, replica el contenedor detrás de un balanceador de carga y comparte el mismo volumen de configuración mediante NFS o GlusterFS.
Con este enfoque, cualquier administrador de homelab o de pequeña oficina puede disponer de un workspace WebSSH funcional, mantener el control total de sus credenciales y minimizar los riesgos asociados a la exposición de un servicio de acceso remoto.