Volver a la wiki

T1 Cifrado de Credenciales SNMP en Reposo

Resumen

Tanda transversal T1 del Informe Supremo (Auditoría Etapa 3): cifrado en reposo de todas las credenciales SNMP de la red — DeviceProfile y MonitoringTarget — utilizando Fernet vía CredentialManager.

Hasta esta entrega, las credenciales SNMP se guardaban en claro en la base de datos PostgreSQL:

Ahora se cifran automáticamente en save() (idempotente: un token Fernet gAAAAA... no se re-cifra) y los lectores descifran en el punto de uso exacto donde se necesitan en claro: pollers, deep-discover, envío al Agente, modal Config, backup/restore.

Motivación

Cierre de deudas críticas de auditoría:

Una base de datos robada o copia de seguridad extraviada ya no expone las contraseñas de acceso a los equipos de red del cliente.

Implementación

Cambios en modelos

DeviceProfile (network/models.py):

MonitoringTarget (monitoring/models.py):

Cambios en CredentialManager (core/security/credential_manager.py)

Dos nuevos métodos públicos:

  1. encrypt_if_plain(value): cifra un valor a menos que:

    • Sea falsy (None, "", etc.) — devuelve tal cual.
    • Ya sea un token Fernet (detectado por is_encrypted(), prefijo gAAAAA) — devuelve tal cual (idempotente).
    • En otro caso: cifra con encrypt_credential().
  2. decrypt_or_passthrough(value, field_name=""): descifra un token Fernet; no es un error encontrar plaintext legado:

    • Falsy o no-string: devuelve tal cual.
    • No es token (no empieza por gAAAAA): devuelve tal cual (plaintext legado, pre-migración).
    • Es token pero no descifrable (clave rotada sin legacy fallback, o token corrupto): devuelve "" (nunca filtra ciphertext a SNMP libs o API).

Migraciones

network/0049: Altera los 3 campos SNMP de CharField a TextField con ayuda.

network/0050: Cifra todos los DeviceProfile existentes. Idempotente: encrypt_if_plain salta tokens. Reverse: no-op deliberado (descifrar re-expondría secretos).

monitoring/0023: Cifra todos los MonitoringTarget.config existentes. Idempotente y reverse no-op.

Puntos de descifrado (solo cuando se necesita claro)

  1. SNMPService.from_config(): descifra snmp_community, snmp_v3_auth_key, snmp_v3_priv_key del dict config.
  2. DeviceDiscoveryService.build_snmp_auth_from_profile() y build_snmp_auth_from_config(): descifran credenciales antes de pasarlas a pysnmp.
  3. DeviceDiscoveryService.walk_if_table(): descifra community.
  4. Deep-discover tasks al Agent: descifran credenciales antes de serializar la tarea.
  5. WS consumer _send_targets() y REST sentinel.get_agent_targets(): descifran el dict config antes de enviarlo al Agente (quien lo recibe por TLS y re-cifra con DPAPI en su lado).
  6. Provisioning.build_snmp_config(): mantiene cifrado en el dict (sale del profile ya cifrado).
  7. Modal Config (get_profile_config): descifra para mostrar al usuario (deuda A4 sa1: exponer credential en respuesta es diferido).
  8. DeviceProfileOut resolver (network/api/common.py): descifra snmp_community para la API (statu quo del contrato).
  9. Backup/Restore:
    • Backup (ZIP): exporta credenciales SNMP en claro (decisión: el backup debe ser restaurable en otra instalación/clave; la descarga ya está gateada a rol edit).
    • Restore: lee ZIP, vuelca en BD; save() re-cifra automáticamente.

Bulk paths que saltan save()

Rotación de claves extendida

Command reencrypt_credentials (core/management/commands/reencrypt_credentials.py):

Tests

tests/api/test_t1_snmp_encryption.py (14 nuevos):

Adaptados (4 tests que asumían plaintext):

Suite corrida en Docker (mismo entorno CI) antes del push.

Compatibilidad

Deudas diferidas

Véase también

Subir