[Tools] ns8-candados: herramienta unificada de seguridad (reemplaza candado.rb + secret_box.rb)
This commit is contained in:
+3
-4
@@ -22,8 +22,7 @@ logs/
|
||||
tmp/
|
||||
.temp/
|
||||
.cache/
|
||||
scripts/.agent_token
|
||||
scripts/.agent_token
|
||||
scripts/.agent_secrets.json
|
||||
scripts/seguridad/.agent_secrets.json
|
||||
|
||||
# Seguridad (ns8-candados)
|
||||
tools/ns8-candados/.boveda.json
|
||||
.agent_token
|
||||
|
||||
+42
-47
@@ -2,7 +2,7 @@
|
||||
|
||||
> Referencia canónica: [`adn/00_indice.md`](00_indice.md)
|
||||
|
||||
Esta hebra gobierna los protocolos de seguridad de red, gestión de secretos, autorización SSH y contingencia ante fallos críticos de conectividad.
|
||||
Esta hebra gobierna los protocolos de seguridad de red, gestión de secretos y autorización SSH.
|
||||
|
||||
## 1. Protocolo de Seguridad de Red (Hombre Muerto)
|
||||
|
||||
@@ -16,78 +16,73 @@ Esta hebra gobierna los protocolos de seguridad de red, gestión de secretos, au
|
||||
3. **Si no hay confirmación**: Revertir cambios inmediatamente y restaurar estado anterior.
|
||||
4. **Si hay confirmación**: Hacer cambios persistentes.
|
||||
|
||||
## 2. Gestión de Secretos (Bóveda Operativa)
|
||||
## 2. Gestión de Secretos — `ns8-candados`
|
||||
|
||||
Todos los artefactos de seguridad residen en `scripts/seguridad/`.
|
||||
Herramienta unificada de seguridad: [`tools/ns8-candados/ns8-candados.rb`](../tools/ns8-candados/ns8-candados.rb)
|
||||
|
||||
### 2.1 Prohibición de Texto Plano
|
||||
La Inteligencia Artificial operante tiene **expresamente prohibido** solicitar contraseñas administrativas, tokens o claves privadas al operador, así como también plasmarlas en logs, comandos literales temporales o en la bitácora.
|
||||
|
||||
### 2.2 Componentes de la Bóveda
|
||||
### 2.2 Componentes
|
||||
|
||||
| Archivo | Ubicación | Git | Descripción |
|
||||
| :--- | :--- | :---: | :--- |
|
||||
| `secret_box.rb` | `scripts/seguridad/` | ✅ | Motor de cifrado AES-256-GCM. Encripta/desencripta payloads. |
|
||||
| `.agent_secrets.json` | `scripts/seguridad/` | ❌ | Payloads cifrados (sudo, rsa, domain_admin). |
|
||||
| `ns8-candados.rb` | `tools/ns8-candados/` | ✅ | Script Ruby unificado: cifrado, bóveda y autorización SSH. |
|
||||
| `.boveda.json` | `tools/ns8-candados/` | ❌ | Payloads AES-256-GCM cifrados. |
|
||||
| `.master.key` | Raíz del proyecto | ❌ | Clave maestra de la bóveda (nunca en Git). |
|
||||
|
||||
### 2.3 Consumo Efímero de Bóveda
|
||||
|
||||
Ante requerimientos de escalada de privilegios (`sudo`, `root`, `sshpass`), la IA accederá de forma autónoma a la bóveda:
|
||||
### 2.3 Comandos Disponibles
|
||||
|
||||
```bash
|
||||
# Ejemplo: ejecutar comando con privilegios en un nodo remoto
|
||||
CMD_PASS=$(ruby scripts/seguridad/secret_box.rb decrypt "$PAYLOAD")
|
||||
sshpass -p "$CMD_PASS" ssh root@10.0.10.205 "comando"
|
||||
ruby tools/ns8-candados/ns8-candados.rb <comando> [args]
|
||||
```
|
||||
|
||||
**Payloads disponibles** (en `.agent_secrets.json`):
|
||||
- `sudo`: Contraseña de sudo/root del entorno local.
|
||||
- `rsa`: Passphrase de la llave SSH (`~/.ssh/id_rsa`).
|
||||
- `domain_admin`: Credenciales de administrador del dominio `dasuten.utnlr`.
|
||||
| Comando | Descripción |
|
||||
| :--- | :--- |
|
||||
| `abrir` | Autorizar SSH (cargar llave con passphrase, sin timeout). |
|
||||
| `encrypt <texto>` | Cifrar texto plano → token. |
|
||||
| `decrypt <token>` | Descifrar token → texto plano. |
|
||||
| `get <clave>` | Obtener secreto de la bóveda (salida limpia para pipelines). |
|
||||
| `set <clave>` | Guardar secreto (input interactivo sin eco). |
|
||||
| `rm <clave>` | Eliminar secreto de la bóveda. |
|
||||
| `list` | Listar claves disponibles. |
|
||||
|
||||
## 3. Autorización SSH (Candado)
|
||||
### 2.4 Consumo Efímero (IA)
|
||||
|
||||
```bash
|
||||
# Obtener password para uso en comando remoto
|
||||
PASS=$(ruby tools/ns8-candados/ns8-candados.rb get admindasu)
|
||||
sshpass -p "$PASS" ssh -J root@10.0.10.205 admindasu@10.0.100.12 "cmd"
|
||||
```
|
||||
|
||||
### 2.5 Payloads en la Bóveda
|
||||
|
||||
| Clave | Propósito |
|
||||
| :--- | :--- |
|
||||
| `sudo` | Contraseña de sudo/root del entorno local. |
|
||||
| `rsa` | Passphrase de la llave SSH (`~/.ssh/id_rsa`). |
|
||||
| `domain_admin` | Credenciales del Administrador del dominio `dasuten.utnlr`. |
|
||||
| `admindasu` | Credenciales del usuario `admindasu` (Domain Admin). |
|
||||
|
||||
## 3. Autorización SSH
|
||||
|
||||
### 3.1 Problema
|
||||
La llave SSH del operador está protegida con passphrase. El agente SSH no retiene las identidades entre sesiones. La IA no puede inyectar la passphrase de forma no interactiva sin `expect`.
|
||||
La llave SSH del operador está protegida con passphrase. El agente SSH no retiene las identidades entre sesiones.
|
||||
|
||||
### 3.2 Solución: `candado.rb`
|
||||
El operador ejecuta el script **interactivamente** para autorizar a la IA durante una ventana de tiempo:
|
||||
### 3.2 Solución: `ns8-candados.rb abrir`
|
||||
El operador ejecuta el comando **interactivamente** para cargar su identidad SSH:
|
||||
|
||||
```bash
|
||||
ruby scripts/seguridad/candado.rb [minutos]
|
||||
ruby tools/ns8-candados/ns8-candados.rb abrir
|
||||
```
|
||||
|
||||
**Flujo:**
|
||||
1. 🧹 Limpia identidades previas del agente SSH (`ssh-add -D`).
|
||||
2. 🔑 Pide la passphrase al operador (`ssh-add`).
|
||||
3. ✅ Verifica que la llave se cargó correctamente.
|
||||
4. 🔓 Genera un token temporal (`.agent_token`, 5 min por defecto).
|
||||
5. La IA puede ahora ejecutar comandos SSH sin intervención.
|
||||
|
||||
### 3.3 Flujo Operativo Completo
|
||||
|
||||
```
|
||||
OPERADOR IA (Agente)
|
||||
│ │
|
||||
│ ruby candado.rb 10 │
|
||||
│ → Ingresa passphrase │
|
||||
│ → Token activo 10 min │
|
||||
│ │
|
||||
│ "Listo, el candado está abierto"│
|
||||
│─────────────────────────────────→│
|
||||
│ │ ssh root@srv-dasu "qm create..."
|
||||
│ │ (usa llave cargada en agente)
|
||||
│ │
|
||||
│ ⏳ Token expira │
|
||||
│ │ ssh → ❌ (sin identidad)
|
||||
```
|
||||
Las identidades permanecen activas mientras dure la sesión del agente SSH (sin timeout artificial).
|
||||
|
||||
## 4. Archivos Protegidos (`.gitignore`)
|
||||
|
||||
Los siguientes artefactos **nunca** deben ser rastreados por Git:
|
||||
- `.master.key` (clave maestra de la bóveda)
|
||||
- `scripts/seguridad/.agent_secrets.json` (payloads cifrados)
|
||||
- `.agent_token` (token temporal de autorización)
|
||||
- `tools/ns8-candados/.boveda.json` (payloads cifrados)
|
||||
- `.agent_token` (token legacy, obsoleto)
|
||||
- `*.key` (claves privadas)
|
||||
- `tmp/` (artefactos transitorios de la IA)
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
### ⏳ En Proceso
|
||||
| ID | NODO | DETALLE |
|
||||
| :--- | :--- | :--- |
|
||||
| [✅ - SEC01 - **Refactorización ns8-candados**](#srv-ns8) | srv-ns8 | Unificación de scripts de seguridad en `tools/ns8-candados/`. |
|
||||
|
||||
### 📝 Resumen de Actividades
|
||||
| NODO | RESUMEN INTEGRAL |
|
||||
@@ -22,6 +23,15 @@
|
||||
|
||||
## 📂 Actividades Detalladas
|
||||
|
||||
### srv-ns8
|
||||
|
||||
#### ✅ - SEC01 - Refactorización ns8-candados
|
||||
🔐 Unificación de `candado.rb`, `secret_box.rb` y `.agent_secrets.json` en una sola herramienta Ruby `ns8-candados.rb`.
|
||||
|
||||
| Tiempo | Descripción |
|
||||
| :--- | :--- |
|
||||
| ✅ 08:34 | (IA) 🔐 Refactorización completada. Nuevo script unificado con subcomandos (`abrir`, `get`, `set`, `encrypt`, `decrypt`, `list`, `rm`). Bóveda migrada. ADN actualizado. Scripts legacy eliminados. `[Físico: 0:30 hs]` |
|
||||
|
||||
### Operaciones Centrales
|
||||
|
||||
#### 👁️ - SINC01 - Sincronización de Inicio de Jornada
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# script: candado.rb
|
||||
# Uso: ruby scripts/candado.rb [minutos]
|
||||
# Descripción: Genera un token de autorización temporal para que el Agente pueda ejecutar tareas críticas.
|
||||
# Requiere que el usuario tenga claves SSH cargadas (prueba de identidad).
|
||||
|
||||
require 'json'
|
||||
require 'fileutils'
|
||||
|
||||
TOKEN_FILE = File.expand_path('../../.agent_token', __dir__)
|
||||
DEFAULT_DURATION_MINS = 5
|
||||
|
||||
# 1. Validación de Identidad (Proof of User Presence)
|
||||
puts "🔐 Requerimiento de Protocolo: Autorización FRESCA."
|
||||
puts "🧹 Limpiando identidades previas en el agente SSH para exigir validación..."
|
||||
system("ssh-add -D > /dev/null 2>&1") # Borra claves cacheadas para forzar el prompt
|
||||
|
||||
puts "🔑 Por favor, ingresa tu passphrase para autorizar al Agente:"
|
||||
# Intentamos cargar las claves interactivamente (ahora sí pedirá pass porque borramos las anteriores)
|
||||
system("ssh-add")
|
||||
|
||||
# Verificamos que la carga haya sido exitosa
|
||||
system("ssh-add -l > /dev/null 2>&1")
|
||||
unless $?.success?
|
||||
puts "❌ ERROR: No se detectaron identidades SSH."
|
||||
puts " Debes completar el desafío de identidad (passphrase) para continuar."
|
||||
exit 1
|
||||
end
|
||||
|
||||
# 2. Generación del Token
|
||||
duration = (ARGV[0] || DEFAULT_DURATION_MINS).to_i
|
||||
expires_at = Time.now + (duration * 60)
|
||||
|
||||
token_data = {
|
||||
created_at: Time.now.to_s,
|
||||
expires_at: expires_at.to_s,
|
||||
duration_mins: duration,
|
||||
authorized_by: ENV['USER'] || 'unknown',
|
||||
signature: rand(36**8).to_s(36) # Firma aleatoria simple
|
||||
}
|
||||
|
||||
File.write(TOKEN_FILE, JSON.pretty_generate(token_data))
|
||||
FileUtils.chmod(0600, TOKEN_FILE) # Solo lectura para el usuario
|
||||
|
||||
puts "\n🔓 VACANCIA DE SEGURIDAD ACTIVADA"
|
||||
puts "--------------------------------"
|
||||
puts "✅ Agente AUTORIZADO para operaciones críticas."
|
||||
puts "⏳ Ventana de tiempo: #{duration} minutos."
|
||||
puts "💀 Expira: #{expires_at.strftime('%H:%M:%S')}"
|
||||
puts "🔑 Token: #{TOKEN_FILE}"
|
||||
puts "\n>> Ahora puedes pedirle al Agente que ejecute sus tareas."
|
||||
@@ -1,91 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'openssl'
|
||||
require 'base64'
|
||||
require 'json'
|
||||
|
||||
class SecretBox
|
||||
ALGORITHM = 'aes-256-gcm'.freeze
|
||||
|
||||
def initialize(master_key_content)
|
||||
# Generar una clave de 32 bytes usando SHA256 sobre el contenido del master_key
|
||||
@key = Digest::SHA256.digest(master_key_content.strip)
|
||||
end
|
||||
|
||||
def encrypt(plaintext)
|
||||
cipher = OpenSSL::Cipher.new(ALGORITHM)
|
||||
cipher.encrypt
|
||||
cipher.key = @key
|
||||
iv = cipher.random_iv
|
||||
|
||||
ciphertext = cipher.update(plaintext.to_s) + cipher.final
|
||||
auth_tag = cipher.auth_tag
|
||||
|
||||
# Base64 encodear las 3 partes para un JSON
|
||||
payload = {
|
||||
iv: Base64.strict_encode64(iv),
|
||||
tag: Base64.strict_encode64(auth_tag),
|
||||
data: Base64.strict_encode64(ciphertext)
|
||||
}
|
||||
|
||||
Base64.strict_encode64(payload.to_json)
|
||||
end
|
||||
|
||||
def decrypt(encrypted_payload)
|
||||
parsed_payload = JSON.parse(Base64.strict_decode64(encrypted_payload), symbolize_names: true)
|
||||
|
||||
cipher = OpenSSL::Cipher.new(ALGORITHM)
|
||||
cipher.decrypt
|
||||
cipher.key = @key
|
||||
cipher.iv = Base64.strict_decode64(parsed_payload[:iv])
|
||||
cipher.auth_tag = Base64.strict_decode64(parsed_payload[:tag])
|
||||
|
||||
cipher.update(Base64.strict_decode64(parsed_payload[:data])) + cipher.final
|
||||
rescue => e
|
||||
raise "Error al desencriptar. Clave incorrecta o payload dañado. Detalles: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $0
|
||||
action = ARGV[0]
|
||||
|
||||
unless ['encrypt', 'decrypt'].include?(action)
|
||||
puts "Uso:"
|
||||
puts " ruby #{$0} encrypt <texto_plano>"
|
||||
puts " ruby #{$0} decrypt <texto_encriptado>"
|
||||
exit 1
|
||||
end
|
||||
|
||||
text = ARGV[1]
|
||||
if text.nil? || text.empty?
|
||||
puts "Por favor, proporciona el texto a #{action}."
|
||||
puts "Ejemplo:"
|
||||
puts " ruby #{$0} encrypt \"MiSuperPassword\""
|
||||
exit 1
|
||||
end
|
||||
|
||||
# Buscar el archivo .master.key en el root del proyecto
|
||||
key_path = File.join(File.expand_path('../..', __dir__), '.master.key')
|
||||
|
||||
unless File.exist?(key_path)
|
||||
require 'securerandom'
|
||||
File.write(key_path, SecureRandom.hex(32))
|
||||
puts "⚠️ ATENCIÓN: Se ha creado una nueva clave maestra en '#{key_path}'"
|
||||
puts " Asegúrate de NO subir este archivo a ningún repositorio (ya está cubierto por **/*.key en .gitignore)."
|
||||
puts " Cualquiera que tenga este archivo podrá desencriptar los textos.\n\n"
|
||||
end
|
||||
|
||||
box = SecretBox.new(File.read(key_path))
|
||||
|
||||
begin
|
||||
if action == 'encrypt'
|
||||
puts box.encrypt(text)
|
||||
else
|
||||
puts box.decrypt(text)
|
||||
end
|
||||
rescue => e
|
||||
puts "Error: #{e.message}"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
Executable
+222
@@ -0,0 +1,222 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
# ns8-candados — Gestión unificada de secretos y autorización SSH
|
||||
# Uso: ruby tools/ns8-candados/ns8-candados.rb <comando> [args]
|
||||
#
|
||||
# Comandos:
|
||||
# abrir Autorizar SSH (cargar llave con passphrase)
|
||||
# encrypt <txt> Cifrar texto plano
|
||||
# decrypt <tok> Descifrar payload
|
||||
# get <clave> Obtener secreto de la bóveda (salida: solo el valor)
|
||||
# set <clave> Guardar secreto (pide input interactivo, sin eco)
|
||||
# rm <clave> Eliminar secreto de la bóveda
|
||||
# list Listar claves disponibles en la bóveda
|
||||
|
||||
require 'openssl'
|
||||
require 'base64'
|
||||
require 'json'
|
||||
require 'io/console'
|
||||
require 'fileutils'
|
||||
|
||||
# ─── Rutas ───────────────────────────────────────────────────────────
|
||||
TOOL_DIR = File.expand_path(__dir__)
|
||||
PROJECT_DIR = File.expand_path('../..', TOOL_DIR)
|
||||
MASTER_KEY = File.join(PROJECT_DIR, '.master.key')
|
||||
BOVEDA = File.join(TOOL_DIR, '.boveda.json')
|
||||
|
||||
# ─── Colores ─────────────────────────────────────────────────────────
|
||||
module C
|
||||
RESET = "\e[0m"
|
||||
GREEN = "\e[32m"
|
||||
CYAN = "\e[36m"
|
||||
YELLOW = "\e[33m"
|
||||
RED = "\e[31m"
|
||||
DIM = "\e[2m"
|
||||
end
|
||||
|
||||
# ─── Motor Criptográfico (AES-256-GCM) ──────────────────────────────
|
||||
class Cripto
|
||||
ALGORITHM = 'aes-256-gcm'
|
||||
|
||||
def initialize(key_content)
|
||||
@key = Digest::SHA256.digest(key_content.strip)
|
||||
end
|
||||
|
||||
def encrypt(plaintext)
|
||||
cipher = OpenSSL::Cipher.new(ALGORITHM).tap do |c|
|
||||
c.encrypt
|
||||
c.key = @key
|
||||
end
|
||||
iv = cipher.random_iv
|
||||
ciphertext = cipher.update(plaintext.to_s) + cipher.final
|
||||
payload = { iv: b64(iv), tag: b64(cipher.auth_tag), data: b64(ciphertext) }
|
||||
Base64.strict_encode64(payload.to_json)
|
||||
end
|
||||
|
||||
def decrypt(token)
|
||||
p = JSON.parse(Base64.strict_decode64(token), symbolize_names: true)
|
||||
cipher = OpenSSL::Cipher.new(ALGORITHM).tap do |c|
|
||||
c.decrypt
|
||||
c.key = @key
|
||||
c.iv = db64(p[:iv])
|
||||
c.auth_tag = db64(p[:tag])
|
||||
end
|
||||
cipher.update(db64(p[:data])) + cipher.final
|
||||
rescue => e
|
||||
abort "#{C::RED}✗ Error al descifrar: #{e.message}#{C::RESET}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def b64(d) = Base64.strict_encode64(d)
|
||||
def db64(d) = Base64.strict_decode64(d)
|
||||
end
|
||||
|
||||
# ─── Bóveda ──────────────────────────────────────────────────────────
|
||||
class Boveda
|
||||
def initialize(path)
|
||||
@path = path
|
||||
@data = File.exist?(path) ? JSON.parse(File.read(path)) : {}
|
||||
end
|
||||
|
||||
def keys = @data.keys
|
||||
def get(k) = @data[k]
|
||||
def set(k, v) = (@data[k] = v) && save!
|
||||
def rm(k) = @data.delete(k) && save!
|
||||
def empty? = @data.empty?
|
||||
|
||||
private
|
||||
|
||||
def save!
|
||||
File.write(@path, JSON.pretty_generate(@data) + "\n")
|
||||
FileUtils.chmod(0600, @path)
|
||||
end
|
||||
end
|
||||
|
||||
# ─── Master Key ──────────────────────────────────────────────────────
|
||||
def cargar_master_key
|
||||
unless File.exist?(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}"
|
||||
end
|
||||
File.read(MASTER_KEY)
|
||||
end
|
||||
|
||||
# ─── Comandos ────────────────────────────────────────────────────────
|
||||
|
||||
def cmd_abrir
|
||||
puts "#{C::CYAN}🔐 ns8-candados: Autorización SSH#{C::RESET}"
|
||||
puts "#{C::DIM}Limpiando identidades previas...#{C::RESET}"
|
||||
system('ssh-add -D > /dev/null 2>&1')
|
||||
|
||||
puts "#{C::YELLOW}🔑 Ingresá tu passphrase:#{C::RESET}"
|
||||
system('ssh-add')
|
||||
|
||||
system('ssh-add -l > /dev/null 2>&1')
|
||||
unless $?.success?
|
||||
abort "#{C::RED}✗ Sin identidades SSH cargadas. Abortando.#{C::RESET}"
|
||||
end
|
||||
|
||||
puts "#{C::GREEN}🔓 SSH autorizado. Las identidades permanecen activas mientras dure la sesión del agente.#{C::RESET}"
|
||||
end
|
||||
|
||||
def cmd_encrypt(cripto, texto)
|
||||
abort "#{C::RED}✗ Falta texto a cifrar.#{C::RESET}" if texto.nil? || texto.empty?
|
||||
puts cripto.encrypt(texto)
|
||||
end
|
||||
|
||||
def cmd_decrypt(cripto, token)
|
||||
abort "#{C::RED}✗ Falta payload a descifrar.#{C::RESET}" if token.nil? || token.empty?
|
||||
puts cripto.decrypt(token)
|
||||
end
|
||||
|
||||
def cmd_get(cripto, boveda, clave)
|
||||
abort "#{C::RED}✗ Falta nombre de clave.#{C::RESET}" if clave.nil?
|
||||
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
|
||||
|
||||
def cmd_set(cripto, boveda, clave)
|
||||
abort "#{C::RED}✗ Falta nombre de clave.#{C::RESET}" if clave.nil?
|
||||
$stderr.print "#{C::YELLOW}🔑 Valor para '#{clave}': #{C::RESET}"
|
||||
valor = $stdin.noecho(&:gets)&.chomp
|
||||
$stderr.puts
|
||||
abort "#{C::RED}✗ Valor vacío.#{C::RESET}" if valor.nil? || valor.empty?
|
||||
|
||||
boveda.set(clave, cripto.encrypt(valor))
|
||||
$stderr.puts "#{C::GREEN}✓ Clave '#{clave}' guardada en la bóveda.#{C::RESET}"
|
||||
end
|
||||
|
||||
def cmd_rm(boveda, clave)
|
||||
abort "#{C::RED}✗ Falta nombre de clave.#{C::RESET}" if clave.nil?
|
||||
abort "#{C::RED}✗ Clave '#{clave}' no encontrada.#{C::RESET}" unless boveda.get(clave)
|
||||
boveda.rm(clave)
|
||||
$stderr.puts "#{C::GREEN}✓ Clave '#{clave}' eliminada.#{C::RESET}"
|
||||
end
|
||||
|
||||
def cmd_list(boveda)
|
||||
if boveda.empty?
|
||||
$stderr.puts "#{C::DIM}Bóveda vacía.#{C::RESET}"
|
||||
return
|
||||
end
|
||||
$stderr.puts "#{C::CYAN}🔐 Claves en la bóveda:#{C::RESET}"
|
||||
boveda.keys.each { |k| $stderr.puts " #{C::GREEN}•#{C::RESET} #{k}" }
|
||||
end
|
||||
|
||||
def cmd_help
|
||||
puts <<~HELP
|
||||
#{C::CYAN}🔐 ns8-candados — Gestión de secretos#{C::RESET}
|
||||
|
||||
#{C::GREEN}Uso:#{C::RESET} ruby ns8-candados.rb <comando> [args]
|
||||
|
||||
#{C::YELLOW}Comandos:#{C::RESET}
|
||||
abrir Autorizar SSH (cargar passphrase)
|
||||
encrypt <texto> Cifrar texto plano → token
|
||||
decrypt <token> Descifrar token → texto plano
|
||||
get <clave> Obtener secreto de la bóveda
|
||||
set <clave> Guardar secreto (input seguro)
|
||||
rm <clave> Eliminar secreto
|
||||
list Listar claves disponibles
|
||||
|
||||
#{C::YELLOW}Ejemplos:#{C::RESET}
|
||||
#{C::DIM}# Guardar un secreto#{C::RESET}
|
||||
ruby ns8-candados.rb set mi_password
|
||||
|
||||
#{C::DIM}# Usar en scripts (salida limpia)#{C::RESET}
|
||||
PASS=$(ruby ns8-candados.rb get admindasu)
|
||||
sshpass -p "$PASS" ssh user@host "cmd"
|
||||
HELP
|
||||
end
|
||||
|
||||
# ─── Main ────────────────────────────────────────────────────────────
|
||||
comando = ARGV[0]
|
||||
arg = ARGV[1]
|
||||
|
||||
case comando
|
||||
when 'abrir'
|
||||
cmd_abrir
|
||||
when 'encrypt', 'decrypt', 'get', 'set', 'rm', 'list'
|
||||
cripto = Cripto.new(cargar_master_key)
|
||||
boveda = Boveda.new(BOVEDA)
|
||||
case comando
|
||||
when 'encrypt' then cmd_encrypt(cripto, arg)
|
||||
when 'decrypt' then cmd_decrypt(cripto, arg)
|
||||
when 'get' then cmd_get(cripto, boveda, arg)
|
||||
when 'set' then cmd_set(cripto, boveda, arg)
|
||||
when 'rm' then cmd_rm(boveda, arg)
|
||||
when 'list' then cmd_list(boveda)
|
||||
end
|
||||
when 'help', '-h', '--help', nil
|
||||
cmd_help
|
||||
else
|
||||
$stderr.puts "#{C::RED}✗ Comando desconocido: '#{comando}'#{C::RESET}"
|
||||
cmd_help
|
||||
exit 1
|
||||
end
|
||||
Reference in New Issue
Block a user