Skip to content

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).

OwnerGabriel Ureña · CTO
QSA ReviewerElswick / Prajesh · ControlCase
AmbienteProducción K8s NYC1
Deployed2026-07-26
Estado✓ LIVE
TLSLet's Encrypt auto-renew

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

Dashboardapp.fintrixspay.com.co
API Gatewayapi.fintrixspay.com.co
Portal PCIdocs.fintrixspay.com.co
TLS GradeA (Let's Encrypt)
curl -sI https://api.fintrixspay.com.co/auth/login
2026-07-26 15:16 UTC

~$ 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

Screenshot 1 - Endpoint API responde HTTP 200 con TLS válido, HSTS, security headers

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
2026-07-26 15:12 UTC

~$ 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

Screenshot 2 - 2 certificados Let's Encrypt emitidos automáticamente via cert-manager + HTTP-01 challenge

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
2026-07-26 14:45 UTC

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

Screenshot 3 - Trusted Sources restringe API server K8s solo a IPs conocidas (bastión + CTO)

5. Nodos CDE dedicados (PCI Req 1.2.1 - segregación)

Los servicios PCI-scope corren en pool cde dedicado con nodeAffinity + taints:

ServicioNamespaceNode poolNodo actual
auth-servicedefaultcdecde-37sqt7
card-vault-servicedefaultcdecde-37sqt7
tokenization-servicedefaultcdecde-37sqt7
kong-kong (gateway)defaultappapp-*
fintrix-dashboarddefaultappapp-*

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

Total rutas19
CORSrestringido a app.fintrixspay.com.co
Rate limit1000 req/min por IP
Max body size10 MB
JWT verify (route)/auth/me RS256

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
2026-07-26 15:16 UTC

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)

Merchant Admin[email protected]
Perms Admin22 (IAM + resources CRUD)
Merchant User[email protected]
Perms User5 (read-only)
System Admin[email protected]
Expiración2026-07-31 (auto-revoke)

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
2026-07-26 15:16 UTC

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):

  1. /var/lib/containerd/ — cache de container images sin GC agresivo
  2. Wazuh audit logs verbosos
  3. ClamAV signature updates acumulados
  4. fluent-bit buffers en disk (si el Collector Rapid7 estaba offline)
  5. 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
2026-07-26 17:08 UTC

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

Screenshot - DaemonSet node-disk-monitor emite un log cada 15 min con DISK_OK / DISK_WARN / DISK_CRIT según threshold. fluent-bit forwarda a Rapid7 InsightIDR para alerting.

Thresholds:

  • DISK_OK < 70%
  • DISK_WARN ≥ 70% — investigar
  • DISK_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)

PrioridadAlta post-audit
Impacto$96/mes ahorro + estabilidad
Riesgo actualBajo (mitigado)
  1. Reciclar cde-3cil88 + cde-3cil8u vía doctl kubernetes cluster node-pool replace-node (auto-crea reemplazos limpios)
  2. Escalar pool CDE back a 2 nodos (eliminar el temporal de escalado)
  3. Investigar app-376gih que está en 68% (cerca de warning)
  4. Ajustar log levels:
    • Wazuh agent: infowarning
    • Falco output rules: reducir verbosidad de reglas ruidosas
    • fluent-bit: storage.type memory en lugar de filesystem
  5. Solicitar a DO support activar imageGCHighThresholdPercent=70 (default 85%) en el kubelet — requiere ticket porque DOKS es managed
  6. 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

FechaCambioAutor
2026-07-26Deploy 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 ControlCaseGabriel Ureña (CTO)
2026-07-26Auth-service v1.1.0: computeRolesFromPermissions() emite admin_staff/super_admin cuando el user tiene iam:rbac:manage:anyGabriel Ureña (CTO)
2026-07-26Node disk monitor DaemonSet + weekly image pruner CronJob desplegados (respuesta a DiskPressure en nodos CDE viejos)Gabriel Ureña (CTO)

Documentación Confidencial — Solo para uso interno y auditoría PCI DSS