Saltar al contenido principal

Seguridad Web — OnnixConnect

Guía de referencia para las medidas de seguridad implementadas en OnnixConnect. Cubre rate limiting, sanitización de logs SOAP, timeout de sesión e IP lock en tokens API.


Resumen de cambios (2026-04-29)

FeatureArchivos principalesEstado
Rate limiting en todos los endpointsAppServiceProvider, routes/api.php, routes/web.phpActivo
Sanitización de logs SOAPSifenHttpClient, SifenBatchServiceActivo
Session timeout configurable (web)CheckSessionTimeout, config/security.phpActivo
IP lock en tokens Sanctum (API)CheckTokenIp, migración created_ipActivo

1. Rate Limiting

Limiters definidos (AppServiceProvider::boot())

NombreLímiteAplica aClave
login5 req / 15 minPOST /api/auth/token, POST /login webemail + IP
api100 req / 15 minTodos los endpoints de lectura autenticadosIP
sifen:send60 req / 15 minEnvíos SIFEN: sendSync, sendBatch, store, sign, reenviar, eventosIP + user ID
sifen:admin10 req / 15 minGestión de emisores (crear, editar, cert, logo) + escritura multitenantIP

Comportamiento al exceder el límite

Laravel retorna automáticamente:

HTTP/1.1 429 Too Many Requests
Retry-After: 847
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0

El cliente debe respetar el header Retry-After (segundos hasta poder reintentar).

Estructura en routes/api.php

auth:sanctum + throttle:api → todos los endpoints autenticados
└── throttle:sifen:admin → emitters CRUD + multitenant escritura
└── throttle:sifen:send → envíos a SIFEN
throttle:login → POST /api/auth/token + POST /login web

Agregar rate limiting a endpoints nuevos

Todo endpoint nuevo bajo auth:sanctum hereda automáticamente throttle:api. Para endpoints sensibles, agregar al sub-grupo correspondiente:

// Endpoint administrativo
Route::middleware(['permission:emitter:manage', 'throttle:sifen:admin'])->group(function () {
Route::post('/nuevo-endpoint-admin', ...);
});

// Endpoint de envío SIFEN
Route::middleware(['permission:sifen:send', 'throttle:sifen:send'])->group(function () {
Route::post('/nuevo-endpoint-send', ...);
});

2. Sanitización de Logs SOAP

Problema original

SifenHttpClient::guardarLog() persistía el XML SOAP completo en disco, incluyendo datos criptográficos del contribuyente.

Solución implementada

SifenHttpClient::sanitizarParaLog() reemplaza antes de persistir:

<ds:SignatureValue>ABCD...==</ds:SignatureValue>
→ <ds:SignatureValue>[REDACTED]</ds:SignatureValue>

<ds:X509Certificate>MIIC...==</ds:X509Certificate>
→ <ds:X509Certificate>[REDACTED]</ds:X509Certificate>

<ds:DigestValue>HASH...==</ds:DigestValue>
→ <ds:DigestValue>[REDACTED]</ds:DigestValue>

<xDE>UEsD...==</xDE>
→ <xDE>[BASE64_ZIP_REDACTED]</xDE>

Se preservan: CDC, dCodRes, dMsgRes, dFecProc, estructura XML.

SifenBatchService reemplazó el log del XML completo del lote por un JSON de metadata:

{
"batch_id": 1,
"timestamp": "2026-04-29T14:30:00-03:00",
"dte_count": 3,
"cdc_list": ["0180069563..."],
"cod_res": "0300",
"prot_lote": "01234567890123"
}

Invariante de seguridad

La sanitización ocurre después de que cURL envía el payload a SIFEN. El XML original nunca se modifica. Verificado con 4 tests en SifenHttpClientTest.


3. Session Timeout (Web)

Cómo funciona

El middleware CheckSessionTimeout se aplica globalmente al grupo web. En cada request autenticado:

  1. Lee session('last_activity').
  2. Si now() - last_activity > SESSION_TIMEOUT_MINUTES * 60 → cierra sesión y redirige al login.
  3. Si no → actualiza last_activity.

Configuración

SESSION_TIMEOUT_ENABLED=true
SESSION_TIMEOUT_MINUTES=30

Cuándo desactivarlo

  • Clientes con formularios lentos o trabajo no guardado.
  • Entornos de testing/staging.
SESSION_TIMEOUT_ENABLED=false

4. IP Lock en Tokens Sanctum (API)

Cómo funciona

Al crear un token (POST /api/auth/token), se guarda la IP del cliente en personal_access_tokens.created_ip.

En cada request posterior, el middleware CheckTokenIp compara la IP actual con la registrada. Si no coinciden:

HTTP 401
{
"message": "Token inválido: la solicitud proviene de una IP diferente a la registrada. Generá un nuevo token desde tu IP actual.",
"code": "TOKEN_IP_MISMATCH"
}

El token queda revocado inmediatamente.

Configuración

API_IP_LOCK_ENABLED=true

Cuándo desactivarlo

  • Empresa con balanceo de carga o múltiples IPs de salida.
  • CDN o proxy con IP dinámica.

Tokens anteriores al feature

Tokens con created_ip = NULL están exentos. Para que queden bajo IP lock, el usuario debe revocarlo y generar uno nuevo.


5. Variables de Entorno de Seguridad

VariableDefaultDescripción
SESSION_TIMEOUT_ENABLEDtrueActiva el timeout por inactividad en sesión web
SESSION_TIMEOUT_MINUTES30Minutos de inactividad antes de cerrar sesión web
API_IP_LOCK_ENABLEDtrueRevoca tokens API si cambia la IP de origen

6. Cómo una Empresa Consume la API

Paso 1 — Setup

El admin crea una cuenta de servicio dedicada con rol operador y permiso sifen:send.

Paso 2 — Obtener token desde IP fija

POST /api/auth/token
Content-Type: application/json

{
"email": "erp@empresa.com.py",
"password": "...",
"device_name": "erp-produccion-v1"
}

Respuesta:

{
"token": "5|abc123xyz...",
"type": "Bearer",
"expires_at": "2026-07-29T00:00:00Z"
}

Guardar el token en variable de entorno o secrets manager. Nunca en el código fuente.

Paso 3 — Usar el token

POST /api/sifen/dtes/sync
Authorization: Bearer 5|abc123xyz...
Content-Type: application/json

Paso 4 — Manejo de errores

HTTPcodeCausaAcción
401TOKEN_IP_MISMATCHCambio de IPGenerar nuevo token desde la IP actual
401Token expiradoRe-autenticar con POST /api/auth/token
429Rate limitEsperar Retry-After segundos y reintentar
422Payload inválidoRevisar el cuerpo del request

Paso 5 — Rotación de token

POST /api/auth/token/refresh
Authorization: Bearer 5|abc123xyz...

Devuelve nuevo token sin pedir password. El anterior queda revocado.

Consideración con IP Lock

Con API_IP_LOCK_ENABLED=true, el servidor cliente debe tener IP estática. Si el proveedor rota IPs, usar un proxy con IP fija o desactivar el lock.


7. Tests de Seguridad

# Solo seguridad
php artisan test tests/Feature/RateLimitingTest.php
php artisan test tests/Unit/SifenHttpClientTest.php

# Suite completa
php artisan test
# Expected: 168 passed, 1 skipped

RateLimitingTest (15 tests): limiters definidos, 429 en todos los grupos, headers correctos, middleware en login web.

SifenHttpClientTest — sanitización (4 tests): payload intacto, campos criptográficos redactados, CDC preservado.


8. Pendientes (Próximos Sprints)

ItemPrioridad
Headers HTTP de seguridad (CSP, X-Frame-Options, HSTS)Alta
SESSION_ENCRYPT=true en producciónMedia
SESSION_SECURE_COOKIE=true con HTTPSMedia
Reducir token_expiry_days de 365 a 30-90 díasMedia
Logging intentos fallidos de autenticaciónMedia
FormRequests en los ~25 endpoints con validación inlineBaja
IP ranges CIDR para API lockBaja
UI toggle session timeout en módulo de ConfiguraciónBaja