Autor: Iker Acevedo¶
Fecha creacion: 2026-11-09
Estado: produccion (desplegada y verificada 2026-11-08)
Endpoint: GET /novedades/evidencia¶
Lambda / Servicio: ApiLambdaNovedadesInterReportes (misma función que genera los Excel)
Auth: No requerida (sin authorizer ni API key); la autenticidad la da la firma HMAC del enlace
Rol: todos (quien tenga un enlace firmado)
Historia: 027 · Spec: 012 §5.7, RN-16, RN-17 · Decisión: ADR-005
Marcas: observado (ruta citada), Iker (fecha) o hipótesis.
¿Qué hace?¶
Entrega las fotos de gestión app de una novedad que el robot de Inter guarda en un bucket privado. El Excel de los reportes lleva un enlace fijo y firmado; este endpoint comprueba la firma y redirige (302) a una URL prefirmada de S3 que vive 300 s. Así las fotos se abren desde el Excel sin abrir el bucket (ver el ADR para las alternativas y los costos).
Request¶
GET {URL_EVIDENCIAS}?k={clave}&f={firma} (API Gateway REST, integración AWS_PROXY, payload 1.0).
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
k |
query | Sí (lo exige la lambda, no API Gateway) | s3_key de la imagen en base64url (RFC 4648 §5) sin relleno = |
f |
query | Sí | HMAC-SHA256 en hexadecimal minúscula; clave = EVIDENCIAS_SECRETO_FIRMA, mensaje = el s3_key en UTF-8 |
Los dos parámetros son opcionales a propósito en API Gateway (request_parameters = false): si faltan, responde la lambda (400) y no API Gateway, que revelaría el contrato.
Ejemplo ficticio: https://api.ejemplo.invalid/novedades/evidencia?k=Z2VzdGlvbi1hcHAvMTIzNDU2Nzg5MDEyLzIwMjYtMTEtMDEvMTIzNDU2Nzg5MDEyLTEuanBn&f=<64 caracteres hexadecimales>.
Validación de la clave (FirmaEvidencia.ClaveValida, observado): empieza por gestion-app/, máximo 300 caracteres, sin .., sin contrabarra ni caracteres de control, extensión .jpg, .jpeg o .png (sin distinguir mayúsculas).
Response¶
| Código | Cuándo | Cuerpo | Resultado en el log |
|---|---|---|---|
302 |
Firma válida: Location = URL prefirmada de S3 GetObject de 300 s, ResponseContentDisposition=inline y ResponseContentType según la extensión (image/png o image/jpeg) |
vacío | ok |
400 |
Falta k o f |
{"error":"Solicitud inválida."} |
parametros_ausentes |
403 |
k no decodifica o la clave no es válida; o la firma no coincide (comparación en tiempo constante, CryptographicOperations.FixedTimeEquals) |
{"error":"Prohibido."} (idéntico en ambos casos, sin pistas) |
clave_invalida, firma_invalida |
405 |
Método distinto de GET |
{"error":"Método no permitido."} |
metodo-no-permitido |
500 |
Sin EVIDENCIAS_BUCKET o falló la presignación |
{"error":"Error interno."} |
sin_configuracion, error_presignado |
Todas las respuestas llevan Cache-Control: no-store. Observado: ReportesHandler.cs, ResolverEvidenciaUseCase.cs, PresignadorS3Evidencias.cs.
Orden de las defensas: método → parámetros presentes → k válida → firma. Nunca se loguea el secreto, la firma, la clave ni la URL prefirmada (solo el resultado de la tabla, y del error solo el tipo de excepción).
Infraestructura¶
| Pieza | Detalle (observado en Terraform) |
|---|---|
| Módulo | modules/novedades-evidencias: recursos /novedades y /evidencia, método GET con authorization = NONE y api_key_required = false, integración AWS_PROXY hacia la función de Reportes y permiso InvocacionEvidenciasFirmadas restringido a GET/novedades/evidencia |
| Dónde se usa | Prod: environments/prod/novedades (REST 7elgyten88, recurso raíz cyyv45wowa, stage Produccion). PreProd: environments/preprod/datos |
| Deployment | Fuera de Terraform en prod: el módulo no toca el API ni el stage; Iker publica con aws apigateway create-deployment --rest-api-id 7elgyten88 --stage-name Produccion |
| Límite de la ruta | burst 50, rate 20 con aws apigateway update-stage sobre /~1novedades~1evidencia/GET/throttling/...; solo después del deployment (el método debe existir) |
| Permiso S3 | Política LeerEvidenciasFirmadas del rol de Reportes: solo s3:GetObject sobre arn:aws:s3:::<bucket>/gestion-app/* (sin comodines sobre otros buckets); depende de evidencias_bucket |
Sin depends_on |
Los módulos de la ruta del comando de Slack y de evidencias no usan depends_on sobre el módulo de funciones: con él, un recurso nuevo dejaba valores «desconocidos» y Terraform proponía reemplazar la integración y el permiso del comando de Slack en vivo (comentario en environments/prod/novedades/main.tf; mismo criterio que el webhook, PR 626) |
URL de la ruta: salida url_evidencias del entorno (va al variable group como URL_EVIDENCIAS).
Variables (solo nombres)¶
EVIDENCIAS_SECRETO_FIRMA (cifrada AES256), URL_EVIDENCIAS (https, sin query), EVIDENCIAS_BUCKET (wsinter-evidencias-prod en prod) y EVIDENCIAS_REGION (opcional; por defecto AWS_REGION). Detalle en
Variables de entorno. Sin secreto o sin URL_EVIDENCIAS el reporte sale sin columnas de imagen.
Operación¶
- Rotar el secreto invalida todos los enlaces ya enviados: runbook D9.
- Verificado en prod 2026-11-08 (observado, ficha del dominio): firma válida 302 a S3 con firma válida; firma alterada 403; sin firma 400.
- PreProd no puede leer las fotos del bucket de prod (otra cuenta): mejora M-275.
- Quien tenga el enlace puede reenviarlo (la firma prueba autenticidad, no identidad; aceptado, ADR-005).
Historial de cambios¶
| Fecha | Autor | Cambio |
|---|---|---|
| 2026-11-09 | Iker Acevedo (redactado con IA) | Creación (Lambdas PR 635, Terraform PR 636; desplegada 2026-11-08) |