kanidmd_lib/plugins/
domain.rsuse 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 {
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)
{
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);
}
}
let u = Value::Uuid(qs.get_domain_uuid());
e.set_ava(&Attribute::DomainUuid, once(u));
trace!("plugin_domain: Applying uuid transform");
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");
}
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");
};
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::*;
#[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)));
}
}