docs: Actualización de contexto IA, Drones, Candados y nuevos nodos
This commit is contained in:
+185
-41
@@ -258,7 +258,14 @@ def cmd_run(cripto, boveda, clave, *args)
|
||||
token_user = boveda.get("#{prefijo}_user")
|
||||
user_value = cripto.decrypt(token_user) if token_user
|
||||
elsif clave.include?(':')
|
||||
user_value = clave.split(':').last
|
||||
# Formato: <nodo>:<usuario>[:<tipo>]
|
||||
# Ej: "srv-ns8:rmonla" → usuario="rmonla"
|
||||
# "srv-ns8:rmonla:sudo" → usuario="rmonla" (no "sudo")
|
||||
partes = clave.split(':')
|
||||
if partes.size >= 2
|
||||
# El usuario es siempre la segunda parte (índice 1)
|
||||
user_value = partes[1]
|
||||
end
|
||||
else
|
||||
token_user = boveda.get("#{clave}_user")
|
||||
user_value = cripto.decrypt(token_user) if token_user
|
||||
@@ -366,6 +373,64 @@ def cmd_load(cripto, boveda, clave)
|
||||
$stderr.puts "#{C::GREEN}✓ Credenciales cargadas en variables USR y PASS (archivo temporal seguro).#{C::RESET}"
|
||||
end
|
||||
|
||||
# ─── Comando ssh (ejecutar comando remoto con password de bóveda) ─────
|
||||
# Uso: candados ssh <clave> <usuario@host> '<comando>'
|
||||
# - clave: <nodo>:<usuario> para SSH (busca automáticamente <nodo>:<usuario>:sudo para sudo)
|
||||
# - Si existe clave :sudo separada, la usa para comandos con sudo
|
||||
def cmd_ssh(cripto, boveda, clave, usuario_host, *cmd_args)
|
||||
abort "#{C::RED}✗ Falta nombre de clave.#{C::RESET}" if clave.nil?
|
||||
abort "#{C::RED}✗ Falta usuario@host.#{C::RESET}" if usuario_host.nil? || usuario_host.empty?
|
||||
abort "#{C::RED}✗ Falta comando a ejecutar.#{C::RESET}" if cmd_args.nil? || cmd_args.empty?
|
||||
|
||||
verificar_sesion!
|
||||
|
||||
# Obtener password SSH
|
||||
token_ssh = boveda.get(clave)
|
||||
abort "#{C::RED}✗ Clave '#{clave}' no encontrada.#{C::RESET}" if token_ssh.nil?
|
||||
ssh_pass = cripto.decrypt(token_ssh)
|
||||
safe_ssh_pass = ssh_pass.gsub("'", "'\\''")
|
||||
|
||||
# Buscar automáticamente clave :sudo si existe
|
||||
comando = cmd_args.join(' ')
|
||||
sudo_pass = ssh_pass
|
||||
|
||||
# Intentar buscar clave :sudo basada en la clave SSH
|
||||
if clave.include?(':') && !clave.end_with?(':sudo')
|
||||
# Construir clave sudo: <nodo>:<usuario>:sudo
|
||||
partes = clave.split(':')
|
||||
if partes.size >= 2
|
||||
clave_sudo = "#{partes[0]}:#{partes[1]}:sudo"
|
||||
token_sudo = boveda.get(clave_sudo)
|
||||
if token_sudo
|
||||
sudo_pass = cripto.decrypt(token_sudo)
|
||||
$stderr.puts "#{C::DIM}ℹ Usando clave sudo: #{clave_sudo}#{C::RESET}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
safe_sudo_pass = sudo_pass.gsub("'", "'\\''")
|
||||
|
||||
registrar_acceso('SSH', "Clave: #{clave}, Host: #{usuario_host}, Cmd: #{comando}")
|
||||
|
||||
# Escapar comando para bash remoto: reemplazar ' con '\''
|
||||
comando_escaped = comando.gsub("'", "'\\''")
|
||||
|
||||
# Crear script temporal con el comando y password para sudo
|
||||
require 'securerandom'
|
||||
require 'fileutils'
|
||||
FileUtils.mkdir_p(TMP_DIR) unless File.exist?(TMP_DIR)
|
||||
script_path = File.join(TMP_DIR, ".ssh_sudo_#{SecureRandom.hex(4)}")
|
||||
File.write(script_path, "#!/bin/bash\nexport DEBIAN_FRONTEND=noninteractive\necho '#{safe_sudo_pass}' | sudo -S -p '' bash -c '#{comando_escaped}'\n")
|
||||
File.chmod(0700, script_path)
|
||||
|
||||
# Ejecutar: sshpass para SSH, luego script remoto que hace sudo
|
||||
result = system("sshpass -p '#{safe_ssh_pass}' ssh -o StrictHostKeyChecking=no -tt #{usuario_host} 'bash -s' < '#{script_path}'")
|
||||
FileUtils.rm_f(script_path)
|
||||
exit result ? 0 : 1
|
||||
end
|
||||
|
||||
# ─── Ayuda mejorada para cmd_help_resumida ─────────────────────────────
|
||||
|
||||
# ─── Comando sudo (primitiva reutilizable) ─────────────────────
|
||||
# Resolución inteligente de la clave sudo:
|
||||
# 1. <hostname>:<usuario>:sudo (convención específica)
|
||||
@@ -426,58 +491,130 @@ def cmd_sudo(cripto, boveda, comando_args)
|
||||
exit $?.exitstatus || 0
|
||||
end
|
||||
|
||||
def cmd_help
|
||||
def cmd_help_resumida
|
||||
puts <<~HELP
|
||||
#{C::CYAN}🔐 candados — Gestión segura de secretos#{C::RESET}
|
||||
#{C::CYAN}╔═══════════════════════════════════════════════════════════════════╗#{C::RESET}
|
||||
#{C::CYAN}║#{C::RESET} 🔐 #{C::BOLD}candados — Acceso Seguro a Contraseñas (Sin Exponer)#{C::RESET} #{C::CYAN}║#{C::RESET}
|
||||
#{C::CYAN}╚═══════════════════════════════════════════════════════════════════╝#{C::RESET}
|
||||
|
||||
#{C::GREEN}Uso:#{C::RESET} ruby adn/tools/seguridad/candados.rb <comando> [args]
|
||||
#{C::RED}⚠️ REGLA DE ORO: NUNCA MOSTRAR CONTRASEÑAS#{C::RESET}
|
||||
|
||||
#{C::YELLOW}━━━ Comandos principales ━━━#{C::RESET}
|
||||
#{C::BOLD}run <clave> <cmd>#{C::RESET} #{C::GREEN}★ PRIMARIO#{C::RESET} Ejecutar comando con USR y PASS inyectados
|
||||
#{C::BOLD}sudo <comando>#{C::RESET} Ejecutar con privilegios (resuelve clave automáticamente)
|
||||
#{C::BOLD}load <clave>#{C::RESET} Cargar USR/PASS en shell actual (usar con eval)
|
||||
#{C::GREEN}┌─────────────────────────────────────────────────────────────────┐#{C::RESET}
|
||||
#{C::GREEN}│#{C::RESET} #{C::BOLD}MÉTODOS PARA CARGAR CONTRASEÑAS EN VARIABLES#{C::RESET} #{C::GREEN}│#{C::RESET}
|
||||
#{C::GREEN}└─────────────────────────────────────────────────────────────────┘#{C::RESET}
|
||||
|
||||
#{C::YELLOW}━━━ Gestión de bóveda ━━━#{C::RESET}
|
||||
authorize Abrir candado (autorización temporal 30 min)
|
||||
cerrar Cerrar candado manualmente
|
||||
set <clave> Guardar secreto (input seguro, sin eco)
|
||||
get <clave> Obtener secreto (⚠ imprime a stdout)
|
||||
rm <clave> Eliminar secreto
|
||||
list Listar claves disponibles
|
||||
#{C::BOLD}★ run <clave> 'comando'#{C::RESET} → Ejecuta con $USR y $PASS inyectadas (PRIMARIO)
|
||||
#{C::BOLD}★ load <clave>#{C::RESET} → Carga $USR y $PASS en shell (usar con eval)
|
||||
|
||||
#{C::DIM}Ejemplo: ruby candados.rb run srv-ns8:rmonla 'sshpass -p $PASS ssh $USR@host'#{C::RESET}
|
||||
|
||||
#{C::YELLOW}━━━ Comandos ━━━#{C::RESET}
|
||||
#{C::BOLD}run#{C::RESET} <clave> <cmd> #{C::DIM}Ejecutar con credenciales inyectadas#{C::RESET}
|
||||
#{C::BOLD}load#{C::RESET} <clave> #{C::DIM}Cargar en shell actual (eval)#{C::RESET}
|
||||
#{C::BOLD}sudo#{C::RESET} <comando> #{C::DIM}Ejecutar con privilegios#{C::RESET}
|
||||
#{C::BOLD}ssh#{C::RESET} <clave> <u@h> <cmd> #{C::DIM}SSH remoto con password de bóveda#{C::RESET}
|
||||
#{C::BOLD}authorize#{C::RESET} #{C::DIM}Abrir candado (30 min)#{C::RESET}
|
||||
#{C::BOLD}list#{C::RESET} #{C::DIM}Listar claves disponibles#{C::RESET}
|
||||
#{C::BOLD}set/rm#{C::RESET} <clave> #{C::DIM}Guardar/Eliminar secreto#{C::RESET}
|
||||
|
||||
#{C::DIM}Usá#{C::RESET} ruby candados.rb --help #{C::DIM}para ayuda completa y ejemplos detallados.#{C::RESET}
|
||||
HELP
|
||||
end
|
||||
|
||||
def cmd_help_completo
|
||||
puts <<~HELP
|
||||
#{C::CYAN}╔═══════════════════════════════════════════════════════════════════╗#{C::RESET}
|
||||
#{C::CYAN}║#{C::RESET} 🔐 #{C::BOLD}candados — Acceso Seguro a Contraseñas (Sin Exponer)#{C::RESET} #{C::CYAN}║#{C::RESET}
|
||||
#{C::CYAN}╚═══════════════════════════════════════════════════════════════════╝#{C::RESET}
|
||||
|
||||
#{C::RED}⚠️ REGLA DE ORO: NUNCA MOSTRAR CONTRASEÑAS#{C::RESET}
|
||||
Esta herramienta está diseñada para que las contraseñas #{C::BOLD}NUNCA#{C::RESET} se muestren en pantalla
|
||||
ni queden registradas en logs, históricos de shell o archivos temporales visibles.
|
||||
|
||||
#{C::GREEN}┌─────────────────────────────────────────────────────────────────┐#{C::RESET}
|
||||
#{C::GREEN}│#{C::RESET} #{C::BOLD}MÉTODOS PARA CARGAR CONTRASEÑAS EN VARIABLES#{C::RESET} #{C::GREEN}│#{C::RESET}
|
||||
#{C::GREEN}└─────────────────────────────────────────────────────────────────┘#{C::RESET}
|
||||
|
||||
#{C::BOLD}★ MÉTODO 1: run (PRIMARIO - Recomendado para comandos)#{C::RESET}
|
||||
Ejecuta un comando con USR y PASS inyectadas en su entorno.
|
||||
La contraseña #{C::BOLD}NUNCA#{C::RESET} se imprime ni queda en logs.
|
||||
|
||||
#{C::DIM}ruby candados.rb run <clave> 'comando que usa $USR y $PASS'#{C::RESET}
|
||||
|
||||
#{C::CYAN}Ejemplos:#{C::RESET}
|
||||
#{C::GREEN}✓#{C::RESET} ruby candados.rb run srv-dasu:rmonla 'sshpass -p $PASS ssh $USR@host'
|
||||
#{C::GREEN}✓#{C::RESET} ruby candados.rb run srv-ns8:root 'ssh $USR@$HOSTNAME "comando"'
|
||||
|
||||
#{C::BOLD}★ MÉTODO 2: load (Para shell interactivo)#{C::RESET}
|
||||
Carga USR y PASS en variables de entorno del shell actual.
|
||||
Usa un archivo temporal (600) que se auto-elimina tras el source.
|
||||
|
||||
#{C::DIM}eval $(ruby candados.rb load <clave>)#{C::RESET}
|
||||
|
||||
#{C::CYAN}Ejemplo:#{C::RESET}
|
||||
#{C::GREEN}✓#{C::RESET} eval $(ruby candados.rb load srv-dasu:rmonla)
|
||||
#{C::DIM} → Carga $USR y $PASS en tu shell actual#{C::RESET}
|
||||
#{C::GREEN}✓#{C::RESET} sshpass -p $PASS ssh $USR@servidor
|
||||
|
||||
#{C::YELLOW}━━━ Otros Comandos ━━━#{C::RESET}
|
||||
#{C::BOLD}sudo <comando>#{C::RESET} Ejecutar con sudo (resuelve contraseña automáticamente)
|
||||
#{C::BOLD}authorize#{C::RESET} Abrir candado (sesión de 30 min, requerida para operar)
|
||||
#{C::BOLD}cerrar#{C::RESET} Cerrar sesión manualmente antes de los 30 min
|
||||
#{C::BOLD}abrir#{C::RESET} Cargar clave SSH en ssh-add (usa passphrase de bóveda)
|
||||
|
||||
#{C::YELLOW}━━━ Gestión de Bóveda ━━━#{C::RESET}
|
||||
#{C::BOLD}set <clave>#{C::RESET} Guardar secreto (input seguro sin eco)
|
||||
#{C::BOLD}list#{C::RESET} Listar claves disponibles (no muestra valores)
|
||||
#{C::BOLD}rm <clave>#{C::RESET} Eliminar una clave de la bóveda
|
||||
|
||||
#{C::YELLOW}━━━ Utilidades ━━━#{C::RESET}
|
||||
abrir Autorizar SSH (cargar passphrase)
|
||||
encrypt <texto> Cifrar texto plano → token
|
||||
decrypt <token> Descifrar token → texto plano
|
||||
#{C::BOLD}encrypt <texto>#{C::RESET} Cifrar texto plano → token (para bóveda)
|
||||
#{C::BOLD}decrypt <token>#{C::RESET} Descifrar token → texto plano
|
||||
|
||||
#{C::YELLOW}Resolución automática de claves:#{C::RESET}
|
||||
#{C::DIM}• run/load: Detecta pares USR/PASS por convención:#{C::RESET}
|
||||
srv-dasu:rmonla → USR=rmonla, PASS=(valor cifrado)
|
||||
tailscale_dasuten → busca _user/_pass automáticamente
|
||||
#{C::DIM}• sudo: Resuelve la clave de sudo automáticamente:#{C::RESET}
|
||||
<hostname>:<usuario>:sudo (ej: ns8:rmonla:sudo)
|
||||
#{C::YELLOW}━━━ Resolución Automática de Claves ━━━#{C::RESET}
|
||||
#{C::DIM}• run/load con formato `servidor:usuario`:#{C::RESET}
|
||||
srv-dasu:rmonla → USR=rmonla, PASS=(descifrado de bóveda)
|
||||
#{C::DIM}• run/load con formato `prefijo_pass`:#{C::RESET}
|
||||
tailscale_dasuten_pass → busca automáticamente tailscale_dasuten_user
|
||||
#{C::DIM}• sudo resuelve la clave automáticamente:#{C::RESET}
|
||||
Busca: <hostname>:<usuario>:sudo → fallback: `sudo`
|
||||
|
||||
#{C::YELLOW}Ejemplos:#{C::RESET}
|
||||
#{C::DIM}# 1. Autorizar (dura 30 min)#{C::RESET}
|
||||
ruby candados.rb authorize
|
||||
#{C::YELLOW}━━━ Flujo de Trabajo Típico ━━━#{C::RESET}
|
||||
#{C::DIM}1. Autorizar (una vez cada 30 min):#{C::RESET}
|
||||
ruby candados.rb authorize
|
||||
|
||||
#{C::GREEN}# 2. Ejecutar comando con credenciales (RECOMENDADO)#{C::RESET}
|
||||
ruby candados.rb run srv-dasu:rmonla 'sshpass -p $PASS ssh $USR@host'
|
||||
ruby candados.rb run admindasu SSHPASS 'sshpass -e ssh root@host'
|
||||
#{C::DIM}2. Ejecutar comandos (Método recomendado):#{C::RESET}
|
||||
ruby candados.rb run srv-ns8:rmonla 'sshpass -p $PASS ssh $USR@host "comando"'
|
||||
|
||||
#{C::DIM}# 3. Ejecutar con sudo#{C::RESET}
|
||||
ruby candados.rb sudo tailscale set --operator=$USER
|
||||
#{C::DIM}3. O cargar en shell interactivo:#{C::RESET}
|
||||
eval $(ruby candados.rb load srv-ns8:rmonla)
|
||||
sshpass -p $PASS ssh $USR@host
|
||||
|
||||
#{C::DIM}# 4. Cargar en shell interactivo (secundario)#{C::RESET}
|
||||
eval $(ruby candados.rb load srv-dasu:rmonla)
|
||||
#{C::RED}⚠️ ADVERTENCIAS DE SEGURIDAD:#{C::RESET}
|
||||
#{C::BOLD}• NUNCA usar `get`#{C::RESET} en automatizaciones → imprime la contraseña a stdout
|
||||
#{C::BOLD}• `run` y `sudo`#{C::RESET} → las credenciales no dejan rastro en logs
|
||||
#{C::BOLD}• `load`#{C::RESET} → archivo temporal con permisos 600, auto-borrado
|
||||
#{C::BOLD}• Siempre ejecutar `authorize`#{C::RESET} antes de operar (sesión de 30 min)
|
||||
|
||||
#{C::RED}⚠ Seguridad:#{C::RESET}
|
||||
#{C::DIM}• run/sudo: Las credenciales NUNCA se imprimen en pantalla ni logs.#{C::RESET}
|
||||
#{C::DIM}• load: Usa archivo temporal (600) con autoborrado, nada visible.#{C::RESET}
|
||||
#{C::DIM}• get: ⚠ Imprime el valor a stdout. Usar solo si es necesario.#{C::RESET}
|
||||
#{C::DIM}Documentación completa: adn/tools/candados/README.md#{C::RESET}
|
||||
HELP
|
||||
end
|
||||
|
||||
alias cmd_help cmd_help_completo
|
||||
|
||||
# ─── Recordatorio de Seguridad (solo en ayuda) ──────────────────
|
||||
def mostrar_recordatorio_seguridad
|
||||
puts <<~RECORDATORIO
|
||||
#{C::CYAN}╔════════════════════════════════════════════════════════════════╗#{C::RESET}
|
||||
#{C::CYAN}║#{C::RESET} 🔐 #{C::BOLD}REGLA DE ORO: NUNCA MOSTRAR CONTRASEÑAS#{C::RESET} #{C::CYAN}║#{C::RESET}
|
||||
#{C::CYAN}║#{C::RESET} #{C::GREEN}✓ CORRECTO:#{C::RESET} ruby candados.rb run <clave> '<cmd>' #{C::CYAN}║#{C::RESET}
|
||||
#{C::CYAN}║#{C::RESET} #{C::RED}✗ INCORRECTO:#{C::RESET} ruby candados.rb get <clave> #{C::CYAN}║#{C::RESET}
|
||||
#{C::CYAN}║#{C::RESET} → `run` inyecta USR/PASS sin mostrar. `get` imprime y deja rastro. #{C::CYAN}║#{C::RESET}
|
||||
#{C::CYAN}╚════════════════════════════════════════════════════════════════╝#{C::RESET}
|
||||
RECORDATORIO
|
||||
puts
|
||||
end
|
||||
|
||||
# ─── Main ────────────────────────────────────────────────────────────
|
||||
if __FILE__ == $0
|
||||
comando = ARGV[0]
|
||||
@@ -496,6 +633,10 @@ if __FILE__ == $0
|
||||
cripto = Cripto.new(cargar_master_key)
|
||||
boveda = Boveda.new(BOVEDA)
|
||||
cmd_sudo(cripto, boveda, ARGV[1..])
|
||||
when 'ssh'
|
||||
cripto = Cripto.new(cargar_master_key)
|
||||
boveda = Boveda.new(BOVEDA)
|
||||
cmd_ssh(cripto, boveda, ARGV[1], ARGV[2], *ARGV[3..])
|
||||
when 'encrypt', 'decrypt', 'get', 'load', 'run', 'set', 'rm', 'list'
|
||||
cripto = Cripto.new(cargar_master_key)
|
||||
boveda = Boveda.new(BOVEDA)
|
||||
@@ -509,11 +650,14 @@ if __FILE__ == $0
|
||||
when 'rm' then cmd_rm(boveda, arg)
|
||||
when 'list' then cmd_list(boveda)
|
||||
end
|
||||
when 'help', 'ayuda', '-h', '--help', nil
|
||||
cmd_help
|
||||
when '--help', '-h'
|
||||
cmd_help_completo
|
||||
when 'help', 'ayuda', nil
|
||||
cmd_help_resumida
|
||||
else
|
||||
$stderr.puts "#{C::RED}✗ Comando desconocido: '#{comando}'#{C::RESET}"
|
||||
cmd_help
|
||||
$stderr.puts ""
|
||||
cmd_help_resumida
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user