Skip to main content

corium_authz/
source.rs

1//! Where a compiled policy comes from: a snapshot of the authz database.
2//!
3//! The trait is deliberately thin — one method returning a [`Db`] value — so
4//! every surface can supply the snapshot it already holds: the transactor
5//! reads its own `DbState`, a peer server reads the database value its
6//! connection keeps in sync, and tests hand over a value they built in
7//! memory. Nothing here fetches over the network on the request path; a
8//! snapshot is a cheap clone of an immutable value.
9
10use std::sync::{Arc, RwLock};
11use std::time::Duration;
12
13use corium_db::Db;
14
15/// How long a source with no change notification waits between polls.
16pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_secs(2);
17
18/// Failure to obtain a policy snapshot.
19#[derive(Clone, Debug, thiserror::Error)]
20pub enum SourceError {
21    /// The configured authz database does not exist on this surface.
22    #[error("authorization database {0:?} is not available")]
23    Unavailable(String),
24    /// The snapshot could not be read.
25    #[error("cannot read authorization database {db:?}: {reason}")]
26    Failed {
27        /// Database name.
28        db: String,
29        /// Underlying failure.
30        reason: String,
31    },
32}
33
34/// Supplies snapshots of the authorization database.
35#[tonic::async_trait]
36pub trait PolicySource: Send + Sync + 'static {
37    /// Name of the authz database, for errors and audit lines.
38    fn name(&self) -> &str;
39
40    /// The current snapshot.
41    ///
42    /// # Errors
43    /// Returns [`SourceError`] when the database is missing or unreadable;
44    /// the authorizer fails closed on it.
45    async fn snapshot(&self) -> Result<Db, SourceError>;
46
47    /// Resolves when the source may have advanced past `basis_t`.
48    ///
49    /// The default polls. Sources with a change signal (a transactor's basis
50    /// watch, a peer's tx-report broadcast) override this so a policy change
51    /// propagates immediately instead of within one poll interval.
52    async fn changed(&self, basis_t: u64) {
53        let _ = basis_t;
54        tokio::time::sleep(DEFAULT_POLL_INTERVAL).await;
55    }
56}
57
58/// A source over a database value held in memory: the embedded case, and what
59/// tests and `corium authz check --policy` use.
60pub struct MemoryPolicySource {
61    name: String,
62    db: RwLock<Db>,
63}
64
65impl MemoryPolicySource {
66    /// Wraps `db` under the name `name`.
67    pub fn new(name: impl Into<String>, db: Db) -> Self {
68        Self {
69            name: name.into(),
70            db: RwLock::new(db),
71        }
72    }
73
74    /// Replaces the snapshot, as a transaction against the authz database
75    /// would.
76    pub fn set(&self, db: Db) {
77        *self
78            .db
79            .write()
80            .unwrap_or_else(std::sync::PoisonError::into_inner) = db;
81    }
82
83    /// Wraps this source for use with [`crate::SystemDbAuthorizer`].
84    #[must_use]
85    pub fn shared(self) -> Arc<dyn PolicySource> {
86        Arc::new(self)
87    }
88}
89
90#[tonic::async_trait]
91impl PolicySource for MemoryPolicySource {
92    fn name(&self) -> &str {
93        &self.name
94    }
95
96    async fn snapshot(&self) -> Result<Db, SourceError> {
97        Ok(self
98            .db
99            .read()
100            .unwrap_or_else(std::sync::PoisonError::into_inner)
101            .clone())
102    }
103}