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==",
"domain_admin": "eyJpdiI6IjlYV0JRSVA1MzNLblFycUEiLCJ0YWciOiJ3aHI4a1cwanpLWU5OUmVHby9pd3ZRPT0iLCJkYXRhIjoiT1B3VGNHNkpUSWI0SnJ5NW5adHNVZz09In0=",
"admindasu": "eyJpdiI6IjlYV0JRSVA1MzNLblFycUEiLCJ0YWciOiJ3aHI4a1cwanpLWU5OUmVHby9pd3ZRPT0iLCJkYXRhIjoiT1B3VGNHNkpUSWI0SnJ5NW5adHNVZz09In0=",
"win_domain": "eyJpdiI6IitrY2V0eEQxektEMFk2cGgiLCJ0YWciOiJtQmdIYU90ZTFFWUlGZElsMXBIOTJBPT0iLCJkYXRhIjoiNVVFanhaN2tIdHViZDRDRnlnPT0ifQ==",
"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==",
"srv-ns8:root": "eyJpdiI6Ik9UbHBieDJhU0JZbFFEM0wiLCJ0YWciOiJZeWJ5K0lUUFdOcEQwTmJ5UzhwcVFRPT0iLCJkYXRhIjoiNUE1QW5yenZ0QldjNEdVNXVBPT0ifQ==",
"srv-ns8:rsa:passphrase": "eyJpdiI6ImpwRnFPYTZJdEtnUXdxT00iLCJ0YWciOiJMcXRBQ0lOT0paUE50Qjgva3lHSElRPT0iLCJkYXRhIjoiRDUzOFNuY3V1RUZXVHV4U3NnPT0ifQ==",
"srv-ns8:rmonla:sudo": "eyJpdiI6Ilg2VzhIMWN6Y2xYVzR3ZzgiLCJ0YWciOiJuWDk0SDcraVhJQTQxd3I3bEpLRFBnPT0iLCJkYXRhIjoiSllyRy9ScXFBRFBFc0l5emRRPT0ifQ==",
"srv-ns8:rmonla:rsa": "eyJpdiI6IkpoTmpZRWZMeE12eWhXSVMiLCJ0YWciOiJUUnJGY05qRlVoOTl2REplTms0ZU5RPT0iLCJkYXRhIjoiNkFIWCsyYm9ndzZKbGM3c0hRPT0ifQ==",
"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
`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
- **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).
## REGLA DE ORO — NUNCA MOSTRAR CONTRASEÑAS
## 🚀 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
# 1. Autorizar (dura 5 min)
ruby candados.rb authorize
# Sintaxis
ruby candados.rb run <clave> 'comando que usa $USR y $PASS'
# 2. Ejecutar comando con credenciales inyectadas
ruby candados.rb run srv-dasu:rmonla 'sshpass -p $PASS ssh $USR@host "comando"'
# Retrocompatible con variable legacy (SSHPASS, etc.)
ruby candados.rb run admindasu SSHPASS 'sshpass -e ssh root@host'
# Ejemplos
ruby candados.rb run srv-dasu:rmonla 'sshpass -p $PASS ssh $USR@host'
ruby candados.rb run srv-ns8:root 'ssh $USR@$HOSTNAME "comando"'
```
**¿Qué pasa internamente?**
@@ -35,6 +47,30 @@ ruby candados.rb run admindasu SSHPASS 'sshpass -e ssh root@host'
4. Limpia las variables al terminar
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`
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
3. Fallback genérico: `sudo`
## 📦 Comando Secundario: `load`
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
## 🔑 Resolución Automática de Claves
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 |
|---|---|---|
| `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`) |
| `get <clave>` | ⚠ Imprime a stdout | Obtener valor directo (usar con cuidado) |
| `authorize` | ✅ | Abrir candado (5 min) |
| `sudo <cmd>` | ✅ Nada expuesto | Ejecuta con privilegios desde bóveda |
| `authorize` | ✅ | Abrir candado (30 min) |
| `cerrar` | ✅ | Cerrar sesión manualmente |
| `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
@@ -93,12 +117,14 @@ Archivo `access.log` (permisos 600) registra:
## 🧠 Contexto para IA
1. **Siempre** ejecutar `authorize` antes de operar
2. **Usar `run`** como método primario (inyecta, no imprime)
3. **Evitar `get`** en comandos automatizados (filtra en logs)
4. Cerrar sesión con `cerrar` cuando ya no se necesite
1. **Siempre** ejecutar `authorize` antes de operar (sesión de 30 min)
2. **Usar `run`** como método primario (inyecta USR/PASS, no imprime)
3. **Usar `load`** para shell interactivo (eval + source)
4. **Evitar `get`** en automatizaciones (imprime la contraseña)
5. **Cerrar sesión** con `cerrar` cuando ya no se necesite
```bash
# Flujo típico
ruby candados.rb authorize
ruby candados.rb run srv-dasu:rmonla 'sshpass -p $PASS ssh $USR@host "ls"'
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")
user_value = cripto.decrypt(token_user) if token_user
elsif clave.include?(':')
user_value = clave.split(':').last
# Formato: <nodo>:<usuario>[:<tipo>]
# Ej: "srv-ns8:rmonla" → usuario="rmonla"
# "srv-ns8:rmonla:sudo" → usuario="rmonla" (no "sudo")
partes = clave.split(':')
if partes.size >= 2
# El usuario es siempre la segunda parte (índice 1)
user_value = partes[1]
end
else
token_user = boveda.get("#{clave}_user")
user_value = cripto.decrypt(token_user) if token_user
@@ -366,6 +373,64 @@ def cmd_load(cripto, boveda, clave)
$stderr.puts "#{C::GREEN}✓ Credenciales cargadas en variables USR y PASS (archivo temporal seguro).#{C::RESET}"
end
# ─── Comando ssh (ejecutar comando remoto con password de bóveda) ─────
# Uso: candados ssh <clave> <usuario@host> '<comando>'
# - clave: <nodo>:<usuario> para SSH (busca automáticamente <nodo>:<usuario>:sudo para sudo)
# - Si existe clave :sudo separada, la usa para comandos con sudo
def cmd_ssh(cripto, boveda, clave, usuario_host, *cmd_args)
abort "#{C::RED}✗ Falta nombre de clave.#{C::RESET}" if clave.nil?
abort "#{C::RED}✗ Falta usuario@host.#{C::RESET}" if usuario_host.nil? || usuario_host.empty?
abort "#{C::RED}✗ Falta comando a ejecutar.#{C::RESET}" if cmd_args.nil? || cmd_args.empty?
verificar_sesion!
# Obtener password SSH
token_ssh = boveda.get(clave)
abort "#{C::RED}✗ Clave '#{clave}' no encontrada.#{C::RESET}" if token_ssh.nil?
ssh_pass = cripto.decrypt(token_ssh)
safe_ssh_pass = ssh_pass.gsub("'", "'\\''")
# Buscar automáticamente clave :sudo si existe
comando = cmd_args.join(' ')
sudo_pass = ssh_pass
# Intentar buscar clave :sudo basada en la clave SSH
if clave.include?(':') && !clave.end_with?(':sudo')
# Construir clave sudo: <nodo>:<usuario>:sudo
partes = clave.split(':')
if partes.size >= 2
clave_sudo = "#{partes[0]}:#{partes[1]}:sudo"
token_sudo = boveda.get(clave_sudo)
if token_sudo
sudo_pass = cripto.decrypt(token_sudo)
$stderr.puts "#{C::DIM} Usando clave sudo: #{clave_sudo}#{C::RESET}"
end
end
end
safe_sudo_pass = sudo_pass.gsub("'", "'\\''")
registrar_acceso('SSH', "Clave: #{clave}, Host: #{usuario_host}, Cmd: #{comando}")
# Escapar comando para bash remoto: reemplazar ' con '\''
comando_escaped = comando.gsub("'", "'\\''")
# Crear script temporal con el comando y password para sudo
require 'securerandom'
require 'fileutils'
FileUtils.mkdir_p(TMP_DIR) unless File.exist?(TMP_DIR)
script_path = File.join(TMP_DIR, ".ssh_sudo_#{SecureRandom.hex(4)}")
File.write(script_path, "#!/bin/bash\nexport DEBIAN_FRONTEND=noninteractive\necho '#{safe_sudo_pass}' | sudo -S -p '' bash -c '#{comando_escaped}'\n")
File.chmod(0700, script_path)
# Ejecutar: sshpass para SSH, luego script remoto que hace sudo
result = system("sshpass -p '#{safe_ssh_pass}' ssh -o StrictHostKeyChecking=no -tt #{usuario_host} 'bash -s' < '#{script_path}'")
FileUtils.rm_f(script_path)
exit result ? 0 : 1
end
# ─── Ayuda mejorada para cmd_help_resumida ─────────────────────────────
# ─── Comando sudo (primitiva reutilizable) ─────────────────────
# Resolución inteligente de la clave sudo:
# 1. <hostname>:<usuario>:sudo (convención específica)
@@ -426,58 +491,130 @@ def cmd_sudo(cripto, boveda, comando_args)
exit $?.exitstatus || 0
end
def cmd_help
def cmd_help_resumida
puts <<~HELP
#{C::CYAN}🔐 candados — Gestión segura de secretos#{C::RESET}
#{C::CYAN}╔═══════════════════════════════════════════════════════════════════╗#{C::RESET}
#{C::CYAN}║#{C::RESET} 🔐 #{C::BOLD}candados — Acceso Seguro a Contraseñas (Sin Exponer)#{C::RESET} #{C::CYAN}║#{C::RESET}
#{C::CYAN}╚═══════════════════════════════════════════════════════════════════╝#{C::RESET}
#{C::GREEN}Uso:#{C::RESET} ruby adn/tools/seguridad/candados.rb <comando> [args]
#{C::RED}⚠️ REGLA DE ORO: NUNCA MOSTRAR CONTRASEÑAS#{C::RESET}
#{C::YELLOW}━━━ Comandos principales ━━━#{C::RESET}
#{C::BOLD}run <clave> <cmd>#{C::RESET} #{C::GREEN}★ PRIMARIO#{C::RESET} Ejecutar comando con USR y PASS inyectados
#{C::BOLD}sudo <comando>#{C::RESET} Ejecutar con privilegios (resuelve clave automáticamente)
#{C::BOLD}load <clave>#{C::RESET} Cargar USR/PASS en shell actual (usar con eval)
#{C::GREEN}┌─────────────────────────────────────────────────────────────────┐#{C::RESET}
#{C::GREEN}│#{C::RESET} #{C::BOLD}MÉTODOS PARA CARGAR CONTRASEÑAS EN VARIABLES#{C::RESET} #{C::GREEN}│#{C::RESET}
#{C::GREEN}└─────────────────────────────────────────────────────────────────┘#{C::RESET}
#{C::YELLOW}━━━ Gestión de bóveda ━━━#{C::RESET}
authorize Abrir candado (autorización temporal 30 min)
cerrar Cerrar candado manualmente
set <clave> Guardar secreto (input seguro, sin eco)
get <clave> Obtener secreto ( imprime a stdout)
rm <clave> Eliminar secreto
list Listar claves disponibles
#{C::BOLD}★ run <clave> 'comando'#{C::RESET} → Ejecuta con $USR y $PASS inyectadas (PRIMARIO)
#{C::BOLD}★ load <clave>#{C::RESET} → Carga $USR y $PASS en shell (usar con eval)
#{C::DIM}Ejemplo: ruby candados.rb run srv-ns8:rmonla 'sshpass -p $PASS ssh $USR@host'#{C::RESET}
#{C::YELLOW}━━━ Comandos ━━━#{C::RESET}
#{C::BOLD}run#{C::RESET} <clave> <cmd> #{C::DIM}Ejecutar con credenciales inyectadas#{C::RESET}
#{C::BOLD}load#{C::RESET} <clave> #{C::DIM}Cargar en shell actual (eval)#{C::RESET}
#{C::BOLD}sudo#{C::RESET} <comando> #{C::DIM}Ejecutar con privilegios#{C::RESET}
#{C::BOLD}ssh#{C::RESET} <clave> <u@h> <cmd> #{C::DIM}SSH remoto con password de bóveda#{C::RESET}
#{C::BOLD}authorize#{C::RESET} #{C::DIM}Abrir candado (30 min)#{C::RESET}
#{C::BOLD}list#{C::RESET} #{C::DIM}Listar claves disponibles#{C::RESET}
#{C::BOLD}set/rm#{C::RESET} <clave> #{C::DIM}Guardar/Eliminar secreto#{C::RESET}
#{C::DIM}Usá#{C::RESET} ruby candados.rb --help #{C::DIM}para ayuda completa y ejemplos detallados.#{C::RESET}
HELP
end
def cmd_help_completo
puts <<~HELP
#{C::CYAN}╔═══════════════════════════════════════════════════════════════════╗#{C::RESET}
#{C::CYAN}║#{C::RESET} 🔐 #{C::BOLD}candados — Acceso Seguro a Contraseñas (Sin Exponer)#{C::RESET} #{C::CYAN}║#{C::RESET}
#{C::CYAN}╚═══════════════════════════════════════════════════════════════════╝#{C::RESET}
#{C::RED}⚠️ REGLA DE ORO: NUNCA MOSTRAR CONTRASEÑAS#{C::RESET}
Esta herramienta está diseñada para que las contraseñas #{C::BOLD}NUNCA#{C::RESET} se muestren en pantalla
ni queden registradas en logs, históricos de shell o archivos temporales visibles.
#{C::GREEN}┌─────────────────────────────────────────────────────────────────┐#{C::RESET}
#{C::GREEN}│#{C::RESET} #{C::BOLD}MÉTODOS PARA CARGAR CONTRASEÑAS EN VARIABLES#{C::RESET} #{C::GREEN}│#{C::RESET}
#{C::GREEN}└─────────────────────────────────────────────────────────────────┘#{C::RESET}
#{C::BOLD}★ MÉTODO 1: run (PRIMARIO - Recomendado para comandos)#{C::RESET}
Ejecuta un comando con USR y PASS inyectadas en su entorno.
La contraseña #{C::BOLD}NUNCA#{C::RESET} se imprime ni queda en logs.
#{C::DIM}ruby candados.rb run <clave> 'comando que usa $USR y $PASS'#{C::RESET}
#{C::CYAN}Ejemplos:#{C::RESET}
#{C::GREEN}✓#{C::RESET} ruby candados.rb run srv-dasu:rmonla 'sshpass -p $PASS ssh $USR@host'
#{C::GREEN}✓#{C::RESET} ruby candados.rb run srv-ns8:root 'ssh $USR@$HOSTNAME "comando"'
#{C::BOLD}★ MÉTODO 2: load (Para shell interactivo)#{C::RESET}
Carga USR y PASS en variables de entorno del shell actual.
Usa un archivo temporal (600) que se auto-elimina tras el source.
#{C::DIM}eval $(ruby candados.rb load <clave>)#{C::RESET}
#{C::CYAN}Ejemplo:#{C::RESET}
#{C::GREEN}✓#{C::RESET} eval $(ruby candados.rb load srv-dasu:rmonla)
#{C::DIM} → Carga $USR y $PASS en tu shell actual#{C::RESET}
#{C::GREEN}✓#{C::RESET} sshpass -p $PASS ssh $USR@servidor
#{C::YELLOW}━━━ Otros Comandos ━━━#{C::RESET}
#{C::BOLD}sudo <comando>#{C::RESET} Ejecutar con sudo (resuelve contraseña automáticamente)
#{C::BOLD}authorize#{C::RESET} Abrir candado (sesión de 30 min, requerida para operar)
#{C::BOLD}cerrar#{C::RESET} Cerrar sesión manualmente antes de los 30 min
#{C::BOLD}abrir#{C::RESET} Cargar clave SSH en ssh-add (usa passphrase de bóveda)
#{C::YELLOW}━━━ Gestión de Bóveda ━━━#{C::RESET}
#{C::BOLD}set <clave>#{C::RESET} Guardar secreto (input seguro sin eco)
#{C::BOLD}list#{C::RESET} Listar claves disponibles (no muestra valores)
#{C::BOLD}rm <clave>#{C::RESET} Eliminar una clave de la bóveda
#{C::YELLOW}━━━ Utilidades ━━━#{C::RESET}
abrir Autorizar SSH (cargar passphrase)
encrypt <texto> Cifrar texto plano token
decrypt <token> Descifrar token texto plano
#{C::BOLD}encrypt <texto>#{C::RESET} Cifrar texto plano → token (para bóveda)
#{C::BOLD}decrypt <token>#{C::RESET} Descifrar token → texto plano
#{C::YELLOW}Resolución automática de claves:#{C::RESET}
#{C::DIM}• run/load: Detecta pares USR/PASS por convención:#{C::RESET}
srv-dasu:rmonla USR=rmonla, PASS=(valor cifrado)
tailscale_dasuten busca _user/_pass automáticamente
#{C::DIM}• sudo: Resuelve la clave de sudo automáticamente:#{C::RESET}
<hostname>:<usuario>:sudo (ej: ns8:rmonla:sudo)
#{C::YELLOW}━━━ Resolución Automática de Claves ━━━#{C::RESET}
#{C::DIM}• run/load con formato `servidor:usuario`:#{C::RESET}
srv-dasu:rmonla USR=rmonla, PASS=(descifrado de bóveda)
#{C::DIM}• run/load con formato `prefijo_pass`:#{C::RESET}
tailscale_dasuten_pass busca automáticamente tailscale_dasuten_user
#{C::DIM}• sudo resuelve la clave automáticamente:#{C::RESET}
Busca: <hostname>:<usuario>:sudo fallback: `sudo`
#{C::YELLOW}Ejemplos:#{C::RESET}
#{C::DIM}# 1. Autorizar (dura 30 min)#{C::RESET}
ruby candados.rb authorize
#{C::YELLOW}━━━ Flujo de Trabajo Típico ━━━#{C::RESET}
#{C::DIM}1. Autorizar (una vez cada 30 min):#{C::RESET}
ruby candados.rb authorize
#{C::GREEN}# 2. Ejecutar comando con credenciales (RECOMENDADO)#{C::RESET}
ruby candados.rb run srv-dasu:rmonla 'sshpass -p $PASS ssh $USR@host'
ruby candados.rb run admindasu SSHPASS 'sshpass -e ssh root@host'
#{C::DIM}2. Ejecutar comandos (Método recomendado):#{C::RESET}
ruby candados.rb run srv-ns8:rmonla 'sshpass -p $PASS ssh $USR@host "comando"'
#{C::DIM}# 3. Ejecutar con sudo#{C::RESET}
ruby candados.rb sudo tailscale set --operator=$USER
#{C::DIM}3. O cargar en shell interactivo:#{C::RESET}
eval $(ruby candados.rb load srv-ns8:rmonla)
sshpass -p $PASS ssh $USR@host
#{C::DIM}# 4. Cargar en shell interactivo (secundario)#{C::RESET}
eval $(ruby candados.rb load srv-dasu:rmonla)
#{C::RED}⚠️ ADVERTENCIAS DE SEGURIDAD:#{C::RESET}
#{C::BOLD}• NUNCA usar `get`#{C::RESET} en automatizaciones → imprime la contraseña a stdout
#{C::BOLD}• `run` y `sudo`#{C::RESET} → las credenciales no dejan rastro en logs
#{C::BOLD}• `load`#{C::RESET} → archivo temporal con permisos 600, auto-borrado
#{C::BOLD}• Siempre ejecutar `authorize`#{C::RESET} antes de operar (sesión de 30 min)
#{C::RED}⚠ Seguridad:#{C::RESET}
#{C::DIM}• run/sudo: Las credenciales NUNCA se imprimen en pantalla ni logs.#{C::RESET}
#{C::DIM}• load: Usa archivo temporal (600) con autoborrado, nada visible.#{C::RESET}
#{C::DIM}• get: ⚠ Imprime el valor a stdout. Usar solo si es necesario.#{C::RESET}
#{C::DIM}Documentación completa: adn/tools/candados/README.md#{C::RESET}
HELP
end
alias cmd_help cmd_help_completo
# ─── Recordatorio de Seguridad (solo en ayuda) ──────────────────
def mostrar_recordatorio_seguridad
puts <<~RECORDATORIO
#{C::CYAN}╔════════════════════════════════════════════════════════════════╗#{C::RESET}
#{C::CYAN}║#{C::RESET} 🔐 #{C::BOLD}REGLA DE ORO: NUNCA MOSTRAR CONTRASEÑAS#{C::RESET} #{C::CYAN}║#{C::RESET}
#{C::CYAN}║#{C::RESET} #{C::GREEN}✓ CORRECTO:#{C::RESET} ruby candados.rb run <clave> '<cmd>' #{C::CYAN}║#{C::RESET}
#{C::CYAN}║#{C::RESET} #{C::RED}✗ INCORRECTO:#{C::RESET} ruby candados.rb get <clave> #{C::CYAN}║#{C::RESET}
#{C::CYAN}║#{C::RESET} → `run` inyecta USR/PASS sin mostrar. `get` imprime y deja rastro. #{C::CYAN}║#{C::RESET}
#{C::CYAN}╚════════════════════════════════════════════════════════════════╝#{C::RESET}
RECORDATORIO
puts
end
# ─── Main ────────────────────────────────────────────────────────────
if __FILE__ == $0
comando = ARGV[0]
@@ -496,6 +633,10 @@ if __FILE__ == $0
cripto = Cripto.new(cargar_master_key)
boveda = Boveda.new(BOVEDA)
cmd_sudo(cripto, boveda, ARGV[1..])
when 'ssh'
cripto = Cripto.new(cargar_master_key)
boveda = Boveda.new(BOVEDA)
cmd_ssh(cripto, boveda, ARGV[1], ARGV[2], *ARGV[3..])
when 'encrypt', 'decrypt', 'get', 'load', 'run', 'set', 'rm', 'list'
cripto = Cripto.new(cargar_master_key)
boveda = Boveda.new(BOVEDA)
@@ -509,11 +650,14 @@ if __FILE__ == $0
when 'rm' then cmd_rm(boveda, arg)
when 'list' then cmd_list(boveda)
end
when 'help', 'ayuda', '-h', '--help', nil
cmd_help
when '--help', '-h'
cmd_help_completo
when 'help', 'ayuda', nil
cmd_help_resumida
else
$stderr.puts "#{C::RED}✗ Comando desconocido: '#{comando}'#{C::RESET}"
cmd_help
$stderr.puts ""
cmd_help_resumida
exit 1
end
end
+6 -1
View File
@@ -66,6 +66,7 @@ module Dron
lines << "\n#{Color::BOLD}Opciones:#{Color::RESET}"
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}--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}--dry-run#{Color::RESET} Simulación sin ejecutar"
lines << " #{Color::GREEN}--help#{Color::RESET} Mostrar esta ayuda"
@@ -107,7 +108,8 @@ module Dron
nota: opciones[:nota],
evento_id: opciones[:evento_id],
timeout: opciones[:timeout],
flujo_id: opciones[:flujo_id]
flujo_id: opciones[:flujo_id],
nodo: opciones[:nodo]
)
# Mostrar resultado
@@ -128,6 +130,7 @@ module Dron
evento_id: nil,
timeout: 3600,
flujo_id: nil,
nodo: nil,
cmd: nil
}
@@ -143,6 +146,8 @@ module Dron
opciones[:timeout] = args.shift.to_i
when '--flujo'
opciones[:flujo_id] = args.shift
when '--nodo'
opciones[:nodo] = args.shift
when '--help', '-h'
Dispatcher.mostrar_ayuda
exit 0
+43 -6
View File
@@ -13,15 +13,15 @@ module Dron
HEARTBEAT_INTERVAL = 30
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}")
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 })
evento_id = registrar_bitacora(nota, evento_id, 'inicio') if nota
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', nodo) if nota
resultado = ejecutar_comando(cmd, dron_id, timeout)
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 ? '✅' : '❌'}")
{ 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) }
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
require_relative '../../db/core/bitacora_db'
# Detectar nodo automáticamente si no se especifica
nodo_id = detectar_nodo_id(nodo)
if tipo == 'inicio'
descripcion = "🛸 dron: #{nota}"
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
else
estado = resultado[:exit_code] == 0 ? '✅' : '❌'
@@ -82,11 +86,44 @@ module Dron
BitacorasDB::BitacoraDB.with_connection do |db|
db.actualizar_evento(evento_id, descripcion: descripcion, fin: Time.now.strftime('%H:%M'))
end
evento_id # Retornar mismo ID
end
rescue StandardError => e
Base.logger.warn("⚠️ Error en bitácora: #{e.message}")
nil
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
+30 -4
View File
@@ -666,20 +666,45 @@ module BitacorasDB
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
SELECT e.id, e.inicio, e.fin, 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,
SELECT ev.id, ev.inicio::text as inicio, ev.fin::text as fin, ev.descripcion, ev.estado,
'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,
n.nombre as nodo_nombre,
t.titulo as tema_titulo,
a.nombre as ambito_nombre
COALESCE(a.nombre, na.nombre) as ambito_nombre
FROM #{SCHEMA}.#{TABLES[:entradas]} e
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[:temas]} t ON e.tema_id = t.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}
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}
SQL
@@ -1007,6 +1032,7 @@ module BitacorasDB
public
# 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)
inicio ||= Time.now.strftime('%H:%M')
query = <<~SQL
@@ -5,8 +5,8 @@
**Código:** A04.P005
**Fecha:** 27 de marzo de 2026
**Autor:** Sistema ADN
**Versión:** 2.2
**Estado:** 🚧 EN EJECUCIÓN (Fase 2 completada, Fase 3 pendiente — Creación VM PC Cliente)
**Versión:** 2.5
**Estado:** 🚧 EN EJECUCIÓN (Nueva VM 105 dasu-sql3 - Fase 4.N1-4.N3 completadas)
**Dependencia:** A04.P001 (Red srv-dasu)
## 📋 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**
- [ ] **3.7:** Instalar herramientas cliente SQL (SSMS o sqlcmd) si es necesario.
- [ ] **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) ✅.
### ⏳ **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**
- [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
**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 |
|---|-----------|----------------|--------|
| 1 | **Bitácora** | Evento con resultados y próximos pasos | Siempre (antes de salir) |
| 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 |
| 4 | `docs/ambito/<ámbito>/<Axx.Pxxx>_plan.md` | Estado del plan, fases, hitos | Si el plan fue modificado/completado |
> **Actualizar/optimizar los documentos correspondientes para mantener la consistencia del sistema.**
Si un dato cambia en un sitio, **debe propagarse** a todos los demás documentos relevantes.
### 📋 Qué documentos actualizar cuando se dice "Aplicar Armonía"
| 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):
```
Cambio: Reestructuración de dtic-DASUTEN a estándar A03
→ 1. Bitácora: Evento #XXXX registrado
→ 2. Contexto/IA.md: No requería (sin cambios operativos)
→ 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-DNS/` | Ámbito A07: Administración DNS — `./adn/tools/run dns` |
| `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
---
## 🤖 Estado de Drones (Verificación 2026-04-06 13:17)
## 🤖 Drones - Sistema Mejorado (2026-04-08)
**Flota:** 2 drones detectados → ambos completados y limpiados
- `dron_125106_654316` — Evento #1395: "Test diario de vuelo" (`sleep 3`) → ✔ Completado (0min)
- `dron_125505_657901` — Evento #1398: "Test diario v2" (`echo Hola...`) → ✔ Completado
**Mejora implementada:** Auto-detección de nodo y ámbito automático en bitácora.
**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
**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