From 8255c937e575aa8fdb65c5abbc196a236aa1620d Mon Sep 17 00:00:00 2001 From: James Hodgkinson Date: Mon, 23 Jan 2023 19:58:13 +1000 Subject: [PATCH] Adding healthcheck functionality to kanidmd (#1330) * closes #1220, adds healthcheck functionality to kanidmd * ssl is old and busted, tls is great --- Cargo.lock | 2 + iam_migrations/freeipa/src/main.rs | 2 +- kanidmd/core/Cargo.toml | 1 + kanidmd/core/src/config.rs | 47 +++++++++++++ kanidmd/daemon/Cargo.toml | 3 +- kanidmd/daemon/src/main.rs | 108 ++++++++++++++++++----------- kanidmd/daemon/src/opt.rs | 14 ++++ 7 files changed, 133 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 729a145a4..b054956a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1178,6 +1178,7 @@ dependencies = [ "kanidmd_core", "kanidmd_lib", "profiles", + "reqwest", "serde", "sketching", "tikv-jemallocator", @@ -2394,6 +2395,7 @@ dependencies = [ "tokio", "tokio-openssl", "tokio-util", + "toml", "tracing", "uuid", ] diff --git a/iam_migrations/freeipa/src/main.rs b/iam_migrations/freeipa/src/main.rs index 873f4c4b3..b08300fe7 100644 --- a/iam_migrations/freeipa/src/main.rs +++ b/iam_migrations/freeipa/src/main.rs @@ -885,7 +885,7 @@ fn config_security_checks(cfg_path: &Path) -> bool { let cfg_meta = match metadata(&cfg_path) { Ok(v) => v, Err(e) => { - error!("Unable to read metadata for {} - {:?}", cfg_path_str, e); + error!("Unable to read metadata for '{}' during security checks - {:?}", cfg_path_str, e); return false; } }; diff --git a/kanidmd/core/Cargo.toml b/kanidmd/core/Cargo.toml index f4b507328..0ab2f786e 100644 --- a/kanidmd/core/Cargo.toml +++ b/kanidmd/core/Cargo.toml @@ -36,6 +36,7 @@ tide-openssl.workspace = true tokio = { workspace = true, features = ["net", "sync", "io-util", "macros"] } tokio-openssl.workspace = true tokio-util = { workspace = true, features = ["codec"] } +toml = {workspace = true} tracing = { workspace = true, features = ["attributes"] } uuid = { workspace = true, features = ["serde", "v4" ] } diff --git a/kanidmd/core/src/config.rs b/kanidmd/core/src/config.rs index c24dee3aa..849d01de1 100644 --- a/kanidmd/core/src/config.rs +++ b/kanidmd/core/src/config.rs @@ -5,6 +5,10 @@ //! or domain entries that are able to be replicated. use std::fmt; +use std::fs::File; +use std::io::Read; +use std::path::Path; + use std::str::FromStr; use kanidm_proto::messages::ConsoleOutputMode; @@ -40,6 +44,39 @@ pub struct TlsConfiguration { pub key: String, } + +#[derive(Debug, Deserialize)] +pub struct ServerConfig { + pub bindaddress: Option, + pub ldapbindaddress: Option, + pub trust_x_forward_for: Option, + // pub threads: Option, + pub db_path: String, + pub db_fs_type: Option, + pub db_arc_size: Option, + pub tls_chain: Option, + pub tls_key: Option, + pub online_backup: Option, + pub domain: String, + pub origin: String, + #[serde(default)] + pub role: ServerRole, +} + +impl ServerConfig { + pub fn new>(config_path: P) -> Result { + let mut f = File::open(config_path).map_err(|e| { + eprintln!("Unable to open config file [{:?}] 🥺", e); + })?; + + let mut contents = String::new(); + f.read_to_string(&mut contents) + .map_err(|e| eprintln!("unable to read contents {:?}", e))?; + + toml::from_str(contents.as_str()).map_err(|e| eprintln!("unable to parse config {:?}", e)) + } +} + #[derive(Debug, Serialize, Deserialize, Clone, Copy)] pub enum ServerRole { WriteReplica, @@ -182,6 +219,16 @@ impl Configuration { } } + // Startup config action, used in kanidmd server etc + pub fn update_config_for_server_mode(&mut self, sconfig: &ServerConfig) { + #[cfg(debug_assertions)] + debug!("update_config_for_server_mode {:?}", sconfig); + self.update_tls(&sconfig.tls_chain, &sconfig.tls_key); + self.update_bind(&sconfig.bindaddress); + self.update_ldapbind(&sconfig.ldapbindaddress); + self.update_online_backup(&sconfig.online_backup); + } + pub fn update_trust_x_forward_for(&mut self, t: Option) { self.trust_x_forward_for = t.unwrap_or(false); } diff --git a/kanidmd/daemon/Cargo.toml b/kanidmd/daemon/Cargo.toml index 1a26fe0be..145f9ee10 100644 --- a/kanidmd/daemon/Cargo.toml +++ b/kanidmd/daemon/Cargo.toml @@ -26,9 +26,10 @@ kanidmd_core.workspace = true sketching.workspace = true clap = { workspace = true, features = ["env"] } +reqwest = { workspace = true } serde = { workspace = true, features = ["derive"] } tokio = { workspace = true, features = ["rt-multi-thread", "macros", "signal"] } -toml.workspace = true +toml = { workspace = true } [target.'cfg(target_family = "windows")'.dependencies] whoami.workspace = true diff --git a/kanidmd/daemon/src/main.rs b/kanidmd/daemon/src/main.rs index d1ef6dbc7..f7b46bd3d 100644 --- a/kanidmd/daemon/src/main.rs +++ b/kanidmd/daemon/src/main.rs @@ -14,15 +14,14 @@ #[global_allocator] static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; -use std::fs::{metadata, File, Metadata}; -use std::io::Read; +use std::fs::{metadata, Metadata}; #[cfg(target_family = "unix")] use std::os::unix::fs::MetadataExt; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::process::exit; use clap::{Args, Parser, Subcommand}; -use kanidmd_core::config::{Configuration, OnlineBackup, ServerRole}; +use kanidmd_core::config::{Configuration, ServerConfig}; use kanidmd_core::{ backup_server_core, create_server_core, dbscan_get_id2entry_core, dbscan_list_id2entry_core, dbscan_list_index_analysis_core, dbscan_list_index_core, dbscan_list_indexes_core, @@ -31,7 +30,6 @@ use kanidmd_core::{ }; #[cfg(not(target_family = "windows"))] use kanidmd_lib::utils::file_permissions_readonly; -use serde::Deserialize; use sketching::tracing_forest::traits::*; use sketching::tracing_forest::util::*; use sketching::tracing_forest::{self}; @@ -42,38 +40,6 @@ use whoami; include!("./opt.rs"); -#[derive(Debug, Deserialize)] -struct ServerConfig { - pub bindaddress: Option, - pub ldapbindaddress: Option, - pub trust_x_forward_for: Option, - // pub threads: Option, - pub db_path: String, - pub db_fs_type: Option, - pub db_arc_size: Option, - pub tls_chain: Option, - pub tls_key: Option, - pub online_backup: Option, - pub domain: String, - pub origin: String, - #[serde(default)] - pub role: ServerRole, -} - -impl ServerConfig { - pub fn new>(config_path: P) -> Result { - let mut f = File::open(config_path).map_err(|e| { - eprintln!("Unable to open config file [{:?}] 🥺", e); - })?; - - let mut contents = String::new(); - f.read_to_string(&mut contents) - .map_err(|e| eprintln!("unable to read contents {:?}", e))?; - - toml::from_str(contents.as_str()).map_err(|e| eprintln!("unable to parse config {:?}", e)) - } -} - impl KanidmdOpt { fn commonopt(&self) -> &CommonOpt { match self { @@ -114,6 +80,7 @@ impl KanidmdOpt { KanidmdOpt::Database { commands: DbCommands::Vacuum(copt), } => &copt, + KanidmdOpt::HealthCheck(hcopt) => &hcopt.commonopts, KanidmdOpt::Version(copt) => &copt, } } @@ -124,7 +91,7 @@ fn read_file_metadata(path: &PathBuf) -> Metadata { Ok(m) => m, Err(e) => { eprintln!( - "Unable to read metadata for {} - {:?}", + "Unable to read metadata for '{}' - {:?}", path.to_str().unwrap_or("invalid file path"), e ); @@ -290,10 +257,7 @@ async fn main() { }; // configuration options that only relate to server mode - config.update_tls(&sconfig.tls_chain, &sconfig.tls_key); - config.update_bind(&sconfig.bindaddress); - config.update_ldapbind(&sconfig.ldapbindaddress); - config.update_online_backup(&sconfig.online_backup); + config.update_config_for_server_mode(&sconfig); if let Some(i_str) = &(sconfig.tls_chain) { let i_path = PathBuf::from(i_str.as_str()); @@ -470,6 +434,66 @@ async fn main() { eprintln!("Running in vacuum mode ..."); vacuum_server_core(&config); } + KanidmdOpt::HealthCheck(sopt) => { + config.update_config_for_server_mode(&sconfig); + + debug!("{sopt:?}"); + + let healthcheck_url = format!("https://{}/status", config.address); + debug!("Checking {healthcheck_url}"); + + + + let client = reqwest::ClientBuilder::new() + .danger_accept_invalid_certs(sopt.no_verify_tls) + .danger_accept_invalid_hostnames(sopt.no_verify_tls) + .https_only(true); + // TODO: work out how to pull the CA from the chain + // client = match config.tls_config { + // Some(tls_config) => { + // eprintln!("{:?}", tls_config); + // let mut buf = Vec::new(); + // File::open(tls_config.chain) + // .unwrap() + // .read_to_end(&mut buf) + // .unwrap(); + // eprintln!("buf: {:?}", buf); + // match reqwest::Certificate::from_pem(&buf){ + // Ok(cert) => client.add_root_certificate(cert), + // Err(err) => { + // error!("Failed to read TLS chain: {err:?}"); + // client + // } + // } + + // }, + // None => client, + // }; + + let client = client + .build() + .unwrap(); + + + let req = match client.get(&healthcheck_url).send().await { + Ok(val) => val, + Err(error) => { + let error_message = { + if error.is_timeout() { + format!("Timeout connecting to url={healthcheck_url}") + } else if error.is_connect() { + format!("Connection failed: {}", error.to_string()) + } else { + format!("Failed to complete healthcheck: {:?}", error) + } + }; + eprintln!("CRITICAL: {error_message}"); + exit(1); + } + }; + debug!("Request: {req:?}"); + println!("OK") + } KanidmdOpt::Version(_) => { return } diff --git a/kanidmd/daemon/src/opt.rs b/kanidmd/daemon/src/opt.rs index 44fe2986e..9cfd167ba 100644 --- a/kanidmd/daemon/src/opt.rs +++ b/kanidmd/daemon/src/opt.rs @@ -70,6 +70,16 @@ struct DbScanListIndex { commonopts: CommonOpt, } + +#[derive(Debug,Parser)] +struct HealthCheckArgs { + /// Disable TLS verification + #[clap(short, long, action)] + no_verify_tls: bool, + #[clap(flatten)] + commonopts: CommonOpt, +} + /* #[derive(Debug, Args)] struct DbScanGetIndex { @@ -149,6 +159,10 @@ enum KanidmdOpt { #[clap(subcommand)] commands: DomainSettingsCmds, }, + /// Load the server config and check services are listening + #[clap(name = "healthcheck")] + HealthCheck(HealthCheckArgs), + /// Print the program version and exit #[clap(name="version")] Version(CommonOpt)