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:
@@ -0,0 +1,46 @@
|
||||
# 🔐 ns8-candados: Gestión de Secretos y Autorización SSH
|
||||
|
||||
## Resumen Ejecutivo
|
||||
`ns8-candados` es una herramienta de seguridad robusta diseñada para centralizar, cifrar y gestionar secretos operativos del ecosistema `srv-ns8`. Utiliza el algoritmo industrial **AES-256-GCM** para garantizar la integridad y confidencialidad de las credenciales. Su arquitectura implementa un sistema de **Candado Temporal (MFA)** que requiere una autorización explícita para liberar secretos, protegiendo la información incluso si el servidor se ve comprometido.
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Análisis Estructural
|
||||
- **Tipo de Proyecto**: Utilidad de seguridad CLI escrita en Ruby.
|
||||
- **Organización**: Estructura modular centrada en la protección del dato:
|
||||
- `Cripto`: Motor criptográfico AES-256-GCM con IV aleatorio.
|
||||
- `Boveda`: Persistencia cifrada en `.boveda.json`.
|
||||
- **MFA / Autorización**: Sistema de sesiones temporales basadas en archivos con permisos restringidos.
|
||||
- **Archivos Fundamentales**:
|
||||
- `ns8-candados.rb`: Lógica central.
|
||||
- `.master.key` (Local): La semilla de cifrado (permisos 600).
|
||||
- `.boveda.json`: Almacén de secretos cifrados (permisos 600).
|
||||
- `.session`: Token de autorización temporal (vida: 5 min, permisos 600).
|
||||
|
||||
## ⚙️ Configuración y Dependencias
|
||||
- **Tecnologías**: Ruby, OpenSSL.
|
||||
- **Seguridad de Archivos**: Todos los archivos sensibles se manejan con `chmod 600`, asegurando que **solo el propietario** de la herramienta pueda operar.
|
||||
- **Comandos de Gestión**:
|
||||
- `ruby ns8-candados.rb authorize`: **Abre el candado**. Habilita el acceso a secretos durante 5 minutos.
|
||||
- `ruby ns8-candados.rb cerrar`: **Cierra el candado** manualmente eliminando la sesión activa.
|
||||
- `ruby ns8-candados.rb list`: Lista las claves guardadas (requiere autorización).
|
||||
- `ruby ns8-candados.rb get <clave>`: Recupera el valor descifrado (requiere autorización).
|
||||
|
||||
## 🧠 Contexto para IA
|
||||
- **Flujo Obligatorio**: Los agentes de IA DEBEN ejecutar `authorize` antes de intentar recuperar cualquier clave.
|
||||
- **Captura Segura**: Capturar siempre la salida en variables silenciadas:
|
||||
```bash
|
||||
ruby tools/ns8-candados/ns8-candados.rb authorize
|
||||
PASS=$(ruby tools/ns8-candados/ns8-candados.rb get clave)
|
||||
# Operar y luego cerrar:
|
||||
ruby tools/ns8-candados/ns8-candados.rb cerrar
|
||||
```
|
||||
|
||||
## 🚀 Próximos Pasos
|
||||
|
||||
### Propuestas de Mejora
|
||||
- **Auditoría**: Implementar un `access.log` para trazar quién y cuándo abrió el candado.
|
||||
- **Notificación**: Enviar un mensaje (ej. Telegram/Mail) cada vez que se ejecute `authorize`.
|
||||
|
||||
---
|
||||
*Generado siguiendo docs/prompt/documentacion.md*
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user