OpenBSD Wireguard Server

This assumes a server with a static address. Things will be more complicated if all the hosts roam. There are two client systems, an OpenBSD laptop, and a macOS laptop, both behind NAT. The same wireguard interface on the server (for better or worse) is used by the client systems; a (perhaps better) alternative would be to configure distinct wg0 and wg1 devices for the two clients on the server.

Configuration

macos-client.gmi

openbsd-client.gmi

This requires some back-and-forth between the systems to generate a private key on one system for the public key to copy to the other. Or, generate the keypairs elsewhere so that everything necessary is already known. The SHOUTYCAPS parts will need to be replaced with appropriate key material. A real system probably should not use documentation subnets; see RFC 1918 or RFC 4193 and maybe check with other folks you might peer with to see if they have claimed any subnets already.

    wgkey SERVERPRIVATEKEY
    wgpeer CLIENTPUBLICKEY wgdescr openbsd-client wgaip 192.0.2.2/32 wgaip 2001:db8:d0c::2/128 wgpsk PRESHAREDKEY
    wgpeer CLIENTPUBLICKEY wgdescr macos-client wgaip 192.0.2.3/32 wgpsk PRESHAREDKEY
    inet 192.0.2.1/24
    inet6 2001:db8:d0c::1/64
    wgport 4433
    up

Traffic is limited by "wgaip" (similar to "AllowedIPs" in the wireguard configuration) to allow traffic from, and to route to, only the IP addresses of the client's side of the tunnel.

Firewall Rules

Put these rules near the top of the pf.conf file, I have them below the leading skip/table/queue statements.

    ...
    table <good> persist file "/etc/goodhosts"
    ...
    pass out quick on egress from wg0:network to any nat-to (egress)
    pass quick on wg0
    pass in quick proto udp from <good> to any port 4433 keep state

There's other rules, and rule order matters, but the above allows all traffic on the wg0 interface (should it?) and performs NAT from wg0 to the internet for both IPv4 and IPv6. The "good" table limits what remote IP addresses can access the VPN, and is managed by other means. I do not use nor support random client IP addresses so can get away with restricted access to services, so presenting less of an attack surface is good. The trade-offs may change if you or have users who do use random clients from random source addresses, and do not have good tooling to (temporarily?) add remote networks to the "good" table.

The server will need to be turned into a router for the NAT to happen.

    # sysctl | fgrep .forward
    net.inet.ip.forwarding=1
    net.inet6.ip6.forwarding=1

DNS

A caching DNS server is configured on the server; this allows clients to perform DNS lookups via the VPN, and thus avoids leaking DNS requests to the client's ISP. (But may leak DNS to the server's ISP, and may break DNS when the VPN goes down. Trade-offs!)

server:
        interface: 127.0.0.1
        interface: ::1
        interface: 192.0.2.1
        interface: 2001:db8:d0c::1

        access-control: 0.0.0.0/0 refuse
        access-control: 127.0.0.0/8 allow
        access-control: 192.0.2.0/24 allow
        access-control: ::0/0 refuse
        access-control: ::1 allow
        access-control: 2001:db8:d0c::/64 allow

        hide-identity: yes
        hide-version: yes
        ...

SMTP Relay

Here one configures the Mail Transport Agent on the server to allow relaying for clients on the VPN. The relay could be totally open, or one might require TLS verification (good for other systems under your control) or SMTP AUTH passwords (good for end-user programs such as Mail.app). An open relay may become very problematic if a spammer gains access to the VPN, or if the configuration turns the internet-facing side of the mail server into an open relay.

    match from src "192.0.2.2" for domain "example.org" action "local_mail"
    match from src "192.0.2.2" for any action "outbound"

Another option is to use distinct mail servers for the roles of "handling mail from the internet" and "handling mail from client systems and users", as then a misconfiguration of the user-facing service is less likely to create an open relay on the internet side of things, and different levels of spam control and relaying can be applied where appropriate.

Restart

Be sure to perform the destroy and startup as a single command; this means the startup will be run regardless of whether the existing connection to the server drops between the two commands. If your connection goes away after you input the first command, then running the second command may be difficult.

    # ifconfig wg0 destroy ; sh /etc/netstart wg0

A similar method can be used for testing firewall rule changes, to load the new configuration, wait for some amount of time, and then to rollback the changes. This means if the changes are not good (presumably you are testing things during that "some amount of time") they will be automatically rolled back.