Terminal mTLS Local QA
This document covers the first local terminal trust-boundary harness. It proves
that local QA can exercise a real TLS proxy with client certificate
verification before traffic reaches terminal-api.
Command
./scripts/local-validation/qa.sh test terminal
The command generates local certificate material, starts the nginx terminal
mTLS proxy on https://localhost:8443, syncs the simulator terminal row with
the generated certificate when terminal-api is running, and emits a Markdown
smoke report under .dev-logs/qa/reports/.
For low-memory planning and fixture review without starting the stack, use:
./scripts/local-validation/qa.sh test terminal-lifecycle
That command renders dry-run SQL and curl evidence for active, expired,
revoked, and wrong-terminal certificate states. It writes reports and fixture
SQL under .dev-logs/qa/ but does not start Docker, terminal-api, or PgAdapter.
Generated Material
scripts/qa/terminal_mtls.py writes the following files under
.dev-logs/qa/terminal-mtls/:
qa-terminal-root-ca.crtandqa-terminal-root-ca.keyqa-terminal-server.crtandqa-terminal-server.keyqa-simulator-terminal.crtandqa-simulator-terminal.keyqa-rogue-terminal.crtandqa-rogue-terminal.keynginx-terminal-mtls.confsimulator-terminal-cert-update.sqlterminal-lifecycle-active.sqlterminal-lifecycle-expired.sqlterminal-lifecycle-revoked.sqlterminal-lifecycle-wrong-terminal.sql
The simulator terminal ID is
00000000-0000-0000-0000-000000000003. The helper computes the SHA-256 hash of
the generated client certificate DER and writes SQL that updates the local
simulator terminal row to match the generated certificate. local-validation/qa.sh applies
that SQL automatically when the terminal-api PgAdapter is reachable.
The wrong-terminal lifecycle fixture reuses the seeded pending QA terminal
qa_terminal_pending_id (36095d1b-3550-55f2-b968-26e031cfbd63) as the
different row that receives the simulator certificate metadata. That keeps the
negative proof distinct from a missing-row/no-certificate case.
Local Smoke
With the proxy running, the accepted case is:
curl -k \
--cert .dev-logs/qa/terminal-mtls/qa-simulator-terminal.crt \
--key .dev-logs/qa/terminal-mtls/qa-simulator-terminal.key \
https://localhost:8443/health
The missing-cert rejection case is:
curl -k https://localhost:8443/health
The wrong-CA rejection case is:
curl -k \
--cert .dev-logs/qa/terminal-mtls/qa-rogue-terminal.crt \
--key .dev-logs/qa/terminal-mtls/qa-rogue-terminal.key \
https://localhost:8443/health
The accepted case must return 200. The missing-cert and wrong-CA cases must
fail or return a non-200 client-certificate rejection from nginx.
When terminal-api is running, the smoke also calls
/v1/sync/products/count through the mTLS proxy. That check proves the proxy
headers, generated certificate hash, terminal table state, and terminal-api
principal lookup agree locally.
Boundary
This harness is local mTLS edge proof plus a DB-backed terminal principal smoke when the local terminal service is up. The dry-run lifecycle harness provides ready-to-apply fixtures for cert revocation, cert expiry, and wrong-terminal states, but those negative cases are still high-memory runtime validation until the local stack is running.
Complete terminal onboarding issuance, Android terminal configuration sync, and real GCP load balancer/Private CA behavior remain in the terminal onboarding, Android POS, and security/compliance checklists until they are promoted into automated local tests.