Descripción
encrypt_channel_config(config: dict) → dict es el punto de entrada para cifrar la configuración de un canal de notificación en el momento de creación o actualización. Implementa el patrón Fernet (simétrico) espejo de StoredCredential.encrypted_data: valores secretos dentro del JSONField son cifrados en situ, mientras que claves públicas (nombres, direcciones, etc.) permanecen en plaintext para debugging y queries.
Ubicación: monitoring/services/notification_service.py (línea ~120).
Qué cifra
- Top-level: claves en
_SECRET_CONFIG_KEYS(url,token, etc.) - Headers anidados: solo aquellos cuyo nombre aparece en
_SECRET_HEADER_NAMES(e.g.,Authorization) - Estructura: todas las demás claves (
addresses,name,enabled) quedan en plaintext
Componentes
_enc_value(v: str) → str
Función auxiliar que:
- Si
ves vacío,None, o no es string → devuelve sin cambios - Si ya es un token Fernet (detectado por
CredentialManager.is_encrypted(v)) → devuelve sin cambios (idempotencia) - Si no → llama a
CredentialManager.encrypt_credential(v)y devuelve el token Fernet
Fuente: core.security.credential_manager.CredentialManager (clase de encriptación centralizada)
Flujo de integración
En create_channel (API endpoint):
config=encrypt_channel_config(data.config)
En update_channel (API endpoint):
c.config = encrypt_channel_config(merge_preserving_secrets(data.config, c.config))
El merge_preserving_secrets mantiene intactos los valores ya cifrados cuando el cliente reenvía el placeholder REDACTED; luego encrypt_channel_config re-cifra (idempotentemente) cualquier nuevo plaintext.
Idempotencia y tolerancia
El commit enfatiza que ambas direcciones son tolerantes:
- No dobla-cifra valores ya encriptados (check
is_encrypted) - No falla si el
configesNoneo no es dict - Seguro de ejecutar múltiples veces (p. ej., si migración se interrumpe y repite)
Esto es crítico para la migración de datos monitoring/0021, que recorre filas existentes en segundo plano.
Seguridad (sa4 A2, 2ª mitad)
Previamente (sa4 A2, 1ª mitad) se redactaba el config en la API (endpoint GET devolvía REDACTED). Ahora:
- El secreto nunca deja la BD en plaintext
- Un dump o backup ahora cifra los valores
- El dispatcher (webhooks, Slack, Teams) descifra justo antes de usar (
decrypt_channel_config)
Esto elimina el riesgo de exposure accidental en logs de infraestructura, snapshots de DB, o capturas de memoria.
Testing
Tres nuevos tests en tests/api/test_monitoring_sa4_sa5.py::TestChannelConfigEncryption:
test_secret_is_encrypted_at_rest: POST a/api/.../channelscon URL plaintext → en la BD está como Fernet tokentest_dispatch_roundtrip_decrypts: encrypt → decrypt recupera original (con headers + non-secret fields intactos)test_encrypt_is_idempotent: encrypt dos veces sobre el mismo config produce idéntico resultado
Véase también
- [[entity—monitoring—service—decrypt-channel-config]]
- [[feature—monitoring—notification-channel-encryption]]
- [[entity—core—model—stored-credential]]
- [[concept—saas—security-at-rest]]
- [[entity—monitoring—model—notification-channel]]