- Go 89.7%
- Shell 6.5%
- Makefile 3.8%
|
|
||
|---|---|---|
| .forgejo/workflows | print versions | |
| cmd/wg-daemon | set maintenance mode through API | |
| .gitignore | initial commit | |
| AUTHORS.md | initial commit | |
| CHANGES.md | initial commit | |
| go.mod | update dependencies | |
| go.sum | update dependencies | |
| LICENSE | initial commit | |
| make_release.sh | new signing key | |
| Makefile |
enable -extra with gofumpt
|
|
| README.md | update README | |
Summary: WireGuard Management Daemon
Description: This is a simple daemon written in Go providing a HTTP(S) API to manage WireGuard server configuration.
License: MIT
NOTE: we are embedding code from the
wgctrl-go project which is licensed
under the MIT license. You can find it in the wgctrl directory of this
project.
API
Info
| Call | Description | Method |
|---|---|---|
/status |
Request Node Information | GET |
Node Information
Request node information.
Request
$ curl http://localhost:51194/status
Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"connection_count": 1,
"cpu_count": 1,
"maintenance_mode": false,
"node_uptime": 522990,
"public_key": "EJQtm4KT8g/fUH/2/PRZnmpJTQggz737jwggN6K00ys=",
"system_load": 5
}
The system_load is based on the output of /proc/loadavg. See proc(5) for
more information. If the system the daemon runs on has no /proc/loadavg, or
it is not available the value returned will be 0.
The system_load value is calculated as follows:
$ cat /proc/loadavg | awk -v nproc=$(nproc) {'print int(1ドル*100/nproc)'}
The cpu_count field indicates the number of CPUs/cores available on the node.
The node_uptime is the output of the first field of /proc/uptime converted
to int. It indicates the number of seconds since the system was booted, i.e.
system uptime.
maintenance_mode is true if the node does not want to accept new
connections, for example in preparation for a software update or reboot. It can
be toggled with the /config API.
The server_public_key field contains the WireGuard public key of the node.
The connection_count field contain the number of VPN clients connected to
this node.
NOTE: cpu_count field value contains the number of CPUs the host had the
moment the daemon was started. To reflect changes to number of CPU cores, the
wg-daemon process MUST be restarted.
Configure
| Call | Description | Method |
|---|---|---|
/config |
Set Node Config | POST |
Configure Node
Currently allows you to enable/disable maintenance mode.
| Parameter | Description | Required | Default | Example(s) |
|---|---|---|---|---|
maintenance_mode |
Enable/disable "Maintenance Mode" | No | N/A | 1, 0 |
Request
To enable "maintenance mode":
$ curl \
-d 'maintenance_mode=1' \
http://localhost:51194/config
Response
HTTP/1.1 204 OK
WireGuard
| Call | Description | Method |
|---|---|---|
/configure |
Configure WireGuard | POST |
/add_peer |
Add a peer | POST |
/peer_list |
List of configured peers | GET |
/remove_peer |
Remove a peer | POST |
Configure WireGuard
This call is used to configure the WireGuard interface on the server. You can set the IPs, the MTU and the port WireGuard listens on.
| Parameter | Description | Required | Default | Example(s) |
|---|---|---|---|---|
wg_mtu |
MTU of the WireGuard interface | No | N/A | 1392 |
wg_port |
UDP Port | No | 51820 |
443 |
ip_net |
The IP network address(es) | Yes, >= 1 | N/A | 10.43.43.1/24, fd43::1/64 |
Request
$ curl \
-d 'ip_net=10.43.43.1/24' \
-d 'ip_net=fd43::1/64' \
-d 'wg_port=443' \
-d 'wg_mtu=1392' \
http://localhost:51194/configure
Add Peer
Add a new WireGuard peer.
| Parameter | Description | Required | Example(s) |
|---|---|---|---|
public_key |
The public key | Yes | QWR3JLun5e/YCzRyV7/IHl8qnAI8dC1WvJTSSZwRBEo= |
preshared_key |
The shared key (PresharedKey) |
Yes | lTW6cmZhm3Wqhw+rGp1VKd7QmstB9o9Gw86+kHNfiFw= |
allowed_ip |
The IP network address(es) of the peer | Yes, >= 1 | 10.43.43.2/32, fd43::2/128 |
Request
To specify both an IPv4 and IPv6 address that the peer is allowed to use, you can use this call:
$ curl \
--data-urlencode 'allowed_ip=10.43.43.2/32' \
--data-urlencode 'allowed_ip=fd43::2/128' \
--data-urlencode 'public_key=QWR3JLun5e/YCzRyV7/IHl8qnAI8dC1WvJTSSZwRBEo=' \
--data-urlencode 'preshared_key=lTW6cmZhm3Wqhw+rGp1VKd7QmstB9o9Gw86+kHNfiFw=' \
http://localhost:51194/add_peer
Response
HTTP/1.1 204 No Content
Peer List
Get a list of configured peers. Note, this is not a list of currently connected WireGuard clients.
| Parameter | Description | Required | Default | Example(s) |
|---|---|---|---|---|
show_all |
Whether to also show "offline" peers | No | no |
yes, no |
Offline peers are peers that have never performed a handshake with the
WireGuard server, or performed the last handshake more than 3 minutes ago. The
default is no.
Request
$ curl http://localhost:51194/peer_list
Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"peer_list": [
{
"bytes_in": 682104,
"bytes_out": 1070200,
"ip_net": [
"10.10.10.12/32",
"fd10::c/128"
],
"last_handshake_time": "2026年02月20日T15:58:22.498948935Z",
"public_key": "7ZjQNqKut+Al4uxqgX30QPlMTHEAgGufXRCKeE1AYHk="
}
]
}
If the peer was not seen yet, the key last_handshake_time is omitted from
the response. The format of last_handshake_time is RFC 3339, always in UTC,
i.e. ends with Z.
Remove Peer
Remove a peer.
| Parameter | Description | Required | Example |
|---|---|---|---|
public_key |
The WireGuard public key | Yes | QWR3JLun5e/YCzRyV7/IHl8qnAI8dC1WvJTSSZwRBEo= |
Request
$ curl \
--data-urlencode 'public_key=QWR3JLun5e/YCzRyV7/IHl8qnAI8dC1WvJTSSZwRBEo=' \
http://localhost:51194/remove_peer
Response
HTTP/1.1 200 OK
Content-Type: application/json
{
"bytes_in": 21480,
"bytes_out": 34200,
"ip_net": [
"10.43.43.2/32",
"fd43::2/128"
],
"last_handshake_time": "2025年03月06日T10:49:18Z",
"public_key": "QWR3JLun5e/YCzRyV7/IHl8qnAI8dC1WvJTSSZwRBEo="
}
If the peer was not seen yet, the key last_handshake_time is omitted from
the response. The format of last_handshake_time is RFC 3339, always in UTC,
i.e. ends with Z.
Or, in case the peer was not registered at the node:
HTTP/1.1 204 No Content
Building
$ git clone https://codeberg.org/eduVPN/wg-daemon
$ cd wg-daemon
$ go build -o wg-daemon codeberg.org/eduVPN/wg-daemon/cmd/wg-daemon
A Makefile is also provided for your convenience.
Running
You can run wg-daemon as root, or use systemd to manage the
daemon.
TLS
By default, having access to the TCP port of wg-daemon is enough to use the API. This is fine when the daemon is only used on one system. When deploying multiple nodes, TLS, i.e. HTTPS can be enabled with client certificate authentication.
TLS is automatically enabled when the CREDENTIALS_DIRECTORY environment
variable is set. This variable should point to the directory containing the
ca.crt, server.crt and server.key files. The ca.crt file will be used
to validate the client certificates. If your CA uses intermediate certificates,
they MUST be included in the server.crt as well.
In order to quickly set up your own CA and test TLS, you can do the following:
$ vpn-ca -init-ca
$ vpn-ca -server -name server
$ vpn-ca -client -name client
Start wg-daemon:
$ sudo CREDENTIALS_DIRECTORY=. ./wg-daemon
Now you can test accessing the daemon through TLS:
$ curl \
--cacert ca.crt \
--cert client.crt \
--key client.key \
--connect-to server:51194:localhost https://server:51194/status
If your CA has already been installed in your OS, you do no need to specify
the --cacert option to curl.
Design
The daemon is written in Go. For configuring WireGuard the daemon integrates
the wgctrl library.
The daemon is secured with systemd which makes it possible to run
it without root permissions, see the wg-daemon.service file below.
Whenever a VPN peer is added, either through the portal or API the peer is immediately configured through the daemon(s) as well. For removal it is exactly the same, so there is no waiting time before a peer can connect.
Our approach at surviving reboots/crashes is that the portal that uses the daemon periodically, e.g. every 5 minutes, synchronizes the list of known WireGuard peers.
The VPN portal is the source of truth. It has a list of all WireGuard peers that should be configured through the daemon(s). The portal retrieves all peers known by the daemon and compares that list with the list it has in the database. Based on this it adds/removes peers known to the daemon. This way after a reboot of the node(s) within 5 minutes the node is back in the correct state.
We do not know how well this scales with thousands of VPN peers over multiple nodes, but we'll find this out and can optimize "Just in Time" when this no longer scales and push portal/daemon updates as needed.
systemd
This is /etc/systemd/system/wg-daemon.service:
[Unit]
Description=WireGuard Management Daemon
[Service]
AmbientCapabilities=CAP_NET_ADMIN
AmbientCapabilities=CAP_NET_BIND_SERVICE
Environment=WG_DEVICE=wg0
Environment=LISTEN=127.0.0.1:51194
ExecStart=wg-daemon
Restart=on-failure
PrivateDevices=yes
DynamicUser=yes
StateDirectory=%N
SystemCallFilter=@system-service
NoNewPrivileges=yes
RestrictNamespaces=yes
ProtectHostname=yes
ProtectHome=yes
ProtectUsers=yes
[Install]
WantedBy=multi-user.target
TLS
In order to enable TLS, you can put the following in the file
/etc/systemd/system/wg-daemon.service.d/credentials.conf:
[Service]
LoadCredential=ca.crt:/etc/ssl/wg-daemon/ca.crt
LoadCredential=server.crt:/etc/ssl/wg-daemon/server.crt
LoadCredential=server.key:/etc/ssl/wg-daemon/private/server.key
This uses the Credentials feature of systemd. Make sure the certificates and key are put in the right place, and run the following commands:
$ sudo systemctl daemon-reload
$ sudo systemctl restart wg-daemon