diff --git a/Cargo.toml b/Cargo.toml index 1ec5017..abf5540 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,9 @@ edition = "2021" [dependencies] config = { version = "0.13.1", features = ["yaml"] } serde = { version = "1.0", features = ["derive"] } -prometheus-client = "0.18.1" +prometheus-client = "0.19.0" futures-util = "0.3.25" -actix-web-httpauth = "0.8.0" -actix-web = "4.2.1" -tokio = "1.21.2" +axum = "0.6.12" +tokio = { version = "1.21.2", features = ["rt-multi-thread","macros"] } rsvici = "0.1" anyhow = "1.0.70" diff --git a/src/main.rs b/src/main.rs index cb89ee1..40a3d41 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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::{ registry::Registry, - encoding::{text::encode}, metrics::{ family::Family, - info::Info, - counter::Counter, - gauge::Gauge, - MetricType::Unknown, } }; use std::{ - collections::HashMap, - error::Error, - sync::Mutex, - net::IpAddr, - path::Path, -}; - - -use futures_util::{ - stream::{TryStreamExt}, - pin_mut, +// collections::HashMap, +// error::Error, + sync::Arc, + net::{IpAddr,SocketAddr}, +// path::Path, }; use serde::Deserialize; use config::Config; -mod vici_structs; +pub mod vici; +pub mod metrics; #[derive(Debug, Deserialize)] struct Configuration { vici_socket: String, - actix_bind_addr: IpAddr, - actix_bind_port: u16, - axtix_auth_token: Option, + axum_bind_addr: IpAddr, + axum_bind_port: u16, } -pub async fn metrics_handler(state: web::Data>) -> Result { - let state = state.lock().unwrap(); - let mut buf = Vec::new(); - encode(&mut buf, &state.registry)?; - let body = std::str::from_utf8(buf.as_slice()).unwrap().to_string(); - Ok(HttpResponse::Ok() - .content_type("application/openmetrics-text; version=1.0.0; charset=utf-8") - .body(body)) +pub async fn metrics_handler(State(state): State>) -> impl IntoResponse { + let state: Arc = state.clone(); + let mut buffer = String::new(); + prometheus_client::encoding::text::encode(&mut buffer, &state.registry).unwrap(); + ( + StatusCode::OK, + [(header::CONTENT_TYPE, "application/openmetrics-text; version=1.0.0; charset=utf-8")], + buffer, + ) } pub struct AppState { pub registry: Registry, - pub vici: vici_structs::VICIState, + pub vici: vici::VICIState, } -pub struct Metrics { - sa_uptime: Family, -} -impl Metrics { - pub fn sa_uptime(&self, security_associations: vici_structs::SecurityAssociations) { - 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{ - 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<()> { +#[tokio::main] +async fn main() -> anyhow::Result<()> { let settings = Config::builder() .add_source(config::File::with_name("config")) .add_source(config::Environment::with_prefix("VICI_EXPORTER")) .build() .unwrap(); let mut conf: Configuration = settings.try_deserialize().unwrap(); - let mut client = rsvici::unix::connect(conf.vici_socket).await?; - let mut vici_state: vici_structs::VICIState; + let mut vici_client = rsvici::unix::connect(conf.vici_socket).await?; + let mut vici_state: vici::VICIState; - let metrics = web::Data::new(Metrics { + let metrics = Arc::new(metrics::Metrics { sa_uptime: Family::default(), }); - let mut state = AppState { - registry: Registry::default(), - vici: vici_structs::VICIState.update(client), - }; + let mut initial_registery = Registry::default(); - state.registry.register( + initial_registery.register( "sa_uptime", "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 || { - App::new() - //.app_data(metrics.clone()) - .app_data(state.clone()) - .service(web::resource("/metrics").route(web::get().to(metrics_handler))) - }) - .bind((conf.actix_bind_addr, conf.actix_bind_port))? - .run() - .await + let mut state = Arc::new( + AppState { + registry: initial_registery, + vici: vici::VICIState::update(&mut vici_client).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(()) } diff --git a/src/metrics.rs b/src/metrics.rs new file mode 100644 index 0000000..726e7ef --- /dev/null +++ b/src/metrics.rs @@ -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, +} + +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(()); + } +} diff --git a/src/metrics/labels.rs b/src/metrics/labels.rs new file mode 100644 index 0000000..a2dd2cb --- /dev/null +++ b/src/metrics/labels.rs @@ -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, + pub local_vips: Vec, + pub remote_vips: Vec, +} + +#[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 { + 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(), + }) + } +} diff --git a/src/vici_structs.rs b/src/vici.rs similarity index 75% rename from src/vici_structs.rs rename to src/vici.rs index 1b4dec4..46b87e0 100644 --- a/src/vici_structs.rs +++ b/src/vici.rs @@ -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 std::collections::HashMap; -use futures_util:: stream::StreamExt; - -use prometheus_client::encoding::text::Encode; +use futures_util::stream::StreamExt; use anyhow::Result; @@ -22,16 +20,16 @@ pub struct VICIState { } impl VICIState { - async fn update(client: &mut rsvici::Client) -> Result { + pub async fn update(client: &mut rsvici::Client) -> Result { Ok(VICIState { version: client.request("version", ()).await?, statistics: client.request("statistics", ()).await?, policies: collected_stream::(client, "list-policies", "list-policy").await, - connections: client.stream_request::<(), Connections>("list-connections", "list-conn", ()).await?, - security_associations: client.stream_request::<(), SecurityAssociations>("list-sas", "list-sa", ()).await?, - certificates: client.stream_request::<(), Certificates>("list-certs", "list-cert", ()).await?, - authorities: client.stream_request::<(), Authorities>("list-authoroties", "list-authoroty", ()).await?, - pools: client.stream_request::<(), Pools>("list-pools", "list-pool", ()).await?, + connections: collected_stream::(client, "list-connections", "list-conn").await, + security_associations: collected_stream::(client, "list-sas", "list-sa").await, + certificates: collected_stream::(client, "list-certs", "list-cert").await, + authorities: collected_stream::(client, "list-authorities", "list-authority").await, + pools: collected_stream::(client, "list-pools", "list-pool").await, }) } } @@ -121,7 +119,7 @@ pub struct Policy { } #[derive(Debug, Deserialize)] -enum PolicyMode { +pub enum PolicyMode { tunnel, transport, pass, @@ -130,6 +128,8 @@ enum PolicyMode { pub type Connections = HashMap; +pub type NamedConnection = (String, Conn); + #[derive(Debug, Deserialize)] pub struct Conn { pub local_addrs: Vec, @@ -167,21 +167,22 @@ pub struct ConnChildSection { } #[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)] -enum ChildSecurityAssociationMode { +pub enum ChildSecurityAssociationMode { TUNNEL, TRANSPORT, BEET, } #[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)] -enum ChildSecurityAssociationProtocol { +pub enum ChildSecurityAssociationProtocol { AH, ESP, } pub type SecurityAssociations = HashMap; +pub type NamedSecurityAssociation = (String, SecurityAssociation); + #[derive(Debug, Deserialize)] -#[serde(rename_all = "kebab-case")] pub struct SecurityAssociation { pub uniqueid: String, pub version: u8, @@ -221,7 +222,6 @@ pub struct SecurityAssociation { } #[derive(Debug, Deserialize)] -#[serde(rename_all = "kebab-case")] pub struct SecurityAssociationChild { pub name: String, pub uniqueid: String, @@ -262,6 +262,8 @@ pub struct SecurityAssociationChild { pub type Certificates = HashMap; +pub type NamedCertificate = (String, Cert); + #[derive(Debug, Deserialize)] pub struct Cert { pub r#type: CertType, @@ -274,7 +276,7 @@ pub struct Cert { } #[derive(Debug, Deserialize)] -enum CertType { +pub enum CertType { X509, X509_AC, X509_CRL, @@ -283,7 +285,7 @@ enum CertType { } #[derive(Debug, Deserialize)] -enum X509CertFlag { +pub enum X509CertFlag { NONE, CA, AA, @@ -292,6 +294,8 @@ enum X509CertFlag { pub type Authorities = HashMap; +pub type NamedAuthority = (String, Authority); + #[derive(Debug, Deserialize)] pub struct Authority { pub cacert: String, @@ -302,6 +306,8 @@ pub struct Authority { pub type Pools = HashMap; +pub type NamedPool = (String, Pool); + #[derive(Debug,Deserialize)] pub struct Pool { pub name: String, @@ -320,66 +326,7 @@ pub struct PoolLease { } #[derive(Debug, Deserialize)] -enum PoolLeaseStatus { +pub enum PoolLeaseStatus { online, 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, - pub local_vips: Vec, - pub remote_vips: Vec, -} - -#[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, - pub local_ts: Vec, - pub remote_ts: Vec, -} - -#[derive(Clone, Hash, PartialEq, Eq, Encode)] -pub struct SecurityAssociationLabels { - pub uniqueid: String, -}