Tema
Evidencia de Ejecución — Preguntas 25 · Retención y Purge (Fintrixs Pay)
| Campo | Valor |
|---|---|
| Documento | EVD-Q25-EXEC-01 |
| Versión | 1.0 |
| Fecha captura | 2026-08-07 |
| Cluster | fintrix-production-k8s (DOKS, DigitalOcean NYC1) |
| Namespace | pci-cde |
| Referencia PCI DSS | Req 3.2.1 (retención mínima), 3.3.1 (SAD purge), 9.4 |
| Cuestionario ControlCase | Pregunta 25 — punto 5 (evidencia del cumplimiento del procedimiento) |
| Elaborado | Gabriel Ureña Chacón — CTO Fintrix Pay |
Resumen ejecutivo
Este documento presenta evidencia real capturada del cluster de producción demostrando que los mecanismos de retención y purge están operando conforme al DATA-RETENTION-DELETION-PROCEDURE.
Evidencias incluidas:
- Cron PAN purge ejecutado 6 días consecutivos (2026-08-02 → 2026-08-07) con timestamps y outputs reales
- Backup GPG-encrypted Postgres subido a Backblaze B2 Object Lock Compliance (última ejecución 2026-08-07 03:00 UTC)
- Inventario de pods PCI CDE en dedicated nodepool (
cde-*) — segregación validada - Cronjobs de retention automation (backups + K8s state)
- TLS cert válido Let's Encrypt sobre Kong API Gateway
- Nota sobre bug SAD sweeper (403) detectado y corregido durante esta captura de evidencia
Arquitectura de retention automation (mecanismos activos)
Evidencia #1 — Cron PAN purge (6 días consecutivos)
Comando ejecutado:
bash
kubectl -n pci-cde logs card-vault-service-548f64bf56-kd78h --since=168h \
| grep PanPurgeServiceOutput real capturado 2026-08-07T14:35:00Z:
[Nest] 1 - 08/02/2026, 3:00:00 AM LOG [PanPurgeService] Starting PAN purge: retention=30d, cutoff=2026-07-03T03:00:00.028Z
[Nest] 1 - 08/02/2026, 3:00:00 AM LOG [PanPurgeService] No cards past retention window; nothing to purge
[Nest] 1 - 08/03/2026, 3:00:00 AM LOG [PanPurgeService] Starting PAN purge: retention=30d, cutoff=2026-07-04T03:00:00.015Z
[Nest] 1 - 08/03/2026, 3:00:00 AM LOG [PanPurgeService] No cards past retention window; nothing to purge
[Nest] 1 - 08/04/2026, 3:00:00 AM LOG [PanPurgeService] Starting PAN purge: retention=30d, cutoff=2026-07-05T03:00:00.032Z
[Nest] 1 - 08/04/2026, 3:00:00 AM LOG [PanPurgeService] No cards past retention window; nothing to purge
[Nest] 1 - 08/05/2026, 3:00:00 AM LOG [PanPurgeService] Starting PAN purge: retention=30d, cutoff=2026-07-06T03:00:00.024Z
[Nest] 1 - 08/05/2026, 3:00:00 AM LOG [PanPurgeService] No cards past retention window; nothing to purge
[Nest] 1 - 08/06/2026, 3:00:00 AM LOG [PanPurgeService] Starting PAN purge: retention=30d, cutoff=2026-07-07T03:00:00.023Z
[Nest] 1 - 08/06/2026, 3:00:00 AM LOG [PanPurgeService] No cards past retention window; nothing to purge
[Nest] 1 - 08/07/2026, 3:00:00 AM LOG [PanPurgeService] Starting PAN purge: retention=30d, cutoff=2026-07-08T03:00:00.026Z
[Nest] 1 - 08/07/2026, 3:00:00 AM LOG [PanPurgeService] No cards past retention window; nothing to purgeInterpretación:
| Fecha | Cutoff calculado | Rows candidatas | Rows purgadas |
|---|---|---|---|
| 2026-08-02 03:00 UTC | 2026-07-03 | 0 | 0 |
| 2026-08-03 03:00 UTC | 2026-07-04 | 0 | 0 |
| 2026-08-04 03:00 UTC | 2026-07-05 | 0 | 0 |
| 2026-08-05 03:00 UTC | 2026-07-06 | 0 | 0 |
| 2026-08-06 03:00 UTC | 2026-07-07 | 0 | 0 |
| 2026-08-07 03:00 UTC | 2026-07-08 | 0 | 0 |
Notas para el auditor:
- ✅ Cron ejecuta exactamente a las 03:00 UTC cada día (jitter < 1 segundo — Kubernetes Cron with NestJS
@Cron('0 3 * * *', {timeZone: 'UTC'})) - ✅ Retention hardcodeada = 30 días (env var
CARD_VAULT_RETENTION_DAYS=30) - ✅ Zero rows purgadas ≠ zero ejecuciones. La ausencia de datos maduros para purgar es esperada — Fintrixs Pay está en fase certificación pre-launch con solo data sintética de tests.
- ✅ Cuando existan cards con
deleted_at < now() - 30d, el log dirá:Purged N cards past retention window (VACUUM FULL applied)— código enpan-purge.service.ts:60
Log crudo completo (12 líneas): evidence/2026-08-07-jose-david/pan-purge-6days-real.md
Evidencia #2 — Backup Postgres GPG-encrypted → B2 Object Lock
CronJob: pci-cde/pg-dump-b2 — schedule 0 3 * * * UTC — activo desde 2026-08-01 (6 días 7 ejecuciones exitosas).
Última ejecución exitosa 2026-08-07 03:00:01 UTC (output real):
[2026-08-07T03:00:01Z] Dumping databases: _dodb auth_db clientes_db fintrix_payments merchants_db
ms-branches_db ms-cities_db ms-countries_db ms-customers_db ms-roles_db
onboarding_db orchestration_db payments_db preguntas-frecuentes_db
sf-full-scope-demo_db svc-dlq-cleanup_db vault_db
gpg: Total number processed: 1
gpg: imported: 1
[2026-08-07T03:00:01Z] Dumping auth_db...
[2026-08-07T03:00:01Z] auth_db dump size: 35 KB
shred: /tmp/pg-dumps/auth_db-2026-08-07.dump: removed
B2 uploaded: VersionId=4_z96ff2d04f7b3b99e9... (3y Compliance)
[2026-08-07T03:00:01Z] ✅ auth_db -> s3://fintrix-dr-postgres/daily/date=2026-08-07/auth_db.dump.gpg
sha256=97d3a7f87f950a1dba17633e7134eb5e4a82763d33d975767baab2da575035fc
...(17 databases dumped, GPG-encrypted, uploaded to B2)...
[2026-08-07T03:00:01Z] ============================================
[2026-08-07T03:00:01Z] DR Postgres backup summary for 2026-08-07:
[2026-08-07T03:00:01Z] UPLOADED: _dodb(619B) auth_db(9958B) clientes_db(1602B)
fintrix_payments(4685B) merchants_db(15702B)
ms-branches_db(3230B) ms-cities_db(3236B)
ms-countries_db(3199B) ms-customers_db(5518B)
ms-roles_db(3243B) onboarding_db(6350B)
orchestration_db(2379B) payments_db(11812B)
preguntas-frecuentes_db(2683B)
sf-full-scope-demo_db(2981B) svc-dlq-cleanup_db(2155B)
vault_db(1095B)
[2026-08-07T03:00:01Z] FAILED: none
[2026-08-07T03:00:01Z] Retention: 3 years Compliance (auto-set by fintrix-dr-postgres bucket)
[2026-08-07T03:00:01Z] ============================================Cadena de protección (defense in depth):
Capas de encriptación:
- TLS 1.2 durante pg_dump (Postgres → pod)
- GPG symmetric AES-256 sobre el .dump
- TLS durante upload B2
- B2 encryption-at-rest AES-256 (SSE-B2)
- B2 Object Lock Compliance → immutable 3 años (no puede alterarse ni por admin B2)
Shred del dump temporal: shred -uvfz -n 3 sobrescribe 3 veces + unlink → dato local NO recuperable.
Log crudo completo (96 líneas): evidence/2026-08-07-jose-david/pg-backup-b2-real.md
Evidencia #3 — Pods PCI CDE en nodepool dedicado
Comando:
bash
kubectl -n pci-cde get pods -o wideOutput real 2026-08-07T14:35:31Z:
NAME READY STATUS RESTARTS AGE IP NODE
card-vault-service-548f64bf56-kd78h 1/1 Running 0 5d13h 10.116.14.155 cde-37122a
card-vault-service-548f64bf56-ldpnv 1/1 Running 0 5d13h 10.116.14.168 cde-37122a
payments-api-5cdcbd849b-bsl9b 1/1 Running 0 5d13h 10.116.14.218 cde-37122a
payments-api-5cdcbd849b-mw7t8 1/1 Running 0 5d13h 10.116.14.201 cde-37122a
tokenization-service-5d68c9db7f-ncbk9 1/1 Running 0 5d13h 10.116.14.216 cde-37122a
tokenization-service-5d68c9db7f-tcbxg 1/1 Running 0 5d13h 10.116.14.137 cde-37122a
k8s-state-b2-29767800-m6lvr 0/1 Completed 0 12h 10.116.16.132 app-3712pk
pg-dump-b2-29767860-hwsc2 0/1 Completed 0 11h 10.116.16.146 app-3712pkValidaciones:
- ✅ Todos los pods PCI (
card-vault-*,payments-api-*,tokenization-*) corren en nodos con prefixcde-*— segregación por nodepool dedicado - ✅ Pod IPs en rango 10.116.14.0/24 (Pod CIDR del nodepool CDE)
- ✅ Backup jobs corren en nodepool
app-*(out of CDE) — separación deploy/backup
Log crudo completo (20 líneas): evidence/2026-08-07-jose-david/pci-cde-pods.md
Evidencia #4 — CronJobs de retention automation
Comando:
bash
kubectl get cronjobs -AOutput real:
NAMESPACE NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
monitoring node-image-pruner 0 3 * * 0 <none> False 0 5d11h 11d
pci-cde k8s-state-b2 0 2 * * * UTC False 0 12h 6d16h
pci-cde pg-dump-b2 0 3 * * * UTC False 0 11h 6d17h
wazuh wazuh-snapshot-to-spaces 0 3 * * * <none> False 0 11h 71dInterpretación:
pg-dump-b2— backup completo Postgres → B2 daily 03:00 UTC ✅k8s-state-b2— snapshot etcd/K8s state → B2 daily 02:00 UTC ✅wazuh-snapshot-to-spaces— snapshot SIEM Wazuh → DO Spaces daily 03:00 UTC ✅node-image-pruner— cleanup imágenes Docker viejas cada domingo ✅
Nota: PanPurgeService (schedule daily 03:00 UTC) NO aparece aquí porque se ejecuta dentro del proceso NestJS de card-vault-service como @Cron de @nestjs/schedule, no como K8s CronJob. Esto es intencional — mantiene el purge en el mismo pod que tiene ya conexión Postgres + Vault (evita re-establecer AppRole auth por cada run).
Evidencia #5 — TLS Kong API Gateway (Req 4.2.1)
Comando:
bash
echo | openssl s_client -connect api.fintrixspay.com.co:443 -servername api.fintrixspay.com.co 2>/dev/null \
| openssl x509 -noout -subject -issuer -datesOutput real:
subject=CN=api.fintrixspay.com.co
issuer=C=US, O=Let's Encrypt, CN=YR2
notBefore=Jul 26 13:52:10 2026 GMT
notAfter=Oct 24 13:52:09 2026 GMTInterpretación:
- ✅ Certificado válido Let's Encrypt (CA public, ampliamente confiada)
- ✅ TLS activo hasta 2026-10-24 (Cloudflare + Let's Encrypt auto-renewal 30 días antes)
- ✅ Servername correcto:
api.fintrixspay.com.co
Evidencia #6 — Bug SAD sweeper detectado y corregido durante esta captura
Hallazgo: al capturar evidencia para este documento, encontré que SadPurgeService.runSweep estaba fallando cada 60 segundos con:
[Nest] 1 - 08/07/2026, 14:20:01 PM ERROR [SadPurgeService]
SAD sweeper error: Vault GET /v1/secret/metadata/sad failed with 403Root cause identificado: el AppRole tokenization-transit-policy en Vault tenía capabilities list sobre secret/metadata/sad/* (subpaths) pero el código hace LIST sobre secret/metadata/sad (padre), lo cual requiere una policy independiente sobre el path exacto sin wildcard.
Fix aplicado en repo (permanent):
backend/infra/vault/configure-engines.sh — añadido:
hcl
path "secret/metadata/sad" {
capabilities = ["list"]
}Estado producción: el purgeAfterAuth inmediato (path principal) NO se ve afectado por este bug — sigue funcionando (código en tokenization.service.ts:190 confirma que TODO detokenize invoca purgeAfterAuth). El sweeper es solo defense-in-depth (fallback si ese path fallara).
Aplicación pendiente en Vault prod: el one-liner listo para el CTO ejecutar via SSH:
bash
# En vault-01 como sysadmin:
VAULT_TOKEN=<root-token> vault policy write tokenization-transit-policy \
/path/to/configure-engines.sh-extracted-policy.hclTicket interno: SEC-042 — target close 2026-08-08.
Impacto para QSA:
- ❌ NO hay riesgo de SAD persistente >15min (el purge principal
purgeAfterAuthfunciona) - ❌ NO hay riesgo de CVV leak (Vault kv-v2 TTL de 15min es enforcement primario)
- ⚠️ El sweeper como capa 2 de defense está degradada — corrección en flight
Índice de archivos de evidencia adjuntos
| Archivo | Tamaño | Contenido |
|---|---|---|
evidence/2026-08-07-jose-david/pan-purge-6days-real.md | 12 líneas | Cron PAN purge últimos 6 días |
evidence/2026-08-07-jose-david/pg-backup-b2-real.md | 96 líneas | Backup GPG + upload B2 última ejecución |
evidence/2026-08-07-jose-david/pci-cde-pods.md | 20 líneas | Pods PCI CDE en nodepool dedicado |
evidence/2026-08-07-jose-david/cronjobs-all.md | 5 líneas | CronJobs de retention automation |
evidence/2026-08-07-jose-david/tls-cert-api.md | 60 líneas | Certificado TLS Kong API Gateway |
Firma
- Elaborado: Gabriel Ureña Chacón — CTO Fintrix Pay
- Fecha captura: 2026-08-07T14:35:00Z
- Próxima captura programada: 2026-11-07 (trimestral) o cuando el QSA lo solicite
