The current user unit wins the boot race but still runs as the human whose Personal key the lock is meant to evict. Record the target souveraine-session principal, its narrow Wayland/PAM/data reach, and the fact that the rich shell is presentation—not release authority.
111 lines
5.7 KiB
Markdown
111 lines
5.7 KiB
Markdown
# souveraine-sessiond
|
|
|
|
User-tier session authority daemon. Owns the ext-session-lock-v1 surface
|
|
and the unlock verb — the one thing in the system that can release the
|
|
session lock. The shell is a *client* of this authority, not the authority
|
|
itself.
|
|
|
|
Source: `src/bin/souveraine-sessiond.rs`, `src/sessiond/{protocol,server,lock,draw,auth}.rs`.
|
|
Companion design: `docs/session-authority-boot-order.md` (Phase C),
|
|
`docs/SESSION-AUTHORITY-DOCTRINE.md` §11.
|
|
|
|
## What it owns
|
|
|
|
- The ext-session-lock-v1 client that holds the session locked at boot,
|
|
before the shell exists, and after the shell dies.
|
|
- The PAM conversation for unlock — hand-rolled libpam FFI (`auth.rs`),
|
|
no wrapper crate (fewer deps for on-device builds).
|
|
- A Unix socket at `$XDG_RUNTIME_DIR/souveraine/sessiond.sock`, same-user
|
|
today (`0600`), machined-shaped JSON-lines protocol.
|
|
|
|
## The zero-gap handoff (load-bearing)
|
|
|
|
A lock client that drops its Wayland connection **without unlocking** leaves
|
|
the compositor holding the session locked. With Hyprland's
|
|
`misc:allow_session_lock_restore`, the next client inherits that locked
|
|
state. sessiond uses this for two transitions, both fail-closed:
|
|
|
|
1. **Boot handoff.** sessiond takes the lock first (splash dissolves onto a
|
|
locked panel), quickshell starts ~3s later, the bridge calls
|
|
`shell_ready`, sessiond drops its connection, the shell's lock inherits
|
|
— no unlocked instant between them.
|
|
2. **Crash retake.** The bridge connection is the shell's heartbeat. EOF on
|
|
that connection = shell dead. sessiond retakes the lock
|
|
(`allow_session_lock_restore`) and renders its own spartan PIN surface.
|
|
A shell crash while locked is no longer an ssh-only recovery.
|
|
|
|
The retake is self-correcting: if sessiond tries to lock against a *live*
|
|
shell lock, the compositor yields Finished/Denied — no deadlock.
|
|
|
|
## What crosses the boundary
|
|
|
|
JSON-lines over the socket (`protocol.rs`). Request ops: `status`,
|
|
`shell_ready`, `locked_ack`, `lock`. Phases: `holding`, `awaiting_shell_lock`,
|
|
`released`, `idle`. The `shell_ready` connection *is* the heartbeat; its EOF
|
|
is the retake trigger. Responses are guarded `ok`/`reason` shaped, same
|
|
convention as machined.
|
|
|
|
Unlock is never an RPC verb. The only path to unlock is the PAM
|
|
conversation on sessiond's own lock surface.
|
|
|
|
## Today's security posture
|
|
|
|
- **Lock-before-shell.** No desktop surface (NM dialog, bar, background)
|
|
can precede the lock at boot — the boot fail-open hole that motivated
|
|
this daemon.
|
|
- **Lock-past-shell-death.** Shell crash while locked retakes to a PIN, not
|
|
a locked brick.
|
|
- **PAM is the unlock path.** `auth include login` (same credentials as
|
|
console login), unprivileged via `unix_chkpwd`. No bypass verb.
|
|
- **Fail-closed default.** Default behavior on start is to lock; `--no-initial-lock`
|
|
is the explicit opt-out for dev.
|
|
|
|
Sessiond is still packaged as a user unit with no `User=` directive. It
|
|
therefore runs as the human graphical-session owner. That ordering closes the
|
|
boot visibility hole but does not close ownership of the lockscreen. The
|
|
target principal is `souveraine-session`, with only the Wayland lock, PAM,
|
|
Ambient, and Revealable reach described in
|
|
`../../../souveraine/saf/authority/01-session.md`.
|
|
|
|
## Audit gaps (toward the RedFlag bar)
|
|
|
|
sessiond is the component that most directly maps onto RedFlag's "capability
|
|
token over a resolved closure, signed by an authority, verified by the
|
|
executor" shape (SESSION-AUTHORITY-DOCTRINE §10), because *unlock is
|
|
exactly the kind of verb that should require a token*. Today it does not:
|
|
|
|
- **`shell_ready` has no caller attestation.** Any same-uid process that
|
|
can reach the socket can call `shell_ready` and induce a handoff, or call
|
|
`lock`. RedFlag's analog: the privileged executor verifies the token's
|
|
signature, `AgentID`, `Operation`, expiry, and closure hash *before*
|
|
acting — it does not trust "a process asked." sessiond trusts the
|
|
heartbeat's mere existence. The fix shape: the shell presents a
|
|
machined-signed capability token (operation=`session_handoff`, expiry
|
|
short, closure = the shell binary hash) that sessiond verifies against
|
|
the pinned machine pubkey before honoring `shell_ready`.
|
|
- **Inhibit / step-up verbs (§6/§10) are stubs.** sessiond is the natural
|
|
home for the `souveraine-stepup` PAM service and the portal Inhibit
|
|
backend, and for verifying capability tokens on session verbs. These are
|
|
named in the doctrine but not implemented; the socket protocol reserves
|
|
the space.
|
|
- **`SO_PEERCRED` not yet read.** machined logs uid/gid/pid on every
|
|
request; sessiond does not yet. Same-user-only today (`server.rs`
|
|
comment: "widens to a group grant deliberately when agents get accounts").
|
|
Per-agent UIDs and worker processes turn this into kernel-backed caller
|
|
identity — the substrate of the capability gate. The account, admission,
|
|
and health contract is
|
|
`../../../souveraine/saf/identity/02-agent-principal.md`; creating accounts
|
|
without moving execution out of the human-owned server is not sufficient.
|
|
- **Binary integrity.** As with the other two and RedFlag SEC-022: no
|
|
self/peer hash check. DAC is the standing guarantee. When the capability
|
|
token lands, the token's closure should carry sessiond's *own* expected
|
|
hash so a swapped binary cannot present a valid old token — RedFlag's
|
|
`ClosureEntry.SHA256` shape pointed inward.
|
|
- **eBPF (KERN-001 shape).** The lock gate's kernel-level stop is
|
|
ext-session-lock-v1 itself (a Wayland protocol the compositor enforces),
|
|
which is already a kernel-adjacent guarantee stronger than RedFlag's
|
|
eBPF-scaffold-for-execve. So sessiond's "defense-in-depth kernel stop"
|
|
is largely already present via the compositor; the gap is the
|
|
*token gate* on top, not the kernel enforcement underneath.
|
|
|
|
See [audit-status.md](audit-status.md) for priority.
|