This commit is contained in:
Evelyn Alicke 2023-04-05 11:42:31 +02:00
parent 54f109f48a
commit 8147aa8e88
No known key found for this signature in database
GPG key ID: 6834780BDA479436
5 changed files with 175 additions and 157 deletions

View file

@ -8,10 +8,9 @@ edition = "2021"
[dependencies] [dependencies]
config = { version = "0.13.1", features = ["yaml"] } config = { version = "0.13.1", features = ["yaml"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
prometheus-client = "0.18.1" prometheus-client = "0.19.0"
futures-util = "0.3.25" futures-util = "0.3.25"
actix-web-httpauth = "0.8.0" axum = "0.6.12"
actix-web = "4.2.1" tokio = { version = "1.21.2", features = ["rt-multi-thread","macros"] }
tokio = "1.21.2"
rsvici = "0.1" rsvici = "0.1"
anyhow = "1.0.70" anyhow = "1.0.70"

View file

@ -1,122 +1,104 @@
#![allow(dead_code,non_camel_case_types)] // I don't want to be bothered for case stuff decided upon me by the VICI API #![allow(dead_code)]
use actix_web::{web, App, HttpResponse, HttpServer, Responder, Result}; use axum::{
response::IntoResponse,
http::{
StatusCode,
header::{self}
},
extract::State,
routing::get,
Router,
};
use prometheus_client::{ use prometheus_client::{
registry::Registry, registry::Registry,
encoding::{text::encode},
metrics::{ metrics::{
family::Family, family::Family,
info::Info,
counter::Counter,
gauge::Gauge,
MetricType::Unknown,
} }
}; };
use std::{ use std::{
collections::HashMap, // collections::HashMap,
error::Error, // error::Error,
sync::Mutex, sync::Arc,
net::IpAddr, net::{IpAddr,SocketAddr},
path::Path, // path::Path,
};
use futures_util::{
stream::{TryStreamExt},
pin_mut,
}; };
use serde::Deserialize; use serde::Deserialize;
use config::Config; use config::Config;
mod vici_structs; pub mod vici;
pub mod metrics;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct Configuration { struct Configuration {
vici_socket: String, vici_socket: String,
actix_bind_addr: IpAddr, axum_bind_addr: IpAddr,
actix_bind_port: u16, axum_bind_port: u16,
axtix_auth_token: Option<String>,
} }
pub async fn metrics_handler(state: web::Data<Mutex<AppState>>) -> Result<HttpResponse> { pub async fn metrics_handler(State(state): State<Arc<AppState>>) -> impl IntoResponse {
let state = state.lock().unwrap(); let state: Arc<AppState> = state.clone();
let mut buf = Vec::new(); let mut buffer = String::new();
encode(&mut buf, &state.registry)?; prometheus_client::encoding::text::encode(&mut buffer, &state.registry).unwrap();
let body = std::str::from_utf8(buf.as_slice()).unwrap().to_string(); (
Ok(HttpResponse::Ok() StatusCode::OK,
.content_type("application/openmetrics-text; version=1.0.0; charset=utf-8") [(header::CONTENT_TYPE, "application/openmetrics-text; version=1.0.0; charset=utf-8")],
.body(body)) buffer,
)
} }
pub struct AppState { pub struct AppState {
pub registry: Registry, pub registry: Registry,
pub vici: vici_structs::VICIState, pub vici: vici::VICIState,
} }
pub struct Metrics {
sa_uptime: Family<vici_structs::SecurityAssociationLabels, Counter>,
}
impl Metrics { #[tokio::main]
pub fn sa_uptime(&self, security_associations: vici_structs::SecurityAssociations) { async fn main() -> anyhow::Result<()> {
for (sa_name, sa_values) in security_associations.into_iter() {
self.sa_uptime.get_or_create(&vici_structs::SecurityAssociationLabels{uniqueid: sa_values.uniqueid}).inner(sa_values.established); // "Vertrau mir Bruder"
}
}
}
/*
pub fn get_vici_state(client: rsvici::Client) -> Result<vici_structs::VICIState, actix_web::Error>{
let version: vici_structs::Version = client.request("version", ()).await?;
let statistics: vici_structs::Statistics = client.request("statistics", ()).await?;
let connections = client.stream_request::<(), vici_structs::Connections>("list-connections", "list-conn", ());
pin_mut!(connections);
while let Some(conn) = connections.try_next().await? {
println!("{:#?}", conn);
}
}
*/
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let settings = Config::builder() let settings = Config::builder()
.add_source(config::File::with_name("config")) .add_source(config::File::with_name("config"))
.add_source(config::Environment::with_prefix("VICI_EXPORTER")) .add_source(config::Environment::with_prefix("VICI_EXPORTER"))
.build() .build()
.unwrap(); .unwrap();
let mut conf: Configuration = settings.try_deserialize().unwrap(); let mut conf: Configuration = settings.try_deserialize().unwrap();
let mut client = rsvici::unix::connect(conf.vici_socket).await?; let mut vici_client = rsvici::unix::connect(conf.vici_socket).await?;
let mut vici_state: vici_structs::VICIState; let mut vici_state: vici::VICIState;
let metrics = web::Data::new(Metrics { let metrics = Arc::new(metrics::Metrics {
sa_uptime: Family::default(), sa_uptime: Family::default(),
}); });
let mut state = AppState { let mut initial_registery = Registry::default();
registry: Registry::default(),
vici: vici_structs::VICIState.update(client),
};
state.registry.register( initial_registery.register(
"sa_uptime", "sa_uptime",
"How Long a connection has been established", "How Long a connection has been established",
Box::new(metrics.sa_uptime.clone()), metrics.sa_uptime.clone(),
); );
let state = web::Data::new(Mutex::new(state));
HttpServer::new(move || { let mut state = Arc::new(
App::new() AppState {
//.app_data(metrics.clone()) registry: initial_registery,
.app_data(state.clone()) vici: vici::VICIState::update(&mut vici_client).await?,
.service(web::resource("/metrics").route(web::get().to(metrics_handler))) },
}) );
.bind((conf.actix_bind_addr, conf.actix_bind_port))?
.run()
.await
let addr = SocketAddr::from((conf.axum_bind_addr,conf.axum_bind_port));
let app = Router::new()
.route("/metrics",get(metrics_handler))
.with_state(state);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
Ok(())
} }

25
src/metrics.rs Normal file
View file

@ -0,0 +1,25 @@
use prometheus_client::{
metrics::{
family::Family,
gauge::Gauge,
}
};
use anyhow::Result;
use crate::vici;
pub mod labels;
pub struct Metrics {
pub sa_uptime: Family<labels::SecurityAssociationLabels, Gauge>,
}
impl Metrics {
pub async fn sa_uptime(&self, security_associations: vici::SecurityAssociations) -> Result<()>{
for named_sa in security_associations.into_iter() {
let label_set = labels::SecurityAssociationLabels::set_from_sa(&mut named_sa).await?;
let (_sa_name, sa_value) = named_sa;
self.sa_uptime.get_or_create(&label_set).set(sa_value.established as i64);
}
Ok(());
}
}

65
src/metrics/labels.rs Normal file
View file

@ -0,0 +1,65 @@
use serde::Deserialize;
use prometheus_client::encoding::{EncodeLabelValue,EncodeLabelSet};
use crate::vici;
use anyhow::Result;
// I don't really wanna define *all* of this here, it's gonna get really tedious and uncomfortable to maintain.
/*
#[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)]
pub struct SecurityAssociationLabels {
pub uniqueid: String,
pub local_id: String,
pub local_host: String,
pub local_port: u16,
pub remote_id: String,
pub remote_host: String,
pub remote_port: u16,
}
*/
#[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)]
pub struct SecurityAssociationInfo {
pub uniqueid: String,
pub version: u8,
pub local_host: String,
pub local_port: u16,
pub local_id: String,
pub remote_host: String,
pub remote_port: u16,
pub remote_id: String,
pub if_id_in: String,
pub if_id_out: String,
pub encr_alg: String,
pub encr_keysize: String,
pub integ_alg: String,
pub integ_keysize: String,
pub prf_alg: String,
pub dh_group: Option<String>,
pub local_vips: Vec<String>,
pub remote_vips: Vec<String>,
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, EncodeLabelSet)]
pub struct SecurityAssociationLabels {
pub name: String,
pub uniqueid: String,
pub ike_version: u8,
pub local_id: String,
pub remote_id: String,
}
impl SecurityAssociationLabels {
pub async fn set_from_sa(sa: &mut vici::NamedSecurityAssociation) -> Result<SecurityAssociationLabels> {
let (sa_name, sa_value) = sa;
Ok(SecurityAssociationLabels {
name: sa_name,
uniqueid: sa_value.uniqueid,
ike_version: sa_value.version,
local_id: sa_value.local_id.unwrap(),
remote_id: sa_value.remote_id.unwrap(),
})
}
}

View file

@ -1,11 +1,9 @@
#![allow(dead_code,non_camel_case_types)] // I don't want to be bothered for case stuff decided upon me by the VICI API #![allow(dead_code)]
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use futures_util:: stream::StreamExt; use futures_util::stream::StreamExt;
use prometheus_client::encoding::text::Encode;
use anyhow::Result; use anyhow::Result;
@ -22,16 +20,16 @@ pub struct VICIState {
} }
impl VICIState { impl VICIState {
async fn update(client: &mut rsvici::Client) -> Result<VICIState> { pub async fn update(client: &mut rsvici::Client) -> Result<VICIState> {
Ok(VICIState { Ok(VICIState {
version: client.request("version", ()).await?, version: client.request("version", ()).await?,
statistics: client.request("statistics", ()).await?, statistics: client.request("statistics", ()).await?,
policies: collected_stream::<NamedPolicy, Policies>(client, "list-policies", "list-policy").await, policies: collected_stream::<NamedPolicy, Policies>(client, "list-policies", "list-policy").await,
connections: client.stream_request::<(), Connections>("list-connections", "list-conn", ()).await?, connections: collected_stream::<NamedConnection, Connections>(client, "list-connections", "list-conn").await,
security_associations: client.stream_request::<(), SecurityAssociations>("list-sas", "list-sa", ()).await?, security_associations: collected_stream::<NamedSecurityAssociation, SecurityAssociations>(client, "list-sas", "list-sa").await,
certificates: client.stream_request::<(), Certificates>("list-certs", "list-cert", ()).await?, certificates: collected_stream::<NamedCertificate, Certificates>(client, "list-certs", "list-cert").await,
authorities: client.stream_request::<(), Authorities>("list-authoroties", "list-authoroty", ()).await?, authorities: collected_stream::<NamedAuthority, Authorities>(client, "list-authorities", "list-authority").await,
pools: client.stream_request::<(), Pools>("list-pools", "list-pool", ()).await?, pools: collected_stream::<NamedPool, Pools>(client, "list-pools", "list-pool").await,
}) })
} }
} }
@ -121,7 +119,7 @@ pub struct Policy {
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
enum PolicyMode { pub enum PolicyMode {
tunnel, tunnel,
transport, transport,
pass, pass,
@ -130,6 +128,8 @@ enum PolicyMode {
pub type Connections = HashMap<String, Conn>; pub type Connections = HashMap<String, Conn>;
pub type NamedConnection = (String, Conn);
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Conn { pub struct Conn {
pub local_addrs: Vec<String>, pub local_addrs: Vec<String>,
@ -167,21 +167,22 @@ pub struct ConnChildSection {
} }
#[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)] #[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)]
enum ChildSecurityAssociationMode { pub enum ChildSecurityAssociationMode {
TUNNEL, TUNNEL,
TRANSPORT, TRANSPORT,
BEET, BEET,
} }
#[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)] #[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)]
enum ChildSecurityAssociationProtocol { pub enum ChildSecurityAssociationProtocol {
AH, AH,
ESP, ESP,
} }
pub type SecurityAssociations = HashMap<String, SecurityAssociation>; pub type SecurityAssociations = HashMap<String, SecurityAssociation>;
pub type NamedSecurityAssociation = (String, SecurityAssociation);
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct SecurityAssociation { pub struct SecurityAssociation {
pub uniqueid: String, pub uniqueid: String,
pub version: u8, pub version: u8,
@ -221,7 +222,6 @@ pub struct SecurityAssociation {
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct SecurityAssociationChild { pub struct SecurityAssociationChild {
pub name: String, pub name: String,
pub uniqueid: String, pub uniqueid: String,
@ -262,6 +262,8 @@ pub struct SecurityAssociationChild {
pub type Certificates = HashMap<String, Cert>; pub type Certificates = HashMap<String, Cert>;
pub type NamedCertificate = (String, Cert);
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Cert { pub struct Cert {
pub r#type: CertType, pub r#type: CertType,
@ -274,7 +276,7 @@ pub struct Cert {
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
enum CertType { pub enum CertType {
X509, X509,
X509_AC, X509_AC,
X509_CRL, X509_CRL,
@ -283,7 +285,7 @@ enum CertType {
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
enum X509CertFlag { pub enum X509CertFlag {
NONE, NONE,
CA, CA,
AA, AA,
@ -292,6 +294,8 @@ enum X509CertFlag {
pub type Authorities = HashMap<String, Authority>; pub type Authorities = HashMap<String, Authority>;
pub type NamedAuthority = (String, Authority);
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Authority { pub struct Authority {
pub cacert: String, pub cacert: String,
@ -302,6 +306,8 @@ pub struct Authority {
pub type Pools = HashMap<String, Pool>; pub type Pools = HashMap<String, Pool>;
pub type NamedPool = (String, Pool);
#[derive(Debug,Deserialize)] #[derive(Debug,Deserialize)]
pub struct Pool { pub struct Pool {
pub name: String, pub name: String,
@ -320,66 +326,7 @@ pub struct PoolLease {
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
enum PoolLeaseStatus { pub enum PoolLeaseStatus {
online, online,
offline, offline,
} }
// Structs for generating metrics, TODO
/*
#[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)]
pub struct SecurityAssociationLabels {
pub uniqueid: String,
pub local_id: String,
pub local_host: String,
pub local_port: u16,
pub remote_id: String,
pub remote_host: String,
pub remote_port: u16,
}
*/
#[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)]
pub struct SecurityAssociationInfo {
pub uniqueid: String,
pub version: u8,
pub local_host: String,
pub local_port: u16,
pub local_id: String,
pub remote_host: String,
pub remote_port: u16,
pub remote_id: String,
pub if_id_in: String,
pub if_id_out: String,
pub encr_alg: String,
pub encr_keysize: String,
pub integ_alg: String,
pub integ_keysize: String,
pub prf_alg: String,
pub dh_group: Option<String>,
pub local_vips: Vec<String>,
pub remote_vips: Vec<String>,
}
#[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)]
pub struct SecurityAssociationChildInfo {
pub name: String,
pub uniqueid: String,
pub reqid: String,
pub mode: ChildSecurityAssociationMode,
pub if_id_in: String,
pub if_id_out: String,
pub encr_alg: String,
pub encr_keysize: String,
pub integ_alg: String,
pub integ_keysize: String,
pub prf_alg: String,
pub dh_group: Option<String>,
pub local_ts: Vec<String>,
pub remote_ts: Vec<String>,
}
#[derive(Clone, Hash, PartialEq, Eq, Encode)]
pub struct SecurityAssociationLabels {
pub uniqueid: String,
}