Adding healthcheck functionality to kanidmd (#1330)

* closes #1220, adds healthcheck functionality to kanidmd

* ssl is old and busted, tls is great
This commit is contained in:
James Hodgkinson 2023-01-23 19:58:13 +10:00 committed by GitHub
parent 421344c347
commit 8255c937e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 133 additions and 44 deletions

2
Cargo.lock generated
View file

@ -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",
]

View file

@ -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;
}
};

View file

@ -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" ] }

View file

@ -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<String>,
pub ldapbindaddress: Option<String>,
pub trust_x_forward_for: Option<bool>,
// pub threads: Option<usize>,
pub db_path: String,
pub db_fs_type: Option<String>,
pub db_arc_size: Option<usize>,
pub tls_chain: Option<String>,
pub tls_key: Option<String>,
pub online_backup: Option<OnlineBackup>,
pub domain: String,
pub origin: String,
#[serde(default)]
pub role: ServerRole,
}
impl ServerConfig {
pub fn new<P: AsRef<Path>>(config_path: P) -> Result<Self, ()> {
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<bool>) {
self.trust_x_forward_for = t.unwrap_or(false);
}

View file

@ -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

View file

@ -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<String>,
pub ldapbindaddress: Option<String>,
pub trust_x_forward_for: Option<bool>,
// pub threads: Option<usize>,
pub db_path: String,
pub db_fs_type: Option<String>,
pub db_arc_size: Option<usize>,
pub tls_chain: Option<String>,
pub tls_key: Option<String>,
pub online_backup: Option<OnlineBackup>,
pub domain: String,
pub origin: String,
#[serde(default)]
pub role: ServerRole,
}
impl ServerConfig {
pub fn new<P: AsRef<Path>>(config_path: P) -> Result<Self, ()> {
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
}

View file

@ -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)