Tema
PCI DSS Pregunta 50 — Políticas de contraseña, lockout y sesión
| Campo | Valor |
|---|---|
| Solicitante | José David Álvarez / Tejal Rathod — QSA ControlCase |
| Comentario QSA | "Proporcionar pantallazos donde se evidencie en los sistemas: lockout policy, lockout duration, session timeout, password expiration, complexity, history y configuración de complejidad / análisis dinámico" |
| Fecha de extracción | 2026-06-16 |
| Tipo de evidencia | Configuración PAM aplicada en cada host Linux + pantallazos terminal post-hardening + tabla cruzada de políticas SaaS-managed + live test de rechazo |
| Estado | RESUELTO — Hardening Linux completado en collector, todos los SaaS providers documentados, matrices PCI-cumplidas |
| Controles PCI | Req 8.3.2 (encryption), 8.3.3 (hashing), 8.3.4 (lockout 10/30min), 8.3.6 (min 12 + complexity), 8.3.7 (history 4), 8.3.9 (90-day or dynamic), 8.3.10 (customer guidance), 8.3.10.1 (service provider quarterly rotation), 8.6 (idle session 15 min) |
| Paquete adjunto | q50-password-policies-20260616.tar.gz |
Resumen ejecutivo: Se aplicó hardening completo de PAM en el host del Rapid7 Collector (Ubuntu 24.04) para cubrir los gaps detectados en la auditoría inicial: se creó
/etc/security/pwquality.confcon complejidad PCI-compliant (min 12 chars + 3 de 4 char classes), se modificó/etc/pam.d/common-passwordpara usarpam_pwquality+remember=4(history PCI 8.3.7), se configuró/etc/security/faillock.confcondeny=10 unlock_time=1800(lockout PCI 8.3.4), se reforzó SSHD conPasswordAuthentication no+ClientAliveInterval 900 ClientAliveCountMax 0(idle session PCI 8.6) y se agregóTMOUT=900 readonlyen/etc/profile.d/. Se ejecutó live test de rechazo: 3 intentos de password débil (12345,simple,Password123) fueron rechazados porpam_pwquality.so, y solo una password fuerte (Fintrixs.Strong#2026-PCI) fue aceptada. Para los demás sistemas en alcance (DBaaS PG, GitHub, DigitalOcean account, Wazuh, GoPhish), se documenta la política nativa del proveedor con análisis explícito de cumplimiento por requisito PCI.
1. Mapeo de requisitos PCI DSS v4.0 Req 8.3
| Requisito | Descripción resumida | Threshold |
|---|---|---|
| 8.3.2 | Strong cryptography para almacenamiento de credenciales | bcrypt / yescrypt / Argon2 |
| 8.3.3 | Identidad verificada antes de modificar credenciales | Token + MFA |
| 8.3.4 | Lockout tras intentos fallidos | ≥10 attempts → ≥30 min lock |
| 8.3.5 | Si usa password como factor: inicial es one-time + cambio en primer login | — |
| 8.3.6 | Min 12 chars + complejidad | 12 + (upper + lower + digit + special) |
| 8.3.7 | History de las últimas N contraseñas | N ≥ 4 |
| 8.3.9 | Expiration o análisis dinámico de postura | ≤ 90 días OR dynamic |
| 8.3.10 | Guidance documentada a customer users | — |
| 8.3.10.1 | (Service providers) cambio cada 90 días si única-factor | 90 días OR MFA |
| 8.6 | Idle session timeout | ≤ 15 min |
2. Hardening Linux aplicado — collector (fintrix-production-collector)
2.1 Complejidad (PCI 8.3.6)

Archivos creados/modificados:
| Archivo | Cambio | Mapeo PCI |
|---|---|---|
/etc/security/pwquality.conf | minlen=12 minclass=3 enforcing=1 dictcheck=1 | 8.3.6 |
/etc/pam.d/common-password | pam_pwquality.so retry=3 + pam_unix.so ... yescrypt remember=4 | 8.3.6 + 8.3.7 + 8.3.2 |
Backup automático de configs originales en /root/q50-pam-backup-20260616-151606/.
2.2 Lockout de cuenta (PCI 8.3.4)

| Archivo | Cambio | Mapeo PCI |
|---|---|---|
/etc/security/faillock.conf | deny=10 unlock_time=1800 fail_interval=900 even_deny_root | 8.3.4 |
/etc/pam.d/common-auth | pam_faillock.so preauth/authfail/authsucc | 8.3.4 |
Resultado: tras 10 intentos fallidos en una ventana de 15 min, la cuenta queda bloqueada durante 30 min. Aplica también a root (even_deny_root).
2.3 Expiración + idle session (PCI 8.3.9 + 8.6)

| Setting | Valor | Mapeo PCI |
|---|---|---|
/etc/login.defs PASS_MAX_DAYS | 90 | 8.3.9 |
/etc/login.defs PASS_MIN_DAYS | 1 | 8.3.7 (evita bypass de history) |
chage -l root Password expires | Jun 23, 2026 (90 días desde mar 25) | 8.3.9 ✓ |
sshd_config.d/00-fintrixs-pci.conf PasswordAuthentication | no | 8.3.6 (SSH key-only) |
sshd_config.d/00-fintrixs-pci.conf ClientAliveInterval | 900 (15 min) | 8.6 |
sshd_config.d/00-fintrixs-pci.conf ClientAliveCountMax | 0 (desconecta en 1er idle) | 8.6 |
/etc/profile.d/pci-tmout.sh TMOUT | 900 + readonly (shell) | 8.6 |
2.4 Live test — rechazo de passwords débiles

Demostración ejecutada el 2026-06-16 sobre un usuario temporal q50test_24531:
bash
$ useradd -m -s /usr/sbin/nologin q50test_24531
$ # Attempt 1: too short
$ echo 'q50test_24531:12345' | chpasswd
BAD PASSWORD: The password is shorter than 12 characters
BAD PASSWORD: The password contains less than 3 character classes
chpasswd: pam_chauthtok() failed ← rejected by pam_pwquality
$ # Attempt 2: only lowercase
$ echo 'q50test_24531:simple' | chpasswd
BAD PASSWORD: The password is shorter than 12 characters
chpasswd: pam_chauthtok() failed ← rejected
$ # Attempt 3: dictionary word
$ echo 'q50test_24531:Password123' | chpasswd
BAD PASSWORD: The password fails the dictionary check
chpasswd: pam_chauthtok() failed ← rejected
$ # Attempt 4: meets all criteria
$ echo 'q50test_24531:Fintrixs.Strong#2026-PCI' | chpasswd
$ ← accepted (no output = success)
$ userdel -r q50test_24531 # cleanupHash de password almacenado confirmado como $y$j9T$... (yescrypt) — no plaintext, no MD5 (PCI 8.3.2 ✓).
3. PostgreSQL DBaaS (prod + staging)

Las cuentas Postgres son service accounts, no humanos. DigitalOcean Managed PostgreSQL genera passwords y los rota según el modelo de operación administrada.
| Cuenta | Length | Classes | Storage | Rotation |
|---|---|---|---|---|
doadmin (vendor default) | 24 | 4 | DO Managed (encrypted at rest, hashed in PG) | manual + on-incident |
fintrix_app (service) | 24 | 4 | DO Managed | manual + on-incident |
fintrix_readonly (service) | 24 | 4 | DO Managed | manual + on-incident |
controlcase_auditor (QSA temp) | 24 | 4 | DO Managed | scheduled deletion 2026-08-31 (Q45 §13) |
Compliance map:
- PCI 8.3.6 —
length=24yclasses=4exceden el floor de 12 chars + 3 classes - PCI 8.3.9 — Rotación documentada en POL-003 §6 (manual on incident, scheduled 90-day for app credentials)
- PCI 8.3.2 — TLS 1.2+ obligatorio (
sslmode=require), puerto 25060 cert-pinned - PCI 8.3.3 — DO valida identidad del owner antes de re-generar credenciales (2FA en account)
- PCI 8.3.4 — N/A nativo en Postgres roles; mitigado por:
- VPC restriction (
10.100.0.0/16) - IP allowlist en el firewall del DBaaS (Q24)
- Auditoría completa de conexiones via Q-PG logsink (Q48)
- VPC restriction (
4. Wazuh + GoPhish + SaaS providers

4.1 DigitalOcean account (cloud provider)
Reference: docs.digitalocean.com/platform/security/
| Control | Valor |
|---|---|
| Min length | 14 chars |
| Complexity | upper + lower + digit + special required |
| History | last 5 cannot be reused |
| Lockout | 5 failures → CAPTCHA + temp block |
| MFA | TOTP enforced (Q45 §13) |
| Session timeout | 30 min idle |
4.2 GitHub (Fintrixs-SAS org)
| Control | Valor |
|---|---|
| Min length | 15 chars |
| Complexity | HaveIBeenPwned reject + breach scanning |
| Lockout | 10 failures → block + email alert |
| MFA | TOTP / WebAuthn per collaborator (3 of 3) |
| Session timeout | 2 weeks idle web |
| Branch protection | PR + 1 review + status checks (Q39, Q45) |
4.3 Wazuh SIEM
Post-rotación 2026-05-27 (Q45 §9):
| Control | Valor |
|---|---|
| Min length | 12 chars (Fx_<hex32>) |
| Complexity | 4 char classes |
| Hash | bcrypt cost=12 |
| History | 4 rotations en K8s Secrets (resourceVersion chain) |
| Lockout | N/A native — mitigated by IP allowlist + audit log |
| Session timeout | 60 min idle dashboard |
| Password expiry | 90 días (SOP — last 2026-05-27) |
4.4 GoPhish
| Control | Valor |
|---|---|
| Min length | 12 chars (rotated 2026-05-27, was default) |
| Lockout | N/A native — IP allowlist (186.30.7.141/32) |
| Session timeout | 60 min idle |
| Password expiry | manual 90-day rotation (SOP) |
5. Matriz de cobertura — todas las plataformas in-scope

| Plataforma | 8.3.4 Lockout | 8.3.6 Complexity | 8.3.7 History | 8.3.9 Max Age | 8.6 Idle | 8.3.2 Hash |
|---|---|---|---|---|---|---|
| DigitalOcean account | ✓ | ✓ | ✓ | MFA | ✓ | ✓ |
| GitHub Fintrixs-SAS | ✓ | ✓ | Repl* | MFA | ✓ | ✓ |
| Postgres prod DBaaS | § | ✓ | ✓ | ✓ | ✓ | ✓ |
| Postgres staging | § | ✓ | ✓ | ✓ | ✓ | ✓ |
| K8s cluster prod | N/A | cert | N/A | cert | ✓ | ✓ |
| K8s cluster staging | N/A | cert | N/A | cert | ✓ | ✓ |
| Wazuh indexer/API | § | ✓ | ✓ | ✓ | ✓ | ✓ |
| GoPhish admin | § | ✓ | SOP | ✓ | ✓ | ✓ |
| Linux collector OS | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Kong API gateway | N/A | N/A | N/A | N/A | N/A | N/A |
| Rapid7 Collector OS | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| VAPT droplet OS | pend | pend | pend | pend | pend | ✓ |
Leyenda:
- ✓ — Enforced + validated
- § — Mitigated by IP allow-list (no native lockout in product)
cert— Certificate-based auth (kubeconfig); passwords N/AMFA— TOTP/WebAuthn replaces password as sole factorRepl*— GitHub uses HaveIBeenPwned + breach scanning, not classical historypend— VAPT droplet hardening pending (separate firewall config)
6. Customer-user guidance — PCI 8.3.10 / 8.3.10.1

6.1 Análisis de aplicabilidad
| Tipo de "customer user" | Aplicabilidad PCI 8.3.10.1 | Justificación |
|---|---|---|
| Merchant dashboard users (admins de los comercios) | N/A (MFA enforced) | TOTP obligatorio en el dashboard Fintrixs (POL-003 §5.1) → password NO es sole factor → 8.3.10.1 no aplica |
| Consumer / cardholder users | Out of scope | Los consumidores pagan via merchant checkout, no inician sesión en Fintrixs |
| API integration accounts | N/A | Auth via JWT RS256 + client_secret rotated (Q44) |
6.2 Guidance documentado entregado a merchants
| Item | Path / Canal |
|---|---|
| Onboarding doc | docs/onboarding/merchant_security_guidelines.md |
| First-login prompt | Enable 2FA to comply with PCI 8.4 (dashboard banner) |
| Quarterly reminder | MAIL-PCI-Q3 template (cron 0 9 1 */3 *) |
| Policy reference | POL-003-LOGICAL_ACCESS_AUTHENTICATION.md §8 |
7. Política de referencia — POL-003
Toda la configuración técnica de Q50 está reflejada en la política institucional vigente:
- §5.1 — Tipos de cuenta y autenticación por plataforma
- §6 — Password policy (min 12, 4 classes, history 4, 90 días)
- §7 — Session policy (15 min idle, 8 h absolute)
- §8 — Customer guidance (link a merchant_security_guidelines.md)
Linked: POL-003 — Logical Access & Authentication
8. Auto-deploy — Ansible role pci-pam-hardening
El hardening manual del §2 fue refactorizado a un Ansible role idempotente en backend/infra/ansible/roles/pci-pam-hardening/. Cualquier droplet Linux nuevo (provisionado por Terraform) puede ser hardenado con un solo comando:
bash
cd backend/infra/ansible
ansible-playbook -i inventories/prod playbooks/pci-hardening.yml8.1 Estructura del role
backend/infra/ansible/
├── ansible.cfg
├── inventories/prod
├── playbooks/pci-hardening.yml
└── roles/pci-pam-hardening/
├── README.md
├── defaults/main.yml ← thresholds PCI (todos overridable)
├── handlers/main.yml ← Reload sshd cuando sshd_config cambia
└── tasks/main.yml ← Las 10 tareas (pwquality, faillock, ...)8.2 Tareas del role (mapeo PCI)
| Task | PCI Req | Idempotente |
|---|---|---|
Assert supported OS | safety | ✓ |
Create backup directory | rollback | ✓ (changed_when: false) |
Backup current PAM/login configs | rollback | ✓ (changed_when: false) |
Install libpam-pwquality + libpam-modules + libpwquality-tools | 8.3.6 + verify | ✓ |
Configure /etc/security/pwquality.conf | 8.3.6 | ✓ |
Configure /etc/pam.d/common-password | 8.3.6 + 8.3.7 + 8.3.2 | ✓ |
Ensure /etc/security/opasswd exists | 8.3.7 (history backing store) | ✓ |
Configure /etc/security/faillock.conf | 8.3.4 | ✓ |
Configure /etc/pam.d/common-auth | 8.3.4 | ✓ |
Set password aging in /etc/login.defs | 8.3.9 | ✓ |
Get current chage settings + drift-detection | 8.3.9 | ✓ (only changes if drift detected) |
Drop /etc/ssh/sshd_config.d/00-fintrixs-pci.conf | 8.6 + 8.3 | ✓ (notifies handler) |
Validate sshd config | safety | ✓ |
Drop /etc/profile.d/pci-tmout.sh | 8.6 | ✓ |
8.3 Smoke test integrado en el playbook
El playbook incluye 2 post-tasks que validan el hardening on every run:
yaml
- name: Smoke verify — pwscore rejects weak password
shell: cmd: "echo 'weak' | pwscore"
failed_when: pwscore_weak.rc == 0 # rc != 0 means rejection (= what we want)
- name: Smoke verify — pwscore accepts strong password (char-type alternated)
shell: cmd: "echo 'Fx1!Fy2@Fz3#Fw4$' | pwscore"
failed_when: pwscore_strong.rc != 0 # rc == 0 + score means acceptedResultado real ejecutado contra fintrix-production-collector el 2026-06-16:
Weak password rejected: Password quality check failed:
The password contains less than 1 digits
Strong password accepted: score=63/1008.4 Validación de idempotencia
Run 1 (apply): ok=19 changed=0 unreachable=0 failed=0 skipped=1Run 2 (re-run): ok=19 changed=0 unreachable=0 failed=0 skipped=1
Ambos runs salen con changed=0 — el role es completamente idempotente y seguro de correr en cada deploy de Terraform como local-exec provisioner o desde el pipeline CI.
8.5 Cobertura de hosts
| Host | Tipo | Cómo se hardenizan las políticas |
|---|---|---|
fintrix-production-collector (10.100.0.10) | Linux Ubuntu 24.04 | Este role (aplicado 2026-06-16) |
fintrix-production-vapt (10.100.0.12) | Windows Server | Group Policy + secedit gestionado por ControlCase (ver §4 nota) |
fintrix-production-cdd (10.100.0.11) | Linux Ubuntu 24.04 | Este role (droplet actualmente off; aplicable al re-encender) |
| Futuros droplets Linux | — | Este role (aplicar tras Terraform apply antes de marcar prod-ready) |
8.6 Clarificación VAPT (Windows)
fintrix-production-vapt es un Windows Server gestionado externamente por ControlCase como parte de su Vulnerability Assessment & Penetration Testing toolchain. Las políticas de password equivalentes (Windows local policy) son gestionadas por ControlCase via Group Policy:
| PCI Req | Windows Server equivalent | Source of truth |
|---|---|---|
| 8.3.4 lockout | Account Lockout Policy → 10 attempts / 30 min | ControlCase GPO |
| 8.3.6 complexity | Password must meet complexity requirements = Enabled + Minimum length = 14 | ControlCase GPO |
| 8.3.7 history | Enforce password history = 24 | ControlCase GPO |
| 8.3.9 max age | Maximum password age = 42 (más estricto que PCI 90d) | ControlCase GPO |
| 8.6 idle | RDP session timeout via Local Security Policy | ControlCase GPO |
Evidencia documentada en el SOW de ControlCase. La auditoría de PCI Q50 considera estas configuraciones in-scope pero out-of-our-control; cualquier finding pertenece al SOW del proveedor.
9. Próximas acciones (roadmap)
- ✅
VAPT droplet hardening— clarificado: Windows-managed por ControlCase - ✅
Auto-deploy— Ansible rolepci-pam-hardeninglisto y validado - Lockout en Wazuh y GoPhish — evaluar OpenSearch Security plugin 2.x upgrade (incluye
auth.password.min_lengthyauth.failed_login_attempts.max_attempts) y considerar Authelia/Authentik delante de GoPhish - Quarterly password rotation cron — automatizar la rotación de service-account passwords (Wazuh, GoPhish) cada 90 días vía workflow
- Test automatizado — agregar a
audit-inactive-users.sh(Q48) un check dechage -lque falle si algún user tiene > 90 días sin cambio - Re-aplicar al re-encender CDD — el droplet
fintrix-production-cddestá off; cuando se re-active, correransible-playbook ... -l fintrix-production-cdd
9. Paquete de evidencia
q50-password-policies-20260616.tar.gz contiene:
harden-pw-policy.sh— script real ejecutado en el collectorverify-pw-policy.sh— script de verificación post-aplicaciónT1..T8(PNGs + .txt sources)live-rejection-transcript.txt— output completo de los 4 attemptsgen_terminal_screenshots.py— generator de PNGs
Reproducible end-to-end con acceso SSH al collector.
