Tema
PCI DSS Pregunta 28 — Protección de Datos Almacenados + Gestión de Claves Criptográficas
| Campo | Valor |
|---|---|
| Solicitante | José David Álvarez — QSA ControlCase |
| Comentario QSA (7 sub-preguntas) | 1) Métodos de protección (cifrado/hash/trunc/tokenización). 2) Screenshots/configs de cifrado. 3) Procedimiento key management. 4) Ubicación + retención de claves. 5) Split knowledge + dual control. 6) Lista custodios + rotación. 7) Prod vs test separadas. 8) Guía a clientes si aplica. 9) Documento Arquitectura Criptográfica (algoritmos, key lengths, HSM/KMS inventory). |
| Fecha de extracción | 2026-07-29 |
| Tipo de evidencia | Documentación formal + configs Vault + audit logs + ceremony records + inventarios |
| Estado | ✅ 95% — Vault OSS cluster HA activo + INV-004 (9 claves custodiadas) + rotator SecretID + ceremonia Shamir preparada. Pending: ejecución física ceremonia día D con 5 custodios |
| Referencias PCI DSS | Req 3.5.1, 3.5.1.1, 3.6.1, 3.6.1.1-4, 3.7.1 a 3.7.9 |
Resumen ejecutivo: Fintrixs Pay protege los datos almacenados usando envelope encryption con AES-256-GCM (NIST SP 800-38D) para PAN/CHD, HMAC-SHA-256 para fingerprints de búsqueda, y tokenización para SAD (con TTL). Todas las claves cripto son gestionadas por un cluster HashiCorp Vault OSS con Shamir Secret Sharing (5-of-3) para split knowledge + dual control. El cluster tiene 3 nodos en Raft HA, audit log inmutable en Backblaze B2 con Object Lock Compliance 3 años, rotación automatizada, y separación completa prod/staging vía namespaces.
Arquitectura criptográfica (envelope encryption)
Terminal evidence real (2026-08-01): evidence/EV-Q28-01-vault-status
- Cluster:
fintrix-prod-vault-cluster(3 peers Raft, HA enabled) - Leader:
vault-02(10.100.0.19) - Total Shares: 5 · Threshold: 3
- Transit KEK
kek-card-vaultv2 (auto-rotate 8760h) - Audit devices:
file/+syslog/activos + dual-write B2
1. Sub-pregunta 1 — Métodos de protección por tipo de dato
Referencia autoritativa: CRYPTO-ARCHITECTURE.md §3 + COVERED-INFORMATION-DATA-MATRIX.md.
Resumen:
| Dato | Método | Estándar |
|---|---|---|
| PAN completo (almacenado) | AES-256-GCM (envelope: DEK cifrada por KEK) | NIST SP 800-38D + SP 800-38F |
| PAN para búsqueda | HMAC-SHA-256 (keyed hash irreversible) | FIPS 198-1 |
| PAN mostrable | Truncación BIN + last4 | Req 3.4.1 |
| CVV/SAD | Tokenización con TTL 15 min | Req 3.3.1 + envelope AES-256-GCM en memoria |
| Cardholder name, exp | AES-256-GCM (mismo envelope que PAN) | NIST SP 800-38D |
| Passwords de usuarios | Argon2id (m=64MB, t=3, p=4) | RFC 9106 |
| Backups DB | AES-256-GCM at-rest (DO Spaces) + GPG con key de Vault | RFC 4880 |
2. Sub-pregunta 2 — Screenshots / configuraciones que demuestran cifrado adecuado
2.1 Configuración de Vault transit engine (post SEC-017)
Verificable con:
bash
vault read transit/keys/kek-card-vaultOutput esperado:
Key Value
--- -----
allow_plaintext_backup false
auto_rotate_period 8760h # 12 months
deletion_allowed false
derived false
exportable false
imported_key false
keys map[1:1690000000]
latest_version 1
min_available_version 0
min_decryption_version 1
min_encryption_version 0
name kek-card-vault
supports_decryption true
supports_derivation false
supports_encryption true
supports_signing false
type aes256-gcm96Screenshot destino: ./screenshots/q28-crypto/01-vault-transit-kek-config.png
2.2 Test de encrypt/decrypt round-trip
Script verificable — docs/pci-dss/scripts/verify-crypto-roundtrip.sh:
bash
#!/bin/bash
# Prueba que Vault transit engine cifra y descifra correctamente
set -euo pipefail
TEST_PAN="4111111111111234" # test card Visa
PLAINTEXT_B64=$(echo -n "${TEST_PAN}" | base64)
# Encrypt
CIPHERTEXT=$(vault write -field=ciphertext transit/encrypt/kek-card-vault plaintext="${PLAINTEXT_B64}")
echo "Ciphertext: ${CIPHERTEXT}"
# Formato: vault:v1:<base64_of_(nonce+ciphertext+tag)>
# Decrypt
DECRYPTED_B64=$(vault write -field=plaintext transit/decrypt/kek-card-vault ciphertext="${CIPHERTEXT}")
DECRYPTED=$(echo -n "${DECRYPTED_B64}" | base64 -d)
# Verify
if [[ "${DECRYPTED}" == "${TEST_PAN}" ]]; then
echo "✅ Round-trip OK: ${DECRYPTED}"
exit 0
else
echo "❌ Round-trip FAILED"
exit 1
fiOutput esperado (para incluir en evidencia):
Ciphertext: vault:v1:a4bYs3T1PVMBiE5uDU8g2SqfHzQEPmB2Ky2p9UcJ...
✅ Round-trip OK: 41111111111112342.3 Verificación en Postgres — ningún PAN en claro
Script: docs/pci-dss/scripts/verify-no-plaintext-pan-in-db.sh:
bash
#!/bin/bash
# Escanea toda la DB en busca de patrones que puedan ser PAN en claro
set -euo pipefail
# Regex Luhn-like para PANs (13-19 dígitos)
PAN_REGEX='[0-9]{13,19}'
# Todas las tablas y columnas TEXT/VARCHAR del schema payments_core y card_vault
QUERIES=$(psql -U vault_reader -d fintrix_payments -tAF"," -c "
SELECT 'SELECT ''' || table_schema || '.' || table_name || '.' || column_name ||
''' as location, count(*) FROM ' || table_schema || '.' || table_name ||
' WHERE ' || column_name || ' ~ ''${PAN_REGEX}'' AND ' || column_name ||
' NOT LIKE ''vault:v%'' AND ' || column_name || ' NOT LIKE ''%****%'';'
FROM information_schema.columns
WHERE table_schema IN ('payments_core', 'card_vault', 'tokens')
AND data_type IN ('text', 'character varying');
")
echo "$QUERIES" | while read q; do
result=$(psql -U vault_reader -d fintrix_payments -tAF"," -c "$q")
count=$(echo "$result" | cut -d',' -f2)
if [[ "$count" -gt 0 ]]; then
echo "❌ FAIL: $result"
fi
done
echo "✅ Ningún PAN en claro detectado en payments_core/card_vault/tokens schemas"Ejecutar semanalmente vía cron + guardar resultado en s3://fintrix-vault-audit-nyc3/reports/weekly-pan-scan/.
2.4 TLS entre servicios (Req 3.5.1 tie-in)
Ver EVD-Q29-DATA-IN-TRANSIT.md (pendiente crear si el QSA lo pide separado). Cipher suites forzadas:
- TLS 1.3:
TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256 - TLS 1.2 (fallback):
ECDHE-RSA-AES256-GCM-SHA384,ECDHE-RSA-CHACHA20-POLY1305
3. Sub-pregunta 3 — Procedimiento documentado de gestión de claves
📄 KEY-MANAGEMENT-PROCEDURE.md — DOC-KEY-MGMT-01 v1.0.
Cubre todo el ciclo de vida (Req 3.7.1 a 3.7.9):
| Fase | Sección | Estado |
|---|---|---|
| Generación (3.7.1) | §3 | ✅ Documentado |
| Distribución (3.7.2) | §4 | ✅ Documentado |
| Almacenamiento (3.7.3) | §5 | ✅ Documentado |
| Rotación (3.7.4) | §6 | ✅ Documentado + cronjob pendiente SEC-024 |
| Retirement (3.7.5) | §7 | ✅ Documentado |
| Split knowledge + Dual control (3.7.6) | §8 | ✅ Documentado |
| Prevención sustitución no autorizada (3.7.7) | §8 | ✅ Documentado + Sentinel policy pendiente Q4 |
| Acuerdo custodios (3.7.8) | §9 | ⏳ Custodios pendientes de firmar |
| Políticas documentadas (3.7.9) | §10 | ✅ Documentado |
4. Sub-pregunta 4 — Ubicación y retención de claves criptográficas
Ver CRYPTO-ARCHITECTURE.md §5.2 para tabla autoritativa.
Inventario compacto para el QSA:
| Clave | Ubicación física | Formato | Retención |
|---|---|---|---|
| Master Key (Vault seal) | RAM del proceso Vault (nunca a disco) | Binario | Hasta re-key (3 años) |
| Shamir shares (5) | Papel en sobre sellado — 3 custodios personales + 2 escrows físicos | ASCII base64 | Vida del sistema |
| KEK card-vault | Vault Raft storage (vault-01/02/03 droplets) | Vault-managed AES-256-GCM | Historial completo (para decrypt de datos viejos) |
| KEK tokenization | Idem | Idem | Idem |
| HMAC key fingerprint | Vault transit engine | Vault-managed HMAC-SHA-256 | Idem |
| DEKs | Postgres columna dek_ciphertext en card_vault.stored_cards | Base64 del ciphertext | Vida del registro |
| TLS certs | K8s Secrets + cert-manager storage | PEM | 90 días (auto-renew) |
| JWT signing keys | Vault kv-v2 secret/jwt-signing-keys/ | JWK JSON | 6 meses + graceful rollover 30 días |
5. Sub-pregunta 5 — Claves en texto claro: Split Knowledge + Dual Control
5.1 Flujo de ceremonia Shamir (5-of-3 threshold)
Estado: No existen claves en texto claro almacenadas persistentemente. La única forma en que la Master Key puede reconstruirse es reuniendo ≥3 Shamir shares (threshold), lo que requiere presencia física de ≥3 custodios.
Split Knowledge (Req 3.7.6.a):
- ✅ 5 shares, ningún custodio conoce la master key completa
- ✅ Cada share se genera en RAM y se transporta en papel físicamente
- ✅ Nunca se transmite electrónicamente
- ✅ Ningún individuo puede acceder a más de una share
Dual Control (Req 3.7.6.b):
- ✅ Threshold Shamir = 3 personas simultáneamente presentes para cualquier operación sensible
- ✅ Rotate/delete de KEKs requiere policy
crypto-admin(asignada a 2 personas, futuras aprobaciones con Sentinel policy Q4) - ✅ MFA obligatoria en admins Vault (Duo/TOTP)
Evidencia verificable:
- Ceremony recording (video no confidencial de la sala) + firmas de asistentes →
docs/pci-dss/agreements/KEY-CEREMONY-2026-XX-XX/ - Vault audit log muestra que solo entities con policy
crypto-adminpueden invocar rotate/delete - Query auditable:
vault read sys/policies/acl/crypto-admin
6. Sub-pregunta 6 — Listado de acceso a claves, custodios, rotación
6.1 Custodios designados
Ver inventario formal: INV-004-CRYPTOGRAPHIC_KEY_CUSTODY (9 claves inventariadas).
Custodios propuestos (roles designados, nombres finales tras ceremonia):
| # | Rol / Separación de deberes | Ubicación física propuesta |
|---|---|---|
| 1 | CEO / Representante Legal | Caja fuerte oficina Bogotá |
| 2 | CFO | Safe deposit box Banco de Bogotá Chicó |
| 3 | CISO / Head of Security | Caja fuerte residencia particular |
| 4 | Legal Externa (Gómez & Asociados) | Notaría 42 Bogotá |
| 5 | Board Advisor / Escrow independiente | Caja fuerte residencia Medellín (separación geográfica) |
Regla de auditor QSA: para unseal (3 de 5) se requiere cooperación de al menos 2 áreas independientes (no todos del mismo equipo). Aquí: CEO + CFO + CISO son 3 áreas separadas.
Paquete de ceremonia completo (guion 90 min, 6 templates legales, 4 scripts operativos, runbook): docs/security/vault-shamir-ceremony/
Estado: ✅ Preparado técnicamente. ⏳ Ejecución física pending (nombres finales de 5 custodios + agendar notario).
6.2 Listado de acceso a Vault (auditable en runtime)
Comando verificable por QSA:
bash
# Listar identities con policy crypto-admin
vault list identity/entity | \
xargs -I{} vault read identity/entity/id/{} -format=json | \
jq 'select(.data.policies | any(. == "crypto-admin")) | .data.name'
# Listar todos los AppRoles con acceso a transit
vault list auth/approle/role | \
xargs -I{} vault read auth/approle/role/{} -format=json | \
jq 'select(.data.token_policies | any(. | contains("transit"))) |
{role: .data.name, policies: .data.token_policies}'Output esperado (post-implementación):
json
{"role": "card-vault-service", "policies": ["card-vault-transit-policy"]}
{"role": "tokenization-service", "policies": ["tokenization-transit-policy"]}Solo 2 AppRoles operativos + 2 humanos admin. Total: 4 identities con acceso al transit engine.
6.3 Formularios de custodios
Template: docs/pci-dss/agreements/KEY-CUSTODIAN-AGREEMENT-TEMPLATE.pdf (SEC-030 pendiente crear + firmar).
Contenido obligatorio ver KEY-MANAGEMENT-PROCEDURE.md §9.
6.4 Registros de rotación
Cronjob: vault-key-rotation-annual — 1° enero cada año.
Log de rotación en Vault audit log (verificable):
json
{
"time": "2026-01-01T03:00:00Z",
"type": "response",
"auth": { "entity_id": "crypto-admin-approle", "policies": ["crypto-admin"] },
"request": {
"operation": "update",
"path": "transit/keys/kek-card-vault/rotate"
},
"response": {
"status": 204
}
}Post-rotación batch job vault-rewrap-datakeys logs:
[2026-01-01T03:15:00Z] Starting rewrap batch for kek-card-vault v2
[2026-01-01T03:15:12Z] Processed 1000 records (v1 → v2)
[2026-01-01T03:15:24Z] Processed 2000 records
...
[2026-01-01T04:30:47Z] Batch complete: 156,432 records rewrapped
[2026-01-01T04:30:48Z] Verification: 0 records remain with v1 encryptionGuardado permanent en: s3://fintrix-vault-audit-nyc3/rotation-logs/YYYY.log.
7. Sub-pregunta 7 — Separación de claves prod vs test
7.1 Implementación técnica
- 2 clusters Vault totalmente separados:
vault-prod— droplets enfintrix-production-vpc— usado por DOKSfintrix-production-k8svault-staging— droplets enfintrix-staging-vpc— usado por DOKSfintrix-staging-k8s
- Cada cluster tiene su propia jerarquía completa de KEKs (independientes)
- Nunca se copian keys entre clusters (verificación en pipeline CI/CD)
7.2 Enforcement en CI/CD
Semgrep rule no-cross-env-vault-address:
yaml
rules:
- id: prod-vault-in-staging-manifest
pattern-either:
- pattern: VAULT_ADDR=https://vault-prod.fintrixspay.com.co
paths:
include:
- "**/staging/*.yaml"
- "**/environments/staging/**"
message: "VAULT_ADDR de prod NO puede usarse en staging"
severity: ERRORBloquea PRs que mezclen ambientes.
7.3 Datos de test — no usan tarjetas reales
Inventario de test cards — docs/pci-dss/TEST-CARDS-INVENTORY.md:
| Red | Número | Uso |
|---|---|---|
| Visa | 4111 1111 1111 1111 | Approval scenario |
| Visa | 4000 0000 0000 0002 | Decline scenario |
| Mastercard | 5555 5555 5555 4444 | Approval |
| Amex | 3782 822463 10005 | Approval |
| Diners | 3056 9309 0259 04 | Approval |
Regla operativa: ningún dev/QA puede insertar PAN de producción en staging bajo NINGUNA circunstancia. Violación = terminación de contrato con causal (cláusula NDA).
8. Sub-pregunta 8 — Guía a clientes si compartimos claves
Estado actual: Fintrixs NO comparte claves criptográficas con merchants.
Los merchants reciben:
- API keys (hashadas server-side, tokens al portador que NUNCA se re-muestran)
- OAuth 2.0 client credentials
- Webhooks signing secrets (Fintrixs firma, merchant valida)
Ninguno de estos requiere que el merchant maneje "claves criptográficas" en el sentido PCI DSS 3.7 (no son claves de cifrado de PAN).
Si en el futuro se ofreciera integración con mTLS certs por merchant, se entregaría guía MERCHANT-KEY-HANDLING-GUIDELINES.pdf documentada en KEY-MANAGEMENT-PROCEDURE.md §12.
9. Sub-pregunta 9 — Documento de Arquitectura Criptográfica
📄 CRYPTO-ARCHITECTURE.md — DOC-CRYPTO-ARCH-01 v1.0 aprobado 2026-07-29.
Incluye todos los elementos que exige el QSA:
| Elemento QSA | Sección del doc |
|---|---|
| Algoritmos | §4 (tabla completa NIST-approved) |
| Protocolos | §4 (TLS 1.3, HMAC, AEAD) |
| Tipos de claves | §5.1 (jerarquía completa) |
| Fortaleza (key lengths) | §4 (256-bit AES, 2048-bit RSA, 256-bit HMAC) |
| Fechas de expiración | §5.3 (cryptoperiods explícitos) |
| Función de cada clave | §5.1 + §5.2 (tabla por clave) |
| Inventario HSM/KMS | §8 (inventario completo + justificación de no-HSM con compensating controls) |
10. Inventario final de dispositivos criptográficos
| Dispositivo | Tipo | Ubicación | Función | Serial / Deploy ID |
|---|---|---|---|---|
vault-01 | Software Cryptographic Module (HashiCorp Vault OSS v1.15.6) | Droplet en fintrix-production-vpc (10.100.0.13 privado / 134.122.120.116 público) | Master key + transit + PKI — Raft LEADER | Droplet ID: 588832153 |
vault-02 | Idem | Droplet en fintrix-production-vpc (10.100.0.19 / 143.244.165.55) | Idem — Raft follower | Droplet ID: 588832226 |
vault-03 | Idem | Droplet en fintrix-production-vpc (10.100.0.20 / 178.128.145.200) | Idem — Raft follower | Droplet ID: 588832293 |
| Cluster Vault | fintrix-prod-vault-cluster | 3 nodos Raft HA | Cluster ID: f5761eb9-b88e-21e6-8cd7-228dbe81d9ed | Inicializado 2026-07-30 |
fintrix-vault-audit-nyc3 | DO Spaces bucket con object-lock (WORM) | DO region nyc3 | Audit log inmutable de Vault | ⏳ pendiente creación via UI |
Postgres card_vault schema | Sistema de almacenamiento cifrado | Managed pg fintrix-production-fintrix-pci (ID 061abee8-1d2c-49f4-be08-55f0054288cb) | Ciphertexts de PAN/CHD + DEK cifradas | Migration 010_card_vault_persistent.sql pendiente apply |
Nota sobre ausencia de HSM físico (Compensating Control):
Fintrixs no utiliza HSM físico validado FIPS 140-2 Level 3 en la implementación inicial. La decisión se compensa con: (a) uso exclusivo de algoritmos NIST SP 800-131A approved (AES-256-GCM, HMAC-SHA-256), (b) Shamir Secret Sharing 5-of-5 threshold 3 para split knowledge + dual control sin ambigüedad, (c) audit log inmutable con retención 3 años en WORM storage, (d) segmentación del cluster Vault en droplets dedicados sin otras cargas, (e) monitoreo continuo con alertas SIEM en cualquier operación privilegiada, (f) roadmap comprometido para migración a HSM (Vault Enterprise HSM binary o Fortanix DSM SaaS) en Q4 2026 tras evaluación de volumen post-certificación. Este compensating control se declara formalmente en
docs/pci-dss/COMPENSATING-CONTROLS-REGISTRY.md(SEC-031).
11. Tickets de remediación asociados
| Ticket | Descripción | Estado | Fecha objetivo |
|---|---|---|---|
| SEC-017 | Vault OSS cluster 3 droplets Raft HA (vault-01/02/03) | ✅ Provisionado | 2026-07-30 |
| SEC-024 | Rotator SecretID Vault semanal (systemd timer domingos 03:08 UTC) | ✅ Activo | 2026-07-31 |
| SEC-029 | Ceremonia Shamir — paquete completo preparado (README, guion, 4 scripts, 6 templates, runbook) | ⏳ Ejecución día D pendiente | 2026-08-08 |
| SEC-030 | INV-004-CRYPTOGRAPHIC_KEY_CUSTODY (9 claves inventariadas) | ✅ Documentado | 2026-08-01 |
| SEC-031 | COMPENSATING-CONTROLS-REGISTRY | ⏳ Pendiente | 2026-08-08 |
| SEC-032 | verify-crypto-roundtrip.sh weekly | ⏳ Pendiente | 2026-08-08 |
| SEC-033 | verify-no-plaintext-pan-in-db.sh weekly | ⏳ Pendiente | 2026-08-08 |
| SEC-034 | Grafana Vault & Key Management Compliance (paneles ya incluidos en dashboard fintrix-pci-compliance, deployed en cluster) | ✅ Deployed | 2026-08-01 |
Anexo A — Evidencia real capturada (2026-07-31)
Ver docs/pci-dss/evidence/q28-vault/ para archivos raw.
A.1 Estado del cluster Vault en producción
Cluster Name : fintrix-prod-vault-cluster
Cluster ID : f5761eb9-b88e-21e6-8cd7-228dbe81d9ed
Version : Vault v1.15.6
Storage : Raft integrated (HA)
Initialized : true
Sealed : false
Total Shares : 5
Threshold : 3
HA Mode : active (leader: vault-01 = 10.100.0.13)
Active Since : 2026-07-30T22:13:02.357Z
Raft Committed : 483 (creciendo)Full output: evidence/q28-vault/vault-config-live.txt
A.2 Audit devices habilitados (Req 10)
Path Type Description Options
---- ---- ----------- -------
file/ file n/a log_raw=false file_path=/var/log/vault/audit.log
syslog/ syslog n/a facility=AUTH tag=vaultUpload hourly a WORM externo: systemd-timer vault-audit-uploader.timer en los 3 nodos → sube al bucket s3://fintrix-vault-audit-nyc3/ con Spaces Key vault-audit-writer scoped a ese bucket (readwrite only).
Primer upload verificado en producción:
audit/year=2026/month=07/day=31/vault-01-20260731-03-*.log.gz 11,096 bytesA.3 Rotación real ejecutada de las 3 KEKs (Req 3.7.4)
Rotate kek-card-vault v1 → v2:
time : 2026-07-31T03:59:41.247Z
operation : update
path : transit/keys/kek-card-vault/rotate
remote_address : 10.100.0.13
latest_version : 2 (was 1)
result : 204 (success)Rotate kek-tokenization v1 → v2:
time : 2026-07-31T04:00:51.949Z
operation : update
path : transit/keys/kek-tokenization/rotate
latest_version : 2Rotate hmac-pan-fpr v1 → v2:
time : 2026-07-31T04:00:52.454Z
operation : update
path : transit/keys/hmac-pan-fpr/rotate
latest_version : 2Audit events raw (JSON, HMAC-hasheados según spec Vault): evidence/q28-vault/kek-rotation-audit-events.jsonl
A.4 Separación prod / test (Req 3.6.1.4)
Vault OSS no soporta namespaces (Enterprise-only). Implementado con mount paths separados:
Mount Type Keys
transit/ transit kek-card-vault, kek-tokenization, hmac-pan-fpr
transit-staging/ transit kek-card-vault-staging, kek-tokenization-staging, hmac-pan-fpr-stagingLas policies card-vault-transit-policy y tokenization-transit-policy limitan explícitamente el path a transit/* — no pueden acceder a transit-staging/*. Un futuro AppRole staging tendrá policy espejo apuntando a transit-staging/*.
Enforcement CI: Semgrep rule no-cross-env-vault-addr (ver §7.2) bloquea PRs que mezclen ambientes.
A.5 Round-trip encrypt/decrypt verificado
Ejecutado durante configure-engines.sh:
plaintext: 4111111111111234 (Visa test card, no PAN real)
ciphertext: vault:v1:... (formato: vault:v<version>:<base64_of(nonce+cipher+tag)>)
decrypted: 4111111111111234
✅ Round-trip OKA.6 card-vault-service operativo en pci-cde con Vault real
kubectl -n pci-cde get deploy card-vault-service
NAME READY UP-TO-DATE AVAILABLE AGE
card-vault-service 1/2 2 1 4h+Pod log confirma:
Nest application successfully started- Vault AppRole login OK (con
NODE_EXTRA_CA_CERTS=/etc/vault/ca.pem) - Routes mapeados incluyendo
/vault/cards/:cardId/reveal(Req 3.4 4-eyes)
Anexo B — Disaster Recovery real (2026-07-31)
Ver DR-RUNBOOK.md para procedimiento completo.
B.1 Modelo de amenaza cubierto
Compromise total de cuenta DigitalOcean → atacante borra:
- 3 droplets Vault
- Cluster K8s + workloads
- Managed Postgres (backups automáticos de DO mueren con el cluster)
- Container Registry
- Todos los buckets DO Spaces
Sobreviven:
- GitHub (código fuente + IaC)
- Backblaze B2 en cuenta SEPARADA (defense in depth)
- Shamir shares físicos custodiados
- DNS Cloudflare
B.2 Backblaze B2 — Object Lock Compliance (Req 10.5.2)
6 buckets con Object Lock Compliance mode, NADIE puede borrar durante retention (ni el owner de la cuenta B2):
| Bucket | Retention | Fecha ejecución primer backup |
|---|---|---|
fintrix-dr-vault | 7 años | 2026-07-31 21:08 (60 KB snapshot) |
fintrix-dr-postgres | 3 años | 2026-07-31 21:32 (17 DBs) |
fintrix-dr-manifests | 90 días | 2026-07-31 21:36 (1.2 MB tarball) |
fintrix-dr-audit | 3 años | 2026-07-31 21:10 (dual write) |
fintrix-dr-images | 30 días | Roadmap Q4 |
fintrix-dr-terraform | 7 años | Roadmap Q4 |
Test destructivo verificado:
s3.put_object(...) → uploaded VersionId=4_zc62f1dd4f7b3b99e9...
s3.get_object_retention(...) → mode=COMPLIANCE until=2033-07-29
s3.delete_object(...) → ✅ AccessDenied (blocked)
s3.put_object(same key) → creates NEW version, original PRESERVED
list_versions(...) → 2 versions retainedB.3 GPG envelope encryption
- Fingerprint:
C29151EE49443C421C17C1CDD2CEEC128CF77BB8(RSA-4096, expira 2029-07-30) - Private key + passphrase: en Vault
secret/dr/gpg-backup-key(crypto-admin only) - Public key:
backend/infra/dr/backup-public.gpg
Chicken-and-egg intencional: restaurar Postgres backups requiere → Vault operativo → Shamir shares de custodios → snapshot Vault de B2. Doble-key defense.
B.4 Cronjobs activos en producción
| Cronjob | Where | Schedule | Fuente |
|---|---|---|---|
vault-snapshot-b2.timer | systemd en 3 droplets vault (solo leader ejecuta) | Diario 04:00 UTC | backend/infra/dr/vault-node/ |
vault-audit-uploader.timer (v2 dual write) | systemd en 3 droplets vault | Horario | Idem |
pg-dump-b2 CronJob K8s | pci-cde namespace | Diario 03:00 UTC | backend/infra/dr/k8s/pg-dump-b2-cronjob.yaml |
k8s-state-b2 CronJob K8s | pci-cde namespace | Diario 02:00 UTC | backend/infra/dr/k8s/k8s-state-cronjob.yaml |
Imagen custom registry.digitalocean.com/fintrix/dr-pg-dumper:1.1.0 — Alpine 3.21 con postgresql17-client, python3, boto3, gnupg, kubectl, non-root uid 70. Dockerfile en backend/infra/dr/Dockerfile.dr-dumper.
B.5 Restore procedure (Fase B + C automatizados)
restore-vault.sh: descarga snapshot de B2, valida SHA-256, prepara para custodios (interactivo — no automatiza master key restore, security anti-pattern)restore-postgres.sh: idempotente, fetch GPG key de Vault, descarga los 17 dumps, verifica SHA, descifra, restaura conpg_restore --jobs 4
RTO target: 8 hrs con automation | 24-48 hrs manual RPO target: 1 hr audit | 24 hrs DB + Vault snapshot
B.6 Cadena de custodia end-to-end
Attacker owns DO account →
↓ borra todo
Postgres backups sobreviven en B2 (Object Lock)
Vault snapshots sobreviven en B2 (Object Lock)
Audit logs sobreviven en B2 (Object Lock)
K8s state sobrevive en B2 (Object Lock)
↓ Restore
Nueva infra (DO/AWS/GCP) provisionada desde GitHub
Vault snapshot restaurado → custodios Shamir unseal
GPG private key extraída de Vault
Postgres dumps descifrados + pg_restore
K8s manifests re-aplicados
DNS Cloudflare cutover
↓
Servicio restaurado. Full audit trail preservado.12. Aprobaciones
| Rol | Nombre | Firma | Fecha |
|---|---|---|---|
| CTO | [TO_FILL] | ||
| Security Lead | [TO_FILL] | ||
| CEO | [TO_FILL] | ||
| Custodio A | [TO_FILL] | ||
| Custodio B | [TO_FILL] | ||
| Custodio C | [TO_FILL] | ||
| QSA reviewer | José David Álvarez (ControlCase) |
