Descripción
Par de endpoints en network/api/assign.py para obtener y editar credenciales SNMP/SSH + asignación a grupos de un DeviceProfile (dispositivo descubierto automáticamente) desde el interfaz de monitorización.
Utilizados por el modal centralizado DeviceConfigModal en Observatory, Wireless, UPS y Digital Signage.
Endpoints
GET /api/network/auto-provision/profiles/{profile_id}/config
Propósito: Obtener configuración actual de un perfil para poblar el modal.
Autenticación: require_perm(request, "network", "view")
Parámetros:
profile_id(int, path): ID del DeviceProfile.
Respuesta (200 OK):
{
"id": <int>,
"name": "<hostname o ip_address>",
"ip_address": "<ip>",
"snmp_community": "<community>",
"snmp_version": "v2c | v3",
"snmp_v3_username": "<username>",
"snmp_v3_auth_protocol": "SHA | MD5 | ''",
"snmp_v3_priv_protocol": "AES | DES | ''",
"has_snmp_v3_keys": <bool>, // flags: no retorna las claves
"ssh_username": "<username>",
"has_stored_ssh": <bool>, // flag: no retorna el password
"group_ids": [<int>, ...]
}
Errores:
404: Profile no encontrado o no pertenece a la org.
Notas:
- Las claves SNMPv3 (
snmp_v3_auth_key,snmp_v3_priv_key) y el password SSH nunca se retornan. Solo se indica si existen con flagshas_snmp_v3_keysehas_stored_ssh. - El frontend muestra placeholders “leave blank to keep unchanged” para campos vacíos.
PUT /api/network/auto-provision/profiles/{profile_id}/config
Propósito: Actualizar credenciales y grupos de un perfil.
Autenticación: require_perm(request, "network", "edit")
Parámetros:
profile_id(int, path): ID del DeviceProfile.- Body JSON (
DeviceProfileConfigInschema):
{
"snmp_community": "<community> | null",
"snmp_version": "v2c | v3 | null",
"snmp_v3_username": "<username> | null",
"snmp_v3_auth_protocol": "SHA | MD5 | null",
"snmp_v3_auth_key": "<secret> | null",
"snmp_v3_priv_protocol": "AES | DES | null",
"snmp_v3_priv_key": "<secret> | null",
"ssh_username": "<username> | null",
"ssh_password": "<plaintext> | '' | null",
"group_ids": [<int>, ...] | null
}
Semántica:
- Campos presentes en payload: se actualizan.
- Campos ausentes: no se tocan (
exclude_unset=True). ssh_password == "": borra la contraseña almacenada.ssh_passwordcon valor: cifra conCredentialManager.encrypt_credential()y almacena.ssh_passwordomitido onull: no afecta password existente.group_ids: null: no modifica asignación de grupos.group_ids: []: quita de todos los grupos.group_ids: [id1, id2, ...]: asigna a esos grupos (solo válidos en la org).
Respuesta (200 OK):
{
"success": true,
"id": <profile_id>
}
Errores:
400: Organization no encontrada o validación fallida.404: Profile no encontrado.
Lógica de actualización:
- Campos SNMP planos (
snmp_community,snmp_version,snmp_v3_*): se asignan directamente. - Flag
supports_snmp: se activa si haysnmp_community. - SSH password:
- Si texto no vacío: encripta y almacena en
ssh_password_encrypted. - Si cadena vacía: borra
ssh_password_encrypted = "". - Si omitido: no toca.
- Si texto no vacío: encripta y almacena en
- Flag
supports_ssh: se activa sissh_usernameyssh_password_encryptedestán ambos presentes. - Grupos:
- Filtra
group_idscontra grupos válidos de la org. - Ejecuta
profile.groups.set(valid_group_ids).
- Filtra
Log de auditoría: Registra NETWORK.auto_provision.profile_config con la IP del perfil.
Schema Ninja
Archivo: network/api/assign.py
Clase: DeviceProfileConfigIn
class DeviceProfileConfigIn(Schema):
"""Edición de credenciales + grupos de un DeviceProfile. Solo se aplican los
campos presentes (``exclude_unset``). NO incluye IP ni nombre a propósito."""
snmp_community: str | None = None
snmp_version: str | None = None
snmp_v3_username: str | None = None
snmp_v3_auth_protocol: str | None = None
snmp_v3_auth_key: str | None = None
snmp_v3_priv_protocol: str | None = None
snmp_v3_priv_key: str | None = None
ssh_username: str | None = None
ssh_password: str | None = None # texto plano de entrada → se cifra; "" = borrar
group_ids: list[int] | None = None # None = no tocar; [] = quitar de todos
Tuple auxiliar: _SNMP_PLAIN_FIELDS
_SNMP_PLAIN_FIELDS = (
"snmp_community",
"snmp_version",
"snmp_v3_username",
"snmp_v3_auth_protocol",
"snmp_v3_auth_key",
"snmp_v3_priv_protocol",
"snmp_v3_priv_key",
)
Ubicación en código
Archivo: network/api/assign.py (líneas ~97–155)
Router: @router (Ninja APIRouter para /api/network/)
Tags: ["Auto-Provision"]
Funciones:
def get_profile_config(request, profile_id: int) -> dictdef update_profile_config(request, profile_id: int, payload: DeviceProfileConfigIn) -> dict
Dependencias
Importaciones:
from network.models import DeviceProfile— Modelo actualizado.from racks.models import RackGroup— Validación de grupos.from core.security import CredentialManager— Cifrado Fernet.from core.auth.permissions import require_perm, get_current_org— Autenticación y org.
Modelos tocados:
DeviceProfile— Lectura y actualización de campos SNMP/SSH/grupos.RackGroup— Validación de grupos por org.
Flujo típico (desde modal)
- Modal carga:
GET /api/network/auto-provision/profiles/{profile_id}/config- Retorna config actual con flags
has_*en lugar de secretos. - Frontend renderiza formulario.
- Retorna config actual con flags
- Usuario edita y clica “Save”.
- Modal ejecuta
PUT /api/network/auto-provision/profiles/{profile_id}/configcon delta de cambios. - Backend aplica cambios con
exclude_unset=True(solo campos presentes). - Si éxito: modal cierra, lista de dispositivos se recarga.
Notas de seguridad
- Multi-tenancy: Validación en GET y PUT asegura que profile pertenece a la org actual.
- RLS (Row-Level Security): Grupos solo de la org; no se pueden asignar grupos ajenos.
- Secretos: SSH password nunca retornado. Claves SNMP v3 tampoco. El frontend tiene la responsabilidad de no guardar localmente si el usuario refresca sin guardar.
- Auditoría: Toda actualización queda registrada con
log_action().
Casos de prueba
- GET con org no encontrada → 404.
- GET config con flags secretos ocultados.
- PUT validación de org.
- PUT asignación de grupos con validación.
- PUT cifrado SSH.
- PUT actualización parcial (solo campos presentes).
Archivo de tests: tests/api/test_network_profile_config.py
Véase también
- [[feature—monitoring—device-config-modal]]
- [[entity—static—service—device-config-modal]]
- [[entity—network—model—device-profile]]
- [[entity—racks—model—rack-group]]
- [[concept—network—auto-provision]]