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:
DeviceProfile.snmp_community(v2c)DeviceProfile.snmp_v3_auth_keyysnmp_v3_priv_key(v3)- Claves sensibles en
MonitoringTarget.config(sub-cadenascommunity,password,auth_key,priv_key,secret,credential)
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:
- network A8: Credenciales SNMP v2c/v3 cifradas en reposo.
- monitoring sa2/sa5: Secretos en
MonitoringTarget.configcifrados. - Fleco E del Plan Hardening: Alineación con estándares de seguridad de datos sensibles.
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):
snmp_community,snmp_v3_auth_key,snmp_v3_priv_key: cambiados deCharFieldaTextField(el token Fernet es más largo que 255 chars).save(): cifra todos los campos SNMP sensibles medianteCredentialManager.encrypt_if_plain().- 3 nuevas properties:
snmp_community_plain,snmp_v3_auth_key_plain,snmp_v3_priv_key_plain— devuelven el valor descifrado (o en claro si es legado). SNMP_SECRET_FIELDS: tupla con los nombres de los campos a cifrar (reutilizada porreencrypt_credentials).
MonitoringTarget (monitoring/models.py):
config(JSONField): claves sensibles (detectadas por sub-cadena) se cifran ensave().encrypt_config(cfg): method estático que cifra idempotentemente valores sensibles dentro del dict.secret_configproperty: descifra el dict completo para lectores (pollers, etc.).public_configproperty: existente, sigue filtrando claves sensibles (sin exponerlas)._is_sensitive_key(): helper para detectar claves sensibles.
Cambios en CredentialManager (core/security/credential_manager.py)
Dos nuevos métodos públicos:
-
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(), prefijogAAAAA) — devuelve tal cual (idempotente). - En otro caso: cifra con
encrypt_credential().
- Sea falsy (
-
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)
- SNMPService.from_config(): descifra
snmp_community,snmp_v3_auth_key,snmp_v3_priv_keydel dictconfig. - DeviceDiscoveryService.build_snmp_auth_from_profile() y build_snmp_auth_from_config(): descifran credenciales antes de pasarlas a pysnmp.
- DeviceDiscoveryService.walk_if_table(): descifra community.
- Deep-discover tasks al Agent: descifran credenciales antes de serializar la tarea.
- WS consumer _send_targets() y REST sentinel.get_agent_targets(): descifran el dict
configantes de enviarlo al Agente (quien lo recibe por TLS y re-cifra con DPAPI en su lado). - Provisioning.build_snmp_config(): mantiene cifrado en el dict (sale del profile ya cifrado).
- Modal Config (get_profile_config): descifra para mostrar al usuario (deuda A4 sa1: exponer credential en respuesta es diferido).
- DeviceProfileOut resolver (
network/api/common.py): descifrasnmp_communitypara la API (statu quo del contrato). - 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()
- MonitoringTarget bulk_create (
targets.py): cifra explícitamente el dictconfigantes de pasarlo a.bulk_create(). - Provisioning.build_snmp_config(): devuelve dict ya cifrado.
Rotación de claves extendida
Command reencrypt_credentials (core/management/commands/reencrypt_credentials.py):
- Ahora también procesa
DeviceProfile(3 campos SNMP +ssh_password_encrypted). - Ahora también procesa
MonitoringTarget.config. - Antes cubría:
StoredCredential,SignagePlayer.api_credentials,NotificationChannel.config.
Tests
tests/api/test_t1_snmp_encryption.py (14 nuevos):
- Cifrado en
save(). - Accessors *_plain descifran.
- Plaintext legado pasa sin error.
- Token corrupto degrada a
"". - Bulk endpoint cifra explícitamente.
- Pollers descifran correctamente.
- Provisioning mantiene cifrado.
- Command de re-cifrado cubre los 2 almacenes nuevos.
Adaptados (4 tests que asumían plaintext):
test_network_profile_config: cifrado en BD, plaintext via property.test_monitoring: acceso viasecret_config.test_monitoring_enable_profile: idem.test_auto_config: idem.
Suite corrida en Docker (mismo entorno CI) antes del push.
Compatibilidad
- Legado: Filas pre-migración con plaintext en BD pasan por
decrypt_or_passthrough()sin error. - Idempotencia:
encrypt_if_plain()permite re-ejecutar migraciones. - Reverse migraciones: no-op deliberado (rollback nunca descifra).
- Multi-instalación: backup/restore exporta en claro, permitiendo restauración en otra instancia/clave.
Deudas diferidas
- A4 sa1: Exponer
snmp_communityenDeviceProfileOutes inseguro pero statu quo; será cerrada en otra tanda.
Véase también
- [[entity—network—model—device-profile]]
- [[entity—monitoring—model—monitoring-target]]
- [[entity—core—service—credential-manager]]
- [[concept—saas—multi-tenancy]]
- [[concept—security—credential-encryption]]