A shell script to quickly and easily set up a simple WireGuard VPN.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Tim Weber 1981499dd6
Update example to use # instead of $
6 months ago
LICENSE.txt Add MIT license 9 months ago
README.md Update example to use # instead of $ 6 months ago
wg-party Introduce -S for Salt "stateful" compatibility 6 months ago

README.md

wg-party

A shell script to quickly and easily set up a simple WireGuard VPN.

Current Limitations

wg-party is deliberately kept simple. It will never support arbitrarily complex WireGuard setups, but instead focus on common usage patterns.

These are its current limitations, along with some information on whether you can consider them to be permanent or just “not implemented yet”.

  • The generated VPN will be IPv4 only. Hosts may connect to each other using IPv6 over the unencrypted network, but inside the VPN, everything is IPv4. This is unlikely to change soon, because I simply dont see any need to.
  • The network will be a /24 IPv4 network, but you can choose your prefix freely. Some flexibility is planned for the future (e.g. allowing a /16 or /20).
  • There is currently no way to inject additional lines (e.g. PostUp and others) to the generated config file. Im thinking of adding per-host config files that would allow such a thing.
  • The topology is fixed: a classic “star” or “hub-and-spoke” model, i.e. one central machine (the “hub”) that every other machine connects to. The hub is required to be reachable by each host using a fixed IP address or DNS name and a fixed UDP port. If you need peer-to-peer communication, bi-directional NAT traversal and the like, you might want to check out Nebula, Tailscale, Headscale or ZeroTier instead.

Setting Up

Some basic principles:

  • WireGuard is using a public and a private key per host.
  • Both are simple, short strings. The public key can be derived from the private key, but not the other way around.
  • The private key should be generated on the host itself and never leave it.

wg-party uses two configuration files:

  • wg-party.conf.sh contains basic information about your network, like its name, IP prefix, and which machine is the hub.
  • wg-party.hosts contains the list of hosts that exist on your network, together with their VPN IPs and public keys.

From these two configuration files, a file compatible with wg-quick will be generated. This file is also the only place that contains the private key of the machine, so make sure not to lose it!

When your networks configuration or the list of hosts changes, an existing WireGuard config file can be updated, without losing the private key for the current machine.

To set up a network from scratch, these are the steps to take:

Installation

Install WireGuard and especially wg-quick on the host (e.g. using apt install wireguard-tools).

Also put a copy of wg-party on the machine, e.g. to /usr/local/sbin.

Your system should also have an implementation of mktemp that accepts a --tmpdir flag, e.g. the one from GNU coreutils.

Configuration File

Change your current working directory to some location where you want to place wg-partys configuration files, e.g. /etc/wg-party or /root. Create a file named wg-party.conf.sh there. This file will not contain any secrets, so you dont need to hide it from other users.

As you can see from the files name, it is a shell script and will be sourced by wg-party at runtime. This means you can get fancy when writing it and put some code and logic in there (no bashisms though, wg-party is using /bin/sh), but you dont have to. In the most simple case, you will just set variables.

A simple example would look like this:

# Name of the generated config file as well as the network interface.
netname=myvpn

# IP prefix. Currently, wg-party is limited to /24 networks.
prefix=192.168.123

# The internal name of the machine that will serve as the network's hub.
hub_name=jita

# The DNS name (or IP address) and UDP port that other hosts will use to connect
# to the hub. (Technically, the other hosts will also use `port` as their own
# listening port, which is why it's not prefixed with "hub_".)
hub_host=vpn.example.com
port=1337

# There are a few more variables you can configure. They are listed below,
# together with their default values.

# The MTU to use for the VPN. The default is somewhat low, but also probably
# safe to use in whatever the VPN packets may be encapsulated in.
# mtu=1312

# How often (in seconds) to send keepalive packets to the hub, e.g. for NAT
# traversal. The default should work for most users, but if you want to save
# every last bit of bandwith (or are on a dial-up connection), you might want to
# set this to a higher number (up to 65535) or even to 0 to disable it.
# keepalive=25

# Where to look for the hosts file. A relative path will be relative to the
# current working directory when invoking wg-party, not to this config file.
# hosts_file=./wg-party.hosts

# Where to place the generated WireGuard config. The default is compatible to
# wg-quick and will probably not need to be changed.
# gen_dir=/etc/wireguard

# The internal name for the current machine.
# hostname="$(hostname -s)"

Hosts File

First, find out how the current machine thinks its called by running hostname -s. This will likely be correct, but if it isnt, either fix it (e.g. using hostnamectl), or override the name that wg-party is going to use by setting hostname=... in the config file above.

For this example, we will assume that youre on a machine named jita, which you intend to use as the hub of your setup.

In the same directory as youve placed your wg-party.conf.sh, create a new file named wg-party.hosts (or whatever you set hosts_file to). This file will contain the list of hosts and their associated public keys. Again, this is not top secret information, so you dont need to take special measures to protect the file.

We will start by adding an entry for the current machine. As we have not generated a keypair yet (and in fact, would like to delegate this to wg-party), we are going to set ? as the public key. The IP number will be appended to whatever you set prefix to in your config file. You can choose any number between 1 and 254, but I prefer to use 254 for the hub.

# My hub.
jita  254  ?

As you can see, comments (starting with #) are allowed, but only on a line of their own. Empty lines will be ignored, too. You may use multiple spaces and/or tabs between fields in a host line.

In the future, wg-party will probably include a command to help you add a new host.

Generating the WireGuard Config

Next, call wg-party generate to create a private key, add the corresponding public key to the hosts file, and generate a WireGuard config. If you have not placed wg-party in your PATH, you will need to call it from whereever you placed it, but dont change your current working directory, or wg-party wont find your config. (If you really want to, you can supply the path to the config file using the WGPARTY_CONFIG environment variable, which defaults to ./wg-party.conf.sh. Note that you will need to prepend ./ to say “in the current working directory”; this is because it will be shell-sourced using the . command.)

$ sudo wg-party generate
wg-party: will create a new config at /etc/wireguard/myvpn.conf
wg-party: config written successfully

Note that to will need to run it with appropriate permissions to write into the output directory (i.e. gen_dir from the config). So, by default, to write to /etc/wireguard, youll need to run this as root.

You should now have a file /etc/wireguard/myvpn.conf that looks something like this:

# WireGuard config for jita, generated by wg-party.
# Sat Apr 30 15:46:55 CEST 2022

[Interface]
PrivateKey = 8Il2b11tCYu4y8hFjWsdZ9zOWauZh+2yXCBytPXxyGE=
Address = 192.168.123.254/24
ListenPort = 1337
MTU = 1312

This file indeed does contain a secret (and was therefore created using 0600 permissions): The private key of the local machine.

You will also have noticed that the line in wg-party.hosts will have changed. The file should now look like this:

# My hub.
jita  254  VOxK9X4goEurmNabn9hxVa5DHjDpt478G1xPWQ5LiX4=

If your distribution comes with a systemd unit to run wg-quick definitions as a service, you should now be able to do something like systemctl enable --now wg-quick@myvpn to start listening for connections.

Adding a Second Host

To add a second host, we basically repeat the steps from above on the new host, i.e.

  • install wg-quick
  • install wg-party
  • copy the config file & hosts file to the new host
  • add a line like clacille 1 ? to wg-party.hosts (if the host is named clacille)
  • run wg-party generate
  • start WireGuard using systemctl enable --now wg-quick@myvpn.

You should now have two hosts (and their associated public keys) in wg-party.hosts, and the generated /etc/wireguard/myvpn.conf should contain an [Interface] section for the local machine (clacille) as well as a [Peer] section for the hub (jita). Also, clacille will have started to try and connect to jita. So, are we done yet?

We are not! Because jita does not know anything about clacille yet and will therefore not accept connections from it.

Telling the Hub About It

This last step is pretty easy actually. You just copy the wg-party.hosts file from clacille back to jita and run wg-party generate there again. The resulting /etc/wireguard/myvpn.conf will now contain a [Peer] section for clacille and, once you restart wireguard (systemctl restart wg-quick@myvpn), jita should accept clacilles connection requests and both should be able to ping each other over the VPN.

What to Do With Each File

So, to recap, these are the files that wg-party cares about:

  • wg-party.conf.sh: Contains the basic configuration about the network. Should be identical on each system.
  • wg-party.hosts: Contains the list of hosts, their address and public key. Should be identical on each system, too.
  • /etc/wireguard/<netname>.conf: The generated WireGuard config. Contains the private key of the host. Youll maybe want to have a backup, else a new private & public key will need to be created for the host, and populated to the other systems, in case of data loss. But other than that, this file should not be copied to other machines.

My suggestion is to add the first two files to a version control system, or to your configuration tooling (like Salt, Ansible, or whatever youre using).

Questions & Answers

How can I determine whether the configuration has been changed after a wg-party generate?

If wg-party was able to successfully generate a new configuration, it will exit with a return status of zero, regardless of the on-disk configuration actually changed or not. However, it will only touch the generated WireGuard configuration file if its contents changed. This is determined by writing the new configuration to a temporary file first and then comparing it to the existing file. (Since the generated config contains a header with a timestamp, the first 3 lines will not be considered for this comparison.)

Therefore, you can compare the modification time of the config file from before and after the wg-party invocation. If the timestamp did not change, neither did the files contents.

Alternatively, you can use the -S option that will output a status line at the end:

# wg-party -S gen
wg-party: will create a new config at /etc/wireguard/example.conf
wg-party: new config written successfully
changed=yes comment='new config written successfully'
# wg-party -S gen
wg-party: updating existing config at /etc/wireguard/example.conf
wg-party: config is unchanged
changed=no comment='config is unchanged'

This line will always start with changed=[yes|no] and is compatible to Salts stateful option. However, it will only be printed if the configuration was generated successfully, i.e. the return status is zero.

Why is it called wg-party?

Its a pun. In German, a Wohngemeinschaft (commonly called WG) is when several unrelated people are living together in an apartment or house (i.e., roommates). In English, you might call that a sharehome or flatshare. And a WG-Party is a party in a WG.