Compare commits

...

30 commits

Author SHA1 Message Date
02d4b5ab13 fix 2023-10-09 21:27:12 +02:00
d5ccea6fc3 set version persistent 2023-10-09 20:06:18 +02:00
Francesco Cogno
2d82a9c06c
Issue/105 (#106)
* fixed tests

* updated lock
2022-11-21 10:28:17 +01:00
Francesco Cogno
56290d6801
Fix semver error from prometheus_exporter_base (#104)
* Fixed semver error

* updated README
2022-11-21 09:46:12 +01:00
Francesco Cogno
a21dc2852e
Rust to latest in Dockerfile 2022-11-20 16:23:15 +01:00
Francesco Cogno
9f0eb7f050
Issue/100 (#101)
* Updating clap

* completed clap ugprade

* Upgraded other deps

* removed unused use

* fixed tests

* exported delay

* fixed multiple params

* added actual calculation
2022-11-20 16:13:06 +01:00
Tim Meusel
8f586ed65b
Document systemd unit for normal users (#97) 2022-11-20 09:24:02 +01:00
Marcel
f0c7a8fbfc
Added GitHub Actions to automatic publish binaries for various architectures (#95)
* Create ci.yaml

* Update ci.yaml

* Update ci.yaml

* Update ci.yaml

* Update config.toml

* Update ci.yaml

* Update ci.yaml

* Update ci.yaml

* Update ci.yaml

* Delete release-binaries.yml

* Update ci.yaml

* Update ci.yaml
2022-05-09 12:05:46 +02:00
Francesco Cogno
f8222bc5ab
version bump 2022-03-29 10:26:26 +02:00
Francesco Cogno
eb7455fa85
Clarify option behavior (#93)
* version bump, README fix

* Fixed "important" sign
2022-03-29 10:23:40 +02:00
mbonino
86cc251954
Add systemd-networkd peer syntax (#92)
Systemd-networkd config file uses ``[WireGuardPeer]`` rather than ``[Peer]``.
2022-03-29 09:53:53 +02:00
Francesco Cogno
9206a4f595
Updated README version 2022-03-11 17:51:49 +01:00
Francesco Cogno
61de41169b
Dependency refresh (#90) 2022-03-11 17:48:35 +01:00
Michael Weinrich
d987baf1f1
feat(ci): add binary build and release workflow (#77)
This adds a workflow (or actually several parallel ones) that gets triggered for a new tag. It builds binaries for several different platforms, creates a release and attaches them as downloads. I wasn't able to make this work for arm64 (because of ring) therefore I commented that part for now.

Fix #59
2022-03-09 06:23:02 -05:00
Francesco Cogno
23298a72c4
Fixed version in README 2021-11-03 11:41:59 +01:00
Francesco Cogno
72d3f7393e
Escape friendly name double quote (#84)
* escape friendly name

* Updated README
2021-11-03 11:36:05 +01:00
Francesco Cogno
5b709b19f1
Ability to use env vars (#83)
* clippy

* exported variables

* docs

* updated deps

* typo

* version in docs

* better docs
2021-11-03 11:14:14 +01:00
Quentin McGaw
6222d71685
Fix Docker build badge in readme (#75) 2021-11-03 11:00:53 +01:00
Quentin McGaw
6678669dfb
Maintenance: remove microbadger CI hook (#76) 2021-11-03 11:00:31 +01:00
Quentin McGaw
d8237c8b7c
Docker: upgrade Alpine to 3.14 (#67) 2021-10-14 09:13:00 -07:00
Francesco Cogno
62fe64e1c0 swapped clippy dep version from * to 0.0.3 2021-08-06 09:50:53 +02:00
Quentin McGaw
638b9d1c33
CI fixes: dockerhub description and linting (#68)
- Github workflow: upgrade `peter-evans/dockerhub-description` to `v2.4.3`
- Dockerfile: fix: lint stage not running, changed from `ENTRYPOINT` to `RUN`
- Dockerfile: fix: install clippy for build platform
2021-07-17 12:40:58 -07:00
Quentin McGaw
5752476184
VSCode Rust development container (#71) 2021-07-17 11:49:23 -07:00
Quentin McGaw
d18c045c98
Docker: use tini as init system (#72) 2021-07-16 15:14:02 +02:00
Quentin McGaw
2bc7ea9e06
Readme development section (#70) 2021-07-16 15:12:30 +02:00
Quentin McGaw
8f44776745
Linting with Clippy (#69)
* Dockerfile: add test stage with cargo test entrypoint

* Workflows: merge all in docker.yml
- Run cargo test in docker.yml workflow
- Manage PRs, releases, branches and latest altogether with variables

* Workflows: update dockerhub description

* Add clippy linting
2021-07-16 15:11:51 +02:00
Quentin McGaw
45472a35b1
Readme simplifications and improvements (#65)
- Split setup section in three subsections:
     1. Pre built binaries, empty for now, with a subscribe link to #59 
     2. Docker with simplified step-by-step instructions
         - Add Docker tagged images information
     3. Rust with simplified step-by-step instructions
- Fix `<switch>` not showing in tables
- Add syntax highlighting to code blocks
- Highlight Docker image cross cpu compatibility, as many users crave for this for their ARM devices
- Precise docker build only work on amd64/686 CPUs
- Clear up rustc version required to build
2021-07-08 10:57:40 -07:00
Francesco Cogno
9d37840b83
Implemented support for multiple peer files (#64) 2021-07-08 11:06:41 +02:00
Quentin McGaw
3a74f8b5b0
Multi arch Dockerfile and CI (#63)
* Modify Dockerfile
- Use rustup from Docker image
- Give up on cross compilation (ring issue)
- Remove useless duplicate cargo install step
- No need to specify target as it is built statically for the docker platform targeted
- Shorten binary path output for shorter COPY

* Github Actions to build for all ARM as well

* Remove unneded musl-dev

* Adapt build for all platforms
- Use Debian based Rust for compilation
- Cross compile on build platform
- Compile static binaries using musl
- Set variables correctly for all Docker supported platforms
- Note: ppc64le, s390x and riscv64 do not support the Rust standard lib
- Persist target platform variables through files
- Define linkers in .cargo/config
- Cache dependencies properly for target platform
- Scratch stage to test the binary for target platform

* Add 386 arch to CI

* Use buildkit for docker build

* Only build for amd64 on branches

* Fix dependencies caching

* Split dependencies fetching and compilation

* Documentation

* Build for all architectures on branch

* Add STATIC build argument

* Documentation
2021-07-08 09:17:51 +02:00
Francesco Cogno
a07a3d0170
Support for friendly_json comment (#55)
* upgrading deps

* tst

* fixed tests

* Json exposed as strings

* fixed double quote

* bumped version

* corrected semantic version bump

* updated README
2021-03-25 14:17:41 +01:00
25 changed files with 1626 additions and 1258 deletions

25
.cargo/config.toml Normal file
View file

@ -0,0 +1,25 @@
# This is used in the Docker build, you might need to adjust it for local usage.
[target.x86_64-unknown-linux-musl]
linker = "x86_64-linux-musl-gcc"
[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"
[target.armv7-unknown-linux-musleabi]
linker = "armv7m-linux-musleabi-gcc"
[target.arm-unknown-linux-musleabi]
linker = "armv6-linux-musleabi-gcc"
[target.i686-unknown-linux-musl]
linker = "i686-linux-musl-gcc"
[target.powerpc64le-unknown-linux-musl]
linker = "powerpc64le-linux-musl-gcc"
[target.s390x-unknown-linux-musl]
linker = "s390x-linux-musl-gcc"
[target.riscv64gc-unknown-linux-musl]
linker = "riscv64-linux-musl-gcc"

View file

@ -0,0 +1,5 @@
.dockerignore
devcontainer.json
docker-compose.yml
Dockerfile
README.md

1
.devcontainer/Dockerfile Normal file
View file

@ -0,0 +1 @@
FROM qmcgaw/rustdevcontainer

70
.devcontainer/README.md Normal file
View file

@ -0,0 +1,70 @@
# Development container
Development container that can be used with VSCode.
It works on Linux, Windows and OSX.
## Requirements
- [VS code](https://code.visualstudio.com/download) installed
- [VS code remote containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) installed
- [Docker](https://www.docker.com/products/docker-desktop) installed and running
- [Docker Compose](https://docs.docker.com/compose/install/) installed
## Setup
1. Create the following files on your host if you don't have them:
```sh
touch ~/.gitconfig ~/.zsh_history
```
Note that the development container will create the empty directories `~/.docker`, `~/.ssh` and `~/.kube` if you don't have them.
1. **For Docker on OSX or Windows without WSL**: ensure your home directory `~` is accessible by Docker.
1. **For Docker on Windows without WSL:** if you want to use SSH keys, bind mount your host `~/.ssh` to `/tmp/.ssh` instead of `~/.ssh` by changing the `volumes` section in the [docker-compose.yml](docker-compose.yml).
1. Open the command palette in Visual Studio Code (CTRL+SHIFT+P).
1. Select `Remote-Containers: Open Folder in Container...` and choose the project directory.
## Customization
### Customize the image
You can make changes to the [Dockerfile](Dockerfile) and then rebuild the image. For example, your Dockerfile could be:
```Dockerfile
FROM qmcgaw/rustdevcontainer
RUN apk add curl
```
To rebuild the image, either:
- With VSCode through the command palette, select `Remote-Containers: Rebuild and reopen in container`
- With a terminal, go to this directory and `docker-compose build`
### Customize VS code settings
You can customize **settings** and **extensions** in the [devcontainer.json](devcontainer.json) definition file.
### Entrypoint script
You can bind mount a shell script to `/home/vscode/.welcome.sh` to replace the [current welcome script](shell/.welcome.sh).
### Publish a port
To access a port from your host to your development container, publish a port in [docker-compose.yml](docker-compose.yml). You can also now do it directly with VSCode without restarting the container.
### Run other services
1. Modify [docker-compose.yml](docker-compose.yml) to launch other services at the same time as this development container, such as a test database:
```yml
database:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: password
```
1. In [devcontainer.json](devcontainer.json), change the line `"runServices": ["vscode"],` to `"runServices": ["vscode", "database"],`.
1. In the VS code command palette, rebuild the container.

View file

@ -0,0 +1,40 @@
{
"name": "prometheus_wireguard_exporter_dev",
"dockerComposeFile": ["docker-compose.yml"],
"service": "vscode",
"runServices": ["vscode"],
"shutdownAction": "stopCompose",
"postCreateCommand": "",
"workspaceFolder": "/workspace",
// "overrideCommand": "",
"extensions": [
"matklad.rust-analyzer",
"tamasfe.even-better-toml", // for Cargo.toml
"eamodio.gitlens", // IDE Git information
"davidanson.vscode-markdownlint",
"ms-azuretools.vscode-docker", // Docker integration and linting
"shardulm94.trailing-spaces", // Show trailing spaces
"Gruntfuggly.todo-tree", // Highlights TODO comments
"bierner.emojisense", // Emoji sense for markdown
"stkb.rewrap", // rewrap comments after n characters on one line
"vscode-icons-team.vscode-icons", // Better file extension icons
"github.vscode-pull-request-github", // Github interaction
"redhat.vscode-yaml", // Kubernetes, Drone syntax highlighting
"bajdzis.vscode-database", // Supports connections to mysql or postgres, over SSL, socked
"IBM.output-colorizer", // Colorize your output/test logs
// "mohsen1.prettify-json", // Prettify JSON data
// "zxh404.vscode-proto3", // Supports Proto syntax
// "jrebocho.vscode-random", // Generates random values
// "alefragnani.Bookmarks", // Manage bookmarks
// "quicktype.quicktype", // Paste JSON as code
// "spikespaz.vscode-smoothtype", // smooth cursor animation
],
"settings": {
"files.eol": "\n",
"remote.extensionKind": {
"ms-azuretools.vscode-docker": "workspace"
},
"editor.codeActionsOnSaveTimeout": 3000,
"rust-analyzer.serverPath": "/usr/local/bin/rust-analyzer"
}
}

View file

@ -0,0 +1,31 @@
version: "3.7"
services:
vscode:
build: .
image: rustdevcontainer
volumes:
- ../:/workspace
# Docker
- ~/.docker:/root/.docker:z
# Docker socket to access Docker server
- /var/run/docker.sock:/var/run/docker.sock
# SSH directory for Linux, OSX and WSL
- ~/.ssh:/root/.ssh:z
# For Windows without WSL, a copy will be made
# from /tmp/.ssh to ~/.ssh to fix permissions
# - ~/.ssh:/tmp/.ssh:ro
# Shell history persistence
- ~/.zsh_history:/root/.zsh_history:z
# Git config
- ~/.gitconfig:/root/.gitconfig:z
# Kubernetes
- ~/.kube:/root/.kube:z
environment:
- TZ=
# Needed for debugging
# cap_add:
# - SYS_PTRACE
# security_opt:
# - seccomp:unconfined
entrypoint: zsh -c "while sleep 1000; do :; done"

View file

@ -1,26 +0,0 @@
name: Docker build
on:
pull_request:
branches: [master]
paths-ignore:
- .github/workflows/buildx-branch.yml
- .github/workflows/buildx-latest.yml
- .github/workflows/buildx-release.yml
- .github/workflows/dockerhub-description.yml
- .github/workflows/rust.yml
- extra
- _config.yml
- .gitignore
- .rustfmt.toml
- .gitignore
- example.json
- LICENSE
- README.md
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Build image
run: docker build .

View file

@ -1,42 +0,0 @@
name: Buildx branch
on:
push:
branches:
- '*'
- '*/*'
- '!master'
paths-ignore:
- .github/workflows/build.yml
- .github/workflows/buildx-latest.yml
- .github/workflows/buildx-release.yml
- .github/workflows/dockerhub-description.yml
- .github/workflows/rust.yml
- extra
- _config.yml
- .gitignore
- .rustfmt.toml
- .gitignore
- example.json
- LICENSE
- README.md
jobs:
buildx:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Buildx setup
uses: crazy-max/ghaction-docker-buildx@v1
- name: Dockerhub login
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u mindflavor --password-stdin 2>&1
- name: Run Buildx
run: |
docker buildx build \
--progress plain \
--platform=linux/amd64 \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
--build-arg COMMIT=`git rev-parse --short HEAD` \
--build-arg VERSION=${GITHUB_REF##*/} \
-t mindflavor/prometheus-wireguard-exporter:${GITHUB_REF##*/} \
--push \
.
- run: curl -X POST https://hooks.microbadger.com/images/mindflavor/prometheus-wireguard-exporter/TODO || exit 0

View file

@ -1,39 +0,0 @@
name: Buildx latest
on:
release:
branches: [master]
paths-ignore:
- .github/workflows/build.yml
- .github/workflows/buildx-branch.yml
- .github/workflows/buildx-release.yml
- .github/workflows/dockerhub-description.yml
- .github/workflows/rust.yml
- extra
- _config.yml
- .gitignore
- .rustfmt.toml
- .gitignore
- example.json
- LICENSE
- README.md
jobs:
buildx:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Buildx setup
uses: crazy-max/ghaction-docker-buildx@v1
- name: Dockerhub login
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u mindflavor --password-stdin 2>&1
- name: Run Buildx
run: |
docker buildx build \
--progress plain \
--platform=linux/amd64 \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
--build-arg COMMIT=`git rev-parse --short HEAD` \
--build-arg VERSION=latest \
-t mindflavor/prometheus-wireguard-exporter:latest \
--push \
.
- run: curl -X POST https://hooks.microbadger.com/images/mindflavor/prometheus-wireguard-exporter/TODO || exit 0

View file

@ -1,39 +0,0 @@
name: Buildx release
on:
release:
types: [published]
paths-ignore:
- .github/workflows/build.yml
- .github/workflows/buildx-branch.yml
- .github/workflows/buildx-latest.yml
- .github/workflows/dockerhub-description.yml
- .github/workflows/rust.yml
- extra
- _config.yml
- .gitignore
- .rustfmt.toml
- .gitignore
- example.json
- LICENSE
- README.md
jobs:
buildx:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Buildx setup
uses: crazy-max/ghaction-docker-buildx@v1
- name: Dockerhub login
run: echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u mindflavor --password-stdin 2>&1
- name: Run Buildx
run: |
docker buildx build \
--progress plain \
--platform=linux/amd64 \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
--build-arg COMMIT=`git rev-parse --short HEAD` \
--build-arg VERSION=${GITHUB_REF##*/} \
-t mindflavor/prometheus-wireguard-exporter:${GITHUB_REF##*/} \
--push \
.
- run: curl -X POST https://hooks.microbadger.com/images/mindflavor/prometheus-wireguard-exporter/TODO || exit 0

58
.github/workflows/ci.yaml vendored Normal file
View file

@ -0,0 +1,58 @@
name: CI
on:
push:
branches: [ master ]
tags: [ '*' ]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
channel: [stable]
target:
# https://doc.rust-lang.org/nightly/rustc/platform-support.html
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-gnu
- armv7-unknown-linux-gnueabihf # rpi
- x86_64-unknown-linux-musl
- aarch64-unknown-linux-musl
- armv7-unknown-linux-musleabihf # rpi
- x86_64-unknown-freebsd
# - aarch64-unknown-freebsd <- std not precompiled
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.channel }}
target: ${{ matrix.target }}
override: true
- run: cargo install --git https://github.com/cross-rs/cross.git # cross in crates.io is too old
- name: Build
continue-on-error: ${{ matrix.channel != 'stable' }}
run: cross build --release --target ${{ matrix.target }}
- name: Rename binary
run: mv target/${{ matrix.target }}/release/prometheus_wireguard_exporter prometheus_wireguard_exporter_${{ matrix.target }}
- uses: actions/upload-artifact@v2
if: ${{ matrix.channel == 'stable' }}
with:
name: prometheus_wireguard_exporter_${{ matrix.target }}
path: prometheus_wireguard_exporter_${{ matrix.target }}
- uses: alexellis/upload-assets@0.3.0
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ github.token }}
with:
asset_paths: '["prometheus_wireguard_exporter_${{ matrix.target }}"]'

84
.github/workflows/docker.yml vendored Normal file
View file

@ -0,0 +1,84 @@
name: CI
on:
push:
paths:
- .github/workflows/docker.yml
- src/**
- .dockerignore
- Cargo.lock
- Cargo.toml
- Dockerfile
pull_request:
paths:
- .github/workflows/docker.yml
- src/**
- .dockerignore
- Cargo.lock
- Cargo.toml
- Dockerfile
jobs:
verify:
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: "1"
steps:
- uses: actions/checkout@v2.3.4
- name: Lint
run: docker build --target lint .
- name: Build test image
run: docker build --target test -t test-container .
- name: Run tests in test container
run: |
docker run --rm test-container
# We run this using the caching from the previous steps
- name: Build final image
run: docker build .
publish:
needs: [verify]
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.4
- uses: docker/setup-qemu-action@v1
- uses: docker/setup-buildx-action@v1
- uses: docker/login-action@v1.9.0
with:
username: mindflavor
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Set variables
id: vars
run: |
BRANCH=${GITHUB_REF#refs/heads/}
TAG=${GITHUB_REF#refs/tags/}
echo ::set-output name=commit::$(git rev-parse --short HEAD)
echo ::set-output name=build_date::$(date -u +%Y-%m-%dT%H:%M:%SZ)
if [ "$TAG" != "$GITHUB_REF" ]; then
echo ::set-output name=version::$TAG
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7
elif [ "$BRANCH" = "master" ]; then
echo ::set-output name=version::latest
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7
else
echo ::set-output name=version::$BRANCH
echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7
fi
- name: Build and push final image
uses: docker/build-push-action@v2.4.0
with:
platforms: ${{ steps.vars.outputs.platforms }}
build-args: |
BUILD_DATE=${{ steps.vars.outputs.build_date }}
COMMIT=${{ steps.vars.outputs.commit }}
VERSION=${{ steps.vars.outputs.version }}
tags: mindflavor/prometheus-wireguard-exporter:${{ steps.vars.outputs.version }}
push: true

View file

@ -10,10 +10,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v2.3.4
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v2.1.0
env:
DOCKERHUB_USERNAME: mindflavor
DOCKERHUB_PASSWORD: ${{ secrets.DOCKERHUB_PASSWORD }}
DOCKERHUB_REPOSITORY: mindflavor/prometheus-wireguard-exporter
uses: peter-evans/dockerhub-description@v2.4.3
with:
username: mindflavor
password: ${{ secrets.DOCKERHUB_PASSWORD }}
repository: mindflavor/prometheus-wireguard-exporter

View file

@ -1,19 +0,0 @@
name: Rust
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

1245
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package]
name = "prometheus_wireguard_exporter"
version = "3.4.2"
version = "3.6.6"
authors = ["Francesco Cogno <francesco.cogno@outlook.com>"]
description = "Prometheus WireGuard Exporter"
edition = "2018"
@ -14,16 +14,23 @@ homepage = "https://github.com/MindFlavor/prometheus_wireguard_e
keywords = ["prometheus", "exporter", "wireguard"]
categories = ["database"]
[features]
default = []
leaky_log = []
[dependencies]
log = "0.4.6"
env_logger = "0.6.1"
clap = "2.33.0"
serde_json = "1.0.39"
serde = "1.0.91"
serde_derive = "1.0.91"
failure = "0.1.5"
hyper = { version = "0.13.0-alpha.4" , features = ["unstable-stream"] }
http = "0.1.17"
tokio = "0.2.0-alpha.6"
prometheus_exporter_base = { version = "0.30.2" }
regex = "1.3.1"
log = "0.4.17"
env_logger = "0.9.3"
clap = { version = "4.0.26", features = ["cargo", "env"] }
serde_json = "1.0.88"
serde = "1.0.147"
thiserror = "1.0.37"
anyhow = "1.0.66"
hyper = { version = "0.14.23", features = ["stream"] }
http = "0.2.8"
tokio = { version = "1.22.0", features = ["macros", "rt"] }
prometheus_exporter_base = { version = "1.3.0", features = ["hyper_server"] }
regex = "1.7.0"
[dev-dependencies]
clippy = "0.0.302"

View file

@ -1,34 +1,135 @@
ARG ALPINE_VERSION=3.12
ARG RUST_VERSION=1-alpine${ALPINE_VERSION}
ARG BUILDPLATFORM=linux/amd64
FROM rust:${RUST_VERSION} AS build
ARG ALPINE_VERSION=3.14
ARG RUST_VERSION=1.69-bullseye
FROM --platform=${BUILDPLATFORM} rust:${RUST_VERSION} AS base
WORKDIR /usr/src/prometheus_wireguard_exporter
# Setup
ARG ARCH=x86_64
RUN apk add --update -q --no-cache musl-dev
RUN rustup target add ${ARCH}-unknown-linux-musl
RUN apt-get update -y && \
apt-get install -y \
# to cross build with musl
musl-tools \
# to download the musl cross build tool
wget \
# for verifying the binary properties
file
# Download dependencies
RUN mkdir src && \
echo 'fn main() {}' > src/main.rs
COPY Cargo.toml Cargo.lock ./
RUN cargo fetch && \
rm src/main.rs
ARG STATIC=yes
RUN touch /tmp/rustflags && \
if [ "${STATIC}" != "yes" ]; then \
echo "-C target-feature=-crt-static" | tee /tmp/rustflags; \
fi
ARG TARGETPLATFORM
RUN echo "Setting variables for ${TARGETPLATFORM:=linux/amd64}" && \
case "${TARGETPLATFORM}" in \
linux/amd64) \
MUSL="x86_64-linux-musl"; \
RUSTTARGET="x86_64-unknown-linux-musl"; \
break;; \
linux/arm64) \
MUSL="aarch64-linux-musl"; \
RUSTTARGET="aarch64-unknown-linux-musl"; \
break;; \
linux/arm/v7) \
MUSL="armv7m-linux-musleabi"; \
RUSTTARGET="armv7-unknown-linux-musleabi"; \
break;; \
linux/arm/v6) \
MUSL="armv6-linux-musleabi"; \
RUSTTARGET="arm-unknown-linux-musleabi"; \
break;; \
linux/386) \
MUSL="i686-linux-musl"; \
RUSTTARGET="i686-unknown-linux-musl"; \
break;; \
linux/ppc64le) \
MUSL="powerpc64le-linux-musl"; \
RUSTTARGET="powerpc64le-unknown-linux-musl"; \
break;; \
linux/s390x) \
MUSL="s390x-linux-musl"; \
RUSTTARGET="s390x-unknown-linux-musl"; \
break;; \
linux/riscv64) \
MUSL="riscv64-linux-musl"; \
RUSTTARGET="riscv64gc-unknown-linux-musl"; \
break;; \
*) echo "unsupported platform ${TARGETPLATFORM}"; exit 1;; \
esac && \
echo "${MUSL}" | tee /tmp/musl && \
echo "${RUSTTARGET}" | tee /tmp/rusttarget
RUN MUSL="$(cat /tmp/musl)" && \
wget -qO- "https://musl.cc/$MUSL-cross.tgz" | tar -xzC /tmp && \
rm "/tmp/$MUSL-cross/usr" && \
cp -fr /tmp/"$MUSL"-cross/* / && \
rm -rf "/tmp/$MUSL-cross"
RUN rustup target add "$(cat /tmp/rusttarget)"
# Copy .cargo/config for cross build configuration
COPY .cargo ./.cargo
# Install Clippy for build platform
RUN rustup component add clippy
# Install dependencies
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && \
echo "fn main() {}" > src/main.rs
RUN cargo build --release && \
rm -rf target/release/deps/prometheus_wireguard_exporter*
RUN echo 'fn main() {}' > src/main.rs && \
RUSTFLAGS="$(cat /tmp/rustflags)" \
CC="$(cat /tmp/musl)-gcc" \
cargo build --target "$(cat /tmp/rusttarget)" --release
RUN rm -r \
target/*-linux-*/release/deps/prometheus_wireguard_exporter* \
target/*-linux-*/release/prometheus_wireguard_exporter* \
src/main.rs
# Build the musl linked binary
COPY . .
RUN cargo build --release
RUN cargo install --target ${ARCH}-unknown-linux-musl --path .
FROM base AS lint
RUN RUSTFLAGS="$(cat /tmp/rustflags)" \
CC="$(cat /tmp/musl)-gcc" \
cargo clippy --target "$(cat /tmp/rusttarget)"
FROM base AS test
ENTRYPOINT \
RUSTFLAGS="$(cat /tmp/rustflags)" \
CC="$(cat /tmp/musl)-gcc" \
cargo test --target "$(cat /tmp/rusttarget)"
FROM base AS build
# Build static binary with musl built-in
RUN RUSTFLAGS="$(cat /tmp/rustflags)" \
CC="$(cat /tmp/musl)-gcc" \
cargo build --target "$(cat /tmp/rusttarget)" --release && \
mv target/*-linux-*/release/prometheus_wireguard_exporter /tmp/binary
RUN description="$(file /tmp/binary)" && \
echo "$description" && \
if [ "${STATIC}" = "yes" ] && [ ! -z "$(echo $description | grep musl)" ]; then \
echo "binary is not statically built!" && exit 1; \
fi
FROM alpine:${ALPINE_VERSION}
EXPOSE 9586/tcp
WORKDIR /usr/local/bin
RUN apk add --no-cache --q tini && \
rm -rf /var/cache/apk/*
RUN adduser prometheus-wireguard-exporter -s /bin/sh -D -u 1000 1000 && \
mkdir -p /etc/sudoers.d && \
echo 'prometheus-wireguard-exporter ALL=(root) NOPASSWD:/usr/bin/wg show * dump' > /etc/sudoers.d/prometheus-wireguard-exporter && \
chmod 0440 /etc/sudoers.d/prometheus-wireguard-exporter
RUN apk add --update -q --no-cache wireguard-tools-wg sudo
USER prometheus-wireguard-exporter
ENTRYPOINT [ "prometheus_wireguard_exporter" ]
#USER root
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/prometheus_wireguard_exporter"]
CMD [ "-a" ]
COPY --from=build --chown=prometheus-wireguard-exporter /usr/local/cargo/bin/prometheus_wireguard_exporter /usr/local/bin/prometheus_wireguard_exporter
COPY --from=build --chown=prometheus-wireguard-exporter /tmp/binary ./prometheus_wireguard_exporter

223
README.md
View file

@ -4,24 +4,30 @@
[![Crate](https://img.shields.io/crates/v/prometheus_wireguard_exporter.svg)](https://crates.io/crates/prometheus_wireguard_exporter) [![cratedown](https://img.shields.io/crates/d/prometheus_wireguard_exporter.svg)](https://crates.io/crates/prometheus_wireguard_exporter) [![cratelastdown](https://img.shields.io/crates/dv/prometheus_wireguard_exporter.svg)](https://crates.io/crates/prometheus_wireguard_exporter)
[![release](https://img.shields.io/github/release/MindFlavor/prometheus_wireguard_exporter.svg)](https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/3.4.2)
[![tag](https://img.shields.io/github/tag/mindflavor/prometheus_wireguard_exporter.svg)](https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/3.4.2)
[![release](https://img.shields.io/github/release/MindFlavor/prometheus_wireguard_exporter.svg)](https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/3.6.6)
[![tag](https://img.shields.io/github/tag/mindflavor/prometheus_wireguard_exporter.svg)](https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/3.6.6)
[![Rust build](https://github.com/mindflavor/prometheus_wireguard_exporter/workflows/Rust/badge.svg)](https://github.com/mindflavor/prometheus_wireguard_exporter/actions?query=workflow%3ARust)
[![commitssince](https://img.shields.io/github/commits-since/mindflavor/prometheus_wireguard_exporter/3.4.2.svg)](https://img.shields.io/github/commits-since/mindflavor/prometheus_wireguard_exporter/3.4.2.svg)
[![commitssince](https://img.shields.io/github/commits-since/mindflavor/prometheus_wireguard_exporter/3.6.6)](https://img.shields.io/github/commits-since/mindflavor/prometheus_wireguard_exporter/3.6.6)
![Docker build](https://github.com/MindFlavor/prometheus_wireguard_exporter/workflows/Buildx%20latest/badge.svg)
[![Docker build](https://github.com/MindFlavor/prometheus_wireguard_exporter/actions/workflows/docker.yml/badge.svg)](https://github.com/qdm12/godevcontainer/actions/workflows/docker.yml)
[![dockeri.co](https://dockeri.co/image/mindflavor/prometheus-wireguard-exporter)](https://hub.docker.com/r/mindflavor/prometheus-wireguard-exporter)
## Intro
A Prometheus exporter for [WireGuard](https://www.wireguard.com), 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](https://prometheus.io/) can understand. The exporter is very light on your server resources, both in terms of memory and CPU usage.
A Prometheus exporter for [WireGuard](https://www.wireguard.com), 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](https://prometheus.io/) 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`.
![](extra/01.png)
## Changelog
* From release [3.6.4](https://github.com/MindFlavor/prometheus_wireguard_exporter/releases/tag/3.6.4) the exporter optionally calculates the delta, in seconds, since the last handshake. The metric is `wireguard_latest_handshake_delay_seconds`. Thanks to [mmahacek](https://github.com/mmahacek) for the [idea](https://github.com/MindFlavor/prometheus_wireguard_exporter/issues/100).
* From release [3.6.3](https://github.com/MindFlavor/prometheus_wireguard_exporter/releases/tag/3.6.3) the exporter automatically parses the systemd-networkd's peer syntax too (`[WireGuardPeer]` rather than `[Peer]`). Thanks to [mbonino](https://github.com/mbonino) for the PR (see https://github.com/MindFlavor/prometheus_wireguard_exporter/pull/92).
* From release [3.6.1](https://github.com/MindFlavor/prometheus_wireguard_exporter/releases/tag/3.6.1) the exporter correctly escapes the double quotes in `friendly_name`. Thanks to [Steven Wood](https://github.com/stvnw) for finding the bug in #82.
* **BREAKING** From version `3.6.0` the exporter takes fallback configuration values from the environment variables. Thanks to [j_r0dd](https://github.com/jr0dd) for the idea. This changes how the exporter evaluates the command line parameters: make sure to consult the documentation on how to convert your command line to the new format. Basically every switch (for example verbose `-v`) not expect values, either `true` or `false`. This is necessary because there is no way to discriminate between an empty environment variable and one that has not been set.
* From release [3.5.1](https://github.com/MindFlavor/prometheus_wireguard_exporter/releases/tag/3.5.1) the exporter supports multiple peer files. Thanks to [Tobias Krischer](https://github.com/tobikris) for the idea.
* From release [3.5.0](https://github.com/MindFlavor/prometheus_wireguard_exporter/releases/tag/3.5.0) the exporter supports the `friendly_json` tag. Entries prepended with the `friendly_json` tag will output all the entries in the specificed json as Prometheus attributes. Thanks to [DrProxyProSupport](https://github.com/iqdoctor) for the idea.
* From release [3.4.1](https://github.com/MindFlavor/prometheus_wireguard_exporter/releases/tag/3.4.0) the exporter supports prepending `sudo` to the `wg` command. This allows to run the exporter as a non root user (although sudoer without password). Thanks to [Jonas Seydel](https://github.com/Thor77) for the idea.
* **BREAKING** From release [3.4.0](https://github.com/MindFlavor/prometheus_wireguard_exporter/releases/tag/3.4.0) the exporter requires you to specify the friendly names in a specific format (only if you want to use them of course). This allows you to use arbitrary comments in the file while keeping the friendly name functionality. Thank you [Miloš Bunčić](https://github.com/psyhomb) for this. This also paves the way for future metadata. In order to migrate you can use this sed command: `sed -i 's/#/# friendly_name=/' peers.conf`. Please make sure to do a backup before using it!
* From release [3.3.1](https://github.com/MindFlavor/prometheus_wireguard_exporter/releases/tag/3.3.1) the exporter accepts multiple interfaces in the command line options. Just pass the `-i` parameter multiple times. Note the not specifying the interface is equivalent to specifying every one of them (the exporter will pass the `all` parameter to `wg show` command).
@ -29,63 +35,108 @@ A Prometheus exporter for [WireGuard](https://www.wireguard.com), written in Rus
* From release [3.0.0](https://github.com/MindFlavor/prometheus_wireguard_exporter/releases/tag/3.0.0) the exporter allows two label modes: one is to dump every allowed ip in a single label (called `allowed_ips`) along with their subnets. The second one is to create a pair of labels for each allowed ip/subnet pair (called `allowed_ip_0`/`allowed_subnet_0`, `allowed_ip_1`/`allowed_subnet_1` and so on for every allowed ip). The default if the single label mode but you can enable the second mode by specifying the `-s` switch at startup. Thank you [Toon Schoenmakers](https://github.com/schoentoon) for this solution (see issue [#8](https://github.com/MindFlavor/prometheus_wireguard_exporter/issues/8)).
* Starting from release [2.0.2](https://github.com/MindFlavor/prometheus_wireguard_exporter/releases/tag/2.0.2) this exporter supports IPv6 addresses too (thanks to [Maximilian Bosch](https://github.com/Ma27)'s PR [#5](https://github.com/MindFlavor/prometheus_wireguard_exporter/pull/5)).
## Prerequisites
## Setup
* You need [Rust](https://www.rust-lang.org/) to compile this code. Simply follow the instructions on Rust's website to install the toolchain. If you get weird errors while compiling please try and update your Rust version first (I have developed it on `rustc 1.42.0 (b8cedc004 2020-03-09)`). Alternatively you can build the docker image or use the prebuilt one.
* You need [WireGuard](https://www.wireguard.com) *and* the `wg` CLI in the path. The tool will call `wg show <interface(s)>|all dump` and of course will fail if the `wg` executable is not found. If you want I can add the option of specifying the `wg` path in the command line, just open an issue for it.
### Pre-built binaries
Alternatively, as long as you have Wireguard on your host kernel with some Wireguard interfaces running, you can use Docker. For example:
Coming soon, subcribe to [#59](https://github.com/MindFlavor/prometheus_wireguard_exporter/issues/59)
### Docker
1. You need Docker installed
1. You need [WireGuard](https://www.wireguard.com) installed in your host kernel
1. You need some Wireguard interfaces running
1. 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](https://wiki.alpinelinux.org/wiki/Release_Notes_for_Alpine_3.13.0#time64_requirements)
1. Check it's up by visiting [http://localhost:9586/metrics](http://localhost:9586/metrics)
You can then update the image with
```sh
docker run -it --rm --init --net=host --cap-add=NET_ADMIN mindflavor/prometheus-wireguard-exporter
# Check it's up
docker run -it --rm alpine:3.12 wget -qO- http://localhost:9586/metrics
docker pull mindflavor/prometheus_wireguard_exporter
```
## Compilation
Or use a [tagged image](https://hub.docker.com/r/mindflavor/prometheus-wireguard-exporter/tags) such as `:3.5.1`.
To compile the latest master version:
```bash
git clone https://github.com/MindFlavor/prometheus_wireguard_exporter.git
cd prometheus_wireguard_exporter
cargo install --path .
```
If you want the latest release you can simply use:
```bash
cargo install prometheus_wireguard_exporter
```
You can also build the Docker image with
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 .
docker build -t mindflavor/prometheus_wireguard_exporter https://github.com/MindFlavor/prometheus_wireguard_exporter.git#master
```
### Build from source
1. You need [Rust](https://www.rust-lang.org/tools/install) installed
1. You need [WireGuard](https://www.wireguard.com) installed on your host
1. 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.
1. You need some Wireguard interfaces running
1. You need `git` installed
1. Clone the repository with
```sh
git clone https://github.com/MindFlavor/prometheus_wireguard_exporter.git
cd prometheus_wireguard_exporter
```
1. 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)`.
1. Run the program
```sh
./prometheus_wireguard_exporter
```
1. Check it's up by visiting [http://localhost:9586/metrics](http://localhost:9586/metrics)
## Usage
Start the binary with `-h` to get the complete syntax. The parameters are:
### Flags available
| Parameter | Mandatory | Valid values | Default | Accepts multiple occurrences? | Description |
| -- | -- | -- | -- | -- | -- |
| `-v` | no | <switch> | | No | Enable verbose mode.
| `-a` | no | <switch> | | No | Prepends sudo to `wg` commands.
| `-l` | 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` | no | any valid port number | 9586 | No | Specify the service port. This is the port your Prometheus instance should point to.
| `-n` | no | path to the wireguard configuration file | | No | This flag adds the *friendly_name* attribute to the exported entries. See [Friendly names](#friendly-names) for more details.
| `-s` | no | <switch> | off | No | Enable the allowed ip + subnet split mode for the labels.
| `-r` | no | <switch> | off | No | Exports peer's remote ip and port as labels (if available).
| `-i` | no | your interface name(s) | `all` | Yes | Specifies the interface(s) passed to the `wg show <interface> dump` parameter. Multiple parameters are allowed.
Start the binary with `-h` to get the complete syntax. The parameters are below.
❗**Important** ❗: since 3.6.0, every parameter requires a value. In other words, even the `-v` (verbose) parameter requires `true` or `false` after it. Passing a parameter without value (for example `-v`) is the same of not passing the parameter at all: the default value will be used instead (in the case of the verbose option, it means `false`).
For example, if you want to enable the verbose mode and enable the *prepend sudo* option you would use the following command line:
```bash
prometheus_wireguard_exporter -a true -v true <...>
```
| 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](#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.
| `-d` | `EXPORT_LATEST_HANDSHAKE_DELAY` | No | `true` or `false` | `false` | No | Adds the `wireguard_latest_handshake_delay_seconds` metric that automatically calculates the seconds passed since the last handshake.
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 Names
### Friendly Tags
Starting from version 1.2 you can instruct the exporter to append a *friendly name* to the exported entries. This can make the output more understandable than using the public keys. For example this is the standard output:
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 wireguard_sent_bytes_total Bytes sent to the peer
# TYPE wireguard_sent_bytes_total counter
wireguard_sent_bytes_total{interface="wg0",public_key="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=",allowed_ips="10.70.0.2/32,10.70.0.66/32"} 3208804
@ -117,7 +168,7 @@ wireguard_latest_handshake_seconds{interface="wg0",public_key="wTjv6hS6fKfNK+SzO
And this is the one augmented with friendly names:
```
```ebnf
# HELP wireguard_sent_bytes_total Bytes sent to the peer
# TYPE wireguard_sent_bytes_total counter
wireguard_sent_bytes_total{interface="wg0",public_key="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=",allowed_ips="10.70.0.2/32,10.70.0.66/32",friendly_name="OnePlus 6T"} 3208804
@ -147,8 +198,11 @@ wireguard_latest_handshake_seconds{interface="wg0",public_key="928vO9Lf4+Mo84cWu
wireguard_latest_handshake_seconds{interface="wg0",public_key="wTjv6hS6fKfNK+SzOLo7O6BQjEb6AD1TN9GjwZ08IwA=",allowed_ips="10.70.0.5/32",friendly_name="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. For example this is how you edit your WireGuard configuration file:
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](https://github.com/MindFlavor/prometheus_wireguard_exporter/releases/tag/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]
@ -178,7 +232,7 @@ As you can see, all you need to do is to add the friendly name in the comments p
This is a sample of the label split mode:
```
```ebnf
# HELP wireguard_sent_bytes_total Bytes sent to the peer
# TYPE wireguard_sent_bytes_total counter
wireguard_sent_bytes_total{interface="wg0",public_key="2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=",allowed_ip_0="10.70.0.2",allowed_subnet_0="32",allowed_ip_1="10.70.0.66",allowed_subnet_1="32",friendly_name="OnePlus 6T"} 3208804
@ -210,9 +264,9 @@ wireguard_latest_handshake_seconds{interface="wg0",public_key="wTjv6hS6fKfNK+SzO
### 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:
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 or configure a sudo rule (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
@ -227,3 +281,74 @@ ExecStart=/usr/local/bin/prometheus_wireguard_exporter -n /etc/wireguard/peers.c
[Install]
WantedBy=multi-user.target
```
Running it as normal user + hardening:
```ini
[Unit]
Description=Prometheus WireGuard Exporter
Wants=network-online.target
After=network-online.target
[Service]
User=wireguard_exporter
Group=wireguard_exporter
Type=simple
Restart=on-failure
EnvironmentFile=-/etc/conf.d/prometheus-wireguard-exporter
ExecStart=/usr/local/bin/prometheus-wireguard-exporter $WIREGUARD_EXPORTER_ARGS
PrivateTmp=yes
ProtectHome=yes
ProtectControlGroups=yes
UMask=077
RemoveIPC=yes
BindReadOnlyPaths=/dev/log /run/systemd/journal/socket /run/systemd/journal/stdout
ProtectSystem=strict
ProtectProc=noaccess
[Install]
WantedBy=multi-user.target
```
Most of the other systemd hardening options won't work because they block sudo. With the above unit, you can use the following sudo rule:
```
wireguard_exporter ALL=(root) NOPASSWD: /usr/bin/wg
```
If you're interested in more hardening, you can analyze the unit with:
```
systemd-analyze security prometheus-wireguard-exporter.service
```
## Development
### Locally
1. Install [Rust](https://www.rust-lang.org/tools/install)
1. Install [Rust Analyzer](https://rust-analyzer.github.io/manual.html#installation) and set it up with your editor
1. Install [Clippy](https://github.com/rust-lang/rust-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/prometheus_wireguard_exporter .
```
### VSCode development container
This is more of a plug and play solution based on Docker and VSCode.
See [.devcontainer/README.md](https://github.com/MindFlavor/prometheus_wireguard_exporter/blob/master/.devcontainer/README.md)

1
clippy.toml Normal file
View file

@ -0,0 +1 @@
# see https://rust-lang.github.io/rust-clippy/master/index.html

View file

@ -1,40 +1,54 @@
#[derive(Debug, Fail)]
pub enum PeerEntryParseError {
#[fail(display = "PublicKey entry not found in lines: {:?}", lines)]
PublicKeyNotFound { lines: Vec<String> },
use thiserror::Error;
#[fail(display = "AllowedIPs entry not found in lines: {:?}", lines)]
AllowedIPsEntryNotFound { lines: Vec<String> },
#[derive(Error, Debug)]
pub enum FriendlyDescritionParseError {
#[error("unsupported header")]
UnsupportedHeader(String),
#[error("json parse error")]
SerdeJsonError(#[from] serde_json::Error),
}
#[derive(Debug, Fail)]
#[derive(Debug, Error)]
pub enum PeerEntryParseError {
#[error("PublicKey entry not found in lines: {:?}", lines)]
PublicKeyNotFound { lines: Vec<String> },
#[error("AllowedIPs entry not found in lines: {:?}", lines)]
AllowedIPsEntryNotFound { lines: Vec<String> },
#[error("Friendly description parse error")]
FriendlyDescritionParseError(#[from] FriendlyDescritionParseError),
}
#[derive(Debug, Error)]
pub enum ExporterError {
#[allow(dead_code)]
#[fail(display = "Generic error")]
#[error("Generic error")]
Generic {},
#[fail(display = "Hyper error: {}", e)]
Hyper { e: hyper::error::Error },
#[error("Hyper error: {}", e)]
Hyper { e: hyper::Error },
#[fail(display = "http error: {}", e)]
#[error("http error: {}", e)]
Http { e: http::Error },
#[fail(display = "UTF-8 error: {}", e)]
#[error("UTF-8 error: {}", e)]
UTF8 { e: std::string::FromUtf8Error },
#[fail(display = "JSON format error: {}", e)]
JSON { e: serde_json::error::Error },
#[error("JSON format error: {}", e)]
Json { e: serde_json::error::Error },
#[fail(display = "IO Error: {}", e)]
#[error("IO Error: {}", e)]
IO { e: std::io::Error },
#[fail(display = "UTF8 conversion error: {}", e)]
#[error("UTF8 conversion error: {}", e)]
Utf8 { e: std::str::Utf8Error },
#[fail(display = "int conversion error: {}", e)]
#[error("int conversion error: {}", e)]
ParseInt { e: std::num::ParseIntError },
#[fail(display = "PeerEntry parse error: {}", e)]
#[error("PeerEntry parse error: {}", e)]
PeerEntryParseError { e: PeerEntryParseError },
}
@ -50,8 +64,8 @@ impl From<std::io::Error> for ExporterError {
}
}
impl From<hyper::error::Error> for ExporterError {
fn from(e: hyper::error::Error) -> Self {
impl From<hyper::Error> for ExporterError {
fn from(e: hyper::Error) -> Self {
ExporterError::Hyper { e }
}
}
@ -70,7 +84,7 @@ impl From<std::string::FromUtf8Error> for ExporterError {
impl From<serde_json::error::Error> for ExporterError {
fn from(e: serde_json::error::Error) -> Self {
ExporterError::JSON { e }
ExporterError::Json { e }
}
}

View file

@ -0,0 +1,51 @@
use crate::exporter_error::FriendlyDescritionParseError;
use std::borrow::Cow;
use std::collections::HashMap;
use std::convert::TryFrom;
#[derive(Debug, Clone, PartialEq)]
pub enum FriendlyDescription<'a> {
Name(Cow<'a, str>),
Json(HashMap<&'a str, serde_json::Value>),
}
impl<'a> TryFrom<(&'a str, &'a str)> for FriendlyDescription<'a> {
type Error = FriendlyDescritionParseError;
fn try_from((header_name, value): (&'a str, &'a str)) -> Result<Self, Self::Error> {
Ok(match header_name {
"friendly_name" => FriendlyDescription::Name(value.replace('\"', "\\\"").into()),
"friendly_json" => {
let ret: HashMap<&str, serde_json::Value> = serde_json::from_str(value)?;
FriendlyDescription::Json(ret)
}
other => {
return Err(FriendlyDescritionParseError::UnsupportedHeader(format!(
"{} is not a supported tag",
other
)))
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::convert::TryInto;
#[test]
fn test_no_escape_friendly_name() {
let fd: FriendlyDescription = ("friendly_name", "no escaping").try_into().unwrap();
assert_eq!(fd, FriendlyDescription::Name("no escaping".into()));
}
#[test]
fn test_escape_friendly_name() {
const TO_ESCAPE: &str = r#"man this is a quote ""#;
const ESCAPED: &str = r#"man this is a quote \""#;
let fd: FriendlyDescription = ("friendly_name", TO_ESCAPE).try_into().unwrap();
assert_eq!(fd, FriendlyDescription::Name(ESCAPED.into()));
}
}

View file

@ -1,48 +1,53 @@
extern crate serde_json;
#[macro_use]
extern crate failure;
use clap::{crate_authors, crate_name, crate_version, Arg};
use anyhow::Context;
use clap::{crate_authors, crate_name, crate_version, value_parser, Arg};
use hyper::{Body, Request};
use log::{debug, info, trace};
use prometheus_exporter_base::prelude::{Authorization, ServerOptions};
use std::env;
mod options;
use options::Options;
mod wireguard;
use std::convert::TryFrom;
use std::process::Command;
use std::string::String;
mod friendly_description;
pub use friendly_description::*;
use wireguard::WireGuard;
mod exporter_error;
mod wireguard_config;
use wireguard_config::peer_entry_hashmap_try_from;
extern crate prometheus_exporter_base;
use prometheus_exporter_base::render_prometheus;
use std::net::IpAddr;
use std::sync::Arc;
use wireguard_config::peer_entry_hashmap_try_from;
async fn perform_request(
_req: Request<Body>,
options: Arc<Options>,
) -> Result<String, failure::Error> {
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
let interfaces_to_handle = match &options.interfaces {
Some(interfaces_str) => interfaces_str.clone(),
None => vec!["all".to_owned()],
};
log::trace!("interfaces_to_handle == {:?}", interfaces_to_handle);
let peer_entry_contents =
if let Some(extract_names_config_file) = &options.extract_names_config_file {
Some(::std::fs::read_to_string(
&extract_names_config_file as &str,
)?)
} else {
None
};
let peer_entry_contents = options
.extract_names_config_files
.as_ref()
.map(|files| {
files // if we have values
.iter() // for each value
.map(|file| std::fs::read_to_string(file as &str)) // read the contents into a String
.collect::<Result<Vec<String>, std::io::Error>>() // And transform it into a vec (stopping in case of errors)
})
.transpose()
.with_context(|| "failed to read peer config file")? // bail out if there was an error
.map(|strings| strings.join("\n")); // now join the strings in a new string
let peer_entry_hashmap = if let Some(peer_entry_contents) = &peer_entry_contents {
Some(peer_entry_hashmap_try_from(peer_entry_contents)?)
} else {
None
};
let peer_entry_hashmap = peer_entry_contents
.as_ref()
.map(|contents| peer_entry_hashmap_try_from(contents))
.transpose()?;
trace!("peer_entry_hashmap == {:#?}", peer_entry_hashmap);
let mut wg_accumulator: Option<WireGuard> = None;
@ -100,74 +105,97 @@ async fn perform_request(
}
if let Some(wg_accumulator) = wg_accumulator {
Ok(wg_accumulator.render_with_names(
peer_entry_hashmap.as_ref(),
options.separate_allowed_ips,
options.export_remote_ip_and_port,
))
Ok(wg_accumulator.render_with_names(peer_entry_hashmap.as_ref(), &options))
} else {
panic!();
}
}
#[tokio::main]
async fn main() {
let matches = clap::App::new(crate_name!())
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let matches = clap::Command::new(crate_name!())
.version(crate_version!())
.author(crate_authors!("\n"))
.arg(
Arg::with_name("addr")
.short("l")
Arg::new("addr")
.short('l')
.long("address")
.env("PROMETHEUS_WIREGUARD_EXPORTER_ADDRESS")
.value_parser(value_parser!(IpAddr))
.help("exporter address")
.default_value("0.0.0.0")
.takes_value(true),
)
.arg(
Arg::with_name("port")
.short("p")
Arg::new("port")
.short('p')
.long("port")
.env("PROMETHEUS_WIREGUARD_EXPORTER_PORT")
.value_parser(value_parser!(u16))
.help("exporter port")
.default_value("9586")
.takes_value(true),
)
.arg(
Arg::with_name("verbose")
.short("v")
Arg::new("verbose")
.short('v')
.long("verbose")
.env("PROMETHEUS_WIREGUARD_EXPORTER_VERBOSE_ENABLED")
.value_parser(value_parser!(bool))
.help("verbose logging")
.takes_value(false),
.default_value("false")
)
.arg(
Arg::with_name("prepend_sudo")
.short("a")
Arg::new("prepend_sudo")
.short('a')
.long("prepend_sudo")
.env("PROMETHEUS_WIREGUARD_EXPORTER_PREPEND_SUDO_ENABLED")
.value_parser(value_parser!(bool))
.help("Prepend sudo to the wg show commands")
.takes_value(false),
.default_value("false")
)
.arg(
Arg::with_name("separate_allowed_ips")
.short("s")
Arg::new("separate_allowed_ips")
.short('s')
.long("separate_allowed_ips")
.env("PROMETHEUS_WIREGUARD_EXPORTER_SEPARATE_ALLOWED_IPS_ENABLED")
.value_parser(value_parser!(bool))
.help("separate allowed ips and ports")
.takes_value(false),
.default_value("false")
)
.arg(
Arg::with_name("export_remote_ip_and_port")
.short("r")
Arg::new("export_remote_ip_and_port")
.short('r')
.long("export_remote_ip_and_port")
.env("PROMETHEUS_WIREGUARD_EXPORTER_EXPORT_REMOTE_IP_AND_PORT_ENABLED")
.value_parser(value_parser!(bool))
.help("exports peer's remote ip and port as labels (if available)")
.takes_value(false),
.default_value("false")
)
.arg(
Arg::with_name("extract_names_config_files")
.short("n")
.help("If set, the exporter will look in the specified WireGuard config file for peer names (must be in [Peer] definition and be a comment)")
.multiple(false)
.number_of_values(1)
.takes_value(true))
Arg::new("extract_names_config_files")
.short('n')
.long("extract_names_config_files")
.num_args(0..)
.env("PROMETHEUS_WIREGUARD_EXPORTER_CONFIG_FILE_NAMES")
.help("If set, the exporter will look in the specified WireGuard config file for peer names (must be in [Peer] definition and be a comment). Multiple files are supported.")
.use_value_delimiter(false))
.arg(
Arg::with_name("interfaces")
.short("i")
Arg::new("interfaces")
.short('i')
.long("interfaces")
.num_args(0..)
.env("PROMETHEUS_WIREGUARD_EXPORTER_INTERFACES")
.help("If set specifies the interface passed to the wg show command. It is relative to the same position config_file. In not specified, all will be passed.")
.multiple(true)
.number_of_values(1)
.takes_value(true))
.get_matches();
.use_value_delimiter(false))
.arg(
Arg::new("export_latest_handshake_delay")
.short('d')
.long("export_latest_handshake_delay")
.env("EXPORT_LATEST_HANDSHAKE_DELAY")
.value_parser(value_parser!(bool))
.help("exports runtime calculated latest handshake delay")
.default_value("false")
)
.get_matches();
let options = Options::from_claps(&matches);
@ -191,15 +219,21 @@ async fn main() {
);
info!("using options: {:?}", options);
let bind = matches.value_of("port").unwrap();
let bind = u16::from_str_radix(&bind, 10).expect("port must be a valid number");
let ip = matches.value_of("addr").unwrap().parse::<IpAddr>().unwrap();
let addr = (ip, bind).into();
let bind: u16 = *matches.get_one("port").unwrap();
let ip: IpAddr = *matches.get_one("addr").unwrap();
let addr: std::net::SocketAddr = (ip, bind).into();
info!("starting exporter on http://{}/metrics", addr);
render_prometheus(addr, options, |request, options| {
let server_options = ServerOptions {
addr,
authorization: Authorization::None,
};
render_prometheus(server_options, options, |request, options| {
Box::pin(perform_request(request, options))
})
.await;
Ok(())
}

View file

@ -1,31 +1,34 @@
use clap::parser::ValuesRef;
#[derive(Debug, Clone)]
pub(crate) struct Options {
pub verbose: bool,
pub prepend_sudo: bool,
pub separate_allowed_ips: bool,
pub extract_names_config_file: Option<String>,
pub extract_names_config_files: Option<Vec<String>>,
pub interfaces: Option<Vec<String>>,
pub export_remote_ip_and_port: bool,
pub export_latest_handshake_delay: bool,
}
impl Options {
pub fn from_claps(matches: &clap::ArgMatches<'_>) -> Options {
pub fn from_claps(matches: &clap::ArgMatches) -> Options {
let options = Options {
verbose: matches.is_present("verbose"),
prepend_sudo: matches.is_present("prepend_sudo"),
separate_allowed_ips: matches.is_present("separate_allowed_ips"),
extract_names_config_file: matches
.value_of("extract_names_config_files")
.map(|e| e.to_owned()),
interfaces: matches.values_of("interfaces").map(|e| {
e.into_iter()
.map(|a| {
println!("a ==> {}", a);
a.to_owned()
})
.collect()
}),
export_remote_ip_and_port: matches.is_present("export_remote_ip_and_port"),
verbose: *matches.get_one("verbose").unwrap_or(&false),
prepend_sudo: *matches.get_one("prepend_sudo").unwrap_or(&false),
separate_allowed_ips: *matches.get_one("separate_allowed_ips").unwrap_or(&false),
extract_names_config_files: matches
.get_many("extract_names_config_files")
.map(|e: ValuesRef<'_, String>| e.into_iter().map(|a| a.to_owned()).collect()),
interfaces: matches
.get_many("interfaces")
.map(|e: ValuesRef<'_, String>| e.into_iter().map(|a| a.to_string()).collect()),
export_remote_ip_and_port: *matches
.get_one("export_remote_ip_and_port")
.unwrap_or(&false),
export_latest_handshake_delay: *matches
.get_one("export_latest_handshake_delay")
.unwrap_or(&false),
};
options

View file

@ -1,18 +1,46 @@
use crate::exporter_error::ExporterError;
use crate::options::Options;
use crate::wireguard_config::PeerEntryHashMap;
use crate::FriendlyDescription;
use log::{debug, trace};
use prometheus_exporter_base::{MetricType, PrometheusMetric};
use prometheus_exporter_base::{MetricType, PrometheusInstance, PrometheusMetric};
use regex::Regex;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::Debug;
use std::net::SocketAddr;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
const EMPTY: &str = "(none)";
#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct SecureString(String);
#[cfg(feature = "leaky_log")]
impl Debug for SecureString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(not(feature = "leaky_log"))]
impl Debug for SecureString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("**hidden**")
}
}
impl From<&str> for SecureString {
fn from(s: &str) -> Self {
Self(s.to_owned())
}
}
#[allow(dead_code)]
#[derive(Default, Debug, Clone)]
pub(crate) struct LocalEndpoint {
pub public_key: String,
pub private_key: String,
pub private_key: SecureString,
pub local_port: u16,
pub persistent_keepalive: bool,
}
@ -26,6 +54,7 @@ pub(crate) struct RemoteEndpoint {
pub latest_handshake: u64,
pub sent_bytes: u128,
pub received_bytes: u128,
#[allow(dead_code)]
pub persistent_keepalive: bool,
}
@ -69,7 +98,7 @@ impl TryFrom<&str> for WireGuard {
// this is the local interface
Endpoint::Local(LocalEndpoint {
public_key: v[1].to_owned(),
private_key: v[2].to_owned(),
private_key: v[2].into(),
local_port: v[3].parse::<u16>().unwrap(),
persistent_keepalive: to_bool(v[4]),
})
@ -114,8 +143,7 @@ impl TryFrom<&str> for WireGuard {
if let Some(endpoints) = wg.interfaces.get_mut(v[0]) {
endpoints.push(endpoint);
} else {
let mut new_vec = Vec::new();
new_vec.push(endpoint);
let new_vec = vec![endpoint];
wg.interfaces.insert(v[0].to_owned(), new_vec);
}
}
@ -128,11 +156,11 @@ impl TryFrom<&str> for WireGuard {
impl WireGuard {
pub fn merge(&mut self, merge_from: &WireGuard) {
for (interface_name, endpoints_to_merge) in merge_from.interfaces.iter() {
if let Some(endpoints) = self.interfaces.get_mut(&interface_name as &str) {
endpoints.extend_from_slice(&endpoints_to_merge);
if let Some(endpoints) = self.interfaces.get_mut(interface_name as &str) {
endpoints.extend_from_slice(endpoints_to_merge);
} else {
let mut new_vec = Vec::new();
new_vec.extend_from_slice(&endpoints_to_merge);
new_vec.extend_from_slice(endpoints_to_merge);
self.interfaces.insert(interface_name.to_owned(), new_vec);
}
}
@ -141,41 +169,40 @@ impl WireGuard {
pub(crate) fn render_with_names(
&self,
pehm: Option<&PeerEntryHashMap>,
split_allowed_ips: bool,
export_remote_ip_and_port: bool,
options: &Options,
) -> String {
debug!("WireGuard::render_with_names(self == {:?}, pehm == {:?}, split_allowed_ips == {:?}, export_remote_ip_and_port == {:?} called", self, pehm, split_allowed_ips,export_remote_ip_and_port);
debug!(
"WireGuard::render_with_names(self == {:?}, pehm == {:?}, options == {:?} called",
self, pehm, options
);
// these are the exported counters
let pc_sent_bytes_total = PrometheusMetric::new(
"wireguard_sent_bytes_total",
MetricType::Counter,
"Bytes sent to the peer",
);
let pc_received_bytes_total = PrometheusMetric::new(
"wireguard_received_bytes_total",
MetricType::Counter,
"Bytes received from the peer",
);
let pc_latest_handshake = PrometheusMetric::new(
"wireguard_latest_handshake_seconds",
MetricType::Gauge,
"Seconds from the last handshake",
);
// these 3 vectors will hold the intermediate
// values. We use the vector in order to traverse
// the interfaces slice only once: since we need to output
// the values grouped by counter we populate the vectors here
// and then reorder during the final string creation phase.
let mut s_sent_bytes_total = Vec::new();
s_sent_bytes_total.push(pc_sent_bytes_total.render_header());
let mut s_received_bytes_total = Vec::new();
s_received_bytes_total.push(pc_received_bytes_total.render_header());
let mut s_latest_handshake = Vec::new();
s_latest_handshake.push(pc_latest_handshake.render_header());
let mut pc_sent_bytes_total = PrometheusMetric::build()
.with_name("wireguard_sent_bytes_total")
.with_metric_type(MetricType::Counter)
.with_help("Bytes sent to the peer")
.build();
let mut pc_received_bytes_total = PrometheusMetric::build()
.with_name("wireguard_received_bytes_total")
.with_metric_type(MetricType::Counter)
.with_help("Bytes received from the peer")
.build();
let mut pc_latest_handshake = PrometheusMetric::build()
.with_name("wireguard_latest_handshake_seconds")
.with_metric_type(MetricType::Gauge)
.with_help("UNIX timestamp seconds of the last handshake")
.build();
let mut pc_latest_handshake_delay = if options.export_latest_handshake_delay {
Some(
PrometheusMetric::build()
.with_name("wireguard_latest_handshake_delay_seconds")
.with_metric_type(MetricType::Gauge)
.with_help("Seconds from the last handshake")
.build(),
)
} else {
None
};
// Here we make sure we process the interfaces in the
// lexicographical order.
@ -201,12 +228,10 @@ impl WireGuard {
// store in attibutes their references. attributes_owned is onyl
// needed for separate ip+subnet
let mut attributes_owned: Vec<(String, String)> = Vec::new();
let mut attributes: Vec<(&str, &str)> = Vec::new();
let mut attributes: Vec<(&str, &str)> =
vec![("interface", interface), ("public_key", &ep.public_key)];
attributes.push(("interface", interface));
attributes.push(("public_key", &ep.public_key));
if split_allowed_ips {
if options.separate_allowed_ips {
let v_ip_and_subnet: Vec<(&str, &str)> = ep
.allowed_ips
.split(',')
@ -240,16 +265,47 @@ impl WireGuard {
// let's add the friendly_name attribute if present
// and has meaniningful value
if let Some(pehm) = pehm {
if let Some(ep_friendly_name) = pehm.get(&ep.public_key as &str) {
if let Some(ep_friendly_name) = ep_friendly_name.name {
attributes.push(("friendly_name", &ep_friendly_name));
if let Some(ep_friendly_description) = pehm.get(&ep.public_key as &str) {
if let Some(friendly_description) =
&ep_friendly_description.friendly_description
{
match friendly_description {
FriendlyDescription::Name(name) => {
attributes.push(("friendly_name", name));
}
FriendlyDescription::Json(json) => {
// let's put them in a intermediate vector and then sort it
let mut v_temp = Vec::new();
json.iter().for_each(|(header, value)| {
//attributes_owned
v_temp.push((
header.to_string(),
match value {
serde_json::Value::Number(number) => {
number.to_string()
}
serde_json::Value::String(s) => s.to_owned(),
serde_json::Value::Bool(b) => b.to_string(),
_ => panic!("unsupported json value"),
},
));
});
v_temp.sort_by(|(k0, _), (k1, _)| k0.cmp(k1));
v_temp
.into_iter()
.for_each(|item| attributes_owned.push(item));
}
}
}
}
}
if export_remote_ip_and_port {
if options.export_remote_ip_and_port {
if let Some(r_ip) = &ep.remote_ip {
attributes.push(("remote_ip", &r_ip));
attributes.push(("remote_ip", r_ip));
}
if let Some(r_port) = &ep.remote_port {
attributes_owned.push(("remote_port".to_string(), r_port.to_string()));
@ -260,31 +316,49 @@ impl WireGuard {
attributes.push((label, val));
}
s_sent_bytes_total
.push(pc_sent_bytes_total.render_sample(Some(&attributes), ep.sent_bytes));
s_received_bytes_total.push(
pc_received_bytes_total.render_sample(Some(&attributes), ep.received_bytes),
);
s_latest_handshake.push(
pc_latest_handshake.render_sample(Some(&attributes), ep.latest_handshake),
let mut instance = PrometheusInstance::new();
for (h, v) in attributes {
instance = instance.with_label(h, v);
}
pc_latest_handshake_delay
.as_mut()
.map(|pc_latest_handshake_delay| {
let earlier = UNIX_EPOCH + Duration::from_secs(ep.latest_handshake);
let delta = SystemTime::now()
.duration_since(earlier)
.expect("time went backwards");
pc_latest_handshake_delay.render_and_append_instance(
&instance.clone().with_value(delta.as_secs() as u128),
)
});
pc_sent_bytes_total
.render_and_append_instance(&instance.clone().with_value(ep.sent_bytes))
.render();
pc_received_bytes_total
.render_and_append_instance(&instance.clone().with_value(ep.received_bytes))
.render();
pc_latest_handshake.render_and_append_instance(
&instance.with_value(ep.latest_handshake.into()),
);
}
}
}
// now let's join the results and return it to the caller
let mut s = String::with_capacity(s_latest_handshake.len() * 64 * 3);
for item in s_sent_bytes_total {
s.push_str(&item);
}
for item in s_received_bytes_total {
s.push_str(&item);
}
for item in s_latest_handshake {
s.push_str(&item);
}
s
format!(
"{}\n{}\n{}{}",
pc_sent_bytes_total.render(),
pc_received_bytes_total.render(),
pc_latest_handshake.render(),
pc_latest_handshake_delay.map_or_else(
// this row adds pc_latest_handshake_delay only if configured
|| "".to_owned(),
|pc_latest_handshake_delay| format!("\n{}", pc_latest_handshake_delay.render())
)
)
}
}
@ -357,7 +431,17 @@ wg0\tsUsR6xufQQ8Tf0FuyY9tfEeYdhVMeFelr4ZMUrj+B0E=\t(none)\t10.211.123.128:51820\
let pe = PeerEntryHashMap::new();
let s = a.render_with_names(Some(&pe), true, true);
let options = Options {
verbose: true,
prepend_sudo: true,
separate_allowed_ips: true,
extract_names_config_files: None,
interfaces: None,
export_remote_ip_and_port: true,
export_latest_handshake_delay: false,
};
let s = a.render_with_names(Some(&pe), &options);
println!("{}", s);
let s_ok = "# HELP wireguard_sent_bytes_total Bytes sent to the peer
@ -379,6 +463,7 @@ wireguard_sent_bytes_total{interface=\"wg0\",public_key=\"yZOoC2t6pBcXvoczuiJqrQ
wireguard_sent_bytes_total{interface=\"wg0\",public_key=\"yjeBkrZqUThSSHySFzWCjxAH8cxtiWSI2I8JFD6t1UM=\",remote_ip=\"10.211.123.126\",allowed_ip_0=\"10.90.0.5\",allowed_subnet_0=\"32\",remote_port=\"51820\"} 10642564136
wireguard_sent_bytes_total{interface=\"wg0\",public_key=\"HtOSi37ALMnSkeAFqeWYZqlBnZqAJERhb5o/i3ZPEFI=\",remote_ip=\"10.211.123.127\",allowed_ip_0=\"10.90.0.17\",allowed_subnet_0=\"32\",remote_port=\"51820\"} 1439257868
wireguard_sent_bytes_total{interface=\"wg0\",public_key=\"sUsR6xufQQ8Tf0FuyY9tfEeYdhVMeFelr4ZMUrj+B0E=\",remote_ip=\"10.211.123.128\",allowed_ip_0=\"10.90.0.18\",allowed_subnet_0=\"32\",remote_port=\"51820\"} 1624251784
# HELP wireguard_received_bytes_total Bytes received from the peer
# TYPE wireguard_received_bytes_total counter
wireguard_received_bytes_total{interface=\"wg0\",public_key=\"923V/iAdcz8BcqB0Xo6pDJzARGBJCQ6fWe+peixQyB4=\",remote_ip=\"10.211.123.112\",allowed_ip_0=\"10.90.0.10\",allowed_subnet_0=\"32\",allowed_ip_1=\"10.0.1.0\",allowed_subnet_1=\"24\",remote_port=\"51820\"} 0
@ -398,7 +483,8 @@ wireguard_received_bytes_total{interface=\"wg0\",public_key=\"yZOoC2t6pBcXvoczui
wireguard_received_bytes_total{interface=\"wg0\",public_key=\"yjeBkrZqUThSSHySFzWCjxAH8cxtiWSI2I8JFD6t1UM=\",remote_ip=\"10.211.123.126\",allowed_ip_0=\"10.90.0.5\",allowed_subnet_0=\"32\",remote_port=\"51820\"} 18576788764
wireguard_received_bytes_total{interface=\"wg0\",public_key=\"HtOSi37ALMnSkeAFqeWYZqlBnZqAJERhb5o/i3ZPEFI=\",remote_ip=\"10.211.123.127\",allowed_ip_0=\"10.90.0.17\",allowed_subnet_0=\"32\",remote_port=\"51820\"} 62592693520
wireguard_received_bytes_total{interface=\"wg0\",public_key=\"sUsR6xufQQ8Tf0FuyY9tfEeYdhVMeFelr4ZMUrj+B0E=\",remote_ip=\"10.211.123.128\",allowed_ip_0=\"10.90.0.18\",allowed_subnet_0=\"32\",remote_port=\"51820\"} 75066288152
# HELP wireguard_latest_handshake_seconds Seconds from the last handshake
# HELP wireguard_latest_handshake_seconds UNIX timestamp seconds of the last handshake
# TYPE wireguard_latest_handshake_seconds gauge
wireguard_latest_handshake_seconds{interface=\"wg0\",public_key=\"923V/iAdcz8BcqB0Xo6pDJzARGBJCQ6fWe+peixQyB4=\",remote_ip=\"10.211.123.112\",allowed_ip_0=\"10.90.0.10\",allowed_subnet_0=\"32\",allowed_ip_1=\"10.0.1.0\",allowed_subnet_1=\"24\",remote_port=\"51820\"} 0
wireguard_latest_handshake_seconds{interface=\"wg0\",public_key=\"9M1fhLa9sIlT39z+SI/0a5H3mNSHYmM+NGA6sirD2nU=\",remote_ip=\"10.211.123.113\",allowed_ip_0=\"10.90.0.3\",allowed_subnet_0=\"32\",allowed_ip_1=\"10.198.171.0\",allowed_subnet_1=\"24\",remote_port=\"51820\"} 0
@ -445,13 +531,24 @@ wireguard_latest_handshake_seconds{interface=\"wg0\",public_key=\"sUsR6xufQQ8Tf0
#[test]
fn test_parse_and_serialize() {
let a = WireGuard::try_from(TEXT).unwrap();
let s = a.render_with_names(None, false, true);
let options = Options {
verbose: true,
prepend_sudo: true,
separate_allowed_ips: false,
extract_names_config_files: None,
interfaces: None,
export_remote_ip_and_port: true,
export_latest_handshake_delay: true,
};
let s = a.render_with_names(None, &options);
println!("{}", s);
}
#[test]
fn test_render_to_prometheus_simple() {
const REF : &str= "# HELP wireguard_sent_bytes_total Bytes sent to the peer\n# TYPE wireguard_sent_bytes_total counter\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"test\",allowed_ips=\"to_change\",remote_ip=\"remote_ip\",remote_port=\"100\"} 1000\n# HELP wireguard_received_bytes_total Bytes received from the peer\n# TYPE wireguard_received_bytes_total counter\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"test\",allowed_ips=\"to_change\",remote_ip=\"remote_ip\",remote_port=\"100\"} 5000\n# HELP wireguard_latest_handshake_seconds Seconds from the last handshake\n# TYPE wireguard_latest_handshake_seconds gauge\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"test\",allowed_ips=\"to_change\",remote_ip=\"remote_ip\",remote_port=\"100\"} 500\n";
const REF : &str= "# HELP wireguard_sent_bytes_total Bytes sent to the peer\n# TYPE wireguard_sent_bytes_total counter\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"test\",allowed_ips=\"to_change\",remote_ip=\"remote_ip\",remote_port=\"100\"} 1000\n\n# HELP wireguard_received_bytes_total Bytes received from the peer\n# TYPE wireguard_received_bytes_total counter\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"test\",allowed_ips=\"to_change\",remote_ip=\"remote_ip\",remote_port=\"100\"} 5000\n\n# HELP wireguard_latest_handshake_seconds UNIX timestamp seconds of the last handshake\n# TYPE wireguard_latest_handshake_seconds gauge\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"test\",allowed_ips=\"to_change\",remote_ip=\"remote_ip\",remote_port=\"100\"} 500\n";
let re = Endpoint::Remote(RemoteEndpoint {
public_key: "test".to_owned(),
@ -467,11 +564,20 @@ wireguard_latest_handshake_seconds{interface=\"wg0\",public_key=\"sUsR6xufQQ8Tf0
interfaces: HashMap::new(),
};
let mut v = Vec::new();
v.push(re);
let v = vec![re];
wg.interfaces.insert("Pippo".to_owned(), v);
let prometheus = wg.render_with_names(None, false, true);
let options = Options {
verbose: true,
prepend_sudo: true,
separate_allowed_ips: false,
extract_names_config_files: None,
interfaces: None,
export_remote_ip_and_port: true,
export_latest_handshake_delay: false,
};
let prometheus = wg.render_with_names(None, &options);
assert_eq!(prometheus, REF);
}
@ -480,11 +586,13 @@ wireguard_latest_handshake_seconds{interface=\"wg0\",public_key=\"sUsR6xufQQ8Tf0
fn test_render_to_prometheus_complex() {
use crate::wireguard_config::PeerEntry;
const REF :&'static str = "# HELP wireguard_sent_bytes_total Bytes sent to the peer\n# TYPE wireguard_sent_bytes_total counter\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"test\",allowed_ips=\"10.0.0.2/32,fd86:ea04:::4/128\",remote_ip=\"remote_ip\",remote_port=\"100\"} 1000\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"second_test\",allowed_ips=\"10.0.0.4/32,fd86:ea04:::4/128,192.168.0.0/16\",friendly_name=\"this is my friendly name\",remote_ip=\"remote_ip\",remote_port=\"100\"} 14\n# HELP wireguard_received_bytes_total Bytes received from the peer\n# TYPE wireguard_received_bytes_total counter\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"test\",allowed_ips=\"10.0.0.2/32,fd86:ea04:::4/128\",remote_ip=\"remote_ip\",remote_port=\"100\"} 5000\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"second_test\",allowed_ips=\"10.0.0.4/32,fd86:ea04:::4/128,192.168.0.0/16\",friendly_name=\"this is my friendly name\",remote_ip=\"remote_ip\",remote_port=\"100\"} 1000000000\n# HELP wireguard_latest_handshake_seconds Seconds from the last handshake\n# TYPE wireguard_latest_handshake_seconds gauge\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"test\",allowed_ips=\"10.0.0.2/32,fd86:ea04:::4/128\",remote_ip=\"remote_ip\",remote_port=\"100\"} 500\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"second_test\",allowed_ips=\"10.0.0.4/32,fd86:ea04:::4/128,192.168.0.0/16\",friendly_name=\"this is my friendly name\",remote_ip=\"remote_ip\",remote_port=\"100\"} 50\n";
const REF :&'static str = "# HELP wireguard_sent_bytes_total Bytes sent to the peer\n# TYPE wireguard_sent_bytes_total counter\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"test\",allowed_ips=\"10.0.0.2/32,fd86:ea04:::4/128\",remote_ip=\"remote_ip\",remote_port=\"100\"} 1000\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"second_test\",allowed_ips=\"10.0.0.4/32,fd86:ea04:::4/128,192.168.0.0/16\",friendly_name=\"this is my friendly name\",remote_ip=\"remote_ip\",remote_port=\"100\"} 14\n\n# HELP wireguard_received_bytes_total Bytes received from the peer\n# TYPE wireguard_received_bytes_total counter\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"test\",allowed_ips=\"10.0.0.2/32,fd86:ea04:::4/128\",remote_ip=\"remote_ip\",remote_port=\"100\"} 5000\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"second_test\",allowed_ips=\"10.0.0.4/32,fd86:ea04:::4/128,192.168.0.0/16\",friendly_name=\"this is my friendly name\",remote_ip=\"remote_ip\",remote_port=\"100\"} 1000000000\n\n# HELP wireguard_latest_handshake_seconds UNIX timestamp seconds of the last handshake\n# TYPE wireguard_latest_handshake_seconds gauge\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"test\",allowed_ips=\"10.0.0.2/32,fd86:ea04:::4/128\",remote_ip=\"remote_ip\",remote_port=\"100\"} 500\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"second_test\",allowed_ips=\"10.0.0.4/32,fd86:ea04:::4/128,192.168.0.0/16\",friendly_name=\"this is my friendly name\",remote_ip=\"remote_ip\",remote_port=\"100\"} 50\n";
const REF_SPLIT :&'static str = "# HELP wireguard_sent_bytes_total Bytes sent to the peer\n# TYPE wireguard_sent_bytes_total counter\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"test\",remote_ip=\"remote_ip\",allowed_ip_0=\"10.0.0.2\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",remote_port=\"100\"} 1000\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"second_test\",friendly_name=\"this is my friendly name\",remote_ip=\"remote_ip\",allowed_ip_0=\"10.0.0.4\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",allowed_ip_2=\"192.168.0.0\",allowed_subnet_2=\"16\",remote_port=\"100\"} 14\n# HELP wireguard_received_bytes_total Bytes received from the peer\n# TYPE wireguard_received_bytes_total counter\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"test\",remote_ip=\"remote_ip\",allowed_ip_0=\"10.0.0.2\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",remote_port=\"100\"} 5000\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"second_test\",friendly_name=\"this is my friendly name\",remote_ip=\"remote_ip\",allowed_ip_0=\"10.0.0.4\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",allowed_ip_2=\"192.168.0.0\",allowed_subnet_2=\"16\",remote_port=\"100\"} 1000000000\n# HELP wireguard_latest_handshake_seconds Seconds from the last handshake\n# TYPE wireguard_latest_handshake_seconds gauge\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"test\",remote_ip=\"remote_ip\",allowed_ip_0=\"10.0.0.2\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",remote_port=\"100\"} 500\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"second_test\",friendly_name=\"this is my friendly name\",remote_ip=\"remote_ip\",allowed_ip_0=\"10.0.0.4\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",allowed_ip_2=\"192.168.0.0\",allowed_subnet_2=\"16\",remote_port=\"100\"} 50\n";
const REF_SPLIT :&'static str = "# HELP wireguard_sent_bytes_total Bytes sent to the peer\n# TYPE wireguard_sent_bytes_total counter\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"test\",remote_ip=\"remote_ip\",allowed_ip_0=\"10.0.0.2\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",remote_port=\"100\"} 1000\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"second_test\",friendly_name=\"this is my friendly name\",remote_ip=\"remote_ip\",allowed_ip_0=\"10.0.0.4\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",allowed_ip_2=\"192.168.0.0\",allowed_subnet_2=\"16\",remote_port=\"100\"} 14\n\n# HELP wireguard_received_bytes_total Bytes received from the peer\n# TYPE wireguard_received_bytes_total counter\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"test\",remote_ip=\"remote_ip\",allowed_ip_0=\"10.0.0.2\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",remote_port=\"100\"} 5000\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"second_test\",friendly_name=\"this is my friendly name\",remote_ip=\"remote_ip\",allowed_ip_0=\"10.0.0.4\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",allowed_ip_2=\"192.168.0.0\",allowed_subnet_2=\"16\",remote_port=\"100\"} 1000000000\n\n# HELP wireguard_latest_handshake_seconds UNIX timestamp seconds of the last handshake\n# TYPE wireguard_latest_handshake_seconds gauge\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"test\",remote_ip=\"remote_ip\",allowed_ip_0=\"10.0.0.2\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",remote_port=\"100\"} 500\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"second_test\",friendly_name=\"this is my friendly name\",remote_ip=\"remote_ip\",allowed_ip_0=\"10.0.0.4\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",allowed_ip_2=\"192.168.0.0\",allowed_subnet_2=\"16\",remote_port=\"100\"} 50\n";
const REF_SPLIT_NO_REMOTE :&'static str = "# HELP wireguard_sent_bytes_total Bytes sent to the peer\n# TYPE wireguard_sent_bytes_total counter\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"test\",allowed_ip_0=\"10.0.0.2\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\"} 1000\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"second_test\",friendly_name=\"this is my friendly name\",allowed_ip_0=\"10.0.0.4\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",allowed_ip_2=\"192.168.0.0\",allowed_subnet_2=\"16\"} 14\n# HELP wireguard_received_bytes_total Bytes received from the peer\n# TYPE wireguard_received_bytes_total counter\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"test\",allowed_ip_0=\"10.0.0.2\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\"} 5000\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"second_test\",friendly_name=\"this is my friendly name\",allowed_ip_0=\"10.0.0.4\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",allowed_ip_2=\"192.168.0.0\",allowed_subnet_2=\"16\"} 1000000000\n# HELP wireguard_latest_handshake_seconds Seconds from the last handshake\n# TYPE wireguard_latest_handshake_seconds gauge\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"test\",allowed_ip_0=\"10.0.0.2\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\"} 500\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"second_test\",friendly_name=\"this is my friendly name\",allowed_ip_0=\"10.0.0.4\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",allowed_ip_2=\"192.168.0.0\",allowed_subnet_2=\"16\"} 50\n";
const REF_SPLIT_NO_REMOTE :&'static str = "# HELP wireguard_sent_bytes_total Bytes sent to the peer\n# TYPE wireguard_sent_bytes_total counter\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"test\",allowed_ip_0=\"10.0.0.2\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\"} 1000\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"second_test\",friendly_name=\"this is my friendly name\",allowed_ip_0=\"10.0.0.4\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",allowed_ip_2=\"192.168.0.0\",allowed_subnet_2=\"16\"} 14\n\n# HELP wireguard_received_bytes_total Bytes received from the peer\n# TYPE wireguard_received_bytes_total counter\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"test\",allowed_ip_0=\"10.0.0.2\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\"} 5000\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"second_test\",friendly_name=\"this is my friendly name\",allowed_ip_0=\"10.0.0.4\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",allowed_ip_2=\"192.168.0.0\",allowed_subnet_2=\"16\"} 1000000000\n\n# HELP wireguard_latest_handshake_seconds UNIX timestamp seconds of the last handshake\n# TYPE wireguard_latest_handshake_seconds gauge\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"test\",allowed_ip_0=\"10.0.0.2\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\"} 500\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"second_test\",friendly_name=\"this is my friendly name\",allowed_ip_0=\"10.0.0.4\",allowed_subnet_0=\"32\",allowed_ip_1=\"fd86:ea04:::4\",allowed_subnet_1=\"128\",allowed_ip_2=\"192.168.0.0\",allowed_subnet_2=\"16\"} 50\n";
const REF_JSON :&'static str = "# HELP wireguard_sent_bytes_total Bytes sent to the peer\n# TYPE wireguard_sent_bytes_total counter\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"test\",allowed_ips=\"10.0.0.2/32,fd86:ea04:::4/128\",remote_ip=\"remote_ip\",remote_port=\"100\"} 1000\nwireguard_sent_bytes_total{interface=\"Pippo\",public_key=\"second_test\",allowed_ips=\"10.0.0.4/32,fd86:ea04:::4/128,192.168.0.0/16\",remote_ip=\"remote_ip\",auth_date=\"1614869789\",first_name=\"Coordinator\",id=\"482217555\",last_name=\"DrProxy.me\",username=\"DrProxyMeCoordinator\",remote_port=\"100\"} 14\n\n# HELP wireguard_received_bytes_total Bytes received from the peer\n# TYPE wireguard_received_bytes_total counter\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"test\",allowed_ips=\"10.0.0.2/32,fd86:ea04:::4/128\",remote_ip=\"remote_ip\",remote_port=\"100\"} 5000\nwireguard_received_bytes_total{interface=\"Pippo\",public_key=\"second_test\",allowed_ips=\"10.0.0.4/32,fd86:ea04:::4/128,192.168.0.0/16\",remote_ip=\"remote_ip\",auth_date=\"1614869789\",first_name=\"Coordinator\",id=\"482217555\",last_name=\"DrProxy.me\",username=\"DrProxyMeCoordinator\",remote_port=\"100\"} 1000000000\n\n# HELP wireguard_latest_handshake_seconds UNIX timestamp seconds of the last handshake\n# TYPE wireguard_latest_handshake_seconds gauge\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"test\",allowed_ips=\"10.0.0.2/32,fd86:ea04:::4/128\",remote_ip=\"remote_ip\",remote_port=\"100\"} 500\nwireguard_latest_handshake_seconds{interface=\"Pippo\",public_key=\"second_test\",allowed_ips=\"10.0.0.4/32,fd86:ea04:::4/128,192.168.0.0/16\",remote_ip=\"remote_ip\",auth_date=\"1614869789\",first_name=\"Coordinator\",id=\"482217555\",last_name=\"DrProxy.me\",username=\"DrProxyMeCoordinator\",remote_port=\"100\"} 50\n";
let re1 = Endpoint::Remote(RemoteEndpoint {
public_key: "test".to_owned(),
@ -511,26 +619,71 @@ wireguard_latest_handshake_seconds{interface=\"wg0\",public_key=\"sUsR6xufQQ8Tf0
interfaces: HashMap::new(),
};
let mut v = Vec::new();
v.push(re1);
v.push(re2);
let v = vec![re1, re2];
wg.interfaces.insert("Pippo".to_owned(), v);
let mut pehm = PeerEntryHashMap::new();
let pe = PeerEntry {
public_key: "second_test",
allowed_ips: "ignored",
name: Some("this is my friendly name"),
friendly_description: Some(FriendlyDescription::Name(
"this is my friendly name".into(),
)),
};
pehm.insert(pe.public_key, pe);
pehm.insert(pe.public_key, pe.clone());
let prometheus = wg.render_with_names(Some(&pehm), false, true);
let mut options = Options {
verbose: true,
prepend_sudo: true,
separate_allowed_ips: false,
extract_names_config_files: None,
interfaces: None,
export_remote_ip_and_port: true,
export_latest_handshake_delay: false,
};
let prometheus = wg.render_with_names(Some(&pehm), &options);
assert_eq!(prometheus, REF);
let prometheus = wg.render_with_names(Some(&pehm), true, true);
options.separate_allowed_ips = true;
let prometheus = wg.render_with_names(Some(&pehm), &options);
assert_eq!(prometheus, REF_SPLIT);
let prometheus = wg.render_with_names(Some(&pehm), true, false);
options.export_remote_ip_and_port = false;
let prometheus = wg.render_with_names(Some(&pehm), &options);
assert_eq!(prometheus, REF_SPLIT_NO_REMOTE);
// second test
let mut pehm = PeerEntryHashMap::new();
let mut hm = HashMap::new();
hm.insert(
"username",
serde_json::Value::String("DrProxyMeCoordinator".to_owned()),
);
hm.insert("id", serde_json::Value::Number(482217555.into()));
hm.insert(
"first_name",
serde_json::Value::String("Coordinator".to_owned()),
);
hm.insert(
"last_name",
serde_json::Value::String("DrProxy.me".to_owned()),
);
hm.insert("auth_date", serde_json::Value::Number(1614869789.into()));
let pe = PeerEntry {
public_key: "second_test",
allowed_ips: "ignored",
friendly_description: Some(FriendlyDescription::Json(hm)),
};
pehm.insert(pe.public_key, pe.clone());
options.separate_allowed_ips = false;
options.export_remote_ip_and_port = true;
let prometheus = wg.render_with_names(Some(&pehm), &options);
assert_eq!(prometheus, REF_JSON);
}
}

View file

@ -1,18 +1,21 @@
use crate::exporter_error::PeerEntryParseError;
use crate::FriendlyDescription;
use log::debug;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::convert::TryInto;
#[derive(Debug, Default, Clone)]
pub(crate) struct PeerEntry<'a> {
pub public_key: &'a str,
#[allow(dead_code)]
pub allowed_ips: &'a str,
pub name: Option<&'a str>,
pub friendly_description: Option<FriendlyDescription<'a>>,
}
fn after_char(s: &str, c_split: char) -> &str {
let mut p: usize = 0;
for c in s.chars().into_iter() {
for c in s.chars() {
if c == c_split {
return &s[p + 1..];
} else {
@ -26,7 +29,7 @@ fn after_char_strip_comment(s: &str, c_split: char) -> &str {
let s = after_char(s, c_split);
if let Some(idx) = s.find('#') {
&s[..idx].trim()
s[..idx].trim()
} else {
s
}
@ -55,7 +58,7 @@ impl<'a> TryFrom<&[&'a str]> for PeerEntry<'a> {
let mut public_key = "";
let mut allowed_ips = "";
let mut name = None;
let mut friendly_description = None;
for line in lines {
let line_lowercase = line.to_lowercase();
@ -71,11 +74,9 @@ impl<'a> TryFrom<&[&'a str]> for PeerEntry<'a> {
// if it's a supported key, let' map it.
// we support one key now but this way
// we can support more in the future
#[allow(clippy::single_match)]
match key {
"friendly_name" => {
name = Some(value);
}
"friendly_name" => friendly_description = Some((key, value).try_into()?),
"friendly_json" => friendly_description = Some((key, value).try_into()?),
_ => {}
}
}
@ -97,7 +98,7 @@ impl<'a> TryFrom<&[&'a str]> for PeerEntry<'a> {
let pe = PeerEntry {
public_key,
allowed_ips,
name, // name can be None
friendly_description, // name can be None
};
debug!("PeerEntry::TryFrom returning PeerEntryHasMap == {:?}", pe);
Ok(pe)
@ -116,7 +117,7 @@ pub(crate) fn peer_entry_hashmap_try_from(
let mut v_blocks = Vec::new();
let mut cur_block: Option<Vec<&str>> = None;
for line in txt.lines().into_iter() {
for line in txt.lines() {
if line.starts_with('[') {
if let Some(inner_cur_block) = cur_block {
// close the block
@ -124,7 +125,7 @@ pub(crate) fn peer_entry_hashmap_try_from(
cur_block = None;
}
if line == "[Peer]" {
if line == "[Peer]" || line == "[WireGuardPeer]" {
// start a new block
cur_block = Some(Vec::new());
}
@ -146,7 +147,7 @@ pub(crate) fn peer_entry_hashmap_try_from(
debug!("peer_entry_hashmap_try_from v_blocks == {:?}", v_blocks);
for block in &v_blocks {
let p: PeerEntry = PeerEntry::try_from(&block as &[&str])?;
let p: PeerEntry = PeerEntry::try_from(block as &[&str])?;
hm.insert(p.public_key, p);
}
@ -157,9 +158,10 @@ pub(crate) fn peer_entry_hashmap_try_from(
#[cfg(test)]
mod tests {
use super::FriendlyDescription;
use super::*;
const TEXT: &'static str = "
const TEXT: &str = "
ListenPort = 51820
PrivateKey = my_super_secret_private_key
# PreUp = iptables -t nat -A POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE
@ -203,7 +205,51 @@ PublicKey = 928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=
AllowedIPs = 10.70.0.80/32
";
const TEXT_NOPK: &'static str = "
const TEXT_JSON: &str = "
ListenPort = 51820
PrivateKey = my_super_secret_private_key
# PreUp = iptables -t nat -A POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE
# PostDown = iptables -t nat -D POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE
[Peer]
# This is a comment
# friendly_name=OnePlus 6T
# This is a comment
# This is a comment
# This is a comment
# This is a comment
PublicKey = 2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=
AllowedIPs = 10.70.0.2/32 # this is a comment in AllowedIPs line
[Peer]
# friendly_name=varch.local (laptop)
PublicKey = qnoxQoQI8KKMupLnSSureORV0wMmH7JryZNsmGVISzU=
AllowedIPs = 10.70.0.3/32
[Peer]
# friendly_json={\"id\":482217555,\"username\":\"DrProxyMeCoordinator\", \"first_name\": \"Coordinator\", \"last_name\": \"DrProxy.me\" ,\"auth_date\":1614869789}
PublicKey = L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=
AllowedIPs = 10.70.0.4/32
[Peer]
# frcognoarch
PublicKey = MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=
AllowedIPs = 10.70.0.50/32
[Peer]
# This is a comment
# friendly_name = frcognowin10
# This is something
PublicKey = lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc= # other comment
AllowedIPs = 10.70.0.40/32
[Peer]
#friendly_name = OnePlus 5T
PublicKey = 928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=
AllowedIPs = 10.70.0.80/32
";
const TEXT_NOPK: &str = "
ListenPort = 51820
PrivateKey = my_super_secret_private_key
# PreUp = iptables -t nat -A POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE
@ -224,7 +270,7 @@ PublicKey = L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=
AllowedIPs = 10.70.0.4/32
";
const TEXT_AIP: &'static str = "
const TEXT_AIP: &str = "
ListenPort = 51820
PrivateKey = my_super_secret_private_key
# PreUp = iptables -t nat -A POSTROUTING -s 10.70.0.0/24 -o enp7s0 -j MASQUERADE
@ -278,25 +324,62 @@ PublicKey = L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=
}
#[test]
fn test_parse_friendly_name() {
fn test_parse_friendly_description_json() {
let a: PeerEntryHashMap = peer_entry_hashmap_try_from(TEXT_JSON).unwrap();
let entry = a.get("L2UoJZN7RmEKsMmqaJgKG0m1S2Zs2wd2ptAf+kb3008=");
let entry = entry.expect("this should have been Some (with json!)!");
let mut hm = HashMap::new();
hm.insert(
"username",
serde_json::Value::String("DrProxyMeCoordinator".to_owned()),
);
hm.insert("id", serde_json::Value::Number(482217555.into()));
hm.insert(
"first_name",
serde_json::Value::String("Coordinator".to_owned()),
);
hm.insert(
"last_name",
serde_json::Value::String("DrProxy.me".to_owned()),
);
hm.insert("auth_date", serde_json::Value::Number(1614869789.into()));
assert_eq!(
Some(FriendlyDescription::Json(hm)),
entry.friendly_description
);
}
#[test]
fn test_parse_friendly_description_name() {
let a: PeerEntryHashMap = peer_entry_hashmap_try_from(TEXT).unwrap();
let entry = a.get("lqYcojJMsIZXMUw1heAFbQHBoKjCEaeo7M1WXDh/KWc=");
let entry = entry.expect("this should have been Some (frcognowin10)!");
assert_eq!(Some("frcognowin10"), entry.name);
assert_eq!(
Some(FriendlyDescription::Name("frcognowin10".into())),
entry.friendly_description
);
let entry = a.get("2S7mA0vEMethCNQrJpJKE81/JmhgtB+tHHLYQhgM6kk=");
let entry = entry.expect("this should have been Some!");
assert_eq!(Some("OnePlus 6T"), entry.name);
assert_eq!(
Some(FriendlyDescription::Name("OnePlus 6T".into())),
entry.friendly_description
);
assert_eq!(entry.allowed_ips, "10.70.0.2/32");
let entry = a.get("928vO9Lf4+Mo84cWu4k1oRyzf0AR7FTGoPKHGoTMSHk=");
let entry = entry.expect("this should have been Some!");
assert_eq!(Some("OnePlus 5T"), entry.name);
assert_eq!(
Some(FriendlyDescription::Name("OnePlus 5T".into())),
entry.friendly_description
);
let entry = a.get("MdVOIPKt9K2MPj/sO2NlWQbOnFJ6L/qX80mmhQwsUlA=");
let entry = entry.expect("this should have been Some!");
assert_eq!(None, entry.name);
assert_eq!(None, entry.friendly_description);
}
#[test]