Add post about wireguard IP tunneling

This commit is contained in:
Avery Winters 2023-10-04 13:40:15 -05:00
parent f8651c959f
commit 86bc53e0a7
Signed by: avery
SSH key fingerprint: SHA256:eesvLB5MMqHLZrAMFt6kEhqJWnASMLcET6Sgmw0FqZI
2 changed files with 170 additions and 6 deletions

View file

@ -10,8 +10,6 @@ updated = 2023-10-04
tags = [ "selfhosting", "nix", "privacy", "security", "networks" ] tags = [ "selfhosting", "nix", "privacy", "security", "networks" ]
+++ +++
# Security & Infrastructure
**Note**: This post was updated to reflect a change in the number of servers **Note**: This post was updated to reflect a change in the number of servers
I use to host everything. I use to host everything.
@ -33,12 +31,12 @@ nameserver in case my home network is down.
The goal with all of this is to have some basic redundancy, while keeping The goal with all of this is to have some basic redundancy, while keeping
sensitive keys and all personal data safely on my physical server. sensitive keys and all personal data safely on my physical server.
## DNSSEC # DNSSEC
`amsterdam` holds a [combined signing key][13] for the zone. Dynamic updates `amsterdam` holds a [combined signing key][13] for the zone. Dynamic updates
are allowed using a [TSIG][1] key to allow [ACME DNS-01 challenges][2] for are allowed using a [TSIG][1] key to allow [ACME DNS-01 challenges][2] for
issuing TLS certificates. issuing TLS certificates.
## TLS/HTTPS # TLS/HTTPS
`amsterdam` holds a [Let's Encrypt][3] wildcard TLS certificate for the domain, `amsterdam` holds a [Let's Encrypt][3] wildcard TLS certificate for the domain,
which is used to protect web services. The DNS zone contains a [CAA][4] record which is used to protect web services. The DNS zone contains a [CAA][4] record
specifying that only Let's Encrypt may issue certificates for the domain, and specifying that only Let's Encrypt may issue certificates for the domain, and
@ -46,13 +44,13 @@ only using ACME DNS-01 challenges. All TLS-capable services have TLSA records
associated with them for [DANE-EE][5] support. Finally, all web services use associated with them for [DANE-EE][5] support. Finally, all web services use
[HTTPS][6] records and [HSTS preload][7] headers to advertise support for HTTPS. [HTTPS][6] records and [HSTS preload][7] headers to advertise support for HTTPS.
## Email # Email
`amsterdam` holds [DKIM][8] keys for the domain, which is published in DNS `amsterdam` holds [DKIM][8] keys for the domain, which is published in DNS
alongside [SPF][9] and [DMARC][10] records together protect against spoofing alongside [SPF][9] and [DMARC][10] records together protect against spoofing
the domain. [MTA-STS][11] and DANE-EE are used to advertise TLS support for the domain. [MTA-STS][11] and DANE-EE are used to advertise TLS support for
incoming mail. Outgoing mail requires that the receiving server support TLS. incoming mail. Outgoing mail requires that the receiving server support TLS.
## WireGuard # WireGuard
Both servers hold [WireGuard][14] keys for their end of the tunnels. The tunnel Both servers hold [WireGuard][14] keys for their end of the tunnels. The tunnel
being encrypted and authenticated isn't actually important for my purposes. This being encrypted and authenticated isn't actually important for my purposes. This
could just as easily use another tunneling protocol like [GRE][12], but I find could just as easily use another tunneling protocol like [GRE][12], but I find

View file

@ -0,0 +1,166 @@
+++
title = "Tunneling Static Public IPs over WireGuard"
description = """\
A technique to assign static public IP addresses to a remote machine using WireGuard. \
"""
date = 2023-10-04
[taxonomies]
tags = [ "selfhosting", "nix", "privacy", "security", "networks" ]
+++
# Motivation
Let's say you have a server somewhere, it has access to the internet, but maybe it:
1. is behind a [NAT][0] (or even a [CGNAT][1]); or
2. is restricted from incoming or outgoing ports (e.g. 25, 53, 80, 443, etc.); or
3. is behind a restrictive firewall; or
4. has a dynamic IP public address; or even
5. has all of the above (if like me, you're using a residential internet service provider)!
In any case, you want to self-host some services and provide access to them from the open
internet, without fighting with NAT or firewall port forwarding, dynamic DNS, or an inability
to receive incoming connections. Wouldn't it be nice if you could just rent a static, public
IP address and assign it to your server? We can get close, but we'll need the help of a VPS
provider, and some legwork to setup a [WireGuard][3] tunnel and some niche networking settings.
# Walkthrough
The first thing you'll need is a VPS. Even the smallest configurations will do. I will describe
the steps to follow if you're using [NixOS][6] on both the VPS and your server, but the steps should
translate well to any capable server OS. There are only a few criteria to look for:
1. Allows assigning at least two pairs of static, public IP addresses to your server. They will
likely give you an entire /48 or /56 of IPv6 space to use, but you will want at least two IPv4
addresses.
2. Has opened all the ports you want to host on (outgoing SMTP port 25 can be hard to find).
3. Has reputable IP addresses so you don't start out on any spam/blocklists (again,
particularly important for outgoing SMTP).
I use [Contabo][2] (not affiliated) and so far, it's been a good experience across all three
criteria.
For simplicity, let's call this VPS your gateway server, and the other your hosting server.
One of the gateway's IP address pairs it will use for itself. On this IP address, we will
host the "server" half of our WireGuard tunnel and likely SSH for administration. You can
of course also host any normal services you like. I personally use mine as a backup
nameserver. You can configure this pair of IP addresses manually on the gateway's main network
interface. On my gateway, I use the following config but yours of course may vary:
```nix
{...}: {
networking = {
enableIPv6 = true;
interfaces.ens18 = {
# Allow default route and nameservers to be autoconfigured.
useDHCP = true;
tempAddress = "disabled";
ipv4.addresses = [
{
address = "209.126.80.126";
prefixLength = 21;
}
];
ipv6.addresses = [
{
address = "2605:a140:2146:1434:8abc:eb50:da25:5f70";
prefixLength = 64;
}
];
};
defaultGateway6 = {
address = "fe80::1";
interface = "ens18";
};
hosts = {
"209.126.80.126" = ["edinburgh.averywinters.org" "edinburgh"];
"2605:a140:2146:1434:8abc:eb50:da25:5f70" = ["edinburgh.averywinters.org" "edinburgh"];
};
};
}
```
The second IP address pair we will not assign to any network interface on the gateway. We
will, however, configure the server to use [Proxy ARP][4], proxy [NDP][5] and IP forwarding.
This way, the gateway server will responds to ARP and NDP requests for the secondary IP
address pair from the upstream router. And, once we configure the WireGuard interface, it
will forward those incoming and outgoing packets between the tunnel interface and the gateway's
primary interface. The config looks like this for me:
```nix
{...}: {
# Enable forwarding and proxy ARP + NDP so we can route our secondary IPs to
# amsterdam over wireguard.
boot.kernel.sysctl = {
"net.ipv4.conf.all.forwarding" = true;
"net.ipv6.conf.all.forwarding" = true;
"net.ipv4.conf.all.proxy_arp" = true;
"net.ipv6.conf.all.proxy_ndp" = true;
};
# Proxy ARP and NDP (adding the IPv4 address here is optional,
# but I like the consistency).
networking.localCommands = ''
ip -4 neigh add proxy 154.12.229.99 dev ens18
ip -6 neigh add proxy 2605:a140:2146:1434:a2c5:34bb:afcf:101d dev ens18
'';
}
```
Next, we will setup the server end of the WireGuard tunnel on our gateway server like so.
Once this is up, the gateway server will start forwarding any packets it gets over the
tunnel.
```nix
{config, ...}: {
# Route our secondary IP addresses to amsterdam.
networking.wg-quick.interfaces.wg-amsterdam = {
# The addresses here are our gateway's IP as seen from the hosting server inside
# the tunnel. You can pick any private addresses you like.
address = ["192.168.88.3/32" "fd3a:e9a5:7a16:3d11:55c8:e71e:74d3:2c3a/128"];
listenPort = 51820;
privateKeyFile = config.age.secrets.wireguardKey.path;
peers = [
{
publicKey = "zMxFfsMdUEtK/n0+OK+4z0db+FTl/SH1F7EB27Z8jls=";
allowedIPs = ["154.12.229.99/32" "2605:a140:2146:1434:a2c5:34bb:afcf:101d/128"];
}
];
};
}
```
Finally, we configure the client end of the WireGuard tunnel on our hosting server. This
interface will also be configured as the default route (for anything that isn't the
WireGuard traffic itself of course).
```nix
{...}: {
# Use edinburgh's secondary IP addresses as our public IP and default route.
networking = {
enableIPv6 = true;
hosts = {
"154.12.229.99" = ["amsterdam.averywinters.org" "amsterdam"];
"2605:a140:2146:1434:a2c5:34bb:afcf:101d" = ["amsterdam.averywinters.org" "amsterdam"];
};
wg-quick.interfaces.wg-edinburgh = {
address = ["154.12.229.99/32" "2605:a140:2146:1434:a2c5:34bb:afcf:101d/128"];
privateKeyFile = config.age.secrets.wireguardKey.path;
peers = [
{
# edinburgh, but don't use fqdn because DNS isn't working yet
endpoint = "209.126.80.126:51820";
publicKey = "iH7+ZqiqYJ/HMDshLP8gjdZYd175frk6qi4YBKaw0BA=";
# needed to keep the tunnel alive behind a NAT or dynamic IP
persistentKeepalive = 25;
allowedIPs = ["0.0.0.0/0" "::/0"];
}
];
};
};
}
```
Once you've done this, your hosting server should be able to accept incoming traffic
on its new IP addresses, and should have all its outgoing traffic originate from its
new IP addresses as well!
[0]: https://en.wikipedia.org/wiki/Network_address_translation
[1]: https://en.wikipedia.org/wiki/Carrier-grade_NAT
[2]: https://contabo.com/en/
[3]: https://www.wireguard.com
[4]: https://en.wikipedia.org/wiki/Proxy_ARP
[5]: https://en.wikipedia.org/wiki/Neighbor_Discovery_Protocol
[6]: https://nixos.org