Skip to main content

kanidmd_core/
lib.rs

1//! These contain the server "cores". These are able to startup the server
2//! (bootstrap) to a running state and then execute tasks. This is where modules
3//! are logically ordered based on their depenedncies for execution. Some of these
4//! are task-only i.e. reindexing, and some of these launch the server into a
5//! fully operational state (https, ldap, etc).
6//!
7//! Generally, this is the "entry point" where the server begins to run, and
8//! the entry point for all client traffic which is then directed to the
9//! various `actors`.
10
11#![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
75// === internal setup helpers
76
77fn 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    // Limit the scope of the schema txn.
87    // let schema_txn = task::block_on(schema.write());
88    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
103// TODO #54: We could move most of the be/schema/qs setup and startup
104// outside of this call, then pass in "what we need" in a cloneable
105// form, this way we could have separate Idm vs Qs threads, and dedicated
106// threads for write vs read
107async 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    // Create a query_server implementation
114    let query_server = QueryServer::new(be, schema, config.domain.clone(), curtime)?;
115
116    // TODO #62: Should the IDM parts be broken out to the IdmServer?
117    // What's important about this initial setup here is that it also triggers
118    // the schema and acp reload, so they are now configured correctly!
119    // Initialise the schema core.
120    //
121    // Now search for the schema itself, and validate that the system
122    // in memory matches the BE on disk, and that it's syntactically correct.
123    // Write it out if changes are needed.
124    query_server
125        .initialise_helper(curtime, DOMAIN_TGT_LEVEL)
126        .await?;
127
128    // We generate a SINGLE idms only!
129    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    // Create a query_server implementation
148    let query_server = QueryServer::new(be, schema, config.domain.clone(), curtime)?;
149
150    // TODO #62: Should the IDM parts be broken out to the IdmServer?
151    // What's important about this initial setup here is that it also triggers
152    // the schema and acp reload, so they are now configured correctly!
153    // Initialise the schema core.
154    //
155    // Now search for the schema itself, and validate that the system
156    // in memory matches the BE on disk, and that it's syntactically correct.
157    // Write it out if changes are needed.
158    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    // TBD in after slopes merge.
236}
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        // No path set, default to stdout
410        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    // Let the txn abort, even on success.
421}
422
423pub async fn restore_server_core(config: &Configuration, dst_path: &Path) {
424    // If it's an in memory database, we don't need to touch anything
425    if let Some(db_path) = config.db_path.as_ref() {
426        touch_file_or_quit(db_path);
427    }
428
429    // First, we provide the in-memory schema so that core attrs are indexed correctly.
430    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    // First, we provide the in-memory schema so that core attrs are indexed correctly.
485    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    // Reindex only the core schema attributes to bootstrap the process.
508    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    // Now that's done, setup a minimal qs and reindex from that.
522    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    // The schema doesn't matter here. Vacuum is run as part of db open to avoid
566    // locking.
567    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    // Start the backend.
588    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    // Setup the qs, and perform any migrations and changes we may have.
597    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    // make sure we're actually changing the domain name...
608    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    // setup the qs - without initialise!
647    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    // Setup the be
655    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    // Run verifications.
672    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    // Now add IDM server verifications?
685}
686
687pub fn cert_generate_core(config: &Configuration) {
688    // Get the cert root
689
690    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        // Generate the CA again.
727        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        // Generate the cert from the ca.
753        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                        // Read the migrations.
792                        // Apply them.
793                        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                // Complete,
841                break;
842            }
843            Err(err) => {
844                error!(?err, "Unable to read directory entries.");
845                return;
846            }
847        }
848    }
849
850    // Filter these.
851
852    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        // Is it valid json?
891        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        // Hash the content.
900        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        // BTreeSet returns false on duplicate value insertion.
914        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    // Okay, we're setup to go - apply them all. Note that we do these
921    // separately, each migration occurs in its own transaction.
922    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    /// This stores a name for the handle, and the handle itself so we can tell which failed/succeeded at the end.
981    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        // Wait on the handles.
996        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        // Can't enable yet until we clean up unix_int cache layer test
1019        // debug_assert!(self.clean_shutdown);
1020    }
1021}
1022
1023pub async fn create_server_core(
1024    config: Configuration,
1025    config_test: bool,
1026) -> Result<CoreHandle, ()> {
1027    // Until this point, we probably want to write to the log macro fns.
1028    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        // TLS is great! We won't run without it.
1035        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    // Setup umask, so that every we touch or create is secure.
1045    #[cfg(not(target_family = "windows"))]
1046    unsafe {
1047        umask(0o0027)
1048    };
1049
1050    // Setup TLS (if any)
1051    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    // Setup the be for the qs.
1068    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    // Start the IDM server.
1076    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    // Any pre-start tasks here.
1085    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        // We need to set the admin pw.
1091        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        // set the idm_admin account password
1102        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        // Add admin to idm_admins to allow tests more flexibility wrt to permissions.
1114        // This way our default access controls can be stricter to prevent lateral
1115        // movement.
1116        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    // Arc the idms and ldap
1165    let idms_arc = Arc::new(idms);
1166    let ldap_arc = Arc::new(ldap);
1167
1168    // Pass it to the actor for threading.
1169    // Start the read query server with the given be path: future config
1170    let server_read_ref = QueryServerReadV1::start_static(idms_arc.clone(), ldap_arc.clone());
1171
1172    // Create the server async write entry point.
1173    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    // Similar, create a stats task which aggregates statistics from the
1227    // server as they come in.
1228    let status_ref = StatusActor::start();
1229
1230    // Delayed actions
1231    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                        // Channel has closed, stop the task.
1240                        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    // Auditing tasks
1258    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    // Run the migrations *once*, only in production though.
1289    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        // Skip all these handles in integration test mode.
1298
1299        // Setup the Migration Reload Trigger.
1300        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        // Setup timed events associated to the write thread
1308        let interval_handle = IntervalActor::start(server_write_ref, broadcast_tx.subscribe());
1309
1310        handles.push((TaskName::IntervalActor, interval_handle));
1311
1312        // Setup timed events associated to the read thread
1313        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        // If we have replication configured, setup the listener with its initial replication
1332        // map (if any).
1333        let maybe_repl_ctrl_tx = match &config.repl_config {
1334            Some(rc) => {
1335                // ⚠️  only start the sockets and listeners in non-config-test modes.
1336                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    // Setup a TLS Acceptor Reload trigger.
1370
1371    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                            // We don't log here as the receivers will notify when they have completed
1397                            // the reload.
1398                            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    // If we have been requested to init LDAP, configure it now.
1413    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    // Finally launch the https tasks.
1441    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}