mirror of
https://github.com/kanidm/kanidm.git
synced 2025-05-17 06:23:54 +02:00
* kanidm cli logs on debug level - Fixes #2745 * such clippy like wow * It's important for a wordsmith to know when to get its fixes in. * updootin' wasms
231 lines
8.9 KiB
Rust
231 lines
8.9 KiB
Rust
use crate::plugins::Plugin;
|
|
use crate::prelude::*;
|
|
use std::sync::Arc;
|
|
|
|
pub struct KeyObjectManagement {}
|
|
|
|
impl Plugin for KeyObjectManagement {
|
|
fn id() -> &'static str {
|
|
"plugin_keyobject_management"
|
|
}
|
|
|
|
#[instrument(
|
|
level = "debug",
|
|
name = "keyobject_management::pre_create_transform",
|
|
skip_all
|
|
)]
|
|
fn pre_create_transform(
|
|
qs: &mut QueryServerWriteTransaction,
|
|
cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
|
|
_ce: &CreateEvent,
|
|
) -> Result<(), OperationError> {
|
|
Self::apply_keyobject_inner(qs, cand)
|
|
}
|
|
|
|
#[instrument(level = "debug", name = "keyobject_management::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::apply_keyobject_inner(qs, cand)
|
|
}
|
|
|
|
#[instrument(
|
|
level = "debug",
|
|
name = "keyobject_management::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::apply_keyobject_inner(qs, cand)
|
|
}
|
|
|
|
/*
|
|
#[instrument(level = "debug", name = "keyobject_management::pre_delete", skip_all)]
|
|
fn pre_delete(
|
|
_qs: &mut QueryServerWriteTransaction,
|
|
// Should these be EntrySealed
|
|
_cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
|
|
_de: &DeleteEvent,
|
|
) -> Result<(), OperationError> {
|
|
Ok(())
|
|
}
|
|
*/
|
|
|
|
#[instrument(level = "debug", name = "keyobject_management::verify", skip_all)]
|
|
fn verify(qs: &mut QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
|
|
let filt_in = filter!(f_eq(Attribute::Class, EntryClass::KeyProvider.into()));
|
|
|
|
let key_providers = match qs
|
|
.internal_search(filt_in)
|
|
.map_err(|_| Err(ConsistencyError::QueryServerSearchFailure))
|
|
{
|
|
Ok(all_cand) => all_cand,
|
|
Err(e) => return vec![e],
|
|
};
|
|
|
|
// Put the providers into a map by uuid.
|
|
let key_providers: hashbrown::HashSet<_> = key_providers
|
|
.into_iter()
|
|
.map(|entry| entry.get_uuid())
|
|
.collect();
|
|
|
|
let filt_in = filter!(f_eq(Attribute::Class, EntryClass::KeyObject.into()));
|
|
|
|
let key_objects = match qs
|
|
.internal_search(filt_in)
|
|
.map_err(|_| Err(ConsistencyError::QueryServerSearchFailure))
|
|
{
|
|
Ok(all_cand) => all_cand,
|
|
Err(e) => return vec![e],
|
|
};
|
|
|
|
key_objects
|
|
.into_iter()
|
|
.filter_map(|key_object_entry| {
|
|
let object_uuid = key_object_entry.get_uuid();
|
|
|
|
// Each key objects must relate to a provider.
|
|
let Some(provider_uuid) =
|
|
key_object_entry.get_ava_single_refer(Attribute::KeyProvider)
|
|
else {
|
|
error!(?object_uuid, "Invalid key object, no key provider uuid.");
|
|
return Some(ConsistencyError::KeyProviderUuidMissing {
|
|
key_object: object_uuid,
|
|
});
|
|
};
|
|
|
|
if !key_providers.contains(&provider_uuid) {
|
|
error!(
|
|
?object_uuid,
|
|
?provider_uuid,
|
|
"Invalid key object, key provider referenced is not found."
|
|
);
|
|
return Some(ConsistencyError::KeyProviderNotFound {
|
|
key_object: object_uuid,
|
|
provider: provider_uuid,
|
|
});
|
|
}
|
|
|
|
// Every key object needs at least *one* key it stores.
|
|
if !key_object_entry
|
|
.attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtEs256.into())
|
|
{
|
|
error!(?object_uuid, "Invalid key object, contains no keys.");
|
|
return Some(ConsistencyError::KeyProviderNoKeys {
|
|
key_object: object_uuid,
|
|
});
|
|
}
|
|
|
|
None
|
|
})
|
|
.map(Err)
|
|
.collect::<Vec<_>>()
|
|
}
|
|
}
|
|
|
|
impl KeyObjectManagement {
|
|
fn apply_keyobject_inner<T: Clone>(
|
|
qs: &mut QueryServerWriteTransaction,
|
|
cand: &mut [Entry<EntryInvalid, T>],
|
|
) -> Result<(), OperationError> {
|
|
// Valid from right meow!
|
|
let valid_from = qs.get_curtime();
|
|
let txn_cid = qs.get_cid().clone();
|
|
|
|
let key_providers = qs.get_key_providers_mut();
|
|
|
|
cand.iter_mut()
|
|
.filter(|entry| {
|
|
entry.attribute_equality(Attribute::Class, &EntryClass::KeyObject.into())
|
|
})
|
|
.try_for_each(|entry| {
|
|
// The entry should not have set any type of KeyObject at this point.
|
|
// Should we force delete those attrs here just incase?
|
|
entry.remove_ava(Attribute::Class, &EntryClass::KeyObjectInternal.into());
|
|
|
|
// Must be set by now.
|
|
let key_object_uuid = entry
|
|
.get_uuid()
|
|
.ok_or(OperationError::KP0008KeyObjectMissingUuid)?;
|
|
|
|
trace!(?key_object_uuid, "Setting up key object");
|
|
|
|
// Get the default provider, and create a new ephemeral key object
|
|
// inside it. If the object existed already, we clone it so that we can stage
|
|
// our changes.
|
|
let mut key_object = key_providers.get_or_create_in_default(key_object_uuid)?;
|
|
|
|
// Import any keys that we were asked to import. This is before revocation so that
|
|
// any keyId here might also be able to be revoked.
|
|
let maybe_import = entry.pop_ava(Attribute::KeyActionImportJwsEs256);
|
|
if let Some(import_keys) = maybe_import
|
|
.as_ref()
|
|
.and_then(|vs| vs.as_private_binary_set())
|
|
{
|
|
key_object.jws_es256_import(import_keys, valid_from, &txn_cid)?;
|
|
}
|
|
|
|
// If revoke. This weird looking let dance is to ensure that the inner hexstring set
|
|
// lives long enough.
|
|
let maybe_revoked = entry.pop_ava(Attribute::KeyActionRevoke);
|
|
if let Some(revoke_keys) =
|
|
maybe_revoked.as_ref().and_then(|vs| vs.as_hexstring_set())
|
|
{
|
|
key_object.revoke_keys(revoke_keys, &txn_cid)?;
|
|
}
|
|
|
|
// Rotation is after revocation, but before assertion. This way if the user
|
|
// asked for rotation and revocation, we don't double rotate when we get to
|
|
// the assert phase. We also only get a rotation time if the time is in the
|
|
// future, to avoid rotating keys in the past.
|
|
if let Some(rotation_time) = entry
|
|
.pop_ava(Attribute::KeyActionRotate)
|
|
.and_then(|vs| vs.to_datetime_single())
|
|
.and_then(|odt| {
|
|
let secs = odt.unix_timestamp() as u64;
|
|
if secs > valid_from.as_secs() {
|
|
Some(Duration::from_secs(secs))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
{
|
|
key_object.rotate_keys(rotation_time, &txn_cid)?;
|
|
}
|
|
|
|
if entry.attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtEs256.into())
|
|
{
|
|
// Assert that this object has a valid es256 key present. Post revoke, it may NOT
|
|
// be present. This differs to rotate, in that the assert verifes we have at least
|
|
// *one* key that is valid in all conditions.
|
|
key_object.jws_es256_assert(Duration::ZERO, &txn_cid)?;
|
|
}
|
|
|
|
if entry
|
|
.attribute_equality(Attribute::Class, &EntryClass::KeyObjectJweA128GCM.into())
|
|
{
|
|
key_object.jwe_a128gcm_assert(Duration::ZERO, &txn_cid)?;
|
|
}
|
|
|
|
// Turn that object into it's entry template to create. I think we need to make this
|
|
// some kind of merge_vs?
|
|
key_object.into_valuesets()?.into_iter().try_for_each(
|
|
|(attribute, valueset)| entry.merge_ava_set(attribute, valueset),
|
|
)?;
|
|
|
|
Ok(())
|
|
})
|
|
}
|
|
}
|
|
|
|
// Unlike other plugins, tests for this plugin will be located in server/lib/src/server/keys.
|
|
//
|
|
// The reason is because we can preconfigure different providers to test these paths in future.
|