feat: enable persistent local Tor client and network helpers
This commit is contained in:
@@ -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 |
|
| `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 |
|
| `configuration.nix` | AWS boot/storage/network/recovery integration and checkout/target identity |
|
||||||
| `users.nix` | `dev`, SSH authorization, sudo, Home Manager and workspace ownership |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `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.
|
- **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.
|
- **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.
|
- **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.
|
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
|
## Build and apply
|
||||||
|
|
||||||
### One-command sync on either host
|
### One-command sync on either host
|
||||||
|
|||||||
+32
-1
@@ -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 = {
|
programs.mtr = {
|
||||||
enable = true;
|
enable = true;
|
||||||
package = latest.mtr;
|
package = latest.mtr;
|
||||||
@@ -39,10 +66,14 @@ in
|
|||||||
ethtool
|
ethtool
|
||||||
netcat-openbsd
|
netcat-openbsd
|
||||||
socat
|
socat
|
||||||
|
fping
|
||||||
|
ldns # drill and DNS/DNSSEC inspection utilities.
|
||||||
|
torsocks
|
||||||
|
proxychains-ng # Opt-in wrappers; no global proxy environment is set.
|
||||||
doggo
|
doggo
|
||||||
iftop
|
iftop
|
||||||
bandwhich
|
bandwhich
|
||||||
wireshark-cli # tshark; no capture group/capabilities or daemon.
|
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.
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-2
@@ -161,10 +161,60 @@ let
|
|||||||
ethtool
|
ethtool
|
||||||
netcat-openbsd
|
netcat-openbsd
|
||||||
socat
|
socat
|
||||||
|
fping
|
||||||
|
ldns
|
||||||
|
torsocks
|
||||||
|
proxychains-ng
|
||||||
|
tor
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
&& config.programs.mtr.enable;
|
&& 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 =
|
assertion =
|
||||||
@@ -181,8 +231,14 @@ pkgs.runCommand "physical-config-check" { } ''
|
|||||||
test ! -e "$sessions/wayland-sessions/plasma.desktop"
|
test ! -e "$sessions/wayland-sessions/plasma.desktop"
|
||||||
test -f "$sessions/wayland-sessions/hyprland-uwsm.desktop"
|
test -f "$sessions/wayland-sessions/hyprland-uwsm.desktop"
|
||||||
test ! -e "$sessions/wayland-sessions/hyprland.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"
|
test -x "${config.system.path}/bin/$tool"
|
||||||
done
|
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"
|
touch "$out"
|
||||||
''
|
''
|
||||||
|
|||||||
@@ -39,6 +39,14 @@ pkgs.runCommand "workstation-tools-check"
|
|||||||
zig version
|
zig version
|
||||||
ruby --version
|
ruby --version
|
||||||
php --version | head -1
|
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
|
printf 'select 42;\n' | sqlite3 | grep -qx 42
|
||||||
python - <<'PY'
|
python - <<'PY'
|
||||||
import importlib.util
|
import importlib.util
|
||||||
|
|||||||
Reference in New Issue
Block a user