all messages for Guix-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Maxim Cournoyer <maxim.cournoyer@gmail.com>
To: help-guix <help-guix@gnu.org>
Subject: A Wireguard VPN made from a virtual private server (VPS)
Date: Thu, 12 Sep 2024 00:21:50 +0900	[thread overview]
Message-ID: <87wmjixlkh.fsf@gmail.com> (raw)

[-- Attachment #1: Type: text/plain, Size: 3026 bytes --]

Hi,

Continuing on the OVH VPS machine that I could successfully install Guix
System to recently [0], I wanted to run Wireguard in a way that would
allow me to tunnel all my local traffic via it to the Internet (to make
it seem as if I was browsing from the country the VPS is in, say).

[0]  https://lists.gnu.org/archive/html/help-guix/2024-08/msg00125.html

Not being too knowledgeable about networking it took some efforts, but
the result is working nicely.

First I've created the secret and public keys with the 'wg' tool, as
explained in its manpage or many tutorials, both for the VPS and for my
own local machine. Then I've configured the wireguard-service-type on
the VPS with my peers, like this:

--8<---------------cut here---------------start------------->8---
;;;
(service wireguard-service-type
                     (wireguard-configuration
                      (peers
                       (list
                        (wireguard-peer
                         (name "terra")
                         (public-key "XXXX=")
                         (allowed-ips '("10.0.0.3/32"
                                        "fdb5:995f:152c::3/128")))
                        (wireguard-peer
                         (name "x200-laptop")
                         (public-key "YYYY=")
                         (allowed-ips '("10.0.0.2/32"
                                        "fdb5:995f:152c::2/128")))))))
--8<---------------cut here---------------end--------------->8---

The important bit is using fully specified /32 IPv4 addresses or /128
IPv6 addresses to avoid any overlapping to exist, otherwise the routes
setup by Wireguard on the VPS could for example redirect the traffic
only to the first peer if both shared the same network in 'allowed-ips'.

Locally on my own machine, I've opted to define my VPN connection via
NetworkManager, as that is convenient (especially from GNOME) to turn on
and off the VPN, as needed.  Since I'm tunelling all my traffic via it,
I don't want it always on, so having a 'wireguard-service-type' on my
local machine didn't make sense for my use case here.

The important details are the private wireguard key generated on the
local machine, and the peer which should match the $host:$port in
endpoint and the public wireguard key as configured on the VPS, as well
as the special 0.0.0.0/0 and ::/0 Allowed-IPs addresses to accept all
the traffic (wg-quick, used by the service, will create the routes
automatically for you).  The last important part of the NetworkManager
VPN connection configuration is that you must set a static IPv4 as well
as a static IPv6 address if you have one; these are the address to be
associated with the WireGuard interface such as wg0 and are necessary
for the traffic to be routed. I've used an ULA IPv6 generated address;
you want it to *not* be routable to the Internet, as we'll want the VPS
to masquerade (rewrite the source IPv6 or IPv4 into its own) our
addresses.

Below is the graphical NetworkManager configuration, if that helps:


[-- Attachment #2: NetworkManager WireGuard VPN - main --]
[-- Type: image/png, Size: 93593 bytes --]

[-- Attachment #3: Wireguard VPN peer config --]
[-- Type: image/png, Size: 45040 bytes --]

[-- Attachment #4: Wireguard VPN IPv4 config --]
[-- Type: image/png, Size: 124158 bytes --]

[-- Attachment #5: Wireguard VPN IPv6 config --]
[-- Type: image/png, Size: 124577 bytes --]

[-- Attachment #6: Type: text/plain, Size: 8405 bytes --]


Upon connecting to the VPN, NetworkManager will setup 'ip rule's that
will cause all you traffic to go through the 'wg0' interface.  The last
part of the puzzle is to configure the VPS networking so that it injects
the traffic found on its own 'wg0' (the other side of the WireGuard
tunnel) into its own network, as if it was from itself. After some
research, the important bits to achieve this is IP forwarding, which is
disabled in Linux by default (at least for Guix System's configuration),
as well as masquerading, which is source NAT (rewriting the source
address), which can be done with netfilter (nft).  The most critical
parts of the nftables "rule set" made to achieve this are, as an input
chain filter on the inet table:

    # forward all packets from WireGuard VPN to WAN
    iifname $wg_iface oifname $pub_iface accept

as well as

    # masquerade all packets from WireGuard VPN to the WAN
    iifname $wg_iface oifname $pub_iface masquerade

as a forward chain filter on that same inet table.

The complete configuration can be seen below:

--8<---------------cut here---------------start------------->8---
;; -*- mode: scheme; -*-
;; This is an operating system configuration template
;; for an OVH virtual private server (VPS), with no X11 display server.
(use-modules (gnu))
(use-service-modules networking ssh sysctl vpn)
(use-package-modules ssh)

(define %username "your-user")

(define %system
  (operating-system
    (host-name "vps-NNNN")
    (locale "en_US.utf8")

    (bootloader (bootloader-configuration
                 (bootloader grub-bootloader)
                 (targets '("/dev/sda"))))

    (kernel-arguments (list "console=ttyS0 console=tty0"))

    (file-systems (cons* (file-system
                           (device (uuid "bbf61fb4-b6ce-44af-ac57-1850cd708965"))
                           (mount-point "/")
                           (type "btrfs")
                           (options "compress=zstd"))
                         %base-file-systems))

    (initrd-modules (cons "virtio_scsi" ; Needed to find the disk
                          %base-initrd-modules))

    ;; This is where user accounts are specified.  The "root"
    ;; account is implicit, and is initially created with the
    ;; empty password.
    (users (cons (user-account
                  (name %username)
                  (group "users")
                  ;; Adding the account to the "wheel" group
                  ;; makes it a sudoer.  Adding it to "audio"
                  ;; and "video" allows the user to play sound
                  ;; and access the webcam.
                  (supplementary-groups '("wheel")))
                 %base-user-accounts))

    ;; Add services to the baseline: a DHCP client and an SSH
    ;; server.  You may wish to add an NTP service here.
    (services
     (append
      (list (service dhcp-client-service-type) ;for IPv4
            (service static-networking-service-type
                     (list (static-networking
                            (provision '(networking-v6))
                            (addresses
                             (list (network-address
                                    (device "eth0")
                                    (value "2607:5300:xxxx:xxxx::aaaa/64"))))
                            (routes
                             (list (network-route
                                    (device "eth0")
                                    (destination "default")
                                    (gateway "2607:5300:xxxx:xxxx::1")))))))
            (service openssh-service-type
                     (openssh-configuration
                      (openssh openssh-sans-x)
                      (port-number 2222)
                      (password-authentication? #f)
                      (authorized-keys
                       `((,%username
                          ,(plain-file "key.pub"
                                       "ssh-ed25519 $MY-PUB-SSH-KEY"))))))
            ;; This implements a 'point to site' Wireguard VPN config,
            ;; where site is the WAN in our case (see:
            ;; https://www.procustodibus.com/blog/2020/11/wireguard-point-to-site-config/,
            ;; https://www.procustodibus.com/blog/2021/11/wireguard-nftables/)
            (service wireguard-service-type
                     (wireguard-configuration
                      (peers
                       (list
                        (wireguard-peer
                         (name "terra")
                         (public-key "XXXXXXXXXXX=")
                         (allowed-ips '("10.0.0.3/32"
                                        "fdb5:995f:152c::3/128")))
                        (wireguard-peer
                         (name "x200-laptop")
                         (public-key "YYYYYYYYYYY=")
                         (allowed-ips '("10.0.0.2/32"
                                        "fdb5:995f:152c::2/128")))))))
            (service nftables-service-type
                     (nftables-configuration
                      (ruleset (plain-file "nftables.conf"
                                           "\
# A simple and safe firewall (based on %default-nftables-ruleset)
define pub_iface = \"eth0\"
define wg_iface = \"wg0\"
define wg_port = 51820
define ssh_port = 2222

table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;

    # early drop of invalid connections
    ct state invalid drop

    # allow established/related connections
    ct state { established, related } accept

    # allow from loopback
    iif lo accept
    # drop connections to lo not coming from lo
    iif != lo ip daddr 127.0.0.1/8 drop
    iif != lo ip6 daddr ::1/128 drop

    # allow icmp
    ip protocol icmp accept
    ip6 nexthdr icmpv6 accept

    # allow ssh
    tcp dport $ssh_port accept

    # allow wireguard port
    udp dport $wg_port accept

    # reject everything else
    reject with icmpx type port-unreachable
  }
  chain forward {
    type filter hook forward priority 0; policy drop;

    # forward all packets that are part of an already-established connection
    ct state vmap { invalid: drop, established: accept, related: accept }

    # forward all packets from WireGuard VPN to WAN
    iifname $wg_iface oifname $pub_iface accept

    # allow traffic between WireGuard peers
    iifname $wg_iface oifname $wg_iface accept

    # reject with polite 'host unreachable' icmp response
    reject with icmpx type host-unreachable
  }
  chain output {
    type filter hook output priority 0; policy accept;
  }
}

table inet nat {
  chain postrouting {
    type nat hook postrouting priority 100; policy accept;

    # masquerade all packets from WireGuard VPN to the WAN
    iifname $wg_iface oifname $pub_iface masquerade
  }
}
")))))
      (modify-services %base-services
        (guix-service-type config =>
                           (guix-configuration
                            (inherit config)
                            (authorized-keys
                             (cons* (local-file "x200-signing-key.pub")
                                    (local-file "terra-signing-key.pub")
                                    %default-authorized-guix-keys))))
        (sysctl-service-type config =>
                             (sysctl-configuration
                              (settings
                               ;; Enable IP-forwarding, for the WireGuard VPN.
                               (cons* '("net.ipv4.ip_forward" . "1")
                                      '("net.ipv6.conf.all.forwarding" . "1")
                                      (sysctl-configuration-settings config))))))))

    (sudoers-file
     (plain-file "sudoers"
                 (string-append (plain-file-content %sudoers-specification)
                                (format #f "~a ALL = NOPASSWD: ALL~%"
                                        %username))))))

(list (machine
       (operating-system %system)
       (environment managed-host-environment-type)
       (configuration (machine-ssh-configuration
                       (host-name "$my-vps-hostname")
                       (port 2222)
		       (user %username)
		       (host-key "ssh-ed25519 $my-host-key")
                       (system "x86_64-linux")))))
--8<---------------cut here---------------end--------------->8---

Hopefully this long text is useful to someone :-)

Happy hacking!

-- 
Maxim

                 reply	other threads:[~2024-09-11 15:58 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=87wmjixlkh.fsf@gmail.com \
    --to=maxim.cournoyer@gmail.com \
    --cc=help-guix@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/guix.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.