Skip to main content

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}