Skip to content

Instantly share code, notes, and snippets.

@andrewhamon
Last active January 1, 2023 05:28
Show Gist options
  • Select an option

  • Save andrewhamon/ed6b2e2b76a527a82e9cd80f4a4f75ca to your computer and use it in GitHub Desktop.

Select an option

Save andrewhamon/ed6b2e2b76a527a82e9cd80f4a4f75ca to your computer and use it in GitHub Desktop.

Revisions

  1. andrewhamon revised this gist Jan 1, 2023. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions namespaced-wg.nix
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    # This file is filly parameterized. Feel free to copy it verbatim, add it to your imports list, and configure
    # it. See configuration.nix for example configuration.
    { config, lib, pkgs, ... }:
    with lib;
    let
  2. andrewhamon revised this gist Jan 1, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion configuration.nix
    Original file line number Diff line number Diff line change
    @@ -34,6 +34,6 @@
    #
    # To do this right, you need to correctly guess the name of the systemd service that corresponds to the nixos service.
    # Usually its the same name, but to be sure you can check the source code in nixpkgs. If you are already running the
    # service, you can try `sudo systemctl status <name>.service` to see if its right
    # service, you can also try `sudo systemctl status <name>.service` to see if you guessed right.
    systemd.services.transmission = config.services.namespaced-wg.systemdMods;
    }
  3. andrewhamon revised this gist Jan 1, 2023. 1 changed file with 5 additions and 0 deletions.
    5 changes: 5 additions & 0 deletions configuration.nix
    Original file line number Diff line number Diff line change
    @@ -15,6 +15,7 @@
    services.namespaced-wg.hostPortalIp = "10.69.44.1"; # Use a different subnet than your LAN
    services.namespaced-wg.guestPortalIp = "10.69.44.2";

    # Enable any service like normal, in this case we use transmission
    services.transmission = {
    enable = true;
    settings = {
    @@ -30,5 +31,9 @@

    # This is the magic. Modify the transmission systemd config so that it runs under the seedbox network namespace
    # It can only access the inernet via the wireguard VPN.
    #
    # To do this right, you need to correctly guess the name of the systemd service that corresponds to the nixos service.
    # Usually its the same name, but to be sure you can check the source code in nixpkgs. If you are already running the
    # service, you can try `sudo systemctl status <name>.service` to see if its right
    systemd.services.transmission = config.services.namespaced-wg.systemdMods;
    }
  4. andrewhamon revised this gist Jan 1, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion configuration.nix
    Original file line number Diff line number Diff line change
    @@ -10,7 +10,7 @@
    services.namespaced-wg.name = "seedbox"; # Name this whatever, but keep it short
    services.namespaced-wg.ips = ["mulvad ipv4" "mulvad ipv6"];
    services.namespaced-wg.peerPublicKey = "mulvad public key";
    services.namespaced-wg.peerEndpoint = "142.147.89.240:51820";
    services.namespaced-wg.peerEndpoint = "mulvad.wireguard.endpoint:51820";
    services.namespaced-wg.privateKeyFile = "/etc/secrets/wireguard_mullvad_key";
    services.namespaced-wg.hostPortalIp = "10.69.44.1"; # Use a different subnet than your LAN
    services.namespaced-wg.guestPortalIp = "10.69.44.2";
  5. andrewhamon revised this gist Jan 1, 2023. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions configuration.nix
    Original file line number Diff line number Diff line change
    @@ -25,8 +25,6 @@
    rpc-authentication-required = false;
    watch-dir-enabled = true;
    # peer-port = 1234; # Provision a port in mulvad UI and set it here
    ratio-limit-enabled = true;
    ratio-limit = 0;
    };
    };

  6. andrewhamon revised this gist Jan 1, 2023. 1 changed file with 22 additions and 2 deletions.
    24 changes: 22 additions & 2 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -1,10 +1,30 @@
    ### Execute the following to confirm everything

    ```sh
    ip netns exec seedbox wg
    NET_NS=seedbox # Or whichever name you chose in your config
    ```

    <img width="508" alt="image" src="https://user-images.githubusercontent.com/5134584/210160307-03e1581e-3be1-42aa-9f39-a628139341d8.png">
    ### Ensure wireguard is working

    ```sh
    ip netns exec $NET_NS wg
    ```

    <img width="493" alt="image" src="https://user-images.githubusercontent.com/5134584/210160341-54cdd59f-80ef-4a3a-bbed-77124ced16ba.png">

    ### Check that the interfaces look right

    ```sh
    ip netns exec $NET_NS ifconfig
    ```

    <img width="745" alt="image" src="https://user-images.githubusercontent.com/5134584/210160355-b4e72244-b205-411a-a2a4-2e2cc4851887.png">

    ### Ensure that the expected processes are running in the namespace

    ```sh
    ps $(ip netns pids $NET_NS)
    ```

    <img width="1382" alt="image" src="https://user-images.githubusercontent.com/5134584/210160372-bf8c4f8c-613a-41ec-b7b9-c7420a4e321e.png">

  7. andrewhamon revised this gist Jan 1, 2023. 1 changed file with 10 additions and 0 deletions.
    10 changes: 10 additions & 0 deletions readme.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,10 @@
    ### Execute the following to confirm everything

    ```sh
    ip netns exec seedbox wg
    ```

    <img width="508" alt="image" src="https://user-images.githubusercontent.com/5134584/210160307-03e1581e-3be1-42aa-9f39-a628139341d8.png">



  8. andrewhamon revised this gist Jan 1, 2023. No changes.
  9. andrewhamon revised this gist Jan 1, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion configuration.nix
    Original file line number Diff line number Diff line change
    @@ -8,7 +8,7 @@
    # Set up wireguard + a new network namespace using the module defined in hardware-configuration.nix
    services.namespaced-wg.enable = true;
    services.namespaced-wg.name = "seedbox"; # Name this whatever, but keep it short
    services.namespaced-wg.ips = ["mulvad ipv4", "mulvad ipv6"];
    services.namespaced-wg.ips = ["mulvad ipv4" "mulvad ipv6"];
    services.namespaced-wg.peerPublicKey = "mulvad public key";
    services.namespaced-wg.peerEndpoint = "142.147.89.240:51820";
    services.namespaced-wg.privateKeyFile = "/etc/secrets/wireguard_mullvad_key";
  10. andrewhamon revised this gist Jan 1, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion configuration.nix
    Original file line number Diff line number Diff line change
    @@ -3,7 +3,7 @@
    imports = [
    ./hardware-configuration.nix
    ./namespaced-wg.nix
    ]
    ];

    # Set up wireguard + a new network namespace using the module defined in hardware-configuration.nix
    services.namespaced-wg.enable = true;
  11. andrewhamon created this gist Jan 1, 2023.
    36 changes: 36 additions & 0 deletions configuration.nix
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,36 @@
    { config, pkgs, ... }:
    {
    imports = [
    ./hardware-configuration.nix
    ./namespaced-wg.nix
    ]

    # Set up wireguard + a new network namespace using the module defined in hardware-configuration.nix
    services.namespaced-wg.enable = true;
    services.namespaced-wg.name = "seedbox"; # Name this whatever, but keep it short
    services.namespaced-wg.ips = ["mulvad ipv4", "mulvad ipv6"];
    services.namespaced-wg.peerPublicKey = "mulvad public key";
    services.namespaced-wg.peerEndpoint = "142.147.89.240:51820";
    services.namespaced-wg.privateKeyFile = "/etc/secrets/wireguard_mullvad_key";
    services.namespaced-wg.hostPortalIp = "10.69.44.1"; # Use a different subnet than your LAN
    services.namespaced-wg.guestPortalIp = "10.69.44.2";

    services.transmission = {
    enable = true;
    settings = {
    rpc-bind-address = "0.0.0.0";
    rpc-port = 8080;
    rpc-host-whitelist-enabled = false;
    rpc-whitelist-enabled = false;
    rpc-authentication-required = false;
    watch-dir-enabled = true;
    # peer-port = 1234; # Provision a port in mulvad UI and set it here
    ratio-limit-enabled = true;
    ratio-limit = 0;
    };
    };

    # This is the magic. Modify the transmission systemd config so that it runs under the seedbox network namespace
    # It can only access the inernet via the wireguard VPN.
    systemd.services.transmission = config.services.namespaced-wg.systemdMods;
    }
    129 changes: 129 additions & 0 deletions namespaced-wg.nix
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,129 @@
    { config, lib, pkgs, ... }:
    with lib;
    let
    cfg = config.services.namespaced-wg;
    in
    {
    options.services.namespaced-wg = {
    enable = mkEnableOption "Namespaced Wireguard";

    # Name should be relatively short. It is used for interface names, which
    # seem to break if they exceed 15 characters.
    name = mkOption {
    type = types.str;
    };
    ips = mkOption {
    type = types.listOf types.str;
    };
    peerPublicKey = mkOption {
    type = types.str;
    };
    peerEndpoint = mkOption {
    type = types.str;
    };
    peerAllowedIps = mkOption {
    type = types.listOf types.str;
    default = [ "0.0.0.0/0" "::/0" ];
    };
    privateKeyFile = mkOption {
    type = types.str;
    };
    guestPortalIp = mkOption {
    type = types.str;
    };
    hostPortalIp = mkOption {
    type = types.str;
    };

    # This isn't exactly config, but I couldn't think of a better way to easily
    # let us reference this attrset elsewhere. This is used to mod the systemd
    # config of any existing service. These changes will cause the services to
    # come up in the network namespace
    systemdMods = mkOption {
    type = types.anything;
    default = {
    after = ["network.target" "netns_${config.services.namespaced-wg.name}.service"];
    bindsTo = ["netns_${config.services.namespaced-wg.name}.service"];
    partOf = ["netns_${config.services.namespaced-wg.name}.service"];
    serviceConfig.NetworkNamespacePath = "/var/run/netns/${config.services.namespaced-wg.name}";
    };
    };
    };
    config = mkIf cfg.enable {
    networking.wireguard.interfaces."${cfg.name}" = {
    ips = cfg.ips;
    privateKeyFile = cfg.privateKeyFile;
    interfaceNamespace = cfg.name;
    peers = [
    {
    publicKey = cfg.peerPublicKey;
    allowedIPs = cfg.peerAllowedIps;
    endpoint = cfg.peerEndpoint;
    }
    ];
    };

    # Modify the wireguard systemd service (implicitly defined using the wireguard
    # module above) to wait for the netns_${cfg.name} service (defined below)
    # to be active. This ensures that the network namespace has already been set
    # up before creating the wireguard interface.
    systemd.services."wireguard-${cfg.name}" = {
    after = ["network.target" "network-online.target" "netns_${cfg.name}.service"];
    bindsTo = ["netns_${cfg.name}.service"];
    partOf = ["netns_${cfg.name}.service"];
    };

    # Create a systemd service that does the following:
    # - creates a new netowrk namespace
    # - creates an pair of veth interfaces and IP addresses that allow
    # communication with processes inside the namespace. This is essential to
    # be able to view UIs and such. Only very specific IP routes are added,
    # these IPs and interfaces will not enable any communication beyond the
    # host.
    #
    # The naming convention is that on the host outside of any network
    # namespace, there is an interface named ${cfg.name}_portal. This connects
    # directly to an interface that is moved inside of the network namespace,
    # named "${cfg.name}_hportal". Software running inside the namesapce can
    # bind to ${cfg.name}_hportal (or 0.0.0.0 to bind to all interfaces) and
    # become accessible from outside the namespace only through the
    # ${cfg.name}_portal interface.
    systemd.services."netns_${cfg.name}" = {
    description = "${cfg.name} network namespace";
    before = [ "network.target" ];
    serviceConfig = {
    Type = "oneshot";
    RemainAfterExit = true;
    ExecStop = "ip netns del ${cfg.name}";
    };
    script = ''
    ipCmd="${pkgs.iproute}/bin/ip"
    set -x
    # Delete the ns if it already exists. Mostly handy for developemt, in
    # case this setup fails partway through and leaves things in an odd
    # state.
    ($ipCmd netns list | grep ${cfg.name}) && $ipCmd netns delete ${cfg.name}
    $ipCmd netns add ${cfg.name}
    # It seems like netns delete doesn't immediately clean up all the
    # related resources. If we are too fast, recreating interfaces with
    # the same name will fail. RIP.
    sleep 3
    $ipCmd link add ${cfg.name}_portal type veth peer ${cfg.name}_hportal
    $ipCmd link set dev ${cfg.name}_hportal netns ${cfg.name}
    $ipCmd addr add ${cfg.hostPortalIp}/32 dev ${cfg.name}_portal
    $ipCmd netns exec ${cfg.name} $ipCmd addr add ${cfg.guestPortalIp}/32 dev ${cfg.name}_hportal
    $ipCmd link set dev ${cfg.name}_portal up
    $ipCmd route add ${cfg.guestPortalIp}/32 dev ${cfg.name}_portal
    $ipCmd netns exec ${cfg.name} $ipCmd link set dev ${cfg.name}_hportal up
    $ipCmd netns exec ${cfg.name} $ipCmd route add ${cfg.hostPortalIp}/32 dev ${cfg.name}_hportal
    '';
    };
    };
    }