Prometheus WireGuard Exporter

legal stability-stable

Crate cratedown cratelastdown

release tag

Rust build commitssince

Docker build

dockeri.co

Intro

A Prometheus exporter for WireGuard, written in Rust. This tool exports the wg show all dump (or wg show <interface> dump if you specify a config file) results in a format that Prometheus can understand. The exporter is very light on your server resources, both in terms of memory and CPU usage. It's also built for Docker for the following CPU architectures: amd64, 386, arm64, armv7 and armv6.

Changelog

Setup

Pre-built binaries

Coming soon, subcribe to #59

Docker

  1. You need Docker installed
  2. You need WireGuard installed in your host kernel
  3. You need some Wireguard interfaces running
  4. Download and run the container with:

    sh docker run -d --net=host --cap-add=NET_ADMIN --name wgexporter mindflavor/prometheus-wireguard-exporter

    ⚠️ If you encounter time issues on your 32 bit operating system, check this

  5. Check it's up by visiting http://localhost:9586/metrics

You can then update the image with

sh docker pull mindflavor/prometheus_wireguard_exporter

Or use a tagged image such as :3.5.1.

If your host has an amd64 or 686 CPU, you can also build the Docker image from source (you need git) with:

sh docker build -t mindflavor/prometheus_wireguard_exporter https://github.com/MindFlavor/prometheus_wireguard_exporter.git#master

Build from source

  1. You need Rust installed
  2. You need WireGuard installed on your host
  3. You need wg accessible in your path. The tool will call wg show <interface(s)>|all dump and of course will fail if the wg executable is not found.
  4. You need some Wireguard interfaces running
  5. You need git installed
  6. Clone the repository with

    sh git clone https://github.com/MindFlavor/prometheus_wireguard_exporter.git cd prometheus_wireguard_exporter

  7. Compile the program with

    sh cargo install --path .

    💁 If you encounter errors, please try updating your rust installation with rustup update. The code should compile with any relatively recent, 2018-compliant rustc version. As a frame of reference, the last release was built using the Rust Docker image using rustc 1.53.0 (53cb7b09b 2021-06-17).

  8. Run the program

    sh ./prometheus_wireguard_exporter

  9. Check it's up by visiting http://localhost:9586/metrics

Usage

Flags available

Start the binary with -h to get the complete syntax. The parameters are:

| Parameter | Env | Mandatory | Valid values | Default | Accepts multiple occurrences? | Description | | -- | -- | -- | -- | -- | -- | -- | | -v | PROMETHEUS_WIREGUARD_EXPORTER_VERBOSE_ENABLED | No | true or false | false | No | Enable verbose mode. | -a | PROMETHEUS_WIREGUARD_EXPORTER_PREPEND_SUDO_ENABLED | No | true or false | false | No | Prepends sudo to wg commands. | -l | PROMETHEUS_WIREGUARD_EXPORTER_ADDRESS | No | Any valid IP address | 0.0.0.0 | No | Specify the service address. This is the address your Prometheus instance should point to. | -p | PROMETHEUS_WIREGUARD_EXPORTER_PORT | No | Any valid port number | 9586 | No | Specify the service port. This is the port your Prometheus instance should point to. | -n | PROMETHEUS_WIREGUARD_EXPORTER_CONFIG_FILE_NAMES | No | Path to the wireguard configuration file | | Yes | This flag adds the friendly_name attribute or the friendly_json attributes to the exported entries. See Friendly tags for more details. Multiple files are allowed (they will be merged as a single file in memory so avoid duplicates). | -s | PROMETHEUS_WIREGUARD_EXPORTER_SEPARATE_ALLOWED_IPS_ENABLED | No | true or false | false | No | Enable the allowed ip + subnet split mode for the labels. | -r | PROMETHEUS_WIREGUARD_EXPORTER_EXPORT_REMOTE_IP_AND_PORT_ENABLED | No | true or false | false | No | Exports peer's remote ip and port as labels (if available). | -i | PROMETHEUS_WIREGUARD_EXPORTER_INTERFACES | No | Your interface name(s) | all | Yes | Specifies the interface(s) passed to the wg show <interface> dump parameter. Multiple parameters are allowed.

Keep in mind that command line values take precedence over environment variables.

Once started, the tool will listen on the specified port (or the default one, 9586, if not specified) and return a Prometheus valid response at the url /metrics. So to check if the tool is working properly simply browse the http://localhost:9586/metrics (or whichever port you choose).

Friendly Tags

Starting from version 3.5 you can instruct the exporter to append a friendly name or a friendly_json to the exported entries. This can make the output more understandable than using the public keys. For example this is the standard output:

```ebnf

HELP wireguardsentbytes_total Bytes sent to the peer

TYPE wireguardsentbytes_total counter

wireguardsentbytestotal{interface="wg0",publickey="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=",allowedips="10.70.0.2/32,10.70.0.66/32"} 3208804 wireguardsentbytestotal{interface="wg0",publickey="qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=",allowedips="10.70.0.3/32"} 0 wireguardsentbytestotal{interface="wg0",publickey="L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=",allowedips="10.70.0.4/32"} 0 wireguardsentbytestotal{interface="wg0",publickey="MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=",allowedips="10.70.0.50/32"} 0 wireguardsentbytestotal{interface="wg0",publickey="lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=",allowedips="10.70.0.40/32"} 0 wireguardsentbytestotal{interface="wg0",publickey="928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=",allowedips="10.70.0.80/32"} 0 wireguardsentbytestotal{interface="wg0",publickey="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=",allowed_ips="10.70.0.5/32"} 0

HELP wireguardreceivedbytes_total Bytes received from the peer

TYPE wireguardreceivedbytes_total counter

wireguardreceivedbytestotal{interface="wg0",publickey="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=",allowedips="10.70.0.2/32,10.70.0.66/32"} 71420072 wireguardreceivedbytestotal{interface="wg0",publickey="qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=",allowedips="10.70.0.3/32"} 0 wireguardreceivedbytestotal{interface="wg0",publickey="L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=",allowedips="10.70.0.4/32"} 0 wireguardreceivedbytestotal{interface="wg0",publickey="MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=",allowedips="10.70.0.50/32"} 0 wireguardreceivedbytestotal{interface="wg0",publickey="lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=",allowedips="10.70.0.40/32"} 0 wireguardreceivedbytestotal{interface="wg0",publickey="928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=",allowedips="10.70.0.80/32"} 0 wireguardreceivedbytestotal{interface="wg0",publickey="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=",allowed_ips="10.70.0.5/32"} 0

HELP wireguardlatesthandshake_seconds Seconds from the last handshake

TYPE wireguardlatesthandshake_seconds gauge

wireguardlatesthandshakeseconds{interface="wg0",publickey="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=",allowedips="10.70.0.2/32,10.70.0.66/32"} 1562834127 wireguardlatesthandshakeseconds{interface="wg0",publickey="qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=",allowedips="10.70.0.3/32"} 0 wireguardlatesthandshakeseconds{interface="wg0",publickey="L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=",allowedips="10.70.0.4/32"} 0 wireguardlatesthandshakeseconds{interface="wg0",publickey="MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=",allowedips="10.70.0.50/32"} 0 wireguardlatesthandshakeseconds{interface="wg0",publickey="lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=",allowedips="10.70.0.40/32"} 0 wireguardlatesthandshakeseconds{interface="wg0",publickey="928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=",allowedips="10.70.0.80/32"} 0 wireguardlatesthandshakeseconds{interface="wg0",publickey="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=",allowed_ips="10.70.0.5/32"} 0 ```

And this is the one augmented with friendly names:

```ebnf

HELP wireguardsentbytes_total Bytes sent to the peer

TYPE wireguardsentbytes_total counter

wireguardsentbytestotal{interface="wg0",publickey="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=",allowedips="10.70.0.2/32,10.70.0.66/32",friendlyname="OnePlus 6T"} 3208804 wireguardsentbytestotal{interface="wg0",publickey="qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=",allowedips="10.70.0.3/32",friendlyname="varch.local (laptop)"} 0 wireguardsentbytestotal{interface="wg0",publickey="L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=",allowedips="10.70.0.4/32",friendlyname="cantarch"} 0 wireguardsentbytestotal{interface="wg0",publickey="MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=",allowedips="10.70.0.50/32",friendlyname="frcognoarch"} 0 wireguardsentbytestotal{interface="wg0",publickey="lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=",allowedips="10.70.0.40/32",friendlyname="frcognowin10"} 0 wireguardsentbytestotal{interface="wg0",publickey="928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=",allowedips="10.70.0.80/32",friendlyname="OnePlus 5T"} 0 wireguardsentbytestotal{interface="wg0",publickey="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=",allowedips="10.70.0.5/32",friendlyname="folioarch"} 0

HELP wireguardreceivedbytes_total Bytes received from the peer

TYPE wireguardreceivedbytes_total counter

wireguardreceivedbytestotal{interface="wg0",publickey="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=",allowedips="10.70.0.2/32,10.70.0.66/32",friendlyname="OnePlus 6T"} 71420072 wireguardreceivedbytestotal{interface="wg0",publickey="qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=",allowedips="10.70.0.3/32",friendlyname="varch.local (laptop)"} 0 wireguardreceivedbytestotal{interface="wg0",publickey="L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=",allowedips="10.70.0.4/32",friendlyname="cantarch"} 0 wireguardreceivedbytestotal{interface="wg0",publickey="MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=",allowedips="10.70.0.50/32",friendlyname="frcognoarch"} 0 wireguardreceivedbytestotal{interface="wg0",publickey="lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=",allowedips="10.70.0.40/32",friendlyname="frcognowin10"} 0 wireguardreceivedbytestotal{interface="wg0",publickey="928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=",allowedips="10.70.0.80/32",friendlyname="OnePlus 5T"} 0 wireguardreceivedbytestotal{interface="wg0",publickey="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=",allowedips="10.70.0.5/32",friendlyname="folioarch"} 0

HELP wireguardlatesthandshake_seconds Seconds from the last handshake

TYPE wireguardlatesthandshake_seconds gauge

wireguardlatesthandshakeseconds{interface="wg0",publickey="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=",allowedips="10.70.0.2/32,10.70.0.66/32",friendlyname="OnePlus 6T"} 1562834127 wireguardlatesthandshakeseconds{interface="wg0",publickey="qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=",allowedips="10.70.0.3/32",friendlyname="varch.local (laptop)"} 0 wireguardlatesthandshakeseconds{interface="wg0",publickey="L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=",allowedips="10.70.0.4/32",friendlyname="cantarch"} 0 wireguardlatesthandshakeseconds{interface="wg0",publickey="MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=",allowedips="10.70.0.50/32",friendlyname="frcognoarch"} 0 wireguardlatesthandshakeseconds{interface="wg0",publickey="lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=",allowedips="10.70.0.40/32",friendlyname="frcognowin10"} 0 wireguardlatesthandshakeseconds{interface="wg0",publickey="928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=",allowedips="10.70.0.80/32",friendlyname="OnePlus 5T"} 0 wireguardlatesthandshakeseconds{interface="wg0",publickey="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=",allowedips="10.70.0.5/32",friendlyname="folioarch"} 0 ```

In order for this to work, you need to add the friendly_name key value to the comments preceding a peer a specific metadata (in your wireguard configuration file). See below the [Peer] definition for an example. The tag is called friendly_name and it will be added to the entry exported to Prometheus. Note that this is not a standard but, since it's a comment, will not interfere with WireGuard in any way. From version 3.5.0 you can optionally specify a friendly_json tag followed by a flat json (that is, a json with only top level, simple entries). If a friendly_json tag will be found every entry will be used as attribute in the exported Prometheus instance. No compliance check will be done. Also, numbers will be converted to strings (as it's expected for a Prometheus attribute).

For example this is how you edit your WireGuard configuration file:

```toml [Peer] PublicKey = lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc= AllowedIPs = 10.70.0.40/32

[Peer]

Custom comment

PublicKey = 928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk= AllowedIPs = 10.70.0.80/32 ```

```toml [Peer]

friendly_name = frcognowin10

PublicKey = lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc= AllowedIPs = 10.70.0.40/32

[Peer]

friendly_name = OnePlus 5T

Custom comment

PublicKey = 928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk= AllowedIPs = 10.70.0.80/32 ```

As you can see, all you need to do is to add the friendly name in the comments preceding a peer (and enable the flag since this feature is opt-in).

This is a sample of the label split mode:

```ebnf

HELP wireguardsentbytes_total Bytes sent to the peer

TYPE wireguardsentbytes_total counter

wireguardsentbytestotal{interface="wg0",publickey="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=",allowedip0="10.70.0.2",allowedsubnet0="32",allowedip1="10.70.0.66",allowedsubnet1="32",friendlyname="OnePlus 6T"} 3208804 wireguardsentbytestotal{interface="wg0",publickey="qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=",allowedip0="10.70.0.3",allowedsubnet0="32",friendlyname="varch.local (laptop)"} 0 wireguardsentbytestotal{interface="wg0",publickey="L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=",allowedip0="10.70.0.4",allowedsubnet0="32",friendlyname="cantarch"} 0 wireguardsentbytestotal{interface="wg0",publickey="MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=",allowedip0="10.70.0.50",allowedsubnet0="32",friendlyname="frcognoarch"} 0 wireguardsentbytestotal{interface="wg0",publickey="lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=",allowedip0="10.70.0.40",allowedsubnet0="32",friendlyname="frcognowin10"} 0 wireguardsentbytestotal{interface="wg0",publickey="928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=",allowedip0="10.70.0.80",allowedsubnet0="32",friendlyname="OnePlus 5T"} 0 wireguardsentbytestotal{interface="wg0",publickey="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=",allowedip0="10.70.0.5",allowedsubnet0="32",friendly_name="folioarch"} 0

HELP wireguardreceivedbytes_total Bytes received from the peer

TYPE wireguardreceivedbytes_total counter

wireguardreceivedbytestotal{interface="wg0",publickey="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=",allowedip0="10.70.0.2",allowedsubnet0="32",allowedip1="10.70.0.66",allowedsubnet1="32",friendlyname="OnePlus 6T"} 71420072 wireguardreceivedbytestotal{interface="wg0",publickey="qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=",allowedip0="10.70.0.3",allowedsubnet0="32",friendlyname="varch.local (laptop)"} 0 wireguardreceivedbytestotal{interface="wg0",publickey="L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=",allowedip0="10.70.0.4",allowedsubnet0="32",friendlyname="cantarch"} 0 wireguardreceivedbytestotal{interface="wg0",publickey="MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=",allowedip0="10.70.0.50",allowedsubnet0="32",friendlyname="frcognoarch"} 0 wireguardreceivedbytestotal{interface="wg0",publickey="lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=",allowedip0="10.70.0.40",allowedsubnet0="32",friendlyname="frcognowin10"} 0 wireguardreceivedbytestotal{interface="wg0",publickey="928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=",allowedip0="10.70.0.80",allowedsubnet0="32",friendlyname="OnePlus 5T"} 0 wireguardreceivedbytestotal{interface="wg0",publickey="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=",allowedip0="10.70.0.5",allowedsubnet0="32",friendly_name="folioarch"} 0

HELP wireguardlatesthandshake_seconds Seconds from the last handshake

TYPE wireguardlatesthandshake_seconds gauge

wireguardlatesthandshakeseconds{interface="wg0",publickey="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=",allowedip0="10.70.0.2",allowedsubnet0="32",allowedip1="10.70.0.66",allowedsubnet1="32",friendlyname="OnePlus 6T"} 1562834127 wireguardlatesthandshakeseconds{interface="wg0",publickey="qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=",allowedip0="10.70.0.3",allowedsubnet0="32",friendlyname="varch.local (laptop)"} 0 wireguardlatesthandshakeseconds{interface="wg0",publickey="L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=",allowedip0="10.70.0.4",allowedsubnet0="32",friendlyname="cantarch"} 0 wireguardlatesthandshakeseconds{interface="wg0",publickey="MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=",allowedip0="10.70.0.50",allowedsubnet0="32",friendlyname="frcognoarch"} 0 wireguardlatesthandshakeseconds{interface="wg0",publickey="lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=",allowedip0="10.70.0.40",allowedsubnet0="32",friendlyname="frcognowin10"} 0 wireguardlatesthandshakeseconds{interface="wg0",publickey="928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=",allowedip0="10.70.0.80",allowedsubnet0="32",friendlyname="OnePlus 5T"} 0 wireguardlatesthandshakeseconds{interface="wg0",publickey="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=",allowedip0="10.70.0.5",allowedsubnet0="32",friendly_name="folioarch"} 0 ```

Systemd service file

Now add the exporter to the Prometheus exporters as usual. I recommend to start it as a service. It's necessary to run it as root (if there is a non-root way to call wg show all dump please let me know). My systemd service file is like this one:

```ini [Unit] Description=Prometheus WireGuard Exporter Wants=network-online.target After=network-online.target

[Service] User=root Group=root Type=simple ExecStart=/usr/local/bin/prometheuswireguardexporter -n /etc/wireguard/peers.conf -i wg0 -i wg1

[Install] WantedBy=multi-user.target ```

Development

Locally

  1. Install Rust
  2. Install Rust Analyzer and set it up with your editor
  3. Install Clippy: rustup clippy

You may want to install Docker as well to build and run the Docker image.

The following commands are available:

```sh

Download dependencies

cargo fetch

Build the program

cargo build

Run tests

cargo test

Run clippy to lint

cargo clippy

Build the Docker image

docker build -t mindflavor/prometheuswireguardexporter . ```

VSCode development container

This is more of a plug and play solution based on Docker and VSCode.

See .devcontainer/README.md