diff --git a/README.md b/README.md index c8ee5d6..1b9de12 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Flat, explicit NixOS modules with locked inputs. Required setup belongs here—n | `physical.nix`, `hardware-configuration.nix` | Laptop boot/storage, panel scale, CPU sensor and checkout/target identity | | `configuration.nix` | AWS boot/storage/network/recovery integration and checkout/target identity | | `users.nix` | `dev`, SSH authorization, sudo, Home Manager and workspace ownership | -| `tools.nix`, `network.nix` | Development toolkit, terminal/shell, rootless Podman, VPN clients and network diagnostics | +| `tools.nix`, `network.nix` | Development toolkit, terminal/shell, rootless Podman, VPN/proxy clients, local Tor service and network diagnostics | | `colors.nix`, `wallpaper.nix`, `wallpaper.svg` | Shared One Ring palette, hash-pinned wallpaper and original fallback artwork | | `desktop.nix`, `hyprland.lua`, `anyrun.css`, `swaync.css`, `desktop-help.py`, `desktop-actions.py` | Session/bar, launchers, described help, capture, clipboard, notifications, lock/idle and styling | | `apps.nix`, `element-nightly.nix` | Firefox ESR, KeePassXC, Thunderbird, Steam, pinned Element Nightly, Slack, file/media viewers and MIME defaults | @@ -86,10 +86,27 @@ See [DESKTOP.md](DESKTOP.md) for the screenshot-led audit, functional coverage, - **Security/backup:** age, sops, GnuPG, Gitleaks, Trivy, Cosign, Syft, Grype, step, mkcert, restic, rclone, rsync, Mosh and SSHFS. No keys, trusted CA, backup destination, schedules or scan targets are created. - **Diagnostics:** btop, procs, lnav, sysstat, iotop, dust/duf/ncdu, strace/lsof, NVMe/SMART/USB/PCI/sensor tools. - **Media/documents:** FFmpeg, ImageMagick, ExifTool, MediaInfo, Poppler utilities, Pandoc, yt-dlp, Chafa, Asciinema, VHS and archive/compression tools. -- **Networking (`network.nix`):** WireGuard/OpenVPN, NetworkManager VPN integration on the laptop, mtr, iperf3, nmap, tcpdump/tshark, doggo/dig, iftop/bandwhich, traceroute, whois, ethtool, netcat and socat. No tunnels, peers, credentials, extra capture privileges or opened firewall ports. +- **Networking (`network.nix`):** WireGuard/OpenVPN, NetworkManager VPN integration on the laptop, mtr, iperf3, nmap (including ncat/nping), tcpdump/tshark, doggo/dig, ldns/drill, fping, iftop/bandwhich, traceroute, whois, ethtool, netcat, socat, torsocks and proxychains-ng. Tor runs as a local client service (below). No tunnels, peers, credentials, extra capture privileges or opened firewall ports. These are system-owned executables from Nix, not unmanaged `npm -g`, `pip install --user` or `cargo install` bootstraps. Project dependencies may still be downloaded by their ordinary package managers. `nix develop` / `.envrc` remain appropriate for project-specific versions; this is not a promise that every language project uses the same global toolchain. +### Local Tor client + +On both hosts, `network.nix` installs Tor from the **system pin** and enables `tor.service` at boot. It runs as the dedicated `tor` user with the NixOS module's sandbox and private persistent state in `/var/lib/tor`. Systemd restarts an exited daemon after five seconds without a retry limit; an explicit `systemctl stop tor` still stops it normally. A running process does not guarantee network connectivity—check for `Bootstrapped 100%` in the journal. + +The SOCKS listener is **`127.0.0.1:9050` only**, with destination and SOCKS-auth isolation. There is no relay/exit, onion service, control listener, transparent proxy, system DNS change or global proxy environment. Applications must opt in; installing Tor does **not** anonymize the whole machine. Prefer native SOCKS5 support with proxy-side hostname resolution (`socks5h`), for example: + +```sh +systemctl status tor.service +journalctl -u tor.service -b --no-pager +curl --fail --show-error --max-time 60 --proxy socks5h://127.0.0.1:9050 \ + https://check.torproject.org/api/ip +# For compatible dynamically linked applications, explicitly wrap one command: +torsocks curl --fail --show-error --max-time 60 https://check.torproject.org/api/ip +``` + +`torsocks` and `proxychains4` are opt-in wrappers, not sandboxes: static binaries and applications that bypass their hooks are not reliably covered. Tor carries TCP, not arbitrary UDP/ICMP; do not assume tools such as fping, raw-packet nmap or traceroute run through it. Keep ordinary DNS lookups out of workflows that require Tor-side resolution. Use Tor Browser separately for browser anonymity rather than treating a generic browser's proxy setting as equivalent. + ## Build and apply ### One-command sync on either host diff --git a/network.nix b/network.nix index fa5a0d8..f752d94 100644 --- a/network.nix +++ b/network.nix @@ -23,6 +23,33 @@ in }; }; + # Local, opt-in SOCKS client only. Keep the daemon on the system package pin. + # No relay/exit, control listener, transparent proxy or host DNS changes. + services.tor = { + enable = true; + openFirewall = false; + relay.enable = false; + client = { + enable = true; + socksListenAddress = { + addr = "127.0.0.1"; + port = 9050; + IsolateDestAddr = true; + IsolateSOCKSAuth = true; + }; + }; + settings.ClientOnly = true; + }; + # The NixOS module enables tor.service at boot and supplies its sandbox/user. + # Retry even after a clean daemon exit; never exhaust systemd's start limit. + systemd.services.tor = { + unitConfig.StartLimitIntervalSec = 0; + serviceConfig = { + Restart = lib.mkForce "always"; + RestartSec = "5s"; + }; + }; + programs.mtr = { enable = true; package = latest.mtr; @@ -39,10 +66,14 @@ in ethtool netcat-openbsd socat + fping + ldns # drill and DNS/DNSSEC inspection utilities. + torsocks + proxychains-ng # Opt-in wrappers; no global proxy environment is set. doggo iftop bandwhich wireshark-cli # tshark; no capture group/capabilities or daemon. ]; - # Client tools only: no VPN services, peers, keys, routes or firewall ports. + # No VPN services, peers, keys, routes or opened firewall ports. } diff --git a/physical-test.nix b/physical-test.nix index 76d61dc..ee46930 100644 --- a/physical-test.nix +++ b/physical-test.nix @@ -161,10 +161,60 @@ let ethtool netcat-openbsd socat + fping + ldns + torsocks + proxychains-ng + tor ] ) && config.programs.mtr.enable; - message = "The VPN clients and network diagnostics must remain installed."; + message = "The VPN/proxy clients and network diagnostics must remain installed."; + } + { + assertion = + lib.all + ( + c: + let + tor = c.services.tor; + unit = c.systemd.services.tor; + in + tor.enable + && tor.client.enable + && !tor.relay.enable + && !tor.openFirewall + && tor.settings.ClientOnly + && tor.settings.ORPort == [ ] + && tor.settings.DirPort == [ ] + && tor.settings.ExitPolicy == [ "reject *:*" ] + && tor.relay.onionServices == { } + && !tor.controlSocket.enable + && tor.settings.ControlPort == [ ] + && !tor.client.dns.enable + && !tor.client.transparentProxy.enable + && tor.settings.DNSPort == [ ] + && tor.settings.TransPort == [ ] + && builtins.length tor.settings.SOCKSPort == 1 + && lib.all ( + listener: + listener.addr == "127.0.0.1" + && listener.port == 9050 + && listener.IsolateDestAddr + && listener.IsolateSOCKSAuth + ) tor.settings.SOCKSPort + && builtins.elem "multi-user.target" unit.wantedBy + && unit.serviceConfig.Restart == "always" + && unit.serviceConfig.RestartSec == "5s" + && unit.unitConfig.StartLimitIntervalSec == 0 + && unit.serviceConfig.User == "tor" + && unit.serviceConfig.NoNewPrivileges + ) + [ + config + ec2Config + ]; + message = "Both hosts need a boot-enabled, restarting, loopback-only Tor client, without relay, control, DNS or transparent-proxy listeners."; } { assertion = @@ -181,8 +231,14 @@ pkgs.runCommand "physical-config-check" { } '' test ! -e "$sessions/wayland-sessions/plasma.desktop" test -f "$sessions/wayland-sessions/hyprland-uwsm.desktop" test ! -e "$sessions/wayland-sessions/hyprland.desktop" - for tool in wg wg-quick openvpn iperf3 nmap traceroute whois mtr dig tcpdump ethtool nc socat nm-connection-editor; do + for tool in wg wg-quick openvpn iperf3 nmap traceroute whois mtr dig tcpdump ethtool nc socat \ + fping drill torsocks proxychains4 tor nm-connection-editor; do test -x "${config.system.path}/bin/$tool" done + # Validate the exact generated torrc offline without touching live Tor state. + mkdir -m 700 "$TMPDIR/tor" + ${config.services.tor.package}/bin/tor --verify-config \ + -f ${builtins.head config.systemd.services.tor.restartTriggers} \ + --DataDirectory "$TMPDIR/tor" touch "$out" '' diff --git a/tools-test.nix b/tools-test.nix index 4f76b1a..3ffa772 100644 --- a/tools-test.nix +++ b/tools-test.nix @@ -39,6 +39,14 @@ pkgs.runCommand "workstation-tools-check" zig version ruby --version php --version | head -1 + socat -V + # fping opens ICMP sockets even for -v; physical-config checks its binary. + drill -v + tor --version + torsocks --version + printf 'socat-offline-check\n' | socat -u STDIN STDOUT | grep -qx socat-offline-check + torsocks curl --version + proxychains4 -q curl --version printf 'select 42;\n' | sqlite3 | grep -qx 42 python - <<'PY' import importlib.util