From ec27643b620ed0a182531a2f76cb2bbb89076447 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 20:26:55 +0000 Subject: [PATCH 1/2] docs: rewrite the README as the project homepage - Lead with what nm-sshuttle is and its features instead of the design status. - Add install from the dnf repository, a quick start and the common setups. - Link the user guide, design, packaging notes and development tools. --- README.md | 240 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 169 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 885ad8b..e66ff41 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,186 @@ # nm-sshuttle -Make an [sshuttle](https://github.com/sshuttle/sshuttle) tunnel behave like a -first-class VPN connection on a GNOME desktop: a toggle in Quick Settings, -next to WireGuard, backed by a systemd unit. The tunnel should survive -suspend, roaming and SSH key unlock without manual work. - -sshuttle needs only SSH access and Python on the remote side, and it uses the -system `ssh` client, so it works through ProxyJump hosts where WireGuard is -not an option. - -## Status - -**M3, usable, done.** The plugin runs its whole lifecycle (M2): -reconnects with backoff, suspend, roaming, captive portals, a health check, -the lock screen and the fail-closed guard. The profile commands are on PATH, -RPM packages (Fedora 43, 44) and deb packages (Debian 13, Ubuntu 26.04) are -built in CI, and the VM tests run in CI too. The -[user guide](docs/user-guide.md) covers installing, creating a profile and -troubleshooting. Suspend and the GNOME checks passed by hand on a Fedora 44 -notebook; still open from M2 is DNS without systemd-resolved (design §7). +[![CI](https://github.com/dennisklein/nm-sshuttle/actions/workflows/ci.yml/badge.svg)](https://github.com/dennisklein/nm-sshuttle/actions/workflows/ci.yml) +[![VM tests](https://github.com/dennisklein/nm-sshuttle/actions/workflows/vm-tests.yml/badge.svg)](https://github.com/dennisklein/nm-sshuttle/actions/workflows/vm-tests.yml) + +**An [sshuttle](https://github.com/sshuttle/sshuttle) tunnel as a first-class +VPN on the GNOME desktop.** A toggle in Quick Settings, next to WireGuard, +backed by a NetworkManager VPN plugin. It needs only SSH access and Python on +the far side, and it uses your own `ssh`, so it works through `ProxyJump` +hosts where WireGuard is not an option. + +- **A real VPN toggle.** The tunnel shows up in GNOME Quick Settings and in + `nmcli`, like any other NetworkManager VPN. Profiles can be created in + GNOME Settings or with the `nm-sshuttle` command. +- **It stays up.** Once switched on, it reconnects by itself after suspend, + roaming, a captive portal, a dropped connection or a locked screen, with + backoff, and it notices a tunnel that is up but stuck. +- **It fails closed.** While the tunnel is down, traffic to the tunnelled + networks is blocked instead of leaking onto the local network. +- **Your SSH setup, unchanged.** `~/.ssh/config`, `ProxyJump`, `known_hosts` + and your agent work as usual. Only `ssh` runs as you; sshuttle runs as + root in its own systemd unit, and no sudoers rule is needed. +- **DNS that follows the tunnel.** Send lookups for your company's domains + to its resolvers (split DNS through systemd-resolved), or all of them. +- **IPv4 and IPv6, TCP and UDP.** `method=tproxy` carries UDP such as QUIC + too. +- **Several tunnels at once.** Up to 8 profiles can be active together; + profiles that would fight over the same traffic or DNS are refused. + +## Install + +Fedora 43 and 44 can install from the signed package repository, which also +brings updates: + +```console +$ sudo rpm --import https://dennisklein.github.io/nm-sshuttle/RPM-GPG-KEY-nm-sshuttle +$ sudo dnf install https://dennisklein.github.io/nm-sshuttle/nm-sshuttle-release.noarch.rpm +$ sudo dnf install nm-sshuttle nm-sshuttle-gnome +``` + +`nm-sshuttle-gnome` adds sshuttle to GNOME Settings' VPN list; leave it out +to use the command line only. Restart Settings afterwards (log out, or +`pkill gnome-control-center`). + +Debian 13 and Ubuntu 26.04 packages are built by CI (download them from a +run's artifacts), and the plugin installs from source with +`meson setup build --prefix=/usr && sudo meson install -C build && +sudo nm-sshuttle post-install`. The +[user guide](docs/user-guide.md#installing) has the details. + +You need NetworkManager 1.42 or later, nftables, sshuttle 1.3.1 or later and, +for the tunnel's DNS, systemd-resolved. The package pulls in the rest. + +## Quick start + +1. Put the way to your far side in `~/.ssh/config`, and make sure a key logs + you in without a prompt from ssh itself (a key in your agent, or a key + without a passphrase): + + ``` + Host corp + HostName gw.corp.example + User alice + ProxyJump bastion.corp.example + ``` + +2. Create a profile as yourself, and test it. `check` makes the login the + plugin will make, so it may ask you to accept a host key once: + + ```console + $ nm-sshuttle add corp --remote corp --subnets 10.0.0.0/8 \ + --dns split --dns-servers 10.1.0.53 --dns-domains '~corp.example' + $ nm-sshuttle check corp + ``` + + With `nm-sshuttle-gnome` installed, the same form is in GNOME Settings: + Network, **+** next to VPN, **sshuttle**. + +3. Switch it on in Quick Settings, or: + + ```console + $ nmcli connection up corp + ``` + + It stays on until you switch it off. If it does not come up, GNOME says + only "Connection failed"; the reason is in + `journalctl -b -u 'nm-sshuttle*'`. + +## Common setups + +Each of these is one `nm-sshuttle add`; the +[user guide](docs/user-guide.md#creating-a-profile) lists every option. + +**Split DNS for a company domain.** Tunnel the network, send lookups for the +domain to its resolver, leave everything else alone. The leading `~` only +routes lookups; a plain `corp.example` also completes short names, so `git` +becomes `git.corp.example`: ```console $ nm-sshuttle add corp --remote corp --subnets 10.0.0.0/8 \ --dns split --dns-servers 10.1.0.53 --dns-domains corp.example -$ nm-sshuttle check corp -$ nmcli connection up corp # or the VPN toggle in Quick Settings ``` -- [`nm_sshuttle/`](nm_sshuttle) is the plugin (design §4.9): the VPN D-Bus - service, its lifecycle state machine, the NetworkManager and logind - watchers, the guard table and `nmss0`, and the commands the systemd units - and users run. [`data/`](data) holds the files it installs, among them - the NetworkManager `conf.d` snippet that keeps `nmss0` unmanaged. -- [`gnome/`](gnome) is the editor for GNOME Settings (C, GTK4): "Add VPN" - offers sshuttle and the Identity tab edits every profile key. It is its - own package, `nm-sshuttle-gnome`. Its validation reads the same - [test cases](tests/profile-cases.json) as the plugin's. -- [`packaging/rpm/`](packaging/rpm) and [`debian/`](debian) build the - Fedora and Debian/Ubuntu packages. [`packaging/`](packaging) also builds - and signs the Fedora dnf repository on GitHub Pages - () from release tags. -- [`docs/design.md`](docs/design.md) is the design: the research findings, - the options considered, the architecture, the risks and the roadmap. -- [`test-vm/`](test-vm) runs the plugin's scenarios in a throwaway Fedora - 44 VM, from a Fedora host or in CI (the VM tests workflow). -- [`lab/`](lab) is a self-contained test lab. It checks the sshuttle and - kernel behaviour the design relies on (network namespaces; run as root in a - throwaway VM or container). -- [`spike/`](spike) is the M1 spike: a bare VPN plugin run through the open - risks in a Fedora 44 VM with GNOME. -- [`docs/upstream/`](docs/upstream) holds draft bug reports for - NetworkManager, GNOME Shell and sshuttle, found along the way and not yet - filed. - -## Building and testing +**IPv4 and IPv6, UDP included.** `--subnets` takes both families. Add +`--method tproxy` to carry UDP, such as QUIC; it needs `iptables` installed: + +```console +$ nm-sshuttle add corp --remote corp --subnets 10.0.0.0/8,2001:db8:10::/48 \ + --method tproxy --dns split --dns-servers 10.1.0.53 --dns-domains '~corp.example' +``` + +**A second tunnel next to the first.** Exclude the nested network from the +larger profile and give it a profile of its own: + +```console +$ nm-sshuttle add corp --remote corp --subnets 10.0.0.0/8 --exclude 10.50.0.0/16 +$ nm-sshuttle add lab --remote lab --subnets 10.50.0.0/16 +``` + +**A jump host behind `ProxyCommand`.** ssh cannot tell which address it +connects to first, so name it, and the tunnel will not swallow it: +`--gateway 203.0.113.7`. + +**Notice a stuck tunnel.** `--probe 10.1.0.10:22` makes the plugin connect +to a server on the far side that speaks first, such as an ssh server, every +10 seconds, and restart the tunnel after two failures. + +**Let traffic out while the tunnel is down.** `--fail-closed no` turns the +block off. + +## How it works + +``` + Quick Settings / nmcli ──► NetworkManager ──► nm-sshuttle (VPN plugin, root) + │ guard · DNS link · health + ▼ + nm-sshuttle-tunnel@N.service + sshuttle (root) ──► ssh (as you) ──► far side +``` + +The plugin is a small Python service that tells NetworkManager the VPN is +persistent, supervises the sshuttle unit, and watches NetworkManager, logind +and the tunnel itself to decide when to reconnect. An nftables guard table +keeps the tunnelled networks fail-closed, and a per-tunnel dummy link +(`nmss0`, `nmss1` …) hands the VPN's DNS servers and routing domains to +systemd-resolved. The [design](docs/design.md) has the research behind it, +the options considered and the architecture. + +## Documentation + +- [User guide](docs/user-guide.md): installing, SSH setup, every profile + option, GNOME Settings, several VPNs at once, troubleshooting, limits. +- [Design](docs/design.md): findings, architecture, risks and roadmap. + The [designs of the later additions](docs/later/README.md) (IPv6, tproxy, + several tunnels, the GNOME editor) are next to it. +- [Packaging](packaging/README.md): the RPM and deb packages, the signed dnf + repository and how to cut a release. +- [Upstream reports](docs/upstream/README.md): drafts of bugs found along + the way in NetworkManager, GNOME Shell and sshuttle. + +## Development ```console -$ python3 -m pytest # needs python3-gobject and dbus-daemon for the D-Bus test +$ python3 -m pytest # needs python3-gobject and dbus-daemon for the D-Bus tests $ meson setup build --prefix=/usr -$ sudo meson install -C build -$ sudo nm-sshuttle post-install +$ meson test -C build $ test-vm/vm.sh # on a Fedora 44 host with KVM; see test-vm/README.md ``` -The editor for GNOME Settings is built when `libnm` and GTK4 are installed -with their development files (`-Dgnome-editor=enabled` requires them). Its -form is tested under a virtual display: -`xvfb-run -a meson test -C build --suite gnome-editor`. - -`post-install` reloads NetworkManager's configuration so that `nmss0` is -unmanaged, unless an nm-sshuttle VPN is active; then the setting takes -effect at NetworkManager's next start. The packages run it themselves. - -## Proposed shape, in short - -- A thin NetworkManager VPN service plugin (`vpn-type sshuttle`), written in - Python, that runs as a D-Bus-activated systemd service. -- sshuttle runs in its own systemd unit as root. Only `ssh` runs as the - desktop user, so `~/.ssh/config`, ProxyJump, `known_hosts` and the - session's agent work as usual, and no sudoers rule is needed. -- The plugin is persistent: once switched on, it reconnects by itself after - suspend, roaming and drops. While the tunnel is down, traffic to the - tunnelled networks fails closed instead of leaking. -- Split DNS uses a per-tunnel dummy link handed to systemd-resolved. +- [`nm_sshuttle/`](nm_sshuttle) is the plugin and the `nm-sshuttle` command; + [`data/`](data) holds the files it installs. +- [`gnome/`](gnome) is the editor for GNOME Settings (C, GTK4). It is built + when `libnm` and GTK4 are installed with their development files + (`-Dgnome-editor=enabled` requires them) and is tested under a virtual + display: `xvfb-run -a meson test -C build --suite gnome-editor`. +- [`test-vm/`](test-vm) runs the plugin's scenarios in a throwaway Fedora 44 + VM with SELinux enforcing, from a Fedora host or in CI. +- [`lab/`](lab) checks the sshuttle and kernel behaviour the design relies + on, in network namespaces. Run it as root in a throwaway VM or container. +- [`spike/`](spike) is the early spike that retired the design's open risks + on Fedora 44 with GNOME. + +Report security problems privately, as [SECURITY.md](SECURITY.md) describes. ## License From 062a93076c3996eba6ef1f099607e9ba2e6f07ab Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 20:26:55 +0000 Subject: [PATCH 2/2] docs: bring the design and guides up to date - Design: mark the Later items and the editor as implemented, and update the code layout, the .name file and the package notes. - User guide: install nm-sshuttle-gnome from the dnf repository. - VM tests: point to the editor's protocol for the manual GNOME checks. --- docs/design.md | 52 ++++++++++++++++++++++++++++---------- docs/later/gnome-editor.md | 4 ++- docs/user-guide.md | 7 ++--- test-vm/README.md | 4 ++- 4 files changed, 49 insertions(+), 18 deletions(-) diff --git a/docs/design.md b/docs/design.md index 38a84d2..c54cfa9 100644 --- a/docs/design.md +++ b/docs/design.md @@ -1,8 +1,11 @@ # nm-sshuttle: design proposal -Status: M0 to M3 done (§7), updated 2026-10-11. The plugin is in -[`nm_sshuttle/`](../nm_sshuttle), packaged for Fedora, Debian and Ubuntu, and -its VM tests ([`test-vm/`](../test-vm)) pass in CI. The research code is the lab in +Status: M0 to M3 done and the "Later" items implemented (§7), updated +2026-10-11. The plugin is in [`nm_sshuttle/`](../nm_sshuttle) and the editor +for GNOME Settings in [`gnome/`](../gnome). They are packaged for Fedora, +Debian and Ubuntu (Fedora also from a signed dnf repository), and the VM +tests ([`test-vm/`](../test-vm)) pass in CI. The sections below describe the +design as proposed for v1; later work is marked where it changes them. The research code is the lab in [`lab/`](../lab), which checks the sshuttle and kernel behaviour this design relies on, and the spike in [`spike/`](../spike), which checks NetworkManager, SELinux and GNOME on Fedora 44. @@ -10,7 +13,9 @@ NetworkManager, SELinux and GNOME on Fedora 44. ## 1. Recommendation in brief Build a **small NetworkManager VPN service plugin in Python, without a GUI -editor**; a ten-line auth-dialog stub is its only GNOME part. It runs as a D-Bus-activated systemd service and supervises +editor** in v1; a ten-line auth-dialog stub is its only GNOME part. (The +[GNOME Settings editor](later/gnome-editor.md) came later, as a separate +package.) It runs as a D-Bus-activated systemd service and supervises sshuttle, which runs in a second systemd unit. - **The toggle.** GNOME Quick Settings shows a VPN toggle for every @@ -586,7 +591,8 @@ DNS behaviour. - **A** buys native editing in GNOME Settings at the cost of a C/GTK4 editor library (gnome-control-center `dlopen`s it) and libnma. It can be - added on top of B later without changing anything else. + added on top of B later without changing anything else; it was, as the + separate package `nm-sshuttle-gnome` ([editor](later/gnome-editor.md)). - **C** is the smallest: a systemd unit plus a Quick Settings extension such as *Custom Command Toggle* (GNOME 45–51). But the toggle sits outside NM's VPN list, extensions break on GNOME upgrades, and the lifecycle work @@ -653,6 +659,10 @@ supports-safe-private-file-access=true auth-dialog=/usr/libexec/nm-sshuttle/nm-sshuttle-auth-dialog ``` +With [several tunnels](later/multiple-tunnels.md) the installed file says +`supports-multiple-connections=true`, and the [editor](later/gnome-editor.md) +adds a `[libnm]` section that names the plugin library of the editor package. + The other installed files: - a D-Bus policy in `/usr/share/dbus-1/system.d/`: only root may own the @@ -1598,21 +1608,27 @@ minimal path under it; `ip` and `nft` there are untested. ``` nm_sshuttle/ - service.py VPN D-Bus interface (Gio), about 250 lines + service.py VPN D-Bus interface (Gio) supervisor.py state machine, backoff, health; pure logic, unit-testable watch.py NM and logind D-Bus watchers (systemd's and NM's active connection signals stay in service.py) profile.py vpn.data parsing and validation tunnel.py sshuttle argv, exec-tunnel, sweep, ssh-as-user guard.py nft guard table, dummy link - cli.py add / check / list / remove (wraps nmcli); sweep and - cleanup, which the units run -data/ .name file, D-Bus policy and activation file, the two units, + claims.py what each active profile claims; the conflict rules + dispatcher.py gives each activation a slot (several tunnels) + const.py names, paths and NM constants + cli.py add / check / list / remove (wraps nmcli); sweep, cleanup + and post-install, which the units and packages run +data/ .name file, D-Bus policy and activation file, the units, the conf.d snippet that leaves nmss0 unmanaged, auth-dialog stub (shell) +gnome/ editor for GNOME Settings (C, GTK4), a separate package ``` -That is roughly 1,200 lines of Python. +v1 was about 1,200 lines of Python. With the lifecycle work, IPv6, tproxy +and several tunnels it is about 4,200, and the editor adds about 1,200 lines +of C. - **Runtime dependencies:** python3 ≥ 3.10, python3-gobject, sshuttle ≥ 1.3.1, nftables, iproute2, util-linux (`setpriv`) and openssh-clients. @@ -1873,9 +1889,9 @@ Later, also propose a hook for an externally provided firewall helper README; and the VM tests in CI, on demand and weekly, on hosted runners with KVM (about 12 min). Suspend and the GNOME checks passed by hand on a notebook; DNS without resolved stays open under M2. -5. **Later:** GTK4 editor plugin for GNOME Settings, several tunnels at once - (template units), UDP through `tproxy`, IPv6 guard sets. Each has a - design in [`later/`](later/README.md). **IPv6 is in:** IPv6 subnets +5. **Later (all four implemented):** GTK4 editor plugin for GNOME Settings, + several tunnels at once (template units), UDP through `tproxy`, IPv6 + guard sets. Each has a design in [`later/`](later/README.md). **IPv6 is in:** IPv6 subnets and exclusions, the IPv6 guard sets, an IPv6 first hop and `gateway`, and the IPv6 part of the network key, with unit tests, lab tests T11 to T14 and VM scenarios V6-01 and V6-02. **So is `method=tproxy`:** UDP @@ -1886,3 +1902,13 @@ Later, also propose a hook for an externally provided firewall helper activation a slot and starts a plugin instance for it, names per slot, the conflict rules between active profiles, and `cleanup --slot` and `--all`, with unit and D-Bus tests and VM scenarios MT-01 to MT-03. + **And the editor for GNOME Settings:** a GTK4 library, packaged as + `nm-sshuttle-gnome`, whose validation reads the same test cases as the + plugin's, with unit tests and a form test under Xvfb. On the Fedora 44 + notebook the protocol + ([`test-vm/gnome-editor-protocol.md`](../test-vm/gnome-editor-protocol.md)) + passed in GNOME Settings except 7.3 and 7.6 (Apply lagged one change + behind the IPv4 method); that is fixed, and the re-check of 7.3 to 7.7 + is still open. + **Packages:** a signed dnf repository for Fedora 43 and 44 on GitHub + Pages, published from release tags ([`packaging/`](../packaging/README.md)). diff --git a/docs/later/gnome-editor.md b/docs/later/gnome-editor.md index d78a930..444eadd 100644 --- a/docs/later/gnome-editor.md +++ b/docs/later/gnome-editor.md @@ -3,7 +3,9 @@ Status: implemented in `gnome/`, with the changes listed under "Differences from the design" at the end, and checked by the unit tests and a form test under Xvfb. The checks in GNOME Settings itself (§Tests, "VM, by -hand") are still open. One of the "Later" items of the [design](../design.md) +hand") passed on a Fedora 44 notebook except 7.3 and 7.6 of the +[protocol](../../test-vm/gnome-editor-protocol.md) (Apply lagged one change +behind the IPv4 method). That is fixed; the re-check of 7.3 to 7.7 is open. One of the "Later" items of the [design](../design.md) (§7); the index is in [README.md](README.md). v1 has no editor plugin (§1, option B in §3). Profiles come from `nm-sshuttle diff --git a/docs/user-guide.md b/docs/user-guide.md index a892890..da03215 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -44,9 +44,10 @@ $ sudo apt install ./nm-sshuttle_*_all.deb ``` To create and edit profiles in GNOME Settings as well, install the editor, -which is a separate package (`nm-sshuttle-gnome`; `nm-sshuttle-gnome-*.rpm`, -`nm-sshuttle-gnome_*.deb` as files). Settings reads its list of VPN types -once, so restart it afterwards (log out, or `pkill gnome-control-center`). +which is a separate package (`sudo dnf install nm-sshuttle-gnome` from the +repository; `nm-sshuttle-gnome-*.rpm`, `nm-sshuttle-gnome_*.deb` as files). +Settings reads its list of VPN types once, so restart it afterwards (log out, +or `pkill gnome-control-center`). From source: diff --git a/test-vm/README.md b/test-vm/README.md index 8df9e9d..3ecbfb2 100644 --- a/test-vm/README.md +++ b/test-vm/README.md @@ -64,7 +64,9 @@ unobserved (for example firewalld's zone binding after a link loss, or what resolved holds between a kill and the link's removal): read those lines, they are the answers. `SKIP` means the scenario cannot run here. -There are no manual GNOME checks; the VM is headless by default. +The VM is headless, so the GNOME checks are not automated. The editor for GNOME +Settings has a protocol for checking it by hand on a laptop: +[`gnome-editor-protocol.md`](gnome-editor-protocol.md). ## Run it