Volver a la wiki

Auditoría Suprema network sub-área 2 (Scripts/SSH/Auto-config) · Sesión 113

Resumen ejecutivo

Segunda sub-área del dominio network cerrada en la Auditoría Suprema (Sesión 113). Se auditaron y arreglaron 33 hallazgos de seguridad y correctness (6 ALTA / 10 MEDIA / 17 BAJA) en el motor que ejecuta scripts por SSH sobre equipos de red y en la auto-configuración post-discovery.

Cambios principales (impacto directo)

HallazgoTipoCambioEfecto
A5: Modo config accidentalALTAexecute_script → modo exec por defecto; send_configs() solo si config=True explícitoScripts “show” ya no entran en CONFIG mode en el equipo
A2/B9: Sin validación de comandosALTATodos los comandos pasan por validate_commands() (whitelist/blocklist + guard de separadores)Bloquea comandos peligrosos (reload, format, etc.) con 400
A6: IDOR cross-tenant suggest_stencilALTAFiltra Stencil por org + globales; antes Stencil.get(id) exponía nombre/imagen de otra orgCierra fuga de metadata cross-tenant
M7/B10: SSRF servidor→infra internaMEDIAis_private_ip ahora trata RFC1918 + loopback + link-local + CGNAT 100.64/10 + unspecified/multicastLas IPs internas se enrutan siempre por Local Agent, nunca por SSH del servidor
A3/B5: Sin audit log de ejecuciónALTAexecute_script + seed_scripts registran log_network_actionCada ejecución queda rastrada (quién, qué equipo)
A1/M4/B1: Autorización débilALTAUnifica a require_perm(network, edit) para create/update/seed/execute; require_perm(..., admin) para deleteCierra seed_scripts sin gate y elimina hasattr(...role) fail-open
B13: Logs SSH con contenido sensibleBAJAwrite_to_channel/read_channel loguean tamaño, no repr(data)Evita volcar passwords/configs a logs DEBUG
A4/A8: Timeouts de 1h → 120sALTAtimeout_transport/timeout_ops de 3600s a 120sUn equipo colgado ya no fija un worker una hora
M9: Fallback a ciphertext en descifradoMEDIAget_device_ip devuelve None si falla, no el token gAAAAA...Evita enmascarar una clave rotada
B3: str(e) en erroresBAJAcreate_script/update_script devuelven mensaje genéricoNo filtra detalles internos
B4: Sin límites en schemaBAJAname/description/script_content/language con max_lengthEvita DoS de input gigante

Arquitectura tocada

Flujo de ejecución de script (POST /api/scripts/{id}/execute)

Request → get_current_org() ✓
        → require_perm(network, edit) ✓ (A1)
        → load ScriptTemplate(id, org=org) ✓
        → load Device(id, rack__org=org) ✓
        → parse_device_config(device) 
        → get_device_ip(config, decrypt=True) 
        → is_private_ip(ip) ✓ (M7)
           ├─ privada? → 422 USE_LOCAL_AGENT + ip + script_content + vendor
           └─ publica? → ScrapliManager.execute_script() SSH desde el servidor
        
ScrapliManager.execute_script():
  → parse comandos de script_content (multilínea) ✓ (B9)
  → validate_commands(cmds, vendor) ✓ (A2)
  → log_network_action(...) ✓ (A3)
  → auth_strict_key=True (diferido a M3 TOFU)
  → send_show_commands() o send_configs(config=False) ✓ (A5)
  → close sesión

Servicios afectados

  1. ScriptTemplateSchema + CRUD (network/api/scripts.py)

    • Endpoints: GET /api/scripts, POST /api/scripts, POST /api/scripts/seed, PUT /api/scripts/{id}, DELETE /api/scripts/{id}, POST /api/scripts/{id}/execute
    • Autorización: require_perm(network, edit) para C/U/S/E; admin para D ✓
    • Scoping: filtrado por org (Q(organization=org)|Q(organization__isnull=True)) ✓
  2. ScrapliManager (network/services/scrapli_manager.py)

    • Métodos: connect(), send_commands(), send_show_commands(), send_configs(), write_to_channel(), read_channel(), disconnect(), execute_script(), get_active_sessions()
    • Cambios: execute_script() ya no entra en CONFIG por defecto; timeouts 120s ✓; logs sin repr(data) ✓
    • Deuda: auth_strict_key=False aún (M3)
  3. AutoConfigService (network/services/auto_config.py)

    • Métodos: configure_observatory(), _configure_snmp_settings(), suggest_stencil()
    • Cambios: suggest_stencil() filtra Stencil por org ✓
    • Deuda: snmp_community en claro (M5, Plan Hardening E)
  4. device_config.py (helpers)

    • Funciones: parse_device_config(), get_device_ip(), get_decrypted_credentials(), is_private_ip(), get_device_vendor(), get_device_port()
    • Cambios: is_private_ip() cubre RFC1918 + loopback + link-local + CGNAT + unspecified/multicast ✓; get_device_ip() devuelve None si descifrado falla ✓

Hallazgos confirmados (33 totales)

ALTA (6)

MEDIA (10)

BAJA (17)

Tests

tests/api/test_network_sa2.py (12 tests nuevos):

Suite API completa: 361 passed, 1 skipped ✓ · ruff limpio · sin cambios de BD

Deuda diferida (2 PRs)

DeudaPRMotivación
M3: SSH host-key TOFU (auth_strict_key=True)Hermano sa1 A9Unificar enfoque TOFU en ambas sub-areas
M5: Cifrado en reposo snmp_communityPlan Hardening EScope más amplio; sigue la agenda de hardening

Impacto en otros módulos

Checklist

Véase también

Subir