Compare commits
6 Commits
master
...
wip-polyun
Author | SHA1 | Date | |
---|---|---|---|
3353add4dd | |||
2c0b725573 | |||
8185c74ddc | |||
42049fb1b2 | |||
7b57ad8acb | |||
8e67bd6890 |
|
@ -2,6 +2,31 @@
|
||||||
|
|
||||||
{ lib, ... }:
|
{ lib, ... }:
|
||||||
{
|
{
|
||||||
|
# remove a few items from /run/wrappers we don't need.
|
||||||
|
options.security.wrappers = lib.mkOption {
|
||||||
|
apply = lib.filterAttrs (name: _: !(builtins.elem name [
|
||||||
|
# wrappers from <repo:nixos/nixpkgs:nixos/modules/programs/shadow.nix>
|
||||||
|
"newgidmap"
|
||||||
|
"newgrp"
|
||||||
|
"newuidmap"
|
||||||
|
# "sg"
|
||||||
|
# "su"
|
||||||
|
# wrappers from <repo:nixos/nixpkgs:nixos/modules/security/pam.nix>
|
||||||
|
# may need to patch e.g. `pam` package (pam_unix) to not refer to unix_chkpwd by path
|
||||||
|
"unix_chkpwd"
|
||||||
|
]));
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
nixpkgs.overlays = [(self: super: {
|
||||||
|
pam = super.pam.overrideAttrs (upstream: {
|
||||||
|
postPatch = (if upstream.postPatch != null then upstream.postPatch else "") + ''
|
||||||
|
substituteInPlace modules/pam_unix/Makefile.am --replace-fail \
|
||||||
|
"/run/wrappers/bin/unix_chkpwd" "$out"
|
||||||
|
'';
|
||||||
|
});
|
||||||
|
})];
|
||||||
|
|
||||||
# disable non-required packages like nano, perl, rsync, strace
|
# disable non-required packages like nano, perl, rsync, strace
|
||||||
environment.defaultPackages = [];
|
environment.defaultPackages = [];
|
||||||
|
|
||||||
|
@ -85,4 +110,5 @@
|
||||||
# - on x86 only: more keyboard stuff: "pcips2" "atkbd" "i8042"
|
# - on x86 only: more keyboard stuff: "pcips2" "atkbd" "i8042"
|
||||||
|
|
||||||
boot.initrd.includeDefaultModules = lib.mkDefault false;
|
boot.initrd.includeDefaultModules = lib.mkDefault false;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -572,16 +572,16 @@ in
|
||||||
iotop.sandbox.capabilities = [ "net_admin" ];
|
iotop.sandbox.capabilities = [ "net_admin" ];
|
||||||
|
|
||||||
# provides `ip`, `routel`, `bridge`, others.
|
# provides `ip`, `routel`, `bridge`, others.
|
||||||
# landlock works fine for most of these, but `ip netns exec` uses namespaces internally,
|
# landlock works fine for most of these, but `ip netns exec` wants to attach to an existing namespace
|
||||||
# and that's incompatible with landlock
|
# and that means we can't use ANY sandboxer for it.
|
||||||
iproute2.sandbox.method = "bwrap";
|
iproute2.sandbox.enable = false;
|
||||||
iproute2.sandbox.net = "all";
|
# iproute2.sandbox.net = "all";
|
||||||
iproute2.sandbox.capabilities = [ "net_admin" ];
|
# iproute2.sandbox.capabilities = [ "net_admin" ];
|
||||||
iproute2.sandbox.extraPaths = [
|
# iproute2.sandbox.extraPaths = [
|
||||||
"/run/netns" # for `ip netns ...` to work, but maybe not needed anymore?
|
# "/run/netns" # for `ip netns ...` to work, but maybe not needed anymore?
|
||||||
"/sys/class/net" # for `ip netns ...` to work
|
# "/sys/class/net" # for `ip netns ...` to work
|
||||||
"/var/run/netns"
|
# "/var/run/netns"
|
||||||
];
|
# ];
|
||||||
|
|
||||||
iptables.sandbox.method = "landlock";
|
iptables.sandbox.method = "landlock";
|
||||||
iptables.sandbox.net = "all";
|
iptables.sandbox.net = "all";
|
||||||
|
|
|
@ -229,7 +229,6 @@ in
|
||||||
fwmark=${builtins.toString vpnCfg.fwmark}
|
fwmark=${builtins.toString vpnCfg.fwmark}
|
||||||
priorityMain=${builtins.toString vpnCfg.priorityMain}
|
priorityMain=${builtins.toString vpnCfg.priorityMain}
|
||||||
priorityFwMark=${builtins.toString vpnCfg.priorityFwMark}
|
priorityFwMark=${builtins.toString vpnCfg.priorityFwMark}
|
||||||
bridgeDevice=${vpnCfg.bridgeDevice}
|
|
||||||
addrV4=${vpnCfg.addrV4}
|
addrV4=${vpnCfg.addrV4}
|
||||||
name=${vpnCfg.name}
|
name=${vpnCfg.name}
|
||||||
dns=(${lib.concatStringsSep " " vpnCfg.dns})
|
dns=(${lib.concatStringsSep " " vpnCfg.dns})
|
||||||
|
|
|
@ -51,7 +51,7 @@ let
|
||||||
|
|
||||||
"/etc" #< especially for /etc/profiles/per-user/$USER/bin
|
"/etc" #< especially for /etc/profiles/per-user/$USER/bin
|
||||||
"/run/current-system" #< for basics like `ls`, and all this program's `suggestedPrograms` (/run/current-system/sw/bin)
|
"/run/current-system" #< for basics like `ls`, and all this program's `suggestedPrograms` (/run/current-system/sw/bin)
|
||||||
"/run/wrappers" #< SUID wrappers. TODO: remove!
|
# "/run/wrappers" #< SUID wrappers. they don't mean much inside a namespace.
|
||||||
# /run/opengl-driver is a symlink into /nix/store; needed by e.g. mpv
|
# /run/opengl-driver is a symlink into /nix/store; needed by e.g. mpv
|
||||||
"/run/opengl-driver"
|
"/run/opengl-driver"
|
||||||
"/run/opengl-driver-32" #< XXX: doesn't exist on aarch64?
|
"/run/opengl-driver-32" #< XXX: doesn't exist on aarch64?
|
||||||
|
|
|
@ -6,18 +6,21 @@
|
||||||
# - wireguard (arch): <https://wiki.archlinux.org/title/WireGuard>
|
# - wireguard (arch): <https://wiki.archlinux.org/title/WireGuard>
|
||||||
#
|
#
|
||||||
# to route all internet traffic through a VPN endpoint, run `systemctl start vpn-${vpnName}`
|
# to route all internet traffic through a VPN endpoint, run `systemctl start vpn-${vpnName}`
|
||||||
|
# to route an application's traffic through a VPN: `sane-vpn do ${vpnName} ${command[@]}`
|
||||||
# to show the routing table: `ip rule`
|
# to show the routing table: `ip rule`
|
||||||
# to show the NAT rules used for bridging: `sudo iptables -t nat --list-rules -v`
|
# to show the NAT rules used for bridging: `sudo iptables -t nat --list-rules -v`
|
||||||
#
|
#
|
||||||
# the rough idea here is:
|
# the rough idea here is:
|
||||||
# 1. each VPN has an IP address: if we originate a packet, and the source address is the VPN's address, then it gets routed over the VPN trivially.
|
# 1. each VPN has an IP address: if we originate a packet, and the source address is the VPN's address, then it gets routed over the VPN trivially.
|
||||||
# 2a. create a bridge device for each VPN. traffic exiting that bridge is source-NAT'd to the VPN's address.
|
# 2a. create a separate routing table for each VPN, with table id = ID.
|
||||||
# 2b. to route all traffic for a specific application (or container), give it access to only the bridge device.
|
# 2b. if a packet enters the VPN's table then it will be routed via the VPN.
|
||||||
# 3a. create a separate routing table for each VPN, with table id = ID.
|
# 2c. to apply a VPN to all internet traffic, system-wide, a rule is added that forces each packet to enter that VPN's routing table.
|
||||||
# 3b. if a packet enters the VPN's table then it will be routed via the VPN.
|
|
||||||
# 3c. to apply a VPN to all internet traffic, system-wide, a rule is added that forces each packet to enter that VPN's routing table.
|
|
||||||
# - that's done with `systemctl start vpn-$VPN`.
|
# - that's done with `systemctl start vpn-$VPN`.
|
||||||
# - the VPN acts as the default route. so traffic destined to e.g. a LAN device do not traverse the VPN in this case. only internet traffic is VPN'd.
|
# - the VPN acts as the default route. so traffic destined to e.g. a LAN device do not traverse the VPN in this case. only internet traffic is VPN'd.
|
||||||
|
# 3. to apply a VPN to internet traffic selectively, just proxy an applications traffic into the VPN device
|
||||||
|
# 3a. use a network namespace and a userspace TCP stack (e.g. pasta/slirp4netns).
|
||||||
|
# 3b. attach the VPN device to a bridge device, then connect that to a network namespace by using a veth pair.
|
||||||
|
# 3c. juse use `sanebox`, which abstracts the above options.
|
||||||
|
|
||||||
{ config, lib, pkgs, sane-lib, ... }:
|
{ config, lib, pkgs, sane-lib, ... }:
|
||||||
let
|
let
|
||||||
|
@ -41,6 +44,7 @@ let
|
||||||
fwmark = mkOption {
|
fwmark = mkOption {
|
||||||
type = types.int;
|
type = types.int;
|
||||||
};
|
};
|
||||||
|
# priority*: used externally, by e.g. `sane-vpn`
|
||||||
priorityMain = mkOption {
|
priorityMain = mkOption {
|
||||||
type = types.int;
|
type = types.int;
|
||||||
};
|
};
|
||||||
|
@ -86,13 +90,6 @@ let
|
||||||
dns servers to use for traffic associated with this VPN.
|
dns servers to use for traffic associated with this VPN.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
bridgeDevice = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "br-${name}";
|
|
||||||
description = ''
|
|
||||||
name of the bridge net device which will be created and configured so as to route all its outbound traffic over the VPN.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
privateKeyFile = mkOption {
|
privateKeyFile = mkOption {
|
||||||
type = types.either types.str types.path;
|
type = types.either types.str types.path;
|
||||||
description = ''
|
description = ''
|
||||||
|
@ -111,9 +108,7 @@ let
|
||||||
priorityFwMark = config.id + 300;
|
priorityFwMark = config.id + 300;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
mkVpnConfig = name: { id, dns, endpoint, publicKey, addrV4, privateKeyFile, bridgeDevice, priorityMain, priorityWgTable, priorityFwMark, fwmark, ... }: let
|
mkVpnConfig = name: { id, dns, endpoint, publicKey, addrV4, privateKeyFile, priorityMain, priorityWgTable, priorityFwMark, fwmark, ... }: {
|
||||||
bridgeAddrV4 = "10.20.${builtins.toString id}.1/24";
|
|
||||||
in {
|
|
||||||
assertions = [
|
assertions = [
|
||||||
{
|
{
|
||||||
assertion = (lib.count (c: c.id == id) (builtins.attrValues cfg)) == 1;
|
assertion = (lib.count (c: c.id == id) (builtins.attrValues cfg)) == 1;
|
||||||
|
@ -153,19 +148,6 @@ let
|
||||||
# networkConfig.DNSDefaultRoute = true;
|
# networkConfig.DNSDefaultRoute = true;
|
||||||
# Domains = ~.: system DNS queries are sent to this link's DNS server
|
# Domains = ~.: system DNS queries are sent to this link's DNS server
|
||||||
# networkConfig.Domains = "~.";
|
# networkConfig.Domains = "~.";
|
||||||
routingPolicyRules = [
|
|
||||||
{
|
|
||||||
routingPolicyRuleConfig = {
|
|
||||||
# send traffic from the container bridge out over the VPN.
|
|
||||||
# the bridge itself does source nat (SNAT) to rewrite the packet source address to that of the VPNs
|
|
||||||
# -- but that happens in POSTROUTING: *after* the kernel decides which interface to give the packet to next.
|
|
||||||
# therefore, we have to route here based on the packet's address as it is in PREROUTING, i.e. the bridge address. weird!
|
|
||||||
Priority = priorityWgTable;
|
|
||||||
From = bridgeAddrV4;
|
|
||||||
Table = id;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
routes = [{
|
routes = [{
|
||||||
routeConfig.Table = id;
|
routeConfig.Table = id;
|
||||||
routeConfig.Scope = "link";
|
routeConfig.Scope = "link";
|
||||||
|
@ -176,35 +158,6 @@ let
|
||||||
linkConfig.RequiredForOnline = false;
|
linkConfig.RequiredForOnline = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.network.netdevs."99-${bridgeDevice}" = {
|
|
||||||
netdevConfig.Kind = "bridge";
|
|
||||||
netdevConfig.Name = bridgeDevice;
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.network.networks."51-${bridgeDevice}" = {
|
|
||||||
matchConfig.Name = bridgeDevice;
|
|
||||||
networkConfig.Description = "NATs inbound traffic to ${name}, intended for container isolation";
|
|
||||||
networkConfig.Address = [ bridgeAddrV4 ];
|
|
||||||
networkConfig.DNS = dns;
|
|
||||||
# ConfigureWithoutCarrier: a bridge with no attached interface has no carrier (this is to be expected).
|
|
||||||
# systemd ordinarily waits for a carrier to be present before "configuring" the bridge.
|
|
||||||
# i tell it to not do that, so that i can assign an IP address to the bridge before i connect it to anything.
|
|
||||||
# alternative may be to issue the bridge a static MACAddress?
|
|
||||||
# see: <https://github.com/systemd/systemd/issues/9252#issuecomment-808710185>
|
|
||||||
networkConfig.ConfigureWithoutCarrier = true;
|
|
||||||
# networkConfig.DHCPServer = true;
|
|
||||||
linkConfig.RequiredForOnline = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
networking.localCommands = with pkgs; ''
|
|
||||||
# view rules with:
|
|
||||||
# - `sudo iptables -t nat --list-rules -v`
|
|
||||||
# rewrite the source address of every packet leaving the container so that it's routable by the wg tunnel.
|
|
||||||
# note that this alone doesn't get it routed *to* the wg device. we can't, since SNAT is POSTROUTING (not PREROUTING).
|
|
||||||
# that part's done by an `ip rule` entry.
|
|
||||||
${iptables}/bin/iptables -A POSTROUTING -t nat -s ${bridgeAddrV4} -j SNAT --to-source ${addrV4}
|
|
||||||
'';
|
|
||||||
|
|
||||||
# linux will drop inbound packets if it thinks a reply to that packet wouldn't exit via the same interface (rpfilter).
|
# linux will drop inbound packets if it thinks a reply to that packet wouldn't exit via the same interface (rpfilter).
|
||||||
# wg-quick has a solution via `iptables -j CONNMARK`, and that does work for system-wide VPNs,
|
# wg-quick has a solution via `iptables -j CONNMARK`, and that does work for system-wide VPNs,
|
||||||
# but i couldn't get that to work for netns with SNAT, so set rpfilter to "loose".
|
# but i couldn't get that to work for netns with SNAT, so set rpfilter to "loose".
|
||||||
|
|
|
@ -24,12 +24,13 @@ vpns=()
|
||||||
defaultVpn=
|
defaultVpn=
|
||||||
|
|
||||||
# loaded from a specific ~/.config/sane-vpn/vpns profile
|
# loaded from a specific ~/.config/sane-vpn/vpns profile
|
||||||
id=
|
addrV4=
|
||||||
fwmark=
|
|
||||||
prioMain=
|
|
||||||
prioFwMark=
|
|
||||||
bridgeDevice=
|
|
||||||
dns=()
|
dns=()
|
||||||
|
fwmark=
|
||||||
|
id=
|
||||||
|
name=
|
||||||
|
priorityFwMark=
|
||||||
|
priorityMain=
|
||||||
|
|
||||||
debug() {
|
debug() {
|
||||||
if [ -n "$SANE_VPN_DEBUG" ]; then
|
if [ -n "$SANE_VPN_DEBUG" ]; then
|
||||||
|
@ -46,15 +47,7 @@ getVpns() {
|
||||||
|
|
||||||
# load a specific VPN profile, `"$1"`
|
# load a specific VPN profile, `"$1"`
|
||||||
sourceVpn() {
|
sourceVpn() {
|
||||||
# populates:
|
# populates: variables declared above
|
||||||
# - id
|
|
||||||
# - fwmark
|
|
||||||
# - prioMain
|
|
||||||
# - prioFwMark
|
|
||||||
# - bridgeDevice
|
|
||||||
# - addrV4
|
|
||||||
# - name
|
|
||||||
# - dns
|
|
||||||
debug "sourcing: ~/.config/sane-vpn/vpns/$1"
|
debug "sourcing: ~/.config/sane-vpn/vpns/$1"
|
||||||
# TODO: don't blindly source this, but parse explicitly as `K=V`
|
# TODO: don't blindly source this, but parse explicitly as `K=V`
|
||||||
source ~/.config/sane-vpn/vpns/$1
|
source ~/.config/sane-vpn/vpns/$1
|
||||||
|
|
Loading…
Reference in New Issue
Block a user