cbc-bridge-service — Security Model
Version: 1.0 Status: Draft Owner: Security + Government / Emergency Last Updated: 2026-04-21
Companion: API_CONTRACTS · DATA_MODEL · EVENT_SCHEMAS · FAILURE_MODES · 13-security-compliance-tenancy.md Related ADR: ADR-0004 §11 HSM key custody
1. Authentication
1.1 gRPC plane — government callers
- mTLS required. Client cert MUST chain to the national-PKI root pinned in the HSM trust store.
- OCSP stapling + CRL check on every handshake. CRL cached 4 h in Redis (
cbc:crl:{issuerSha}); OCSP freshness ≤ 10 min. On cache miss + responder unreachable → fail-closed (CBC-US-008). - Cert-subject pinning. The client cert's Subject DN + SHA-256 fingerprint MUST match an active row in
cbc.authorised_callers(CBC-US-009). Cert validity alone is insufficient. - Detached PKCS#7 signature in
x-gov-signatureover the RFC 8785 JCS canonical JSON of the request body. Verified via HSM PKCS#11C_Verifyagainst the presented cert's public key. In-process verification with a file-mounted key is prohibited and enforced by a start-up guard that refuses to boot whenCBC_HSM_BYPASS=trueoutsideNODE_ENV=development. - Anti-replay.
(certFingerprint, nonce)must be unseen in the last 24 h (Redis SETNX + Postgres fallback).requestedAtmust be within 120 s of server time.
1.2 gRPC plane — platform peers
regulator-portal-service calls on-behalf-of a government caller and therefore relays both its own SPIFFE SVID (service mesh mTLS) and the caller's national-PKI signature. The service enforces both identities. regulator-portal-service cannot impersonate callers because the signature is always against the caller's HSM-held private key — the portal never holds a signing key.
1.3 REST plane — admin + regulator read
- Kong validates the platform JWT (issued by
auth-service, RS256, JWKS-backed). - Kong injects
X-User-Id,X-Roles,X-Caller-Roleon the upstream request. - The service never parses JWTs directly — it trusts Kong's injection and enforces RLS via
SET LOCAL app.caller_role. /v1/cbc/public/drills*is anonymous (public drill feed, CBC-US-017); Kong rate-limits per source IP.
1.4 IdP-agnostic
Per ADR-0002, the IdP that authenticated the admin caller is irrelevant to cbc-bridge-service. All it ever sees is the platform JWT. The idp claim is captured in audit for forensic purposes.
2. Authorization (RBAC)
| Role | Capabilities |
|---|---|
platform.cbc.admin | Full CRUD on authorised_callers, drills, cbe_credentials_ref, mno_bind_registry; trigger cell-DB refresh/rollback; read full signature_audit; view un-masked broadcast evidence |
platform.auditor | Read cbc.audit.*, signature_audit, hash-chain verifier status; read broadcasts with caller identity masked below cert-subject CN first 4 chars; no state changes |
platform.regulator.read | Read drill records, drill reports, broadcasts metadata (no PKI evidence); for the regulator workbench surface |
platform.cbc.operator | Can initiate drill runs; cannot create/edit authorised_callers or cbe_credentials_ref |
| Government caller (mTLS) | BroadcastEmergency, CancelBroadcast, GetBroadcastStatus on their own broadcasts (scoped by callerId) |
Enforcement points:
- Kong (
jwt+aclplugins) for REST. - gRPC interceptor — requires mTLS + national-PKI signature verification before any application code runs.
- Application-level
@RequireRoles(...)decorator for REST admin handlers. - Postgres RLS on
cbc.broadcasts,cbc.signature_auditas belt-and-braces. - Body-redaction interceptor masks
cert_subject/mou_refoutsideplatform.cbc.admin/platform.auditor.
3. Data protection
3.1 PII & sensitivity inventory
| Field | Class | Storage | Transit |
|---|---|---|---|
cbc.broadcasts.body_variants | Public (will be broadcast) | Plain | TLS 1.3 |
cbc.authorised_callers.cert_subject | CONFIDENTIAL-INTERNAL | Plain; role-gated API | TLS 1.3 |
cbc.authorised_callers.cert_fingerprint_sha256 | CONFIDENTIAL-INTERNAL | Plain; role-gated API | TLS 1.3 |
cbc.authorised_callers.mou_ref | CONFIDENTIAL-INTERNAL (legal artifact) | Plain; role-gated API | TLS 1.3 |
cbc.signature_audit.* | CONFIDENTIAL-INTERNAL | Plain; role-gated API + append-only | TLS 1.3 |
cbc.mno_cell_database.lat/lng | INTERNAL (national-infra map) | Plain; role-gated API | TLS 1.3 |
cbc.cbe_credentials_ref.vault_path | INTERNAL (no secret) | Plain (pointer) | TLS 1.3 |
cbc.nonce_audit.* | INTERNAL | Plain | TLS 1.3 |
| HSM-sealed signing keys (national-PKI) | RESTRICTED | HSM only; never exported | — |
| Per-MNO CBE credentials | RESTRICTED | Vault Transit (secret/cbc/mno/{mno}/cbe-credentials) | TLS 1.3 |
3.2 Encryption at rest
- Postgres transparent data encryption (TDE) at the storage layer.
- HSM-held keys never leave the HSM (PKCS#11
C_Verifyoperations happen in the HSM). - Per-MNO CBE credentials in Vault Transit; pods hold 15-min envelope leases.
- No per-tenant KEK needed — there is no tenant-specific confidential content (bodies are public).
3.3 Encryption in transit
- gRPC: mTLS 1.3 with
ECDHE-ECDSA-AES256-GCM-SHA384(platform default cipher suite). - REST: HTTPS via Kong (TLS 1.3, HSTS,
X-Frame-Options: DENY). - MNO CBE: IPSec tunnel (where available) + TLS 1.3 at the adapter layer; failed tunnel = failed dispatch (
CBC_DEPENDENCY_UNAVAILABLE). - NATS: TLS 1.3 + NATS ACCOUNT mTLS (platform-peer identity).
3.4 Redaction rules
- In logs (Pino redactor):
cert_subject→CN=****.gov.af;cert_fingerprint_sha256→ first 8 + last 4 hex;mou_ref→****in non-admin access logs. Body content is not sensitive and is logged verbatim for regulator evidence. - In events (EVENT_SCHEMAS §5):
presentedCertSubjectMasked. Full value only on internal-only streams consumed byplatform-internalsubscribers. - In REST responses (body-redaction interceptor): caller identity masked unless
platform.cbc.admin/platform.auditor.
4. Audit
- Append-only.
cbc.audit,cbc.signature_audit,cbc.authorised_callers_history,cbc.outbox,cbc.nonce_auditreject UPDATE/DELETE at the Postgres rule level. - Hash-chained.
cbc.broadcastsandcbc.auditcarryprevHash/rowHash. Verifier runs daily (UC-07) and alertsCbcAuditChainBrokenCRITICAL on any break. - Tamper-evident cold archive. Partitions older than 13 months are promoted to an S3-compatible bucket with Object Lock (WORM retention 7 years) in
dxbsovereign region. - Meta-audit. Auditor role reads of
signature_auditare themselves audited (cbc.audit.meta.v1— reserved; not yet implemented).
5. Fail-loud, fail-closed posture
- HSM unavailable → every
BroadcastEmergencyrejectedCBC_HSM_UNAVAILABLE; PagerDutyCbcHsmUnavailableCRITICAL; NDMA notified via side-channel (phone tree runbook). - Signature verify fails →
CBC_SIGNATURE_INVALID+ audit row; if rate > 1/min over 5 min,CbcPkiVerifyFailureSpikeHIGH alert (probing detection, CBC-US-007). - CRL / OCSP unreachable → fail-closed; no connection accepted until refresh.
- Replay detected →
CBC_REPLAY_DETECTED; alert on any repeat within 24 h per caller. - All MNO CBEs unreachable → broadcast
FAILED; manual fallback runbook (runbooks/cbc-all-cbes-out.md).
Availability attacks cannot bypass policy — at worst they delay a broadcast, which is less harmful than a bypassed authorisation.
6. Caller isolation
- Postgres RLS on
cbc.broadcastskeyed oncaller_idfor government-caller self-view. - Evidence fetch (
/v1/cbc/broadcasts/{id}/pdus) isplatform.cbc.adminonly; callers cannot retrieve their own PDU bytes via the customer surface (regulator evidence is a separate flow).
7. Secrets
| Secret | Store | Injected as |
|---|---|---|
| gRPC server cert (public-facing for gov callers) | Vault PKI engine → K8s Secret via Vault Agent | File mount /etc/cbc-tls |
| HSM PKCS#11 unseal key | Vault KV via Vault-Agent init container | Env var (one-way, purged after HSM slot unseal) |
| National-PKI trust anchor | K8s ConfigMap (+ HSM trust-store) | File mount |
| Per-MNO CBE credentials | Vault Transit (secret/cbc/mno/{mno}/cbe-credentials) | Retrieved on pod start; cached 15 min |
| PostgreSQL credentials | Vault DB dynamic secret | Env var; rotated hourly |
| Redis credentials | Vault KV | Env var |
| NATS credentials | Vault KV | Env var |
| JWT validation | JWKS from auth-service via Kong | No keys stored |
No secret is ever written to logs, events, or config files. Pre-commit gitleaks scan blocks accidental commits.
8. Threat model
| Threat | Mitigation |
|---|---|
| Unauthorised broadcast via stolen PKI cert | HSM-bound private key (cert holder cannot export); OCSP revocation on every handshake; dual-control for P0 |
| Replay of valid signature | Nonce + 120-s clock window + 24-h nonce retention |
| Prompt injection | N/A — no LLM in hot path (AI_INTEGRATION) |
| Compromised admin account adds caller | platform.cbc.admin MFA mandatory; caller create requires counter-signed mouRef; audit log + SOC event |
| Audit-log tampering | Postgres rule reject UPDATE/DELETE + daily hash-chain verifier + cold-archive Object Lock (WORM 7 y) |
| CRL/OCSP MITM | CRL/OCSP transport via HTTPS with pinned CA; stapled OCSP preferred |
| Cell-DB poisoning (wrong coordinates → wrong targeting) | MNO exports delivered over IPSec/leased link; schema validation + coverage-delta anomaly check before atomic swap; manual admin approval required if delta > 10% |
| Signature-oracle (probing HSM for timing) | Constant-time verification path (HSM native); rate limits per cert; PagerDuty on anomalous failure rate |
| Side-channel body capture on outbox relay | Relay runs in same pod as service; TLS to JetStream; no unencrypted egress |
| NOC-role release of fraudulent broadcast | No release path exists in CBC (unlike compliance-engine). Broadcasts are either dispatched or cancelled; cancellation is dual-control |
| Regulator-channel breach | regulator.ca.trust.updated.v1 is signed by regulator HSM; we verify signature before reloading trust anchor |
9. GDPR & regulatory
- Broadcast bodies are public data — GDPR Art. 17 erasure does not apply; retention is regulator-mandated (7 years cold).
- Authorised-caller registry fields (cert_subject, MOU refs, dual-control partners) are operational metadata of a state actor, not "personal data" under GDPR Art. 4(1); nonetheless, redaction-on-erasure available.
- Right of access: government callers can export their own broadcasts via
GET /v1/cbc/broadcasts?callerId=me. - Audit evidence window: ≥ 7 years cold (exceeds GDPR-mandated minimums for incident evidence).
- Data residency: Afghan regions (
kbl,mzr) hold authoritative state. Sovereign cold archive indxbis sealed and governed by platform's sovereign-cloud DPA per ADR-0004 §5.
10. Security testing
- Contract tests per API_CONTRACTS §6.
- ZAP baseline + API scan on every main-branch build.
- Quarterly penetration test scoped to gRPC (signature bypass, replay, scope escalation) + REST admin.
- Role-matrix integration test — every endpoint × every role — verifies 200/403 behaviour.
- PKI-bypass test — attempt to verify a signature with a file-mounted key; MUST fail at start-up guard.
- Replay test — submit the same
(certFp, nonce)twice → second MUST beCBC_REPLAY_DETECTED. - Chain-tamper test — manually rewrite a
cbc.broadcastsrow; verifier MUST detect on next run. - Secret scanning (
gitleaks); dependency scanning (osv-scanner); container scanning (trivy). - HSM fail-closed drill — kill HSM connectivity in staging; verify no broadcast accepted.
11. Dual-control surfaces
| Surface | Initiator | Approver | Window |
|---|---|---|---|
CancelBroadcast | Caller A | Caller B ∈ A.dualControlPartners | 60 s |
| Severity escalation P1→P0 | Caller A | Caller B ∈ A.dualControlPartners | 60 s |
| Emergency HSM bypass (prohibited — documented only) | CISO | CTO | n/a — requires joint ADR |
| Cell-DB rollback | platform.cbc.admin | CISO | 5 min approval |
authorised_callers create | platform.cbc.admin | Legal (signed approval in audit log) | — |
12. Runbook references
runbooks/cbc-hsm-out.mdrunbooks/cbc-all-cbes-out.mdrunbooks/cbc-pki-probing.mdrunbooks/cbc-audit-chain-broken.mdrunbooks/cbc-drill-overdue.mdrunbooks/cbc-cell-db-stale.md