Skip to content

Nerve 0.0.6

Latest

Choose a tag to compare

@github-actions github-actions released this 22 Oct 20:24
39e9f2a

The desktop app

  • nerve-node-desktop is the node with a window, for Linux x86_64 (GNOME or KDE Plasma, Wayland or X11; glibc 2.35 or newer) and Windows x86_64 (#10). It is one program: started with arguments it is nerve-node, started without any it opens the window. The window runs the CLI for every action, so it has the CLI's checks and audit records. See docs/user/desktop.md.
  • Its pages: overview (the node's state, panic and unlock, and the service's start, stop and restart); centers with pairing (also from a nerve:// link), pause, resume and key rotation; relay; permissions (mode, folders, rules, capability switches, sudo); audit; log; settings (config, proxy, backup and restore); help; and the About dialog. A first-run setup names the node and pairs its first center. Light and dark follow the system, or the choice saved in the app.
  • The audit page has allow, allow as wildcard, deny and deny as wildcard on each entry, which write the same rule as nerve-node rule add --action (#23).
  • The downloaded file installs itself: the program, the node as a service under its own user (as nerve-node install), the menu or Start menu entry with its icon, and the nerve:// handler, after the system's question for the rights (pkexec, UAC). Started again, it offers to open, update or uninstall Nerve. Settings update it (from the latest release, checked against SHA256SUMS, or a file) and uninstall it, keeping or deleting the node's data. On Windows it is in Apps & features, whose Uninstall opens the app's uninstall.
  • A node installed with nerve-node is taken over: the service runs the desktop app's program, so there is one program to update. On Linux /usr/local/bin/nerve-node becomes a link to it, so the terminal command keeps working (--version through it says nerve-node-desktop), and uninstall removes the link. On Windows the old nerve-node.exe is removed, as the app has no console; for a terminal, download nerve-node-x86_64-windows.exe from the release. A plain nerve-node uninstall after the takeover removes the app too.
  • A change the running service takes only when it starts again (on Windows every change but a folder's, a pairing too) makes the app restart the service, two seconds after the last one or as the app closes, and say so. A restore stops the service and starts it again.
  • On Windows a standard user can install and use the app with an administrator's name and password in the UAC prompt; the commands still run as that user.
  • On Linux the app says what the desktop lacks instead of failing silently: no display, no pkexec or polkit agent, no desktop portal for the file dialogs, no update-desktop-database; without OpenGL it draws in software. docs/user/desktop.md lists what each desktop needs.
  • The release has nerve-node-desktop-x86_64-linux and nerve-node-desktop-x86_64-windows.exe, with their checksums in SHA256SUMS.
  • The NOTICE file holds the licence of the Lucide icons, which the app and the center's web UI use. The app is built with Slint under its Royalty-free license 2.0.
  • Rights once, like an installed program: the user who installs becomes the node's owner, and opens the app, changes settings, pairs, edits rules and starts, stops and restarts the service without a question. The administrator's password is asked only for update, uninstall, sudo on and the machine.* settings that pick an account or program (also a restore that would change them), each time for that action.
  • The look: colour with a meaning (the node's state, the page's icon, the open page), text that reads well in light and dark (WCAG AA), short texts. A busy button shows a spinner in place of its icon and keeps its size. Light and dark are chosen at the foot of the sidebar, and a change comes in one step, with no light frame between two dark ones. Keys: Ctrl+1 … Ctrl+8 for the pages, Ctrl+R and F5 read the page again, Esc and Enter in dialogs, Tab through every control. A click on the open page's tab reads nothing again.
  • Settings has tabs (General, Keys, Network, Limits, Backup, App, Advanced), with a control for each setting an owner changes, the limits per command too; Any setting is under Advanced. Network keeps the proxy: the relay has a page of its own.
  • The Relay page has both sides of a relay: this node through one, from one pasted link, and this machine as one, with its nodes, their links, centers, limits and tunnels now. Pairing checks the link first (the center answers, its pin, the code still valid) and offers Pair only when it can work. The first-run steps have Back.
  • Permissions puts the rules that count in the center's mode first (in allowlist mode the deny rules as its exceptions), keeps the sudo list with sudo, and lets every rule be edited in place. It says who runs the commands, offers Run them as you in allowlist mode, lists nerve-run's built-in folders, and shows a folder grant that failed. A one-line warning says when commands run without their limits.
  • A list longer than some eight rows (rules, folders, capabilities, a relay's nodes and tunnels, the Overview's centers) scrolls in a box of its own with a scroll bar in sight. Help has the keys and the command reference, by topic or A to Z and filtered, each command opening on its arguments, an example and the place in the app; each part, topic and command opens and closes on its own. Clear on Audit and Log asks first. Audit filters by decision (allowed, refused, settings changes), center, action and words in the target and reason, combined and counted, newest or oldest first.
  • The window has the app's icon in GNOME's dash, KDE's task manager and the Windows taskbar. With System, the app is dark when GNOME (also Zorin OS, by its GTK theme) or KDE is. The downloaded file opened while the app is open brings the window up and offers its update.
  • Animations of 100 to 200 ms: pages and settings tabs fade in, dialogs rise in and their veil lifts as they close, the note in the corner comes and goes, Help's sections slide open, the open page's mark grows, buttons and switches change smoothly. None delays a click or a key. They are off, and the loading dots and spinners still, when the desktop asks for less motion: GNOME's Reduce Animation, KDE's animation speed Instant, Windows' Animation effects off, or the desktop portal's reduced-motion (#136).

The guard against changing the node (#143)

  • A new pairing starts with deny rules, the guard, that keep its commands off the node itself: a command that runs the node's programs (not one that only names them, like cargo build -p nerve-node), one that names its socket or its config and state folders, and a write into those folders. rule guard adds them to a pairing that lacks them, a pairing made before 0.0.6 included, and the desktop app offers that on Permissions. They catch an agent's mistake, not a name built at run time; the real protection is nerve-run, the separate account for commands on Linux.
  • The guard also refuses a command that stops, restarts, disables or kills the node's service or process by its name: systemctl stop, restart, disable, kill or mask nerve-node, service nerve-node stop or restart, and pkill or killall on nerve-node on Linux, and sc stop, config or delete, net stop, Stop-Service, Restart-Service, Set-Service, Remove-Service, Stop-Process -Name and taskkill /im nerve-node.exe (or nerve-node-desktop.exe) on Windows. The Windows commands match in any case, a line break after a command or a name ends it like a space, and sudo counts at a command's start only, so a quoted mention such as git commit -m "run sudo systemctl stop nerve-node" runs. Reading stays allowed (systemctl status, journalctl -u nerve-node, pgrep, sc query, Get-Service nerve-node). A command after then, do, else, {, !, time or nohup counts as one that starts a line, and an indent of up to 16 characters is read. A refusal's reason and the settings log write the rule's tabs and line breaks as \t, \r and \n, and config add and the app refuse the listed text of such a rule as a new rule. The rules that name the node's programs, sockets and folders end a name at a tab or line break too, and see a command after one. A rule with a tab or line break is listed with \t, \r and \n written out (rule ls, the app's rule lists), and rule rm and rule edit take it so.

Folder mode as an account of its own (#142)

  • On the node installed as a service on Linux, folder mode commands, and allowlist mode commands unless mode set allowlist --as-owner, run as nerve-run, an account install makes with a home of its own (/var/lib/nerve-run), no login and no other groups; uninstall removes it. In 0.0.5 the commands of every mode ran as one account that install took or setup chose (machine.user). nerve-run is not the owner: the owner socket refuses it, and the owner's session programs (service manager, session bus, tmux) take no requests from it, on every kernel. user mode runs as the owner, the account that ran install. There is no other account to choose: setup --create-user, setup's question for the account and machine.user on Linux (it stays for Windows) are gone.
  • The folders you give a pairing become POSIX ACL entries for nerve-run (the folder and everything below it, default entries so that what it makes stays yours, a pass-through entry on your closed folders above it), taken off again when the grant goes and by uninstall (which also takes them off what nerve-run made there, and gives what it made to the owner before its account goes, but for a file with more than one name, which may lie outside the folders and which it counts). A default ACL of the owner's own is left as it was, with an entry the owner gave itself there, and a folder that had none has none again. An inner grant that goes is counted off by a walk of the grant around it only where that walk saw all of it (no other file system, no folder closed to the owner); else it stays stale until its own walk goes through. Only pairings whose commands run as nerve-run give it folders. The node walks the folders by descriptor and follows no link, also one a command puts in place of a folder meanwhile. /tmp, /var/tmp and its home are always its own; HOME and the XDG_* folders point there, and git trusts the repositories it reaches. Landlock holds each command to its own pairing's folders on top. A folder grant that failed is named by folder add, folder set and status, also after a restart, and tried again. Access nerve-run should have lost but keeps stops all its commands until it is off; folder rm, status (grant_stale with --json on the folder commands) and the app (nerve-run keeps access it should have lost) say so on every pairing of nerve-run.
  • A new pairing starts in folder mode. In 0.0.5 it started in allowlist mode with no rule, so every call was refused until you allowed something.
  • Where the kernel has no Landlock, the installed node runs folder and allowlist mode commands as nerve-run all the same, held back by its account alone; status says as nerve-run, without Landlock, and the app says Without Landlock. In 0.0.5 folder mode ran no command there, and a node you run yourself still runs none.
  • Without Landlock, install, status (home_warning in --json) and the app warn that nerve-run can read every file in the owner's home that other accounts may read: when the home is open to them (mode 755, 711), or when a folder given to it lies in the home, which it passes through. For an open home the warning lasts until the owner closes it (chmod 750 ~); for a folder in the home it lasts as long as that folder is given, and only folders outside the home or a kernel with Landlock end it, while umask 027 only narrows what nerve-run can read. With Landlock nothing is said.
  • A sudo command the sudo list allows runs as root in every mode, with sudo on: for a pairing whose commands run as the owner through the owner's sudo and password, as in 0.0.5; for one whose commands run as nerve-run the node starts it as root itself, without the owner's password and without the sudo program, so the sudo list is nerve-run's only way to root. It runs in /bin/sh with the allowed line alone (a leading -n or -- dropped, any other option of sudo refused with the reason) and a fixed environment, in its working folder, which the node opens as the owner without following a link and enters before the command becomes root. sudo on asks for the owner's own sudo password, which shows that the owner, who lets the sudo list run as root, may run commands as root here; in 0.0.5 it asked for the password of the account the commands ran as.
  • The node's keys may be in the keyring on every Linux kernel (#135). In 0.0.5 they stayed in files below Landlock ABI 9 (Linux 7.1): config set machine.secret_storage keyring was refused and setup did not ask. On the installed node the keyring is nerve's and no command runs as nerve, so none reaches it. A node you run yourself runs no folder mode command while its keys are in the keyring, on every kernel; 0.0.5 ran them on Linux 7.1 or newer, with Landlock keeping them off the session bus, which no longer holds them.
  • install names the owner in the unit and suggests center add and folder add +rwx next. update leaves the unit as it was, so a node installed with 0.0.5 gets nerve-run, its owner and the new unit from sudo nerve-node install run again from the owner's own account: after update alone it runs no command for a center.
  • On Linux the owner runs setup without sudo: its questions take the owner's input through the owner socket, and its one step that needs root, installing a keyring for the node's user, takes root by the service's own rights, only after a yes at a terminal. That step runs the package manager and loginctl by their full paths from the system's folders, with a fixed environment, in /, without input, and installs GNOME Keyring without what it only recommends. Setup's folder step gives +rwx on Enter.
  • The keyring setup installs for the node's user is asked in that user's own session: the probe runs with a fixed environment (HOME, USER, XDG_RUNTIME_DIR=/run/user/<uid>, the bus in it, the system's PATH) and nothing of the caller's, also when the owner's setup runs it as the node's user, and setup waits until the user's service manager answers. A session whose service manager got another user's XDG_RUNTIME_DIR, bus or XDG folders from the system-wide environment of the login (/etc/environment, as on a GitHub runner), which kept GNOME Keyring from starting ("Failed to activate service 'org.freedesktop.secrets': timed out") or from finding its keyrings, is set right (the user's own folders are set, not unset), and its bus and keyring started again, whether the keyring's unit is gnome-keyring-daemon.service or, in GNOME Keyring 40 (Ubuntu 22.04), gnome-keyring.service. It stays right: setup writes /etc/systemd/user-environment-generators/99-nerve-session, which gives the session of the node's user its own folders at every start of its manager (uninstall removes it), and the installed node does the same repair when it starts with its keys in the keyring (both only for the node's own user nerve, never for a node you run as yourself or a session with a desktop) (#135).

Root as the owner (#151)

  • install run by root itself (a server where you log in as root) asks at a terminal whether root is to be the node's owner, after saying what it means; the answer is no unless you type y or yes, and without a terminal (piped input included) nothing is asked. --owner=root --accept-root-owner says yes without a terminal; either flag alone, or the flags through sudo by another account, is refused, and on Windows both are. The question is asked only where input and output are a terminal and not with --json; elsewhere the answer is no at once, and install says so on stderr. A later install by root keeps root as the owner without asking; install --owner=none leaves the node without an owner; update keeps it. In 0.0.5 an install run by root made root the account commands run as, with a warning.
  • When install changes the owner (root to none, alice to root, root to alice), it first stops the service and takes nerve-run's ACL entries off every folder the earlier owner gave it, as uninstall does, once nothing else in the install can refuse; the node grants them again for the new owner, and without an owner (--owner=none) it gives nerve-run no folders, which install says. Where not all come off, the owner stays, its node walks every grant again, and the service runs again; so it does after any later failure. Entries an earlier owner's grants left are kept in the record and walked again at each sync, and uninstall takes them off. The question and the install that makes root the owner name the centers that have the settings grant already.
  • With root as the owner, user mode and allowlist mode with --as-owner run commands as root, which can do everything on the machine, Nerve and its keys included; folder mode and allowlist mode run as nerve-run, as on any node. nerve, nerve-run, another account with uid 0 and an account that reaches the node's folders are never the owner.
  • status says so ("owner_is_root" with --json), and so do the desktop app's Overview, Permissions (user mode and the chip say as root, a banner Its commands run as root) and Settings. Root gets no polkit files, and no center turns sudo on while root is the owner (sudo -v checks no password of root's). A center with the settings grant can still set its own mode to user and so run as root: center grant settings write and the desktop app warn of that while root is the owner.

Commands

  • --json on the read commands (status, center ls, folder ls, rule ls, cap ls, config get, proxy ls, relay ls, sudo status, service status, audit, log), and {"messages": [...]} or {"error": ...} from every other command (docs/user/commands.md).
  • nerve-node proxy set --password-stdin --headers-stdin takes the proxy's password and headers from stdin, so neither shows in the process list.
  • rule add and rule rm take --action, the rule of an audit entry. audit --last N shows the newest entries only.
  • nerve-node log [--problems] [--last N] [--follow] shows the node's log, with --problems its errors and warnings only.
  • nerve-node audit clear starts the audit log afresh: the entries so far become audit.jsonl.1 (as at 10 MiB) and the new log begins with "audit cleared by "; audit shows the entries from the clearing on. nerve-node log clear empties the log and its older file. Owner or root only, refused below a command a center runs.
  • With --json a change says "next_start": true when the node takes it only at its next start; relay add and relay token give the token as data ("token"), backup what it wrote ("backup"), center rotate-key the new key ("public_key"), and restore what it did ("restored").
  • nerve-node service start|stop|restart|status controls the installed service; status needs no root.
  • On the installed node its owner needs no sudo (on Windows no administrator's terminal): on Linux the account that ran install, which the unit names (on Windows the SIDs install records). The running node runs the owner's command as nerve over its owner socket (/run/nerve-node/owner.sock; a pipe on Windows) with the CLI's checks, letting in the owner and root only, by the caller's account; install, update, uninstall, sudo on and the machine.* settings that pick an account or program stay root's, and on Windows setup. backup and restore work for the owner too: the owner's own nerve-node opens the file and hands it to the node (on Linux the open file over the socket, on Windows its bytes), so the node never opens a path in the owner's folders; for a restore the node stops and starts again by itself, and a backup that would change those machine.* settings is refused to the owner. A polkit rule (Linux) and the service's DACL (Windows) let the owner start, stop and restart the service; on Linux through a helper that does only that (/usr/local/lib/nerve-node/service-control, its own polkit action), so polkit 0.105 (Ubuntu 22.04), which takes no rules, lets the owner too, by a .pkla file. uninstall removes the rule and the rights. Settings changes in the audit name the account that made them ("user", text by local (alice)); status --json names the owner ("owner") and the account each pairing's commands run as. A change the owner makes is in the node's log (alice runs rule add … as an owner); the reads the app makes every few seconds are at debug.
  • cap ls --json lists the switchable capabilities (known).
  • center add checks the link before it asks: the center answers, its certificate is the pinned one, and the code is still valid (POST /nerve/v0/pair/check on the center, which neither uses the code nor counts a try); it asks only when the pairing can work. center add --json without --yes says so in "check".
  • relay add and relay token show a nerve://relay link with all the other node needs (the relay's address, which --address sets, the node's name and token, the pin), and proxy set takes that link whole.
  • rule edit <list> <entry> <new> changes one rule in place, as one change in the audit.
  • install run through pkexec takes the user who ran it as the owner, as with sudo.
  • On Windows, status asks the running service through its control pipe, as it does on Linux (in 0.0.5 it said daemon: not known there).
  • config get machine.secret_storage --json says whether the keyring can hold the keys here ("keyring").
  • A node's name (node.name, a relay's nodes), a provider's (providers.allowed) and a center's name on the node start and end with a letter or digit, so that none reads as a flag: the node refuses a config with another, as do setup, center add --name and relay add, and the center refuses such a node name in a pairing and a rename (#146); the published schemas say the same of node names.
  • Changed: on the installed node, sudo nerve-node backup and restore open the backup file as the user who ran sudo (or pkexec), where 0.0.5 opened it as nerve, so a backup in your own folders works and is yours; the command itself still runs as nerve. Where that user may not open the file, the owner of its folder does: a root shell (sudo -i) writes to root's folders as root, and sudo nerve-node backup /var/lib/nerve/state/… writes as nerve. Through pkexec (the desktop app) a file its user may not open is refused, and so is an asker without an account.

The center

  • The web UI is redrawn in the desktop app's design (#137): its colours in light and dark, type scale, spaces and controls, and its animations of 100 to 200 ms (0.0.5's ran up to 350 ms), none when the system asks for less motion. The choice of System, Light and Dark has its names at the foot of the menu, and Help sits there too. A busy button shows a spinner. Help is built as the app's: the keyboard shortcuts and the command reference, by topic or A to Z, filtered, each part opening on its own, and a User guide link at the top that opens these pages on GitHub. The center has the app's icon.
  • The Action checker's switches in the web UI save again: a click sent nothing and showed "Cannot read properties of undefined". The page no longer prints the word "null" in its Local model part. A save that the center refuses no longer leaves its change in the page: the switch goes back, and a later save does not send it. The Action checker saves one change at a time: its buttons and switches are off until a save has settled, so a refused change is never saved by a second one. A page that is drawn anew (by a save or by the page's refresh) keeps the keyboard focus on the same control, found by what it is and not by where it was; when that item is gone, the focus goes to the heading of its list, a text field that gets the focus back keeps what was typed into it, and a save that is still running does not pull the focus back from where you moved it. An off switch, in the web UI and in the desktop app, has a track with at least 3:1 contrast against its surface (WCAG 1.4.11).
  • The center no longer stops answering on its web UI port under quick use (#150): two requests that read the node list and the session list at the same time (the overview the UI polls) could lock each other out for good, and every request after them waited too, until the center was restarted.
  • The center's ports are no longer held by slow peers (#150): a peer that reads nothing of an answer, or sends its request a byte now and then, is dropped after 15 s plus a second per 16 KiB, where before it kept one of the port's 256 connections as long as it liked.
  • A slow disk no longer holds up the center's ports: the state file is written after the state is unlocked, so a request that only reads does not wait for it; changes that come together are written once, and a save that waits for the disk holds none of the threads that serve the ports. On Linux the state file's folder is synced after each write, so a saved change is still there after a power cut.
  • The pairing card of the web UI offers the command and the bare link (for the desktop app), each with Copy; 0.0.5 showed the command and, apart from it, the center, the code and the pin.
  • POST /nerve/v0/pair/check tells a node whether a pairing code is still open without using it or counting a try; checks with codes that are not open have a budget of their own (10 in 10 minutes), after which the center answers busy.

The action checker

  • New built-in blocklist entries keep an agent off the node it runs on: running nerve-node or nerve-node-desktop, a command that names the node's socket, pipe, config or state folder, and a write into those folders (ids nerve-node, nerve-node-desktop, nerve-node-socket, nerve-node-folders, fs-nerve-…). They catch mistakes, not commands written to get past them.

No migrations before 0.1.0 (#144)

  • Until the first 0.1.0 release nobody runs Nerve, so the code that read or upgraded what older versions wrote is gone; from 0.1.0 on, migrations come as they are needed.
  • The node's control socket refuses status without its token too (0.0.5 answered it, for update of 0.0.2).
  • update on Windows replaces the program and restarts the service only: it no longer registers the Event Log source, which install does.

Static Linux binaries (x86_64, aarch64) and Windows x86_64 programs of nerve-center and nerve-node, and the desktop app nerve-node-desktop for Linux x86_64 (glibc 2.35 or newer) and Windows x86_64, built from 39e9f2a. SHA256SUMS lists their checksums. They are not signed; nerve-node update and the desktop app's update check the one they download against SHA256SUMS (docs/user/service.md, docs/user/desktop.md).