regulator-portal-service — Security Model
Version: 1.0 Status: Draft Owner: Regulator-facing + Legal Last Updated: 2026-04-21 Companion: API_CONTRACTS · DATA_MODEL · EVENT_SCHEMAS · 13-security-compliance-tenancy.md · ADR-0004 §11 HSM, §14 multi-region
1. Authentication
1.1 Regulator plane (:3082) — mTLS + national PKI
- Envoy ingress with
require_client_certificate: true. - Trust anchors (
regulator_ca_trust_store) loaded from Vault PKI; refreshed every 15 min. - OCSP enforcement per RFC 6066 — stapled response preferred. If absent and
OCSP_HARD_FAIL=true(default), the handshake is terminated. Stapled response max-age 24 h. - CRL fallback — full CRL refreshed every 15 min cached in Redis under
regulator:crl:{issuerDn}; entry-found → revoked → hard reject. - Cert-extension pinning —
keyUsage ⊇ {digitalSignature, keyAgreement},extendedKeyUsage ⊇ {clientAuth},subjectAltNameURI or DNS matching*.atra.afor configured accredited bodies. - TLS 1.3 only; cipher-suite allowlist:
TLS_AES_256_GCM_SHA384,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_GCM_SHA256. No session resumption across pods (no fast-forward on revoked certs). - Session issued server-side (opaque Redis token) after subject-DN lookup in
regulator.users. Client cert is re-presented on every request; session cookie binds to cert fingerprint (rotating client cert mid-session triggers re-auth).
1.2 Auditor plane (:3083) — mTLS + auditor PKI
- Separate trust anchor (
auditor_ca_trust_store); no overlap with national PKI. - Same OCSP / CRL / cipher constraints.
- Additional gate: DB row in
regulator.auditor_accesswithstate = GRANTEDandaccess_expires_at > now(). Expired →403 AUDITOR_ACCESS_EXPIREDbefore any data read. - Session TTL min(30 min, time remaining on access grant).
1.3 Internal admin plane (:3084) — Kong JWT
- Kong validates platform JWT (RS256, JWKS from
auth-service). - Role
platform.regulator.adminrequired for most endpoints;platform.compliance.adminfor bundle-generation and LI escalation. - Service-to-service internal calls (cron pods, BFF) use short-lived HS256 tokens signed by a dedicated regulator-internal HMAC key.
1.4 IdP-agnostic
Per ADR-0002, the platform IdP is irrelevant to regulator and auditor authentication — they use mTLS exclusively. The idp claim (when present on internal JWTs) is captured in audit rows for forensic purposes only.
2. Authorization (RBAC)
Regulator-facing roles:
| Role | Capabilities |
|---|---|
regulator-read | List own org's LI + complaints + reports; read attestation catalog; generate ad-hoc reports |
regulator-li | Above + submit LI requests; read LI delivery manifest |
regulator-auditor | Read attestation evidence + request bundle generation |
external-auditor | Read evidence within grantedFrameworks; download bundles |
Internal roles (via platform JWT):
| Role | Capabilities |
|---|---|
platform.regulator.admin | SIEM destination CRUD, auditor grant CRUD, evidence collection trigger |
platform.compliance.admin | LI escalation, bundle generation |
platform.legal | LI transition initiator |
platform.security | LI transition approver; cert-revocation cascade override |
platform.auditor | Read-only audit-log query |
Enforcement:
- NestJS RoleGuard (or equivalent) at handler boundary — 403
INSUFFICIENT_SCOPEbefore handler entry. - Region scoping —
allowedRegions(ISO 3166-2) on the user controls which rows are returned; handler setsSET LOCAL app.current_org_name, app.caller_role, app.allowed_regions. Postgres RLS enforces row filtering as the last line of defence. - Dual-control on LI transitions — Ed25519 detached signature from a distinct user; same-user signature rejected by service.
- Time-box on auditor reads — every handler first validates
access_expires_at > now(); Redis session TTL mirrors backend TTL.
3. Data Protection
3.1 PII inventory & classification
| Field | Class | Storage | Transit |
|---|---|---|---|
regulator.users.cert_subject_dn | INTERNAL | Plain | TLS 1.3 |
regulator.li_requests.target_msisdn_ciphertext | RESTRICTED | AES-256-GCM per-request DEK, KEK transit/regulator-li-dek | TLS 1.3 |
regulator.li_requests.warrant_object_ref (PDF) | RESTRICTED | SSE-KMS + object-lock | HTTPS |
LI delivery packages (iri.json, cc.csv, cover.pdf) | RESTRICTED-LEGAL | Per-request DEK, KEK transit/regulator-li-dek; signed .p7s | SFTP over SSH-2 |
regulator.complaints.citizen_msisdn_ciphertext | RESTRICTED | Per-tenant KEK transit/regulator-complaints-{tenantId} | TLS 1.3 |
regulator.complaints.summary | CONFIDENTIAL | Plain (summary is already narrative) | TLS 1.3 |
| Reports PDF | CONFIDENTIAL | SSE-KMS + object-lock; PKCS#7 signed | HTTPS |
| Attestation evidence | CONFIDENTIAL | SSE-KMS + object-lock | HTTPS |
| SIEM destination credentials | SECRET | Vault KV | Vault API |
| Signing keys | SECRET | Vault Transit / HSM (PKCS#11); never exported | HSM API |
3.2 Encryption keys
| Key | Store | Rotation |
|---|---|---|
| mTLS server cert (regulator + auditor planes) | Vault PKI | 90 days |
| Per-tenant complaint KEK | Vault Transit | Annual |
| LI delivery KEK | Vault Transit | Annual |
regulator-reports-signing | HSM (PKCS#11 slot) | Annual; prior retained 10 years |
regulator-li-signing | HSM (PKCS#11 slot) | Annual; prior retained 10 years |
attestation-bundle-signing | HSM (PKCS#11 slot) | Annual; prior retained permanently |
| SIEM destination credentials (HEC tokens, syslog keys) | Vault KV | Quarterly or on ATRA request |
| Internal HS256 HMAC (BFF ↔ REST) | Vault KV | Monthly |
JWT validation — JWKS from auth-service | N/A | auth-service managed |
3.3 Redaction rules
- In events. Target MSISDN in LI events is always masked. Citizen MSISDN in complaint events is always masked. Message content bodies never appear.
- In logs. Pino redactor masks
target_msisdn,citizen_msisdn,warrant_hash(shown as prefix+suffix only). ESLint rule forbidslogger.*(..., { body })patterns; pre-commit check. - In REST responses. Regulator sees masked target MSISDN except inside authenticated LI-detail calls scoped to that LI request. Auditor never sees MSISDNs.
3.4 HSM posture (per ADR-0004 §11)
- Production HSM is network-attached FIPS 140-3 Level 3 (Thales/Luna equivalent).
- PKCS#11 sessions are per-pod; no cached handles across restart.
- HSM PIN lives in Vault KV, rotated monthly.
- Key-usage policy: sign-only on all three signing keys; extraction forbidden.
- Secondary (cold-standby) HSM in DR region; activation requires CISO + CTO sign.
3.5 LLM / AI data residency
Per AI_INTEGRATION.md — no LLM or external AI provider is used. The only AI is an on-prem classifier on complaint summaries. Start-up guard enforces AI_EXTERNAL_PROVIDER_ALLOWED=false in production.
4. Audit
All regulator and auditor actions are audit-logged:
| Actor | Audit table | Retention |
|---|---|---|
| Regulator login / failure | regulator.users_audit | 13 months |
| LI state transitions | regulator.li_audit (hash-chained, Postgres rule-protected) | 7 years |
| Complaint state changes | regulator.complaint_triage + outbox | 7 years |
| Report download | outbox + analytics-service archive | 7 years |
| Auditor reads | regulator.auditor_access_audit | 13 months |
| Admin config changes | outbox to regulator.audit.v1 stream | 13 months |
Audit events are also forwarded to the ATRA SIEM via UC-08 StreamToSiem, providing a tamper-evident external copy.
Hash chain: li_audit.hash_self = SHA-256(hash_prev || fromState || toState || initiator || approver || rationale || occurredAt). Chain integrity verified nightly; break detected → CRITICAL alert.
5. Fail-Closed Posture
- Cert-chain failure → hard reject. No soft fail for regulator plane; soft fail tolerated for auditor plane only with Legal sign-off per engagement.
- HSM unavailable → reports un-downloadable. Consumers see
503 HSM_UNAVAILABLE; the unsigned PDF is never served. - Postgres unavailable →
/readyreturns 503. K8s removes pod from service; regulator sees 503 from Envoy. - SIEM destination unreachable → disk-WAL engages; no event loss. See FAILURE_MODES §FM-02.
- Upstream unreachable during report generation → report marked
READYwith a WARNINGS section. Regulator trust > silent omission.
Availability attacks cannot bypass auditability — they can only delay or degrade read surfaces.
6. Tenant / Org Isolation
regulator.users.org_name+ RLS restrict each regulator's view to its own org's data.- Complaint resolution is scoped to the assigned
resolver_tenant_id; other tenants cannot read. - Auditor
grantedFrameworksrestricts which control catalog and bundles the auditor sees. - Per-tenant complaint KEKs ensure a key compromise is scoped to that tenant's complaints.
7. Secrets
| Secret | Store | Injection |
|---|---|---|
| Envoy server cert + key | Vault PKI → K8s Secret via CSI driver | File mount |
| Postgres credentials | Vault DB dynamic secret | Env var, 1-hour TTL |
| Redis credentials | Vault KV | Env var |
| NATS credentials | Vault KV | Env var |
| HSM PIN | Vault KV | Env var, 15-min TTL, refreshed by sidecar |
| SIEM destination auth | Vault KV per destination | Loaded per request (short-lived cache) |
| S3 access | IRSA / AWS IAM Role for ServiceAccount | No key material on disk |
| Internal HS256 HMAC | Vault KV | Env var |
No secret is ever written to logs, events, or config files. Pre-commit gitleaks scan + CI-time scan.
8. Threat Model
| Threat | Mitigation |
|---|---|
| Stolen regulator cert | OCSP/CRL enforcement on every handshake + cert-revocation cascade flips user to REVOKED within 15 min |
| Compromised auditor cert | Time-box limits damage window; meta-audit flags anomalous download bursts |
| Malicious regulator submits fake LI request | Warrant PDF SHA-256 verified; ATRA signs warrant with national PKI; cross-check possible via regulator-side CA |
| Dual-control bypass attempt | Backend rejects same-user initiator+approver; detached Ed25519 sig over liRequestId binds to distinct key |
| Admin compromise to tamper audit | li_audit + auditor_access_audit are Postgres-rule protected append-only; NATS+S3 mirror provides tamper-evidence; hash chain makes tampering observable |
| SFTP MITM on LI delivery | SSH-2 host-key pin in known_hosts.pinned; no host-key trust-on-first-use |
| Compromised SIEM endpoint | Each destination has scoped auth; one compromise does not cross-contaminate other destinations; outbound egress via NetworkPolicy allowlist |
| ATRA cert CA compromise | Ghasi maintains pinning to specific intermediate CA DNs; rotation only after Legal sign |
| HSM unavailability | Block downloads, surface 503; no unsigned artefact ever served |
| Cross-region data leak (LI to DR) | S3 ghasi-regulator bucket policy forbids cross-region replication for LI and complaint prefixes; bundles only cross-replicated for DR with legal sign |
9. GDPR / Regulatory
Afghanistan does not yet have a GDPR-equivalent statute, but the platform pre-emptively aligns:
- Right-to-erasure (
auth.user.erased.v1): consumed to redactregulator.complaints.citizen_msisdn_ciphertextandsummarywhile preserving audit-required metadata (complaint_id, regulator_ref, resolution_summary).li_requests.target_msisdn_ciphertextis not erased — LI evidence retention takes precedence (legal obligation). - Data portability: regulators can export their own LI + complaint history as signed PDFs via report generation.
- Sub-processor disclosure: reports disclose on-prem AI classifier usage; no other processors.
- Audit retention: 7-year for LI + complaints + reports (regulator mandate); permanent for attestation bundles; 13 months for audit trails.
10. Security Testing
- Contract tests per API_CONTRACTS §9.
- mTLS handshake tests with mock CRL / OCSP revocation scenarios in integration suite.
- Role-matrix test — every endpoint × every role — asserts 200 / 403 behaviour.
- Dual-control bypass test — same user cannot satisfy initiator + approver.
- Audit-chain tamper test — manual row alteration in test DB verifies nightly hash-verify catches it.
- SFTP host-key pinning test — altered host key rejects.
- HSM fault injection — confirmed downloads blocked, no unsigned fallback.
- Quarterly penetration test scoped to regulator + auditor planes.
- Secret scanning (
gitleaks), dependency scanning (osv-scanner), container scanning (trivy).