docs: Actualización de contexto IA, Drones, Candados y nuevos nodos

This commit is contained in:
Ricardo Monla
2026-04-08 18:09:04 -03:00
parent cad987ce7f
commit fb8c8eefe5
139 changed files with 900 additions and 36345 deletions
+12 -13
View File
@@ -1,16 +1,15 @@
{ {
"rsa": "eyJpdiI6IkpoTmpZRWZMeE12eWhXSVMiLCJ0YWciOiJUUnJGY05qRlVoOTl2REplTms0ZU5RPT0iLCJkYXRhIjoiNkFIWCsyYm9ndzZKbGM3c0hRPT0ifQ==", "srv-ns8:root": "eyJpdiI6Ik9UbHBieDJhU0JZbFFEM0wiLCJ0YWciOiJZeWJ5K0lUUFdOcEQwTmJ5UzhwcVFRPT0iLCJkYXRhIjoiNUE1QW5yenZ0QldjNEdVNXVBPT0ifQ==",
"domain_admin": "eyJpdiI6IjlYV0JRSVA1MzNLblFycUEiLCJ0YWciOiJ3aHI4a1cwanpLWU5OUmVHby9pd3ZRPT0iLCJkYXRhIjoiT1B3VGNHNkpUSWI0SnJ5NW5adHNVZz09In0=", "srv-ns8:rsa:passphrase": "eyJpdiI6ImpwRnFPYTZJdEtnUXdxT00iLCJ0YWciOiJMcXRBQ0lOT0paUE50Qjgva3lHSElRPT0iLCJkYXRhIjoiRDUzOFNuY3V1RUZXVHV4U3NnPT0ifQ==",
"admindasu": "eyJpdiI6IjlYV0JRSVA1MzNLblFycUEiLCJ0YWciOiJ3aHI4a1cwanpLWU5OUmVHby9pd3ZRPT0iLCJkYXRhIjoiT1B3VGNHNkpUSWI0SnJ5NW5adHNVZz09In0=", "srv-ns8:rmonla:sudo": "eyJpdiI6Ilg2VzhIMWN6Y2xYVzR3ZzgiLCJ0YWciOiJuWDk0SDcraVhJQTQxd3I3bEpLRFBnPT0iLCJkYXRhIjoiSllyRy9ScXFBRFBFc0l5emRRPT0ifQ==",
"win_domain": "eyJpdiI6IitrY2V0eEQxektEMFk2cGgiLCJ0YWciOiJtQmdIYU90ZTFFWUlGZElsMXBIOTJBPT0iLCJkYXRhIjoiNVVFanhaN2tIdHViZDRDRnlnPT0ifQ==", "srv-ns8:rmonla:rsa": "eyJpdiI6IkpoTmpZRWZMeE12eWhXSVMiLCJ0YWciOiJUUnJGY05qRlVoOTl2REplTms0ZU5RPT0iLCJkYXRhIjoiNkFIWCsyYm9ndzZKbGM3c0hRPT0ifQ==",
"deepseek_key": "eyJpdiI6InRCc2IvL3J4SGNsejgvL04iLCJ0YWciOiJBbjRlMTNXNlZzUGhNSWowSUU1a01nPT0iLCJkYXRhIjoiQXR4azk1aU9sb2QydzNwa0FMTHpjVkJKQTIvZU4wY1U2V2crK3NBbVd1UnRmUGh6In0=",
"srv-dasu:rmonla": "eyJpdiI6IkFoczJZWUIwQ2piZzdJNzgiLCJ0YWciOiJJdDhCZndRc1RoZFhVMVQ4OEhaN1J3PT0iLCJkYXRhIjoiaUQycmxndGFsUkE2dVRaTEgrTT0ifQ==",
"dominio-utnlarioja.intranet:monlaricardo": "eyJpdiI6InFRQ3VNYWlVSHlKMURNdzciLCJ0YWciOiJ1a3hESUZlaEt6enV3UXZxWUFrWXhRPT0iLCJkYXRhIjoiNVNaekdxVyt6dE5WQmhPaXF3PT0ifQ==",
"ns8:rmonla:sudo": "eyJpdiI6IkRqbTBhVlZSYUpVdno3TFkiLCJ0YWciOiJJSnE2ei8zZHIrV2hFQks1VjNpVGN3PT0iLCJkYXRhIjoiT3JxTjRhTmlQK2R3TlJZZk9BPT0ifQ==",
"srv-dasu:root": "eyJpdiI6Ik5LY09JR1FvaFVPRXd3bjUiLCJ0YWciOiJwb0NLZ3A4N3VOQUZMQWhMS1hUVWZnPT0iLCJkYXRhIjoicEgvWTFmd0RhSFQ0YWRHRUxyZ1lBQT09In0=",
"srv-ns8:rmonla:sudo": "eyJpdiI6InJDOUp2STdYR1crb2NkMTgiLCJ0YWciOiJHSUZVbXM5VnVmZ3d1NXQ3SFVwOUdRPT0iLCJkYXRhIjoib1ZVTzdaLzk1WGdwZDhKREM3aWpnV3c9In0=",
"dasu-sql2:Administrator": "eyJpdiI6IlJ0VWJzblVGM2NhWEtvNlgiLCJ0YWciOiJtM0llb2luUmRUZng0NnBsL1JtKzl3PT0iLCJkYXRhIjoiSHVhaklOTFlsSm1PbDJkTjNoYlF0UT09In0=",
"srvv-nginx-rm:root": "eyJpdiI6Ijc0STVkTk92Tk5LZEZZWWciLCJ0YWciOiJlOTAwcGpTa21PZFVhN2FJTzIxU1VBPT0iLCJkYXRhIjoiYUJRR3hRakFub1F1OCtyTVdWcW8ifQ==",
"srvv-dns:rmonla:sudo": "eyJpdiI6InkyY01pSHlNeGFzZ2lDcnkiLCJ0YWciOiJqYTdkaFM0bTBNMWZhTWxJRUtGTWtBPT0iLCJkYXRhIjoiWmVYbzRWRjNNS244UktuaFVnPT0ifQ==", "srvv-dns:rmonla:sudo": "eyJpdiI6InkyY01pSHlNeGFzZ2lDcnkiLCJ0YWciOiJqYTdkaFM0bTBNMWZhTWxJRUtGTWtBPT0iLCJkYXRhIjoiWmVYbzRWRjNNS244UktuaFVnPT0ifQ==",
"srvv-DNS:rmonla:sudo": "eyJpdiI6InkyY01pSHlNeGFzZ2lDcnkiLCJ0YWciOiJqYTdkaFM0bTBNMWZhTWxJRUtGTWtBPT0iLCJkYXRhIjoiWmVYbzRWRjNNS244UktuaFVnPT0ifQ==" "srv-dasu:rmonla": "eyJpdiI6IkFoczJZWUIwQ2piZzdJNzgiLCJ0YWciOiJJdDhCZndRc1RoZFhVMVQ4OEhaN1J3PT0iLCJkYXRhIjoiaUQycmxndGFsUkE2dVRaTEgrTT0ifQ==",
"srv-dasu:root": "eyJpdiI6Ik5LY09JR1FvaFVPRXd3bjUiLCJ0YWciOiJwb0NLZ3A4N3VOQUZMQWhMS1hUVWZnPT0iLCJkYXRhIjoicEgvWTFmd0RhSFQ0YWRHRUxyZ1lBQT09In0=",
"dasu-dc:admindasu": "eyJpdiI6IjlYV0JRSVA1MzNLblFycUEiLCJ0YWciOiJ3aHI4a1cwanpLWU5OUmVHby9pd3ZRPT0iLCJkYXRhIjoiT1B3VGNHNkpUSWI0SnJ5NW5adHNVZz09In0=",
"srvv-nginx-rm:root": "eyJpdiI6Ijc0STVkTk92Tk5LZEZZWWciLCJ0YWciOiJlOTAwcGpTa21PZFVhN2FJTzIxU1VBPT0iLCJkYXRhIjoiYUJRR3hRakFub1F1OCtyTVdWcW8ifQ==",
"dominio-utnlarioja.intranet:monlaricardo": "eyJpdiI6InFRQ3VNYWlVSHlKMURNdzciLCJ0YWciOiJ1a3hESUZlaEt6enV3UXZxWUFrWXhRPT0iLCJkYXRhIjoiNVNaekdxVyt6dE5WQmhPaXF3PT0ifQ==",
"deepseek_key": "eyJpdiI6InRCc2IvL3J4SGNsejgvL04iLCJ0YWciOiJBbjRlMTNXNlZzUGhNSWowSUU1a01nPT0iLCJkYXRhIjoiQXR4azk1aU9sb2QydzNwa0FMTHpjVkJKQTIvZU4wY1U2V2crK3NBbVd1UnRmUGh6In0=",
"srv-ns8:rmonla": "eyJpdiI6IlJ4cHZkbFpJQWVsTTl3T1YiLCJ0YWciOiJ3WTZGMDEvNHNsZm1wZm5jMEF4Q1lBPT0iLCJkYXRhIjoibFljUHRYTm44bUxvQjhCa2tBPT0ifQ==",
"dasu-sql3:Administrador": "eyJpdiI6IlRiMDh3U1N2aVhTcDYzbzciLCJ0YWciOiI5NVNyUDFlMXlrM0dGRlNqUXhLbEt3PT0iLCJkYXRhIjoiVkp2V09zSjczcS96UktTSlZTOFdBdz09In0="
} }
+1 -1
View File
@@ -1 +1 @@
1775576757 1775682230
+65 -39
View File
@@ -1,31 +1,43 @@
# 🔐 candados: Gestión Segura de Secretos # 🔐 candados: Acceso Seguro a Contraseñas (Sin Exponer)
## Resumen Ejecutivo ## Resumen Ejecutivo
`candados` es la herramienta central de seguridad del ecosistema ADN. Cifra y gestiona secretos con **AES-256-GCM**, implementando un sistema de **Candado Temporal** que requiere autorización explícita (5 min) antes de liberar credenciales. `candados` es la herramienta central de seguridad del ecosistema ADN. Cifra y gestiona secretos con **AES-256-GCM**, implementando un sistema de **Candado Temporal** que requiere autorización explícita (30 min) antes de liberar credenciales.
**Principio fundamental:** Las contraseñas **NUNCA** deben aparecer en pantalla, logs ni archivos temporales. **Principio fundamental:** Las contraseñas **NUNCA** deben aparecer en pantalla, logs ni archivos temporales visibles.
--- ---
## Arquitectura ## REGLA DE ORO — NUNCA MOSTRAR CONTRASEÑAS
- **Cripto**: Motor AES-256-GCM con IV aleatorio y autenticación.
- **Bóveda**: Persistencia cifrada en `.boveda.json` (permisos 600).
- **MFA Temporal**: Sesiones de 5 min via `.session` (permisos 600).
- **Master Key**: `.master.key` local (permisos 600, nunca se comparte).
## 🚀 Comando Primario: `run` ★ Esta herramienta está diseñada para que las contraseñas **NUNCA** se muestren en pantalla ni queden registradas en logs, históricos de shell o archivos temporales visibles.
El comando `run` es la forma **recomendada** de usar credenciales. Inyecta `USR` y `PASS` directamente en el entorno del subproceso sin imprimir nada: ```
┌─────────────────────────────────────────────────────────────────────┐
│ 🛡️ NUNCA uses `get` en comandos automatizados o logs │
│ │
│ ✅ CORRECTO: ruby candados.rb run <clave> 'ssh $USR@host' │
│ ❌ INCORRECTO: ruby candados.rb get <clave> | algo │
│ │
│ El comando `get` imprime la contraseña a stdout → queda en logs │
│ El comando `run` inyecta USR/PASS en el entorno → no deja rastro │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 📥 MÉTODOS PARA CARGAR CONTRASEÑAS EN VARIABLES
### ★ MÉTODO 1: `run` (PRIMARIO - Recomendado para comandos)
Ejecuta un comando con `USR` y `PASS` inyectadas en su entorno. La contraseña **NUNCA** se imprime ni queda en logs.
```bash ```bash
# 1. Autorizar (dura 5 min) # Sintaxis
ruby candados.rb authorize ruby candados.rb run <clave> 'comando que usa $USR y $PASS'
# 2. Ejecutar comando con credenciales inyectadas # Ejemplos
ruby candados.rb run srv-dasu:rmonla 'sshpass -p $PASS ssh $USR@host "comando"' ruby candados.rb run srv-dasu:rmonla 'sshpass -p $PASS ssh $USR@host'
ruby candados.rb run srv-ns8:root 'ssh $USR@$HOSTNAME "comando"'
# Retrocompatible con variable legacy (SSHPASS, etc.)
ruby candados.rb run admindasu SSHPASS 'sshpass -e ssh root@host'
``` ```
**¿Qué pasa internamente?** **¿Qué pasa internamente?**
@@ -35,6 +47,30 @@ ruby candados.rb run admindasu SSHPASS 'sshpass -e ssh root@host'
4. Limpia las variables al terminar 4. Limpia las variables al terminar
5. **Nada se imprime en pantalla ni queda en logs** 5. **Nada se imprime en pantalla ni queda en logs**
### ★ MÉTODO 2: `load` (Para shell interactivo)
Carga `USR` y `PASS` en variables de entorno del shell actual. Usa un archivo temporal (600) que se auto-elimina tras el source.
```bash
# Sintaxis
eval $(ruby candados.rb load <clave>)
# Ejemplo
eval $(ruby candados.rb load srv-dasu:rmonla)
# → Carga $USR y $PASS en tu shell actual
sshpass -p $PASS ssh $USR@servidor
```
**Seguridad:** El archivo temporal tiene permisos `600` y se auto-borra inmediatamente después del `source`.
---
## ⚙️ Arquitectura
- **Cripto**: Motor AES-256-GCM con IV aleatorio y autenticación.
- **Bóveda**: Persistencia cifrada en `.boveda.json` (permisos 600).
- **MFA Temporal**: Sesiones de 30 min via `.session` (permisos 600).
- **Master Key**: `.master.key` local (permisos 600, nunca se comparte).
## 🔒 Comando: `sudo` ## 🔒 Comando: `sudo`
Ejecuta comandos con privilegios, resolviendo la contraseña automáticamente: Ejecuta comandos con privilegios, resolviendo la contraseña automáticamente:
@@ -49,20 +85,7 @@ La clave se busca automáticamente:
2. Variantes normalizadas del hostname 2. Variantes normalizadas del hostname
3. Fallback genérico: `sudo` 3. Fallback genérico: `sudo`
## 📦 Comando Secundario: `load` ## 🔑 Resolución Automática de Claves
Para uso interactivo cuando se necesitan las variables en el shell actual. **Nunca imprime la contraseña:**
```bash
eval $(ruby candados.rb load srv-dasu:rmonla)
# USR y PASS están disponibles en el shell
```
Internamente escribe los exports a un archivo temporal (`tmp/.candados_env_<hex>`) con permisos `600`, y a stdout solo emite el comando `source` + autoborrado.
**Nota:** Los temporales se guardan en `tmp/` del repositorio (no en `/tmp/`) para cumplir con el principio 11 de `05_ia.md` (Workspace Bounds).
## 🔑 Resolución Automática de Pares
Los comandos `run` y `load` detectan automáticamente el par usuario/contraseña: Los comandos `run` y `load` detectan automáticamente el par usuario/contraseña:
@@ -77,12 +100,13 @@ Los comandos `run` y `load` detectan automáticamente el par usuario/contraseña
| Comando | Seguridad | Descripción | | Comando | Seguridad | Descripción |
|---|---|---| |---|---|---|
| `run <clave> <cmd>` | ✅ Nada expuesto | **★ PRIMARIO** - Ejecuta con USR/PASS inyectados | | `run <clave> <cmd>` | ✅ Nada expuesto | **★ PRIMARIO** - Ejecuta con USR/PASS inyectados |
| `sudo <cmd>` | ✅ Nada expuesto | Ejecuta con privilegios desde bóveda |
| `load <clave>` | ✅ Archivo temporal 600 | Carga en shell (usar con `eval`) | | `load <clave>` | ✅ Archivo temporal 600 | Carga en shell (usar con `eval`) |
| `get <clave>` | ⚠ Imprime a stdout | Obtener valor directo (usar con cuidado) | | `sudo <cmd>` | ✅ Nada expuesto | Ejecuta con privilegios desde bóveda |
| `authorize` | ✅ | Abrir candado (5 min) | | `authorize` | ✅ | Abrir candado (30 min) |
| `cerrar` | ✅ | Cerrar sesión manualmente |
| `set <clave>` | ✅ Input sin eco | Guardar secreto | | `set <clave>` | ✅ Input sin eco | Guardar secreto |
| `list` | ✅ | Listar claves | | `list` | ✅ | Listar claves (no muestra valores) |
| `get <clave>` | ⚠ Imprime a stdout | Obtener valor directo (⚠ solo uso manual) |
## 🛡️ Auditoría ## 🛡️ Auditoría
@@ -93,12 +117,14 @@ Archivo `access.log` (permisos 600) registra:
## 🧠 Contexto para IA ## 🧠 Contexto para IA
1. **Siempre** ejecutar `authorize` antes de operar 1. **Siempre** ejecutar `authorize` antes de operar (sesión de 30 min)
2. **Usar `run`** como método primario (inyecta, no imprime) 2. **Usar `run`** como método primario (inyecta USR/PASS, no imprime)
3. **Evitar `get`** en comandos automatizados (filtra en logs) 3. **Usar `load`** para shell interactivo (eval + source)
4. Cerrar sesión con `cerrar` cuando ya no se necesite 4. **Evitar `get`** en automatizaciones (imprime la contraseña)
5. **Cerrar sesión** con `cerrar` cuando ya no se necesite
```bash ```bash
# Flujo típico
ruby candados.rb authorize ruby candados.rb authorize
ruby candados.rb run srv-dasu:rmonla 'sshpass -p $PASS ssh $USR@host "ls"' ruby candados.rb run srv-dasu:rmonla 'sshpass -p $PASS ssh $USR@host "ls"'
ruby candados.rb cerrar ruby candados.rb cerrar
+185 -41
View File
@@ -258,7 +258,14 @@ def cmd_run(cripto, boveda, clave, *args)
token_user = boveda.get("#{prefijo}_user") token_user = boveda.get("#{prefijo}_user")
user_value = cripto.decrypt(token_user) if token_user user_value = cripto.decrypt(token_user) if token_user
elsif clave.include?(':') 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 else
token_user = boveda.get("#{clave}_user") token_user = boveda.get("#{clave}_user")
user_value = cripto.decrypt(token_user) if token_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}" $stderr.puts "#{C::GREEN}✓ Credenciales cargadas en variables USR y PASS (archivo temporal seguro).#{C::RESET}"
end 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) ───────────────────── # ─── Comando sudo (primitiva reutilizable) ─────────────────────
# Resolución inteligente de la clave sudo: # Resolución inteligente de la clave sudo:
# 1. <hostname>:<usuario>:sudo (convención específica) # 1. <hostname>:<usuario>:sudo (convención específica)
@@ -426,58 +491,130 @@ def cmd_sudo(cripto, boveda, comando_args)
exit $?.exitstatus || 0 exit $?.exitstatus || 0
end end
def cmd_help def cmd_help_resumida
puts <<~HELP 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::GREEN}┌─────────────────────────────────────────────────────────────────┐#{C::RESET}
#{C::BOLD}run <clave> <cmd>#{C::RESET} #{C::GREEN}★ PRIMARIO#{C::RESET} Ejecutar comando con USR y PASS inyectados #{C::GREEN}│#{C::RESET} #{C::BOLD}MÉTODOS PARA CARGAR CONTRASEÑAS EN VARIABLES#{C::RESET} #{C::GREEN}│#{C::RESET}
#{C::BOLD}sudo <comando>#{C::RESET} Ejecutar con privilegios (resuelve clave automáticamente) #{C::GREEN}└─────────────────────────────────────────────────────────────────┘#{C::RESET}
#{C::BOLD}load <clave>#{C::RESET} Cargar USR/PASS en shell actual (usar con eval)
#{C::YELLOW}━━━ Gestión de bóveda ━━━#{C::RESET} #{C::BOLD}★ run <clave> 'comando'#{C::RESET} → Ejecuta con $USR y $PASS inyectadas (PRIMARIO)
authorize Abrir candado (autorización temporal 30 min) #{C::BOLD}★ load <clave>#{C::RESET} → Carga $USR y $PASS en shell (usar con eval)
cerrar Cerrar candado manualmente
set <clave> Guardar secreto (input seguro, sin eco) #{C::DIM}Ejemplo: ruby candados.rb run srv-ns8:rmonla 'sshpass -p $PASS ssh $USR@host'#{C::RESET}
get <clave> Obtener secreto ( imprime a stdout)
rm <clave> Eliminar secreto #{C::YELLOW}━━━ Comandos ━━━#{C::RESET}
list Listar claves disponibles #{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} #{C::YELLOW}━━━ Utilidades ━━━#{C::RESET}
abrir Autorizar SSH (cargar passphrase) #{C::BOLD}encrypt <texto>#{C::RESET} Cifrar texto plano → token (para bóveda)
encrypt <texto> Cifrar texto plano token #{C::BOLD}decrypt <token>#{C::RESET} Descifrar token → texto plano
decrypt <token> Descifrar token texto plano
#{C::YELLOW}Resolución automática de claves:#{C::RESET} #{C::YELLOW}━━━ Resolución Automática de Claves ━━━#{C::RESET}
#{C::DIM}• run/load: Detecta pares USR/PASS por convención:#{C::RESET} #{C::DIM}• run/load con formato `servidor:usuario`:#{C::RESET}
srv-dasu:rmonla USR=rmonla, PASS=(valor cifrado) srv-dasu:rmonla USR=rmonla, PASS=(descifrado de bóveda)
tailscale_dasuten busca _user/_pass automáticamente #{C::DIM}• run/load con formato `prefijo_pass`:#{C::RESET}
#{C::DIM}• sudo: Resuelve la clave de sudo automáticamente:#{C::RESET} tailscale_dasuten_pass busca automáticamente tailscale_dasuten_user
<hostname>:<usuario>:sudo (ej: ns8:rmonla:sudo) #{C::DIM}• sudo resuelve la clave automáticamente:#{C::RESET}
Busca: <hostname>:<usuario>:sudo fallback: `sudo`
#{C::YELLOW}Ejemplos:#{C::RESET} #{C::YELLOW}━━━ Flujo de Trabajo Típico ━━━#{C::RESET}
#{C::DIM}# 1. Autorizar (dura 30 min)#{C::RESET} #{C::DIM}1. Autorizar (una vez cada 30 min):#{C::RESET}
ruby candados.rb authorize ruby candados.rb authorize
#{C::GREEN}# 2. Ejecutar comando con credenciales (RECOMENDADO)#{C::RESET} #{C::DIM}2. Ejecutar comandos (Método recomendado):#{C::RESET}
ruby candados.rb run srv-dasu:rmonla 'sshpass -p $PASS ssh $USR@host' ruby candados.rb run srv-ns8:rmonla 'sshpass -p $PASS ssh $USR@host "comando"'
ruby candados.rb run admindasu SSHPASS 'sshpass -e ssh root@host'
#{C::DIM}# 3. Ejecutar con sudo#{C::RESET} #{C::DIM}3. O cargar en shell interactivo:#{C::RESET}
ruby candados.rb sudo tailscale set --operator=$USER eval $(ruby candados.rb load srv-ns8:rmonla)
sshpass -p $PASS ssh $USR@host
#{C::DIM}# 4. Cargar en shell interactivo (secundario)#{C::RESET} #{C::RED}⚠️ ADVERTENCIAS DE SEGURIDAD:#{C::RESET}
eval $(ruby candados.rb load srv-dasu:rmonla) #{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}Documentación completa: adn/tools/candados/README.md#{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}
HELP HELP
end 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 ──────────────────────────────────────────────────────────── # ─── Main ────────────────────────────────────────────────────────────
if __FILE__ == $0 if __FILE__ == $0
comando = ARGV[0] comando = ARGV[0]
@@ -496,6 +633,10 @@ if __FILE__ == $0
cripto = Cripto.new(cargar_master_key) cripto = Cripto.new(cargar_master_key)
boveda = Boveda.new(BOVEDA) boveda = Boveda.new(BOVEDA)
cmd_sudo(cripto, boveda, ARGV[1..]) 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' when 'encrypt', 'decrypt', 'get', 'load', 'run', 'set', 'rm', 'list'
cripto = Cripto.new(cargar_master_key) cripto = Cripto.new(cargar_master_key)
boveda = Boveda.new(BOVEDA) boveda = Boveda.new(BOVEDA)
@@ -509,11 +650,14 @@ if __FILE__ == $0
when 'rm' then cmd_rm(boveda, arg) when 'rm' then cmd_rm(boveda, arg)
when 'list' then cmd_list(boveda) when 'list' then cmd_list(boveda)
end end
when 'help', 'ayuda', '-h', '--help', nil when '--help', '-h'
cmd_help cmd_help_completo
when 'help', 'ayuda', nil
cmd_help_resumida
else else
$stderr.puts "#{C::RED}✗ Comando desconocido: '#{comando}'#{C::RESET}" $stderr.puts "#{C::RED}✗ Comando desconocido: '#{comando}'#{C::RESET}"
cmd_help $stderr.puts ""
cmd_help_resumida
exit 1 exit 1
end end
end end
+6 -1
View File
@@ -66,6 +66,7 @@ module Dron
lines << "\n#{Color::BOLD}Opciones:#{Color::RESET}" lines << "\n#{Color::BOLD}Opciones:#{Color::RESET}"
lines << " #{Color::GREEN}--nota \"texto\"#{Color::RESET} Nota descriptiva para la bitácora" lines << " #{Color::GREEN}--nota \"texto\"#{Color::RESET} Nota descriptiva para la bitácora"
lines << " #{Color::GREEN}--evento ID#{Color::RESET} Vincular a evento existente" lines << " #{Color::GREEN}--evento ID#{Color::RESET} Vincular a evento existente"
lines << " #{Color::GREEN}--nodo NOMBRE#{Color::RESET} Nodo para bitácora (auto-detect si no se especifica)"
lines << " #{Color::GREEN}--timeout N#{Color::RESET} Timeout en segundos (default: 3600)" lines << " #{Color::GREEN}--timeout N#{Color::RESET} Timeout en segundos (default: 3600)"
lines << " #{Color::GREEN}--dry-run#{Color::RESET} Simulación sin ejecutar" lines << " #{Color::GREEN}--dry-run#{Color::RESET} Simulación sin ejecutar"
lines << " #{Color::GREEN}--help#{Color::RESET} Mostrar esta ayuda" lines << " #{Color::GREEN}--help#{Color::RESET} Mostrar esta ayuda"
@@ -107,7 +108,8 @@ module Dron
nota: opciones[:nota], nota: opciones[:nota],
evento_id: opciones[:evento_id], evento_id: opciones[:evento_id],
timeout: opciones[:timeout], timeout: opciones[:timeout],
flujo_id: opciones[:flujo_id] flujo_id: opciones[:flujo_id],
nodo: opciones[:nodo]
) )
# Mostrar resultado # Mostrar resultado
@@ -128,6 +130,7 @@ module Dron
evento_id: nil, evento_id: nil,
timeout: 3600, timeout: 3600,
flujo_id: nil, flujo_id: nil,
nodo: nil,
cmd: nil cmd: nil
} }
@@ -143,6 +146,8 @@ module Dron
opciones[:timeout] = args.shift.to_i opciones[:timeout] = args.shift.to_i
when '--flujo' when '--flujo'
opciones[:flujo_id] = args.shift opciones[:flujo_id] = args.shift
when '--nodo'
opciones[:nodo] = args.shift
when '--help', '-h' when '--help', '-h'
Dispatcher.mostrar_ayuda Dispatcher.mostrar_ayuda
exit 0 exit 0
+43 -6
View File
@@ -13,15 +13,15 @@ module Dron
HEARTBEAT_INTERVAL = 30 HEARTBEAT_INTERVAL = 30
class << self class << self
def lanzar(cmd:, nota: nil, evento_id: nil, timeout: DEFAULT_TIMEOUT, flujo_id: nil) def lanzar(cmd:, nota: nil, evento_id: nil, timeout: DEFAULT_TIMEOUT, flujo_id: nil, nodo: nil)
Base.logger.info("🛠️ Dron Ejecutor iniciado: #{Process.pid}") Base.logger.info("🛠️ Dron Ejecutor iniciado: #{Process.pid}")
dron_id = ADN::DB::DronDB.generar_dron_id dron_id = ADN::DB::DronDB.generar_dron_id
ADN::DB::DronDB.registrar_inicio(tipo: 'ejecutor', cmd: cmd, flujo_id: flujo_id, metadata: { nota: nota, evento_id: evento_id, timeout: timeout }) ADN::DB::DronDB.registrar_inicio(tipo: 'ejecutor', cmd: cmd, flujo_id: flujo_id, metadata: { nota: nota, evento_id: evento_id, timeout: timeout, nodo: nodo })
evento_id = registrar_bitacora(nota, evento_id, 'inicio') if nota evento_id = registrar_bitacora(nota, evento_id, 'inicio', nodo) if nota
resultado = ejecutar_comando(cmd, dron_id, timeout) resultado = ejecutar_comando(cmd, dron_id, timeout)
ADN::DB::DronDB.registrar_fin(dron_id, resultado[:exit_code], resultado[:output]) ADN::DB::DronDB.registrar_fin(dron_id, resultado[:exit_code], resultado[:output])
registrar_bitacora(nota, evento_id, 'fin', resultado) if evento_id registrar_bitacora(nota, evento_id, 'fin', resultado, nodo) if evento_id
Base.logger.info("🛠️ Dron Ejecutor completado: #{dron_id} - #{resultado[:exit_code] == 0 ? '✅' : '❌'}") Base.logger.info("🛠️ Dron Ejecutor completado: #{dron_id} - #{resultado[:exit_code] == 0 ? '✅' : '❌'}")
{ dron_id: dron_id, exit_code: resultado[:exit_code], duration: resultado[:duration], evento_id: evento_id } { dron_id: dron_id, exit_code: resultado[:exit_code], duration: resultado[:duration], evento_id: evento_id }
@@ -67,14 +67,18 @@ module Dron
{ exit_code: exit_code || 1, output: output.join("\n").strip, duration: (Time.now - start_time).round(2) } { exit_code: exit_code || 1, output: output.join("\n").strip, duration: (Time.now - start_time).round(2) }
end end
def registrar_bitacora(nota, evento_id, tipo, resultado = nil) def registrar_bitacora(nota, evento_id, tipo, resultado = nil, nodo = nil)
return nil unless nota return nil unless nota
require_relative '../../db/core/bitacora_db' require_relative '../../db/core/bitacora_db'
# Detectar nodo automáticamente si no se especifica
nodo_id = detectar_nodo_id(nodo)
if tipo == 'inicio' if tipo == 'inicio'
descripcion = "🛸 dron: #{nota}" descripcion = "🛸 dron: #{nota}"
BitacorasDB::BitacoraDB.with_connection do |db| BitacorasDB::BitacoraDB.with_connection do |db|
db.crear_evento(nodo_id: 1, descripcion: descripcion, inicio: Time.now.strftime('%H:%M'), estado: '⏳') evento = db.crear_evento(nodo_id: nodo_id, descripcion: descripcion, inicio: Time.now.strftime('%H:%M'), estado: '⏳')
evento['id'] # Retornar ID para actualizar después
end end
else else
estado = resultado[:exit_code] == 0 ? '✅' : '❌' estado = resultado[:exit_code] == 0 ? '✅' : '❌'
@@ -82,11 +86,44 @@ module Dron
BitacorasDB::BitacoraDB.with_connection do |db| BitacorasDB::BitacoraDB.with_connection do |db|
db.actualizar_evento(evento_id, descripcion: descripcion, fin: Time.now.strftime('%H:%M')) db.actualizar_evento(evento_id, descripcion: descripcion, fin: Time.now.strftime('%H:%M'))
end end
evento_id # Retornar mismo ID
end end
rescue StandardError => e rescue StandardError => e
Base.logger.warn("⚠️ Error en bitácora: #{e.message}") Base.logger.warn("⚠️ Error en bitácora: #{e.message}")
nil nil
end end
def detectar_nodo_id(nodo)
return nodo if nodo.is_a?(Integer)
# Si no se especifica nodo, usar el hostname para detectar
require 'socket'
hostname = Socket.gethostname.downcase
# Buscar nodo por nombre en la tabla de nodos
require_relative '../../db/core/bitacora_db'
sql = 'SELECT id FROM nodos WHERE LOWER(nombre) = $1 LIMIT 1'
resultado = nil
BitacorasDB::BitacoraDB.with_connection do |db|
resultado = db.execute(sql, [hostname])
end
if resultado&.any?
resultado.first['id']
else
# Fallback: buscar por prefijo (ej: srv-ns8 coincide con srv-ns8.frlr.utn.edu.ar)
sql = 'SELECT id FROM nodos WHERE LOWER(nombre) LIKE $1 LIMIT 1'
resultado = nil
BitacorasDB::BitacoraDB.with_connection do |db|
resultado = db.execute(sql, ["#{hostname}%"])
end
resultado&.any? ? resultado.first['id'] : 1
end
rescue StandardError => e
Base.logger.warn("⚠️ Error detectando nodo: #{e.message}, usando default (1)")
1
end
end end
end end
end end
+30 -4
View File
@@ -666,20 +666,45 @@ module BitacorasDB
where_clause = conditions.empty? ? '' : "WHERE #{conditions.join(' AND ')}" where_clause = conditions.empty? ? '' : "WHERE #{conditions.join(' AND ')}"
# Para events, adaptar condiciones (solo las que aplican)
where_clause_events = if conditions.empty?
''
else
# Reemplazar referencias a e. por ev. y eliminar condiciones que no aplican a events
conditions_events = conditions.reject { |c| c.include?('bitacora_id') || c.include?('tema_id') || c.include?('ambito_id') }
"WHERE #{conditions_events.join(' AND ').gsub('e.', 'ev.')}"
end
# Unificar eventos de tabla events y entradas para mostrar todo junto
query = <<~SQL query = <<~SQL
SELECT e.id, e.inicio, e.fin, e.descripcion, e.estado, e.modo, e.es_ia, SELECT ev.id, ev.inicio::text as inicio, ev.fin::text as fin, ev.descripcion, ev.estado,
e.tema_id, e.bitacora_id, e.nodo_id, e.ambito_id, e.created_at, e.updated_at, 'A' as modo, false as es_ia,
NULL as tema_id, NULL as bitacora_id, ev.nodo_id, NULL as ambito_id,
ev.created_at, ev.updated_at,
CURRENT_DATE as bitacora_fecha,
n.nombre as nodo_nombre,
NULL as tema_titulo,
na.nombre as ambito_nombre
FROM bitacoras.events ev
LEFT JOIN bitacoras.nodos n ON ev.nodo_id = n.id
LEFT JOIN bitacoras.ambitos na ON n.ambito_id = na.id
#{where_clause_events}
UNION ALL
SELECT e.id, e.inicio::text, e.fin::text, e.descripcion, e.estado, e.modo, e.es_ia,
e.tema_id, e.bitacora_id, e.nodo_id, e.ambito_id,
e.created_at, e.updated_at,
b.fecha as bitacora_fecha, b.fecha as bitacora_fecha,
n.nombre as nodo_nombre, n.nombre as nodo_nombre,
t.titulo as tema_titulo, t.titulo as tema_titulo,
a.nombre as ambito_nombre COALESCE(a.nombre, na.nombre) as ambito_nombre
FROM #{SCHEMA}.#{TABLES[:entradas]} e FROM #{SCHEMA}.#{TABLES[:entradas]} e
LEFT JOIN #{SCHEMA}.#{TABLES[:bitacoras]} b ON e.bitacora_id = b.id LEFT JOIN #{SCHEMA}.#{TABLES[:bitacoras]} b ON e.bitacora_id = b.id
LEFT JOIN #{SCHEMA}.#{TABLES[:nodos]} n ON e.nodo_id = n.id LEFT JOIN #{SCHEMA}.#{TABLES[:nodos]} n ON e.nodo_id = n.id
LEFT JOIN #{SCHEMA}.#{TABLES[:temas]} t ON e.tema_id = t.id LEFT JOIN #{SCHEMA}.#{TABLES[:temas]} t ON e.tema_id = t.id
LEFT JOIN #{SCHEMA}.ambitos a ON e.ambito_id = a.id LEFT JOIN #{SCHEMA}.ambitos a ON e.ambito_id = a.id
LEFT JOIN #{SCHEMA}.ambitos na ON n.ambito_id = na.id
#{where_clause} #{where_clause}
ORDER BY COALESCE(b.fecha, CURRENT_DATE) DESC, e.inicio DESC ORDER BY bitacora_fecha DESC, inicio DESC
LIMIT $#{param_index} OFFSET $#{param_index + 1} LIMIT $#{param_index} OFFSET $#{param_index + 1}
SQL SQL
@@ -1007,6 +1032,7 @@ module BitacorasDB
public public
# Crear un evento en la tabla events (visible en Bitácora Web) # Crear un evento en la tabla events (visible en Bitácora Web)
# El ámbito se obtiene automáticamente del nodo mediante JOIN en las consultas
def crear_evento(nodo_id:, descripcion:, estado: '⏳', inicio: nil, fin: nil, modo: 'A', metadata: nil) def crear_evento(nodo_id:, descripcion:, estado: '⏳', inicio: nil, fin: nil, modo: 'A', metadata: nil)
inicio ||= Time.now.strftime('%H:%M') inicio ||= Time.now.strftime('%H:%M')
query = <<~SQL query = <<~SQL
@@ -5,8 +5,8 @@
**Código:** A04.P005 **Código:** A04.P005
**Fecha:** 27 de marzo de 2026 **Fecha:** 27 de marzo de 2026
**Autor:** Sistema ADN **Autor:** Sistema ADN
**Versión:** 2.2 **Versión:** 2.5
**Estado:** 🚧 EN EJECUCIÓN (Fase 2 completada, Fase 3 pendiente — Creación VM PC Cliente) **Estado:** 🚧 EN EJECUCIÓN (Nueva VM 105 dasu-sql3 - Fase 4.N1-4.N3 completadas)
**Dependencia:** A04.P001 (Red srv-dasu) **Dependencia:** A04.P001 (Red srv-dasu)
## 📋 Resumen Ejecutivo ## 📋 Resumen Ejecutivo
@@ -80,8 +80,22 @@ Crear dos VMs nuevas (SQL Server + PC cliente) en `srv-dasu`, instalar el sistem
- [x] **3.6:** Instalar Tailscale VPN (cuenta `pcdasu0@frlr.utn.edu.ar`). Anotar IP Tailscale asignada. ✅ **IP: 100.98.225.100** - [x] **3.6:** Instalar Tailscale VPN (cuenta `pcdasu0@frlr.utn.edu.ar`). Anotar IP Tailscale asignada. ✅ **IP: 100.98.225.100**
- [ ] **3.7:** Instalar herramientas cliente SQL (SSMS o sqlcmd) si es necesario. - [ ] **3.7:** Instalar herramientas cliente SQL (SSMS o sqlcmd) si es necesario.
- [ ] **3.8:** Verificar conectividad hacia dasu-sql2 (ping + test TCP 1433). - [ ] **3.8:** Verificar conectividad hacia dasu-sql2 (ping + test TCP 1433).
- [ ] **3.9:** ⚠️ PROBLEMA: VMs sin conectividad de red IP. Estado: 192.168.1.x NO responde a ping desde srv-dasu. Investiguando causa (posible conflicto de bridge/red).
- [x] **3.9:** Instalar qemu-guest-agent en VM dasu-pcv2 para backups desatendidos (ACPI shutdown) ✅. - [x] **3.9:** Instalar qemu-guest-agent en VM dasu-pcv2 para backups desatendidos (ACPI shutdown) ✅.
### ⏳ **FASE 4: Nueva VM dasu-sql3 (VM 105)** *(2026-04-08)*
- [x] **4.N1:** Crear VM 105 (dasu-sql3) ✅. Eliminar VMs 103 y 104 problemáticas, crear VM 105 limpia.
- [x] **4.N2:** ISO Windows Server 2022 Español ✅. Usar `Windows_Server_2022_Spanish_Evaluation.iso`.
- [x] **4.N3:** Discos SATA ✅. 60GB (sata0) para SO + 120GB (sata1) para datos SQL.
- [ ] **4.N4:** Finalizar instalación Windows.
- [ ] **4.N5:** Instalar VirtIO drivers.
- [ ] **4.N6:** Configurar hostname `dasu-sql3` y Workgroup.
- [ ] **4.N7:** Configurar red DHCP.
- [ ] **4.N8:** Instalar OpenSSH Server (puerto 7022).
- [ ] **4.N9:** Instalar Tailscale.
- [ ] **4.N10:** Instalar SQL Server 2019.
### ⏳ **FASE 4: Instalación del Sistema DASUTEN** ### ⏳ **FASE 4: Instalación del Sistema DASUTEN**
- [x] **4.1:** Obtener backup/script de la base de datos DASUTEN del sistema actual. ✅ (Descargado localmente archivo de 604MB y confirmado acceso vía SMB al crudo `.bak`) - [x] **4.1:** Obtener backup/script de la base de datos DASUTEN del sistema actual. ✅ (Descargado localmente archivo de 604MB y confirmado acceso vía SMB al crudo `.bak`)
@@ -0,0 +1,124 @@
# Plan: Conocimiento del Contexto y Ámbito de Trabajo
> **Ámbito:** A09 — dtic-DIIAA
**Código:** A09.P001
**Fecha:** 08 de abril de 2026
**Autor:** Lic. Ricardo Monla
**Versión:** 1.2
**Estado:** 🚧 En Curso (Fase 3 completada)
**Dependencia:** Ninguna
## 📋 Resumen Ejecutivo
Este plan tiene como objetivo establecer el conocimiento completo del contexto y ámbito de trabajo del Departamento de Investigación en Inteligencia Artificial y Automatizaciones (DIIAA).
**Alcances:**
- Comprender la estructura institucional
- Identificar nodos y recursos disponibles
- Establecer conexiones y accesos
- Documentar el estado actual de infraestructura
## 🎯 Objetivos
1. **Conocer el contexto institucional** — DTIC, FRLR, UTN
2. **Identificar el nodo principal** — srv-ns8 y sus servicios
3. **Establecer accesos seguros** — Candados y bóveda
4. **Configurar nginx nativo** — Servidor web para aplicaciones DIIAA
5. **Documentar estado actual** — Infraestructura y servicios existentes
## 📅 Fases de Implementación
### FASE 1: Reconocimiento Institucional ✅ COMPLETADA
- [x] Documentar estructura organizacional (DTIC → DIIAA)
- [x] Identificar responsables y roles
- [x] Mapear dependencias institucionales
### FASE 2: Reconocimiento de Infraestructura ✅ COMPLETADA
- [x] Identificar nodos disponibles
- [x] Verificar conectividad y accesos
- [x] Listar servicios existentes en srv-ns8
### FASE 3: Configuración de Accesos ✅ COMPLETADA
- [x] Optimizar herramienta `candados` (help resumida/completa)
- [x] Documentar métodos para cargar contraseñas en variables
- [x] Actualizar README de candados
- [x] Sanear bóveda (eliminar duplicados, renombrar claves)
### FASE 3.5: Mejora de Drones ✅ COMPLETADA
- [x] Auto-detección de nodo por hostname
- [x] Ámbito automático heredado del nodo
- [x] Unificación de vistas (events + entradas)
- [x] Registro automático en bitácora sin intervención manual
### FASE 4: Setup de nginx Nativo ⏳ PENDIENTE
- [ ] Instalar nginx en srv-ns8 (nativo, no Docker)
- [ ] Configurar servidor virtual para dtic-DIIAA
- [ ] Habilitar acceso web al ámbito
### FASE 5: Documentación y Puesta en Marcha ⏳ EN CURSO
- [x] Crear manifiesto del ámbito A09
- [x] Crear plan A09.P001
- [ ] Crear ficha del nodo srv-ns8 (ya existe en `nodos/`)
- [ ] Documentar configuración nginx
- [x] Registrar en bitácora actividades
## 🎯 Criterios de Éxito
| Criterio | Verificación |
| :--- | :--- |
| **Manifiesto creado** | `A09_dtic-DIIAA.md` existe |
| **Plan documentado** | `A09.P001_Contexto-y-Ambito.md` existe |
| **Accesos configurados** | SSH a srv-ns8 funcional |
| **nginx operativo** | Servicio web accesible |
| **Bitácora actualizada** | Eventos registrados |
## 📝 Notas Técnicas
### Stack Tecnológico
- **Servidor Web:** nginx (nativo en srv-ns8)
- **Gestión de Secretos:** candados (AES-256-GCM)
- **Automatización:** ADN CLI + Drones
- **Bitácora:** Sistema ADN con web de visualización
### Nodo Principal: srv-ns8
| Campo | Valor |
| :--- | :--- |
| **Hostname** | srv-ns8.frlr.utn.edu.ar |
| **IP** | 10.0.10.8 |
| **SO** | Debian GNU/Linux 12 (bookworm) |
| **Rol** | Servidor de aplicaciones y gestión |
| **Servicios** | nginx, DNS (srvv-dns), otros |
## 🔄 Armonía Integral
**Última actualización:** 2026-04-08 09:50 (Fases 1-3 completadas, drones mejorados)
| Documento | Estado | Notas |
| :--- | :--- | :--- |
| `A09_dtic-DIIAA.md` | ✅ Creado | Manifiesto del ámbito con pilares ADN |
| `A09.P001_Contexto-y-Ambito.md` | ✅ Fases 1-3.5 completadas | Plan actualizado (v1.2) |
| `nodos/srv-ns8.md` | ✅ Existe | Ficha técnica en `nodos/` (global) |
| `adn/tools/candados/candados.rb` | ✅ Optimizado | Help resumida + completa (--help) |
| `adn/tools/candados/README.md` | ✅ Actualizado | Sincronizado con help |
| `adn/tools/cli/dron/ejecutor.rb` | ✅ Mejorado | Auto-detección de nodo y ámbito |
| `adn/tools/cli/dron/dispatcher.rb` | ✅ Mejorado | Opción --nodo agregada |
| `adn/tools/db/core/bitacora_db.rb` | ✅ Mejorado | Vista unificada events + entradas |
| `docs/contexto/IA.md` | ✅ Actualizado | Documentación de drones mejorados |
| nginx config | ⏳ Pendiente | Configuración nativa en srv-ns8 |
---
## Referencias
- **Ámbito de referencia:** `docs/ambito/dtic-DNS/A07_dtic-DNS.md`
- **ADN Ontología:** `adn/01_ontologia.md`
- **Bitácoras:** `./adn/tools/run db evento:listar --ambito dtic-DIIAA`
+123
View File
@@ -0,0 +1,123 @@
# A09 - Ámbito dtic-DIIAA
> Manifiesto de Ámbito | Referencia: [`adn/01_ontologia.md`](../../../adn/01_ontologia.md)
## Identificación
| Campo | Valor |
| :--- | :--- |
| **Código** | A09 |
| **Nombre** | Departamento de Investigación en Inteligencia Artificial y Automatizaciones |
| **Estado** | 🚧 En Implementación |
| **Inicio** | 08/04/2026 |
| **Fin Estimado** | Continuo (investigación y desarrollo) |
## Objetivo
El ámbito **dtic-DIIAA** engloba todas las operaciones relacionadas con la investigación, desarrollo y administración de sistemas de Inteligencia Artificial y Automatizaciones de la Facultad.
Este ámbito cubre:
1. **Investigación en IA** — Desarrollo y aplicación de modelos de inteligencia artificial.
2. **Automatizaciones** — Scripts y sistemas para automatización de tareas operativas.
3. **Administración de servidores** — Gestión de servidores y sistemas de altas prestaciones.
4. **Capacitación** — Formación y transferencia de conocimientos en IA y automatizaciones.
## Contexto Institucional
```
Lic. Ricardo MONLA
└── Departamento de Investigación en IA y Automatizaciones (DIIAA)
└── Dirección de Tecnologías de la Información y Comunicación (DTIC)
└── Facultad Regional La Rioja (FRLR)
└── Universidad Tecnológica Nacional (UTN)
```
## Funciones Principales
1. **Administración de servidores y sistemas de altas prestaciones**
2. **Investigación y desarrollo en Inteligencia Artificial**
3. **Automatización de procesos operativos**
4. **Soporte técnico especializado**
## Ecosistema ADN
Este ámbito opera bajo el **Ecosistema ADN** — el núcleo autónomo que gestiona la ontología, bitácoras, seguridad y procesos del proyecto.
### Pilares Fundamentales
El ADN se rige por tres principios rectores que guían toda decisión operativa:
| Pilar | Descripción |
| :--- | :--- |
| **Menos es Más** | Cada componente debe justificar su existencia. Eliminar lo innecesario, consolidar lo disperso, evitar duplicación. |
| **Armonía Integral** | Todo está conectado. Si un dato cambia en un sitio, debe propagarse a todos los demás. La consistencia es obligatoria. |
| **Mejora Continua** | El ADN evoluciona. Las buenas prácticas emergentes se formalizan como nuevas reglas (Evolución Progresiva). |
### Hebras del ADN
| # | Hebra | Descripción |
| :--- | :--- | :--- |
| **01** | **Ontología** | Define identidad, nodos, ámbitos y topología de red |
| **02** | **Bitácora** | Registro diario de eventos, triggers y métricas |
| **03** | **Seguridad** | Protocolos de red, candados y bóveda cifrada |
| **04** | **Iconografía** | Taxonomía visual de estados e iconos semánticos |
| **05** | **Directivas IA** | Premisas de trabajo para Inteligencia Artificial |
| **06** | **Gobernanza** | Idioma, nomenclatura, coherencia documental, resguardo |
| **07** | **Proyectos** | Gestión de proyectos transversales |
| **08** | **Procesos** | Procesos automatizados sobre infraestructura |
**CLI Principal:** `./adn/tools/run <subcomando>`
| Categoría | Comandos frecuentes |
| :--- | :--- |
| **Bitácoras** | `db evento:crear`, `db evento:actualizar`, `jornada iniciar/cerrar` |
| **Nodos** | `nodos info <nombre>`, `nodos listar`, `nodos agrupar` |
| **Seguridad** | `candados run <clave> '<cmd>'`, `candados authorize` |
| **Drones** | `dron lanzar`, `dron flota`, `dron salud`, `dron limpiar` |
> **Drones:** Subsistema del ADN para tareas en background con auto-bitácora. Ver [`adn/tools/cli/dron.rb`](../../../adn/tools/cli/dron.rb)
## Nodos Involucrados
### Nodo Principal
| Nodo | IP | Rol en el Ámbito |
| :--- | :--- | :--- |
| **srv-ns8** | `10.0.10.8` | Workstation / Docker Host / Hipervisor — servidor principal de aplicaciones y gestión |
> **Ficha técnica completa:** [`nodos/srv-ns8.md`](../../../nodos/srv-ns8.md)
### Nodos de Soporte
| Nodo | IP | Rol en el Ámbito |
| :--- | :--- | :--- |
| **srv-pmox1** | `10.0.10.201` | Hipervisor Proxmox (anfitrión de VMs) |
| **srvv-dns** | `10.0.10.2` | Servidor DNS (soporte de resolución) |
> Ver fichas completas en [`nodos/`](../../../nodos/)
## Planes Asociados
| Código | Nombre | Estado |
| :--- | :--- | :--- |
| [A09.P001](A09.P001_Contexto-y-Ambito.md) | Conocimiento del Contexto y Ámbito de Trabajo | 🚧 En Curso |
## 🔄 Armonía Integral
**Última actualización:** 2026-04-08 (Creación del ámbito A09 - dtic-DIIAA)
| Documento | Estado | Notas |
| :--- | :--- | :--- |
| `A09_dtic-DIIAA.md` | ✅ Creado | Manifiesto del ámbito |
| `A09.P001_Contexto-y-Ambito.md` | 🚧 En curso | Plan de conocimiento del contexto |
| `nodos/srv-ns8.md` | ✅ Existe | Ficha técnica en `nodos/` |
---
## Referencias
- **Ecosistema ADN**: [`adn/00_indice.md`](../../../adn/00_indice.md)
- **Ontología**: [`adn/01_ontologia.md`](../../../adn/01_ontologia.md)
- **Bitácoras**: `./adn/tools/run db evento:listar --ambito dtic-DIIAA`
- **Nodos**: [`nodos/`](../../../nodos/)
@@ -0,0 +1,80 @@
#!/bin/bash
# Script para agregar registros CNAME de DKIM en srvv-dns
# Ejecutar: sudo bash A07.P002_agregar_dkim.sh
ZONA_FILE="/docker/coreDNS/zones/frlr.utn.edu.ar.db"
BACKUP_FILE="/docker/coreDNS/zones/bkp_260407_preDKIM_frlr.utn.edu.ar.db"
echo "🔧 Agregando registros DKIM para Microsoft 365..."
# 1. Crear backup
echo " 1. Creando backup..."
cp "$ZONA_FILE" "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo " ✅ Backup creado: $BACKUP_FILE"
else
echo " ❌ Error al crear backup"
exit 1
fi
# 2. Verificar si los registros ya existen
if grep -q "selector1._domainkey" "$ZONA_FILE"; then
echo " ⚠️ El registro selector1._domainkey ya existe"
else
echo " ✅ Registro selector1._domainkey no existe (ok para agregar)"
fi
if grep -q "selector2._domainkey" "$ZONA_FILE"; then
echo " ⚠️ El registro selector2._domainkey ya existe"
else
echo " ✅ Registro selector2._domainkey no existe (ok para agregar)"
fi
# 3. Agregar registros CNAME de DKIM
# Los agrego después de los registros NS existentes
echo " 2. Agregando registros CNAME de DKIM..."
# Crear archivo temporal con los nuevos registros
cat >> "$ZONA_FILE" << 'EOF'
; =============================================
; Registros CNAME para DKIM (Microsoft 365)
; Agregados: 2026-04-07 - A07.P002
; =============================================
selector1 IN CNAME selector1-frlr-utn-edu-ar._domainkey.o365frlrutneduar.onmicrosoft.com.
selector2 IN CNAME selector2-frlr-utn-edu-ar._domainkey.o365frlrutneduar.onmicrosoft.com.
EOF
if [ $? -eq 0 ]; then
echo " ✅ Registros DKIM agregados"
else
echo " ❌ Error al agregar registros"
echo " Restaurando backup..."
cp "$BACKUP_FILE" "$ZONA_FILE"
exit 1
fi
# 4. Actualizar serial de zona (incrementar último dígito)
echo " 3. Actualizando serial de zona..."
SERIAL_ACTUAL=$(grep -A5 'IN SOA' "$ZONA_FILE" | grep -oE '[0-9]{10}' | head -1)
NUEVO_SERIAL=$((SERIAL_ACTUAL + 1))
sed -i "s/$SERIAL_ACTUAL/$NUEVO_SERIAL/" "$ZONA_FILE"
echo " Serial: $SERIAL_ACTUAL$NUEVO_SERIAL"
# 5. Forzar reload de CoreDNS
echo " 4. Forzando reload de CoreDNS..."
docker kill --signal=HUP coredns 2>/dev/null || echo " (signal enviado)"
echo " ✅ CoreDNS notificado"
echo ""
echo "✅ ¡Completado!"
echo ""
echo "📋 Verificación:"
echo " dig selector1._domainkey.frlr.utn.edu.ar CNAME"
echo " dig selector2._domainkey.frlr.utn.edu.ar CNAME"
echo ""
echo "⏱️ Esperar 15-30 minutos para propagación"
echo ""
echo "🔐 Luego habilitar DKIM en:"
echo " https://security.microsoft.com/dkimv2"
@@ -0,0 +1,26 @@
=== Backup DNS frlr.utn.edu.ar ===
== REGISTROS MX ==
0 frlr-utn-edu-ar.mail.protection.outlook.com.
== REGISTROS TXT (SPF, DKIM, etc) ==
"v=spf1 mx ip4:190.114.205.2 include:spf.protection.outlook.com -all"
"MS=ms22480661"
"google-site-verification=vZpwlfgIM_oSwlxfhvCY3zvbLEkRzHz8AFUDsVPmgzo"
== REGISTROS A ==
190.114.205.20
== REGISTROS CNAME ==
== REGISTROS NS ==
ns1.frlr.utn.edu.ar.
ns3.utn.edu.ar.
== DKIM LOCAL (mail._domainkey) ==
"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOMtst0Un3kUnMr+azAfJ1zho2"
"l5+Zg7E575exs30JJi5R044EqIRIB3cqxL8QN5pawb0meiHOn6D5S+LEaGKd4+jxPW"
"WY/haivsuUP8tJuNcV53LdlpiTcBcrvtJLPCOsxgQl7XofghR8+dNEZ66uKNI+w7/"
"7BoMeOHMivP8auwIDAQAB"
== DMARC ==
+45
View File
@@ -0,0 +1,45 @@
Ese mensaje de error es el "mapa del tesoro". Te está confirmando que el sistema no puede habilitar el DKIM porque todavía no has publicado los registros en el DNS de la UTN.
Aquí tienes los datos exactos que debes copiar y pegar en tu panel de control de DNS (donde gestionas el dominio frlr.utn.edu.ar):
Debes crear estos 2 registros CNAME:
Registro 1:
Tipo: CNAME
Nombre (Host): selector1._domainkey
Valor (Apunta a): selector1-frlr-utn-edu-ar._domainkey.o365frlrutneduar.onmicrosoft.com
Registro 2:
Tipo: CNAME
Nombre (Host): selector2._domainkey
Valor (Apunta a): selector2-frlr-utn-edu-ar._domainkey.o365frlrutneduar.onmicrosoft.com
¿Dónde se hace esto?
Vuelve a la pantalla que me mostraste antes (la del listado de registros MX, SPF y CNAME).
Busca un botón que diga "Agregar registro" o "Administrar DNS".
Carga los dos registros de arriba.
El paso final (Paciencia)
Una vez que los guardes en el DNS:
Espera al menos 15 a 30 minutos (aunque el cartel diga 4 días, suele ser rápido, pero no instantáneo).
Regresa a la pantalla de DKIM (la de la imagen anterior).
Intenta mover el interruptor a Habilitado.
Nota técnica: Si el panel de DNS te pide el nombre completo, asegúrate de que sea selector1._domainkey.frlr.utn.edu.ar. Algunos paneles añaden el dominio automáticamente, otros no.
Cuando ese interruptor se ponga en azul, ¡Gmail dejará de rechazarte! ¿Sabes dónde está el panel para agregar estos registros nuevos?
Enlaces
https://security.microsoft.com/dkimv2
https://admin.cloud.microsoft/?#/Domains/Details/frlr.utn.edu.ar
+93 -16
View File
@@ -168,21 +168,28 @@ La vista principal de la Bitácora Web ahora agrupa las entradas **por Ámbito**
## 🔄 Armonía Integral — Procedimiento de Actualización Documental ## 🔄 Armonía Integral — Procedimiento de Actualización Documental
**Cuándo aplicar:** Después de cualquier cambio significativo (nueva herramienta, refactorización, cambio de estructura, plan completado, etc.). ### ¿Qué significa "Aplicar Armonía Integral"?
### Checklist obligatorio (en orden): **Armonía Integral** es el principio rector que establece que **todo está conectado** en el ecosistema ADN. Cuando se dice "Aplicar Armonía", se refiere a:
| # | Documento | Qué actualizar | Cuándo | > **Actualizar/optimizar los documentos correspondientes para mantener la consistencia del sistema.**
|---|-----------|----------------|--------|
| 1 | **Bitácora** | Evento con resultados y próximos pasos | Siempre (antes de salir) | Si un dato cambia en un sitio, **debe propagarse** a todos los demás documentos relevantes.
| 2 | `docs/contexto/IA.md` | Estado de drones, lecciones aprendidas, quick changes | Si hay cambios operativos o de flujo |
| 3 | `docs/ambito/<ámbito>/<Axx>_manifiesto.md` | Tabla de planes, herramientas, nodos, estado | Si el cambio afecta al ámbito | ### 📋 Qué documentos actualizar cuando se dice "Aplicar Armonía"
| 4 | `docs/ambito/<ámbito>/<Axx.Pxxx>_plan.md` | Estado del plan, fases, hitos | Si el plan fue modificado/completado |
| Orden | Documento | Qué actualizar | Cuándo corresponde |
| :--- | :--- | :--- | :--- |
| **1** | **Bitácora** | Evento con resultados y próximos pasos | **Siempre** (antes de finalizar sesión) |
| **2** | `docs/contexto/IA.md` | Lecciones aprendidas, estado de herramientas, cambios operativos | Si hubo cambios en el flujo de trabajo o herramientas |
| **3** | `docs/ambito/<AMBITO>/Axx_<AMBITO>.md` | Tabla de planes, herramientas, nodos, estado del ámbito | Si el cambio afecta al ámbito de trabajo |
| **4** | `docs/ambito/<AMBITO>/Axx.Pxxx_*.md` | Estado del plan, fases completadas, hitos alcanzados | Si un plan fue modificado, completado o iniciado |
### Ejemplo de aplicación (2026-04-06): ### Ejemplo de aplicación (2026-04-06):
``` ```
Cambio: Reestructuración de dtic-DASUTEN a estándar A03 Cambio: Reestructuración de dtic-DASUTEN a estándar A03
→ 1. Bitácora: Evento #XXXX registrado → 1. Bitácora: Evento #XXXX registrado
→ 2. Contexto/IA.md: No requería (sin cambios operativos) → 2. Contexto/IA.md: No requería (sin cambios operativos)
→ 3. A04_dtic-DASUTEN.md: Manifiesto creado + tabla de planes actualizada → 3. A04_dtic-DASUTEN.md: Manifiesto creado + tabla de planes actualizada
@@ -226,22 +233,37 @@ Para información completa sobre todas las hebras del ADN, consultar `adn/00_ind
| `docs/ambito/dtic-DASUTEN/` | Ámbito A04: Infraestructura DASUTEN — `./adn/tools/run dasuten` | | `docs/ambito/dtic-DASUTEN/` | Ámbito A04: Infraestructura DASUTEN — `./adn/tools/run dasuten` |
| `docs/ambito/dtic-DNS/` | Ámbito A07: Administración DNS — `./adn/tools/run dns` | | `docs/ambito/dtic-DNS/` | Ámbito A07: Administración DNS — `./adn/tools/run dns` |
| `docs/ambito/dtic-NOTAS/` | Ámbito A08: Sistema de Notas e Informes — Web de plantillas | | `docs/ambito/dtic-NOTAS/` | Ámbito A08: Sistema de Notas e Informes — Web de plantillas |
| `adn/tools/cli/dron.rb` | Vigía tareas largas — `./adn/tools/run dron` | | `docs/ambito/dtic-DIIAA/` | Ámbito A09: IA y Automatizaciones — `./adn/tools/run dron` |
| `adn/tools/cli/dron.rb` | Drones: tareas background con auto-bitácora — `./adn/tools/run dron` |
**Última actualización:** 2026-04-07 22:30 (A08 creado - Web de notas funcional; A07.P002: Triada completa) **Última actualización:** 2026-04-08 09:50 (A09 creado; Drones con auto-bitácora y ámbito automático)
**Próxima revisión:** Al inicio de cada sesión **Próxima revisión:** Al inicio de cada sesión
--- ---
## 🤖 Estado de Drones (Verificación 2026-04-06 13:17) ## 🤖 Drones - Sistema Mejorado (2026-04-08)
**Flota:** 2 drones detectados → ambos completados y limpiados **Mejora implementada:** Auto-detección de nodo y ámbito automático en bitácora.
- `dron_125106_654316` — Evento #1395: "Test diario de vuelo" (`sleep 3`) → ✔ Completado (0min)
- `dron_125505_657901` — Evento #1398: "Test diario v2" (`echo Hola...`) → ✔ Completado
**Diagnóstico:** Drones operaron correctamente pero quedaron como "zombies" (PID muerto, estado=vigilando). El health check (`dron salud`) los auto-reparó y `dron limpiar` los removió. **Cambios realizados:**
1. **Auto-detección de nodo:** El dron detecta automáticamente el nodo por hostname
2. **Ámbito automático:** El ámbito se hereda del nodo mediante JOIN en la consulta
3. **Unificación de vistas:** `evento:listar` muestra tanto `events` (drones) como `entradas` (tradicional)
**Lección:** El sistema de auto-reparación funciona. Verificar flota al inicio de sesión con `./adn/tools/run dron salud`. **Flujo de trabajo:**
```bash
# Al iniciar sesión: verificar flota
./adn/tools/run dron salud
./adn/tools/run dron limpiar # Si hay zombies
# Para tareas en background (auto-registra en bitácora)
./adn/tools/run dron lanzar --nota "Descripción de la tarea" -- <comando>
# Opcional: especificar nodo manualmente
./adn/tools/run dron lanzar --nodo srv-ns8 --nota "Tarea" -- <comando>
```
**Importante:** Los drones registran automáticamente en bitácora con el ámbito correcto. Para SSH interactivo, usar `candados run`.
--- ---
@@ -267,3 +289,58 @@ Para información completa sobre todas las hebras del ADN, consultar `adn/00_ind
3. **Historial del dominio** - Correos anteriores sin autenticación afectan reputación inicial 3. **Historial del dominio** - Correos anteriores sin autenticación afectan reputación inicial
**Próximo paso:** Agregar DMARC con `p=none` (solo monitoreo) para completar triada de autenticación. **Próximo paso:** Agregar DMARC con `p=none` (solo monitoreo) para completar triada de autenticación.
---
## 🛠️ Optimización de `candados` (2026-04-08)
**Contexto:** La herramienta `candados.rb` es crítica para la seguridad del ecosistema. Se requiere que cualquier IA sepa usarla correctamente sin exponer contraseñas.
**Cambios realizados:**
1. **Help resumida (default):** Se muestra al ejecutar sin argumentos o con comando desconocido
- Mensaje conciso con lo esencial
- Los dos métodos para cargar variables (`run` y `load`)
- Lista de comandos principales
- Indica usar `--help` para más detalles
2. **Help completa (`--help` o `-h`):**
- Explicación detallada de cada método
- Ejemplos de uso
- Flujo de trabajo típico
- Advertencias de seguridad
3. **README actualizado:** Sincronizado con la ayuda integrada
**Lección:** La documentación integrada en la CLI es más efectiva que archivos externos. Cualquier IA que ejecute `candados` sin argumentos verá inmediatamente cómo usarla de forma segura.
**Comando para verificar:**
```bash
ruby adn/tools/candados/candados.rb # Help resumida
ruby adn/tools/candados/candados.rb --help # Help completa
```
---
## 🛠️ Lecciones Aprendidas - DASUTEN VMs (2026-04-08)
**Problema:** VMs dasu-sql2 y dasu-pcv2 mostraban "running" pero sin conectividad IP ni respuesta a ping.
**Diagnóstico:**
- Tabla ARP mostraba MACs en bridge pero sin respuesta ICMP
- Tailscale reportaba "offline, last seen 1d ago"
- qemu-guest-agent no estaba corriendo dentro de las VMs
**Causa probable:** Windows Server Core iniciaba pero sin stack de red activo o servicios de red fallando.
**Solución aplicada:** Crear VM 105 (dasu-sql3) limpia con:
- ISO Windows Server 2022 **Español** (no Core evaluation)
- **Discos SATA** (no VirtIO que puede causar problemas)
- 60GB disco para SO + 120GB disco para datos SQL
- virtio drivers integrados
**Nota técnica - LVM-thin:**
- `lvcreate -L` crea volúmenes finos pero requiere espacio físico real
- `lvcreate -V -T pool/thin` crea volúmenes thin que solo usan espacio cuando se escriben
- Verificar con `vgs` y `lvs -a` para diagnosticar espacio
- Thin volumes pueden tener metadata más pequeña que el tamaño virtual
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More