feat: advance login workflow and integration gates
This commit is contained in:
196
README.md
196
README.md
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user