Skip to main content

ankurah_core/
policy.rs

1use crate::util::Iterable;
2use crate::{
3    entity::Entity,
4    error::ValidationError,
5    node::{ContextData, Node, NodeInner, WeakNode},
6    property::PropertyError,
7    proto::{self},
8    storage::StorageEngine,
9};
10use ankql::{ast::Predicate, error::ParseError};
11use ankurah_proto::Attested;
12use async_trait::async_trait;
13use thiserror::Error;
14use tracing::debug;
15/// The result of a policy check. Currently just Allow/Deny, but will support Trace in the future
16#[derive(Debug, Error)]
17pub enum AccessDenied {
18    #[error("Access denied by policy: {0}")]
19    ByPolicy(&'static str),
20    #[error("Access denied by collection: {0}")]
21    CollectionDenied(proto::CollectionId),
22    #[error("Access denied by property error: {0}")]
23    PropertyError(Box<PropertyError>),
24    #[error("Access denied by parse error: {0}")]
25    ParseError(ParseError),
26    #[error("Insufficient attestation")]
27    InsufficientAttestation,
28}
29
30impl From<PropertyError> for AccessDenied {
31    fn from(error: PropertyError) -> Self { AccessDenied::PropertyError(Box::new(error)) }
32}
33impl From<ParseError> for AccessDenied {
34    fn from(error: ParseError) -> Self { AccessDenied::ParseError(error) }
35}
36
37#[cfg(feature = "wasm")]
38impl From<AccessDenied> for wasm_bindgen::JsValue {
39    fn from(error: AccessDenied) -> Self { wasm_bindgen::JsValue::from_str(&error.to_string()) }
40}
41
42impl AccessDenied {}
43
44/// PolicyAgents control access to resources, by:
45/// - signing requests which are sent to other nodes - this may come in the form of a bearer token, or a signature, or some other arbitrary method of authentication as defined by the PolicyAgent
46/// - checking access for requests. If approved, yield a ContextData
47/// - attesting events for requests that were approved
48/// - validating attestations for events
49#[async_trait]
50pub trait PolicyAgent: Clone + Send + Sync + 'static {
51    /// The context type that will be used for all resource requests.
52    /// This will typically represent a user or service account.
53    type ContextData: ContextData;
54
55    /// Called after the Node is fully constructed, giving the PolicyAgent a weak reference to its owning node.
56    /// Use this to start background tasks (file watchers, policy subscriptions) that need the node.
57    fn on_node_ready<SE: StorageEngine + Send + Sync + 'static>(&self, _node: WeakNode<SE, Self>) {}
58
59    /// Create relevant auth data for a given request
60    /// This could be a JWT or a cryptographic signature, or some other arbitrary method of authentication as defined by the PolicyAgent
61    fn sign_request<SE: StorageEngine, C>(
62        &self,
63        node: &NodeInner<SE, Self>,
64        cdata: &C,
65        request: &proto::NodeRequest,
66    ) -> Result<Vec<proto::AuthData>, AccessDenied>
67    where
68        C: Iterable<Self::ContextData>;
69
70    /// Reverse of sign_request. This will typically parse + validate the auth data and return a ContextData if valid
71    /// optionally, the PolicyAgent may introspect the request directly for signature validation, or other policy checks
72    /// Note that check_read and check_write will be called with the ContextData as well if the request is approved
73    /// Meaning that the PolicyAgent need not necessarily introspect the request directly here if it doesn't want to.
74    async fn check_request<SE: StorageEngine, A>(
75        &self,
76        node: &Node<SE, Self>,
77        auth: &A,
78        request: &proto::NodeRequest,
79    ) -> Result<Vec<Self::ContextData>, ValidationError>
80    where
81        Self: Sized,
82        A: Iterable<proto::AuthData> + Send + Sync;
83
84    /// Check the event and optionally return an attestation
85    /// This could be used to attest that the event has passed the policy check for a given context
86    /// or you could just return None if you don't want to attest to the event
87    /// entity_before: Entity state before the event is applied
88    /// entity_after: Entity state after the event has been applied (allows inspection of resulting state)
89    fn check_event<SE: StorageEngine>(
90        &self,
91        node: &Node<SE, Self>,
92        cdata: &Self::ContextData,
93        entity_before: &Entity,
94        entity_after: &Entity,
95        event: &proto::Event,
96    ) -> Result<Option<proto::Attestation>, AccessDenied>;
97
98    /// Validate an event attestation
99    /// This could be used to validate that the event has sufficient attestation as to be trusted
100    fn validate_received_event<SE: StorageEngine>(
101        &self,
102        node: &Node<SE, Self>,
103        received_from_node: &proto::EntityId,
104        event: &Attested<proto::Event>,
105    ) -> Result<(), AccessDenied>;
106
107    /// Attest a state which the caller asserts is valid. Implementation may return None if no attestation is required
108    fn attest_state<SE: StorageEngine>(&self, node: &Node<SE, Self>, state: &proto::EntityState) -> Option<proto::Attestation>;
109
110    fn validate_received_state<SE: StorageEngine>(
111        &self,
112        node: &Node<SE, Self>,
113        received_from_node: &proto::EntityId,
114        state: &Attested<proto::EntityState>,
115    ) -> Result<(), AccessDenied>;
116
117    // For checking if a context can access a collection
118    fn can_access_collection<C>(&self, data: &C, collection: &proto::CollectionId) -> Result<(), AccessDenied>
119    where C: Iterable<Self::ContextData>;
120
121    /// Filter a predicate based on the context data
122    fn filter_predicate<C>(&self, data: &C, collection: &proto::CollectionId, predicate: Predicate) -> Result<Predicate, AccessDenied>
123    where C: Iterable<Self::ContextData>;
124
125    /// Check if a context can read an entity
126    /// If the policy agent wants to inspect the entity state, it can do so with either TemporaryEntity::new or entityset.with_state
127    /// Optimization: Consider adding a common trait implemented by Entity and TemporaryEntity returned by entityset.get_evaluation_entity that
128    /// returns a real entity if resident, falling back to a temporary entity if not. (as the former case would save cycles creating/populating the backends)
129    fn check_read<C>(
130        &self,
131        data: &C,
132        id: &proto::EntityId,
133        collection: &proto::CollectionId,
134        state: &proto::State,
135    ) -> Result<(), AccessDenied>
136    where
137        C: Iterable<Self::ContextData>;
138
139    /// Check if a context can read an event
140    fn check_read_event<C>(&self, data: &C, event: &Attested<proto::Event>) -> Result<(), AccessDenied>
141    where C: Iterable<Self::ContextData>;
142
143    /// Check if a context can edit an entity
144    fn check_write(&self, data: &Self::ContextData, entity: &Entity, event: Option<&proto::Event>) -> Result<(), AccessDenied>;
145
146    /// Validate a lineage attestation from a peer
147    /// This validates that the relation attestation correctly describes the lineage between two entity heads
148    fn validate_causal_assertion<SE: StorageEngine>(
149        &self,
150        node: &Node<SE, Self>,
151        peer_id: &proto::EntityId,
152        head_relation: &proto::CausalAssertion,
153    ) -> Result<(), AccessDenied>;
154
155    // fn check_write_event(&self, data: &Self::ContextData, entity: &Entity, event: &proto::Event) -> Result<(), AccessDenied>;
156
157    // // For checking if a context can subscribe to changes
158    // fn can_subscribe(&self, data: &Self::ContextData, collection: &CollectionId, predicate: &Predicate) -> AccessResult;
159
160    // // For checking if a context can communicate with another node
161    // fn can_communicate_with_node(&self, data: &Self::ContextData, node_id: &ID) -> AccessResult;
162}
163
164/// A policy agent that allows all operations
165#[derive(Clone)]
166pub struct PermissiveAgent {}
167
168impl Default for PermissiveAgent {
169    fn default() -> Self { Self::new() }
170}
171
172impl PermissiveAgent {
173    pub fn new() -> Self { Self {} }
174}
175
176#[async_trait]
177impl PolicyAgent for PermissiveAgent {
178    type ContextData = &'static DefaultContext;
179
180    /// Create relevant auth data for a given request
181    fn sign_request<SE: StorageEngine, C>(
182        &self,
183        _node: &NodeInner<SE, Self>,
184        cdata: &C,
185        _request: &proto::NodeRequest,
186    ) -> Result<Vec<proto::AuthData>, AccessDenied>
187    where
188        C: Iterable<Self::ContextData>,
189    {
190        debug!("PermissiveAgent sign_request: {:?}", _request);
191        // Create one AuthData per context (though PermissiveAgent doesn't really use them)
192        Ok(cdata.iterable().map(|_| proto::AuthData(vec![])).collect())
193    }
194
195    /// Validate auth data and yield the context data if valid
196    async fn check_request<SE: StorageEngine, A>(
197        &self,
198        _node: &Node<SE, Self>,
199        auth: &A,
200        _request: &proto::NodeRequest,
201    ) -> Result<Vec<Self::ContextData>, ValidationError>
202    where
203        A: Iterable<proto::AuthData> + Send + Sync,
204    {
205        // PermissiveAgent accepts all auth attempts and returns one context per auth
206        Ok(auth.iterable().map(|_| DEFAULT_CONTEXT).collect())
207    }
208
209    /// Create an attestation for an event
210    fn check_event<SE: StorageEngine>(
211        &self,
212        _node: &Node<SE, Self>,
213        _cdata: &Self::ContextData,
214        _entity_before: &Entity,
215        _entity_after: &Entity,
216        _event: &proto::Event,
217    ) -> Result<Option<proto::Attestation>, AccessDenied> {
218        Ok(None)
219    }
220
221    fn validate_received_event<SE: StorageEngine>(
222        &self,
223        _node: &Node<SE, Self>,
224        _from_node: &proto::EntityId,
225        _event: &proto::Attested<proto::Event>,
226    ) -> Result<(), AccessDenied> {
227        Ok(())
228    }
229
230    fn attest_state<SE: StorageEngine>(&self, _node: &Node<SE, Self>, _state: &proto::EntityState) -> Option<proto::Attestation> {
231        // This PolicyAgent does not require attestation, so we return None
232        // Client/Server policy agents may also return None and defer to the server identity to validate the received state
233        None
234    }
235
236    fn validate_received_state<SE: StorageEngine>(
237        &self,
238        _node: &Node<SE, Self>,
239        _from_node: &proto::EntityId,
240        _state: &Attested<proto::EntityState>,
241    ) -> Result<(), AccessDenied> {
242        // This PolicyAgent does not require validation, so we return Ok
243        // Client/Server policy agents may use the _from_node to validate the received state rather than an attestation
244        Ok(())
245    }
246
247    fn can_access_collection<C>(&self, _data: &C, _collection: &proto::CollectionId) -> Result<(), AccessDenied>
248    where C: Iterable<Self::ContextData> {
249        // PermissiveAgent allows access if any context is provided
250        Ok(())
251    }
252
253    fn check_read<C>(
254        &self,
255        _data: &C,
256        _id: &proto::EntityId,
257        _collection: &proto::CollectionId,
258        _state: &proto::State,
259    ) -> Result<(), AccessDenied>
260    where
261        C: Iterable<Self::ContextData>,
262    {
263        // PermissiveAgent allows access if any context is provided
264        Ok(())
265    }
266
267    fn check_read_event<C>(&self, _data: &C, _event: &Attested<proto::Event>) -> Result<(), AccessDenied>
268    where C: Iterable<Self::ContextData> {
269        // PermissiveAgent allows access if any context is provided
270        Ok(())
271    }
272
273    fn check_write(&self, _context: &Self::ContextData, _entity: &Entity, _event: Option<&proto::Event>) -> Result<(), AccessDenied> {
274        Ok(())
275    }
276
277    fn validate_causal_assertion<SE: StorageEngine>(
278        &self,
279        _node: &Node<SE, Self>,
280        _peer_id: &proto::EntityId,
281        _head_relation: &proto::CausalAssertion,
282    ) -> Result<(), AccessDenied> {
283        // PermissiveAgent trusts all causal assertions
284        Ok(())
285    }
286
287    fn filter_predicate<C>(&self, _data: &C, _collection: &proto::CollectionId, predicate: Predicate) -> Result<Predicate, AccessDenied>
288    where C: Iterable<Self::ContextData> {
289        // PermissiveAgent allows access if any context is provided
290        Ok(predicate)
291    }
292
293    // fn can_read_entity(&self, _context: &Self::ContextData, _entity: &Entity) -> AccessResult { AccessResult::Allow }
294
295    // fn can_modify_entity(&self, _context: &Self::ContextData, _collection: &CollectionId, _id: &ID) -> AccessResult { AccessResult::Allow }
296
297    // fn can_create_in_collection(&self, _context: &Self::ContextData, _collection: &CollectionId) -> AccessResult { AccessResult::Allow }
298
299    // fn can_subscribe(&self, _context: &Self::ContextData, _collection: &CollectionId, _predicate: &Predicate) -> AccessResult {
300    //     AccessResult::Allow
301    // }
302
303    // fn can_communicate_with_node(&self, _context: &Self::ContextData, _node_id: &ID) -> AccessResult { AccessResult::Allow }
304}
305
306/// A default context that is used when no context is needed
307
308#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
309pub struct DefaultContext {}
310pub static DEFAULT_CONTEXT: &DefaultContext = &DefaultContext {};
311
312impl Default for DefaultContext {
313    fn default() -> Self { Self::new() }
314}
315
316impl DefaultContext {
317    pub fn new() -> Self { Self {} }
318}
319
320#[async_trait]
321impl ContextData for &'static DefaultContext {}