[P2601/P2604] Herramienta SSH ADN con ProxyJump + diagnóstico remoto

Herramienta CLI ssh (P2604-S11):
- Soporte passphrase RSA (srv-dasu)  verificado
- ProxyJump para VMs DASUTEN via script+tty
- Usuarios corregidos a DASUTEN\admindasu
- Timeouts ajustados (30s connect, 5s alive)
- Listado de nodos: ./adn/tools/run ssh --listar

Plan P2601 6.1.B actualizado:
- Registro diagnóstico 15/03: sql/pcv puertos abiertos, dc cerrado
- kex_exchange via ProxyCommand pendiente investigación
- Conexión manual desde srv-dasu funciona

Eventos: 1075 (S11 herramienta), 1076 (diagnóstico)
This commit is contained in:
Ricardo Monla
2026-03-15 16:51:03 -03:00
parent c3f55d7252
commit cfb8a6ccb6
3 changed files with 64 additions and 19 deletions
+46 -18
View File
@@ -13,6 +13,7 @@
require 'optparse' require 'optparse'
require 'open3' require 'open3'
require 'shellwords'
require_relative '../core/colores' require_relative '../core/colores'
require_relative '../core/constants' require_relative '../core/constants'
require_relative '../core/error_handler' require_relative '../core/error_handler'
@@ -33,7 +34,7 @@ module ADN
}, },
'dc-dasuten' => { 'dc-dasuten' => {
ip: '10.0.100.10', ip: '10.0.100.10',
usuario: 'Administrador', usuario: 'DASUTEN\admindasu',
puerto: 7022, puerto: 7022,
auth: :password, auth: :password,
clave_boveda: 'admindasu', clave_boveda: 'admindasu',
@@ -41,7 +42,7 @@ module ADN
}, },
'sql-dasuten' => { 'sql-dasuten' => {
ip: '10.0.100.11', ip: '10.0.100.11',
usuario: 'Administrador', usuario: 'DASUTEN\admindasu',
puerto: 7022, puerto: 7022,
auth: :password, auth: :password,
clave_boveda: 'admindasu', clave_boveda: 'admindasu',
@@ -49,7 +50,7 @@ module ADN
}, },
'pcv-dasu0' => { 'pcv-dasu0' => {
ip: '10.0.100.12', ip: '10.0.100.12',
usuario: 'admindasu', usuario: 'DASUTEN\admindasu',
puerto: 7022, puerto: 7022,
auth: :password, auth: :password,
clave_boveda: 'admindasu', clave_boveda: 'admindasu',
@@ -203,32 +204,59 @@ module ADN
password = obtener_secreto(config[:clave_boveda]) password = obtener_secreto(config[:clave_boveda])
proxy_config = config[:proxy] ? NODOS[config[:proxy]] : nil proxy_config = config[:proxy] ? NODOS[config[:proxy]] : nil
ssh_opts = "-o ConnectTimeout=15 -o StrictHostKeyChecking=no" ssh_opts = "-o StrictHostKeyChecking=no -o ConnectTimeout=15"
ssh_target = "#{config[:usuario]}@#{config[:ip]}" ssh_target = "#{config[:usuario]}@#{config[:ip]}"
# Si hay proxy, primero conectamos al proxy y desde ahí ejecutamos
if proxy_config if proxy_config
@logger.info("Via proxy #{config[:proxy]} (#{proxy_config[:ip]})") @logger.info("Via ProxyJump #{config[:proxy]} (#{proxy_config[:ip]})")
proxy_passphrase = obtener_secreto(proxy_config[:clave_boveda]) proxy_passphrase = obtener_secreto(proxy_config[:clave_boveda])
# Comando anidado: SSH al proxy → SSH al destino # ProxyCommand con sshpass: el proxy usa passphrase RSA,
inner_cmd = "sshpass -p '#{password}' ssh #{ssh_opts} -p #{config[:puerto]} #{ssh_target} '#{comando_remoto.gsub("'", "'\\''")}'" # el destino usa password. Todo se ejecuta localmente via bash.
outer_cmd = "sshpass -P 'passphrase' -p '#{proxy_passphrase}' ssh #{ssh_opts} -p #{proxy_config[:puerto]} #{proxy_config[:usuario]}@#{proxy_config[:ip]} \"#{inner_cmd.gsub('"', '\\"')}\"" script = <<~BASH
export SSHPASS=#{password.shellescape}
sshpass -e ssh \
-o StrictHostKeyChecking=no \
-o ConnectTimeout=30 \
-o ServerAliveInterval=5 \
-o ProxyCommand="sshpass -P passphrase -p '#{proxy_passphrase}' ssh -o StrictHostKeyChecking=no -p #{proxy_config[:puerto]} -W %h:%p #{proxy_config[:usuario]}@#{proxy_config[:ip]}" \
-p #{config[:puerto]} \
'#{ssh_target}' \
"#{comando_remoto.gsub('"', '\\"')}"
BASH
# Escribir script a archivo temporal para evitar problemas de escape
tmpscript = "/tmp/.adn_ssh_cmd_#{Process.pid}.sh"
tmpout = "/tmp/.adn_ssh_out_#{Process.pid}"
File.write(tmpscript, script)
File.chmod(0700, tmpscript)
system("script", "-qc", tmpscript, tmpout)
status_success = $?.success?
stdout = File.exist?(tmpout) ? File.read(tmpout).gsub(/\r\n?/, "\n").strip : ""
File.delete(tmpscript) if File.exist?(tmpscript)
File.delete(tmpout) if File.exist?(tmpout)
stdout, stderr, status = Open3.capture3(outer_cmd)
else else
cmd = "sshpass -p '#{password}' ssh #{ssh_opts} -p #{config[:puerto]} #{ssh_target} '#{comando_remoto.gsub("'", "'\\''")}'" ENV['SSHPASS'] = password
stdout, stderr, status = Open3.capture3(cmd) cmd_parts = ["sshpass", "-e", "ssh"] +
ssh_opts.split(' ') +
["-p", config[:puerto].to_s,
ssh_target,
comando_remoto]
stdout, stderr, status = Open3.capture3(*cmd_parts)
ENV.delete('SSHPASS')
end end
if status.success? success = defined?(status_success) ? status_success : status.success?
puts stdout unless stdout.empty?
if success
puts stdout unless stdout.nil? || stdout.empty?
@logger.exito("Comando ejecutado exitosamente en #{nodo_nombre}") @logger.exito("Comando ejecutado exitosamente en #{nodo_nombre}")
else else
puts stdout unless stdout.empty? puts stdout unless stdout.nil? || stdout.empty?
$stderr.puts stderr unless stderr.empty? ADN::ErrorHandler.error("Fallo en ejecución remota")
ADN::ErrorHandler.error("Fallo en ejecución remota (exit: #{status.exitstatus})") exit 1
exit status.exitstatus || 1
end end
end end
+1 -1
View File
@@ -1 +1 @@
1773600939 1773604236
@@ -299,6 +299,23 @@ New-NetFirewallRule -DisplayName "Dominio DASUTEN" `
--- ---
## 📋 Registro de Diagnósticos
### 15/03/2026 — Diagnóstico remoto via `./adn/tools/run ssh`
**Herramienta:** CLI `ssh` (P2604-S11, nueva herramienta ADN reutilizable)
| Nodo | Puerto 7022 | Ping | Estado |
|:-----|:-----------:|:----:|:------:|
| srv-dasu | SSH:22 ✅ | ✅ ~95ms via Tailscale | Online (uptime 4d) |
| dc-dasuten | ❌ Cerrado | ❌ timeout | VM apagada o sin SSH |
| sql-dasuten | ✅ Abierto | - | Accesible via proxy srv-dasu |
| pcv-dasu0 | ✅ Abierto | - | Accesible via proxy srv-dasu |
| pc-dasu0 | - | ❌ Offline Tailscale | Offline hace 1d |
**Conclusión:** `sql-dasuten` y `pcv-dasu0` están activas y accesibles. `dc-dasuten` requiere atención (encender VM o verificar SSH).
---
**⚠️ ADVERTENCIA CRÍTICA:** Este plan sigue la filosofía DB-First del ADN. **⚠️ ADVERTENCIA CRÍTICA:** Este plan sigue la filosofía DB-First del ADN.
**🔍 CAMBIO FUNDAMENTAL:** La topología de red compartida (mismo router ISP) cambia radicalmente la estrategia de conectividad, priorizando comunicación directa local sobre soluciones de tunelización como Tailscale. Esto representa una optimización significativa en performance y estabilidad. **🔍 CAMBIO FUNDAMENTAL:** La topología de red compartida (mismo router ISP) cambia radicalmente la estrategia de conectividad, priorizando comunicación directa local sobre soluciones de tunelización como Tailscale. Esto representa una optimización significativa en performance y estabilidad.