feat: advance login workflow and integration gates

This commit is contained in:
2026-09-26 01:18:57 +05:30
parent 71de5bc6cd
commit 9492dbd7c9
16 changed files with 1471 additions and 151 deletions

196
README.md
View File

@@ -1,148 +1,142 @@
# latchd
`latchd` is a Linux/PAM terminal login manager written in Rust. It presents a keyboard-first Ratatui login screen on a virtual terminal, authenticates through PAM, and starts an isolated TTY, X11, or Wayland session with an explicit per-user environment.
Want a TUI-based login manager because you are tired of GDM or Plasma Login? Latchd gives you a small, keyboard-first login screen on a Linux virtual terminal. It uses PAM for authentication and can start a TTY shell, an X11 desktop, or a Wayland desktop.
The Rust binary is the supported implementation. The original Go/cgo tree under `legacy/go/` is retained as a quarantined comparison artifact and is not built by the default targets.
Latchd is written in Rust. The old Go prototype is kept in `legacy/go/` for reference and is not built or shipped.
## Security model
## What it does
- The service requires root for real sessions and refuses to start from an existing desktop session.
- PAM work is serialized on one dedicated worker thread. PAM handles never cross that thread boundary; authentication tickets are opaque RAII-owned transactions.
- Password buffers are zeroized and are never placed in argv, logs, or the rendered UI.
- Session environments are rebuilt from an allowlist for every login. Shell commands are argv vectors; no configured command is passed through a shell.
- Desktop files and session scripts must be root-owned, regular, non-group-writable files. Account shells such as `false` and `nologin` are rejected.
- Xauthority cookies are generated in a `0600` file owned by the account, supplied to `xauth` through stdin, and removed by an RAII cleanup guard.
- Main, graphical-session, and Xorg logs are private (`0600`) and opened without following symlinks.
- Power actions are an explicit absolute-path allowlist and require confirmation. They are disabled in `--preview` mode.
- Finds normal login accounts through NSS.
- Shows password, account-expiry, locked-account, and faillock status without replacing PAM's decision.
- Lets you switch users and sessions without leaving the login screen.
- Finds X11 and Wayland desktop entries and can include a TTY shell.
- Starts sessions with a cleared, explicit environment and drops privileges before running the user process.
- Records user sessions with UTMPX when the platform provides it.
- Provides a Ratatui TUI with filtering, keyboard help, refresh, notifications, wallpaper, and reduced-motion support.
- Runs safely in preview mode for UI work and configuration checks.
The implementation intentionally does not provide an administrator unlock shortcut: account lockout and authorization remain the responsibility of the configured PAM policy.
The real login handoff still needs a Linux system with PAM, a virtual terminal, and the display/session services you want to use. The Docker tests cover builds, unit tests, rendering, configuration, and capability probes; they do not pretend to be a real VT or systemd-logind session.
## Build and test
## Build
Requirements: Rust 1.88 or newer, a Linux PAM development package, and a C toolchain. Debian/Ubuntu packages are `libpam0g-dev`, `libpam-modules`, and `build-essential`.
On Debian or Ubuntu, install the build dependencies first:
```bash
make deps
make build
make test # unit tests and rustdoc warnings
make lint # Clippy with warnings denied
make audit # RustSec dependency advisory scan
make docker-test # the same checks in a pinned Debian container
make package # deterministic install archive plus SHA-256
make release # all local gates, Docker, and package output
```sh
sudo apt-get install build-essential pkg-config libpam0g-dev libpam-modules
```
`make build` installs the release binary at `build/latchd`. The reproducible test image is defined by [Dockerfile.test](Dockerfile.test) and does not require `--privileged`.
Then run:
## Releases
```sh
make check
make docker-test
make integration-test
```
The supported release pipeline is GitLab CI in [`.gitlab-ci.yml`](.gitlab-ci.yml).
Every branch and merge request runs formatting, tests, Clippy, rustdoc, a
RustSec dependency audit, and the Docker suite. Docker-in-Docker requires a trusted privileged GitLab Runner with
the runner client-certificate volume configured; untrusted fork pipelines
should not be assigned to that runner. A SemVer tag
such as `v0.3.0` additionally builds a deterministic
Linux archive containing the binary, systemd unit, PAM policy, configuration,
license, changelog, build metadata, and a SHA-256 checksum, then publishes it
as a GitLab Release. The tag version must match the workspace version. CI
creates the GitLab Release only after both Rust and Docker verification pass.
`make build` writes the optimized binary to `build/latchd`. `make package` creates a deterministic archive and SHA-256 file in `dist/`.
Release builds use `SOURCE_DATE_EPOCH`, commit metadata, stripped symbols,
`opt-level = 3`, thin LTO, one codegen unit, no incremental state, and
`panic = "abort"`. Set `TARGET` or `DESTDIR` when invoking `make package` for a
controlled package build; `VERSION` must match the workspace version. The
legacy Go tree is physically isolated under `legacy/go/` and is never included
in release artifacts.
## Try the UI
## Installation
Preview mode does not call PAM, start a session, or run power actions:
```bash
```sh
make run-preview
# or
./build/latchd --preview --no-log
```
Use `LATCHD_ASCII=1` for terminals without reliable Unicode support, `NO_COLOR=1` for monochrome output, and `LATCHD_HIGH_CONTRAST=1` for a stronger focus palette.
## Install on a test machine
Do this on a disposable machine first. Keep another login path available while changing a display manager.
```sh
sudo make install
sudo systemctl enable latchd.service
```
Installation places the binary at `/usr/bin/latchd`, the configuration at `/etc/latchd/config.toml`, the PAM policy at `/etc/pam.d/latchd`, and the systemd unit at `/usr/lib/systemd/system/latchd.service`. The unit owns `/dev/tty2`; adjust `TTYPath`, `After`, and the `tty` configuration together if another VT is required.
This installs:
Keep the previous package/binary available while validating a deployment. `--preview` is a safe UI exercise that does not authenticate, create a user session, or execute power actions. `--no-tui` (also `--preflight`) validates configuration and exits without entering the login screen.
- `/usr/bin/latchd`
- `/etc/latchd/config.toml`
- `/etc/pam.d/latchd`
- `/usr/lib/systemd/system/latchd.service`
## Command line
```text
latchd [OPTIONS]
-c, --config <PATH> Configuration TOML (default /etc/latchd/config.toml)
-v, --variables <PATH> Optional TOML variable file
--tty <N> Override the virtual terminal number
--xsessions <DIR> Override the X11 desktop-entry directory
--wlsessions <DIR> Override the Wayland desktop-entry directory
--initial-path <PATH> Override the session PATH
--preview Draw the UI without authentication or session startup
--no-tui, --preflight Validate configuration and exit
--no-log Disable the main log
--show-config Print the key validated settings and exit
--build-info Print version, commit, and build epoch and exit
-V, --version Print the version
```
An explicit configuration or variables file used by a real (non-preview) service must be root-owned, regular, and not group/other writable. The default configuration is optional: when `/etc/latchd/config.toml` is absent, built-in defaults are used.
The default service owns `/dev/tty2`. Change the `tty` setting and the unit's `TTYPath` together when using another virtual terminal.
## Configuration
See [extra/config.toml](extra/config.toml) for the complete schema. Values may be substituted from a TOML variables file with `$NAME` syntax. Power entries contain an executable path and an argv array, never a shell command string.
The complete example is in [`extra/config.toml`](extra/config.toml). Important sections are:
Session discovery reads root-owned `.desktop` files from the configured X11 and Wayland directories and executable root-owned scripts from `/etc/latchd/wms` and `/etc/latchd/wayland`. Unsupported desktop-entry field codes are rejected rather than interpreted by a shell. A TTY shell is offered when no graphical sessions are available, or when `include_tty_shell = true`.
- `[accounts]` controls NSS discovery, the faillock helper, faillock policy, and `/etc/shadow` account-age checks.
- `[x11]` and `[wayland]` select desktop-entry and administrator script directories.
- `[environment_switcher]` controls session choices and the optional TTY shell.
- `[visual]` controls wallpaper size, animation, notifications, and reduced motion.
- `[power_controls]` contains absolute program paths and argv arrays. Commands are never passed through a shell.
When enabled, the account chooser discovers eligible NSS users (by default,
UID 1000 and above with a real home directory and login shell). `Ctrl-N` and
`Ctrl-P` cycle accounts, clear the password, and start a bounded advisory
`faillock` query. The displayed lockout state never overrides PAM: a PAM
conversation remains the only authorization decision. Set
`accounts.enumerate = false` when listing local accounts is not appropriate.
Configuration files used by a real root service must be root-owned and not writable by group or other users. Use a variables file for deployment-specific values instead of putting secrets in command arguments.
The `[visual]` section is TUI-only. It can load a root-owned PNG/JPEG as a
downsampled terminal-cell wallpaper, run a low-rate cellular-automata ambient
layer, and show short-lived notifications. `reduced_motion = true` disables
motion while preserving the static layout; `NO_COLOR=1` disables the image
layer and all color effects.
## TUI controls
The screen is designed for 80×24 and remains usable down to 60 columns. Below the supported minimum it reports the required size without drawing a partial frame.
## Controls
| Key | Action |
| --- | --- |
| Tab / Shift-Tab | Move focus |
| Up / Down | Move focus |
| Left / Right | Change session when the chooser is focused |
| Ctrl-N / Ctrl-P | Select the next / previous discovered account |
| Ctrl-B | Toggle the TUI-only wallpaper/automata background |
| Up / Down | Move focus or move in a filtered list |
| Left / Right | Change the selected session |
| `/` | Filter sessions or discovered accounts |
| Ctrl-R | Refresh account and session discovery |
| Ctrl-N / Ctrl-P | Select the next or previous account |
| Ctrl-B | Toggle wallpaper and ambient effects |
| Ctrl-M | Toggle reduced motion |
| Enter | Authenticate from the password field |
| Esc | Clear the password / cancel power confirmation |
| Esc | Clear the password or close a popup |
| `?` | Show keyboard help |
| Ctrl-C | Restore the terminal and quit |
| F1–F12 | Confirm and run a configured power action |
| F1–F12 | Confirm a configured power action |
`NO_COLOR=1` requests a monochrome semantic style, and `LATCHD_ASCII=1` selects ASCII session markers for terminals without reliable Unicode support.
The filter is smart-case: lowercase searches ignore case; a search containing uppercase characters is case-sensitive. The account-age and faillock messages are advisory. PAM remains the only authorization decision.
## Testing a deployment
The ordinary tests do not need a privileged container:
```sh
make check
make audit
make docker-test
make integration-test
make system-e2e
```
`make system-e2e` reports real host capabilities as `PASS`, `SKIP`, or `FAIL`. It does not call an account password or claim that a pseudo-terminal is a virtual terminal. `make system-e2e-strict` is for a prepared disposable VM with a test PAM policy, a real VT, systemd-logind, and the X11 or Wayland stack under test. See [`docs/e2e-testing.md`](docs/e2e-testing.md).
## Security notes
- PAM handles stay on one worker thread.
- Password buffers are zeroized and never placed in argv, logs, or the TUI.
- Session environments are rebuilt from an allowlist.
- Desktop files, scripts, binaries, homes, logs, and configuration sources are checked before use.
- Xauthority files and session logs use private permissions and cleanup guards.
- Power actions require confirmation and are disabled in preview mode.
- Account status and faillock output never bypass PAM or provide an administrator unlock shortcut.
## Project layout
```text
crates/
├── latchd-core/ validated config, accounts, sessions, and UTMPX accounting
├── latchd-auth/ PAM FFI, conversation callback, and transaction worker
└── latchd-tui/ Ratatui state machine, wallpaper, automata, and effects
rust/main.rs CLI, privilege checks, logging, and orchestration
extra/ example config, PAM policy, systemd unit, session directories
scripts/ Docker test entry point
legacy/
└── go/ non-production Go reference (explicit `make legacy-build` only)
crates/latchd-core/ configuration, account discovery, sessions, UTMPX
crates/latchd-auth/ PAM FFI and the authentication worker
crates/latchd-tui/ Ratatui state, rendering, filtering, and effects
rust/main.rs command-line entry point and service orchestration
extra/ example configuration, PAM policy, and systemd unit
scripts/ Docker and CLI/system capability tests
legacy/go/ non-production Go reference
```
## Platform support
## Releases
The first release targets Linux with PAM and systemd. BSD and non-PAM platforms are not claimed or tested. Real PAM/VT/X11 integration should be exercised on an isolated test host; the ordinary Docker suite covers compilation, unit tests, rendering tests, formatting, Clippy, and rustdoc without privileged container access.
GitLab CI runs formatting, tests, Clippy, rustdoc, RustSec auditing, CLI integration tests, capability probes, and the Docker suite. SemVer tags produce a reproducible Linux archive with checksums. Live VT, PAM-policy, X11, Wayland, and logind validation belongs in the isolated VM gate described above.
## License
Apache-2.0; see [LICENSE](LICENSE).
Apache-2.0. See [`LICENSE`](LICENSE).