Auditoría de acceso en ns8-candados: Implementación de access.log y registro de eventos (authorize/get/lock)
This commit is contained in:
@@ -26,6 +26,15 @@
|
|||||||
- `ruby ns8-candados.rb list`: Lista las claves guardadas (requiere autorización).
|
- `ruby ns8-candados.rb list`: Lista las claves guardadas (requiere autorización).
|
||||||
- `ruby ns8-candados.rb get <clave>`: Recupera el valor descifrado (requiere autorización).
|
- `ruby ns8-candados.rb get <clave>`: Recupera el valor descifrado (requiere autorización).
|
||||||
|
|
||||||
|
## 🛡️ Auditoría (logging)
|
||||||
|
La herramienta mantiene un archivo `access.log` en su directorio local que registra de forma inmutable:
|
||||||
|
- **Timestamp**: Fecha y hora exacta de la acción.
|
||||||
|
- **Usuario**: El usuario del sistema operativo que ejecutó el comando.
|
||||||
|
- **Acción**: `AUTHORIZE`, `GET`, `LOCK`, `DENIED` o `EXPIRED`.
|
||||||
|
- **Detalle**: Nombre de la clave solicitada (solo en comandos `get`).
|
||||||
|
|
||||||
|
El archivo tiene permisos `600` para asegurar que solo el propietario pueda auditar los accesos.
|
||||||
|
|
||||||
## 🧠 Contexto para IA
|
## 🧠 Contexto para IA
|
||||||
- **Flujo Obligatorio**: Los agentes de IA DEBEN ejecutar `authorize` antes de intentar recuperar cualquier clave.
|
- **Flujo Obligatorio**: Los agentes de IA DEBEN ejecutar `authorize` antes de intentar recuperar cualquier clave.
|
||||||
- **Captura Segura**: Capturar siempre la salida en variables silenciadas:
|
- **Captura Segura**: Capturar siempre la salida en variables silenciadas:
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ MASTER_KEY = File.join(TOOL_DIR, '.master.key')
|
|||||||
OLD_KEY = File.expand_path('../../.master.key', TOOL_DIR)
|
OLD_KEY = File.expand_path('../../.master.key', TOOL_DIR)
|
||||||
BOVEDA = File.join(TOOL_DIR, '.boveda.json')
|
BOVEDA = File.join(TOOL_DIR, '.boveda.json')
|
||||||
SESION = File.join(TOOL_DIR, '.session')
|
SESION = File.join(TOOL_DIR, '.session')
|
||||||
|
ACCESS_LOG = File.join(TOOL_DIR, 'access.log')
|
||||||
|
|
||||||
# ─── Transición de Seguridad ─────────────────────────────────────────
|
# ─── Transición de Seguridad ─────────────────────────────────────────
|
||||||
if File.exist?(OLD_KEY) && !File.exist?(MASTER_KEY)
|
if File.exist?(OLD_KEY) && !File.exist?(MASTER_KEY)
|
||||||
@@ -122,26 +123,44 @@ def cargar_master_key
|
|||||||
File.read(MASTER_KEY)
|
File.read(MASTER_KEY)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# ─── Auditoría ───────────────────────────────────────────────────────
|
||||||
|
def registrar_acceso(accion, detalle = '')
|
||||||
|
usuario = ENV['USER'] || ENV['USERNAME'] || 'unknown'
|
||||||
|
timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
log_entry = "[#{timestamp}] - user:#{usuario} - action:#{accion} - #{detalle}\n"
|
||||||
|
|
||||||
|
File.open(ACCESS_LOG, 'a') do |f|
|
||||||
|
f.write(log_entry)
|
||||||
|
end
|
||||||
|
FileUtils.chmod(0600, ACCESS_LOG) if File.exist?(ACCESS_LOG)
|
||||||
|
end
|
||||||
|
|
||||||
# ─── Gestión de Sesión (MFA) ─────────────────────────────────────────
|
# ─── Gestión de Sesión (MFA) ─────────────────────────────────────────
|
||||||
def autorizar!
|
def autorizar!
|
||||||
File.write(SESION, Time.now.to_i.to_s)
|
File.write(SESION, Time.now.to_i.to_s)
|
||||||
FileUtils.chmod(0600, SESION)
|
FileUtils.chmod(0600, SESION)
|
||||||
|
registrar_acceso('AUTHORIZE', 'Candado abierto (Sesión iniciada)')
|
||||||
$stderr.puts "#{C::GREEN}✓ Autorización concedida (Válida por 5 minutos).#{C::RESET}"
|
$stderr.puts "#{C::GREEN}✓ Autorización concedida (Válida por 5 minutos).#{C::RESET}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def cerrar_sesion!
|
def cerrar_sesion!
|
||||||
FileUtils.rm_f(SESION)
|
if File.exist?(SESION)
|
||||||
$stderr.puts "#{C::YELLOW}🔒 Sesión cerrada. Candado puesto.#{C::RESET}"
|
FileUtils.rm_f(SESION)
|
||||||
|
registrar_acceso('LOCK', 'Candado cerrado (Sesión finalizada)')
|
||||||
|
$stderr.puts "#{C::YELLOW}🔒 Sesión cerrada. Candado puesto.#{C::RESET}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def verificar_sesion!
|
def verificar_sesion!
|
||||||
unless File.exist?(SESION)
|
unless File.exist?(SESION)
|
||||||
|
registrar_acceso('DENIED', 'Intento de acceso sin autorización')
|
||||||
abort "#{C::RED}✗ Error: El candado está puesto. Ejecutá 'ruby #{File.basename($0)} authorize' para abrirlo.#{C::RESET}"
|
abort "#{C::RED}✗ Error: El candado está puesto. Ejecutá 'ruby #{File.basename($0)} authorize' para abrirlo.#{C::RESET}"
|
||||||
end
|
end
|
||||||
|
|
||||||
inicio = File.read(SESION).to_i
|
inicio = File.read(SESION).to_i
|
||||||
if Time.now.to_i - inicio > 300 # 5 minutos
|
if Time.now.to_i - inicio > 300 # 5 minutos
|
||||||
cerrar_sesion!
|
cerrar_sesion!
|
||||||
|
registrar_acceso('EXPIRED', 'Sesión expirada automáticamente')
|
||||||
abort "#{C::RED}✗ Sesión expirada. Por seguridad, volvé a autorizar.#{C::RESET}"
|
abort "#{C::RED}✗ Sesión expirada. Por seguridad, volvé a autorizar.#{C::RESET}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -179,6 +198,8 @@ def cmd_get(cripto, boveda, clave)
|
|||||||
verificar_sesion!
|
verificar_sesion!
|
||||||
token = boveda.get(clave)
|
token = boveda.get(clave)
|
||||||
abort "#{C::RED}✗ Clave '#{clave}' no encontrada.#{C::RESET}" if token.nil?
|
abort "#{C::RED}✗ Clave '#{clave}' no encontrada.#{C::RESET}" if token.nil?
|
||||||
|
|
||||||
|
registrar_acceso('GET', "Clave: #{clave}")
|
||||||
puts cripto.decrypt(token)
|
puts cripto.decrypt(token)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user