diff --git a/Makefile b/Makefile index b4b6074c4..b8435c4c0 100644 --- a/Makefile +++ b/Makefile @@ -60,6 +60,7 @@ build/kanidmd: ## Build the kanidmd docker image locally build/kanidmd: @$(CONTAINER_TOOL) build $(CONTAINER_TOOL_ARGS) -f server/Dockerfile \ -t $(IMAGE_BASE)/server:$(IMAGE_VERSION) \ + --platform $(IMAGE_ARCH) \ --build-arg "KANIDM_BUILD_PROFILE=container_generic" \ --build-arg "KANIDM_FEATURES=" \ $(CONTAINER_BUILD_ARGS) . @@ -68,6 +69,7 @@ build/kanidmd: build/radiusd: ## Build the radiusd docker image locally build/radiusd: @$(CONTAINER_TOOL) build $(CONTAINER_TOOL_ARGS) \ + --platform $(IMAGE_ARCH) \ -f rlm_python/Dockerfile \ -t $(IMAGE_BASE)/radius:$(IMAGE_VERSION) . diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 0c5c5ff5c..719f9f53b 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -1,6 +1,7 @@ # Kanidm - [Introduction to Kanidm](intro.md) +- [Evaluation Quickstart](quickstart.md) - [Installing the Server](installing_the_server.md) - [Choosing a Domain Name](choosing_a_domain_name.md) - [Preparing for your Deployment](prepare_the_server.md) diff --git a/book/src/quickstart.md b/book/src/quickstart.md new file mode 100644 index 000000000..ed43b82f2 --- /dev/null +++ b/book/src/quickstart.md @@ -0,0 +1,83 @@ +# Evaluation Quickstart + +This section will guide you through a quick setup of Kanidm for evaluation. It's recommended that +for a production deployment you follow the steps in the +[installation chapter](installing_the_server.html) instead as there are a number of security +considerations you should understand. + +### Requirements + +- docker or podman +- `x86_64` cpu supporting `x86_64_v2` OR `aarch64` cpu supporting `neon` + +### Get the software + +```bash +docker pull kanidm/server:latest +``` + +### Configure the container + +```bash +docker volume create kanidmd +docker create --name kanidmd \ + -p 443:8443 \ + -p 636:3636 \ + -v kanidmd:/data \ + kanidm/server:latest +``` + +### Configure the server + +Create server.toml + +```toml +{{#rustdoc_include ../../examples/server_container.toml}} +``` + +### Add configuration to container + +```bash +docker cp server.toml kanidmd:/data/server.toml +``` + +### Generate evaluation certificates + +```bash +docker run --rm -i -t -v kanidmd:/data \ + kanidm/server:latest \ + kanidmd cert-generate -c /data/server.toml +``` + +### Recover the admin password + +```bash +docker run --rm -i -t -v kanidmd:/data \ + kanidm/server:latest \ + kanidmd recover-account admin -c /data/server.toml +``` + +### Start Kanidmd + +```bash +docker start kanidmd +``` + +### Setup the client configuration + +```toml +# ~/.config/kanidm + +uri = "https://localhost:443" +verify_ca = false +``` + +### Check you can login + +```bash +kanidm login +``` + +### What next? + +You can now follow the steps in the [administration section](administrivia.md) diff --git a/examples/kanidm b/examples/kanidm index 1a9b1a959..efb0431da 100644 --- a/examples/kanidm +++ b/examples/kanidm @@ -19,8 +19,8 @@ auth_token = "putyourtokenhere" radius_cert_path = "/certs/cert.pem" # the TLS certificate radius_key_path = "/certs/key.pem" # the signing key for radius TLS -radius_dh_path = "/certs/dh.pem" # the diffie-hellman output radius_ca_path = "/certs/ca.pem" # the CA certificate +radius_dh_path = "/certs/dh.pem" # the diffie-hellman output # A list of groups, if a user is in them, they're approved for RADIUS authentication radius_required_groups = [ @@ -42,4 +42,4 @@ radius_clients = [ ] # The client connection timeout, in seconds. -connect_timeout = 30 \ No newline at end of file +connect_timeout = 30 diff --git a/rlm_python/Dockerfile b/rlm_python/Dockerfile index 56fbfbcc9..ff0e8a16f 100644 --- a/rlm_python/Dockerfile +++ b/rlm_python/Dockerfile @@ -47,7 +47,6 @@ RUN python3 -m pip install --no-cache-dir --no-warn-script-location /pkg/pykanid COPY rlm_python/radius_entrypoint.py /radius_entrypoint.py -ENV LD_PRELOAD=/usr/lib64/libpython3.so ENV KANIDM_CONFIG_FILE="/data/kanidm" RUN chmod a+r /etc/raddb/certs/ -R diff --git a/rlm_python/radius_entrypoint.py b/rlm_python/radius_entrypoint.py index b881e7a39..3c2cbaa14 100644 --- a/rlm_python/radius_entrypoint.py +++ b/rlm_python/radius_entrypoint.py @@ -79,12 +79,15 @@ def setup_certs( if kanidm_config_object.radius_dh_path is not None: cert_dh = Path(kanidm_config_object.radius_dh_path).expanduser().resolve() if not cert_dh.exists(): - print(f"Failed to find radiusd dh file ({cert_dh}), quitting!", file=sys.stderr) - sys.exit(1) + # print(f"Failed to find radiusd dh file ({cert_dh}), quitting!", file=sys.stderr) + # sys.exit(1) + print(f"Generating dh params in {cert_dh}") + subprocess.check_call(["openssl", "dhparam", "-out", cert_dh, "2048"]) if cert_dh != CERT_DH_DEST: print(f"Copying {cert_dh} to {CERT_DH_DEST}") shutil.copyfile(cert_dh, CERT_DH_DEST) + server_key = Path(kanidm_config_object.radius_key_path).expanduser().resolve() if not server_key.exists() or not server_key.is_file(): print( diff --git a/rlm_python/run_radius_container.sh b/rlm_python/run_radius_container.sh index d73e8ec07..b58d6fc80 100755 --- a/rlm_python/run_radius_container.sh +++ b/rlm_python/run_radius_container.sh @@ -1,23 +1,29 @@ #!/bin/bash set -x + if [ -z "${IMAGE}" ]; then IMAGE="kanidm/radius:devel" fi echo "Running docker container: ${IMAGE}" +if [ ! -z "${IMAGE_ARCH}" ]; then + IMAGE_ARCH="--platform ${IMAGE_ARCH}" +fi + if [ -z "${CONFIG_FILE}" ]; then CONFIG_FILE="$(pwd)/../examples/kanidm" fi echo "Using config file: ${CONFIG_FILE}" if [ ! -d "/tmp/kanidm/" ]; then - echo "Can't find /tmp/kanidm - you might need to run insecure_generate_tls.sh" + echo "Can't find /tmp/kanidm - you may need to run run_insecure_dev_server" fi echo "Starting the dev container..." #shellcheck disable=SC2068 docker run --rm -it \ + ${IMAGE_ARCH} \ --network host \ --name radiusd \ -v /tmp/kanidm/:/data/ \ diff --git a/server/core/src/crypto.rs b/server/core/src/crypto.rs index 5bd96da7f..f19886ab6 100644 --- a/server/core/src/crypto.rs +++ b/server/core/src/crypto.rs @@ -1,11 +1,28 @@ //! This module contains cryptographic setup code, a long with what policy //! and ciphers we accept. +use openssl::ec::{EcGroup, EcKey}; use openssl::error::ErrorStack; +use openssl::nid::Nid; use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod}; +use openssl::x509::{ + extension::{ + AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectAlternativeName, + SubjectKeyIdentifier, + }, + X509NameBuilder, X509ReqBuilder, X509, +}; +use openssl::{asn1, bn, hash, pkey}; use crate::config::Configuration; +use std::fs::File; +use std::io::{Read, Write}; +use std::path::Path; + +const CA_VALID_DAYS: u32 = 30; +const CERT_VALID_DAYS: u32 = 5; + /// From the server configuration, generate an OpenSSL acceptor that we can use /// to build our sockets for https/ldaps. pub fn setup_tls(config: &Configuration) -> Result, ErrorStack> { @@ -20,3 +37,264 @@ pub fn setup_tls(config: &Configuration) -> Result, E None => Ok(None), } } + +fn get_group() -> Result { + EcGroup::from_curve_name(Nid::X9_62_PRIME256V1) +} + +pub(crate) struct CaHandle { + key: pkey::PKey, + cert: X509, +} + +pub(crate) fn write_ca( + key_ar: impl AsRef, + cert_ar: impl AsRef, + handle: &CaHandle, +) -> Result<(), ()> { + let key_path: &Path = key_ar.as_ref(); + let cert_path: &Path = cert_ar.as_ref(); + + let key_pem = handle.key.private_key_to_pem_pkcs8().map_err(|e| { + error!(err = ?e, "Failed to convert key to PEM"); + })?; + + let cert_pem = handle.cert.to_pem().map_err(|e| { + error!(err = ?e, "Failed to convert cert to PEM"); + })?; + + File::create(key_path) + .and_then(|mut file| file.write_all(&key_pem)) + .map_err(|e| { + error!(err = ?e, "Failed to create {:?}", key_path); + })?; + + File::create(cert_path) + .and_then(|mut file| file.write_all(&cert_pem)) + .map_err(|e| { + error!(err = ?e, "Failed to create {:?}", cert_path); + }) +} + +pub(crate) fn build_ca() -> Result { + let ecgroup = get_group()?; + let eckey = EcKey::generate(&ecgroup)?; + let ca_key = pkey::PKey::from_ec_key(eckey)?; + let mut x509_name = X509NameBuilder::new()?; + + x509_name.append_entry_by_text("C", "AU")?; + x509_name.append_entry_by_text("ST", "QLD")?; + x509_name.append_entry_by_text("O", "Kanidm")?; + x509_name.append_entry_by_text("CN", "Kanidm Generated CA")?; + x509_name.append_entry_by_text("OU", "Development and Evaluation - NOT FOR PRODUCTION")?; + let x509_name = x509_name.build(); + + let mut cert_builder = X509::builder()?; + // Yes, 2 actually means 3 here ... + cert_builder.set_version(2)?; + + let serial_number = bn::BigNum::from_u32(1).and_then(|serial| serial.to_asn1_integer())?; + + cert_builder.set_serial_number(&serial_number)?; + cert_builder.set_subject_name(&x509_name)?; + cert_builder.set_issuer_name(&x509_name)?; + + let not_before = asn1::Asn1Time::days_from_now(0)?; + cert_builder.set_not_before(¬_before)?; + let not_after = asn1::Asn1Time::days_from_now(CA_VALID_DAYS)?; + cert_builder.set_not_after(¬_after)?; + + cert_builder.append_extension(BasicConstraints::new().critical().ca().pathlen(0).build()?)?; + cert_builder.append_extension( + KeyUsage::new() + .critical() + .key_cert_sign() + .crl_sign() + .build()?, + )?; + + let subject_key_identifier = + SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(None, None))?; + cert_builder.append_extension(subject_key_identifier)?; + + cert_builder.set_pubkey(&ca_key)?; + + cert_builder.sign(&ca_key, hash::MessageDigest::sha256())?; + let ca_cert = cert_builder.build(); + + Ok(CaHandle { + key: ca_key, + cert: ca_cert, + }) +} + +pub(crate) fn load_ca( + ca_key_ar: impl AsRef, + ca_cert_ar: impl AsRef, +) -> Result { + let ca_key_path: &Path = ca_key_ar.as_ref(); + let ca_cert_path: &Path = ca_cert_ar.as_ref(); + + let mut ca_key_pem = vec![]; + File::open(ca_key_path) + .and_then(|mut file| file.read_to_end(&mut ca_key_pem)) + .map_err(|e| { + error!(err = ?e, "Failed to read {:?}", ca_key_path); + })?; + + let mut ca_cert_pem = vec![]; + File::open(ca_cert_path) + .and_then(|mut file| file.read_to_end(&mut ca_cert_pem)) + .map_err(|e| { + error!(err = ?e, "Failed to read {:?}", ca_cert_path); + })?; + + let ca_key = pkey::PKey::private_key_from_pem(&ca_key_pem).map_err(|e| { + error!(err = ?e, "Failed to convert PEM to key"); + })?; + + let ca_cert = X509::from_pem(&ca_cert_pem).map_err(|e| { + error!(err = ?e, "Failed to convert PEM to cert"); + })?; + + Ok(CaHandle { + key: ca_key, + cert: ca_cert, + }) +} + +pub(crate) struct CertHandle { + key: pkey::PKey, + cert: X509, + chain: Vec, +} + +pub(crate) fn write_cert( + key_ar: impl AsRef, + chain_ar: impl AsRef, + cert_ar: impl AsRef, + handle: &CertHandle, +) -> Result<(), ()> { + let key_path: &Path = key_ar.as_ref(); + let chain_path: &Path = chain_ar.as_ref(); + let cert_path: &Path = cert_ar.as_ref(); + + let key_pem = handle.key.private_key_to_pem_pkcs8().map_err(|e| { + error!(err = ?e, "Failed to convert key to PEM"); + })?; + + let cert_pem = handle.cert.to_pem().map_err(|e| { + error!(err = ?e, "Failed to convert cert to PEM"); + })?; + + let mut chain_pem = cert_pem.clone(); + + // Build the chain PEM. + for ca_cert in &handle.chain { + match ca_cert.to_pem() { + Ok(c) => { + chain_pem.extend_from_slice(&c); + } + Err(e) => { + error!(err = ?e, "Failed to convert cert to PEM"); + return Err(()); + } + } + } + + File::create(key_path) + .and_then(|mut file| file.write_all(&key_pem)) + .map_err(|e| { + error!(err = ?e, "Failed to create {:?}", key_path); + })?; + + File::create(chain_path) + .and_then(|mut file| file.write_all(&chain_pem)) + .map_err(|e| { + error!(err = ?e, "Failed to create {:?}", chain_path); + })?; + + File::create(cert_path) + .and_then(|mut file| file.write_all(&cert_pem)) + .map_err(|e| { + error!(err = ?e, "Failed to create {:?}", cert_path); + }) +} + +pub(crate) fn build_cert( + domain_name: &str, + ca_handle: &CaHandle, +) -> Result { + let ecgroup = get_group()?; + let eckey = EcKey::generate(&ecgroup)?; + let int_key = pkey::PKey::from_ec_key(eckey)?; + + // + let mut req_builder = X509ReqBuilder::new()?; + req_builder.set_pubkey(&int_key)?; + + let mut x509_name = X509NameBuilder::new()?; + x509_name.append_entry_by_text("C", "AU")?; + x509_name.append_entry_by_text("ST", "QLD")?; + x509_name.append_entry_by_text("O", "Kanidm")?; + x509_name.append_entry_by_text("CN", domain_name)?; + // Requirement of packed attestation. + x509_name.append_entry_by_text("OU", "Development and Evaluation - NOT FOR PRODUCTION")?; + let x509_name = x509_name.build(); + + req_builder.set_subject_name(&x509_name)?; + req_builder.sign(&int_key, hash::MessageDigest::sha256())?; + let req = req_builder.build(); + // == + + let mut cert_builder = X509::builder()?; + // Yes, 2 actually means 3 here ... + cert_builder.set_version(2)?; + let serial_number = bn::BigNum::from_u32(2).and_then(|serial| serial.to_asn1_integer())?; + + cert_builder.set_pubkey(&int_key)?; + + cert_builder.set_serial_number(&serial_number)?; + cert_builder.set_subject_name(req.subject_name())?; + cert_builder.set_issuer_name(ca_handle.cert.subject_name())?; + + let not_before = asn1::Asn1Time::days_from_now(0)?; + cert_builder.set_not_before(¬_before)?; + let not_after = asn1::Asn1Time::days_from_now(CERT_VALID_DAYS)?; + cert_builder.set_not_after(¬_after)?; + + cert_builder.append_extension(BasicConstraints::new().build()?)?; + + cert_builder.append_extension( + KeyUsage::new() + .critical() + .non_repudiation() + .digital_signature() + .key_encipherment() + .build()?, + )?; + + let subject_key_identifier = SubjectKeyIdentifier::new() + .build(&cert_builder.x509v3_context(Some(&ca_handle.cert), None))?; + cert_builder.append_extension(subject_key_identifier)?; + + let auth_key_identifier = AuthorityKeyIdentifier::new() + .keyid(false) + .issuer(false) + .build(&cert_builder.x509v3_context(Some(&ca_handle.cert), None))?; + cert_builder.append_extension(auth_key_identifier)?; + + let subject_alt_name = SubjectAlternativeName::new() + .dns(domain_name) + .build(&cert_builder.x509v3_context(Some(&ca_handle.cert), None))?; + cert_builder.append_extension(subject_alt_name)?; + + cert_builder.sign(&ca_handle.key, hash::MessageDigest::sha256())?; + let int_cert = cert_builder.build(); + + Ok(CertHandle { + key: int_key, + cert: int_cert, + chain: vec![ca_handle.cert.clone()], + }) +} diff --git a/server/core/src/lib.rs b/server/core/src/lib.rs index 8d2b6b419..ac1566961 100644 --- a/server/core/src/lib.rs +++ b/server/core/src/lib.rs @@ -32,6 +32,7 @@ pub mod https; mod interval; mod ldaps; +use std::path::Path; use std::sync::Arc; use compact_jwt::JwsSigner; @@ -51,7 +52,6 @@ use tokio::sync::broadcast; use crate::actors::v1_read::QueryServerReadV1; use crate::actors::v1_write::QueryServerWriteV1; use crate::config::Configuration; -use crate::crypto::setup_tls; use crate::interval::IntervalActor; // === internal setup helpers @@ -553,6 +553,98 @@ pub async fn recover_account_core(config: &Configuration, name: &str) { ); } +pub fn cert_generate_core(config: &Configuration) { + // Get the cert root + + let (tls_key_path, tls_chain_path) = match &config.tls_config { + Some(tls_config) => ( + Path::new(tls_config.key.as_str()), + Path::new(tls_config.chain.as_str()), + ), + None => { + error!("Unable to find tls configuration"); + std::process::exit(1); + } + }; + + if tls_key_path.exists() && tls_chain_path.exists() { + info!( + "tls key and chain already exist - remove them first if you intend to regenerate these" + ); + return; + } + + let origin = match Url::parse(&config.origin) { + Ok(url) => url, + Err(e) => { + error!(err = ?e, "Unable to parse origin URL - refusing to start. You must correct the value for origin. {:?}", config.origin); + std::process::exit(1); + } + }; + + let origin_domain = if let Some(d) = origin.domain() { + d + } else { + error!("origin does not contain a valid domain"); + std::process::exit(1); + }; + + let cert_root = match tls_key_path.parent() { + Some(parent) => parent, + None => { + error!("Unable to find parent directory of {:?}", tls_key_path); + std::process::exit(1); + } + }; + + let ca_cert = cert_root.join("ca.pem"); + let ca_key = cert_root.join("cakey.pem"); + let tls_cert_path = cert_root.join("cert.pem"); + + let ca_handle = if !ca_cert.exists() || !ca_key.exists() { + // Generate the CA again. + let ca_handle = match crypto::build_ca() { + Ok(ca_handle) => ca_handle, + Err(e) => { + error!(err = ?e, "Failed to build CA"); + std::process::exit(1); + } + }; + + if crypto::write_ca(ca_key, ca_cert, &ca_handle).is_err() { + error!("Failed to write CA"); + std::process::exit(1); + } + + ca_handle + } else { + match crypto::load_ca(ca_key, ca_cert) { + Ok(ca_handle) => ca_handle, + Err(_) => { + error!("Failed to load CA"); + std::process::exit(1); + } + } + }; + + if !tls_key_path.exists() || !tls_chain_path.exists() || !tls_cert_path.exists() { + // Generate the cert from the ca. + let cert_handle = match crypto::build_cert(origin_domain, &ca_handle) { + Ok(cert_handle) => cert_handle, + Err(e) => { + error!(err = ?e, "Failed to build certificate"); + std::process::exit(1); + } + }; + + if crypto::write_cert(tls_key_path, tls_chain_path, tls_cert_path, &cert_handle).is_err() { + error!("Failed to write certificates"); + std::process::exit(1); + } + } + info!("certificate generation complete"); +} + #[derive(Clone, Debug)] pub enum CoreAction { Shutdown, @@ -626,7 +718,7 @@ pub async fn create_server_core( let status_ref = StatusActor::start(); // Setup TLS (if any) - let _opt_tls_params = match setup_tls(&config) { + let _opt_tls_params = match crypto::setup_tls(&config) { Ok(opt_tls_params) => opt_tls_params, Err(e) => { error!("Failed to configure TLS parameters -> {:?}", e); @@ -784,7 +876,7 @@ pub async fn create_server_core( // If we have been requested to init LDAP, configure it now. let maybe_ldap_acceptor_handle = match &config.ldapaddress { Some(la) => { - let opt_ldap_tls_params = match setup_tls(&config) { + let opt_ldap_tls_params = match crypto::setup_tls(&config) { Ok(t) => t, Err(e) => { error!("Failed to configure LDAP TLS parameters -> {:?}", e); diff --git a/server/daemon/run_insecure_dev_server.sh b/server/daemon/run_insecure_dev_server.sh index e00869929..d1f1a7415 100755 --- a/server/daemon/run_insecure_dev_server.sh +++ b/server/daemon/run_insecure_dev_server.sh @@ -2,10 +2,6 @@ # This script based on the developer readme and allows you to run a test server. -if [ -z "$KANI_TMP" ]; then - KANI_TMP=/tmp/kanidm -fi - if [ -z "$KANI_CARGO_OPTS" ]; then KANI_CARGO_OPTS="" fi @@ -17,14 +13,9 @@ if [ ! -f "${CONFIG_FILE}" ]; then echo "Couldn't find configuration file at ${CONFIG_FILE}, please ensure you're running this script from its base directory (${SCRIPT_DIR})." exit 1 fi -if [ ! -f "${KANI_TMP}/chain.pem" ]; then - echo "Couldn't find certificate at /tmp/kanidm/chain.pem, quitting" - exit 1 -fi -if [ ! -f "${KANI_TMP}/key.pem" ]; then - echo "Couldn't find key file at /tmp/kanidm/key.pem, quitting" - exit 1 -fi + +#shellcheck disable=SC2086 +cargo run ${KANI_CARGO_OPTS} --bin kanidmd -- cert-generate -c "${CONFIG_FILE}" COMMAND="server" if [ -n "${1}" ]; then diff --git a/server/daemon/src/main.rs b/server/daemon/src/main.rs index 6334f422c..e0af11691 100644 --- a/server/daemon/src/main.rs +++ b/server/daemon/src/main.rs @@ -25,10 +25,10 @@ use std::process::ExitCode; use clap::{Args, Parser, Subcommand}; use kanidmd_core::config::{Configuration, ServerConfig}; use kanidmd_core::{ - backup_server_core, create_server_core, dbscan_get_id2entry_core, dbscan_list_id2entry_core, - dbscan_list_index_analysis_core, dbscan_list_index_core, dbscan_list_indexes_core, - domain_rename_core, recover_account_core, reindex_server_core, restore_server_core, - vacuum_server_core, verify_server_core, + backup_server_core, cert_generate_core, create_server_core, dbscan_get_id2entry_core, + dbscan_list_id2entry_core, dbscan_list_index_analysis_core, dbscan_list_index_core, + dbscan_list_indexes_core, domain_rename_core, recover_account_core, reindex_server_core, + restore_server_core, vacuum_server_core, verify_server_core, }; use sketching::tracing_forest::traits::*; use sketching::tracing_forest::util::*; @@ -44,6 +44,7 @@ impl KanidmdOpt { fn commonopt(&self) -> &CommonOpt { match self { KanidmdOpt::Server(sopt) + | KanidmdOpt::CertGenerate(sopt) | KanidmdOpt::ConfigTest(sopt) | KanidmdOpt::DbScan { commands: DbScanOpt::ListIndexes(sopt), @@ -148,7 +149,18 @@ async fn main() -> ExitCode { // Check the permissions are OK. #[cfg(target_family = "unix")] { - let cfg_path = &opt.commands.commonopt().config_path; + let cfg_path = &opt.commands.commonopt().config_path; // TODO: this needs to be pulling from the default or something? + if format!("{}", cfg_path.display()) == "".to_string() { + error!("Refusing to run - config file path is empty"); + return ExitCode::FAILURE + } + if !cfg_path.exists() { + error!( + "Refusing to run - config file {} does not exist", + cfg_path.to_str().unwrap_or("invalid file path") + ); + return ExitCode::FAILURE + } let cfg_meta = match metadata(cfg_path) { Ok(m) => m, Err(e) => { @@ -381,8 +393,11 @@ async fn main() -> ExitCode { } info!("Stopped 🛑 "); } - - + } + KanidmdOpt::CertGenerate(_sopt) => { + info!("Running in certificate generate mode ..."); + config.update_config_for_server_mode(&sconfig); + cert_generate_core(&config); } KanidmdOpt::Database { commands: DbCommands::Backup(bopt), diff --git a/server/daemon/src/opt.rs b/server/daemon/src/opt.rs index 89687394a..acb530e02 100644 --- a/server/daemon/src/opt.rs +++ b/server/daemon/src/opt.rs @@ -136,6 +136,11 @@ enum KanidmdOpt { #[clap(name = "configtest")] /// Test the IDM Server configuration, without starting network listeners. ConfigTest(CommonOpt), + #[clap(name = "cert-generate")] + /// Create a self-signed ca and tls certificate in the locations listed from the + /// configuration. These certificates should *not* be used in production, they + /// are for testing and evaluation only! + CertGenerate(CommonOpt), #[clap(name = "recover-account")] /// Recover an account's password RecoverAccount(RecoverAccountOpt),