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}