Evolución de seguridad en ns8-candados: MFA por sesión (authorize/cerrar), permisos 600 y reubicación de master key

This commit is contained in:
Ricardo Monla
2026-03-07 07:52:08 -03:00
parent ac9bdf516b
commit 8243315e4f
13 changed files with 1967 additions and 10 deletions
+43 -10
View File
@@ -31,9 +31,16 @@ require 'fileutils'
# ─── Rutas ───────────────────────────────────────────────────────────
TOOL_DIR = File.expand_path(__dir__)
PROJECT_DIR = File.expand_path('../..', TOOL_DIR)
MASTER_KEY = File.join(PROJECT_DIR, '.master.key')
MASTER_KEY = File.join(TOOL_DIR, '.master.key')
OLD_KEY = File.expand_path('../../.master.key', TOOL_DIR)
BOVEDA = File.join(TOOL_DIR, '.boveda.json')
SESION = File.join(TOOL_DIR, '.session')
# ─── Transición de Seguridad ─────────────────────────────────────────
if File.exist?(OLD_KEY) && !File.exist?(MASTER_KEY)
FileUtils.mv(OLD_KEY, MASTER_KEY)
FileUtils.chmod(0600, MASTER_KEY)
end
# ─── Colores ─────────────────────────────────────────────────────────
module C
@@ -110,12 +117,35 @@ def cargar_master_key
require 'securerandom'
File.write(MASTER_KEY, SecureRandom.hex(32))
FileUtils.chmod(0600, MASTER_KEY)
$stderr.puts "#{C::YELLOW}⚠ Nueva clave maestra creada en #{MASTER_KEY}#{C::RESET}"
$stderr.puts "#{C::YELLOW} NO subir a Git (ya cubierta por .gitignore).#{C::RESET}"
$stderr.puts "#{C::YELLOW}⚠ Nueva clave maestra creada en local: #{MASTER_KEY}#{C::RESET}"
end
File.read(MASTER_KEY)
end
# ─── Gestión de Sesión (MFA) ─────────────────────────────────────────
def autorizar!
File.write(SESION, Time.now.to_i.to_s)
FileUtils.chmod(0600, SESION)
$stderr.puts "#{C::GREEN}✓ Autorización concedida (Válida por 5 minutos).#{C::RESET}"
end
def cerrar_sesion!
FileUtils.rm_f(SESION)
$stderr.puts "#{C::YELLOW}🔒 Sesión cerrada. Candado puesto.#{C::RESET}"
end
def verificar_sesion!
unless File.exist?(SESION)
abort "#{C::RED}✗ Error: El candado está puesto. Ejecutá 'ruby #{File.basename($0)} authorize' para abrirlo.#{C::RESET}"
end
inicio = File.read(SESION).to_i
if Time.now.to_i - inicio > 300 # 5 minutos
cerrar_sesion!
abort "#{C::RED}✗ Sesión expirada. Por seguridad, volvé a autorizar.#{C::RESET}"
end
end
# ─── Comandos ────────────────────────────────────────────────────────
def cmd_abrir
@@ -146,10 +176,9 @@ end
def cmd_get(cripto, boveda, clave)
abort "#{C::RED}✗ Falta nombre de clave.#{C::RESET}" if clave.nil?
verificar_sesion!
token = boveda.get(clave)
abort "#{C::RED}✗ Clave '#{clave}' no encontrada.#{C::RESET}" if token.nil?
# Salida limpia (solo el valor) para uso en scripts:
# PASS=$(ruby tools/ns8-candados/ns8-candados.rb get admindasu)
puts cripto.decrypt(token)
end
@@ -187,6 +216,8 @@ def cmd_help
#{C::GREEN}Uso:#{C::RESET} ruby ns8-candados.rb <comando> [args]
#{C::YELLOW}Comandos:#{C::RESET}
authorize Abrir candado (autorización temporal 5 min)
cerrar Cerrar candado manual (eliminar sesión)
abrir Autorizar SSH (cargar passphrase)
encrypt <texto> Cifrar texto plano token
decrypt <token> Descifrar token texto plano
@@ -196,8 +227,8 @@ def cmd_help
list Listar claves disponibles
#{C::YELLOW}Ejemplos:#{C::RESET}
#{C::DIM}# Guardar un secreto#{C::RESET}
ruby ns8-candados.rb set mi_password
# Autorizar para operar
ruby ns8-candados.rb authorize
#{C::DIM}# Usar en scripts (salida limpia)#{C::RESET}
PASS=$(ruby ns8-candados.rb get admindasu)
@@ -210,8 +241,10 @@ comando = ARGV[0]
arg = ARGV[1]
case comando
when 'abrir'
cmd_abrir
when 'authorize'
autorizar!
when 'cerrar'
cerrar_sesion!
when 'encrypt', 'decrypt', 'get', 'set', 'rm', 'list'
cripto = Cripto.new(cargar_master_key)
boveda = Boveda.new(BOVEDA)