kanidmd_lib/plugins/
keyobject.rs

1use crate::plugins::Plugin;
2use crate::prelude::*;
3use std::sync::Arc;
4
5pub struct KeyObjectManagement {}
6
7impl Plugin for KeyObjectManagement {
8    fn id() -> &'static str {
9        "plugin_keyobject_management"
10    }
11
12    #[instrument(
13        level = "debug",
14        name = "keyobject_management::pre_create_transform",
15        skip_all
16    )]
17    fn pre_create_transform(
18        qs: &mut QueryServerWriteTransaction,
19        cand: &mut Vec<Entry<EntryInvalid, EntryNew>>,
20        _ce: &CreateEvent,
21    ) -> Result<(), OperationError> {
22        Self::apply_keyobject_inner(qs, cand)
23    }
24
25    #[instrument(level = "debug", name = "keyobject_management::pre_modify", skip_all)]
26    fn pre_modify(
27        qs: &mut QueryServerWriteTransaction,
28        _pre_cand: &[Arc<EntrySealedCommitted>],
29        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
30        _me: &ModifyEvent,
31    ) -> Result<(), OperationError> {
32        Self::apply_keyobject_inner(qs, cand)
33    }
34
35    #[instrument(
36        level = "debug",
37        name = "keyobject_management::pre_batch_modify",
38        skip_all
39    )]
40    fn pre_batch_modify(
41        qs: &mut QueryServerWriteTransaction,
42        _pre_cand: &[Arc<EntrySealedCommitted>],
43        cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
44        _me: &BatchModifyEvent,
45    ) -> Result<(), OperationError> {
46        Self::apply_keyobject_inner(qs, cand)
47    }
48
49    /*
50    #[instrument(level = "debug", name = "keyobject_management::pre_delete", skip_all)]
51    fn pre_delete(
52        _qs: &mut QueryServerWriteTransaction,
53        // Should these be EntrySealed
54        _cand: &mut Vec<Entry<EntryInvalid, EntryCommitted>>,
55        _de: &DeleteEvent,
56    ) -> Result<(), OperationError> {
57        Ok(())
58    }
59    */
60
61    #[instrument(level = "debug", name = "keyobject_management::verify", skip_all)]
62    fn verify(qs: &mut QueryServerReadTransaction) -> Vec<Result<(), ConsistencyError>> {
63        let filt_in = filter!(f_eq(Attribute::Class, EntryClass::KeyProvider.into()));
64
65        let key_providers = match qs
66            .internal_search(filt_in)
67            .map_err(|_| Err(ConsistencyError::QueryServerSearchFailure))
68        {
69            Ok(all_cand) => all_cand,
70            Err(e) => return vec![e],
71        };
72
73        // Put the providers into a map by uuid.
74        let key_providers: hashbrown::HashSet<_> = key_providers
75            .into_iter()
76            .map(|entry| entry.get_uuid())
77            .collect();
78
79        let filt_in = filter!(f_eq(Attribute::Class, EntryClass::KeyObject.into()));
80
81        let key_objects = match qs
82            .internal_search(filt_in)
83            .map_err(|_| Err(ConsistencyError::QueryServerSearchFailure))
84        {
85            Ok(all_cand) => all_cand,
86            Err(e) => return vec![e],
87        };
88
89        key_objects
90            .into_iter()
91            .filter_map(|key_object_entry| {
92                let object_uuid = key_object_entry.get_uuid();
93
94                // Each key objects must relate to a provider.
95                let Some(provider_uuid) =
96                    key_object_entry.get_ava_single_refer(Attribute::KeyProvider)
97                else {
98                    error!(?object_uuid, "Invalid key object, no key provider uuid.");
99                    return Some(ConsistencyError::KeyProviderUuidMissing {
100                        key_object: object_uuid,
101                    });
102                };
103
104                if !key_providers.contains(&provider_uuid) {
105                    error!(
106                        ?object_uuid,
107                        ?provider_uuid,
108                        "Invalid key object, key provider referenced is not found."
109                    );
110                    return Some(ConsistencyError::KeyProviderNotFound {
111                        key_object: object_uuid,
112                        provider: provider_uuid,
113                    });
114                }
115
116                // Every key object needs at least *one* key it stores.
117                if !key_object_entry
118                    .attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtEs256.into())
119                {
120                    error!(?object_uuid, "Invalid key object, contains no keys.");
121                    return Some(ConsistencyError::KeyProviderNoKeys {
122                        key_object: object_uuid,
123                    });
124                }
125
126                None
127            })
128            .map(Err)
129            .collect::<Vec<_>>()
130    }
131}
132
133impl KeyObjectManagement {
134    fn apply_keyobject_inner<T: Clone>(
135        qs: &mut QueryServerWriteTransaction,
136        cand: &mut [Entry<EntryInvalid, T>],
137    ) -> Result<(), OperationError> {
138        // New keys will be valid from right meow!
139        let valid_from = qs.get_curtime();
140        let txn_cid = qs.get_cid().clone();
141        let key_providers = qs.get_key_providers_mut();
142        // ====================================================================
143        // Transform any found KeyObjects and manage any related key operations
144        // for them
145
146        cand.iter_mut()
147            .filter(|entry| {
148                entry.attribute_equality(Attribute::Class, &EntryClass::KeyObject.into())
149            })
150            .try_for_each(|entry| {
151                // The entry should not have set any type of KeyObject at this point.
152                // Should we force delete those attrs here just incase?
153                entry.remove_ava(Attribute::Class, &EntryClass::KeyObjectInternal.into());
154
155                // Must be set by now.
156                let key_object_uuid = entry
157                    .get_uuid()
158                    .ok_or(OperationError::KP0008KeyObjectMissingUuid)?;
159
160                trace!(?key_object_uuid, "Setting up key object");
161
162                // Get the default provider, and create a new ephemeral key object
163                // inside it. If the object existed already, we clone it so that we can stage
164                // our changes.
165                let mut key_object = key_providers.get_or_create_in_default(key_object_uuid)?;
166
167                // Import any keys that we were asked to import. This is before revocation so that
168                // any keyId here might also be able to be revoked.
169                let maybe_import = entry.pop_ava(Attribute::KeyActionImportJwsEs256);
170                if let Some(import_keys) = maybe_import
171                    .as_ref()
172                    .and_then(|vs| vs.as_private_binary_set())
173                {
174                    key_object.jws_es256_import(import_keys, valid_from, &txn_cid)?;
175                }
176
177                let maybe_import = entry.pop_ava(Attribute::KeyActionImportJwsRs256);
178                if let Some(import_keys) = maybe_import
179                    .as_ref()
180                    .and_then(|vs| vs.as_private_binary_set())
181                {
182                    key_object.jws_rs256_import(import_keys, valid_from, &txn_cid)?;
183                }
184
185                // If revoke. This weird looking let dance is to ensure that the inner hexstring set
186                // lives long enough.
187                let maybe_revoked = entry.pop_ava(Attribute::KeyActionRevoke);
188                if let Some(revoke_keys) =
189                    maybe_revoked.as_ref().and_then(|vs| vs.as_hexstring_set())
190                {
191                    key_object.revoke_keys(revoke_keys, &txn_cid)?;
192                }
193
194                // Rotation is after revocation, but before assertion. This way if the user
195                // asked for rotation and revocation, we don't double rotate when we get to
196                // the assert phase. We also only get a rotation time if the time is in the
197                // future, to avoid rotating keys in the past.
198                if let Some(rotation_time) = entry
199                    .pop_ava(Attribute::KeyActionRotate)
200                    .and_then(|vs| vs.to_datetime_single())
201                    .and_then(|odt| {
202                        let secs = odt.unix_timestamp() as u64;
203                        if secs > valid_from.as_secs() {
204                            Some(Duration::from_secs(secs))
205                        } else {
206                            None
207                        }
208                    })
209                {
210                    key_object.rotate_keys(rotation_time, &txn_cid)?;
211                }
212
213                if entry.attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtEs256.into())
214                {
215                    // Assert that this object has a valid es256 key present. Post revoke, it may NOT
216                    // be present. This differs to rotate, in that the assert verifes we have at least
217                    // *one* key that is valid in all conditions.
218                    key_object.jws_es256_assert(Duration::ZERO, &txn_cid)?;
219                }
220
221                if entry.attribute_equality(Attribute::Class, &EntryClass::KeyObjectJwtRs256.into())
222                {
223                    key_object.jws_rs256_assert(Duration::ZERO, &txn_cid)?;
224                }
225
226                if entry
227                    .attribute_equality(Attribute::Class, &EntryClass::KeyObjectJweA128GCM.into())
228                {
229                    key_object.jwe_a128gcm_assert(Duration::ZERO, &txn_cid)?;
230                }
231
232                // Turn that object into it's entry template to create. I think we need to make this
233                // some kind of merge_vs?
234                key_object
235                    .as_valuesets()?
236                    .into_iter()
237                    .try_for_each(|(attribute, valueset)| {
238                        entry.merge_ava_set(&attribute, valueset)
239                    })?;
240
241                Ok(())
242            })
243    }
244}
245
246// Unlike other plugins, tests for this plugin will be located in server/lib/src/server/keys.
247//
248// The reason is because we can preconfigure different providers to test these paths in future.