Volver a la wiki

Servicio crypto del Agente — Cifrado DPAPI en reposo (terminal/agent/core/crypto.py)

Ubicación

Ruta: terminal/agent/core/crypto.py
Módulo Python: terminal.agent.core.crypto
Líneas: ~103
Estado: ✅ Activo desde s124 (2026-06-10)

Propósito

Encripta secretos sensibles (credenciales SNMP: snmp_community, snmp_v3_auth_key, snmp_v3_priv_key) antes de escribirlos en la BD local (metrics.db) del Agent. Usa Windows DPAPI (Data Protection API), atado a la cuenta Windows del usuario, garantizando que solo esa cuenta puede descifrar los datos.

Interfaz pública

Función encrypt_secret(value: str | None) -> str | None

Firma:

def encrypt_secret(value: str | None) -> str | None

Propósito: Encripta un secreto para almacenamiento en reposo.

Comportamiento:

Retorno: string con prefijo "dpapi:" + token base64, o el valor original si no se cifró.

Función decrypt_secret(value: str | None) -> str | None

Firma:

def decrypt_secret(value: str | None) -> str | None

Propósito: Descifra un secreto almacenado.

Comportamiento:

Retorno: el secreto en claro, o string vacío si descifrado falló.

Funciones internas (Windows DPAPI)

_dpapi_encrypt(data: bytes) -> bytes

Llama a CryptProtectData (ctypes). Eleva RuntimeError si falla. Libera memoria alocada por Windows.

_dpapi_decrypt(data: bytes) -> bytes

Llama a CryptUnprotectData (ctypes). Eleva RuntimeError si falla. Libera memoria.

Integración en el sistema

core/store.py — TimeSeriesStore

En lectura (get_target_list()):

for row in rows:
    for field in _SECRET_TARGET_FIELDS:  # ("snmp_community", "snmp_v3_auth_key", "snmp_v3_priv_key")
        if field in row:
            row[field] = decrypt_secret(row[field])
return rows

En escritura (upsert_target()):

encrypt_secret(target.get("snmp_community")),
encrypt_secret(target.get("snmp_v3_auth_key", "")),
encrypt_secret(target.get("snmp_v3_priv_key", "")),

Flujo completo

  1. SaaS → Agent (vía WebSocket): SaaS envía target con credenciales en claro.
  2. Agent escribe (upsert_target): crypto.encrypt_secret() → almacena con prefijo "dpapi:" en metrics.db.
  3. Agent lee (get_target_list): crypto.decrypt_secret() → descifra en memoria, retorna en claro a quien lo llamó (ej. routes/network.py).
  4. Uso local: credencial está en claro solo en memoria del proceso Agent, nunca toca disco así.

Compatibilidad hacia atrás (legacy)

Problema: DBs existentes pueden tener credenciales en claro (s123 y antes).

Solución: decrypt_secret() detecta el prefijo "dpapi:":

Auto-recifrado: cuando SaaS hace upsert_target() (push de actualización), encrypt_secret() recifra el valor, migrando la BD poco a poco sin downtime.

Detalles de implementación

Formato de almacenamiento

"dpapi:" + base64(CryptProtectData(utf-8_bytes))

Variables privadas

Logging

Usa logger = logging.getLogger("agent.crypto") → mensajes en nivel INFO/ERROR, sin exponer valores secretos.

Seguridad

DPAPI (Data Protection API)

Protección del known_hosts

El fichero known_hosts (creado por network/host_keys.py) contiene public keys de los dispositivos, no secretos. No requiere cifrado DPAPI (los public keys no son confidenciales).

Testing

Notas de operación

Véase también

Subir