Booted Ubuntu desktop SSH qualification (#853)

Booted Ubuntu desktop SSH qualification (#853)

The production seed transform and guest verifier passed a fresh Ubuntu 24.04.3 KVM boot and real OpenSSH account tests on 2026-09-14 UTC. The guest reported `openssh-server 1:9.6p1-3ubuntu13.19`, cloud-init completed successfully, and the generated `desktop-tunnel` account had UID 1001, only its own group and the `/usr/sbin/nologin` shell.

The result artifact records the guest facts, base content digest and each outcome. A counted guest-loopback marker on port 3389 substitutes for RDP. Exactly two connections reached it: the original instance/incarnation certificate and its replacement after atomic principal rotation. Foreign instances, stale incarnations, the legacy `agent` principal, bare keys, another destination, shell, SFTP and remote forwarding were denied. The old certificate was denied after rotation without an sshd reload.

This proves the generated account's new-connection SSH behavior on this Ubuntu image. It does not prove authenticated RDP, user delegation, trusted enrollment completion, revocation of existing channels, isolation from privileged runtime accounts, or Ubuntu 26.04 compatibility. The base SHA-512 is an observed local content pin, not a newly verified publisher signature.

Reproduce

Use a disposable Ubuntu cloud image and its independently recorded SHA-512. The fixture verifies that digest, uses a new overlay, generated test-only keys, a pinned guest host key and a host-loopback-only QEMU forwarding port. It does not attach shared host directories or modify host SSH configuration.

python3 scripts/desktop-comparison/vm_fixture.py start \
  --base /path/to/ubuntu-cloud.qcow2 --sha512 EXPECTED_SHA512 \
  --output /tmp/unique-desktop-ssh-fixture --desktop-ssh-policy
python3 scripts/desktop-comparison/vm_fixture.py status \
  --output /tmp/unique-desktop-ssh-fixture
# Continue only after this same VM reports provisioned=true.
python3 scripts/desktop-comparison/qualify-guest-ssh.py \
  --output /tmp/unique-desktop-ssh-fixture
python3 scripts/desktop-comparison/vm_fixture.py stop \
  --output /tmp/unique-desktop-ssh-fixture

The probe uses the shipped seed transform, rather than manually installing a second account policy. Its privileged `desktop` account exists only to control this disposable test VM; it is not the restricted `desktop-tunnel` account. The marker service has a five-minute runtime bound and is stopped in cleanup.

Failures found and corrected

The first VM reached a terminal cloud-init failure because socket-activated SSH had not yet created `/run/sshd`. The verifier now safely establishes and checks that root-owned directory, validates policy, then reloads or starts SSH.

The second VM completed in degraded status: cloud-init rejected `groups: []` and deprecated `sudo: false`. The transform now emits the valid empty group string and `sudo: null`. The guest's schema validator accepted the corrected seed before the third, fresh VM boot; that boot completed without the warnings. The fixture status check also now disables host SSH-agent identities and host configuration so unrelated keys cannot exhaust guest authentication attempts.

All three owned VMs were stopped through their verified QMP sockets; see cleanup evidence. Private disk/seed/serial evidence remains local and was not published. Five seed/verifier regression tests, Python compilation, documentation links and diff checks passed.

Final probe log SHA-256: `3e8d3d9883178289c078e49328710834b181d53e5acf2d0c85ace772feab12a7`. Result artifact SHA-256: `29f7c837fbce5b59b2d6769c12c05fd6116d7a1ccb297a60d68ee255a049148e`.

Dedicated inspection account

A subsequent fresh Ubuntu boot qualified the `desktop-inspect` account installed when enrollment configuration supplies its separate inspection key. Its root-owned SSH policy forces the fixed, isolated Python proof command and disables forwarding. The completion client now uses this account, not a runtime account or runtime SSH key.

The inspection result records that a caller command attempting to create a file and run `id` returned only the expected principal JSON; the file was absent. Forwarding through this account was denied. All original tunnel account cases and rotation also passed, with exactly two target accepts. The six seed/verifier and six HTTPS/mTLS completion-client tests passed, along with Python/shell syntax, provisioning dry-run and documentation checks. The HTTPS tests still use controlled guest proof; this does not qualify the combined live guest-to-real-admission path.

The first inspection seed booted successfully, then was stopped because the proof command was hardened to use the absolute cloud-init executable path. The final qualification used a fresh seed containing that final script. Both owned VMs were stopped and their process handles checked; see inspection cleanup. Private images, keys and serial logs remain local.

This removes enrollment inspection's dependency on runtime SSH access. It does not contain a guest-root harness: #841 explicitly requires an external trusted boundary and its prerequisite planning decisions remain required. No root-harness or authenticated RDP acceptance is claimed here.

Inspection probe log SHA-256: `cfa08e87ffbeb7ff294032da9a412023ee92ddd3abf3734807d26ad6f53d5a4b`. Inspection result SHA-256: `574edde14760156565e76623eb638522d1201951259f7bb455475072065149f0`.

Combined guest-to-admission qualification

The production completion client passed through the real guest inspection account, HTTPS/mTLS listener, admission coordinator, durable enrollment store and discovery endpoint. The identity authority is a controlled implementation; this is not deployed Keycloak/Bridge qualification. See the combined result for source-file digests, log digest and verified VM cleanup.

The test rejected a wrong host-key pin and stale guest incarnation before any gateway identity verification. A valid request enrolled revision 1 and discovery returned that incarnation with `supported=false`. Repeating a create, removing membership, and changing to a foreign workspace failed without changing the revision. Restoring authority and supplying revision 1 succeeded as revision 2. The explicit live test passed in 7.80 seconds; nine focused HTTP tests and 26 SSH gateway tests passed, each suite excluding its respective live fixture. Seven Python completion-client tests, formatting and documentation checks also passed.

Reproduce with a running, provisioned VM from the preceding fixture command:

AGENTIC_DESKTOP_GUEST_FIXTURE=/tmp/unique-desktop-ssh-fixture \
  cargo test --manifest-path management/Cargo.toml --lib \
  live_guest_completion_client -- --ignored --nocapture

The test first verifies ownership and readiness of that exact VM process. It creates a separate temporary admission server/database and private test-only TLS material, then runs the actual completion CLI. The VM remains caller-owned and must be stopped with the fixture stop command afterward. The recorded VM was stopped and its process handle verified terminal.

Three initial runs failed because the test CA and leaf shared the same default distinguished name. Python/OpenSSL rejected the chain with verification error 18 (OpenSSL error reference). Giving the fixture CA a distinct name fixed interoperability; neither hostname nor certificate verification was relaxed. CLI errors now include a safe exception category and numeric certificate-verification code, without raw errors, tokens, key paths, guest output or HTTP bodies.

The client now accepts an explicitly configured `guest_ssh_port`, defaulting to 22. The exact port is used for SSH, host-key pinning and enrollment, allowing the isolated QEMU fixture's forwarded endpoint without a trust exception. The overall coverage gate remains unmet; new Python branch coverage has not been measured.