Work / Enterprise & Security / Zero-Trust MySQL

A database that trusts nothing.

Most MySQL clusters trust the network, the operator, and the application. Three places that get breached weekly. This deployment trusts none of them. The DB cannot decrypt PII. The operator cannot rewrite history. The application cannot bypass the procedure layer. Every claim is verifiable by a script in the repo.

TLS 1.3mTLS · required
AES-256-GCMClient-side · HSM-wrapped
SHA-256Append-only audit chain
60 sWORM anchor cadence
0Plaintext PII at rest

Act I · The Threat Model

Four breaches. All survived.

A threat model is only as honest as its non-goals. Here is what this design defeats, and what it does not pretend to.

01

Network attacker reads plaintext

TLS 1.3 with mutual authentication. require_secure_transport=ON. Cipher locked to AES-256-GCM-SHA384 and CHACHA20-POLY1305.

Defeated
02

App identity bypasses access control

App role can EXECUTE a small set of stored procedures and SELECT tenant-scoped views. No direct table access. No DDL.

Defeated
03

DBA quietly rewrites history

Audit table is append-only, globally hash-chained, and anchored every minute to off-host WORM storage. Two triggers reject UPDATE and DELETE on it.

Defeated
04

DB host compromise leaks PII

The DB never holds the key. PII is decrypted only in the client, with DEKs unwrapped by HSM-backed KMS. Compromising the host gets you ciphertext and IVs.

Defeated

Act II · The Control Matrix

Every control. Every standard.

Eleven controls across six layers. Each one cites the standard that backs it and the script that proves it.

Zero-Trust MySQL · Control matrix · v1.0
Layer Control Standard Status
TransportTLS 1.3 with mutual authentication. Strong cipher suites only.RFC 8446Enforced
IdentityPer-connection client certificate. Locked server keys.X.509 · mTLSEnforced
AccessStored-procedure-only writes. Tenant-scoped views. No SUPER on app accounts.Least privilegeEnforced
TenantPer-connection tenant context via security_set_tenant(). Cross-tenant rejected.Tenant isolationEnforced
CryptoClient-side AES-256-GCM. DEKs wrapped by HSM-backed KMS.NIST SP 800-38DEnforced
CryptoOptional Kyber768 hybrid wrap for long-tail PQ posture.FIPS 203Available
AuditSHA-256 chained prev_hash · curr_hash. Triggers reject UPDATE / DELETE.Tamper-evidentEnforced
AuditChain head emitted to syslog every 60 s. Shipped to retention-locked WORM bucket.Off-host anchorEnforced
HostUFW default deny. fail2ban on SSH. local_infile=0. skip_name_resolve=ON.CIS-alignedEnforced
Proofproof_suite.sh generates /tmp/proof_bundle.tar.gz for assessors.Evidence packGenerated
ScanOptional OpenSCAP CIS / STIG scan. Output bundled with evidence.SCAP 1.3Optional

Act III · The Data Path

PII in. Ciphertext stored.

The DB holds ciphertext, IV, AAD, and a wrapped DEK. It cannot decrypt. The app cannot decrypt without the HSM. The HSM cannot read the DB.

Write path · client → DB
  1. Client calls encrypt(kms_key, DEK). KMS never reveals the master key. Wrapped DEK returned.
  2. Client AES-256-GCM encrypts each PII field locally. Output: ciphertext, IV, AAD.
  3. Client invokes app_create_user_encrypted(...) over mTLS, passing only ciphertexts.
  4. DB stores values without any decrypt path in SQL. Procedure validates tenant context.
  5. Audit triggers append a row. Chain head recomputed and anchored to syslog within 60 s.
Read path · DB → client
  1. Client calls a tenant-scoped view. mTLS required. App role has SELECT only.
  2. DB returns ciphertext, IV, AAD, and the KMS-wrapped DEK. No plaintext leaves the host.
  3. Client unwraps the DEK by calling decrypt(kms_key, wrapped_DEK) against the HSM.
  4. Client AES-256-GCM decrypts locally. Plaintext lives only in the client process.
  5. Audit triggers log the read with actor, tenant, columns. Chain head re-anchored.

Act IV · Deployment

Five steps. Production-ready.

Three scripts run outside the VM. Two scripts run on the VM. One evidence bundle for the auditor. No yak-shaving.

# 1. PKI on workstation
./outside/outside_pki.sh
scp pki_server.tar.gz vm:/tmp/

# 2. KMS + WORM sink (cloud equivalent of your choice)
PROJECT_ID=<id> BUCKET=gs://<worm_bucket> \
  ./outside/outside_gcp_setup.sh

# 3. VM one-click installer
sudo PKI_TAR=/tmp/pki_server.tar.gz \
  ./vm/vm_oneclick.sh
# configures: TLS, mTLS, hardening, roles, proc API,
# tenant isolation, audit triggers, chain anchor, UFW, fail2ban

# 4. Client-side envelope encryption
python3 outside/pq_envelope_cli.py \
  projects/<p>/locations/<l>/keyRings/<r>/cryptoKeys/<k> \
  <tenant-uuid> "Alice Admin" "admin" "[email protected]" ...

# 5. Evidence bundle for the auditor
sudo CLIENTS_DIR=/path/to/clients ./vm/proof_suite.sh
# → /tmp/proof_bundle.tar.gz

Act V · Proof

Evidence the auditor accepts.

proof_suite.sh runs the matrix

Verifies TLS enforced, no-SSL connections blocked, TLS 1.3 handshake, mTLS client validation, hardened MySQL variables, proc-only writes, tenant isolation.

Append-only chain integrity proof

Walks the audit table, recomputes SHA-256(prev_hash || payload), compares against curr_hash. Any break is flagged before evidence ships.

Off-host WORM anchor

systemd timer logs chain head every minute to syslog. Cloud Logging exports to retention-locked object storage. Auditors can verify continuity end to end.

OpenSCAP CIS / STIG output

Optional scripts/openscap_scan.sh. Bundled into the evidence tarball. FIPS, AppArmor, SELinux signals captured.

CodeQL + CI green

GitHub CodeQL workflow runs on every push. CI workflow validates installer scripts. Both badges live on the repo README.

Honest non-goals

The README states what this defeats and what it does not: live-memory scraping in the client, or a fully compromised KMS control plane. No security theatre.

If your DB is the keys to the kingdom, stop trusting it.

I build database deployments where the operator, the network, and the application all distrust each other. The auditor gets a bundle. The compliance team gets a script. Production gets a deployment that fails closed.