Día 07 - Configurar autenticación SSH sin contraseña (password-less)
Problema / Desafío
Configurar autenticación SSH sin contraseña para el usuario thor desde el servidor jump box hacia todos los app servers, usando el sudo user correspondiente de cada servidor:
| Jump Box | App Server | Sudo User |
|---|---|---|
| thor | stapp01 | tony |
| thor | stapp02 | steve |
| thor | stapp03 | banner |
El objetivo es que thor pueda conectarse por SSH a cada app server sin ingresar contraseña, usando llaves SSH (key-based authentication).
Conceptos clave
- Autenticación por llave SSH: Mecanismo que usa un par de llaves criptográficas (pública y privada) para autenticar al usuario sin necesidad de contraseña. La llave privada permanece en el cliente y la pública se copia al servidor.
- Llave privada (
~/.ssh/id_rsa): Archivo secreto que solo debe existir en la máquina del usuario. Nunca se comparte. Es el equivalente a la contraseña, pero criptográfica. - Llave pública (
~/.ssh/id_rsa.pub): Archivo que se copia a los servidores remotos. Cualquiera puede verla sin riesgo de seguridad. Se almacena en el archivo~/.ssh/authorized_keysdel usuario remoto. authorized_keys: Archivo en~/.ssh/del usuario remoto que contiene las llaves públicas autorizadas para conectarse como ese usuario. Cada línea es una llave pública.ssh-keygen: Herramienta para generar pares de llaves SSH. Por defecto genera llaves RSA de 3072 bits (en versiones recientes de OpenSSH).ssh-copy-id: Herramienta que copia la llave pública al archivoauthorized_keysdel usuario remoto. Maneja automáticamente la creación del directorio~/.ssh/y los permisos correctos.- Passphrase vs Password: La passphrase protege la llave privada localmente. Es diferente a la contraseña de login del servidor. Para autenticación completamente sin interacción, se genera la llave sin passphrase.
Cómo funciona la autenticación por llave SSH
1. thor genera un par de llaves (pública + privada)
2. La llave pública se copia al servidor remoto en ~/.ssh/authorized_keys del sudo user
3. Al conectarse, el servidor envía un desafío cifrado con la llave pública
4. El cliente (thor) descifra el desafío con su llave privada
5. El servidor valida la respuesta y permite el acceso sin contraseña
┌─────────────┐ ┌─────────────┐
│ Jump Box │ │ App Server │
│ │ │ │
│ thor │ ── SSH request ──> │ tony │
│ ~/.ssh/ │ │ ~/.ssh/ │
│ id_rsa │ <── challenge ──── │ authorized_ │
│ id_rsa.pub │ ── response ─────> │ keys │
│ │ <── access ─────── │ │
└─────────────┘ └─────────────┘
Pasos
- Generar el par de llaves SSH para el usuario
thoren el jump box - Copiar la llave pública a
tonyen stapp01 - Copiar la llave pública a
steveen stapp02 - Copiar la llave pública a
banneren stapp03 - Verificar la conexión SSH sin contraseña a cada servidor
Comandos / Código
1. Generar el par de llaves SSH
Desde el jump box, como usuario thor:
Cuando pregunte por la ubicación, aceptar el valor por defecto (/home/thor/.ssh/id_rsa). Cuando pregunte por passphrase, dejar vacío (presionar Enter dos veces) para que la autenticación sea completamente sin interacción:
Generating public/private rsa key pair.
Enter file in which to save the key (/home/thor/.ssh/id_rsa): [Enter]
Enter passphrase (empty for no passphrase): [Enter]
Enter same passphrase again: [Enter]
Your identification has been saved in /home/thor/.ssh/id_rsa
Your public key has been saved in /home/thor/.ssh/id_rsa.pub
Nota: En entornos de producción se recomienda usar passphrase junto con
ssh-agentpara no comprometer la seguridad. En este ejercicio se omite para cumplir el requisito de acceso sin interacción.
2. Copiar la llave pública a cada app server
Cada comando pedirá la contraseña del usuario remoto una sola vez. Después de esto, ya no será necesaria.
Salida esperada:
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/thor/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s)
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is because of the password
Number of key(s) added: 1
Now try logging into the machine with: "ssh 'tony@stapp01'"
and check to make sure that only the key(s) you wanted were added.
3. Verificar la conexión sin contraseña
ssh tony@stapp01 "whoami && hostname"
ssh steve@stapp02 "whoami && hostname"
ssh banner@stapp03 "whoami && hostname"
Salida esperada (sin solicitar contraseña):
Qué hace ssh-copy-id internamente
ssh-copy-id es un atajo que realiza varias acciones en el servidor remoto:
# Esto es lo que ssh-copy-id hace por debajo:
# 1. Crea el directorio ~/.ssh si no existe
mkdir -p ~/.ssh
# 2. Establece permisos correctos en el directorio
chmod 700 ~/.ssh
# 3. Agrega la llave pública al archivo authorized_keys
cat id_rsa.pub >> ~/.ssh/authorized_keys
# 4. Establece permisos correctos en el archivo
chmod 600 ~/.ssh/authorized_keys
Si ssh-copy-id no está disponible en el sistema, se puede hacer manualmente:
cat ~/.ssh/id_rsa.pub | ssh tony@stapp01 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Permisos críticos en SSH
SSH es muy estricto con los permisos. Si los permisos no son correctos, la autenticación por llave falla silenciosamente y SSH pide contraseña como fallback.
| Archivo/Directorio | Permiso | Descripción |
|---|---|---|
~/.ssh/ |
700 (drwx------) |
Solo el dueño puede leer/escribir/entrar |
~/.ssh/id_rsa (llave privada) |
600 (-rw-------) |
Solo el dueño puede leer/escribir |
~/.ssh/id_rsa.pub (llave pública) |
644 (-rw-r--r--) |
Todos pueden leer, solo el dueño escribe |
~/.ssh/authorized_keys |
600 (-rw-------) |
Solo el dueño puede leer/escribir |
/home/usuario/ (home dir) |
755 o más restrictivo |
No debe tener permisos de escritura para group/others |
Si algo falla, lo primero que se debe verificar son estos permisos.
Tipos de llaves SSH
ssh-keygen soporta varios algoritmos. La elección del tipo de llave afecta la seguridad y compatibilidad:
| Tipo | Flag | Tamaño por defecto | Recomendación |
|---|---|---|---|
| RSA | -t rsa |
3072 bits | Amplia compatibilidad. Usar mínimo 2048, preferible 4096 |
| Ed25519 | -t ed25519 |
256 bits (fijo) | Más seguro y rápido que RSA. Recomendado si el servidor lo soporta |
| ECDSA | -t ecdsa |
256 bits | Buen rendimiento, pero menos adoptado que Ed25519 |
| DSA | -t dsa |
1024 bits | Obsoleto. No usar |
# Generar llave Ed25519 (recomendado para sistemas modernos)
ssh-keygen -t ed25519
# Generar llave RSA de 4096 bits (máxima compatibilidad)
ssh-keygen -t rsa -b 4096
En este ejercicio se usa RSA por ser el más compatible en entornos mixtos.
Passphrase: qué es, cuándo usarla y cuándo no
Qué es la passphrase
La passphrase es una contraseña que cifra la llave privada en disco. Sin passphrase, cualquier persona que obtenga acceso al archivo ~/.ssh/id_rsa puede usarlo directamente para autenticarse en todos los servidores donde esté autorizada esa llave. Con passphrase, la llave privada está cifrada y es inútil sin conocer la frase.
Sin passphrase:
Alguien roba id_rsa → tiene acceso inmediato a todos los servidores
Con passphrase:
Alguien roba id_rsa → archivo cifrado, no puede usarlo sin la passphrase
Por qué NO usarla en sistemas automatizados
En este ejercicio generamos la llave sin passphrase. Esto es intencional y es la práctica estándar en contextos de automatización. La razón es simple: si la llave tiene passphrase, alguien debe ingresarla cada vez que se usa.
Escenarios donde la passphrase rompe la automatización:
- Cron jobs que se conectan por SSH a otros servidores
- Scripts de deploy que copian archivos o reinician servicios
- Ansible/Terraform/Chef que ejecutan tareas en hosts remotos
- Pipelines de CI/CD que hacen SSH a servidores de staging/producción
- Herramientas de backup que transfieren archivos entre servidores
En todos estos casos no hay un humano presente para escribir la passphrase. Si la llave la tiene, el proceso falla o se queda colgado esperando input.
Cuándo SÍ usar passphrase
Para acceso interactivo (un humano conectándose manualmente por SSH), siempre se recomienda passphrase. Si alguien compromete tu laptop o workstation, la passphrase es la última línea de defensa.
ssh-agent: passphrase sin repetirla
ssh-agent es un proceso que corre en segundo plano y almacena en memoria las llaves privadas descifradas. Se ingresa la passphrase una sola vez al inicio de la sesión, y ssh-agent la recuerda hasta que se cierre la sesión o se elimine manualmente.
# Iniciar ssh-agent (si no está corriendo)
eval $(ssh-agent)
# Agregar la llave (pide la passphrase una vez)
ssh-add ~/.ssh/id_rsa
Enter passphrase for /home/thor/.ssh/id_rsa: [ingresa passphrase]
Identity added: /home/thor/.ssh/id_rsa
# A partir de aquí, SSH usa la llave sin pedir passphrase
ssh tony@stapp01 # entra sin pedir nada
ssh steve@stapp02 # entra sin pedir nada
ssh-agent solo sirve para sesiones interactivas. No resuelve el problema de automatización porque requiere que alguien ingrese la passphrase al menos una vez por sesión.
Cómo proteger llaves sin passphrase
Si la llave no tiene passphrase (como en automatización), se compensa con otras medidas de seguridad:
| Medida | Qué hace |
|---|---|
Permisos 600 en id_rsa |
Solo el dueño puede leer la llave |
| Limitar el usuario en el servidor | El usuario remoto tiene permisos mínimos (principio de menor privilegio) |
Restringir comandos en authorized_keys |
Permite solo un comando específico por llave (ver sección siguiente) |
| Rotación de llaves | Regenerar llaves periódicamente y revocar las anteriores |
| Monitoreo de acceso | Revisar logs de /var/log/secure o /var/log/auth.log |
Opciones avanzadas de SSH
Restringir comandos por llave en authorized_keys
Se puede limitar qué comando puede ejecutar una llave específica. Útil para llaves de automatización sin passphrase:
# En ~/.ssh/authorized_keys del servidor remoto:
command="/usr/local/bin/backup.sh",no-port-forwarding,no-X11-forwarding ssh-rsa AAAA...== thor@jumpbox
Con esta configuración, aunque alguien robe la llave, solo puede ejecutar backup.sh. Cualquier otro comando se ignora.
Restringir por IP de origen
Se puede limitar desde qué IPs se acepta una llave:
# Solo acepta conexiones desde 192.168.1.100
from="192.168.1.100" ssh-rsa AAAA...== thor@jumpbox
# Acepta desde una subred
from="192.168.1.0/24" ssh-rsa AAAA...== thor@jumpbox
# Combinar con restricción de comando
from="192.168.1.100",command="/usr/local/bin/deploy.sh" ssh-rsa AAAA...== thor@jumpbox
Archivo ~/.ssh/config para simplificar conexiones
En lugar de recordar usuarios, hosts y puertos, se pueden definir alias en ~/.ssh/config:
# ~/.ssh/config
Host app1
HostName stapp01.stratos.xfusioncorp.com
User tony
IdentityFile ~/.ssh/id_rsa
Host app2
HostName stapp02.stratos.xfusioncorp.com
User steve
IdentityFile ~/.ssh/id_rsa
Host app3
HostName stapp03.stratos.xfusioncorp.com
User banner
IdentityFile ~/.ssh/id_rsa
Después solo se usa el alias:
También se pueden definir opciones globales:
Host *
ServerAliveInterval 60 # Enviar keepalive cada 60 segundos
ServerAliveCountMax 3 # Desconectar después de 3 keepalives sin respuesta
StrictHostKeyChecking no # No preguntar al conectar a hosts nuevos (solo en labs)
IdentitiesOnly yes # Solo usar la llave especificada, no probar todas
Comparación de métodos de autenticación SSH
| Método | Seguridad | Automatización | Caso de uso |
|---|---|---|---|
| Contraseña | Baja (vulnerable a brute force) | No (requiere input) | Acceso temporal o inicial |
| Llave sin passphrase | Media (depende de protección del archivo) | Sí | Scripts, cron, CI/CD, Ansible |
| Llave con passphrase | Alta | No (requiere input) | Acceso interactivo diario |
| Llave + passphrase + ssh-agent | Alta | Parcial (requiere input al inicio de sesión) | Acceso interactivo frecuente |
| Llave + restricción de comando/IP | Alta | Sí | Automatización en producción |
| Certificados SSH | Muy alta (gestión centralizada) | Sí | Infraestructura grande con muchos servidores |
Control de privilegios sudo después de la conexión SSH
SSH solo controla quién puede conectarse. Una vez dentro del servidor, los privilegios se controlan con sudo y el archivo /etc/sudoers.
Sin sudo (por defecto)
Un usuario normal no tiene sudo a menos que se le otorgue explícitamente:
Sudo completo
Acceso total a cualquier comando como cualquier usuario:
Es lo más común pero lo menos seguro. El usuario puede hacer cualquier cosa como root.
Sudo limitado a comandos específicos
Solo permite ejecutar ciertos comandos:
# tony solo puede reiniciar httpd y ver logs
tony ALL=(ALL) /usr/bin/systemctl restart httpd, /usr/bin/cat /var/log/messages
Si intenta sudo rm -rf /, se le niega.
Sudo sin contraseña (NOPASSWD)
No pide la contraseña del usuario al ejecutar sudo. Necesario para automatización:
# Sin contraseña para todo
tony ALL=(ALL) NOPASSWD: ALL
# Sin contraseña solo para comandos específicos (más seguro)
tony ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart httpd
Sudo por grupo
Permite dar privilegios a todos los miembros de un grupo con %:
# Todos los del grupo "deployers" pueden hacer deploy sin contraseña
%deployers ALL=(ALL) NOPASSWD: /usr/local/bin/deploy.sh
Limitar a qué usuario puede escalar
Por defecto, sudo ejecuta como root. Se puede restringir a otro usuario:
# tony solo puede ejecutar comandos como "www-data", no como root
tony ALL=(www-data) /usr/bin/systemctl restart httpd
Anatomía de una línea en sudoers
tony ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart httpd
│ │ │ │ │
│ │ │ │ └─ Comando(s) permitido(s)
│ │ │ └─ No pedir contraseña (opcional)
│ │ └─ Como qué usuario puede ejecutar (ALL = cualquiera)
│ └─ Desde qué host (ALL = cualquier host)
└─ Usuario o %grupo
Buena práctica: usar /etc/sudoers.d/
En vez de editar /etc/sudoers directamente, crear un archivo por usuario o rol en /etc/sudoers.d/:
Por qué visudo y no editar directo: Si hay un error de sintaxis en /etc/sudoers, puedes quedarte sin acceso a sudo en todo el sistema. visudo valida la sintaxis antes de guardar y rechaza archivos con errores.
Relación entre SSH y sudo en este ejercicio
En el contexto de este día, el flujo completo es:
- SSH controla la conexión: thor se autentica con llave pública en stapp01 como tony
- sudo controla los privilegios: tony puede (o no) escalar a root según lo definido en sudoers
Son dos capas de seguridad independientes. Tener acceso SSH no implica tener sudo, y tener sudo configurado no sirve sin poder conectarse primero.
Troubleshooting
| Problema | Solución |
|---|---|
| SSH sigue pidiendo contraseña después de copiar la llave | Verificar permisos: chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys en el servidor remoto |
ssh-copy-id: command not found |
Copiar manualmente con cat ~/.ssh/id_rsa.pub \| ssh user@host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys" |
Permission denied (publickey) |
La llave no está en authorized_keys del usuario remoto, o los permisos del home directory son demasiado abiertos |
Agent admitted failure to sign |
Agregar la llave al agente: ssh-add ~/.ssh/id_rsa |
WARNING: UNPROTECTED PRIVATE KEY FILE! |
La llave privada tiene permisos demasiado abiertos. Corregir con chmod 600 ~/.ssh/id_rsa |
Host key verification failed |
Primera conexión al servidor. Agregar el host con ssh-keyscan stapp01 >> ~/.ssh/known_hosts o aceptar manualmente |
| Funciona con un usuario pero no con otro | Verificar que la llave se copió al usuario correcto en el servidor correcto. Revisar ~/.ssh/authorized_keys de cada usuario |