Cómo configurar un bastión SSH (bastion host)

Un bastión es un servidor pequeño que acepta SSH desde fuera y lo reenvía solo a tus servidores internos. Da a sus usuarios un grupo con AllowTcpForwarding local, PermitOpen con los destinos permitidos, PermitTTY no y ForceCommand /usr/sbin/nologin, dentro de un bloque Match Group, con el reenvío desactivado para todos los demás. Los usuarios se conectan entonces con ssh -J user@bastion user@internal. No pongas DisableForwarding yes para ellos, porque eso también bloquea el salto.

Actualizada el . Esta configuración se ejecutó con OpenSSH 10.0 en Debian 13, usando dos contenedores desechables como bastión y destino.

Cómo debe ser

  • Un servidor pequeño y aparte que ejecute SSH y nada más: sin base de datos, sin sitio web, sin herramientas de desarrollo.
  • Solo claves, con actualizaciones de seguridad automáticas.
  • Sin claves privadas en él. Un salto lo hace innecesario. El inicio de sesión en el servidor interno se cifra desde tu propia máquina, y el bastión solo pasa los datos.
  • La única entrada. Los servidores internos deben aceptar SSH solo desde la dirección del bastión, en el cortafuegos.

Para el resto de ajustes SSH del servidor, consulta cómo reforzar sshd_config.

Crear los usuarios

sudo groupadd jump
sudo useradd -m -G jump -s /bin/bash alice
sudo install -d -m 700 -o alice -g alice /home/alice/.ssh
sudo install -m 600 -o alice -g alice alice.pub /home/alice/.ssh/authorized_keys

Mantén tu propia cuenta de administración fuera del grupo jump, para seguir teniendo un shell.

La configuración de sshd

# /etc/ssh/sshd_config.d/10-bastion.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
AllowAgentForwarding no
AllowTcpForwarding no
AllowStreamLocalForwarding no
X11Forwarding no
PermitTunnel no
GatewayPorts no
LogLevel VERBOSE

Match Group jump
    AllowTcpForwarding local
    PermitOpen 10.0.1.10:22 10.0.1.11:22
    PermitTTY no
    ForceCommand /usr/sbin/nologin

Cada ajuste del bloque tiene su función:

  • AllowTcpForwarding local permite saltos y -L, pero no -R.
  • PermitOpen lista adónde pueden ir. Enumera cada servidor: * puede sustituir a un host o puerto entero, pero no a parte de una dirección como 10.0.1.*.
  • PermitTTY no y ForceCommand quitan el shell. ForceCommand por sí solo no detiene el reenvío, y eso es lo que permite que funcione aquí.

Ponlo en un archivo propio dentro de /etc/ssh/sshd_config.d/, y comprueba antes que ningún otro archivo defina las mismas opciones primero: sshd se queda con el primer valor que lee. Un bloque Match en un archivo así termina donde termina el archivo. Luego comprueba y recarga, dejando una sesión abierta:

sudo sshd -t
sudo sshd -T -C user=alice,host=client,addr=203.0.113.10 | grep -iE '^(allowtcpforwarding|permitopen|permittty|forcecommand) '
sudo systemctl reload ssh
permittty no
allowtcpforwarding local
forcecommand /usr/sbin/nologin
permitopen 10.0.1.10:22 10.0.1.11:22

La ruta de nologin puede ser /sbin/nologin en otros sistemas, así que compruébala con command -v nologin.

DisableForwarding es para otros usuarios

DisableForwarding yes desactiva todo el reenvío y anula los demás ajustes, así que es útil para un grupo que nunca debe reenviar nada. No lo uses para los usuarios de salto. Un salto es un reenvío, y en nuestra prueba falló con stdio forwarding failed.

Conectarse a través de él

ssh -J alice@bastion.example.com user@10.0.1.10

O, para escribir menos, en ~/.ssh/config de tu propia máquina:

Host bastion
    HostName bastion.example.com
    User alice

Host app1
    HostName 10.0.1.10
    User user
    ProxyJump bastion

Después ejecuta ssh app1. Tu clave debe estar tanto en el bastión como en el servidor interno. Hay más sobre el lado del cliente en jump hosts y ProxyJump. Evita ForwardAgent, por los motivos de riesgos del reenvío del agente.

Probar lo que se rechaza

Estos son los resultados que obtuvimos con esta configuración, y deberías ver los mismos:

PruebaResultado
ssh -J alice@bastion user@10.0.1.10Sesión iniciada en el servidor interno
ssh -W 10.0.1.10:80 alice@bastionstdio forwarding failed
ssh -tt alice@bastionPTY allocation request failed on channel 0
ssh alice@bastion idThis account is currently not available.
ssh -N -o ExitOnForwardFailure=yes -R 8080:localhost:22 alice@bastionError: remote port forwarding failed for listen port 8080

Registros: lo que obtienes y lo que no

Ya en el nivel por defecto, cada inicio de sesión se registra con la huella de su clave, lo que te dice quién se conectó. LogLevel VERBOSE registra también las claves rechazadas. Un inicio de sesión se ve así:

Accepted publickey for alice from 203.0.113.10 port 51018 ssh2: ED25519 SHA256:NxFS05Q+k1zf...

Los destinos rechazados también se registran:

Received request from 203.0.113.10 port 51036 to connect to host 10.0.1.10 port 80, but the request was denied.

Los destinos que sí se alcanzan no se registran, ni en INFO ni en VERBOSE. Lo comprobamos: el registro del bastión muestra que alice se conectó, pero no a qué servidor fue después. En nuestra prueba eso solo apareció con LogLevel DEBUG1, y el manual advierte de que los niveles de depuración pueden registrar detalles privados de los usuarios:

debug1: server_request_direct_tcpip: originator 127.0.0.1 port 65535, target 10.0.1.10 port 22

Así que lee ambos lados. El registro del servidor interno muestra quién inició sesión y con qué clave, con la dirección del bastión como origen. Une las dos cosas por la hora, y envía ambos registros a una máquina fuera del bastión, ya que un atacante con root allí podría editar la copia local. Consulta cómo revisar los intentos de inicio de sesión para leer estos registros.

Quitar el acceso

Retira la clave pública del usuario del bastión, y pierde esta ruta hacia todos los servidores que hay detrás. Todavía puede usar cualquier servidor interno al que llegue por otro camino, así que quita también la clave allí. Consulta cómo revocar una clave.

Preguntas

¿Qué diferencia hay entre un bastión y un servidor de salto?

Poca. Un servidor de salto es cualquier servidor por el que pasas para llegar a otro. Un bastión es un servidor de salto preparado y protegido solo para eso, y es la única entrada desde fuera.

¿El bastión necesita mi clave privada?

No. Con ProxyJump, el inicio de sesión en el servidor interno sale de tu propia máquina y va cifrado de extremo a extremo. El bastión solo reenvía la conexión.

¿Por qué ssh -J falla con stdio forwarding failed?

El bastión rechazó el reenvío. Comprueba que AllowTcpForwarding lo permita para tu usuario, que el destino esté en PermitOpen y que DisableForwarding no esté activado.

Fuentes

Más en Túneles y jump hosts

Todas las guías