Skip to main content

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.crt and qa-terminal-root-ca.key
  • qa-terminal-server.crt and qa-terminal-server.key
  • qa-simulator-terminal.crt and qa-simulator-terminal.key
  • qa-rogue-terminal.crt and qa-rogue-terminal.key
  • nginx-terminal-mtls.conf
  • simulator-terminal-cert-update.sql
  • terminal-lifecycle-active.sql
  • terminal-lifecycle-expired.sql
  • terminal-lifecycle-revoked.sql
  • terminal-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.