Skip to content

WireGuard configs and keys generaor for systemd-networkd and OpenWrt

Notifications You must be signed in to change notification settings

7Ji/wireguard-deployer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Wireguard deployer

A tool to generate WireGuard configs and keys for systemd-networkd and OpenWrt and pack them into .tar (only for sd-networkd) with correct permissions and ownerships that are easily deployable.

Everything can be configured on a centralized host in a centralized config file, and almost any common network topologies are supported.

Check out my blog post about my complicated setup made easy with this tool, where I have systemd-networkd + OpenWrt mixed setup and connected two full-mesh networks via a single inter-node whose endpoint address is different based on the connection source, all of which couldn't be done with simple generators.

Build

cargo build --release

The ouput binary would be target/release/sd-networkd-wg-deploy

Usage

Usage: wireguard-deployer [OPTIONS] <CONFIG> <DEPLOY>

Arguments:
  <CONFIG>  Path to .yaml config file
  <DEPLOY>  Path to folder that configs and keys shall be cached from and deployed into

Options:
  -f, --flatten <FLATTEN>  Create a flattened config [default: ]
  -r, --rawkey             Cache keys as raw bytes instead of base64, saves a few bytes, useful if you don't need to check the content of keys
  -h, --help               Print help

in which:

  • [config file] is the path to a .yaml file that meets the format documented in the following section
  • [to be deployed dir] is the path to a (possibly already existing) folder that keys and configs would be stored in. Keys are always lazily generated while configs are always freshly generated, so you can place your existing keys in corresponding path to only let the deployer generate configs.

e.g.:

./wireguard-deployer example.conf.yaml example.d

The result to-be-deployed dir structure would be like this:

example.d
├── configs
│   ├── hostA
│   │   ├── 30-wireguard.netdev
│   │   ├── 40-wireguard.network
│   │   └── keys
│   │       └── wg
│   │           ├── pre-shared-hostA-siteC
│   │           ├── pre-shared-hostA-vmA
│   │           ├── pre-shared-hostA-vmB
│   │           ├── pre-shared-hostA-vmC
│   │           └── private-hostA
│   ├── hostA.openwrt.conf
│   ├── hostA.tar
│   ├── ...
│   └── vmC.tar
└── keys
    ├── pre-shared-hostA-siteC
    ├── ...
    └── private-vmC

In which the .tar file already contains a sane permissioon and ownership setup:

> tar -tvf example.d/configs/hostA.tar
drwxr-x--- root/systemd-network 0 2024-06-10 12:59 keys
drwxr-x--- root/systemd-network 0 2024-06-10 12:59 keys/wg
-rw-r----- root/systemd-network 44 2024-06-10 12:59 keys/wg/private-hostA
-rw-r----- root/systemd-network 44 2024-06-10 12:59 keys/wg/pre-shared-hostA-hostB
-rw-r--r-- root/root           706 2024-06-10 12:59 30-wireguard.netdev
-rw-r--r-- root/root           327 2024-06-10 12:59 40-wireguard.network

Consider plain folders as quick lookup reference. It's recommended to use the .tar files to actually deploy your configs and keys so you won't need to chown and chmod by yourself.

You can deploy it however as you like. For a quick example, use the helper script to deploy the config to SSH remotes with systemd-networkd:

./script/deploy-to-ssh.sh example.d vmA vmB vmC hostA siteC:siteC.example.com ...

On a host where the tar file is already available (e.g. on current host), you can deploy it as follows:

sudo tar -C /etc/systemd/network -xvf example.d/configs/self.tar
sudo systemctl restart systemd-networkd

For OpenWrt, update /etc/config/network with the corresponding config.

Config

Format

The config format is simple yet powerful, it's defined as follows:

psk: [bool, global pre-shared keys option, default true]
iface: [string, optional, global interface name, default wg0]
netdev: [string, global systemd.netdev name without suffix, e.g. 30-wireguard]
network: [string, global systemd.network name without suffix, e.g. 40-wireguard]
port: [unsigned integer, optional, global fallback listening port, default 51820]
mask: [unsigned integer, optional, global wireguard network subnet netmask suffix, default 24]
peers: [map of peer, top level peers]

In which, a peer map is defined as follows:

[string, unique peer name, e.g. siteA]:
  iface: [string, optional, peer interface name, if not set then global iface would be used, e.g. wg1]
  port: [unsigned integer, optional, peer-specific listening port, default to global port if not set]
  netdev: [string, optional, peer systemd.netdev name without suffix, if not set then global netdev would be used, e.g. 50-wireguard-personal]
  network: [string, optional, peer systemd.network name without suffix, if not set then global network would be used, e.g. 60-wireguard-personal]
  ip: [string, wireguard network ip without subnet netmask suffix, e.g. 192.168.77.2]
  endpoint: [endpoint definition, optional, either a plain endpoint address (see below), or advanced endpoint definition (see below)]
  forward: [list of IP ranges, optional, non-wireguard subnets this peer can forward wireguard traffic into]
  children: [map of peer, optional, peers using this peer as "router" to talk to the wireguard network]
  direct: [list of peer names, optional, selective peers in current layer that this peer is able to connect to directly, if not set then assuming all, if set to empty then none and only able to connect to parent (if existing) / children]
  keep: [list of peer names, optional, selective peers in current layer that this peer connects to through NAT (the other end cannot connect directly to this peer)]

In which, advanced endpoint definition is as follows:

endpoint:
  ^parent: [string, endpoint address that this peer's parent shall use to connect to this peer]
  ^neighbor: [string, endpoint address that this peer's neighbors shall use to connect to this peer]
  ^child: [string, endpoint address that this peer's children shall use to connect to this peer]
  peerA: [string, endpoint address that peerA shall use to connect to this peer]
  peerB: [string, ...]
  ...

In which, endpoint addresss is a plain string in any one of the following formats:

endpoint: example.com # domain as host
ednpoint: example.com:51820 # domain as host + port
endpoint: 123.234.51.67 # ipv4 as host
endpoint: 123.234.51.67:51820 # ipv4 as host + port
endpoint: 1111:2222::3333 # ipv6 as host
endpoint: "[1111:2222::3333]:51800" # ipv6 as host + port

If there's no port in an endpoint address, then it would be filled with the peer port if that's set, or global port if peer port is not set.

Structure

In most cases you can just use a single layer structure, and the traffic rule is as simple as:

  • A peer, if its direct list is not set, can connect to all other peers directly, otherwise it could only connect to the set peers directly, and traffics to other peers need to go through these set peers.

This is mostly useful for a mesh setup, and a full-mesh setup is espesially simple as you can write all peers without direct list.

While nearly all network configuration can be described in a single layer config by verbose direct settings, it's recommended to use a layered structure if your setup is not full-mesh and the network can be splitted into multiple subnets, connected through a single peer or multiple such peers.

The traffic rules in layered structure are more complicated, but not that complicated:

  • A peer can always connect to all of its children directly
  • A peer can always connect to its parent (it being the child of) directly, essentially a reversed rule of the prior one
  • A peer, if its direct list is not set, can connect to all other peers in the same layer with the same parent directly (all peers in the top level are considered to have the same parent)

The layered structure is mostly for a via-host style setup, e.g. all other peers using a single peer as "router" to access remaining peers. For the example usage just config the "router" as the only top-level peer, and all others as its children with empty direct list.

As both a backup method and comparison, you can always get a flattened, single-level config file by passing --flatten argument to the deployer. In any cases, the flattened config would produce the same network configs as your original one.

Example

The above may look complicated but it's in fact very simple, e.g. a simple full mesh setup could be defined as follows:

psk: true
iface: wg0
netdev: 30-wireguard
network: 40-wireguard
mask: 24
peers:
  hostA:
    ip: 192.168.66.2
    endpoint: hostA.example.com
  hostB:
    ip: 192.168.66.3
    endpoint: hostB.example.com
  hostC:
    ip: 192.168.66.4
    endpoint: hostC.example.com
  ....

(global options would be omitted in following examples)

A simple star setup could be defined as follows, (i.e. single router/server + multiple clients), note each "client" can only access the parent "server", and to access other clients the traffic need to go through the parent.

  • Multi-Layered:
    peers:
      server:
        ip: 192.168.66.1
        endpoint: server.example.com
        children:
          clientA:
            ip: 192.168.66.2
            endpoint: clientA.example.com
            direct: []
          clientB:
            ip: 192.168.66.3
            endpoint: clientB.example.com
            direct: []
          clientC:
            ip: 192.168.66.4
            endpoint: clientC.example.com
            direct: []
          ....
  • Single-layered
    peers:
      server:
        ip: 192.168.66.1
        endpoint: server.example.com
      clientA:
        ip: 192.168.66.2
        endpoint: clientA.example.com
        direct:
        - server
      clientB:
        ip: 192.168.66.3
        endpoint: clientB.example.com
        direct:
        - server
      clientC:
        ip: 192.168.66.4
        endpoint: clientC.example.com
        direct:
        - server
      ....

A single layer star + mesh setup could be defined as follows, where "clients" in a "star" network can access each other just like in a full-mesh network.

peers:
  server:
    ip: 192.168.66.1
    endpoint: server.example.com
    children:
      clientA:
        ip: 192.168.66.2
        endpoint: clientA.example.com
      clientB:
        ip: 192.168.66.3
        endpoint: clientB.example.com
      clientC:
        ip: 192.168.66.4
        endpoint: clientC.example.com
        ....

A in-wireguard full mesh where each peer also forwards their non-wireguard traffic into the network, i.e. a common site + site VPN setup

peers:
  siteA:
    ip: 192.168.66.2
    endpoint: siteA.example.com
    forward:
      - 192.168.100.0/24
      - 10.19.0.0/16
  siteB:
    ip: 192.168.66.3
    endpoint: siteB.example.com
    forward:
      - 192.168.102.0/24
      - fdb5:c701:19a6::/48
  siteC:
    ip: 192.168.66.4
    endpoint: siteC.example.com
    forward:
      - 192.168.105.0/24
      - fd60:c3e0:a2d7::/48
  ....

A multi layer full mesh + star hybrid setup, where siteA + siteB + siteC + siteD function all as "VPN site", but traffic behind siteD need to go through it in wireguard, instead of forwarding the lan traffic directly, this is useful if the network behind siteD is not trustworthy (i.e. public network for a personal wireguard network). An example traffic line with the following config is 192.168.100.23 -> siteA (192.168.100.1 + 192.168.66.2) -> siteD (192.168.66.5) -> hostA (192.168.66.51 + 172.16.14.1) -> 172.16.14.14

  • Multi-Layered:
    peers:
      siteA:
        ip: 192.168.66.2
        endpoint: siteA.example.com
        forward:
          - 192.168.100.0/24
          - 10.19.0.0/16
      siteB:
        ip: 192.168.66.3
        endpoint: siteB.example.com
        forward:
          - 192.168.102.0/24
          - fdb5:c701:19a6::/48
      siteC:
        ip: 192.168.66.4
        endpoint: siteC.example.com
        forward:
          - 192.168.105.0/24
          - fd60:c3e0:a2d7::/48
      siteD:
        ip: 192.168.66.5
        endpoint:
          ^neighbor: siteD.example.com
          ^child: siteD.lan
        children:
          hostA:
            ip: 192.168.66.51
            endpoint: hostA.lan
            forward:
              - 172.16.14.0/24
              - 172.16.16.0/24
          hostB:
            ip: 192.168.66.52
            endpoint: hostB.lan
          hostC:
            ip: 192.168.66.53
            endpoint: hostC.lan
      ....
  • Single-layered:
    peers:
      hostA:
        ip: 192.168.66.51
        endpoint: hostA.lan
        forward:
        - 172.16.14.0/24
        - 172.16.16.0/24
        direct:
        - hostB
        - hostC
        - siteD
      hostB:
        ip: 192.168.66.52
        endpoint: hostB.lan
        direct:
        - hostA
        - hostC
        - siteD
      hostC:
        ip: 192.168.66.53
        endpoint: hostC.lan
        direct:
        - hostA
        - hostB
        - siteD
      siteA:
        ip: 192.168.66.2
        endpoint: siteA.example.com
        forward:
        - 192.168.100.0/24
        - 10.19.0.0/16
        direct:
        - siteB
        - siteC
        - siteD
      siteB:
        ip: 192.168.66.3
        endpoint: siteB.example.com
        forward:
        - 192.168.102.0/24
        - fdb5:c701:19a6::/48
        direct:
        - siteA
        - siteC
        - siteD
      siteC:
        ip: 192.168.66.4
        endpoint: siteC.example.com
        forward:
        - 192.168.105.0/24
        - fd60:c3e0:a2d7::/48
        direct:
        - siteA
        - siteB
        - siteD
      siteD:
        ip: 192.168.66.5
        endpoint:
          ^neighbor: siteD.example.com
          hostA: siteD.lan
          hostB: siteD.lan
          hostC: siteD.lan
      ....

See also

sd-networkd-wg-ddns, systemd-networkd wireguard netdev endpoints DynDNS updater. Use it to actively monitor for wireguard peers with endpoints that're set up using domain name instead of plain IPs, and update them in case DNS record updated.

License

wireguard-deployer, WireGuard configs and keys generaor for systemd-networkd and OpenWrt

Copyright (C) 2024-present Guoxin "7Ji" Pu

This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/.

About

WireGuard configs and keys generaor for systemd-networkd and OpenWrt

Resources

Stars

Watchers

Forks

Packages

No packages published