mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-24 04:57:00 +01:00
352 lines
11 KiB
Rust
352 lines
11 KiB
Rust
//! The server configuration as processed from the startup wrapper. This controls a number of
|
|
//! variables that determine how our backends, query server, and frontends are configured.
|
|
//!
|
|
//! These components should be "per server". Any "per domain" config should be in the system
|
|
//! 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;
|
|
use serde::{Deserialize, Serialize};
|
|
use sketching::tracing_subscriber::EnvFilter;
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct IntegrationTestConfig {
|
|
pub admin_user: String,
|
|
pub admin_password: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct OnlineBackup {
|
|
pub path: String,
|
|
#[serde(default = "default_online_backup_schedule")]
|
|
pub schedule: String,
|
|
#[serde(default = "default_online_backup_versions")]
|
|
pub versions: usize,
|
|
}
|
|
|
|
fn default_online_backup_schedule() -> String {
|
|
"@daily".to_string()
|
|
}
|
|
|
|
fn default_online_backup_versions() -> usize {
|
|
7
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct TlsConfiguration {
|
|
pub chain: String,
|
|
pub key: String,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct ServerConfig {
|
|
pub bindaddress: Option<String>,
|
|
pub ldapbindaddress: Option<String>,
|
|
pub adminbindpath: 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,
|
|
pub log_level: Option<LogLevel>,
|
|
}
|
|
|
|
impl ServerConfig {
|
|
pub fn new<P: AsRef<Path>>(config_path: P) -> Result<Self, std::io::Error> {
|
|
let mut f = File::open(config_path).map_err(|e| {
|
|
eprintln!("Unable to open config file [{:?}] 🥺", e);
|
|
e
|
|
})?;
|
|
|
|
let mut contents = String::new();
|
|
f.read_to_string(&mut contents).map_err(|e| {
|
|
eprintln!("unable to read contents {:?}", e);
|
|
e
|
|
})?;
|
|
|
|
toml::from_str(contents.as_str()).map_err(|e| {
|
|
eprintln!("unable to parse config {:?}", e);
|
|
std::io::Error::new(std::io::ErrorKind::Other, e)
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, Eq, PartialEq)]
|
|
pub enum ServerRole {
|
|
#[default]
|
|
WriteReplica,
|
|
WriteReplicaNoUI,
|
|
ReadOnlyReplica,
|
|
}
|
|
|
|
impl ToString for ServerRole {
|
|
fn to_string(&self) -> String {
|
|
match self {
|
|
ServerRole::WriteReplica => "write replica".to_string(),
|
|
ServerRole::WriteReplicaNoUI => "write replica (no ui)".to_string(),
|
|
ServerRole::ReadOnlyReplica => "read only replica".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromStr for ServerRole {
|
|
type Err = &'static str;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s {
|
|
"write_replica" => Ok(ServerRole::WriteReplica),
|
|
"write_replica_no_ui" => Ok(ServerRole::WriteReplicaNoUI),
|
|
"read_only_replica" => Ok(ServerRole::ReadOnlyReplica),
|
|
_ => Err("Must be one of write_replica, write_replica_no_ui, read_only_replica"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
|
pub enum LogLevel {
|
|
#[default]
|
|
#[serde(rename = "info")]
|
|
Info,
|
|
#[serde(rename = "debug")]
|
|
Debug,
|
|
#[serde(rename = "trace")]
|
|
Trace,
|
|
}
|
|
|
|
impl FromStr for LogLevel {
|
|
type Err = &'static str;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s {
|
|
"info" => Ok(LogLevel::Info),
|
|
"debug" => Ok(LogLevel::Debug),
|
|
"trace" => Ok(LogLevel::Trace),
|
|
_ => Err("Must be one of info, debug, trace"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ToString for LogLevel {
|
|
fn to_string(&self) -> String {
|
|
match self {
|
|
LogLevel::Info => "info".to_string(),
|
|
LogLevel::Debug => "debug".to_string(),
|
|
LogLevel::Trace => "trace".to_string(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<LogLevel> for EnvFilter {
|
|
fn from(value: LogLevel) -> Self {
|
|
match value {
|
|
LogLevel::Info => EnvFilter::new("info"),
|
|
LogLevel::Debug => EnvFilter::new("debug"),
|
|
LogLevel::Trace => EnvFilter::new("trace"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
|
pub struct Configuration {
|
|
pub address: String,
|
|
pub ldapaddress: Option<String>,
|
|
pub adminbindpath: String,
|
|
pub threads: usize,
|
|
// db type later
|
|
pub db_path: String,
|
|
pub db_fs_type: Option<String>,
|
|
pub db_arc_size: Option<usize>,
|
|
pub maximum_request: usize,
|
|
pub trust_x_forward_for: bool,
|
|
pub tls_config: Option<TlsConfiguration>,
|
|
pub integration_test_config: Option<Box<IntegrationTestConfig>>,
|
|
pub online_backup: Option<OnlineBackup>,
|
|
pub domain: String,
|
|
pub origin: String,
|
|
pub role: ServerRole,
|
|
pub output_mode: ConsoleOutputMode,
|
|
pub log_level: LogLevel,
|
|
}
|
|
|
|
impl fmt::Display for Configuration {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "address: {}, ", self.address)?;
|
|
write!(f, "domain: {}, ", self.domain)
|
|
.and_then(|_| match &self.ldapaddress {
|
|
Some(la) => write!(f, "ldap address: {}, ", la),
|
|
None => write!(f, "ldap address: disabled, "),
|
|
})
|
|
.and_then(|_| write!(f, "admin bind path: {}, ", self.adminbindpath))
|
|
.and_then(|_| write!(f, "thread count: {}, ", self.threads))
|
|
.and_then(|_| write!(f, "dbpath: {}, ", self.db_path))
|
|
.and_then(|_| match self.db_arc_size {
|
|
Some(v) => write!(f, "arcsize: {}, ", v),
|
|
None => write!(f, "arcsize: AUTO, "),
|
|
})
|
|
.and_then(|_| write!(f, "max request size: {}b, ", self.maximum_request))
|
|
.and_then(|_| write!(f, "trust X-Forwarded-For: {}, ", self.trust_x_forward_for))
|
|
.and_then(|_| write!(f, "with TLS: {}, ", self.tls_config.is_some()))
|
|
// TODO: include the backup timings
|
|
.and_then(|_| match &self.online_backup {
|
|
Some(_) => write!(f, "online_backup: enabled, "),
|
|
None => write!(f, "online_backup: disabled, "),
|
|
})
|
|
.and_then(|_| write!(f, "role: {}, ", self.role.to_string()))
|
|
.and_then(|_| {
|
|
write!(
|
|
f,
|
|
"integration mode: {}, ",
|
|
self.integration_test_config.is_some()
|
|
)
|
|
})
|
|
.and_then(|_| write!(f, "console output format: {:?} ", self.output_mode))
|
|
.and_then(|_| write!(f, "log_level: {}", self.log_level.clone().to_string()))
|
|
}
|
|
}
|
|
|
|
impl Configuration {
|
|
pub fn new() -> Self {
|
|
Configuration {
|
|
address: String::from("127.0.0.1:8080"),
|
|
ldapaddress: None,
|
|
adminbindpath: env!("KANIDM_ADMIN_BIND_PATH").to_string(),
|
|
threads: std::thread::available_parallelism()
|
|
.map(|t| t.get())
|
|
.unwrap_or_else(|_e| {
|
|
eprintln!("WARNING: Unable to read number of available CPUs, defaulting to 4");
|
|
4
|
|
}),
|
|
db_path: String::from(""),
|
|
db_fs_type: None,
|
|
db_arc_size: None,
|
|
maximum_request: 256 * 1024, // 256k
|
|
trust_x_forward_for: false,
|
|
tls_config: None,
|
|
integration_test_config: None,
|
|
online_backup: None,
|
|
domain: "idm.example.com".to_string(),
|
|
origin: "https://idm.example.com".to_string(),
|
|
role: ServerRole::WriteReplica,
|
|
output_mode: ConsoleOutputMode::default(),
|
|
log_level: Default::default(),
|
|
}
|
|
}
|
|
|
|
pub fn new_for_test() -> Self {
|
|
Configuration {
|
|
threads: 1,
|
|
..Configuration::new()
|
|
}
|
|
}
|
|
|
|
pub fn update_online_backup(&mut self, cfg: &Option<OnlineBackup>) {
|
|
match cfg {
|
|
None => {}
|
|
Some(cfg) => {
|
|
let path = cfg.path.to_string();
|
|
let schedule = cfg.schedule.to_string();
|
|
let versions = cfg.versions;
|
|
self.online_backup = Some(OnlineBackup {
|
|
path,
|
|
schedule,
|
|
versions,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn update_log_level(&mut self, level: &Option<LogLevel>) {
|
|
let level = level.clone();
|
|
self.log_level = level.unwrap_or_default();
|
|
}
|
|
|
|
// 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);
|
|
self.update_log_level(&sconfig.log_level);
|
|
}
|
|
|
|
pub fn update_trust_x_forward_for(&mut self, t: Option<bool>) {
|
|
self.trust_x_forward_for = t.unwrap_or(false);
|
|
}
|
|
|
|
pub fn update_db_path(&mut self, p: &str) {
|
|
self.db_path = p.to_string();
|
|
}
|
|
|
|
pub fn update_db_arc_size(&mut self, v: Option<usize>) {
|
|
self.db_arc_size = v
|
|
}
|
|
|
|
pub fn update_db_fs_type(&mut self, p: &Option<String>) {
|
|
self.db_fs_type = p.as_ref().map(|v| v.to_lowercase());
|
|
}
|
|
|
|
pub fn update_bind(&mut self, b: &Option<String>) {
|
|
self.address = b
|
|
.as_ref()
|
|
.cloned()
|
|
.unwrap_or_else(|| String::from("127.0.0.1:8080"));
|
|
}
|
|
|
|
pub fn update_ldapbind(&mut self, l: &Option<String>) {
|
|
self.ldapaddress = l.clone();
|
|
}
|
|
|
|
pub fn update_admin_bind_path(&mut self, p: &Option<String>) {
|
|
if let Some(p) = p {
|
|
self.adminbindpath = p.clone();
|
|
}
|
|
}
|
|
|
|
pub fn update_origin(&mut self, o: &str) {
|
|
self.origin = o.to_string();
|
|
}
|
|
|
|
pub fn update_domain(&mut self, d: &str) {
|
|
self.domain = d.to_string();
|
|
}
|
|
|
|
pub fn update_role(&mut self, r: ServerRole) {
|
|
self.role = r;
|
|
}
|
|
|
|
/// Sets the output mode for writing to the console
|
|
pub fn update_output_mode(&mut self, om: ConsoleOutputMode) {
|
|
self.output_mode = om;
|
|
}
|
|
|
|
pub fn update_tls(&mut self, chain: &Option<String>, key: &Option<String>) {
|
|
match (chain, key) {
|
|
(None, None) => {}
|
|
(Some(chainp), Some(keyp)) => {
|
|
let chain = chainp.to_string();
|
|
let key = keyp.to_string();
|
|
self.tls_config = Some(TlsConfiguration { chain, key })
|
|
}
|
|
_ => {
|
|
eprintln!("ERROR: Invalid TLS configuration - must provide chain and key!");
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
}
|