Skip to content

Commit

Permalink
Support for friendly_json comment (#55)
Browse files Browse the repository at this point in the history
* upgrading deps

* tst

* fixed tests

* Json exposed as strings

* fixed double quote

* bumped version

* corrected semantic version bump

* updated README
  • Loading branch information
MindFlavor committed Mar 25, 2021
1 parent 33c5372 commit a07a3d0
Show file tree
Hide file tree
Showing 8 changed files with 542 additions and 833 deletions.
961 changes: 243 additions & 718 deletions Cargo.lock

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "prometheus_wireguard_exporter"
version = "3.4.2"
version = "3.5.0"
authors = ["Francesco Cogno <[email protected]>"]
description = "Prometheus WireGuard Exporter"
edition = "2018"
Expand All @@ -15,15 +15,15 @@ keywords = ["prometheus", "exporter", "wireguard"]
categories = ["database"]

[dependencies]
log = "0.4.6"
env_logger = "0.6.1"
log = "0.4.8"
env_logger = "0.7.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" }
serde_json = "1.0"
serde = "1.0"
serde_derive = "1.0"
thiserror = "1.0"
hyper = { version = "0.14", features = ["stream"] }
http = "0.2"
tokio = { version = "1.0", features = ["macros", "rt"] }
prometheus_exporter_base = { version = "1.2", features = ["hyper_server"] }
regex = "1.3.1"
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

[![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.5.0)
[![tag](https://img.shields.io/github/tag/mindflavor/prometheus_wireguard_exporter.svg)](https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/3.5.0)

[![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.5.0.svg)](https://img.shields.io/github/commits-since/mindflavor/prometheus_wireguard_exporter/3.5.0.svg)

![Docker build](https://github.com/MindFlavor/prometheus_wireguard_exporter/workflows/Buildx%20latest/badge.svg)

Expand All @@ -22,6 +22,7 @@ A Prometheus exporter for [WireGuard](https://www.wireguard.com), written in Rus

## Changelog

* 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).
Expand All @@ -31,7 +32,7 @@ A Prometheus exporter for [WireGuard](https://www.wireguard.com), written in Rus

## Prerequisites

* 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 [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.53.0-nightly (f82664191 2021-03-21)`). 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.

Alternatively, as long as you have Wireguard on your host kernel with some Wireguard interfaces running, you can use Docker. For example:
Expand Down Expand Up @@ -74,16 +75,16 @@ Start the binary with `-h` to get the complete syntax. The parameters are:
| `-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.
| `-n` | no | path to the wireguard configuration file | | No | This flag adds the *friendly_name* attribute or the *friendly_json* attributes to the exported entries. See [Friendly tags](#friendly-tags) 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.

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:

```
# HELP wireguard_sent_bytes_total Bytes sent to the peer
Expand Down Expand Up @@ -148,7 +149,10 @@ wireguard_latest_handshake_seconds{interface="wg0",public_key="wTjv6hS6fKfNK+SzO
```

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:
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]
Expand Down
46 changes: 30 additions & 16 deletions src/exporter_error.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,54 @@
#[derive(Debug, Fail)]
use thiserror::Error;

#[derive(Error, Debug)]
pub enum FriendlyDescritionParseError {
#[error("unsupported header")]
UnsupportedHeader(String),

#[error("json parse error")]
SerdeJsonError(#[from] serde_json::Error),
}

#[derive(Debug, Error)]
pub enum PeerEntryParseError {
#[fail(display = "PublicKey entry not found in lines: {:?}", lines)]
#[error("PublicKey entry not found in lines: {:?}", lines)]
PublicKeyNotFound { lines: Vec<String> },

#[fail(display = "AllowedIPs entry not found in lines: {:?}", lines)]
#[error("AllowedIPs entry not found in lines: {:?}", lines)]
AllowedIPsEntryNotFound { lines: Vec<String> },

#[error("Friendly description parse error")]
FriendlyDescritionParseError(#[from] FriendlyDescritionParseError),
}

#[derive(Debug, Fail)]
#[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)]
#[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 },
}

Expand All @@ -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 }
}
}
Expand Down
31 changes: 31 additions & 0 deletions src/friendly_description.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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.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
)))
}
})
}
}
18 changes: 9 additions & 9 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
extern crate serde_json;
#[macro_use]
extern crate failure;
//extern crate serde_json;
use clap::{crate_authors, crate_name, crate_version, Arg};
use hyper::{Body, Request};
use log::{debug, info, trace};
Expand All @@ -10,20 +8,20 @@ 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()],
Expand Down Expand Up @@ -110,8 +108,8 @@ async fn perform_request(
}
}

#[tokio::main]
async fn main() {
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let matches = clap::App::new(crate_name!())
.version(crate_version!())
.author(crate_authors!("\n"))
Expand Down Expand Up @@ -202,4 +200,6 @@ async fn main() {
Box::pin(perform_request(request, options))
})
.await;

Ok(())
}
Loading

0 comments on commit a07a3d0

Please sign in to comment.