Tema
EVD-Q10 — Producción endurecida — Deploy + TLS + Hardening
PCI DSS v4.0 — Múltiples requerimientos consolidados en esta evidencia: Req 1 (segmentación), 2 (defaults seguros), 4 (crypto in transit), 6 (SDLC), 7 (RBAC), 8 (auth), 10 (logging).
Contexto: preparación del ambiente productivo para el Application Penetration Test (ticket ControlCase 684992) y confirmación de scope DOKS clusters (ticket 684996).
1. URLs productivas
curl -sI https://api.fintrixspay.com.co/auth/login
~$ curl -sI https://api.fintrixspay.com.co/auth/login -X POST -d '{}'HTTP/2 200 strict-transport-security: max-age=31536000; includeSubDomains x-frame-options: DENY x-content-type-options: nosniff x-xss-protection: 1; mode=block referrer-policy: strict-origin-when-cross-origin permissions-policy: camera=(), microphone=(), geolocation=() cache-control: no-store, no-cache, must-revalidate
2. Infraestructura productiva
text
┌──────────────────────────┐
│ Cloudflare DNS (GoDaddy)│
│ A record app → LB nginx │
│ A record api → LB nginx │
└────────────┬─────────────┘
│
▼
┌──────────────────────────┐
│ nginx-ingress LB │
│ 134.199.178.138 │
│ TLS termination (LE) │
│ Security headers │
└────────────┬─────────────┘
│
┌────────────┴─────────────┐
│ │
▼ ▼
┌─────────────────────┐ ┌──────────────────────┐
│ app-ingress │ │ api-ingress │
│ → fintrix-dashboard│ │ → kong-kong-proxy │
│ Service (2 pods) │ │ Service (1 pod) │
└─────────────────────┘ └──────────┬───────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────────┐ ┌────────────────┐
│ auth-service │ │ payments-api │ │ card-vault- │
│ (2 pods CDE) │ │ (2 pods) │ │ service │
└──────┬───────┘ └──────────┬───────┘ │ (2 pods CDE) │
│ │ └────────┬───────┘
└──────────────────────┴────────────────────┘
│
▼
┌──────────────────────────┐
│ DO Managed PostgreSQL │
│ fintrix-production-pci │
│ Private VPC endpoint │
│ SSL/TLS required │
└──────────────────────────┘3. TLS Let's Encrypt automatizado (PCI Req 4.2.1)
kubectl get certificate -n default
~$ kubectl get certificate NAME READY SECRET AGE api-fintrixspay-tls True api-fintrixspay-tls 4m app-fintrixspay-tls True app-fintrixspay-tls 1m
~$ kubectl get clusterissuer letsencrypt-prod NAME READY AGE letsencrypt-prod True 30m
Auto-renewal: cert-manager renueva automáticamente 30 días antes de expiración.
4. Hardening DOKS API Server (PCI Req 1.4.5)
Aplicado en ambos clusters — el API server ya NO es accesible desde Internet abierto:
doctl kubernetes cluster get [prod|staging] | jq control_plane_firewall
PRODUCTION control plane firewall: Enabled: TrueAllowed: - 146.190.69.141/32 ← VPN gateway (bastión admin) - 201.244.255.204/32 ← CTO workstation
STAGING control plane firewall: Enabled: TrueAllowed: mismas 2 IPs
5. Nodos CDE dedicados (PCI Req 1.2.1 - segregación)
Los servicios PCI-scope corren en pool cde dedicado con nodeAffinity + taints:
| Servicio | Namespace | Node pool | Nodo actual |
|---|---|---|---|
| auth-service | default | cde | cde-37sqt7 |
| card-vault-service | default | cde | cde-37sqt7 |
| tokenization-service | default | cde | cde-37sqt7 |
| kong-kong (gateway) | default | app | app-* |
| fintrix-dashboard | default | app | app-* |
nodeAffinity + taint enforcement: los pods PCI declaran nodeAffinity: pci-scope=true + toleration: pci-scope:NoSchedule. Otros pods NO pueden schedulearse en nodos CDE (ni siquiera con evictionThreshold).
6. Kong Gateway — Rutas + Security Plugins
Rutas expuestas:
/auth/login,/auth/register,/auth/system/login,/auth/logout/auth/me,/auth/public-key,/auth/change-password,/auth/mfa/auth/sessions,/auth/tenants/resolve/merchant/users,/admin/users/api/v1/payment-intents,/api/v1/tokens,/api/v1/onboarding/api/v1/merchants,/api/v1/transactions,/api/v1/platform-settings/api/webhooks
Config declarativa: backend/infra/kong/production/kong.yaml
7. Frontend con CSP estricto (PCI Req 6.4.3)
curl -s https://app.fintrixspay.com.co/ | grep CSP
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https://.fintrixspay.com.co https://.fintrix.com; form-action 'self'; base-uri 'self'; object-src 'none'; upgrade-insecure-requests;
Subresource Integrity (SRI) automático en todos los assets del build vía vite-plugin-sri.ts.
8. Cuentas de auditoría (ControlCase Pentest)
Credenciales entregadas vía ticket ControlCase 684992. Rotación automática programada al cierre del engagement.
9. Verificación end-to-end
Verificación E2E completa
curl -sI https://app.fintrixspay.com.co/ HTTP/2 200 content-length: 801 x-frame-options: DENY
curl -sI https://api.fintrixspay.com.co/auth/login -X POST HTTP/2 200 x-ratelimit-limit-minute: 1000
curl -X POST https://api.fintrixspay.com.co/auth/login -d '{...}'"access_token": "eyJhbGc...", "expires_in": 3600
curl -X OPTIONS https://api.fintrixspay.com.co/api/v1/onboarding/status \ -H "Origin: https://app.fintrixspay.com.co" HTTP/2 404 access-control-allow-origin: https://app.fintrixspay.com.co access-control-allow-credentials: true
10. Disk hygiene — Hallazgo + monitoring + plan de remediación
10.1 Hallazgo (2026-07-26)
Durante la preparación del ambiente productivo se detectó que 2 de los 3 nodos del pool CDE tenían DiskPressure=True (disco al 100%), impidiendo el scheduling de los servicios PCI (auth-service, card-vault-service, tokenization-service).
Root cause preliminar: crecimiento lineal ~2.3 GB/día por nodo, extrapolado a 138 GB en 60 días (edad de los nodos afectados). Culpables candidatos (por probabilidad):
/var/lib/containerd/— cache de container images sin GC agresivo- Wazuh audit logs verbosos
- ClamAV signature updates acumulados
- fluent-bit buffers en disk (si el Collector Rapid7 estaba offline)
- Container logs sin rotación
Mitigación inmediata: se escaló el pool CDE de 2 → 3 nodos (nuevo nodo cde-37sqt7 limpio) para restablecer el scheduling. Los servicios PCI arrancaron ahí sin interrupción del servicio.
10.2 Monitoring desplegado
kubectl get ds -n monitoring
kubectl -n monitoring get ds node-disk-monitor NAME DESIRED CURRENT READY node-disk-monitor 7 7 5
kubectl -n monitoring logs -l app=node-disk-monitor 2026-07-26T17:08:36Z DISK_OK node=cde-37sqt7 used_pct=5 used=6.8G/157.3G 2026-07-26T17:08:36Z DISK_OK node=app-37t0mj used_pct=44 used=65.9G/157.3G 2026-07-26T17:08:36Z DISK_OK node=app-376gih used_pct=68 used=102.6G/157.3G 2026-07-26T17:08:36Z DISK_OK node=system-* used_pct=5 used=4.0G/78.5G
Thresholds:
DISK_OK< 70%DISK_WARN≥ 70% — investigarDISK_CRIT≥ 85% — evictar/reciclar
CronJob de cleanup: node-image-pruner corre cada domingo 03:00 UTC ejecutando crictl rmi --prune para eliminar container images huérfanas.
10.3 Plan post-pentest (después de 2026-07-31)
- Reciclar
cde-3cil88+cde-3cil8uvíadoctl kubernetes cluster node-pool replace-node(auto-crea reemplazos limpios) - Escalar pool CDE back a 2 nodos (eliminar el temporal de escalado)
- Investigar
app-376gihque está en 68% (cerca de warning) - Ajustar log levels:
- Wazuh agent:
info→warning - Falco output rules: reducir verbosidad de reglas ruidosas
- fluent-bit:
storage.type memoryen lugar defilesystem
- Wazuh agent:
- Solicitar a DO support activar
imageGCHighThresholdPercent=70(default 85%) en el kubelet — requiere ticket porque DOKS es managed - Confirmar en 30 días que los nodos nuevos NO se llenan (validar fix)
10.4 Postmortem — congelamiento durante el pentest
Se decidió NO reciclar los nodos viejos durante el engagement ControlCase (2026-07-31) para preservar el inventario congelado enviado al QSA y evitar cambios de IP durante scans en curso. Los nodos con DiskPressure no aceptan pods pero no interrumpen el servicio (todos los pods PCI corren en el nodo limpio).
Costo temporal del "desperdicio": ~$16 (5 días × $48/mes de 2 nodos degradados). Aceptable ante el valor de estabilidad audit-trail.
11. Change log
| Fecha | Cambio | Autor |
|---|---|---|
| 2026-07-26 | Deploy inicial producción: nginx-ingress + cert-manager + Ingresses TLS + frontend Deployment + Kong routing extendido + hardening DOKS Trusted Sources + Migraciones DB (auth_db, merchants_db, payments_db, etc.) + 3 usuarios pentest ControlCase | Gabriel Ureña (CTO) |
| 2026-07-26 | Auth-service v1.1.0: computeRolesFromPermissions() emite admin_staff/super_admin cuando el user tiene iam:rbac:manage:any | Gabriel Ureña (CTO) |
| 2026-07-26 | Node disk monitor DaemonSet + weekly image pruner CronJob desplegados (respuesta a DiskPressure en nodos CDE viejos) | Gabriel Ureña (CTO) |
