TotlProvision — Architecture¶
Three deployable surfaces, one product.
engine/ PowerShell provisioning engine (runs on each PC / USB)
backend/ Cloudflare Worker + D1 (fleet reporting, zero-knowledge escrow, audit)
portal/ Cloudflare Pages (Access/Entra SSO; engineer dashboard + secret reveal)
docs/ MkDocs Material site, auto-deployed to Cloudflare Pages on push
build/ release/packaging (Phase 1)
Tooling¶
- CI (
.github/workflows/ci.yml) runs on every push/PR: Node tests (backend/,portal/) + JS syntax checks, Pester (engine/tests) on a Windows runner, andmkdocs build --strict. - Docs build from the repo root via MkDocs and deploy to a dedicated Cloudflare Pages project. See Deploying these docs.
Engine (engine/)¶
Unchanged from 1.0 in structure; paths are relative to engine/ as the repo root.
- src/Invoke-Provision.ps1 — orchestrator: ordered phases, state.json, resume scheduled task.
- src/modules/Totl.* — one module per phase + shared Totl.Core.
- New: Totl.Crypto (encrypt-only escrow crypto) and Totl.Report (reporting + escrow client). These
are not yet wired into the provisioning phases — Phase 0 builds the spine only.
- gui/, oobe/, scripts/, config/, data/, assets/, tests/ as before.
Backend (backend/)¶
Cloudflare Worker (src/index.js) over D1 (migrations/0001_init.sql).
Two trust paths:
- Machine ingest — per-tenant Bearer API token (stored as a salted hash). Endpoints: /v1/report,
/v1/secret, /v1/tenant/pubkey.
- Portal — Cloudflare Access JWT (Entra SSO), verified against the team JWKS in auth.js, mapped
to a tenant + role via the users table. Endpoints: tenant key onboarding, dashboard reads, secret
reveal, audit.
Multi-tenant from day one: every table carries tenant_id.
Portal (portal/)¶
Static site (Pages) behind Access. All crypto is client-side (crypto.js, WebCrypto). The tenant
private key is generated in-browser, wrapped by an org passphrase, and never sent to Cloudflare in the
clear. Reveal fetches ciphertext and decrypts locally.
Secret-escrow data flow¶
machine: New-TotlPassword / BitLocker key
└─ Protect-TotlSecret (RSA-OAEP-SHA256, tenant public JWK)
└─ POST /v1/secret ──► D1.secrets.ciphertext (ciphertext only)
engineer browser (after Access SSO):
GET /v1/tenant/wrapped-key ──► unwrap with passphrase (PBKDF2+AES-GCM)
POST /v1/secret/:id/reveal ──► ciphertext + audit row
└─ decryptSecret (RSA-OAEP, in-memory private key) ──► plaintext shown locally
Interop contract (engine ↔ portal): tenant public key is a JWK {kty,n,e,alg:"RSA-OAEP-256"};
ciphertext is base64(RSA-OAEP-SHA256(utf8(secret))). JWK (not SPKI) because PowerShell 5.1
(.NET Framework) builds the key from n/e.
Enrollment at finalize (end-to-end slice)¶
When reporting.enabled is true, Invoke-Provision calls Totl.Enroll\Invoke-TotlEnrollment after all
phases complete:
- If
identity.localAdmin.rotatePasswordis true, generate a unique per-machine admin password, set it on the local account, and escrow it (RSA-OAEP to the tenant key) viaPOST /v1/secret— replacing the shared setup password right before autologon credentials are cleared. - Roll up per-phase results (
Get-TotlRunStatus) andPOST /v1/reportwith machine facts + timings.
Reporting is off by default; with it off the engine behaves exactly as before.
What is still NOT built yet¶
BitLocker enable + key escrow, config serving (/v1/config), the installer/build artifacts, RBAC
beyond the admin/engineer split, white-label, and licensing. Those are Phases 1–7 in BUILD-PLAN.md.