CreaRack-SL

Endpoint: Obtener y actualizar configuración de DeviceProfile

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 flags has_snmp_v3_keys e has_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 (DeviceProfileConfigIn schema):
{
  "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_password con valor: cifra con CredentialManager.encrypt_credential() y almacena.
  • ssh_password omitido o null: 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:

  1. Campos SNMP planos (snmp_community, snmp_version, snmp_v3_*): se asignan directamente.
  2. Flag supports_snmp: se activa si hay snmp_community.
  3. 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.
  4. Flag supports_ssh: se activa si ssh_username y ssh_password_encrypted están ambos presentes.
  5. Grupos:
    • Filtra group_ids contra grupos válidos de la org.
    • Ejecuta profile.groups.set(valid_group_ids).

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) -> dict
  • def 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)

  1. Modal carga: GET /api/network/auto-provision/profiles/{profile_id}/config
    • Retorna config actual con flags has_* en lugar de secretos.
    • Frontend renderiza formulario.
  2. Usuario edita y clica “Save”.
  3. Modal ejecuta PUT /api/network/auto-provision/profiles/{profile_id}/config con delta de cambios.
  4. Backend aplica cambios con exclude_unset=True (solo campos presentes).
  5. 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]]