mirror of
https://github.com/kanidm/kanidm.git
synced 2025-05-02 07:05:06 +02:00
170 lines
6.5 KiB
Rust
170 lines
6.5 KiB
Rust
// Manage and generate domain uuid's and/or trust related domain
|
|
// management.
|
|
|
|
// The primary point of this is to generate a unique domain UUID on startup
|
|
// which is importart for management of the replication topo and trust
|
|
// relationships.
|
|
use std::iter::once;
|
|
use std::sync::Arc;
|
|
|
|
use compact_jwt::JwsEs256Signer;
|
|
use rand::prelude::*;
|
|
use regex::Regex;
|
|
use tracing::trace;
|
|
|
|
use crate::event::{CreateEvent, ModifyEvent};
|
|
use crate::plugins::Plugin;
|
|
use crate::prelude::*;
|
|
|
|
lazy_static! {
|
|
pub static ref DOMAIN_LDAP_BASEDN_RE: Regex = {
|
|
#[allow(clippy::expect_used)]
|
|
Regex::new(r"^(dc|o|ou)=[a-z][a-z0-9]*(,(dc|o|ou)=[a-z][a-z0-9]*)*$")
|
|
.expect("Invalid domain ldap basedn regex")
|
|
};
|
|
}
|
|
|
|
pub struct Domain {}
|
|
|
|
impl Plugin for Domain {
|
|
fn id() -> &'static str {
|
|
"plugin_domain"
|
|
}
|
|
|
|
#[instrument(level = "debug", name = "domain_pre_create_transform", skip_all)]
|
|
fn pre_create_transform(
|
|
qs: &mut QueryServerWriteTransaction,
|
|
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
|
|
_ce: &CreateEvent,
|
|
) -> Result<(), OperationError> {
|
|
Self::modify_inner(qs, cand)
|
|
}
|
|
|
|
#[instrument(level = "debug", name = "domain_pre_modify", skip_all)]
|
|
fn pre_modify(
|
|
qs: &mut QueryServerWriteTransaction,
|
|
_pre_cand: &[Arc<EntrySealedCommitted>],
|
|
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
|
_me: &ModifyEvent,
|
|
) -> Result<(), OperationError> {
|
|
Self::modify_inner(qs, cand)
|
|
}
|
|
|
|
#[instrument(level = "debug", name = "domain_pre_batch_modify", skip_all)]
|
|
fn pre_batch_modify(
|
|
qs: &mut QueryServerWriteTransaction,
|
|
_pre_cand: &[Arc<EntrySealedCommitted>],
|
|
cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
|
_me: &BatchModifyEvent,
|
|
) -> Result<(), OperationError> {
|
|
Self::modify_inner(qs, cand)
|
|
}
|
|
}
|
|
|
|
fn generate_domain_cookie_key() -> Value {
|
|
let mut key = [0; 64];
|
|
let mut rng = StdRng::from_entropy();
|
|
rng.fill(&mut key);
|
|
Value::new_privatebinary(&key)
|
|
}
|
|
|
|
impl Domain {
|
|
/// Generates the cookie key for the domain.
|
|
fn modify_inner<T: Clone + std::fmt::Debug>(
|
|
qs: &mut QueryServerWriteTransaction,
|
|
cand: &mut [Entry<EntryInvalid, T>],
|
|
) -> Result<(), OperationError> {
|
|
cand.iter_mut().try_for_each(|e| {
|
|
if e.attribute_equality(Attribute::Class, &EntryClass::DomainInfo.into())
|
|
&& e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO)
|
|
{
|
|
// Validate the domain ldap basedn syntax.
|
|
if let Some(basedn) = e
|
|
.get_ava_single_iutf8(Attribute::DomainLdapBasedn) {
|
|
|
|
if !DOMAIN_LDAP_BASEDN_RE.is_match(basedn) {
|
|
error!("Invalid {} '{}'. Must pass regex \"{}\"", Attribute::DomainLdapBasedn,basedn, *DOMAIN_LDAP_BASEDN_RE);
|
|
return Err(OperationError::InvalidState);
|
|
}
|
|
}
|
|
|
|
// We always set this, because the DB uuid is authoritative.
|
|
let u = Value::Uuid(qs.get_domain_uuid());
|
|
e.set_ava(&Attribute::DomainUuid, once(u));
|
|
trace!("plugin_domain: Applying uuid transform");
|
|
|
|
// We only apply this if one isn't provided.
|
|
if !e.attribute_pres(Attribute::DomainName) {
|
|
let n = Value::new_iname(qs.get_domain_name());
|
|
e.set_ava(&Attribute::DomainName, once(n));
|
|
trace!("plugin_domain: Applying domain_name transform");
|
|
}
|
|
|
|
// Setup the minimum functional level if one is not set already.
|
|
if !e.attribute_pres(Attribute::Version) {
|
|
let n = Value::Uint32(DOMAIN_LEVEL_0);
|
|
e.set_ava(&Attribute::Version, once(n));
|
|
warn!("plugin_domain: Applying domain version transform");
|
|
} else {
|
|
debug!("plugin_domain: NOT Applying domain version transform");
|
|
};
|
|
|
|
// create the domain_display_name if it's missing
|
|
if !e.attribute_pres(Attribute::DomainDisplayName) {
|
|
let domain_display_name = Value::new_utf8(format!("Kanidm {}", qs.get_domain_name()));
|
|
security_info!("plugin_domain: setting default domain_display_name to {:?}", domain_display_name);
|
|
|
|
e.set_ava(&Attribute::DomainDisplayName, once(domain_display_name));
|
|
}
|
|
|
|
if qs.get_domain_version() < DOMAIN_LEVEL_6 && !e.attribute_pres(Attribute::FernetPrivateKeyStr) {
|
|
security_info!("regenerating domain token encryption key");
|
|
let k = fernet::Fernet::generate_key();
|
|
let v = Value::new_secret_str(&k);
|
|
e.add_ava(Attribute::FernetPrivateKeyStr, v);
|
|
}
|
|
|
|
if qs.get_domain_version() < DOMAIN_LEVEL_6 && !e.attribute_pres(Attribute::Es256PrivateKeyDer) {
|
|
security_info!("regenerating domain es256 private key");
|
|
let der = JwsEs256Signer::generate_es256()
|
|
.and_then(|jws| jws.private_key_to_der())
|
|
.map_err(|e| {
|
|
admin_error!(err = ?e, "Unable to generate ES256 JwsSigner private key");
|
|
OperationError::CryptographyError
|
|
})?;
|
|
let v = Value::new_privatebinary(&der);
|
|
e.add_ava(Attribute::Es256PrivateKeyDer, v);
|
|
}
|
|
|
|
if qs.get_domain_version() < DOMAIN_LEVEL_6 && !e.attribute_pres(Attribute::PrivateCookieKey) {
|
|
security_info!("regenerating domain cookie key");
|
|
e.add_ava(Attribute::PrivateCookieKey, generate_domain_cookie_key());
|
|
}
|
|
|
|
trace!(?e);
|
|
Ok(())
|
|
} else {
|
|
Ok(())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::prelude::*;
|
|
|
|
// test we can create and generate the id
|
|
#[qs_test]
|
|
async fn test_domain_generate_uuid(server: &QueryServer) {
|
|
let mut server_txn = server.write(duration_from_epoch_now()).await.unwrap();
|
|
let e_dom = server_txn
|
|
.internal_search_uuid(UUID_DOMAIN_INFO)
|
|
.expect("must not fail");
|
|
|
|
let u_dom = server_txn.get_domain_uuid();
|
|
|
|
assert!(e_dom.attribute_equality(Attribute::DomainUuid, &PartialValue::Uuid(u_dom)));
|
|
}
|
|
}
|