Definición
./manage.py reencrypt_credentials [--dry-run]
Ubicación: core/management/commands/reencrypt_credentials.py
Propósito: Re-cifrar todas las credenciales almacenadas con clave anterior (DEV_KEY o legacy) a la clave activa, permitiendo retirar fallback.
Precondición: Ejecutar en staging primero, validar reporte, luego en PROD con mismo comando.
Cobertura
Itera sobre los siguientes almacenes de credenciales:
| Modelo | Campo | Descripción |
|---|---|---|
monitoring.NotificationChannel | config | webhook url/token + Authorization headers |
network.DeviceProfile | (snmp_community, v3 keys, ssh_password) | T1 — configuración SNMP/SSH |
monitoring.MonitoringTarget | config | snmp_community / v3 keys |
racks.Device | management_config | ip/username/password/enable_password (Etapa 3 · E — nuevo) |
Método principal
def handle(self, *args, **options):
dry_run = options.get("dry_run", False)
totals = {"rows": 0, "reencrypted": 0, "errors": 0}
self._process_notification_channels(dry_run, totals)
self._process_device_profiles(dry_run, totals)
self._process_monitoring_targets(dry_run, totals)
self._process_device_management_configs(dry_run, totals) # Nuevo (E)
# Imprime reporte
Nuevo: _process_device_management_configs (Etapa 3 · E)
def _process_device_management_configs(self, dry_run, totals):
"""racks.Device.management_config: JSON string con creds cifradas
(ip/username/password/enable_password), también anidadas en "connection".
Era el caso pendiente del fleco E antes de poder retirar la DEV_KEY.
"""
for device in Device.objects.exclude(...).iterator():
config = json.loads(device.management_config)
new_config, changed = self._reencrypt_mapping(config, totals, label=...)
conn = new_config.get("connection")
if isinstance(conn, dict):
new_conn, c_changed = self._reencrypt_mapping(conn, totals, label=...)
if c_changed:
new_config["connection"] = new_conn
changed = True
if changed and not dry_run:
device.management_config = json.dumps(new_config)
device.save(update_fields=["config"])
Sub-componentes:
config— dict de top-level: ip_address, username, password, enable_password (puede estar vacío).connection— sub-dict anidado dentro deconfigcon credenciales adicionales._reencrypt_mapping(mapping, totals, label)— helper que re-cifra todos los valores en un dict (busca campos que contengan ciphertext).
Descifrado multi-clave
El helper _derive_fernet(key) intenta descifrar con la cadena completa:
- Clave activa (
SECRET_KEYoCIPHER_KEY) - DEV_KEY (fallback — será retirado tras esta migración)
cipher = CredentialManager._derive_fernet(key)
plaintext = cipher.decrypt(ciphertext_bytes)
Tras descifrar, siempre re-cifra con la clave activa.
Uso
Modo dry-run (ver qué cambiaría):
./manage.py reencrypt_credentials --dry-run
Ejecución real (con confirmación interactiva):
./manage.py reencrypt_credentials
Salida esperada:
[reencrypt_credentials] rows scanned=42 reencrypted=15 errors=0 elapsed=2.3s
Tests
Véase tests/api/test_config_ia_diferidos.py::TestReencryptCoversDeviceConfig:
test_device_management_config_reencrypted— Device con password cifrado en legacy DEV_KEY se re-encripta a clave activa; verificar decrypt tras salvar.
Seguridad & Procedimiento
Antes de desplegar a PROD
- Ejecutar en staging con
--dry-run→ validar reporte. - Ejecutar sin flag → validar que devices siguen accesibles.
- Comprobar en logs que no hay “errors”.
Después de desplegar
- En PROD:
./manage.py reencrypt_credentials --dry-run(inspeccionar reporte). - En PROD:
./manage.py reencrypt_credentials(migrar). - PR posterior: remover DEV_KEY + fallback del
CredentialManager.decrypt_chain.
Rationale
- Completitud: Device.management_config era el último almacén cifrado sin cobertura.
- Atomicidad por tenant: usa
transaction.atomic()para cada device. - Reversibilidad: descifrado multi-clave garantiza acceso incluso a credenciales antiguas.
Véase también
- [[decision—20260611—auditoria-suprema-etapa-3-cierre-findings-m1-b6-e]] — decisión y contexto.
- [[entity—racks—model—device]] — modelo Device con management_config.
- [[entity—core—service—credential-manager]] — gestor de cifrado de credenciales.
- [[concept—security—credential-lifecycle]] — ciclo de vida de credenciales.