CreaRack-SL

Cifrado en reposo de NotificationChannel.config (encrypt_channel_config)

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:

  1. Si v es vacío, None, o no es string → devuelve sin cambios
  2. Si ya es un token Fernet (detectado por CredentialManager.is_encrypted(v)) → devuelve sin cambios (idempotencia)
  3. 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 config es None o 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:

  1. test_secret_is_encrypted_at_rest: POST a /api/.../channels con URL plaintext → en la BD está como Fernet token
  2. test_dispatch_roundtrip_decrypts: encrypt → decrypt recupera original (con headers + non-secret fields intactos)
  3. 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]]