Skip to content

4. Technical Design

Overall architecture

Reference technology stack

  • Business DB & vault: PostgreSQL (TDE, pgcrypto, native RLS). Can be SQL Server (Always Encrypted) or Oracle (TDE + Data Redaction).
  • Key management: self-hosted HashiCorp Vault or cloud KMS/HSM with a region in Vietnam.
  • Access gateway (M2): stateless gRPC/REST service, horizontally scaled behind a load balancer.
  • Audit & monitoring: separate append-only store (separate PostgreSQL or OpenSearch); SIEM.
  • Identity: IdP/SSO issuing JWT for users, mTLS for service-to-service.

Cross-cutting principles

  • Default-deny.
  • Separation of duties: data, keys, and audit on three distinct trust boundaries.
  • Single entry: all PII access via gateway M2.
  • Full traceability: no PII operation without audit.
  • Fail-closed for security.
[Apps: Web/App/CRM/OMS]
| (store only pii_ref)
v JWT/mTLS + purpose
[M2 PII Gateway] --(check)--> [M5 Access Control]
| reveal/store
v
[M3 Encryption & KMS] <---> [Vault: keys]
| ciphertext + blind index
v
[Vault DB: TDE] [M4 Masking on return]
+--> [M6 Immutable Audit] --> [M7 Monitoring/Alert]
+--> [M8 Compliance/DSAR]

M1 — Subject Management

TABLE subject (
pii_ref UUID PRIMARY KEY DEFAULT gen_random_uuid(),
status TEXT NOT NULL DEFAULT 'active', -- active|merged|shredded
merged_into UUID NULL REFERENCES subject(pii_ref),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
TABLE subject_field (
pii_ref UUID REFERENCES subject(pii_ref),
field TEXT NOT NULL, -- phone|email|address|fullname
value_enc BYTEA NOT NULL, -- AES-256-GCM ciphertext
value_bidx TEXT NULL, -- HMAC blind index
dek_id TEXT NOT NULL, -- key reference in Vault
PRIMARY KEY (pii_ref, field)
);
CREATE INDEX ON subject_field(value_bidx);

Decision: pii_ref uses UUIDv4 (unguessable). Split subject_field per row so each field uses its own DEK and rotates independently.

M2 — PII Access Gateway

AuthContext { identity, roles[], auth_method(JWT|mTLS) }
RevealField(pii_ref, field, purpose) -> { value | masked_value, audit_id }
StoreSubject(fields{}, purpose) -> { pii_ref, audit_id }
UpdateSubject(pii_ref, patch{}, purpose) -> { ok, audit_id }
LookupByIndex(field, value, purpose) -> { pii_ref | null, audit_id }
BulkReveal(pii_refs[], field, purpose) -> { request_id, PENDING_APPROVAL }

RevealField sequence: authenticate → M5.Authorize → (DENY/PENDING/ALLOW) → M3.Decrypt → M4.Mask → M6 record audit → return.

M3 — Encryption & Key Management

KEK (in Vault/HSM, never exported)
└─ wrap/unwrap ─> DEK (per subject/field)
└─ AES-256-GCM encrypts value
value_enc = AES_GCM(DEK, nonce, value)
value_bidx = HMAC-SHA256(IndexKey, normalize(value))

Decision: AES-256-GCM (AEAD), unique nonce each time. Per subject/field DEK narrows blast radius. Key rotation only re-wraps DEK (no full re-encryption).

M4 — Dynamic Masking

TABLE mask_policy (
role TEXT, field TEXT,
strategy TEXT, -- FULL|PARTIAL|HIDE
PRIMARY KEY (role, field)
);
-- Default when no row: HIDE
-- phone 0901234567 -> 09****4567 ; email an@mail.com -> a***@mail.com

Decision: data-driven configuration (no hard-coding). Multiple roles → apply the least-revealing strategy.

M5 — Access Control

TABLE role_grant (role_id TEXT, field TEXT, action TEXT,
PRIMARY KEY (role_id, field, action));
TABLE purpose_catalog (purpose TEXT PRIMARY KEY, active BOOL);
TABLE fourEyes_rule (action TEXT, condition TEXT);

Authorize algorithm: check valid purpose → check role_grant (default-deny) → apply RLS row scope → match four-eyes → ALLOW. Every branch returns a reason for M6 to record.

M6 — Immutable Audit Log

TABLE pii_audit (
seq BIGSERIAL PRIMARY KEY,
ts TIMESTAMPTZ DEFAULT now(),
actor TEXT, action TEXT, subject_ref UUID,
field TEXT, purpose TEXT, result TEXT,
meta JSONB, prev_hash TEXT, row_hash TEXT NOT NULL
);
row_hash = SHA256(prev_hash || ts || actor || action ||
subject_ref || field || purpose || result)

Immutability: revoke UPDATE/DELETE; hash-chain each INSERT; separate instance/credentials; periodic VerifyChain().

M7 — Monitoring & Detection

RuleExample conditionSeverity
Abnormal bulk read> N subjects/min by one actorHigh
Off-hours accessREAD outside 7am–9pmMedium
High DENY rate> X% DENY in windowMedium
Scattered accessone actor touches many unrelated subjectsHigh
Data exportany EXPORTHigh

M8 — Compliance & DSAR

TABLE consent (pii_ref UUID, purpose TEXT, granted BOOL, ts TIMESTAMPTZ,
PRIMARY KEY(pii_ref, purpose));
TABLE dsar_request (request_id UUID PRIMARY KEY, pii_ref UUID,
type TEXT, status TEXT, due_date DATE);
TABLE retention_policy (field TEXT, ttl_days INT);

ERASE process: receive & verify → M5 four-eyes → M1.ShredSubject → M3.ShredKey → mark shredded + M6 audit → issue deletion confirmation.

Module dependencies

ModuleDepends onImplemented in
M6 AuditIndependent (foundation)P2 — first
M2 GatewayM5, M3, M6P2 skeleton → P3 full
M3 EncryptionVault/KMSP3
M1 SubjectM3, M6P1 base → P3
M4 MaskingM5P3
M5 AccessIdP/SSO, M6P4
M7 MonitoringM6P5
M8 ComplianceM1, M3, M5, M6P6