Funcionalidadactivecreado Sun Jun 07#network#security#ssh#scripts#multi-tenancy#rls#correctness#scrapli
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)
| Hallazgo | Tipo | Cambio | Efecto |
|---|---|---|---|
| A5: Modo config accidental | ALTA | execute_script → modo exec por defecto; send_configs() solo si config=True explícito | Scripts “show” ya no entran en CONFIG mode en el equipo |
| A2/B9: Sin validación de comandos | ALTA | Todos los comandos pasan por validate_commands() (whitelist/blocklist + guard de separadores) | Bloquea comandos peligrosos (reload, format, etc.) con 400 |
A6: IDOR cross-tenant suggest_stencil | ALTA | Filtra Stencil por org + globales; antes Stencil.get(id) exponía nombre/imagen de otra org | Cierra fuga de metadata cross-tenant |
| M7/B10: SSRF servidor→infra interna | MEDIA | is_private_ip ahora trata RFC1918 + loopback + link-local + CGNAT 100.64/10 + unspecified/multicast | Las IPs internas se enrutan siempre por Local Agent, nunca por SSH del servidor |
| A3/B5: Sin audit log de ejecución | ALTA | execute_script + seed_scripts registran log_network_action | Cada ejecución queda rastrada (quién, qué equipo) |
| A1/M4/B1: Autorización débil | ALTA | Unifica a require_perm(network, edit) para create/update/seed/execute; require_perm(..., admin) para delete | Cierra seed_scripts sin gate y elimina hasattr(...role) fail-open |
| B13: Logs SSH con contenido sensible | BAJA | write_to_channel/read_channel loguean tamaño, no repr(data) | Evita volcar passwords/configs a logs DEBUG |
| A4/A8: Timeouts de 1h → 120s | ALTA | timeout_transport/timeout_ops de 3600s a 120s | Un equipo colgado ya no fija un worker una hora |
| M9: Fallback a ciphertext en descifrado | MEDIA | get_device_ip devuelve None si falla, no el token gAAAAA... | Evita enmascarar una clave rotada |
B3: str(e) en errores | BAJA | create_script/update_script devuelven mensaje genérico | No filtra detalles internos |
| B4: Sin límites en schema | BAJA | name/description/script_content/language con max_length | Evita 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
-
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;adminpara D ✓ - Scoping: filtrado por org (Q(organization=org)|Q(organization__isnull=True)) ✓
- Endpoints:
-
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)
- Métodos:
-
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)
- Métodos:
-
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 ✓
- Funciones:
Hallazgos confirmados (33 totales)
ALTA (6)
- A1:
seed_scriptssin gate de rol (readonly sembraba templates) →require_perm(network, edit)✓ - A2: Sin validación de comandos peligrosos →
validate_commands()bloqueador ✓ - A3: Sin audit log de ejecución de script →
log_network_action()✓ - A4: Timeouts SSH 3600s → 120s (DoS de sesión colgada) ✓
- A5: Modo CONFIG accidental para scripts “show” →
execpor defecto ✓ - A6: IDOR
suggest_stencilsin filtro de org → Filtro Q(org|global) ✓
MEDIA (10)
- M1:
hasattr(...role)fail-open →require_perm()✓ - M2: Dupe checks en CRUD → Validación schema ✓
- M3: RLS en Device cargado (no afecta esta sub-area, defer sa1 fix)
- M4: Inconsistencia
rolevsrequire_perm→ Unificado ✓ - M5: Snmp_community en claro → Defer Plan Hardening E
- M6: N+1 en configure_observatory → Optimización futura
- M7: SSRF
is_private_ipincompleto → Cubre CGNAT 100.64/10 ✓ - M8: Sesiones SSH sin cap/TTL → Tracking issue abierto
- M9: Fallback a ciphertext en get_device_ip → Devuelve None ✓
- M10: Error messages genéricos → No filtra str(e) ✓
BAJA (17)
- B1:
role not in [...]vsrequire_perm→ Unificado ✓ - B2-B14: Optimizaciones, limpieza, schema bounds → Varios parcialmente ✓
- B15: Tests de SSRF, comando bloqueado, IDOR, readonly denied ✓
Tests
tests/api/test_network_sa2.py (12 tests nuevos):
- ✓
test_execute_script_readonly_denied— Role readonly no ejecuta - ✓
test_execute_script_operator_allowed— Role operator ejecuta - ✓
test_execute_script_admin_allowed— Role admin ejecuta - ✓
test_dangerous_command_blocked— Comandoreloadrechazado con 400 - ✓
test_multilne_parse_to_agent— Script multilínea se parsea y enruta a Agente (privada) - ✓
test_idor_suggest_stencil— Stencil de otra org no se filtra - ✓
test_is_private_ip_cgnat— CGNAT 100.64/10 se trata como privada - ✓
test_seed_scripts_readonly_denied— Readonly no puede sembrar templates - ✓
test_audit_log_execute— Cada ejecución queda logueada - ✓
test_script_schema_bounds— Max length en name/content/description - ✓
test_ssh_log_no_passwords— write_to_channel no loguea repr(data) - ✓
test_timeout_transport_120s— Timeout es 120s, no 3600s
Suite API completa: 361 passed, 1 skipped ✓ · ruff limpio · sin cambios de BD
Deuda diferida (2 PRs)
| Deuda | PR | Motivación |
|---|---|---|
| M3: SSH host-key TOFU (auth_strict_key=True) | Hermano sa1 A9 | Unificar enfoque TOFU en ambas sub-areas |
| M5: Cifrado en reposo snmp_community | Plan Hardening E | Scope más amplio; sigue la agenda de hardening |
Impacto en otros módulos
monitoring.services.command_validation: reutiliza el validador de runbooks (whitelist/blocklist) ✓core.utils.require_perm: aplicado en create/update/seed/execute/delete ✓core.security.CredentialManager: descifrado viasafe_decrypt()✓network.services.device_discovery.scan_guard: patrón de SSRF; device_config.is_private_ip es el equivalente local ✓
Checklist
- ✅ Una page por cada servicio/endpoint nuevo (feature + 4 entities)
- ✅ Slugs/títulos en español
- ✅ Entities con sub-componentes y fuentes
- ✅ Una sola sección “Véase también”
- ✅ Wikilinks a páginas creadas o slugs canónicos
- ✅ Suite API verde (361 passed, 1 skipped)
- ✅ Sin migraciones de BD
- ✅ Ruff limpio
Véase también
- [[entity—network—endpoint—execute-script]]
- [[entity—network—service—scrapli-manager]]
- [[entity—network—service—auto-config]]
- [[entity—network—service—device-config]]
- [[concept—saas—multi-tenancy]]
Referenciado desde
- Auditoría Suprema 2 · Cola network: DeviceTask sin agente que no caduca y comandos de la whitelist por vendor inalcanzables
- Decisión: sa4-G2 cerrada — el validador de comandos vuelve a aceptar '| include' con regex anclada
- Endpoint POST /api/scripts/{id}/execute · Ejecución de scripts SSH
- Módulo device_config · Helpers de parseo/credenciales/SSRF guard
- Servicio AutoConfigService · Auto-config post-discovery
- Servicio ScrapliManager · Manager de sesiones SSH multi-vendor