secret_manager/backend.rs
1use async_trait::async_trait;
2use std::time::SystemTime;
3
4pub(super) const EPOCH_CURSOR: SystemTime = SystemTime::UNIX_EPOCH;
5
6/// A versioned secret key record as returned by a [`SecretBackend`].
7///
8/// The ciphertext in `key_bytes` must be decrypted with the matching [`KeyEncryptor`](crate::KeyEncryptor)
9/// before being placed into an [`InMemorySecretGroup`](crate::InMemorySecretGroup).
10#[derive(Clone)]
11pub struct KeyRecord {
12 /// Monotonic database ID, used as a tie-breaker for polling cursors.
13 pub id: i64,
14 /// Ring-buffer slot index (fits in `u8` for the default 256-slot ring).
15 pub version: u8,
16 /// Encrypted key bytes (ciphertext). Decryption is the caller's responsibility.
17 pub key_bytes: Vec<u8>,
18 /// Nonce used during encryption, or `None` for KMS / no-op.
19 pub nonce: Option<Vec<u8>>,
20 /// Version of the key-encryption key used (0 = no-op/plaintext).
21 pub encryption_key_version: u8,
22 /// When this key became (or will become) active.
23 pub activated_at: SystemTime,
24}
25
26/// Read-side storage contract used by [`SecretSyncer`](crate::SecretSyncer).
27///
28/// Implement this trait (alongside [`SecretRotationBackend`](crate::SecretRotationBackend) if
29/// the same backend serves both roles) to connect `SecretSyncer` to your storage layer.
30/// Built-in implementations are available behind feature flags:
31/// `DieselPgSecretBackend` (`pg-diesel-async`) and `SqlxPgSecretBackend` (`pg-sqlx`).
32#[async_trait]
33pub trait SecretBackend: Send + Sync + 'static {
34 /// The error type returned on backend failures.
35 type Error: std::error::Error + Send + Sync + 'static;
36
37 /// Load **all** keys for `group_id`, ordered by `activated_at` ascending.
38 ///
39 /// Called once at startup by [`SecretSyncer::initial_load`](crate::SecretSyncer::initial_load).
40 async fn load_all(&self, group_id: &str) -> Result<Vec<KeyRecord>, Self::Error>;
41
42 /// Return keys inserted after `(since_time, since_id)`, ordered by `activated_at` ascending.
43 ///
44 /// The cursor is a `(SystemTime, i64)` pair — both components must be strictly greater
45 /// than the cursor for a record to be returned, ensuring no record is delivered twice
46 /// even when multiple records share the same `activated_at`.
47 async fn poll_new(
48 &self,
49 group_id: &str,
50 since_time: SystemTime,
51 since_id: i64,
52 ) -> Result<Vec<KeyRecord>, Self::Error>;
53}