1#![deny(warnings)]
12#![warn(unused_extern_crates)]
13#![warn(unused_imports)]
14#![deny(clippy::todo)]
15#![deny(clippy::unimplemented)]
16#![deny(clippy::unwrap_used)]
17#![deny(clippy::expect_used)]
18#![deny(clippy::panic)]
19#![deny(clippy::unreachable)]
20#![deny(clippy::await_holding_lock)]
21#![deny(clippy::needless_pass_by_value)]
22#![deny(clippy::trivially_copy_pass_by_ref)]
23#![deny(clippy::indexing_slicing)]
24
25#[macro_use]
26extern crate tracing;
27#[macro_use]
28extern crate kanidmd_lib;
29
30mod actors;
31pub mod admin;
32pub mod config;
33mod crypto;
34mod https;
35mod interval;
36mod ldaps;
37mod repl;
38mod tcp;
39mod utils;
40
41use crate::actors::{QueryServerReadV1, QueryServerWriteV1};
42use crate::admin::AdminActor;
43use crate::config::{Configuration, ServerRole};
44use crate::interval::IntervalActor;
45use crate::repl::ReplicationServerHandles;
46use crate::utils::touch_file_or_quit;
47use crypto_glue::{
48 s256::{Sha256, Sha256Output},
49 traits::Digest,
50};
51use kanidm_proto::backup::BackupCompression;
52use kanidm_proto::internal::OperationError;
53use kanidm_proto::scim_v1::client::ScimAssertGeneric;
54use kanidmd_lib::be::{Backend, BackendConfig, BackendTransaction};
55use kanidmd_lib::idm::ldap::LdapServer;
56use kanidmd_lib::prelude::*;
57use kanidmd_lib::schema::Schema;
58use kanidmd_lib::status::StatusActor;
59use kanidmd_lib::value::CredentialType;
60use regex::Regex;
61use sketching::LoggerType;
62use std::collections::BTreeSet;
63use std::fmt::{Display, Formatter};
64use std::path::Path;
65use std::path::PathBuf;
66use std::sync::Arc;
67use std::sync::LazyLock;
68use tokio::sync::broadcast;
69use tokio::task;
70use tokio_rustls::TlsAcceptor;
71
72#[cfg(not(target_family = "windows"))]
73use libc::umask;
74
75fn setup_backend(config: &Configuration, schema: &Schema) -> Result<Backend, OperationError> {
78 setup_backend_vacuum(config, schema, false)
79}
80
81fn setup_backend_vacuum(
82 config: &Configuration,
83 schema: &Schema,
84 vacuum: bool,
85) -> Result<Backend, OperationError> {
86 let schema_txn = schema.write();
89 let idxmeta = schema_txn.reload_idxmeta();
90
91 let pool_size: u32 = config.threads as u32;
92
93 let cfg = BackendConfig::new(
94 config.db_path.as_deref(),
95 pool_size,
96 config.db_fs_type.unwrap_or_default(),
97 config.db_arc_size,
98 );
99
100 Backend::new(cfg, idxmeta, vacuum)
101}
102
103async fn setup_qs_idms(
108 be: Backend,
109 schema: Schema,
110 config: &Configuration,
111) -> Result<(QueryServer, IdmServer, IdmServerDelayed, IdmServerAudit), OperationError> {
112 let curtime = duration_from_epoch_now();
113 let query_server = QueryServer::new(be, schema, config.domain.clone(), curtime)?;
115
116 query_server
125 .initialise_helper(curtime, DOMAIN_TGT_LEVEL)
126 .await?;
127
128 let is_integration_test = config.integration_test_config.is_some();
130 let (idms, idms_delayed, idms_audit) = IdmServer::new(
131 query_server.clone(),
132 &config.origin,
133 is_integration_test,
134 curtime,
135 )
136 .await?;
137
138 Ok((query_server, idms, idms_delayed, idms_audit))
139}
140
141async fn setup_qs(
142 be: Backend,
143 schema: Schema,
144 config: &Configuration,
145) -> Result<QueryServer, OperationError> {
146 let curtime = duration_from_epoch_now();
147 let query_server = QueryServer::new(be, schema, config.domain.clone(), curtime)?;
149
150 query_server
159 .initialise_helper(curtime, DOMAIN_TGT_LEVEL)
160 .await?;
161
162 Ok(query_server)
163}
164
165macro_rules! dbscan_setup_be {
166 (
167 $config:expr
168 ) => {{
169 let schema = match Schema::new() {
170 Ok(s) => s,
171 Err(e) => {
172 error!("Failed to setup in memory schema: {:?}", e);
173 std::process::exit(1);
174 }
175 };
176
177 match setup_backend($config, &schema) {
178 Ok(be) => be,
179 Err(e) => {
180 error!("Failed to setup BE: {:?}", e);
181 return;
182 }
183 }
184 }};
185}
186
187pub fn dbscan_list_indexes_core(config: &Configuration) {
188 let be = dbscan_setup_be!(config);
189 let mut be_rotxn = match be.read() {
190 Ok(txn) => txn,
191 Err(err) => {
192 error!(?err, "Unable to proceed, backend read transaction failure.");
193 return;
194 }
195 };
196
197 match be_rotxn.list_indexes() {
198 Ok(mut idx_list) => {
199 idx_list.sort_unstable();
200 idx_list.iter().for_each(|idx_name| {
201 println!("{idx_name}");
202 })
203 }
204 Err(e) => {
205 error!("Failed to retrieve index list: {:?}", e);
206 }
207 };
208}
209
210pub fn dbscan_list_id2entry_core(config: &Configuration) {
211 let be = dbscan_setup_be!(config);
212 let mut be_rotxn = match be.read() {
213 Ok(txn) => txn,
214 Err(err) => {
215 error!(?err, "Unable to proceed, backend read transaction failure.");
216 return;
217 }
218 };
219
220 match be_rotxn.list_id2entry() {
221 Ok(mut id_list) => {
222 id_list.sort_unstable_by_key(|k| k.0);
223 id_list.iter().for_each(|(id, value)| {
224 println!("{id:>8}: {value}");
225 })
226 }
227 Err(e) => {
228 error!("Failed to retrieve id2entry list: {:?}", e);
229 }
230 };
231}
232
233pub fn dbscan_list_index_analysis_core(config: &Configuration) {
234 let _be = dbscan_setup_be!(config);
235 }
237
238pub fn dbscan_list_index_core(config: &Configuration, index_name: &str) {
239 let be = dbscan_setup_be!(config);
240 let mut be_rotxn = match be.read() {
241 Ok(txn) => txn,
242 Err(err) => {
243 error!(?err, "Unable to proceed, backend read transaction failure.");
244 return;
245 }
246 };
247
248 match be_rotxn.list_index_content(index_name) {
249 Ok(mut idx_list) => {
250 idx_list.sort_unstable_by(|a, b| a.0.cmp(&b.0));
251 idx_list.iter().for_each(|(key, value)| {
252 println!("{key:>50}: {value:?}");
253 })
254 }
255 Err(e) => {
256 error!("Failed to retrieve index list: {:?}", e);
257 }
258 };
259}
260
261pub fn dbscan_get_id2entry_core(config: &Configuration, id: u64) {
262 let be = dbscan_setup_be!(config);
263 let mut be_rotxn = match be.read() {
264 Ok(txn) => txn,
265 Err(err) => {
266 error!(?err, "Unable to proceed, backend read transaction failure.");
267 return;
268 }
269 };
270
271 match be_rotxn.get_id2entry(id) {
272 Ok((id, value)) => println!("{id:>8}: {value}"),
273 Err(e) => {
274 error!("Failed to retrieve id2entry value: {:?}", e);
275 }
276 };
277}
278
279pub fn dbscan_quarantine_id2entry_core(config: &Configuration, id: u64) {
280 let be = dbscan_setup_be!(config);
281 let mut be_wrtxn = match be.write() {
282 Ok(txn) => txn,
283 Err(err) => {
284 error!(
285 ?err,
286 "Unable to proceed, backend write transaction failure."
287 );
288 return;
289 }
290 };
291
292 match be_wrtxn
293 .quarantine_entry(id)
294 .and_then(|_| be_wrtxn.commit())
295 {
296 Ok(()) => {
297 println!("quarantined - {id:>8}")
298 }
299 Err(e) => {
300 error!("Failed to quarantine id2entry value: {:?}", e);
301 }
302 };
303}
304
305pub fn dbscan_list_quarantined_core(config: &Configuration) {
306 let be = dbscan_setup_be!(config);
307 let mut be_rotxn = match be.read() {
308 Ok(txn) => txn,
309 Err(err) => {
310 error!(?err, "Unable to proceed, backend read transaction failure.");
311 return;
312 }
313 };
314
315 match be_rotxn.list_quarantined() {
316 Ok(mut id_list) => {
317 id_list.sort_unstable_by_key(|k| k.0);
318 id_list.iter().for_each(|(id, value)| {
319 println!("{id:>8}: {value}");
320 })
321 }
322 Err(e) => {
323 error!("Failed to retrieve id2entry list: {:?}", e);
324 }
325 };
326}
327
328pub fn dbscan_restore_quarantined_core(config: &Configuration, id: u64) {
329 let be = dbscan_setup_be!(config);
330 let mut be_wrtxn = match be.write() {
331 Ok(txn) => txn,
332 Err(err) => {
333 error!(
334 ?err,
335 "Unable to proceed, backend write transaction failure."
336 );
337 return;
338 }
339 };
340
341 match be_wrtxn
342 .restore_quarantined(id)
343 .and_then(|_| be_wrtxn.commit())
344 {
345 Ok(()) => {
346 println!("restored - {id:>8}")
347 }
348 Err(e) => {
349 error!("Failed to restore quarantined id2entry value: {:?}", e);
350 }
351 };
352}
353
354pub fn backup_server_core(config: &Configuration, dst_path: Option<&Path>) {
355 let schema = match Schema::new() {
356 Ok(s) => s,
357 Err(e) => {
358 error!("Failed to setup in memory schema: {:?}", e);
359 std::process::exit(1);
360 }
361 };
362
363 let be = match setup_backend(config, &schema) {
364 Ok(be) => be,
365 Err(e) => {
366 error!("Failed to setup BE: {:?}", e);
367 return;
368 }
369 };
370
371 let mut be_ro_txn = match be.read() {
372 Ok(txn) => txn,
373 Err(err) => {
374 error!(?err, "Unable to proceed, backend read transaction failure.");
375 return;
376 }
377 };
378
379 let compression = match config.online_backup.as_ref() {
380 Some(backup_config) => backup_config.compression,
381 None => BackupCompression::default(),
382 };
383
384 if let Some(dst_path) = dst_path {
385 if dst_path.exists() {
386 error!(
387 "backup file {} already exists, will not overwrite it.",
388 dst_path.display()
389 );
390 return;
391 }
392
393 let output = match std::fs::File::create(dst_path) {
394 Ok(output) => output,
395 Err(err) => {
396 error!(?err, "File::create error creating {}", dst_path.display());
397 return;
398 }
399 };
400
401 match be_ro_txn.backup(output, compression) {
402 Ok(_) => info!("Backup success!"),
403 Err(e) => {
404 error!("Backup failed: {:?}", e);
405 std::process::exit(1);
406 }
407 };
408 } else {
409 let stdout = std::io::stdout().lock();
411
412 match be_ro_txn.backup(stdout, compression) {
413 Ok(_) => info!("Backup success!"),
414 Err(e) => {
415 error!("Backup failed: {:?}", e);
416 std::process::exit(1);
417 }
418 };
419 };
420 }
422
423pub async fn restore_server_core(config: &Configuration, dst_path: &Path) {
424 if let Some(db_path) = config.db_path.as_ref() {
426 touch_file_or_quit(db_path);
427 }
428
429 let schema = match Schema::new() {
431 Ok(s) => s,
432 Err(e) => {
433 error!("Failed to setup in memory schema: {:?}", e);
434 std::process::exit(1);
435 }
436 };
437
438 let be = match setup_backend(config, &schema) {
439 Ok(be) => be,
440 Err(e) => {
441 error!("Failed to setup backend: {:?}", e);
442 return;
443 }
444 };
445
446 let mut be_wr_txn = match be.write() {
447 Ok(txn) => txn,
448 Err(err) => {
449 error!(
450 ?err,
451 "Unable to proceed, backend write transaction failure."
452 );
453 return;
454 }
455 };
456
457 let compression = BackupCompression::identify_file(dst_path);
458
459 let input = match std::fs::File::open(dst_path) {
460 Ok(output) => output,
461 Err(err) => {
462 error!(?err, "File::open error reading {}", dst_path.display());
463 return;
464 }
465 };
466
467 let r = be_wr_txn
468 .restore(input, compression)
469 .and_then(|_| be_wr_txn.commit());
470
471 if r.is_err() {
472 error!("Failed to restore database: {:?}", r);
473 std::process::exit(1);
474 }
475 info!("Database loaded successfully");
476
477 reindex_inner(be, schema, config).await;
478
479 info!("✅ Restore Success!");
480}
481
482pub async fn reindex_server_core(config: &Configuration) {
483 info!("Start Index Phase 1 ...");
484 let schema = match Schema::new() {
486 Ok(s) => s,
487 Err(e) => {
488 error!("Failed to setup in memory schema: {:?}", e);
489 std::process::exit(1);
490 }
491 };
492
493 let be = match setup_backend(config, &schema) {
494 Ok(be) => be,
495 Err(e) => {
496 error!("Failed to setup BE: {:?}", e);
497 return;
498 }
499 };
500
501 reindex_inner(be, schema, config).await;
502
503 info!("✅ Reindex Success!");
504}
505
506async fn reindex_inner(be: Backend, schema: Schema, config: &Configuration) {
507 let mut be_wr_txn = match be.write() {
509 Ok(txn) => txn,
510 Err(err) => {
511 error!(
512 ?err,
513 "Unable to proceed, backend write transaction failure."
514 );
515 return;
516 }
517 };
518
519 let r = be_wr_txn.reindex(true).and_then(|_| be_wr_txn.commit());
520
521 if r.is_err() {
523 error!("Failed to reindex database: {:?}", r);
524 std::process::exit(1);
525 }
526 info!("Index Phase 1 Success!");
527
528 info!("Attempting to init query server ...");
529
530 let (qs, _idms, _idms_delayed, _idms_audit) = match setup_qs_idms(be, schema, config).await {
531 Ok(t) => t,
532 Err(e) => {
533 error!("Unable to setup query server or idm server -> {:?}", e);
534 return;
535 }
536 };
537 info!("Init Query Server Success!");
538
539 info!("Start Index Phase 2 ...");
540
541 let Ok(mut qs_write) = qs.write(duration_from_epoch_now()).await else {
542 error!("Unable to acquire write transaction");
543 return;
544 };
545 let r = qs_write.reindex(true).and_then(|_| qs_write.commit());
546
547 match r {
548 Ok(_) => info!("Index Phase 2 Success!"),
549 Err(e) => {
550 error!("Reindex failed: {:?}", e);
551 std::process::exit(1);
552 }
553 };
554}
555
556pub fn vacuum_server_core(config: &Configuration) {
557 let schema = match Schema::new() {
558 Ok(s) => s,
559 Err(e) => {
560 eprintln!("Failed to setup in memory schema: {e:?}");
561 std::process::exit(1);
562 }
563 };
564
565 let r = setup_backend_vacuum(config, &schema, true);
568
569 match r {
570 Ok(_) => eprintln!("Vacuum Success!"),
571 Err(e) => {
572 eprintln!("Vacuum failed: {e:?}");
573 std::process::exit(1);
574 }
575 };
576}
577
578pub async fn domain_rename_core(config: &Configuration) {
579 let schema = match Schema::new() {
580 Ok(s) => s,
581 Err(e) => {
582 eprintln!("Failed to setup in memory schema: {e:?}");
583 std::process::exit(1);
584 }
585 };
586
587 let be = match setup_backend(config, &schema) {
589 Ok(be) => be,
590 Err(e) => {
591 error!("Failed to setup BE: {:?}", e);
592 return;
593 }
594 };
595
596 let qs = match setup_qs(be, schema, config).await {
598 Ok(t) => t,
599 Err(e) => {
600 error!("Unable to setup query server -> {:?}", e);
601 return;
602 }
603 };
604
605 let new_domain_name = config.domain.as_str();
606
607 match qs.read().await.map(|qs| qs.get_domain_name().to_string()) {
609 Ok(old_domain_name) => {
610 admin_info!(?old_domain_name, ?new_domain_name);
611 if old_domain_name == new_domain_name {
612 admin_info!("Domain name not changing, stopping.");
613 return;
614 }
615 admin_debug!(
616 "Domain name is changing from {:?} to {:?}",
617 old_domain_name,
618 new_domain_name
619 );
620 }
621 Err(e) => {
622 admin_error!("Failed to query domain name, quitting! -> {:?}", e);
623 return;
624 }
625 }
626
627 let Ok(mut qs_write) = qs.write(duration_from_epoch_now()).await else {
628 error!("Unable to acquire write transaction");
629 return;
630 };
631 let r = qs_write
632 .danger_domain_rename(new_domain_name)
633 .and_then(|_| qs_write.commit());
634
635 match r {
636 Ok(_) => info!("Domain Rename Success!"),
637 Err(e) => {
638 error!("Domain Rename Failed - Rollback has occurred: {:?}", e);
639 std::process::exit(1);
640 }
641 };
642}
643
644pub async fn verify_server_core(config: &Configuration) {
645 let curtime = duration_from_epoch_now();
646 let schema_mem = match Schema::new() {
648 Ok(sc) => sc,
649 Err(e) => {
650 error!("Failed to setup in memory schema: {:?}", e);
651 return;
652 }
653 };
654 let be = match setup_backend(config, &schema_mem) {
656 Ok(be) => be,
657 Err(e) => {
658 error!("Failed to setup BE: {:?}", e);
659 return;
660 }
661 };
662
663 let server = match QueryServer::new(be, schema_mem, config.domain.clone(), curtime) {
664 Ok(qs) => qs,
665 Err(err) => {
666 error!(?err, "Failed to setup query server");
667 return;
668 }
669 };
670
671 let r = server.verify().await;
673
674 if r.is_empty() {
675 eprintln!("Verification passed!");
676 std::process::exit(0);
677 } else {
678 for er in r {
679 error!("{:?}", er);
680 }
681 std::process::exit(1);
682 }
683
684 }
686
687pub fn cert_generate_core(config: &Configuration) {
688 let (tls_key_path, tls_chain_path) = match &config.tls_config {
691 Some(tls_config) => (tls_config.key.as_path(), tls_config.chain.as_path()),
692 None => {
693 error!("Unable to find TLS configuration");
694 std::process::exit(1);
695 }
696 };
697
698 if tls_key_path.exists() && tls_chain_path.exists() {
699 info!(
700 "TLS key and chain already exist - remove them first if you intend to regenerate these"
701 );
702 return;
703 }
704
705 let origin_domain = match config.origin.domain() {
706 Some(val) => val,
707 None => {
708 error!("origin does not contain a valid domain");
709 std::process::exit(1);
710 }
711 };
712
713 let cert_root = match tls_key_path.parent() {
714 Some(parent) => parent,
715 None => {
716 error!("Unable to find parent directory of {:?}", tls_key_path);
717 std::process::exit(1);
718 }
719 };
720
721 let ca_cert = cert_root.join("ca.pem");
722 let ca_key = cert_root.join("cakey.pem");
723 let tls_cert_path = cert_root.join("cert.pem");
724
725 let ca_handle = if !ca_cert.exists() || !ca_key.exists() {
726 let ca_handle = match crypto::build_ca() {
728 Ok(ca_handle) => ca_handle,
729 Err(e) => {
730 error!(err = ?e, "Failed to build CA");
731 std::process::exit(1);
732 }
733 };
734
735 if crypto::write_ca(ca_key, ca_cert, &ca_handle).is_err() {
736 error!("Failed to write CA");
737 std::process::exit(1);
738 }
739
740 ca_handle
741 } else {
742 match crypto::load_ca(ca_key, ca_cert) {
743 Ok(ca_handle) => ca_handle,
744 Err(_) => {
745 error!("Failed to load CA");
746 std::process::exit(1);
747 }
748 }
749 };
750
751 if !tls_key_path.exists() || !tls_chain_path.exists() || !tls_cert_path.exists() {
752 let cert_handle = match crypto::build_cert(origin_domain, &ca_handle) {
754 Ok(cert_handle) => cert_handle,
755 Err(e) => {
756 error!(err = ?e, "Failed to build certificate");
757 std::process::exit(1);
758 }
759 };
760
761 if crypto::write_cert(tls_key_path, tls_chain_path, tls_cert_path, &cert_handle).is_err() {
762 error!("Failed to write certificates");
763 std::process::exit(1);
764 }
765 }
766 info!("certificate generation complete");
767}
768
769static MIGRATION_PATH_RE: LazyLock<Regex> = LazyLock::new(|| {
770 #[allow(clippy::expect_used)]
771 Regex::new("^\\d\\d-.*\\.h?json$").expect("Invalid SPN regex found")
772});
773
774struct ScimMigration {
775 path: PathBuf,
776 hash: Sha256Output,
777 assertions: ScimAssertGeneric,
778}
779
780async fn migration_reload_supervisor(
781 mut broadcast_rx: broadcast::Receiver<CoreAction>,
782 server_write_ref: &'static QueryServerWriteV1,
783 migration_path: PathBuf,
784) {
785 loop {
786 tokio::select! {
787 Ok(action) = broadcast_rx.recv() => {
788 match action {
789 CoreAction::Shutdown => break,
790 CoreAction::Reload => {
791 let eventid = Uuid::new_v4();
794 migration_apply(
795 eventid,
796 server_write_ref,
797 migration_path.as_path(),
798 ).await;
799
800 info!("Migration reload complete");
801 },
802 }
803 }
804 }
805 }
806 info!("Stopped {}", TaskName::MigrationReload);
807}
808
809#[instrument(
810 level = "info",
811 fields(uuid = ?eventid),
812 skip_all,
813)]
814async fn migration_apply(
815 eventid: Uuid,
816 server_write_ref: &'static QueryServerWriteV1,
817 migration_path: &Path,
818) {
819 if !migration_path.exists() {
820 info!(migration_path = %migration_path.display(), "Migration path does not exist - migrations will be skipped.");
821 return;
822 }
823
824 let mut dir_ents = match tokio::fs::read_dir(migration_path).await {
825 Ok(dir_ents) => dir_ents,
826 Err(err) => {
827 error!(?err, "Unable to read migration directory.");
828 let diag = kanidm_lib_file_permissions::diagnose_path(migration_path);
829 info!(%diag);
830 return;
831 }
832 };
833
834 let mut migration_paths = Vec::with_capacity(8);
835
836 loop {
837 match dir_ents.next_entry().await {
838 Ok(Some(dir_ent)) => migration_paths.push(dir_ent.path()),
839 Ok(None) => {
840 break;
842 }
843 Err(err) => {
844 error!(?err, "Unable to read directory entries.");
845 return;
846 }
847 }
848 }
849
850 let mut migration_paths: Vec<_> = migration_paths.into_iter()
853 .filter(|path| {
854 if !path.is_file() {
855 info!(path = %path.display(), "ignoring path that is not a file.");
856 return false;
857 }
858
859 let Some(file_name) = path.file_name().and_then(std::ffi::OsStr::to_str) else {
860 info!(path = %path.display(), "ignoring path that has no file name, or is not a valid utf-8 file name.");
861 return false;
862 };
863
864 if !MIGRATION_PATH_RE.is_match(file_name) {
865 info!(path = %path.display(), "ignoring file that does not match naming pattern.");
866 info!("expected pattern 'XX-NAME.json' where XX are two numbers, followed by a hypen, with the file extension .json");
867 return false;
868 }
869
870 true
871 })
872 .collect();
873
874 migration_paths.sort_unstable();
875 let mut migrations = Vec::with_capacity(migration_paths.len());
876
877 for migration_path in migration_paths {
878 info!(path = %migration_path.display(), "examining migration");
879
880 let migration_content = match tokio::fs::read(&migration_path).await {
881 Ok(bytes) => bytes,
882 Err(err) => {
883 error!(?err, "Unable to read migration - it will be ignored.");
884 let diag = kanidm_lib_file_permissions::diagnose_path(&migration_path);
885 info!(%diag);
886 continue;
887 }
888 };
889
890 let assertions: ScimAssertGeneric = match serde_hjson::from_slice(&migration_content) {
892 Ok(assertions) => assertions,
893 Err(err) => {
894 error!(?err, path = %migration_path.display(), "Invalid JSON SCIM Assertion");
895 continue;
896 }
897 };
898
899 let mut hasher = Sha256::new();
901 hasher.update(&migration_content);
902 let migration_hash: Sha256Output = hasher.finalize();
903
904 migrations.push(ScimMigration {
905 path: migration_path,
906 hash: migration_hash,
907 assertions,
908 });
909 }
910
911 let mut migration_ids = BTreeSet::new();
912 for migration in &migrations {
913 if !migration_ids.insert(migration.assertions.id) {
915 error!(path = %migration.path.display(), uuid = ?migration.assertions.id, "Duplicate migration UUID found, refusing to proceed!!! All migrations must have a unique ID!!!");
916 return;
917 }
918 }
919
920 for ScimMigration {
923 path,
924 hash,
925 assertions,
926 } in migrations
927 {
928 if let Err(err) = server_write_ref
929 .handle_scim_migration_apply(eventid, assertions, hash)
930 .await
931 {
932 error!(?err, path = %path.display(), "Failed to apply migration");
933 };
934 }
935}
936
937#[derive(Clone, Debug)]
938pub enum CoreAction {
939 Shutdown,
940 Reload,
941}
942
943pub(crate) enum TaskName {
944 AdminSocket,
945 AuditdActor,
946 BackupActor,
947 DelayedActionActor,
948 HttpsServer,
949 IntervalActor,
950 LdapActor,
951 ReplicationSupervisor,
952 TlsAcceptorReload,
953 MigrationReload,
954}
955
956impl Display for TaskName {
957 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
958 write!(
959 f,
960 "{}",
961 match self {
962 TaskName::AdminSocket => "Admin Socket",
963 TaskName::AuditdActor => "Auditd Actor",
964 TaskName::BackupActor => "Backup Actor",
965 TaskName::DelayedActionActor => "Delayed Action Actor",
966 TaskName::HttpsServer => "HTTPS Server",
967 TaskName::IntervalActor => "Interval Actor",
968 TaskName::LdapActor => "LDAP Acceptor Actor",
969 TaskName::ReplicationSupervisor => "Replication Supervisor",
970 TaskName::TlsAcceptorReload => "TlsAcceptor Reload Monitor",
971 TaskName::MigrationReload => "Migration Reload Monitor",
972 }
973 )
974 }
975}
976
977pub struct CoreHandle {
978 clean_shutdown: bool,
979 tx: broadcast::Sender<CoreAction>,
980 handles: Vec<(TaskName, task::JoinHandle<()>)>,
982}
983
984impl CoreHandle {
985 pub fn subscribe(&mut self) -> broadcast::Receiver<CoreAction> {
986 self.tx.subscribe()
987 }
988
989 pub async fn shutdown(&mut self) {
990 if self.tx.send(CoreAction::Shutdown).is_err() {
991 eprintln!("No receivers acked shutdown request. Treating as unclean.");
992 return;
993 }
994
995 while let Some((handle_name, handle)) = self.handles.pop() {
997 debug!("Waiting for {handle_name} ...");
998 if let Err(error) = handle.await {
999 eprintln!("Task {handle_name} failed to finish: {error:?}");
1000 }
1001 }
1002
1003 self.clean_shutdown = true;
1004 }
1005
1006 pub async fn reload(&mut self) {
1007 if self.tx.send(CoreAction::Reload).is_err() {
1008 eprintln!("No receivers acked reload request.");
1009 }
1010 }
1011}
1012
1013impl Drop for CoreHandle {
1014 fn drop(&mut self) {
1015 if !self.clean_shutdown {
1016 eprintln!("⚠️ UNCLEAN SHUTDOWN OCCURRED ⚠️ ");
1017 }
1018 }
1021}
1022
1023pub async fn create_server_core(
1024 config: Configuration,
1025 config_test: bool,
1026) -> Result<CoreHandle, ()> {
1027 let (mut broadcast_tx, _broadcast_rx) = broadcast::channel(4);
1029
1030 if config.integration_test_config.is_some() {
1031 warn!("RUNNING IN INTEGRATION TEST MODE.");
1032 warn!("IF YOU SEE THIS IN PRODUCTION YOU MUST CONTACT SUPPORT IMMEDIATELY.");
1033 } else if config.tls_config.is_none() {
1034 error!("Running without TLS is not supported! Quitting!");
1036 return Err(());
1037 }
1038
1039 info!(
1040 "Starting kanidm with {}configuration: {}",
1041 if config_test { "TEST " } else { "" },
1042 config
1043 );
1044 #[cfg(not(target_family = "windows"))]
1046 unsafe {
1047 umask(0o0027)
1048 };
1049
1050 let maybe_tls_acceptor = match crypto::setup_tls(&config.tls_config) {
1052 Ok(tls_acc) => tls_acc,
1053 Err(err) => {
1054 error!(?err, "Failed to configure TLS acceptor");
1055 return Err(());
1056 }
1057 };
1058
1059 let schema = match Schema::new() {
1060 Ok(s) => s,
1061 Err(e) => {
1062 error!("Failed to setup in memory schema: {:?}", e);
1063 return Err(());
1064 }
1065 };
1066
1067 let be = match setup_backend(&config, &schema) {
1069 Ok(be) => be,
1070 Err(e) => {
1071 error!("Failed to setup BE -> {:?}", e);
1072 return Err(());
1073 }
1074 };
1075 let (_qs, idms, idms_delayed, idms_audit) = match setup_qs_idms(be, schema, &config).await {
1077 Ok(t) => t,
1078 Err(e) => {
1079 error!("Unable to setup query server or idm server -> {:?}", e);
1080 return Err(());
1081 }
1082 };
1083
1084 if let Some(itc) = &config.integration_test_config {
1086 let Ok(mut idms_prox_write) = idms.proxy_write(duration_from_epoch_now()).await else {
1087 error!("Unable to acquire write transaction");
1088 return Err(());
1089 };
1090 match idms_prox_write.recover_account(&itc.admin_user, Some(&itc.admin_password)) {
1092 Ok(_) => {}
1093 Err(e) => {
1094 error!(
1095 "Unable to configure INTEGRATION TEST {} account -> {:?}",
1096 &itc.admin_user, e
1097 );
1098 return Err(());
1099 }
1100 };
1101 match idms_prox_write.recover_account(&itc.idm_admin_user, Some(&itc.idm_admin_password)) {
1103 Ok(_) => {}
1104 Err(e) => {
1105 error!(
1106 "Unable to configure INTEGRATION TEST {} account -> {:?}",
1107 &itc.idm_admin_user, e
1108 );
1109 return Err(());
1110 }
1111 };
1112
1113 match idms_prox_write.qs_write.internal_modify_uuid(
1117 UUID_IDM_ADMINS,
1118 &ModifyList::new_append(Attribute::Member, Value::Refer(UUID_ADMIN)),
1119 ) {
1120 Ok(_) => {}
1121 Err(e) => {
1122 error!(
1123 "Unable to configure INTEGRATION TEST admin as member of idm_admins -> {:?}",
1124 e
1125 );
1126 return Err(());
1127 }
1128 };
1129
1130 match idms_prox_write.qs_write.internal_modify_uuid(
1131 UUID_IDM_ALL_PERSONS,
1132 &ModifyList::new_purge_and_set(
1133 Attribute::CredentialTypeMinimum,
1134 CredentialType::Any.into(),
1135 ),
1136 ) {
1137 Ok(_) => {}
1138 Err(e) => {
1139 error!(
1140 "Unable to configure INTEGRATION TEST default credential policy -> {:?}",
1141 e
1142 );
1143 return Err(());
1144 }
1145 };
1146
1147 match idms_prox_write.commit() {
1148 Ok(_) => {}
1149 Err(e) => {
1150 error!("Unable to commit INTEGRATION TEST setup -> {:?}", e);
1151 return Err(());
1152 }
1153 }
1154 }
1155
1156 let ldap = match LdapServer::new(&idms).await {
1157 Ok(l) => l,
1158 Err(e) => {
1159 error!("Unable to start LdapServer -> {:?}", e);
1160 return Err(());
1161 }
1162 };
1163
1164 let idms_arc = Arc::new(idms);
1166 let ldap_arc = Arc::new(ldap);
1167
1168 let server_read_ref = QueryServerReadV1::start_static(idms_arc.clone(), ldap_arc.clone());
1171
1172 let server_write_ref = QueryServerWriteV1::start_static(idms_arc.clone());
1174
1175 let mut handles: Vec<(TaskName, task::JoinHandle<()>)> = Vec::with_capacity(16);
1176
1177 let startup_success = if config_test {
1178 info!("This config rocks! 🪨 ");
1179 Ok(())
1180 } else {
1181 launch_server_tasks(
1182 &mut handles,
1183 &config,
1184 &mut broadcast_tx,
1185 idms_delayed,
1186 idms_audit,
1187 server_read_ref,
1188 server_write_ref,
1189 idms_arc,
1190 maybe_tls_acceptor,
1191 )
1192 .await
1193 };
1194
1195 let mut server_ctx = CoreHandle {
1196 clean_shutdown: false,
1197 tx: broadcast_tx,
1198 handles,
1199 };
1200
1201 if startup_success.is_ok() {
1202 Ok(server_ctx)
1203 } else {
1204 server_ctx.shutdown().await;
1205 Err(())
1206 }
1207}
1208
1209#[allow(clippy::too_many_arguments)]
1210async fn launch_server_tasks(
1211 handles: &mut Vec<(TaskName, task::JoinHandle<()>)>,
1212
1213 config: &Configuration,
1214 broadcast_tx: &mut broadcast::Sender<CoreAction>,
1215
1216 mut idms_delayed: IdmServerDelayed,
1217 mut idms_audit: IdmServerAudit,
1218
1219 server_read_ref: &'static QueryServerReadV1,
1220 server_write_ref: &'static QueryServerWriteV1,
1221
1222 idms_arc: Arc<IdmServer>,
1223
1224 maybe_tls_acceptor: Option<TlsAcceptor>,
1225) -> Result<(), ()> {
1226 let status_ref = StatusActor::start();
1229
1230 let mut broadcast_rx = broadcast_tx.subscribe();
1232
1233 let delayed_handle = task::spawn(async move {
1234 let mut buffer = Vec::with_capacity(DELAYED_ACTION_BATCH_SIZE);
1235 loop {
1236 tokio::select! {
1237 added = idms_delayed.recv_many(&mut buffer) => {
1238 if added == 0 {
1239 break
1241 }
1242 server_write_ref.handle_delayedaction(&mut buffer).await;
1243 }
1244 Ok(action) = broadcast_rx.recv() => {
1245 match action {
1246 CoreAction::Shutdown => break,
1247 CoreAction::Reload => {},
1248 }
1249 }
1250 }
1251 }
1252 info!("Stopped {}", TaskName::DelayedActionActor);
1253 });
1254
1255 handles.push((TaskName::DelayedActionActor, delayed_handle));
1256
1257 let mut broadcast_rx = broadcast_tx.subscribe();
1259
1260 let auditd_handle = task::spawn(async move {
1261 loop {
1262 tokio::select! {
1263 Ok(action) = broadcast_rx.recv() => {
1264 match action {
1265 CoreAction::Shutdown => break,
1266 CoreAction::Reload => {},
1267 }
1268 }
1269 audit_event = idms_audit.audit_rx().recv() => {
1270 match serde_json::to_string(&audit_event) {
1271 Ok(audit_event) => {
1272 warn!(%audit_event);
1273 }
1274 Err(e) => {
1275 error!(err=?e, "Unable to process audit event to json.");
1276 warn!(?audit_event, json=false);
1277 }
1278 }
1279
1280 }
1281 }
1282 }
1283 info!("Stopped {}", TaskName::AuditdActor);
1284 });
1285
1286 handles.push((TaskName::AuditdActor, auditd_handle));
1287
1288 let migration_path = config
1290 .migration_path
1291 .clone()
1292 .unwrap_or(PathBuf::from(env!("KANIDM_SERVER_MIGRATION_PATH")));
1293
1294 if config.integration_test_config.is_none() {
1295 let eventid = Uuid::new_v4();
1296 migration_apply(eventid, server_write_ref, migration_path.as_path()).await;
1297 let broadcast_rx = broadcast_tx.subscribe();
1301 let migration_reload_handle = task::spawn(async move {
1302 migration_reload_supervisor(broadcast_rx, server_write_ref, migration_path).await
1303 });
1304
1305 handles.push((TaskName::MigrationReload, migration_reload_handle));
1306
1307 let interval_handle = IntervalActor::start(server_write_ref, broadcast_tx.subscribe());
1309
1310 handles.push((TaskName::IntervalActor, interval_handle));
1311
1312 match &config.online_backup {
1314 Some(online_backup_config) => {
1315 if online_backup_config.enabled {
1316 let backup_handle = IntervalActor::start_online_backup(
1317 server_read_ref,
1318 online_backup_config,
1319 broadcast_tx.subscribe(),
1320 )?;
1321 handles.push((TaskName::BackupActor, backup_handle));
1322 } else {
1323 debug!("Backups disabled");
1324 }
1325 }
1326 None => {
1327 debug!("Online backup not configured, skipping");
1328 }
1329 };
1330
1331 let maybe_repl_ctrl_tx = match &config.repl_config {
1334 Some(rc) => {
1335 let repl_server_handles =
1337 repl::create_repl_server(idms_arc.clone(), rc, broadcast_tx.subscribe())
1338 .await?;
1339
1340 let ReplicationServerHandles {
1341 repl_handle,
1342 ctrl_tx,
1343 } = repl_server_handles;
1344
1345 handles.push((TaskName::ReplicationSupervisor, repl_handle));
1346
1347 Some(ctrl_tx)
1348 }
1349 None => {
1350 debug!("Replication not configured, skipping");
1351 None
1352 }
1353 };
1354
1355 let broadcast_tx_ = broadcast_tx.clone();
1356
1357 let admin_handle = AdminActor::create_admin_sock(
1358 config.adminbindpath.as_str(),
1359 server_write_ref,
1360 server_read_ref,
1361 broadcast_tx_,
1362 maybe_repl_ctrl_tx,
1363 )
1364 .await?;
1365
1366 handles.push((TaskName::AdminSocket, admin_handle));
1367 }
1368
1369 let mut broadcast_rx = broadcast_tx.subscribe();
1372 let tls_config = config.tls_config.clone();
1373
1374 let (tls_acceptor_reload_tx, _tls_acceptor_reload_rx) = broadcast::channel(1);
1375 let tls_acceptor_reload_tx_c = tls_acceptor_reload_tx.clone();
1376
1377 let tls_acceptor_reload_handle = task::spawn(async move {
1378 loop {
1379 tokio::select! {
1380 Ok(action) = broadcast_rx.recv() => {
1381 match action {
1382 CoreAction::Shutdown => break,
1383 CoreAction::Reload => {
1384 let tls_acceptor = match crypto::setup_tls(&tls_config) {
1385 Ok(Some(tls_acc)) => tls_acc,
1386 Ok(None) => {
1387 warn!("TLS not configured, ignoring reload request.");
1388 continue;
1389 }
1390 Err(err) => {
1391 error!(?err, "Failed to configure and reload TLS acceptor");
1392 continue;
1393 }
1394 };
1395
1396 if tls_acceptor_reload_tx_c.send(tls_acceptor).is_err() {
1399 error!("TLS acceptor did not accept the reload, the server may have failed!");
1400 };
1401 info!("TLS acceptor reload notification sent");
1402 },
1403 }
1404 }
1405 }
1406 }
1407 info!("Stopped {}", TaskName::TlsAcceptorReload);
1408 });
1409
1410 handles.push((TaskName::TlsAcceptorReload, tls_acceptor_reload_handle));
1411
1412 match &config.ldapbindaddress {
1414 Some(la) => {
1415 let logging_pipeline = match config.otel_grpc_endpoint {
1416 Some(_) => LoggerType::OpenTelemetry,
1417 None => LoggerType::TracingForest,
1418 };
1419 let opt_ldap_ssl_acceptor = maybe_tls_acceptor.clone();
1420
1421 let ldap_handles = ldaps::create_ldap_server(
1422 la,
1423 opt_ldap_ssl_acceptor,
1424 server_read_ref,
1425 broadcast_tx,
1426 &tls_acceptor_reload_tx,
1427 config.ldap_client_address_info.trusted_tcp_info(),
1428 logging_pipeline,
1429 )
1430 .await?;
1431 for ldap_handle in ldap_handles {
1432 handles.push((TaskName::LdapActor, ldap_handle));
1433 }
1434 }
1435 None => {
1436 debug!("LDAP not requested, skipping");
1437 }
1438 };
1439
1440 let http_handles: Vec<task::JoinHandle<()>> = https::create_https_server(
1442 config.clone(),
1443 status_ref,
1444 server_write_ref,
1445 server_read_ref,
1446 broadcast_tx.clone(),
1447 maybe_tls_acceptor,
1448 &tls_acceptor_reload_tx,
1449 )
1450 .await
1451 .inspect_err(|err| {
1452 error!(?err, "Failed to start HTTPS server");
1453 })?;
1454
1455 if config.role != ServerRole::WriteReplicaNoUI {
1456 admin_info!("Ready to rock! 🪨 UI available at: {}", config.origin);
1457 } else {
1458 admin_info!("Ready to rock! 🪨 ");
1459 }
1460
1461 for http_handle in http_handles {
1462 handles.push((TaskName::HttpsServer, http_handle))
1463 }
1464
1465 Ok(())
1466}