Teleport con Docker: Bastión SSH + Agente

Docker + certificado autofirmado + MFA/OTP + acceso SSH auditado

Guía técnica para desplegar Teleport como bastión SSH en Docker y unir un segundo servidor como agente/nodo, con login local y segundo factor.

1. Introducción

Esta guía despliega Teleport como bastión SSH usando Docker en un primer servidor, y une un segundo servidor como agente/nodo para poder conectarse a él por SSH a través del bastión, con usuarios nominales, MFA y sesiones auditadas.

El montaje utiliza certificado autofirmado (sin dominio) para simplificar el laboratorio. La sección 19 explica en detalle por qué aparece el aviso de certificado no confiable y qué cambia en un entorno de producción.

2. Variables de esta guía

Todos los comandos usan los siguientes marcadores en MAYÚSCULAS. Sustitúyelos por los valores reales de tu propia red antes de ejecutar cada bloque — tu IP, tu nombre de nodo y tus tokens serán distintos a los de este ejemplo.

MarcadorQué esDónde se obtiene
IP_BASTION_WANIP del bastión en la red desde la que te conectas (navegador / tsh)La IP de esa interfaz de red en tu servidor bastión
IP_BASTION_LANIP del bastión en la red donde están los servidores a protegerLa IP de la segunda interfaz de tu servidor bastión
NOMBRE_NODOHostname con el que el nodo se registra en TeleportLo defines tú al instalar el agente (por defecto, el hostname del sistema)
LOGIN_SISTEMAUsuario del sistema operativo que ya existe en el nodoEl usuario con el que sueles entrar por SSH a esa máquina
TOKEN_NODOToken de invitación de un solo uso, caduca en el tiempo indicadoLo genera el bastión con tctl tokens add
CA_PINHuella (fingerprint) de la CA del clúster TeleportSe muestra junto al token al generarlo
VERSION_TELEPORTVersión exacta de Teleport instalada en el bastiónSe consulta con docker exec teleport teleport version
Usa la misma versión exacta de Teleport en el bastión y en el nodo. Mezclar versiones distintas puede impedir el registro del agente.

3. Arquitectura y flujo

Resumen visual
[Red de acceso]  IP_BASTION_WAN
        │
        ▼  https://IP_BASTION_WAN:3080  (UI web + tsh)
[BASTIÓN — Docker]
  Teleport auth + proxy
  Interfaz de acceso: IP_BASTION_WAN
  Interfaz hacia servidores: IP_BASTION_LAN
        │
        ▲  el nodo se conecta hacia IP_BASTION_LAN:3025 (auth service)
[Red de servidores]
  NODO / AGENTE (NOMBRE_NODO)
  No necesita puertos entrantes

El nodo inicia la conexión hacia el bastión (túnel saliente), así que no hace falta abrir ningún puerto entrante en el servidor que se quiere proteger. El cliente (navegador o tsh) siempre entra por IP_BASTION_WAN, y el nodo siempre se une usando IP_BASTION_LAN.

4. Requisitos

  • Dos servidores Ubuntu Server (22.04 o 24.04) actualizados.
  • Servidor bastión con Docker Engine y Docker Compose plugin instalados (docker compose version).
  • Acceso sudo/root en ambos servidores.
  • El servidor bastión debe ser alcanzable, con al menos una dirección accesible para el cliente (IP_BASTION_WAN) y una dirección alcanzable desde la red del nodo (IP_BASTION_LAN). Si solo tienes una IP, usa la misma dirección en ambos casos.
  • Puertos 3080/tcp y 3025/tcp abiertos en el firewall del bastión.

5. Estructura de carpetas (en el bastión)

Crear carpeta de trabajo
mkdir -p /docker/teleport/data
cd /docker/teleport

6. Configurar teleport.yaml

Crear el fichero de configuración
nano /docker/teleport/teleport.yaml
Contenido de teleport.yaml
version: v3
teleport:
  nodename: bastion
  data_dir: /var/lib/teleport
  log:
    output: stderr
    severity: INFO

auth_service:
  enabled: true
  listen_addr: 0.0.0.0:3025
  proxy_listener_mode: multiplex
  authentication:
    type: local
    second_factor: otp
    local_auth: true

proxy_service:
  enabled: true
  web_listen_addr: 0.0.0.0:3080
  public_addr:
    - "IP_BASTION_WAN:3080"
    - "IP_BASTION_LAN:3080"

ssh_service:
  enabled: false
proxy_listener_mode se declara dentro de auth_service, no en proxy_service. Al no indicar ningún certificado propio, Teleport genera uno autofirmado válido para las direcciones listadas en public_addr: por eso conviene incluir tanto la IP de acceso como la IP hacia la red de servidores.

7. Configurar docker-compose.yml

Crear el fichero de Compose
nano /docker/teleport/docker-compose.yml
Contenido de docker-compose.yml
services:
  teleport:
    image: public.ecr.aws/gravitational/teleport-distroless:18
    container_name: teleport
    restart: unless-stopped
    ports:
      - "3080:3080"
      - "3025:3025"
    volumes:
      - ./data:/var/lib/teleport
      - ./teleport.yaml:/etc/teleport/teleport.yaml:ro
La imagen distroless de Teleport busca la configuración en /etc/teleport/teleport.yaml (con subcarpeta). No es necesario indicar ningún command: el entrypoint de la imagen ya arranca Teleport con esa configuración.

8. Firewall del bastión

Reglas ufw
sudo ufw allow 3080/tcp
sudo ufw allow 3025/tcp
sudo ufw allow OpenSSH
sudo ufw enable
sudo ufw status
Si el bastión separa el tráfico por interfaz o red, restringe el origen de cada regla a la red correspondiente (acceso por IP_BASTION_WAN y unión de nodos por IP_BASTION_LAN) en lugar de abrir los puertos a cualquier origen.

9. Levantar el contenedor

Arrancar Teleport
cd /docker/teleport
docker compose up -d
docker compose logs -f teleport
Arranque correcto cuando aparecen líneas como Auth service is starting, Starting web proxy service y Starting SSH proxy service sin errores posteriores.

10. Crear el usuario administrador

Alta del primer usuario
docker exec teleport tctl users add admin \
  --roles=editor,access,auditor \
  --logins=LOGIN_SISTEMA

El comando devuelve una URL de invitación con este formato:

URL de invitación (ejemplo de formato)
https://IP_BASTION_WAN:3080/web/invite/TOKEN_DE_INVITACION
Ábrela en el navegador, acepta el aviso de certificado autofirmado, define la contraseña y escanea el código QR con Google Authenticator o Authy para registrar el segundo factor (OTP).

11. Instalar el cliente tsh

Windows

Descarga el instalador desde https://goteleport.com/download y ejecútalo.

Linux / macOS

Instalar tsh (misma versión que el bastión)
curl https://goteleport.com/static/install.sh | sudo bash -s VERSION_TELEPORT
tsh version

12. Instalar Teleport en el nodo

Consultar la versión exacta del bastión
docker exec teleport teleport version
Instalar esa misma versión en el nodo
curl https://goteleport.com/static/install.sh | sudo bash -s VERSION_TELEPORT
teleport version

13. Generar el token de unión

Generar token (válido 1 hora) — ejecutar en el bastión
docker exec teleport tctl tokens add --type=node --ttl=1h

La salida incluye el token y el ca-pin (huella de la CA) que se usan en el siguiente paso. Anota ambos valores.

El token caduca en el tiempo indicado. Si tarda más en generar uno nuevo antes de usarlo.

14. Configurar el agente (en el nodo)

Generar la configuración inicial
sudo teleport node configure \
  --output=file:///etc/teleport.yaml \
  --proxy=IP_BASTION_LAN:3080 \
  --token=TOKEN_NODO \
  --ca-pin=CA_PIN

Después, edita el fichero generado y sustituye proxy_server por auth_server apuntando al puerto 3025:

Editar la configuración del nodo
sudo nano /etc/teleport.yaml
Contenido final de /etc/teleport.yaml (nodo)
version: v3
teleport:
  nodename: NOMBRE_NODO
  data_dir: /var/lib/teleport
  join_params:
    token_name: TOKEN_NODO
    method: token
  auth_server: IP_BASTION_LAN:3025
  log:
    output: stderr
    severity: INFO
    format:
      output: text
  ca_pin: CA_PIN
  diag_addr: ""
auth_service:
  enabled: "no"
ssh_service:
  enabled: "yes"
proxy_service:
  enabled: "no"
  https_keypairs: []
  https_keypairs_reload_interval: 0s
  acme: {}
Usar auth_server en el puerto 3025 en lugar de proxy_server en el puerto 3080 evita el error de validación TLS del certificado autofirmado: el registro del nodo se autentica mediante el token y el ca_pin, no mediante una cadena de certificados HTTPS confiable. Más detalle en la sección 19.

15. Arrancar y verificar el nodo

Arrancar el servicio (en el nodo)
sudo systemctl enable teleport --now
sudo systemctl status teleport
Verificar el registro (en el bastión)
docker exec teleport tctl nodes ls
El nodo debe aparecer en la lista con el hostname NOMBRE_NODO. En los logs del nodo, la señal de éxito es Successfully registered instance client seguido de SSH Service is starting.

16. Dar acceso SSH al usuario

Asociar el login del sistema al usuario Teleport
docker exec teleport tctl users update admin --set-logins=LOGIN_SISTEMA
LOGIN_SISTEMA debe ser un usuario que ya exista en el nodo (por ejemplo, el que usas habitualmente para entrar por SSH a esa máquina).

17. Probar la conexión de extremo a extremo

Login, listado y conexión SSH desde el cliente
tsh login --proxy=IP_BASTION_WAN:3080 --insecure --user=admin
tsh ls
tsh ssh LOGIN_SISTEMA@NOMBRE_NODO

También puedes conectarte desde la interfaz web abriendo https://IP_BASTION_WAN:3080, localizando el nodo en Resources y pulsando Connect.

Si obtienes el prompt del nodo, la instalación está completa y funcionando de extremo a extremo.

18. Chuleta de comandos

Bastión
docker compose -f /docker/teleport/docker-compose.yml ps
docker compose -f /docker/teleport/docker-compose.yml logs -f teleport

docker exec teleport tctl status
docker exec teleport tctl users ls
docker exec teleport tctl users add USUARIO --roles=access,editor,auditor --logins=LOGIN_SISTEMA
docker exec teleport tctl users update USUARIO --set-logins=LOGIN_SISTEMA
docker exec teleport tctl users rm USUARIO

docker exec teleport tctl nodes ls
docker exec teleport tctl tokens add --type=node --ttl=1h
Cliente (tsh)
tsh login --proxy=IP_BASTION_WAN:3080 --insecure --user=USUARIO
tsh ls
tsh ssh LOGIN_SISTEMA@NOMBRE_NODO
tsh logout

19. Por qué usamos "--insecure" y certificado autofirmado

Al no configurar un dominio con Let's Encrypt, Teleport genera automáticamente un certificado autofirmado para la interfaz web. Ese certificado no está emitido por una autoridad reconocida por el sistema operativo ni por el navegador, así que ambos avisan de "certificado no confiable".

  • El flag --insecure en tsh le indica que continúe la conexión aunque no pueda validar la cadena de certificación, algo razonable en un laboratorio controlado, pero desaconsejable en producción porque expone la conexión a un posible ataque de intermediario si el atacante puede interceptar el tráfico.
  • El registro de nuevos nodos, en cambio, no depende de --insecure: se protege con el token de invitación (de un solo uso y caducidad corta) y el ca-pin (huella criptográfica de la CA del clúster), que el nodo verifica antes de confiar en el bastión.
  • Por eso el nodo se une usando el puerto del auth service (3025) en lugar del proxy web (3080): evita la validación de certificado HTTPS del navegador, pero mantiene la autenticación fuerte mediante token + ca-pin.
En producción, la recomendación es usar un dominio propio con Let's Encrypt (Teleport lo gestiona de forma nativa con ACME). Así desaparecen los avisos de certificado y deja de ser necesario el flag --insecure.

20. Errores comunes y cómo resolverlos

ProblemaCausa probableSolución
unexpected teleport / unexpected start Se indicó un command en el compose que repite el entrypoint de la imagen Eliminar la línea command del docker-compose.yml
path '/etc/teleport/teleport.yaml' does not exist El volumen se montó en /etc/teleport.yaml en lugar de la ruta con subcarpeta Montar el volumen en /etc/teleport/teleport.yaml
field proxy_listener_mode not found in type config.Proxy El campo se colocó en proxy_service Mover proxy_listener_mode a auth_service
cannot disable multi-factor authentication Se intentó usar second_factor: off Usar second_factor: otp
missing required webauthn configuration Se usó second_factor: optional sin configurar WebAuthn Usar second_factor: otp
x509: certificate signed by unknown authority al unir el nodo El nodo intenta unirse por el proxy web (3080) y rechaza el certificado autofirmado Unir el nodo usando auth_server en el puerto 3025
Failed to launch: user: unknown user ... El login configurado en Teleport no existe como usuario del sistema en el nodo Ejecutar tctl users update USUARIO --set-logins=LOGIN_SISTEMA con un usuario real del nodo
El desplegable de logins en la UI web muestra un valor antiguo Caché del navegador o sesión activa Recargar con caché vacía o cerrar sesión y volver a entrar

21. Notas para producción

Este montaje es válido para laboratorio y demostraciones. Para un entorno de producción se recomienda:

  • Dominio propio + Let's Encrypt en proxy_service, para eliminar el certificado autofirmado y el flag --insecure.
  • Exponer Teleport detrás de una VPN o túnel en lugar de exposición directa a internet, cuando sea posible.
  • Sustituir OTP por WebAuthn / llave de hardware, o por SSO corporativo (Google Workspace, GitHub, SAML), para revocación instantánea de acceso.
  • Revisar periódicamente el audit log y la grabación de sesiones SSH, incluidos en Teleport Community.
  • Restringir en el firewall de cada nodo el acceso SSH directo, dejando la conexión solo a través del bastión.