SSH Avanzado y Túneles
SSH (Secure Shell) es mucho más que un protocolo para conexiones remotas seguras. Sus capacidades avanzadas incluyen la creación de túneles, port forwarding, proxies SOCKS, y multiplexado de conexiones. Estas funcionalidades son fundamentales para administradores de sistemas que trabajan con infraestructuras complejas y necesitan acceso seguro a recursos de red.
Los túneles SSH permiten encapsular tráfico de red inseguro dentro de conexiones SSH cifradas, proporcionando una capa adicional de seguridad y permitiendo el acceso a servicios que no están directamente disponibles desde nuestra ubicación de red.
Casos de Uso Comunes
- Acceso a bases de datos: Conectar de forma segura a DB en redes privadas
- Navegación segura: Usar el servidor como proxy para navegación web
- Bypass de firewalls: Acceder a servicios bloqueados por firewalls corporativos
- Desarrollo remoto: Acceder a servicios de desarrollo en servidores remotos
SSH Básico y Configuración
Conexiones SSH Básicas
Repaso de los comandos SSH fundamentales con opciones avanzadas.
# Conexión SSH básica
ssh [email protected]
# Especificar puerto personalizado
ssh -p 2222 [email protected]
# Usar clave SSH específica
ssh -i ~/.ssh/mi_clave_privada [email protected]
# Conexión con forwarding de X11 (aplicaciones gráficas)
ssh -X [email protected]
# Conexión con compresión (útil para conexiones lentas)
ssh -C [email protected]
# Modo verbose para debugging
ssh -v [email protected]
# Forzar autenticación por clave pública únicamente
ssh -o PreferredAuthentications=publickey [email protected]
# Conexión con timeout personalizado
ssh -o ConnectTimeout=10 [email protected]
# Ejecutar comando remoto y salir
ssh [email protected] "ls -la && df -h"
# Conexión sin verificación de host (solo para testing)
ssh -o StrictHostKeyChecking=no [email protected]
Configuración SSH Cliente
Optimizar la configuración SSH para uso profesional.
# Configuración SSH profesional
# ~/.ssh/config
# Configuración global
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
TCPKeepAlive yes
Compression yes
# Servidor de producción
Host prod-web
HostName 192.168.1.100
User admin
Port 2222
IdentityFile ~/.ssh/prod_key
ForwardAgent yes
# Servidor de desarrollo
Host dev-web
HostName dev.miempresa.com
User developer
IdentityFile ~/.ssh/dev_key
LocalForward 3000 localhost:3000
LocalForward 5432 localhost:5432
# Servidor jump/bastion
Host bastion
HostName bastion.miempresa.com
User sysadmin
Port 22
IdentityFile ~/.ssh/bastion_key
# Servidores detrás del bastion
Host prod-db
HostName 10.0.1.50
User dbadmin
ProxyJump bastion
IdentityFile ~/.ssh/db_key
# Servidor con múltiples túneles
Host tunnel-server
HostName servidor.com
User tunneluser
IdentityFile ~/.ssh/tunnel_key
LocalForward 8080 localhost:80
LocalForward 8443 localhost:443
LocalForward 3306 localhost:3306
DynamicForward 1080
# Con la configuración anterior, ahora puedes usar:
$ ssh prod-web # Se conecta automáticamente con todos los parámetros
$ ssh dev-web # Incluye los port forwards automáticamente
$ ssh prod-db # Usa bastion como jump host automáticamente
Port Forwarding (Túneles SSH)
Local Port Forwarding
Reenvía puertos desde tu máquina local a un servidor remoto.
# Sintaxis básica: ssh -L puerto_local:destino:puerto_destino usuario@servidor
# Ejemplo: Acceder a base de datos MySQL remota
ssh -L 3306:localhost:3306 [email protected]
# Ahora puedes conectar a localhost:3306 para acceder a la DB remota
# Ejemplo: Acceder a aplicación web interna
ssh -L 8080:192.168.1.50:80 [email protected]
# Accede vía http://localhost:8080
# Múltiples port forwards en una sola conexión
ssh -L 3306:db.interno.com:3306 -L 8080:web.interno.com:80 [email protected]
# Port forward en background
ssh -f -N -L 3306:localhost:3306 [email protected]
# Port forward con autenticación por clave
ssh -i ~/.ssh/mi_clave -L 5432:postgres-server:5432 [email protected]
Remote Port Forwarding
Permite que el servidor remoto acceda a servicios en tu máquina local.
# Sintaxis: ssh -R puerto_remoto:destino:puerto_local usuario@servidor
# Ejemplo: Compartir servidor web local con servidor remoto
ssh -R 8080:localhost:80 [email protected]
# El servidor remoto puede acceder a tu web via localhost:8080
# Ejemplo: Permitir acceso remoto a servicio local
ssh -R 3000:localhost:3000 [email protected]
# Útil para demos o desarrollo colaborativo
# Remote forward en background
ssh -f -N -R 9000:localhost:8000 [email protected]
# Ejemplo práctico: Acceso a servicio detrás de NAT
ssh -R 2222:localhost:22 [email protected]
# Permite SSH reverso desde el servidor público
Dynamic Port Forwarding (SOCKS Proxy)
Crear un proxy SOCKS para enrutar todo el tráfico a través del servidor SSH.
# Crear proxy SOCKS5
ssh -D 1080 [email protected]
# Proxy SOCKS en background
ssh -f -N -D 1080 [email protected]
# Proxy SOCKS con puerto específico
ssh -D 127.0.0.1:1080 [email protected]
# Combinado con otros forwards
ssh -D 1080 -L 3306:localhost:3306 [email protected]
# Usar el proxy SOCKS con curl
curl --socks5 localhost:1080 http://sitio-bloqueado.com
# Configurar Firefox para usar el proxy SOCKS
# Preferences → Network → Settings → Manual proxy configuration
# SOCKS Host: localhost, Port: 1080, SOCKS v5
Scripts Avanzados para SSH
Gestor de Túneles SSH
Script completo para gestionar múltiples túneles SSH de forma automatizada.
#!/bin/bash
# SSH Tunnel Manager - Gestión avanzada de túneles SSH
# Uso: ./ssh_tunnel_manager.sh [start|stop|status|list] [profile]
CONFIG_FILE="/etc/ssh_tunnels.conf"
PID_DIR="/var/run/ssh_tunnels"
LOG_FILE="/var/log/ssh_tunnels.log"
# Configuración por defecto
DEFAULT_CONFIG='
# Configuración de túneles SSH
# Formato: PROFILE:TYPE:LOCAL_PORT:REMOTE_HOST:REMOTE_PORT:SSH_HOST:SSH_USER:SSH_KEY
TUNNEL_PROFILES=(
"web-dev:local:8080:localhost:80:webserver.com:admin:/home/admin/.ssh/web_key"
"database:local:3306:db.internal.com:3306:gateway.com:dbadmin:/home/admin/.ssh/db_key"
"proxy:dynamic:1080:::proxy.server.com:user:/home/admin/.ssh/proxy_key"
"reverse-ssh:remote:2222:localhost:22:remote.server.com:admin:/home/admin/.ssh/reverse_key"
)
'
# Cargar configuración
[[ -f "$CONFIG_FILE" ]] && source "$CONFIG_FILE" || echo "$DEFAULT_CONFIG" > "$CONFIG_FILE"
# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_message() {
local level=$1
local message=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
case $level in
ERROR) echo -e "${RED}[ERROR]${NC} $message" ;;
SUCCESS) echo -e "${GREEN}[SUCCESS]${NC} $message" ;;
WARNING) echo -e "${YELLOW}[WARNING]${NC} $message" ;;
INFO) echo -e "${BLUE}[INFO]${NC} $message" ;;
esac
}
# Crear directorios necesarios
setup_directories() {
mkdir -p "$PID_DIR" "$(dirname "$LOG_FILE")"
}
# Parsear perfil de túnel
parse_profile() {
local profile_string="$1"
IFS=':' read -r profile type local_port remote_host remote_port ssh_host ssh_user ssh_key <<< "$profile_string"
echo "PROFILE=$profile"
echo "TYPE=$type"
echo "LOCAL_PORT=$local_port"
echo "REMOTE_HOST=$remote_host"
echo "REMOTE_PORT=$remote_port"
echo "SSH_HOST=$ssh_host"
echo "SSH_USER=$ssh_user"
echo "SSH_KEY=$ssh_key"
}
# Construir comando SSH para túnel
build_tunnel_command() {
local type=$1
local local_port=$2
local remote_host=$3
local remote_port=$4
local ssh_host=$5
local ssh_user=$6
local ssh_key=$7
local ssh_cmd="ssh -f -N -o ServerAliveInterval=60 -o ExitOnForwardFailure=yes"
# Agregar clave SSH si se especifica
[[ -n "$ssh_key" && -f "$ssh_key" ]] && ssh_cmd="$ssh_cmd -i '$ssh_key'"
# Agregar tipo de túnel
case $type in
local)
ssh_cmd="$ssh_cmd -L ${local_port}:${remote_host}:${remote_port}"
;;
remote)
ssh_cmd="$ssh_cmd -R ${local_port}:${remote_host}:${remote_port}"
;;
dynamic)
ssh_cmd="$ssh_cmd -D $local_port"
;;
*)
log_message "ERROR" "Tipo de túnel desconocido: $type"
return 1
;;
esac
ssh_cmd="$ssh_cmd ${ssh_user}@${ssh_host}"
echo "$ssh_cmd"
}
# Iniciar túnel
start_tunnel() {
local profile_string="$1"
eval "$(parse_profile "$profile_string")"
local pid_file="$PID_DIR/${PROFILE}.pid"
# Verificar si ya está ejecutándose
if [[ -f "$pid_file" ]] && kill -0 "$(cat "$pid_file")" 2>/dev/null; then
log_message "WARNING" "Túnel $PROFILE ya está ejecutándose"
return 1
fi
log_message "INFO" "Iniciando túnel: $PROFILE"
# Construir y ejecutar comando
local tunnel_cmd=$(build_tunnel_command "$TYPE" "$LOCAL_PORT" "$REMOTE_HOST" "$REMOTE_PORT" "$SSH_HOST" "$SSH_USER" "$SSH_KEY")
if [[ -z "$tunnel_cmd" ]]; then
log_message "ERROR" "No se pudo construir comando para túnel $PROFILE"
return 1
fi
log_message "INFO" "Ejecutando: $tunnel_cmd"
# Ejecutar comando y guardar PID
if eval "$tunnel_cmd"; then
# Encontrar PID del proceso SSH creado
local ssh_pid=$(pgrep -f "$SSH_HOST.*$LOCAL_PORT" | tail -1)
if [[ -n "$ssh_pid" ]]; then
echo "$ssh_pid" > "$pid_file"
log_message "SUCCESS" "Túnel $PROFILE iniciado (PID: $ssh_pid)"
# Verificar que el túnel funciona
sleep 2
if verify_tunnel "$TYPE" "$LOCAL_PORT"; then
log_message "SUCCESS" "Túnel $PROFILE verificado correctamente"
else
log_message "WARNING" "Túnel $PROFILE iniciado pero la verificación falló"
fi
return 0
else
log_message "ERROR" "No se pudo obtener PID del túnel $PROFILE"
return 1
fi
else
log_message "ERROR" "Falló al iniciar túnel $PROFILE"
return 1
fi
}
# Detener túnel
stop_tunnel() {
local profile_name="$1"
local pid_file="$PID_DIR/${profile_name}.pid"
if [[ ! -f "$pid_file" ]]; then
log_message "WARNING" "Archivo PID no encontrado para $profile_name"
return 1
fi
local pid=$(cat "$pid_file")
if kill -0 "$pid" 2>/dev/null; then
log_message "INFO" "Deteniendo túnel $profile_name (PID: $pid)"
if kill "$pid"; then
rm -f "$pid_file"
log_message "SUCCESS" "Túnel $profile_name detenido"
return 0
else
log_message "ERROR" "No se pudo detener túnel $profile_name"
return 1
fi
else
log_message "WARNING" "Proceso $pid para túnel $profile_name no existe"
rm -f "$pid_file"
return 1
fi
}
# Verificar túnel
verify_tunnel() {
local type="$1"
local port="$2"
case $type in
local|dynamic)
# Verificar que el puerto local está en escucha
if netstat -ln | grep -q ":$port "; then
return 0
else
return 1
fi
;;
remote)
# Para remote tunnels, verificar conexión SSH
return 0
;;
esac
}
# Mostrar estado de túneles
show_status() {
echo -e "${BLUE}=== Estado de Túneles SSH ===${NC}"
printf "%-15s %-8s %-10s %-20s %-10s %s\n" "PERFIL" "TIPO" "PUERTO" "DESTINO" "PID" "ESTADO"
echo "─────────────────────────────────────────────────────────────────────────────"
for profile_string in "${TUNNEL_PROFILES[@]}"; do
eval "$(parse_profile "$profile_string")"
local pid_file="$PID_DIR/${PROFILE}.pid"
local pid_status="No ejecutándose"
local pid_value="N/A"
if [[ -f "$pid_file" ]]; then
local pid=$(cat "$pid_file")
if kill -0 "$pid" 2>/dev/null; then
pid_status="✅ Ejecutándose"
pid_value="$pid"
else
pid_status="❌ Proceso muerto"
rm -f "$pid_file"
fi
fi
local destination="$REMOTE_HOST:$REMOTE_PORT"
[[ "$TYPE" == "dynamic" ]] && destination="SOCKS Proxy"
printf "%-15s %-8s %-10s %-20s %-10s %s\n" "$PROFILE" "$TYPE" "$LOCAL_PORT" "$destination" "$pid_value" "$pid_status"
done
}
# Listar perfiles disponibles
list_profiles() {
echo -e "${BLUE}=== Perfiles Disponibles ===${NC}"
for profile_string in "${TUNNEL_PROFILES[@]}"; do
eval "$(parse_profile "$profile_string")"
echo -e "${GREEN}$PROFILE${NC}"
echo " Tipo: $TYPE"
echo " Puerto local: $LOCAL_PORT"
[[ "$TYPE" != "dynamic" ]] && echo " Destino: $REMOTE_HOST:$REMOTE_PORT"
echo " SSH: $SSH_USER@$SSH_HOST"
echo
done
}
# Iniciar todos los túneles
start_all() {
log_message "INFO" "Iniciando todos los túneles..."
local success_count=0
local total_count=0
for profile_string in "${TUNNEL_PROFILES[@]}"; do
((total_count++))
if start_tunnel "$profile_string"; then
((success_count++))
fi
done
log_message "INFO" "Túneles iniciados: $success_count/$total_count"
}
# Detener todos los túneles
stop_all() {
log_message "INFO" "Deteniendo todos los túneles..."
for profile_string in "${TUNNEL_PROFILES[@]}"; do
eval "$(parse_profile "$profile_string")"
stop_tunnel "$PROFILE"
done
}
# Reiniciar túnel específico
restart_tunnel() {
local profile_name="$1"
log_message "INFO" "Reiniciando túnel: $profile_name"
# Buscar perfil
local found_profile=""
for profile_string in "${TUNNEL_PROFILES[@]}"; do
eval "$(parse_profile "$profile_string")"
if [[ "$PROFILE" == "$profile_name" ]]; then
found_profile="$profile_string"
break
fi
done
if [[ -z "$found_profile" ]]; then
log_message "ERROR" "Perfil no encontrado: $profile_name"
return 1
fi
stop_tunnel "$profile_name"
sleep 2
start_tunnel "$found_profile"
}
# Mostrar ayuda
show_help() {
cat << EOF
Uso: $0 COMANDO [PERFIL]
COMANDOS:
start PERFIL - Iniciar túnel específico
stop PERFIL - Detener túnel específico
restart PERFIL - Reiniciar túnel específico
start-all - Iniciar todos los túneles
stop-all - Detener todos los túneles
status - Mostrar estado de túneles
list - Listar perfiles disponibles
EJEMPLOS:
$0 start web-dev
$0 stop database
$0 restart proxy
$0 start-all
$0 status
CONFIGURACIÓN:
Archivo de configuración: $CONFIG_FILE
EOF
}
# Función principal
main() {
setup_directories
case "${1:-}" in
start)
if [[ -z "$2" ]]; then
log_message "ERROR" "Nombre de perfil requerido"
exit 1
fi
# Buscar perfil por nombre
local found_profile=""
for profile_string in "${TUNNEL_PROFILES[@]}"; do
eval "$(parse_profile "$profile_string")"
if [[ "$PROFILE" == "$2" ]]; then
found_profile="$profile_string"
break
fi
done
if [[ -z "$found_profile" ]]; then
log_message "ERROR" "Perfil no encontrado: $2"
exit 1
fi
start_tunnel "$found_profile"
;;
stop)
if [[ -z "$2" ]]; then
log_message "ERROR" "Nombre de perfil requerido"
exit 1
fi
stop_tunnel "$2"
;;
restart)
if [[ -z "$2" ]]; then
log_message "ERROR" "Nombre de perfil requerido"
exit 1
fi
restart_tunnel "$2"
;;
start-all)
start_all
;;
stop-all)
stop_all
;;
status)
show_status
;;
list)
list_profiles
;;
-h|--help|help)
show_help
exit 0
;;
*)
log_message "ERROR" "Comando desconocido: ${1:-}"
show_help
exit 1
;;
esac
}
# Ejecutar si es llamado directamente
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
SSH Jump Hosts (Bastion Hosts)
ProxyJump y Conexiones en Cadena
Técnicas para conectar a servidores a través de hosts intermedios.
# Método moderno: ProxyJump (OpenSSH 7.3+)
ssh -J [email protected] [email protected]
# Múltiples saltos
ssh -J [email protected],[email protected] [email protected]
# Con configuración en ~/.ssh/config
Host bastion
HostName bastion.miempresa.com
User admin
IdentityFile ~/.ssh/bastion_key
Host servidor-interno
HostName 10.0.1.100
User appuser
ProxyJump bastion
IdentityFile ~/.ssh/interno_key
# Ahora solo necesitas: ssh servidor-interno
# Método tradicional: ProxyCommand
ssh -o ProxyCommand="ssh -W %h:%p [email protected]" usuario@servidor-interno
# Port forwarding a través de jump host
ssh -J bastion -L 3306:db-server:3306 usuario@app-server
# SFTP a través de jump host
sftp -o ProxyJump=bastion usuario@servidor-interno
Multiplexado de Conexiones SSH
Connection Sharing
Optimizar conexiones SSH reutilizando conexiones existentes.
# Configuración en ~/.ssh/config para multiplexing
Host *
ControlMaster auto
ControlPath ~/.ssh/connections/%r@%h:%p
ControlPersist 600
# Crear directorio para control sockets
mkdir -p ~/.ssh/connections
# Primera conexión crea el master
ssh [email protected]
# Conexiones subsiguientes reutilizan el master (mucho más rápidas)
ssh [email protected] "uptime"
scp archivo.txt [email protected]:/tmp/
sftp [email protected]
# Verificar conexiones activas
ssh -O check [email protected]
# Cerrar master connection
ssh -O exit [email protected]
# Listar conexiones activas
ls -la ~/.ssh/connections/
Scripts de Administración SSH
Sistema de Gestión SSH Completo
Script para administrar múltiples servidores SSH:
#!/bin/bash
# SSH Administration Tool
# Gestión centralizada de múltiples servidores SSH
SERVERS_CONFIG="/etc/ssh_servers.conf"
LOG_FILE="/var/log/ssh_admin.log"
# Configuración por defecto
DEFAULT_CONFIG='
# Configuración de servidores
# Formato: ALIAS:HOSTNAME:USER:PORT:KEY_FILE:DESCRIPTION
SERVERS=(
"web1:web1.miempresa.com:admin:22:/root/.ssh/web_key:Servidor Web Principal"
"web2:web2.miempresa.com:admin:22:/root/.ssh/web_key:Servidor Web Backup"
"db1:db1.miempresa.com:dbadmin:2222:/root/.ssh/db_key:Base de Datos Principal"
"jump:bastion.miempresa.com:sysadmin:22:/root/.ssh/jump_key:Servidor Bastion"
)
'
# Cargar configuración
[[ -f "$SERVERS_CONFIG" ]] && source "$SERVERS_CONFIG" || echo "$DEFAULT_CONFIG" > "$SERVERS_CONFIG"
# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_message() {
local level=$1
local message=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
case $level in
ERROR) echo -e "${RED}[ERROR]${NC} $message" ;;
SUCCESS) echo -e "${GREEN}[SUCCESS]${NC} $message" ;;
WARNING) echo -e "${YELLOW}[WARNING]${NC} $message" ;;
INFO) echo -e "${BLUE}[INFO]${NC} $message" ;;
esac
}
# Parsear configuración de servidor
parse_server() {
local server_string="$1"
IFS=':' read -r alias hostname user port key_file description <<< "$server_string"
echo "ALIAS=$alias"
echo "HOSTNAME=$hostname"
echo "USER=$user"
echo "PORT=$port"
echo "KEY_FILE=$key_file"
echo "DESCRIPTION=$description"
}
# Conectar a servidor
connect_server() {
local server_alias="$1"
# Buscar servidor
local found_server=""
for server_string in "${SERVERS[@]}"; do
eval "$(parse_server "$server_string")"
if [[ "$ALIAS" == "$server_alias" ]]; then
found_server="$server_string"
break
fi
done
if [[ -z "$found_server" ]]; then
log_message "ERROR" "Servidor no encontrado: $server_alias"
return 1
fi
log_message "INFO" "Conectando a $ALIAS ($HOSTNAME)"
local ssh_cmd="ssh -p $PORT"
[[ -f "$KEY_FILE" ]] && ssh_cmd="$ssh_cmd -i $KEY_FILE"
ssh_cmd="$ssh_cmd ${USER}@${HOSTNAME}"
eval "$ssh_cmd"
}
# Ejecutar comando en servidor(es)
execute_command() {
local server_alias="$1"
local command="$2"
if [[ "$server_alias" == "all" ]]; then
log_message "INFO" "Ejecutando comando en todos los servidores: $command"
for server_string in "${SERVERS[@]}"; do
eval "$(parse_server "$server_string")"
execute_on_single_server "$server_string" "$command"
done
else
# Buscar servidor específico
local found_server=""
for server_string in "${SERVERS[@]}"; do
eval "$(parse_server "$server_string")"
if [[ "$ALIAS" == "$server_alias" ]]; then
found_server="$server_string"
break
fi
done
if [[ -z "$found_server" ]]; then
log_message "ERROR" "Servidor no encontrado: $server_alias"
return 1
fi
execute_on_single_server "$found_server" "$command"
fi
}
# Ejecutar comando en un servidor específico
execute_on_single_server() {
local server_string="$1"
local command="$2"
eval "$(parse_server "$server_string")"
log_message "INFO" "Ejecutando en $ALIAS: $command"
local ssh_cmd="ssh -p $PORT -o ConnectTimeout=10 -o BatchMode=yes"
[[ -f "$KEY_FILE" ]] && ssh_cmd="$ssh_cmd -i $KEY_FILE"
ssh_cmd="$ssh_cmd ${USER}@${HOSTNAME} \"$command\""
echo -e "\n${BLUE}=== $ALIAS ($HOSTNAME) ===${NC}"
if eval "$ssh_cmd"; then
log_message "SUCCESS" "Comando ejecutado exitosamente en $ALIAS"
else
log_message "ERROR" "Comando falló en $ALIAS"
fi
}
# Verificar conectividad
check_connectivity() {
local server_alias="$1"
if [[ "$server_alias" == "all" ]]; then
log_message "INFO" "Verificando conectividad a todos los servidores"
echo -e "${BLUE}=== Test de Conectividad ===${NC}"
printf "%-10s %-25s %-15s %-10s %s\n" "ALIAS" "HOSTNAME" "USER" "PUERTO" "ESTADO"
echo "─────────────────────────────────────────────────────────────────────"
for server_string in "${SERVERS[@]}"; do
eval "$(parse_server "$server_string")"
local ssh_cmd="ssh -p $PORT -o ConnectTimeout=5 -o BatchMode=yes"
[[ -f "$KEY_FILE" ]] && ssh_cmd="$ssh_cmd -i $KEY_FILE"
if $ssh_cmd "${USER}@${HOSTNAME}" "echo 'OK'" &>/dev/null; then
printf "%-10s %-25s %-15s %-10s %s\n" "$ALIAS" "$HOSTNAME" "$USER" "$PORT" "✅ OK"
else
printf "%-10s %-25s %-15s %-10s %s\n" "$ALIAS" "$HOSTNAME" "$USER" "$PORT" "❌ FALLO"
fi
done
else
# Verificar servidor específico
local found_server=""
for server_string in "${SERVERS[@]}"; do
eval "$(parse_server "$server_string")"
if [[ "$ALIAS" == "$server_alias" ]]; then
found_server="$server_string"
break
fi
done
if [[ -z "$found_server" ]]; then
log_message "ERROR" "Servidor no encontrado: $server_alias"
return 1
fi
eval "$(parse_server "$found_server")"
local ssh_cmd="ssh -p $PORT -o ConnectTimeout=10 -v"
[[ -f "$KEY_FILE" ]] && ssh_cmd="$ssh_cmd -i $KEY_FILE"
log_message "INFO" "Probando conectividad a $ALIAS"
$ssh_cmd "${USER}@${HOSTNAME}" "echo 'Conexión exitosa a $ALIAS'"
fi
}
# Listar servidores
list_servers() {
echo -e "${BLUE}=== Servidores Configurados ===${NC}"
printf "%-10s %-25s %-15s %-10s %s\n" "ALIAS" "HOSTNAME" "USER" "PUERTO" "DESCRIPCIÓN"
echo "──────────────────────────────────────────────────────────────────────────────────"
for server_string in "${SERVERS[@]}"; do
eval "$(parse_server "$server_string")"
printf "%-10s %-25s %-15s %-10s %s\n" "$ALIAS" "$HOSTNAME" "$USER" "$PORT" "$DESCRIPTION"
done
}
# Transferir archivo
transfer_file() {
local direction="$1" # upload o download
local server_alias="$2"
local source="$3"
local destination="$4"
# Buscar servidor
local found_server=""
for server_string in "${SERVERS[@]}"; do
eval "$(parse_server "$server_string")"
if [[ "$ALIAS" == "$server_alias" ]]; then
found_server="$server_string"
break
fi
done
if [[ -z "$found_server" ]]; then
log_message "ERROR" "Servidor no encontrado: $server_alias"
return 1
fi
eval "$(parse_server "$found_server")"
local scp_cmd="scp -P $PORT"
[[ -f "$KEY_FILE" ]] && scp_cmd="$scp_cmd -i $KEY_FILE"
case $direction in
upload)
log_message "INFO" "Subiendo $source a $ALIAS:$destination"
scp_cmd="$scp_cmd \"$source\" \"${USER}@${HOSTNAME}:$destination\""
;;
download)
log_message "INFO" "Descargando $ALIAS:$source a $destination"
scp_cmd="$scp_cmd \"${USER}@${HOSTNAME}:$source\" \"$destination\""
;;
*)
log_message "ERROR" "Dirección inválida: $direction (usa upload o download)"
return 1
;;
esac
if eval "$scp_cmd"; then
log_message "SUCCESS" "Transferencia completada"
else
log_message "ERROR" "Transferencia falló"
return 1
fi
}
# Mostrar ayuda
show_help() {
cat << EOF
SSH Administration Tool
Uso: $0 COMANDO [OPCIONES]
COMANDOS:
connect ALIAS - Conectar a servidor
exec ALIAS "COMANDO" - Ejecutar comando en servidor
exec all "COMANDO" - Ejecutar comando en todos los servidores
check ALIAS - Verificar conectividad a servidor
check all - Verificar conectividad a todos
upload ALIAS SRC DEST - Subir archivo a servidor
download ALIAS SRC DEST - Descargar archivo de servidor
list - Listar servidores configurados
EJEMPLOS:
$0 connect web1
$0 exec db1 "systemctl status mysql"
$0 exec all "uptime"
$0 check all
$0 upload web1 ./app.tar.gz /tmp/
$0 download db1 /var/log/mysql.log ./
$0 list
CONFIGURACIÓN:
Archivo: $SERVERS_CONFIG
EOF
}
# Función principal
main() {
case "${1:-}" in
connect)
[[ -z "$2" ]] && { log_message "ERROR" "Alias de servidor requerido"; exit 1; }
connect_server "$2"
;;
exec)
[[ -z "$2" || -z "$3" ]] && { log_message "ERROR" "Alias y comando requeridos"; exit 1; }
execute_command "$2" "$3"
;;
check)
[[ -z "$2" ]] && { log_message "ERROR" "Alias de servidor requerido (o 'all')"; exit 1; }
check_connectivity "$2"
;;
upload)
[[ -z "$2" || -z "$3" || -z "$4" ]] && { log_message "ERROR" "Alias, fuente y destino requeridos"; exit 1; }
transfer_file "upload" "$2" "$3" "$4"
;;
download)
[[ -z "$2" || -z "$3" || -z "$4" ]] && { log_message "ERROR" "Alias, fuente y destino requeridos"; exit 1; }
transfer_file "download" "$2" "$3" "$4"
;;
list)
list_servers
;;
-h|--help|help)
show_help
exit 0
;;
*)
log_message "ERROR" "Comando desconocido: ${1:-}"
show_help
exit 1
;;
esac
}
# Ejecutar si es llamado directamente
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
Ejercicios Prácticos Avanzados
Ejercicio 1: Túnel SSH para Base de Datos
- Configura un túnel SSH para acceder a una base de datos remota
- Crea un script que inicie el túnel automáticamente
- Implementa verificación de que el túnel está funcionando
- Añade logging y notificaciones de fallos
Proyecto: VPN Casera con SSH
Implementa una solución VPN completa usando SSH:
- Proxy SOCKS5 permanente
- Script de inicio/parada automático
- Configuración automática del navegador
- Monitoreo de la conexión
- Fallback a múltiples servidores
Consideraciones de Seguridad
- Claves SSH: Usa claves específicas para cada propósito
- Permisos: Configura permisos restrictivos (600) en claves privadas
- Timeouts: Configura timeouts apropiados para conexiones
- Auditoría: Registra todas las conexiones y transferencias
- Firewall: Limita acceso SSH solo desde IPs autorizadas
Casos de Uso Empresariales
- Desarrollo: Acceso seguro a entornos de desarrollo
- DevOps: Despliegues automatizados a través de bastion hosts
- Monitoreo: Acceso a sistemas de monitoreo internos
- Backup: Transferencias seguras de backups
- Troubleshooting: Acceso de emergencia a sistemas críticos