1#![deny(warnings)]
2#![warn(unused_extern_crates)]
3#![deny(clippy::todo)]
4#![deny(clippy::unimplemented)]
5#![deny(clippy::unwrap_used)]
6#![deny(clippy::expect_used)]
7#![deny(clippy::panic)]
8#![deny(clippy::unreachable)]
9#![deny(clippy::await_holding_lock)]
10#![deny(clippy::needless_pass_by_value)]
11#![deny(clippy::trivially_copy_pass_by_ref)]
12
13#[cfg(all(not(feature = "dhat-heap"), target_os = "linux"))]
14#[global_allocator]
15static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
16
17#[cfg(feature = "dhat-heap")]
18#[global_allocator]
19static ALLOC: dhat::Alloc = dhat::Alloc;
20
21use std::fs::{metadata, File};
22use fs4::fs_std::FileExt;
24use kanidm_proto::messages::ConsoleOutputMode;
25use sketching::otel::TracingPipelineGuard;
26use std::io::Read;
27#[cfg(target_family = "unix")]
28use std::os::unix::fs::MetadataExt;
29use std::path::PathBuf;
30use std::process::ExitCode;
31
32use clap::{Args, Parser, Subcommand};
33use futures::{SinkExt, StreamExt};
34#[cfg(not(target_family = "windows"))] use kanidm_utils_users::{get_current_gid, get_current_uid, get_effective_gid, get_effective_uid};
36use kanidmd_core::admin::{
37 AdminTaskRequest, AdminTaskResponse, ClientCodec, ProtoDomainInfo,
38 ProtoDomainUpgradeCheckReport, ProtoDomainUpgradeCheckStatus,
39};
40use kanidmd_core::config::{Configuration, ServerConfigUntagged};
41use kanidmd_core::{
42 backup_server_core, cert_generate_core, create_server_core, dbscan_get_id2entry_core,
43 dbscan_list_id2entry_core, dbscan_list_index_analysis_core, dbscan_list_index_core,
44 dbscan_list_indexes_core, dbscan_list_quarantined_core, dbscan_quarantine_id2entry_core,
45 dbscan_restore_quarantined_core, domain_rename_core, reindex_server_core, restore_server_core,
46 vacuum_server_core, verify_server_core,
47};
48use sketching::tracing_forest::util::*;
49use tokio::net::UnixStream;
50use tokio_util::codec::Framed;
51#[cfg(target_family = "windows")] use whoami;
53
54include!("./opt.rs");
55
56#[cfg(target_family = "windows")]
58fn get_user_details_windows() {
59 eprintln!(
60 "Running on windows, current username is: {:?}",
61 whoami::username()
62 );
63}
64
65async fn submit_admin_req(path: &str, req: AdminTaskRequest, output_mode: ConsoleOutputMode) {
66 let stream = match UnixStream::connect(path).await {
68 Ok(s) => s,
69 Err(e) => {
70 error!(err = ?e, %path, "Unable to connect to socket path");
71 let diag = kanidm_lib_file_permissions::diagnose_path(path.as_ref());
72 info!(%diag);
73 return;
74 }
75 };
76
77 let mut reqs = Framed::new(stream, ClientCodec);
78
79 if let Err(e) = reqs.send(req).await {
80 error!(err = ?e, "Unable to send request");
81 return;
82 };
83
84 if let Err(e) = reqs.flush().await {
85 error!(err = ?e, "Unable to flush request");
86 return;
87 }
88
89 trace!("flushed, waiting ...");
90
91 match reqs.next().await {
92 Some(Ok(AdminTaskResponse::RecoverAccount { password })) => match output_mode {
93 ConsoleOutputMode::JSON => {
94 let json_output = serde_json::json!({
95 "password": password
96 });
97 println!("{json_output}");
98 }
99 ConsoleOutputMode::Text => {
100 info!(new_password = ?password)
101 }
102 },
103 Some(Ok(AdminTaskResponse::ShowReplicationCertificate { cert })) => match output_mode {
104 ConsoleOutputMode::JSON => {
105 println!("{{\"certificate\":\"{cert}\"}}")
106 }
107 ConsoleOutputMode::Text => {
108 info!(certificate = ?cert)
109 }
110 },
111
112 Some(Ok(AdminTaskResponse::DomainUpgradeCheck { report })) => {
113 match output_mode {
114 ConsoleOutputMode::JSON => {
115 let json_output = serde_json::json!({
116 "domain_upgrade_check": report
117 });
118 println!("{json_output}");
119 }
120 ConsoleOutputMode::Text => {
121 let ProtoDomainUpgradeCheckReport {
122 name,
123 uuid,
124 current_level,
125 upgrade_level,
126 report_items,
127 } = report;
128
129 info!("domain_name : {}", name);
130 info!("domain_uuid : {}", uuid);
131 info!("domain_current_level : {}", current_level);
132 info!("domain_upgrade_level : {}", upgrade_level);
133
134 if report_items.is_empty() {
135 info!("------------------------");
137 info!("status : PASS");
138 return;
139 }
140
141 for item in report_items {
142 info!("------------------------");
143 match item.status {
144 ProtoDomainUpgradeCheckStatus::Pass6To7Gidnumber => {
145 info!("upgrade_item : gidnumber range validity");
146 debug!("from_level : {}", item.from_level);
147 debug!("to_level : {}", item.to_level);
148 info!("status : PASS");
149 }
150 ProtoDomainUpgradeCheckStatus::Fail6To7Gidnumber => {
151 info!("upgrade_item : gidnumber range validity");
152 debug!("from_level : {}", item.from_level);
153 debug!("to_level : {}", item.to_level);
154 info!("status : FAIL");
155 info!("description : The automatically allocated gidnumbers for posix accounts was found to allocate numbers into systemd-reserved ranges. These can no longer be used.");
156 info!("action : Modify the gidnumber of affected entries so that they are in the range 65536 to 524287 OR reset the gidnumber to cause it to automatically regenerate.");
157 for entry_id in item.affected_entries {
158 info!("affected_entry : {}", entry_id);
159 }
160 }
161 ProtoDomainUpgradeCheckStatus::Pass7To8SecurityKeys => {
163 info!("upgrade_item : security key usage");
164 debug!("from_level : {}", item.from_level);
165 debug!("to_level : {}", item.to_level);
166 info!("status : PASS");
167 }
168 ProtoDomainUpgradeCheckStatus::Fail7To8SecurityKeys => {
169 info!("upgrade_item : security key usage");
170 debug!("from_level : {}", item.from_level);
171 debug!("to_level : {}", item.to_level);
172 info!("status : FAIL");
173 info!("description : Security keys no longer function as a second factor due to the introduction of CTAP2 and greater forcing PIN interactions.");
174 info!("action : Modify the accounts in question to remove their security key and add it as a passkey or enable TOTP");
175 for entry_id in item.affected_entries {
176 info!("affected_entry : {}", entry_id);
177 }
178 }
179 ProtoDomainUpgradeCheckStatus::Pass7To8Oauth2StrictRedirectUri => {
181 info!("upgrade_item : oauth2 strict redirect uri enforcement");
182 debug!("from_level : {}", item.from_level);
183 debug!("to_level : {}", item.to_level);
184 info!("status : PASS");
185 }
186 ProtoDomainUpgradeCheckStatus::Fail7To8Oauth2StrictRedirectUri => {
187 info!("upgrade_item : oauth2 strict redirect uri enforcement");
188 debug!("from_level : {}", item.from_level);
189 debug!("to_level : {}", item.to_level);
190 info!("status : FAIL");
191 info!("description : To harden against possible public client open redirection vulnerabilities, redirect uris must now be registered ahead of time and are validated rather than the former origin verification process.");
192 info!("action : Verify the redirect uri's for OAuth2 clients and then enable strict-redirect-uri on each client.");
193 for entry_id in item.affected_entries {
194 info!("affected_entry : {}", entry_id);
195 }
196 }
197 }
198 } }
200 }
201 }
202
203 Some(Ok(AdminTaskResponse::DomainRaise { level })) => match output_mode {
204 ConsoleOutputMode::JSON => {
205 eprintln!("{{\"success\":\"{level}\"}}")
206 }
207 ConsoleOutputMode::Text => {
208 info!("success - raised domain level to {}", level)
209 }
210 },
211 Some(Ok(AdminTaskResponse::DomainShow { domain_info })) => match output_mode {
212 ConsoleOutputMode::JSON => {
213 let json_output = serde_json::json!({
214 "domain_info": domain_info
215 });
216 println!("{json_output}");
217 }
218 ConsoleOutputMode::Text => {
219 let ProtoDomainInfo {
220 name,
221 displayname,
222 uuid,
223 level,
224 } = domain_info;
225
226 info!("domain_name : {}", name);
227 info!("domain_display: {}", displayname);
228 info!("domain_uuid : {}", uuid);
229 info!("domain_level : {}", level);
230 }
231 },
232 Some(Ok(AdminTaskResponse::Success)) => match output_mode {
233 ConsoleOutputMode::JSON => {
234 eprintln!("\"success\"")
235 }
236 ConsoleOutputMode::Text => {
237 info!("success")
238 }
239 },
240 Some(Ok(AdminTaskResponse::Error)) => match output_mode {
241 ConsoleOutputMode::JSON => {
242 eprintln!("\"error\"")
243 }
244 ConsoleOutputMode::Text => {
245 info!("Error - you should inspect the logs.")
246 }
247 },
248 Some(Err(err)) => {
249 error!(?err, "Error during admin task operation");
250 }
251 None => {
252 error!("Error making request to admin socket");
253 }
254 }
255}
256
257fn check_file_ownership(opt: &KanidmdParser) -> Result<(), ExitCode> {
259 #[cfg(target_family = "unix")]
261 let (cuid, ceuid) = {
262 let cuid = get_current_uid();
263 let ceuid = get_effective_uid();
264 let cgid = get_current_gid();
265 let cegid = get_effective_gid();
266
267 if cuid == 0 || ceuid == 0 || cgid == 0 || cegid == 0 {
268 warn!("This is running as uid == 0 (root) which may be a security risk.");
269 }
272
273 if cuid != ceuid || cgid != cegid {
274 error!("{} != {} || {} != {}", cuid, ceuid, cgid, cegid);
275 error!("Refusing to run - uid and euid OR gid and egid must be consistent.");
276 return Err(ExitCode::FAILURE);
277 }
278 (cuid, ceuid)
279 };
280
281 if let Some(cfg_path) = &opt.config_path {
282 #[cfg(target_family = "unix")]
283 {
284 if let Some(cfg_meta) = match metadata(cfg_path) {
285 Ok(m) => Some(m),
286 Err(e) => {
287 error!(
288 "Unable to read metadata for configuration file '{}' - {:?}",
289 cfg_path.display(),
290 e
291 );
292 None
294 }
295 } {
296 if !kanidm_lib_file_permissions::readonly(&cfg_meta) {
297 warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...",
298 cfg_path.to_str().unwrap_or("invalid file path"));
299 }
300
301 if cfg_meta.mode() & 0o007 != 0 {
302 warn!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...",
303 cfg_path.to_str().unwrap_or("invalid file path")
304 );
305 }
306
307 if cfg_meta.uid() == cuid || cfg_meta.uid() == ceuid {
308 warn!("WARNING: {} owned by the current uid, which may allow file permission changes. This could be a security risk ...",
309 cfg_path.to_str().unwrap_or("invalid file path")
310 );
311 }
312 }
313 }
314 }
315 Ok(())
316}
317
318async fn start_daemon(opt: KanidmdParser, config: Configuration) -> ExitCode {
320 let (provider, sub) = match sketching::otel::start_logging_pipeline(
324 &config.otel_grpc_url,
325 config.log_level,
326 "kanidmd",
327 ) {
328 Err(err) => {
329 eprintln!("Error starting logger - {err:} - Bailing on startup!");
330 return ExitCode::FAILURE;
331 }
332 Ok(val) => val,
333 };
334 if let Err(err) = tracing::subscriber::set_global_default(sub).map_err(|err| {
335 eprintln!("Error starting logger - {err:} - Bailing on startup!");
336 ExitCode::FAILURE
337 }) {
338 return err;
339 };
340
341 info!(version = %env!("KANIDM_PKG_VERSION"), "Starting Kanidmd");
345
346 let _otelguard = TracingPipelineGuard(provider); if let Err(err) = check_file_ownership(&opt) {
354 return err;
355 };
356
357 if let Some(db_path) = config.db_path.as_ref() {
358 let db_pathbuf = db_path.to_path_buf();
359 if let Some(db_parent_path) = db_pathbuf.parent() {
361 if !db_parent_path.exists() {
362 warn!(
363 "DB folder {} may not exist, server startup may FAIL!",
364 db_parent_path.to_str().unwrap_or("invalid file path")
365 );
366 let diag = kanidm_lib_file_permissions::diagnose_path(&db_pathbuf);
367 info!(%diag);
368 }
369
370 let db_par_path_buf = db_parent_path.to_path_buf();
371 let i_meta = match metadata(&db_par_path_buf) {
372 Ok(m) => m,
373 Err(e) => {
374 error!(
375 "Unable to read metadata for database folder '{}' - {:?}",
376 &db_par_path_buf.to_str().unwrap_or("invalid file path"),
377 e
378 );
379 return ExitCode::FAILURE;
380 }
381 };
382 if !i_meta.is_dir() {
383 error!(
384 "ERROR: Refusing to run - DB folder {} may not be a directory",
385 db_par_path_buf.to_str().unwrap_or("invalid file path")
386 );
387 return ExitCode::FAILURE;
388 }
389
390 if kanidm_lib_file_permissions::readonly(&i_meta) {
391 warn!("WARNING: DB folder permissions on {} indicate it may not be RW. This could cause the server start up to fail!", db_par_path_buf.to_str().unwrap_or("invalid file path"));
392 }
393 #[cfg(not(target_os = "windows"))]
394 if i_meta.mode() & 0o007 != 0 {
395 warn!("WARNING: DB folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", db_par_path_buf.to_str().unwrap_or("invalid file path"));
396 }
397 }
398 } else {
399 error!("No db_path set in configuration, server startup will FAIL!");
400 return ExitCode::FAILURE;
401 }
402
403 let lock_was_setup = match &opt.commands {
404 KanidmdOpt::ShowReplicationCertificate
406 | KanidmdOpt::RenewReplicationCertificate
407 | KanidmdOpt::RefreshReplicationConsumer { .. }
408 | KanidmdOpt::RecoverAccount { .. }
409 | KanidmdOpt::DisableAccount { .. }
410 | KanidmdOpt::HealthCheck(_) => None,
411 _ => {
412 #[allow(clippy::expect_used)]
414 let klock_path = match config.db_path.clone() {
415 Some(val) => val.with_extension("klock"),
416 None => std::env::temp_dir().join("kanidmd.klock"),
417 };
418
419 let flock = match File::create(&klock_path) {
420 Ok(flock) => flock,
421 Err(err) => {
422 error!(
423 "ERROR: Refusing to start - unable to create kanidmd exclusive lock at {}",
424 klock_path.display()
425 );
426 error!(?err);
427 return ExitCode::FAILURE;
428 }
429 };
430
431 match flock.try_lock_exclusive() {
432 Ok(true) => debug!("Acquired kanidm exclusive lock"),
433 Ok(false) => {
434 error!(
435 "ERROR: Refusing to start - unable to lock kanidmd exclusive lock at {}",
436 klock_path.display()
437 );
438 error!("Is another kanidmd process running?");
439 return ExitCode::FAILURE;
440 }
441 Err(err) => {
442 error!(
443 "ERROR: Refusing to start - unable to lock kanidmd exclusive lock at {}",
444 klock_path.display()
445 );
446 error!(?err);
447 return ExitCode::FAILURE;
448 }
449 };
450
451 Some(klock_path)
452 }
453 };
454
455 let result_code = kanidm_main(config, opt).await;
456
457 if let Some(klock_path) = lock_was_setup {
458 if let Err(reason) = std::fs::remove_file(&klock_path) {
459 warn!(
460 ?reason,
461 "WARNING: Unable to clean up kanidmd exclusive lock at {}",
462 klock_path.display()
463 );
464 }
465 }
466
467 result_code
468}
469
470fn main() -> ExitCode {
471 #[cfg(all(target_os = "linux", not(debug_assertions)))]
474 if let Err(code) = prctl::set_dumpable(false) {
475 println!(
476 "CRITICAL: Unable to set prctl flags, which breaches our security model, quitting! {:?}", code
477 );
478 return ExitCode::FAILURE;
479 }
480
481 #[cfg(feature = "dhat-heap")]
483 let _profiler = dhat::Profiler::builder().trim_backtraces(Some(40)).build();
484
485 let opt = KanidmdParser::parse();
487
488 if let KanidmdOpt::Version = &opt.commands {
490 println!("kanidmd {}", env!("KANIDM_PKG_VERSION"));
491 return ExitCode::SUCCESS;
492 };
493
494 if env!("KANIDM_SERVER_CONFIG_PATH").is_empty() {
495 println!("CRITICAL: Kanidmd was not built correctly and is missing a valid KANIDM_SERVER_CONFIG_PATH value");
496 return ExitCode::FAILURE;
497 }
498
499 let default_config_path = PathBuf::from(env!("KANIDM_SERVER_CONFIG_PATH"));
500
501 let maybe_config_path = if let Some(p) = &opt.config_path {
502 Some(p.clone())
503 } else {
504 if default_config_path.exists() {
506 Some(default_config_path)
508 } else {
509 None
512 }
513 };
514
515 let maybe_sconfig = if let Some(config_path) = maybe_config_path {
516 match ServerConfigUntagged::new(config_path) {
517 Ok(c) => Some(c),
518 Err(err) => {
519 eprintln!("ERROR: Configuration Parse Failure: {err:?}");
520 return ExitCode::FAILURE;
521 }
522 }
523 } else {
524 eprintln!("WARNING: No configuration path was provided, relying on environment variables.");
525 None
526 };
527
528 let is_server = matches!(&opt.commands, KanidmdOpt::Server);
529
530 let config = Configuration::build()
531 .add_opt_toml_config(maybe_sconfig)
532 .add_cli_config(&opt.kanidmd_options)
533 .is_server_mode(is_server)
535 .finish();
536
537 let Some(config) = config else {
538 eprintln!(
539 "ERROR: Unable to build server configuration from provided configuration inputs."
540 );
541 return ExitCode::FAILURE;
542 };
543
544 #[cfg(target_family = "windows")]
549 get_user_details_windows();
550
551 let maybe_rt = tokio::runtime::Builder::new_multi_thread()
553 .worker_threads(config.threads)
554 .enable_all()
555 .thread_name("kanidmd-thread-pool")
556 .build();
562
563 let rt = match maybe_rt {
564 Ok(rt) => rt,
565 Err(err) => {
566 eprintln!("CRITICAL: Unable to start runtime! {err:?}");
567 return ExitCode::FAILURE;
568 }
569 };
570
571 rt.block_on(start_daemon(opt, config))
572}
573
574async fn kanidm_main(config: Configuration, opt: KanidmdParser) -> ExitCode {
577 match &opt.commands {
578 KanidmdOpt::Server | KanidmdOpt::ConfigTest => {
579 let config_test = matches!(&opt.commands, KanidmdOpt::ConfigTest);
580 if config_test {
581 info!("Running in server configuration test mode ...");
582 } else {
583 info!("Running in server mode ...");
584 };
585
586 if let Some(tls_config) = config.tls_config.as_ref() {
588 {
589 let i_meta = match metadata(&tls_config.chain) {
590 Ok(m) => m,
591 Err(e) => {
592 error!(
593 "Unable to read metadata for TLS chain file '{}' - {:?}",
594 tls_config.chain.display(),
595 e
596 );
597 let diag =
598 kanidm_lib_file_permissions::diagnose_path(&tls_config.chain);
599 info!(%diag);
600 return ExitCode::FAILURE;
601 }
602 };
603 if !kanidm_lib_file_permissions::readonly(&i_meta) {
604 warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", tls_config.chain.display());
605 }
606 }
607
608 {
609 let i_meta = match metadata(&tls_config.key) {
610 Ok(m) => m,
611 Err(e) => {
612 error!(
613 "Unable to read metadata for TLS key file '{}' - {:?}",
614 tls_config.key.display(),
615 e
616 );
617 let diag = kanidm_lib_file_permissions::diagnose_path(&tls_config.key);
618 info!(%diag);
619 return ExitCode::FAILURE;
620 }
621 };
622 if !kanidm_lib_file_permissions::readonly(&i_meta) {
623 warn!("permissions on {} may not be secure. Should be readonly to running uid. This could be a security risk ...", tls_config.key.display());
624 }
625 #[cfg(not(target_os = "windows"))]
626 if i_meta.mode() & 0o007 != 0 {
627 warn!("WARNING: {} has 'everyone' permission bits in the mode. This could be a security risk ...", tls_config.key.display());
628 }
629 }
630
631 if let Some(ca_dir) = tls_config.client_ca.as_ref() {
632 let ca_dir_path = PathBuf::from(&ca_dir);
634 if !ca_dir_path.exists() {
635 error!(
636 "TLS CA folder {} does not exist, server startup will FAIL!",
637 ca_dir.display()
638 );
639 let diag = kanidm_lib_file_permissions::diagnose_path(&ca_dir_path);
640 info!(%diag);
641 }
642
643 let i_meta = match metadata(&ca_dir_path) {
644 Ok(m) => m,
645 Err(e) => {
646 error!(
647 "Unable to read metadata for '{}' - {:?}",
648 ca_dir.display(),
649 e
650 );
651 let diag = kanidm_lib_file_permissions::diagnose_path(&ca_dir_path);
652 info!(%diag);
653 return ExitCode::FAILURE;
654 }
655 };
656 if !i_meta.is_dir() {
657 error!(
658 "ERROR: Refusing to run - TLS Client CA folder {} may not be a directory",
659 ca_dir.display()
660 );
661 return ExitCode::FAILURE;
662 }
663 if kanidm_lib_file_permissions::readonly(&i_meta) {
664 warn!("WARNING: TLS Client CA folder permissions on {} indicate it may not be RW. This could cause the server start up to fail!", ca_dir.display());
665 }
666 #[cfg(not(target_os = "windows"))]
667 if i_meta.mode() & 0o007 != 0 {
668 warn!("WARNING: TLS Client CA folder {} has 'everyone' permission bits in the mode. This could be a security risk ...", ca_dir.display());
669 }
670 }
671 }
672
673 let sctx = create_server_core(config, config_test).await;
674 if !config_test {
675 #[cfg(target_os = "linux")]
677 {
678 let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
679 let _ = sd_notify::notify(
680 true,
681 &[sd_notify::NotifyState::Status("Started Kanidm 🦀")],
682 );
683 };
684
685 match sctx {
686 Ok(mut sctx) => {
687 loop {
688 #[cfg(target_family = "unix")]
689 {
690 let mut listener = sctx.subscribe();
691 tokio::select! {
692 Ok(()) = tokio::signal::ctrl_c() => {
693 break
694 }
695 Some(()) = async move {
696 let sigterm = tokio::signal::unix::SignalKind::terminate();
697 #[allow(clippy::unwrap_used)]
698 tokio::signal::unix::signal(sigterm).unwrap().recv().await
699 } => {
700 break
701 }
702 Some(()) = async move {
703 let sigterm = tokio::signal::unix::SignalKind::alarm();
704 #[allow(clippy::unwrap_used)]
705 tokio::signal::unix::signal(sigterm).unwrap().recv().await
706 } => {
707 }
709 Some(()) = async move {
710 let sigterm = tokio::signal::unix::SignalKind::hangup();
711 #[allow(clippy::unwrap_used)]
712 tokio::signal::unix::signal(sigterm).unwrap().recv().await
713 } => {
714 sctx.tls_acceptor_reload().await;
716 info!("Reload complete");
717 }
718 Some(()) = async move {
719 let sigterm = tokio::signal::unix::SignalKind::user_defined1();
720 #[allow(clippy::unwrap_used)]
721 tokio::signal::unix::signal(sigterm).unwrap().recv().await
722 } => {
723 }
725 Some(()) = async move {
726 let sigterm = tokio::signal::unix::SignalKind::user_defined2();
727 #[allow(clippy::unwrap_used)]
728 tokio::signal::unix::signal(sigterm).unwrap().recv().await
729 } => {
730 }
732 Ok(msg) = async move {
734 listener.recv().await
735 } => {
736 debug!("Main loop received message: {:?}", msg);
737 break
738 }
739 }
740 }
741 #[cfg(target_family = "windows")]
742 {
743 tokio::select! {
744 Ok(()) = tokio::signal::ctrl_c() => {
745 break
746 }
747 }
748 }
749 }
750 info!("Signal received, shutting down");
751 sctx.shutdown().await;
753 }
754 Err(_) => {
755 error!("Failed to start server core!");
756 return ExitCode::FAILURE;
759 }
760 }
761 info!("Stopped 🛑 ");
762 }
763 }
764 KanidmdOpt::CertGenerate => {
765 info!("Running in certificate generate mode ...");
766 cert_generate_core(&config);
767 }
768 KanidmdOpt::Database {
769 commands: DbCommands::Backup(bopt),
770 } => {
771 info!("Running in backup mode ...");
772
773 backup_server_core(&config, &bopt.path);
774 }
775 KanidmdOpt::Database {
776 commands: DbCommands::Restore(ropt),
777 } => {
778 info!("Running in restore mode ...");
779 restore_server_core(&config, &ropt.path).await;
780 }
781 KanidmdOpt::Database {
782 commands: DbCommands::Verify,
783 } => {
784 info!("Running in db verification mode ...");
785 verify_server_core(&config).await;
786 }
787 KanidmdOpt::ShowReplicationCertificate => {
788 info!("Running show replication certificate ...");
789 submit_admin_req(
790 config.adminbindpath.as_str(),
791 AdminTaskRequest::ShowReplicationCertificate,
792 opt.kanidmd_options.output_mode,
793 )
794 .await;
795 }
796 KanidmdOpt::RenewReplicationCertificate => {
797 info!("Running renew replication certificate ...");
798 submit_admin_req(
799 config.adminbindpath.as_str(),
800 AdminTaskRequest::RenewReplicationCertificate,
801 opt.kanidmd_options.output_mode,
802 )
803 .await;
804 }
805 KanidmdOpt::RefreshReplicationConsumer { proceed } => {
806 info!("Running refresh replication consumer ...");
807 if !proceed {
808 error!("Unwilling to proceed. Check --help.");
809 } else {
810 submit_admin_req(
811 config.adminbindpath.as_str(),
812 AdminTaskRequest::RefreshReplicationConsumer,
813 opt.kanidmd_options.output_mode,
814 )
815 .await;
816 }
817 }
818 KanidmdOpt::RecoverAccount { name } => {
819 info!("Running account recovery ...");
820
821 submit_admin_req(
822 config.adminbindpath.as_str(),
823 AdminTaskRequest::RecoverAccount {
824 name: name.to_owned(),
825 },
826 opt.kanidmd_options.output_mode,
827 )
828 .await;
829 }
830 KanidmdOpt::DisableAccount { name } => {
831 info!("Running account disable ...");
832
833 submit_admin_req(
834 config.adminbindpath.as_str(),
835 AdminTaskRequest::DisableAccount {
836 name: name.to_owned(),
837 },
838 opt.kanidmd_options.output_mode,
839 )
840 .await;
841 }
842 KanidmdOpt::Database {
843 commands: DbCommands::Reindex,
844 } => {
845 info!("Running in reindex mode ...");
846 reindex_server_core(&config).await;
847 }
848 KanidmdOpt::DbScan {
849 commands: DbScanOpt::ListIndexes,
850 } => {
851 info!("👀 db scan - list indexes");
852 dbscan_list_indexes_core(&config);
853 }
854 KanidmdOpt::DbScan {
855 commands: DbScanOpt::ListId2Entry,
856 } => {
857 info!("👀 db scan - list id2entry");
858 dbscan_list_id2entry_core(&config);
859 }
860 KanidmdOpt::DbScan {
861 commands: DbScanOpt::ListIndexAnalysis,
862 } => {
863 info!("👀 db scan - list index analysis");
864 dbscan_list_index_analysis_core(&config);
865 }
866 KanidmdOpt::DbScan {
867 commands: DbScanOpt::ListIndex(dopt),
868 } => {
869 info!("👀 db scan - list index content - {}", dopt.index_name);
870 dbscan_list_index_core(&config, dopt.index_name.as_str());
871 }
872 KanidmdOpt::DbScan {
873 commands: DbScanOpt::GetId2Entry(dopt),
874 } => {
875 info!("👀 db scan - get id2 entry - {}", dopt.id);
876 dbscan_get_id2entry_core(&config, dopt.id);
877 }
878
879 KanidmdOpt::DbScan {
880 commands: DbScanOpt::QuarantineId2Entry { id },
881 } => {
882 info!("☣️ db scan - quarantine id2 entry - {}", id);
883 dbscan_quarantine_id2entry_core(&config, *id);
884 }
885
886 KanidmdOpt::DbScan {
887 commands: DbScanOpt::ListQuarantined,
888 } => {
889 info!("☣️ db scan - list quarantined");
890 dbscan_list_quarantined_core(&config);
891 }
892
893 KanidmdOpt::DbScan {
894 commands: DbScanOpt::RestoreQuarantined { id },
895 } => {
896 info!("☣️ db scan - restore quarantined entry - {}", id);
897 dbscan_restore_quarantined_core(&config, *id);
898 }
899
900 KanidmdOpt::DomainSettings {
901 commands: DomainSettingsCmds::Change,
902 } => {
903 info!("Running in domain name change mode ... this may take a long time ...");
904 domain_rename_core(&config).await;
905 }
906
907 KanidmdOpt::DomainSettings {
908 commands: DomainSettingsCmds::Show,
909 } => {
910 info!("Running domain show ...");
911
912 submit_admin_req(
913 config.adminbindpath.as_str(),
914 AdminTaskRequest::DomainShow,
915 opt.kanidmd_options.output_mode,
916 )
917 .await;
918 }
919
920 KanidmdOpt::DomainSettings {
921 commands: DomainSettingsCmds::UpgradeCheck,
922 } => {
923 info!("Running domain upgrade check ...");
924
925 submit_admin_req(
926 config.adminbindpath.as_str(),
927 AdminTaskRequest::DomainUpgradeCheck,
928 opt.kanidmd_options.output_mode,
929 )
930 .await;
931 }
932
933 KanidmdOpt::DomainSettings {
934 commands: DomainSettingsCmds::Raise,
935 } => {
936 info!("Running domain raise ...");
937
938 submit_admin_req(
939 config.adminbindpath.as_str(),
940 AdminTaskRequest::DomainRaise,
941 opt.kanidmd_options.output_mode,
942 )
943 .await;
944 }
945
946 KanidmdOpt::DomainSettings {
947 commands: DomainSettingsCmds::Remigrate { level },
948 } => {
949 info!("⚠️ Running domain remigrate ...");
950
951 submit_admin_req(
952 config.adminbindpath.as_str(),
953 AdminTaskRequest::DomainRemigrate { level: *level },
954 opt.kanidmd_options.output_mode,
955 )
956 .await;
957 }
958
959 KanidmdOpt::Database {
960 commands: DbCommands::Vacuum,
961 } => {
962 info!("Running in vacuum mode ...");
963 vacuum_server_core(&config);
964 }
965 KanidmdOpt::HealthCheck(sopt) => {
966 debug!("{sopt:?}");
967
968 let healthcheck_url = match &sopt.check_origin {
969 true => format!("{}/status", config.origin),
970 false => {
971 format!(
973 "https://{}/status",
974 config.address[0].replace("[::]", "localhost")
975 )
976 }
977 };
978
979 info!("Checking {healthcheck_url}");
980
981 let mut client = reqwest::ClientBuilder::new()
982 .danger_accept_invalid_certs(!sopt.verify_tls)
983 .danger_accept_invalid_hostnames(!sopt.verify_tls)
984 .https_only(true);
985
986 client = match &config.tls_config {
987 None => client,
988 Some(tls_config) => {
989 debug!(
990 "Trying to load {} to build a CA cert path",
991 tls_config.chain.display()
992 );
993 let ca_cert_path = tls_config.chain.clone();
995 match ca_cert_path.exists() {
996 true => {
997 let mut cert_buf = Vec::new();
998 if let Err(err) = std::fs::File::open(&ca_cert_path)
999 .and_then(|mut file| file.read_to_end(&mut cert_buf))
1000 {
1001 error!(
1002 "Failed to read {:?} from filesystem: {:?}",
1003 ca_cert_path, err
1004 );
1005 return ExitCode::FAILURE;
1006 }
1007
1008 let ca_chain_parsed =
1009 match reqwest::Certificate::from_pem_bundle(&cert_buf) {
1010 Ok(val) => val,
1011 Err(e) => {
1012 error!(
1013 "Failed to parse {:?} into CA chain!\nError: {:?}",
1014 ca_cert_path, e
1015 );
1016 return ExitCode::FAILURE;
1017 }
1018 };
1019
1020 for cert in ca_chain_parsed.into_iter().skip(1) {
1022 client = client.add_root_certificate(cert)
1023 }
1024 client
1025 }
1026 false => {
1027 warn!(
1028 "Couldn't find ca cert {} but carrying on...",
1029 tls_config.chain.display()
1030 );
1031 client
1032 }
1033 }
1034 }
1035 };
1036 #[allow(clippy::unwrap_used)]
1037 let client = client.build().unwrap();
1038
1039 let req = match client.get(&healthcheck_url).send().await {
1040 Ok(val) => val,
1041 Err(error) => {
1042 let error_message = {
1043 if error.is_timeout() {
1044 format!("Timeout connecting to url={healthcheck_url}")
1045 } else if error.is_connect() {
1046 format!("Connection failed: {error}")
1047 } else {
1048 format!("Failed to complete healthcheck: {error:?}")
1049 }
1050 };
1051 error!("CRITICAL: {error_message}");
1052 return ExitCode::FAILURE;
1053 }
1054 };
1055 debug!("Request: {req:?}");
1056 match opt.kanidmd_options.output_mode {
1057 ConsoleOutputMode::JSON => {
1058 println!("{{\"result\":\"OK\"}}")
1059 }
1060 ConsoleOutputMode::Text => {
1061 info!("OK")
1062 }
1063 }
1064 }
1065 KanidmdOpt::Version => {}
1066 }
1067 ExitCode::SUCCESS
1068}