1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
use std::collections::HashSet;
use async_trait::async_trait;
use cosmian_kmip::{
kmip_0::kmip_types::State,
kmip_2_1::{kmip_attributes::Attributes, kmip_objects::Object},
};
use cosmian_logger::warn;
use time::OffsetDateTime;
use crate::{InterfaceResult, ObjectWithMetadata};
/// An atomic operation on the objects database
pub enum AtomicOperation {
/// Create (uid, object, attributes, tags) - the state will be active
Create((String, Object, Attributes, HashSet<String>)),
/// Upsert (uid, object, attributes, tags, state) - the state be updated
Upsert((String, Object, Attributes, Option<HashSet<String>>, State)),
/// Update the object (uid, object, attributes, tags) - the state will be not be updated
UpdateObject((String, Object, Attributes, Option<HashSet<String>>)),
/// Update the state (uid, state)
UpdateState((String, State)),
/// Delete (uid)
Delete(String),
}
impl AtomicOperation {
#[must_use]
pub fn get_object_uid(&self) -> &str {
match self {
Self::Create((uid, _, _, _))
| Self::Upsert((uid, _, _, _, _))
| Self::UpdateObject((uid, _, _, _))
| Self::UpdateState((uid, _))
| Self::Delete(uid) => uid,
}
}
}
/// Trait that must implement all object stores (DBs, HSMs, etc.) that store objects
#[async_trait(?Send)]
pub trait ObjectsStore {
/// Create the given Object in the database.
///
/// A new UUID will be created if none is supplier.
/// This method will fail if a `uid` is supplied
/// and an object with the same id already exists
async fn create(
&self,
uid: Option<String>,
owner: &str,
object: &Object,
attributes: &Attributes,
tags: &HashSet<String>,
) -> InterfaceResult<String>;
/// Retrieve an object from the database.
async fn retrieve(&self, uid: &str) -> InterfaceResult<Option<ObjectWithMetadata>>;
/// Retrieve the tags of the object with the given `uid`
async fn retrieve_tags(&self, uid: &str) -> InterfaceResult<HashSet<String>>;
/// Update an object in the database.
///
/// If tags is `None`, the tags will not be updated.
async fn update_object(
&self,
uid: &str,
object: &Object,
attributes: &Attributes,
tags: Option<&HashSet<String>>,
) -> InterfaceResult<()>;
/// Update the state of an object in the database.
async fn update_state(&self, uid: &str, state: State) -> InterfaceResult<()>;
/// Delete an object from the database.
async fn delete(&self, uid: &str) -> InterfaceResult<()>;
/// Perform an atomic set of operation on the database
/// (typically in a transaction)
///
/// # Returns
/// The list objects uid that operations were performed on
async fn atomic(
&self,
user: &str,
operations: &[AtomicOperation],
) -> InterfaceResult<Vec<String>>;
/// Test if an object identified by its `uid` is currently owned by `owner`
async fn is_object_owned_by(&self, uid: &str, owner: &str) -> InterfaceResult<bool>;
/// List the `uid` of all the objects that have the given `tags`
async fn list_uids_for_tags(&self, tags: &HashSet<String>) -> InterfaceResult<HashSet<String>>;
/// Return uid, state and attributes of the object identified by its owner,
/// and possibly by its attributes and/or its `state`
async fn find(
&self,
researched_attributes: Option<&Attributes>,
state: Option<State>,
user: &str,
user_must_be_owner: bool,
vendor_id: &str,
) -> InterfaceResult<Vec<(String, State, Attributes)>>;
/// Return (uid, state, attributes) for every object whose
/// `key_wrapping_data.encryption_key_information.unique_identifier` equals
/// `wrapping_key_uid`. Used by key rotation to re-wrap all objects protected by
/// the rotated key.
///
/// SQL backends should implement an efficient JSON-path query.
/// HSM backends should return an empty list (HSM keys are non-extractable and
/// are never wrapped in KMIP format).
async fn find_wrapped_by(
&self,
_wrapping_key_uid: &str,
_user: &str,
) -> InterfaceResult<Vec<(String, State, Attributes)>>;
/// Return UIDs of all Active objects that have a `rotate_interval > 0` and whose
/// next rotation instant is ≤ `now`.
///
/// The next rotation instant is computed as:
/// - `rotate_date + rotate_interval` (if `rotate_date` is set), or
/// - `initial_date + rotate_interval + rotate_offset` (if `rotate_date` is None)
///
/// Each entry is `(uid, owner)` so the auto-rotation scheduler can issue a
/// Re-Key on behalf of the correct owner without an additional DB round-trip.
///
/// Backends that do not support date-driven rotation should return an empty list.
async fn find_due_for_rotation(
&self,
_now: OffsetDateTime,
) -> InterfaceResult<Vec<(String, String)>>;
/// Find objects by their `x-rotate-name` vendor attribute.
///
/// Optionally filter by:
/// - `generation`: match `x-rotate-generation` exactly
/// - `latest`: match `x-rotate-latest` flag
/// - `owner`: match the object owner
///
/// Returns a list of `(uid, attributes)` pairs.
async fn find_by_rotate_name(
&self,
_name: &str,
_generation: Option<i32>,
_owner: &str,
) -> InterfaceResult<Vec<(String, Attributes)>>;
/// Set the human-readable label on a key object.
///
/// For HSM backends this writes `CKA_LABEL` via `C_SetAttributeValue`.
/// The SQL backends ignore this call (labels are carried in the KMIP `Name` attribute
/// and managed separately). Default: no-op.
async fn set_key_label(&self, _uid: &str, _label: &str) -> InterfaceResult<()> {
Ok(())
}
/// Rewrite the PKCS#11 rotation dates on an HSM key identified by `uid`.
///
/// `start_date` and `end_date` are stored as `CKA_START_DATE` / `CKA_END_DATE`.
/// SQL backends ignore this call. Default: no-op.
async fn set_key_rotation_dates(
&self,
_uid: &str,
_start_date: Option<time::Date>,
_end_date: Option<time::Date>,
) -> InterfaceResult<()> {
Ok(())
}
/// Count all objects that are **not** in a terminal (destroyed) state.
///
/// # Purpose — metrics only
///
/// This method is called exclusively by the OTEL metrics layer to feed the
/// `kms.objects.total` gauge. It deliberately skips all user/permission
/// filters so the result reflects the true server-wide object inventory,
/// not just the subset visible to a particular caller.
///
/// **Never expose the result to client requests** — it bypasses access control.
///
/// # Why a default of `Ok(0)`?
///
/// Adding a required method to this trait would force every backend
/// (SQL, Redis, HSM stubs) to implement it in the same commit. The default
/// lets backends compile immediately; each one should replace it with a
/// real implementation when ready. A `TODO` comment is added at each
/// call site that still uses the default.
async fn count_all_non_destroyed(&self) -> InterfaceResult<u64> {
warn!(
"count_all_non_destroyed not implemented for this ObjectsStore backend — \
kms.objects.total will read 0 until a real implementation is provided"
);
Ok(0)
}
/// Returns the count of non-destroyed key objects (`SymmetricKey`, `PrivateKey`,
/// `PublicKey`, `SplitKey`) across this store.
///
/// "Non-destroyed" means state ∉ {`Destroyed`, `Destroyed_Compromised`}.
/// This covers `PreActive`, `Active`, `Deactivated`, and `Compromised` keys —
/// all states in which the key material is still present.
///
/// Backends should override this with a real implementation. The default
/// logs a warning and returns 0 so that the gauge shows a valid lower-bound
/// until a proper implementation is provided.
async fn count_non_destroyed_keys(&self) -> InterfaceResult<u64> {
warn!(
"count_non_destroyed_keys not implemented for this ObjectsStore backend — \
kms.keys.active.count will read 0 until a real implementation is provided"
);
Ok(0)
}
/// Perform an authoritative reconciliation of any cached object-count
/// counters maintained by this store.
///
/// For in-memory counters (e.g. Redis `INCRBY` counters) this should
/// recompute the true count from the authoritative data source and overwrite
/// the cached value. For SQL backends this is a no-op because every COUNT(*)
/// query is already authoritative.
///
/// Called by the slow-path cron loop (every 5 minutes) to prevent counter
/// drift from accumulating due to partial failures.
async fn reconcile_counts(&self) -> InterfaceResult<()> {
Ok(())
}
}