Managed desktop resources and lifecycle
Managed desktop resources and lifecycle
The desktop gateway uses its enrollment SQLite database for resource state, controller ownership, reconciliation attempts and expiry anchors. Identity remains in authenticated admission; database APIs accept trusted server-derived bindings. Resources never contain passwords, bearer grants or OpenBao leaf paths.
Resources and authenticated operations
Creation atomically inserts the resource and authorization policy. A unique `(workspace, subject, idempotency_key)` binds the canonical instance/incarnation, enrollment revision, optional existing resource references and rights. Matching retries return the same resource and original eight-hour authorization epoch; conflicting payloads fail. Revoked resources are never recreated by retry.
The opt-in mTLS router exposes:
| Route | Request | Result |
|---|---|---|
| `POST /api/v2/instances/{id}/desktop-sessions` | `{"idempotency_key":"request-a"}` | 200 scoped lifecycle metadata; reconciliation runs asynchronously when configured |
| `GET /api/v2/desktop-sessions/{id}` | Empty body | Current desired/observed/cleanup state, generation and expiry |
| `POST /api/v2/desktop-sessions/{id}/attach` | Empty body | 201 one-use attachment grant and its expiry; action is owned by the route |
| `POST /api/v2/desktop-sessions/{id}/reconnect` | Empty body | 201 one-use reconnect grant and its expiry; never extends the absolute expiry |
| `POST /api/v2/desktop-sessions/{id}/close` | `{"expected_generation":1,"sign_out":false}` | 200 access fence; 202 while requested sign-out cleanup remains pending |
Every operation verifies the current user session, workspace membership and its Create/Get/Attach/Reconnect/Revoke action. Get and close also enforce persisted policy rights. Creation rebinds current enrollment ownership/incarnation/revision in the commit. Writes carry delegation/leaf expiry and a five-second monotonic admission window; queued expired writes cannot commit. Uncertain timeouts require scoped retry/read.
Payloads reject caller ownership, routes, credentials, unverified cross-resource references, unknown/duplicate JSON fields, duplicate authorization and queries. Bodies are capped at 8192 bytes and one second; GET accepts no body. Responses use `Cache-Control: no-store`. Missing and foreign resources return generic 403, authority failures 503, invalid payloads 400 and conflicts 409.
Desired, observed and cleanup state are independent. New resources are active/unknown/none. Revocation fences access without asserting guest logout. Sign-out fences access and records pending cleanup. Legacy policy rebinding cannot reactivate a managed resource. Unknown/failed/provisioning, inactive desired state, fences and elapsed boot-clock deadlines deny target resolution. Resource-backed sessions cannot use the older raw TCP proxy to bypass controller ownership.
Controller ownership and worker leases
The controller table has one slot per desktop, independent of policy generation. After fresh authorization and one-use grant redemption, `run_controller` reserves that slot before starting the trusted adapter command. A competing attachment cannot start a worker. New worker incarnation IDs are permanently single-use.
Renewal rechecks identity and policy every 20 seconds and grants at most 60 seconds. The durable renewal CAS binds attachment, worker incarnation, original identity binding, sequence and prior expiry. Duplicate/out-of-order renewals fail. Fence is terminal and does not free the slot. Replacement requires an exact teardown ACK or the previous granted expiry plus five seconds; advancing policy generation alone never permits handoff.
Linux `CLOCK_BOOTTIME` anchors deadlines and includes suspend. The eight-hour resource deadline is anchored at creation; the original delegation deadline is anchored at controller acquisition. Renewals cannot move either anchor. Broker restart on the same host preserves them. Old resources fail closed after host reboot instead of reconstructing their epoch from potentially rolled-back wall time. Local workers require the same boot ID and an unshifted boot clock; remote workers are unsupported by this implementation.
Idle expiry is 15 minutes. Only the trusted adapter's accepted human keyboard or pointer events advance it; frames, pings and renewal do not. Confirmed transport teardown starts five-minute detach retention. Absolute, idle and detach expiry remain independent; the earliest wins. The reconciliation loop fences expired resources and requests guest cleanup. Disconnect, revoke and sign-out therefore retain distinct semantics.
A separate Python supervisor owns the foreground worker process group. Its bounded private-pipe protocol checks binding, sequence and authority-issued boot expiry. A received renewal never starts a fresh window at receipt. Fence/Close, pipe loss, expiry and invalid renewal terminate the worker. Broker SIGKILL closes the pipe; a stalled broker cannot stall the supervisor's timer. Teardown is ACKed only after the owned group is gone; otherwise the durable slot remains occupied until conservative expiry. Commands are constructed by a trusted adapter, never by HTTP payloads. This process-lifetime mechanism is not qualification of hostile native-code containment; the namespace/cgroup/egress worker boundary remains #856.
Guest reconciliation
Begin commits an opaque attempt before guest work starts. The attempt binds resource, owner/workspace, generation, enrollment revision, incarnation and purpose. An ordered attempt serial also fences stale guest-side mutations. Rechecking an allocated display advances policy generation before changing guest state. Consumption and observation commit atomically; stale, superseded, replayed or revoked callbacks cannot restore readiness. Reconciler claims serialize scheduled work across processes and expire conservatively after a lost broker.
The opt-in adapter reads an operator-owned config and scoped OpenBao token file. It uses verified HTTPS without redirects/proxies, KV-v2 CAS creation and a distinct credential-version UUID. Credentials travel through private pipes, not arguments or resource metadata. A pinned provisioning SSH route invokes the root-owned guest helper. The helper checks incarnation and attempt order, creates a dedicated non-sudo account with a private home, applies PAM credentials and durably records its mapping. Managed desktop accounts are denied SSH through their group.
Readiness requires a real certificate-pinned RDP login, a fresh xrdp successful-login record for that account, and its XFCE session. The verification connection detaches; its tracked display remains the resource's desktop. Cleanup locks the account, terminates its sessions/processes, verifies their absence and retires the OpenBao credential. Failure remains pending; only verified completion records stopped/confirmed and clears live resource references. Homes are retained. A missing or changed guest incarnation cannot be optimistically called cleaned up.
Configuration and remaining rollout gates
Existing desktop configuration remains opt-in and requires mTLS. Additional optional operator-owned paths are:
worker_supervisor = "/opt/agentic-sandbox/scripts/desktop-worker-supervisor.py"
guest_reconciler_adapter = "/opt/agentic-sandbox/scripts/desktop-reconcile.py"
guest_reconciler_config = "/etc/agentic-sandbox/desktop-guest.json"
Both reconciler paths are required together. The private JSON adapter config contains `ssh_host`, `ssh_port`, `ssh_user`, `ssh_key`, `known_hosts`, `guacd_host`, `guacd_port`, `rdp_fingerprint`, `bao_url`, `bao_ca`, `bao_token_file`, `bao_mount` and `bao_prefix`. Provisioning SSH host identity and RDP certificate fingerprint must be independently registered. The guest requires xrdp/xorgxrdp/XFCE and the root-owned helper/incarnation/SSH-denial files installed by managed seed generation. OpenBao must provide a KV-v2 mount and external workload-token renewal; scope the adapter to its configured credential prefix. No production realm/service/config was changed by qualification.
Controller/lease enforcement and real guest reconciliation are implemented and qualified as described in the test record. Discovery remains unsupported/unknown while attachment/browser integration, isolated native worker execution (#856), full adapter/surface/loadout acceptance (#842/#843/#851), privileged-harness isolation (#841), quotas, cross-resource reference validation and overall coverage gates remain open. These results do not claim the whole desktop feature or those dependent issues are complete.