Descripción
Sesión 115: Nueva interfaz modal compartida para editar configuración de perfiles de dispositivo detectados (DeviceProfile) en las páginas de monitorización. Permite cambiar:
- Credenciales SNMP: versión (v2c/v3), community, usuario v3, protocolos y claves.
- Credenciales SSH: usuario y contraseña (cifradas con Fernet en backend).
- Asignación a grupos: vincular el dispositivo a uno o más
RackGroup.
NO edita IP ni nombre — esos valores vienen de Auto-Provision y se modifican allí.
Motivación
Antes, solo los dispositivos racks.Device (manuales en rack editor) podían ser configurados con credenciales. Los dispositivos descubiertos automáticamente (Wireless AP, UPS, Digital Signage, etc., como DeviceProfile) no tenían forma fácil de modificar estas credenciales desde el interfaz de monitorización. Esto fuerza a los operadores a ir a Auto-Provision cada vez que quieren cambiar una contraseña o agrupar un dispositivo.
Alcance
Disponible en:
- Observatory (todos los
DeviceProfileen el sidebar) - Wireless
- UPS
- Digital Signage
Cada página importa el servicio modal centralizado y añade un botón “Config” en la pestaña o detalle del dispositivo.
Flujo
- Usuario clica “Config” en un dispositivo de monitorización (se muestra su hostname/IP).
- Modal carga configuración actual con
GET /api/network/auto-provision/profiles/{profile_id}/config.- Los secrets (claves SNMPv3, password SSH) NO se retornan; solo flags
has_snmp_v3_keysehas_stored_ssh. - El usuario ve placeholders: “leave blank to keep unchanged”.
- Los secrets (claves SNMPv3, password SSH) NO se retornan; solo flags
- Usuario edita formulario: versión SNMP, credenciales, asignación a grupos (checkboxes).
- Clica “Save” →
PUT /api/network/auto-provision/profiles/{profile_id}/config. - Backend cifra el password SSH con
CredentialManager.encrypt_credential()y valida grupos por org (scope). - Modal cierra, lista de dispositivos se recarga.
Seguridad
- Multi-tenancy: Endpoint valida que
profile.organization == orgy quegroup_idspertenecen a la org. - Cifrado: SSH password siempre cifrado con Fernet (AES) antes de almacenamiento; GET nunca lo retorna.
- XSS: Modal construido sin inyección; datos sensibles vía JSON.
- Secretos en GET: Las claves SNMP v3 y el password SSH no se retornan. Solo indica si existen (
has_*flags).
Cambios principales
Backend
Endpoint POST asignación de tipos (existente):
POST /api/network/auto-provision/assign-types— cambiadevice_typede múltiples perfiles.
Nuevos endpoints:
GET /api/network/auto-provision/profiles/{profile_id}/config— obtiene config actual (sin secretos).PUT /api/network/auto-provision/profiles/{profile_id}/config— actualiza credenciales y grupos.
Schemas (network/api/assign.py):
DeviceProfileConfigIn— entrada para PUT: campos opcionales (exclude_unset=True), solo se aplican los enviados._SNMP_PLAIN_FIELDS— tuple con nombres de campos SNMP a actualizar directamente.
Lógica:
- SNMP: texto plano (como en racks.Device); si
snmp_communitypresente →supports_snmp = True. - SSH: password enviado en texto plano; se cifra con
CredentialManager.encrypt_credential(). - Grupos: solo se usan los que existen en la org;
profile.groups.set(valid_group_ids).
Frontend
Servicio reutilizable (static/js/services/DeviceConfigModal.js):
- Modal overlay dinámico con CSS variables (
--card-bg,--border-color,--text-primary, etc.). - Campos SNMP con toggle v2c ↔ v3 (muestra/oculta protocolos).
- Campos SSH con hint “A password is stored. Leave blank to keep it unchanged.”
- Carga grupos con checkboxes desde
GET /api/racks/groups. - Valida presencia de credenciales (SSH: username + password_encrypted).
- Envía solo campos presentes (
exclude_unset); secretos solo si escritos.
Integraciones por página:
observatory.js: MétodoopenDeviceConfig(profileId, name)+ decodificación dePROFILE_ID_OFFSET(1e9).ObservatoryTabs.js: Botón “Config” en pestaña deDeviceProfile(no en manual targets).WirelessDetail.js,UpsDetail.js,SignageDetail.js: Botones “Config” + listeners.
Fixes simultáneos
-
Colisión de IDs en Observatory: Los
DeviceProfilese renderizaban con id negativo (-profile_id), pero los “manual targets” ya usaban negativos. Fix: desplazar id aPROFILE_ID_OFFSET + profile_id(positivo grande). Frontend decodifica:profile_id = id - PROFILE_ID_OFFSET. -
XSS en Wireless/UPS/Digital Signage: Datos inyectados con
{{ *_json|safe }}en<script>. Fix: usarjson_script(escape seguro) + parsear desde DOM conJSON.parse(document.getElementById(...).textContent).
Pruebas
Añadido tests/api/test_network_profile_config.py (8 test cases):
- GET sin org → 404.
- GET config con flags secretos ocultados.
- PUT con validación de org y grupos.
- PUT actualización parcial (campos presentes).
- Cifrado SSH.
- Asignación de grupos.
Interconexiones
- DeviceProfile (
network/models.py): Nuevos campos SNMP v3, SSH cifrado, relación M2M aRackGroup. - RackGroup (
racks/models.py): Relación inversa desdeDeviceProfile. - CredentialManager (
core/security/credential_manager.py): Cifrado Fernet de credenciales. - Observatory, Wireless, UPS, Digital Signage (vistas + templates): Botones + modal.
Notas de implementación
- Offset grande (1e9): Evita colisiones. En el futuro, si la DB crece, considerar un offset mayor o scheme distinto.
- SNMP v2c ↔ v3: Toggle en frontend cambia visibilidad de campos; backend valida según lo enviado.
- Grupos opcionales: Si
group_ids: nullen payload, no toca grupos existentes. Si[], los quita todos.
Véase también
- [[entity—network—endpoint—profile-config]]
- [[entity—static—service—device-config-modal]]
- [[entity—network—model—device-profile]]
- [[concept—monitoring—auto-provision]]
- [[feature—monitoring—observatory-sidebar-unificado]]