Files
SilverMetal/windows/welcome-app-spec.md
sysadmin 4f3e25e816
All checks were successful
Build SilverMetal Enhanced - Windows ISO / build (pull_request) Successful in 4m37s
docs(welcome): record VM e2e validation + 3 bugs found/fixed + BitLocker-PIN follow-up
2026-06-09 11:02:52 +01:00

128 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SilverOS Welcome — Specification (v1)
> **Status**: design approved 2026-06-09. Part of **SilverMetal Enhanced — Windows**.
> First-logon onboarding app that turns a hardened image into a personalised device:
> the user picks a **flavour**, creates their **account + BitLocker PIN**, and the app
> applies the chosen configuration by orchestrating the existing §AH hardening modules.
Bound by [`hardening-spec.md`](hardening-spec.md), [`iso-builder.md`](iso-builder.md), and the SilverMetal [`../docs/design-principles.md`](../docs/design-principles.md).
## 1. Purpose & positioning
Today the image applies **one fixed** hardening config at first boot (`SetupComplete.cmd` → §AH modules). The Welcome app makes "SilverMetal Enhanced — Windows" a **selectable platform**: a friendly first-logon wizard where the buyer chooses *what the device is for* (a **flavour** = config profile + app set), sets up their own account, and the device configures itself accordingly.
It also resolves two real gaps found during VM validation:
- **No throwaway credential in the public repo** — the committed account becomes a locked **bootstrap** account that **self-destructs** after onboarding.
- **BitLocker PIN enrollment** — collected interactively in the wizard (BitLocker TPM+PIN can't be fully automated; this is the right place for it).
**v1 = interactive auto-launch only.** A fully-silent pre-baked SKU mode is a later phase (§8).
## 2. Architecture & components
A **Blazor Hybrid (.NET MAUI + WebView2)** desktop app, published **self-contained** (bundled runtime) and baked into the image. Reuses SilverSHELL components + the SilverDESK "Welcome" wizard aesthetic (Mercury). Four well-bounded units:
| Unit | Responsibility | Depends on |
|---|---|---|
| **Welcome.Shell** | MAUI host + wizard UI (the steps in §3). What the user sees. | Welcome.Flavours, Welcome.Apply |
| **Welcome.Flavours** | Load + validate flavour manifests from `windows/flavours/*.json` (baked into the image). | manifest schema |
| **Welcome.Apply** | Orchestrator: run the §AH PowerShell modules per the manifest, install the app set, create the real account, enrol BitLocker, tear down the bootstrap. | PowerShell modules, Windows account/BitLocker APIs |
| **Welcome.Bootstrap** | Answer-file / AutoLogon / RunOnce wiring that auto-logs-in and launches the app. | autounattend, build.ps1 |
Each unit is independently testable: Welcome.Apply can run headless against a flavour manifest (no UI); Welcome.Flavours validates manifests without applying; the Shell can be run with a mock applier.
## 3. Boot → onboard → handoff flow (bootstrap lifecycle)
1. **Image** ships a **locked bootstrap admin account** (`sm-bootstrap`) with **AutoLogon (LogonCount=1)** and a RunOnce/custom-shell that launches the Welcome app in a kiosk-ish session (no taskbar/Explorer escape). The §AH modules **no longer run from `SetupComplete`**`SetupComplete` only stages the payload; the Welcome app applies it.
2. **First logon** (auto, as `sm-bootstrap`) → Welcome app launches. Wizard steps:
- **Welcome** — brand intro.
- **What's this device for?** — persona → selects a **flavour** (cards: Privacy-Max / Daily-Driver / Journalist / Developer).
- **Create your account** — daily username + password (a **Standard User**), plus a separate **device-administrator password** (for the `SilverOS Admin` elevation account).
- **Secure this device** — set a **BitLocker PIN** (≥ flavour-min).
- **Preferences** — a few toggles (e.g. telemetry-floor confirmation, browser default, timezone).
- **Apply** — progress UI while Welcome.Apply runs.
- **Done** — "restart to finish."
3. **Apply** (Welcome.Apply, elevated):
- run the flavour's **hardening modules** (per manifest) + **app-set** install;
- **create the daily Standard account** + the separate **`SilverOS Admin`** account;
- **enrol BitLocker TPM+PIN** with the entered PIN;
- remove AutoLogon + the RunOnce.
4. **Self-destruct + reboot** — delete the `sm-bootstrap` account, then **reboot** into the real account (also activates VBS/Credential Guard, which need a reboot).
## 4. Flavour manifest model
A flavour is a JSON file in `windows/flavours/` baked into the image:
```jsonc
{
"id": "privacy-max",
"label": "Privacy-Max",
"description": "Maximum lockdown. Everything Microsoft exposes, turned to its tightest.",
"hardening": {
"modules": ["00","01","02","03","04","05","06","07"], // which §A-H modules
"params": { "wdac": "enforce", "telemetry": "security" } // per-module overrides
},
"appSet": ["SilverBrowser","SilverVPN","SilverKeys","SilverDuress"],
"settings": { "defaultBrowser": "SilverBrowser", "autoLock": 120 }
}
```
**Welcome.Apply orchestrates the existing PowerShell §AH modules** per `hardening.modules`/`params` — PowerShell stays the single source of truth for *what hardening does* (also reusable headless for the future silent SKU). *(Rejected alternative: re-implement hardening in a .NET library — cleaner for an all-.NET app but duplicates the proven logic.)* The Shell renders one card per manifest; adding a flavour = adding a JSON file.
**Hardened baseline applies to ALL flavours** — no flavour is unhardened. Every flavour runs the core §AH baseline (telemetry floor, BitLocker+PIN, VBS/HVCI, firewall default-deny, Defender/ASR, etc.); flavours only **tune strictness above that baseline** and choose the app set. There is no "off."
**Starter flavours (v1):** **Daily-Driver** *(the **default / recommended** — balanced: full baseline, WDAC in audit, everyday app set)*, **Privacy-Max** *(strictest: WDAC enforce, tightest toggles, removable-storage controls — opt-in, not default)*, **Journalist** *(privacy + duress/comms emphasis)*, **Developer** *(baseline + dev tooling allowances, e.g. WDAC audit + dev-signed allowances)*. The wizard pre-selects **Daily-Driver**.
## 5. Account & BitLocker integration
- The user's **daily account is a Standard User** (least-privilege by default — a real hardening win, and the SilverLABS default). Elevation uses a **separate local Administrator account** (`SilverOS Admin`) created alongside it; the user sets the admin password in the account step (distinct from their daily password). *(Single-admin/"personal-device owner" remains available as a flavour/pref option, but is not the default.)*
- **BitLocker TPM+PIN** is enrolled with the user's PIN during Apply (`Enable-BitLocker -TpmAndPinProtector`), satisfying module C's interactive step. Recovery key stored offline / shown to the user (never cloud-escrowed) per `hardening-spec.md` §C.
- The **bootstrap account** is admin only so the app can run hardening + manage accounts; it is deleted in step 4 so no standing extra admin remains.
## 6. Build & repo integration
- New **`windows/welcome/`** (MAUI app source) + **`windows/flavours/*.json`**.
- **`build.ps1`** gains a stage that drops the **published Welcome app + flavours** into the image (`/Program Files/SilverMetal/Welcome` + a flavours dir), and wires the bootstrap (AutoLogon + RunOnce) via the answer file / offline registry.
- The **answer file** switches from "create `silvermetal`/`open sesame`" to "create **`sm-bootstrap`** + AutoLogon + launch Welcome." `SetupComplete.cmd` stops running the §AH modules directly (the Welcome app runs them).
- A repo switch (**`vars.SILVERMETAL_WELCOME_ENABLED`**, default on) controls whether the build wires onboarding or falls back to today's direct-hardening behaviour (so the current validated path remains available).
## 7. Testing
- **Welcome.Flavours**: unit tests validate every shipped manifest against the schema.
- **Welcome.Apply**: headless run against each flavour on the VM (reuses the SLAB01 VM-test harness) → assert the firstboot/apply log shows the expected modules + the real account + BitLocker protector + bootstrap removed.
- **Welcome.Shell**: bUnit-style component tests for step navigation/validation; a manual VM pass for the full wizard.
- The existing **offline ISO assertions** extend to check the Welcome app + flavours are baked.
## 8. v1 scope
**In:** interactive auto-launch onboarding; persona→flavour; account + BitLocker-PIN creation; prefs; apply-via-PowerShell-modules; bootstrap self-destruct; 34 starter flavours; build/answer-file wiring + `WELCOME_ENABLED` switch.
**Out (later phases):**
- **Silent pre-baked mode** (apply a baked flavour + pre-set account with no wizard — the "we did everything for you" SKU).
- **SilverDESK / fleet enrollment** role.
- **Shared flavour model with the Linux line.**
- **Real Stack app installs** — the app-set installer is wired, but Stack entries remain stubs until native Windows Stack builds exist (M4).
- **Single-admin / "personal-device owner"** account model as a selectable option (Standard-user daily + separate admin is the v1 default).
## 9. Open questions / risks
- **WebView2 + .NET runtime in the image** — confirm WebView2 Evergreen is present on IoT Enterprise LTSC (or bundle the fixed-version runtime); self-contained publish covers .NET.
- **Kiosk lock-down of the bootstrap session** — ensure the user can't escape the wizard to a privileged shell before the real account exists.
- **Apply failure handling** — if a module fails mid-apply, the wizard must surface it and leave the device in a recoverable state (don't delete bootstrap until apply + account creation succeed).
- **Admin-credential UX** — confirm the daily/admin two-password step is clear (vs. generating + showing the admin secret once); ensure password strength rules on both.
## 10. VM end-to-end validation — VALIDATED 2026-06-09
Built the packed ISO via CI (`build-iso-windows.yaml`, all green: 22 unit/bUnit tests + ISO bake + offline payload assertions) and ran it on SLAB01 VM 102 (UEFI + SecureBoot pre-enrolled keys + vTPM 2.0). **Verified end-to-end:**
- Boots UEFI/SecureBoot; forced-legacy hands-off install onto a blank disk; OOBE auto-completes.
- `sm-bootstrap` auto-logs in; the Welcome app **auto-launches elevated** and renders the wizard (Mercury theme, bundled fonts).
- All four flavours load from the baked `flavours/*.json`; **Daily-Driver** pre-selected.
- Drove the full wizard: Welcome → Flavour → Account (created `alice` + admin password + BitLocker PIN) → Prefs → Apply → "All Done!".
- Post-apply, offline + on-login assertions: **`alice` created** (Standard daily user) and **`SilverOS Admin` created** (separate elevation account) both appear on the login screen; **`sm-bootstrap` removed**; **no auto-login** (AutoAdminLogon disabled → teardown ran); **BitLocker enabled** (volume encrypted, `-FVE-FS-`). Account creation + bootstrap teardown require elevation, confirming the app runs elevated.
**Three deployment bugs found + fixed during validation** (only a real run surfaced them): (1) WebView2 user-data folder defaulted to non-writable `Program Files` → redirected to LocalAppData; (2) FirstLogonCommands run un-elevated → bake `ConsentPromptBehaviorAdmin=0` offline + launch via `Start-Process -Verb RunAs`; (3) AccountStep `Next` never enabled because the wizard host didn't re-render on child validity change → added an `OnValidityChanged` callback. Also a separate CI fix: bUnit tests must reference a Razor Class Library, not the MAUI app assembly (else `WindowsAppRuntime` `DllNotFound` on the clean runner).
**Follow-ups (not v1-pipeline blockers):**
- **BitLocker PIN not enforced** — the device booted with no pre-boot PIN prompt (TPM-only auto-unlock). The intended TPM+PIN protector did not take effect, most likely because the FVE "require startup PIN" policy (`UseAdvancedStartup`/`UseTPMPIN`) wasn't set before `Enable-BitLocker`. The user's PIN currently provides no boot protection — fix before shipping.
- **Apply services don't check PowerShell exit codes** — `AccountService`/`BitLockerService`/`BootstrapService` run `powershell.exe` but ignore `ProcessResult.ExitCode`, so silent failures (like the BitLocker PIN degradation) go unsurfaced. They should check exit codes and fail/log loudly.