commit a48db9c4c8d7a55e494d5f3848aa153d1893f030 Author: Evelyn Alicke Date: Tue Mar 28 09:04:26 2023 +0200 Initial Commit diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..54ad01d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "openmetrics-vici-exporter" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +config = { version = "0.13.1", features = ["yaml"] } +serde = { version = "1.0", features = ["derive"] } +prometheus-client = "0.18.1" +futures-util = "0.3.25" +actix-web-httpauth = "0.8.0" +actix-web = "4.2.1" +tokio = "1.21.2" +rsvici = "0.1" diff --git a/README.md b/README.md new file mode 100644 index 0000000..b863589 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +`groupadd vici` +`chown root:vici /var/run/charon.vici` +`chmod 0770 /var/run/charon.vici` +`usermod -aG vici $user` diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..7c42627 --- /dev/null +++ b/config.yml @@ -0,0 +1,5 @@ +--- +vici_socket: "/var/run/charon.vici" +actix_bind_addr: "0.0.0.0" +actix_bind_port: "80" +actix_auth_token: "" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..cb89ee1 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,122 @@ +#![allow(dead_code,non_camel_case_types)] // I don't want to be bothered for case stuff decided upon me by the VICI API + +use actix_web::{web, App, HttpResponse, HttpServer, Responder, Result}; + +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, +}; + +use serde::Deserialize; + +use config::Config; + +mod vici_structs; + + +#[derive(Debug, Deserialize)] +struct Configuration { + vici_socket: String, + actix_bind_addr: IpAddr, + actix_bind_port: u16, + axtix_auth_token: Option, +} + +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 struct AppState { + pub registry: Registry, + pub vici: vici_structs::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<()> { + 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 metrics = web::Data::new(Metrics { + sa_uptime: Family::default(), + }); + + let mut state = AppState { + registry: Registry::default(), + vici: vici_structs::VICIState.update(client), + }; + + state.registry.register( + "sa_uptime", + "How Long a connection has been established", + Box::new(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 +} diff --git a/src/vici_structs.rs b/src/vici_structs.rs new file mode 100644 index 0000000..fde8266 --- /dev/null +++ b/src/vici_structs.rs @@ -0,0 +1,373 @@ +#![allow(dead_code,non_camel_case_types)] // I don't want to be bothered for case stuff decided upon me by the VICI API + +use serde::Deserialize; +use std::collections::HashMap; + +use prometheus_client::{ + encoding::text::Encode, +}; + +#[derive(Debug, Deserialize)] +pub struct VICIState { + pub version: Version, + pub statistics: Statistics, + pub policies: Policies, + pub connections: Connections, + pub security_associations: SecurityAssociations, + pub certificates: Certificates, + pub authorities: Authorities, + pub pools: Pools, +} + +impl VICIState { + async fn update(client: rsvici::Client) -> Result { + VICIState { + version: client.request("version", ()).await?, + statistics: client.request("statistics", ()).await?, + policies: client.stream_request::<(), Policies>("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?, + } + } +} + +// Structs for parsing the control interface + +#[derive(Debug, Deserialize)] +pub struct Version { + pub daemon: String, + pub version: String, + pub sysname: String, + pub release: String, + pub machine: String, +} + +#[derive(Debug, Deserialize)] +pub struct Statistics { + pub uptime: StatisticsUptime, + pub workers: StatisticsWorkers, + pub queues: StatisticsJobPriorities, + pub scheduled: String, + pub ikesecurity_associations: StatisticsIKESecurityAssociations, + pub plugins: Vec, + pub mem: Option, + pub mallinfo: Option, +} + +#[derive(Debug, Deserialize)] +pub struct StatisticsUptime { + pub running: String, + pub since: String, +} + +#[derive(Debug, Deserialize)] +pub struct StatisticsWorkers { + pub total: String, + pub idle: String, + pub active: StatisticsJobPriorities, +} + +#[derive(Debug, Deserialize)] +pub struct StatisticsJobPriorities { + pub critical: String, + pub high: String, + pub medium: String, + pub low: String, +} + +#[derive(Debug, Deserialize)] +pub struct StatisticsIKESecurityAssociations { + pub total: String, + pub half_open: Option, +} + +#[derive(Debug, Deserialize)] +pub struct StatisticsMem { + pub total: String, + pub allocs: String, +} +#[derive(Debug, Deserialize)] +pub struct StatisticsMallinfo { + pub sbrk: String, + pub mmap: String, + pub used: String, + pub free: String, +} + +pub type Policies = HashMap; + +#[derive(Debug, Deserialize)] +pub struct Policy { + pub child: String, + pub ike: Option, + pub mode: PolicyMode, + pub local_ts: Option>, + pub remote_ts: Option>, +} + +#[derive(Debug, Deserialize)] +enum PolicyMode { + tunnel, + transport, + pass, + drop, +} + +pub type Connections = HashMap; + +#[derive(Debug, Deserialize)] +pub struct Conn { + pub local_addrs: Vec, + pub remote_addrs: Vec, + pub version: String, + pub reauth_time: u32, + pub rekey_time: u32, + pub children: HashMap, +} + +#[derive(Debug, Deserialize)] +pub struct ConnAuthSection { + pub class: String, + pub eap_type: Option, + pub eap_vendor: Option, + pub xauth: Option, + pub revocation: Option, + pub id: String, + pub aaa_id: Option, + pub eap_id: Option, + pub xauth_id: Option, + pub groups: Option>, + pub certificates: Option>, + pub cacerts: Option>, +} + +#[derive(Debug, Deserialize)] +pub struct ConnChildSection { + pub mode: ChildSecurityAssociationMode, + pub rekey_time: u32, + pub rekey_bytes: u64, + pub rekey_packets: u64, + pub local_ts: Option>, + pub remote_ts: Option>, +} + +#[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)] +enum ChildSecurityAssociationMode { + TUNNEL, + TRANSPORT, + BEET, +} +#[derive(Debug, Deserialize, Clone, Hash, PartialEq, Eq)] +enum ChildSecurityAssociationProtocol { + AH, + ESP, +} + +pub type SecurityAssociations = HashMap; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct SecurityAssociation { + pub uniqueid: String, + pub version: u8, + pub state: String, + pub local_host: Option, + pub local_port: Option, + pub local_id: Option, + pub remote_host: Option, + pub remote_port: Option, + pub remote_id: Option, + pub remote_xauth_id: Option, + pub remote_epa_id: Option, + pub initiator: Option, + pub initiator_spi: Option, + pub responder_spi: Option, + pub nat_local: Option, + pub nat_remote: Option, + pub nat_fake: Option, + pub nat_any: Option, + pub if_id_in: Option, + pub if_id_out: Option, + pub encr_alg: Option, + pub encr_keysize: Option, + pub integ_alg: Option, + pub integ_keysize: Option, + pub prf_alg: Option, + pub dh_group: Option, + pub established: u64, + pub rekey_time: Option, + pub reauth_time: Option, + pub local_vips: Option>, + pub remote_vips: Option>, + pub tasks_queued: Option>, + pub tasks_active: Option>, + pub tasks_passive: Option>, + pub child_security_associations: Option>, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub struct SecurityAssociationChild { + pub name: String, + pub uniqueid: String, + pub reqid: String, + pub state: String, + pub mode: ChildSecurityAssociationMode, + pub protocol: ChildSecurityAssociationProtocol, + pub encap: Option, + pub spi_in: String, + pub spi_out: String, + pub cpi_in: Option, + pub cpi_out: Option, + pub mark_in: Option, + pub mark_mask_in: Option, + pub mark_out: Option, + pub mark_mask_out: Option, + pub if_id_in: Option, + pub if_id_out: Option, + pub encr_alg: Option, + pub encr_keysize: Option, + pub integ_alg: Option, + pub integ_keysize: Option, + pub prf_alg: Option, + pub dh_group: Option, + pub esn: Option, + pub bytes_in: u64, + pub packets_in: u64, + pub use_in: Option, + pub bytes_out: u64, + pub packets_out: u64, + pub use_out: Option, + pub rekey_time: Option, + pub life_time: u32, + pub install_time: u64, + pub local_ts: Vec, + pub remote_ts: Vec, +} + +pub type Certificates = HashMap; + +#[derive(Debug, Deserialize)] +pub struct Cert { + pub r#type: CertType, + pub flag: X509CertFlag, + pub has_privkey: Option, + pub data: String, + pub subject: Option, + pub not_before: Option, + pub not_after: Option, +} + +#[derive(Debug, Deserialize)] +enum CertType { + X509, + X509_AC, + X509_CRL, + OSCP_RESPONSE, + PUBKEY, +} + +#[derive(Debug, Deserialize)] +enum X509CertFlag { + NONE, + CA, + AA, + OCSP, +} + +pub type Authorities = HashMap; + +#[derive(Debug, Deserialize)] +pub struct Authority { + pub cacert: String, + pub crl_uris: Vec, + pub ocsp_uris: Vec, + pub cert_uri_base: String, +} + +pub type Pools = HashMap; + +#[derive(Debug,Deserialize)] +pub struct Pool { + pub name: String, + pub base: String, + pub size: u128, + pub online: u128, + pub offline: u128, + pub leases: Option>, +} + +#[derive(Debug,Deserialize)] +pub struct PoolLease { + pub address: String, + pub identity: String, + pub status: PoolLeaseStatus, +} + +#[derive(Debug, Deserialize)] +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, +}