ankurah_core/
policy.rs

1use crate::util::Iterable;
2use crate::{
3    entity::Entity,
4    error::ValidationError,
5    node::{ContextData, Node, NodeInner},
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    /// Create relevant auth data for a given request
56    /// This could be a JWT or a cryptographic signature, or some other arbitrary method of authentication as defined by the PolicyAgent
57    fn sign_request<SE: StorageEngine, C>(
58        &self,
59        node: &NodeInner<SE, Self>,
60        cdata: &C,
61        request: &proto::NodeRequest,
62    ) -> Result<Vec<proto::AuthData>, AccessDenied>
63    where
64        C: Iterable<Self::ContextData>;
65
66    /// Reverse of sign_request. This will typically parse + validate the auth data and return a ContextData if valid
67    /// optionally, the PolicyAgent may introspect the request directly for signature validation, or other policy checks
68    /// Note that check_read and check_write will be called with the ContextData as well if the request is approved
69    /// Meaning that the PolicyAgent need not necessarily introspect the request directly here if it doesn't want to.
70    async fn check_request<SE: StorageEngine, A>(
71        &self,
72        node: &Node<SE, Self>,
73        auth: &A,
74        request: &proto::NodeRequest,
75    ) -> Result<Vec<Self::ContextData>, ValidationError>
76    where
77        Self: Sized,
78        A: Iterable<proto::AuthData> + Send + Sync;
79
80    /// Check the event and optionally return an attestation
81    /// This could be used to attest that the event has passed the policy check for a given context
82    /// or you could just return None if you don't want to attest to the event
83    /// entity_before: Entity state before the event is applied
84    /// entity_after: Entity state after the event has been applied (allows inspection of resulting state)
85    fn check_event<SE: StorageEngine>(
86        &self,
87        node: &Node<SE, Self>,
88        cdata: &Self::ContextData,
89        entity_before: &Entity,
90        entity_after: &Entity,
91        event: &proto::Event,
92    ) -> Result<Option<proto::Attestation>, AccessDenied>;
93
94    /// Validate an event attestation
95    /// This could be used to validate that the event has sufficient attestation as to be trusted
96    fn validate_received_event<SE: StorageEngine>(
97        &self,
98        node: &Node<SE, Self>,
99        received_from_node: &proto::EntityId,
100        event: &Attested<proto::Event>,
101    ) -> Result<(), AccessDenied>;
102
103    /// Attest a state which the caller asserts is valid. Implementation may return None if no attestation is required
104    fn attest_state<SE: StorageEngine>(&self, node: &Node<SE, Self>, state: &proto::EntityState) -> Option<proto::Attestation>;
105
106    fn validate_received_state<SE: StorageEngine>(
107        &self,
108        node: &Node<SE, Self>,
109        received_from_node: &proto::EntityId,
110        state: &Attested<proto::EntityState>,
111    ) -> Result<(), AccessDenied>;
112
113    // For checking if a context can access a collection
114    fn can_access_collection<C>(&self, data: &C, collection: &proto::CollectionId) -> Result<(), AccessDenied>
115    where C: Iterable<Self::ContextData>;
116
117    /// Filter a predicate based on the context data
118    fn filter_predicate<C>(&self, data: &C, collection: &proto::CollectionId, predicate: Predicate) -> Result<Predicate, AccessDenied>
119    where C: Iterable<Self::ContextData>;
120
121    /// Check if a context can read an entity
122    /// If the policy agent wants to inspect the entity state, it can do so with either TemporaryEntity::new or entityset.with_state
123    /// Optimization: Consider adding a common trait implemented by Entity and TemporaryEntity returned by entityset.get_evaluation_entity that
124    /// 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)
125    fn check_read<C>(
126        &self,
127        data: &C,
128        id: &proto::EntityId,
129        collection: &proto::CollectionId,
130        state: &proto::State,
131    ) -> Result<(), AccessDenied>
132    where
133        C: Iterable<Self::ContextData>;
134
135    /// Check if a context can read an event
136    fn check_read_event<C>(&self, data: &C, event: &Attested<proto::Event>) -> Result<(), AccessDenied>
137    where C: Iterable<Self::ContextData>;
138
139    /// Check if a context can edit an entity
140    fn check_write(&self, data: &Self::ContextData, entity: &Entity, event: Option<&proto::Event>) -> Result<(), AccessDenied>;
141
142    /// Validate a lineage attestation from a peer
143    /// This validates that the relation attestation correctly describes the lineage between two entity heads
144    fn validate_causal_assertion<SE: StorageEngine>(
145        &self,
146        node: &Node<SE, Self>,
147        peer_id: &proto::EntityId,
148        head_relation: &proto::CausalAssertion,
149    ) -> Result<(), AccessDenied>;
150
151    // fn check_write_event(&self, data: &Self::ContextData, entity: &Entity, event: &proto::Event) -> Result<(), AccessDenied>;
152
153    // // For checking if a context can subscribe to changes
154    // fn can_subscribe(&self, data: &Self::ContextData, collection: &CollectionId, predicate: &Predicate) -> AccessResult;
155
156    // // For checking if a context can communicate with another node
157    // fn can_communicate_with_node(&self, data: &Self::ContextData, node_id: &ID) -> AccessResult;
158}
159
160/// A policy agent that allows all operations
161#[derive(Clone)]
162pub struct PermissiveAgent {}
163
164impl Default for PermissiveAgent {
165    fn default() -> Self { Self::new() }
166}
167
168impl PermissiveAgent {
169    pub fn new() -> Self { Self {} }
170}
171
172#[async_trait]
173impl PolicyAgent for PermissiveAgent {
174    type ContextData = &'static DefaultContext;
175
176    /// Create relevant auth data for a given request
177    fn sign_request<SE: StorageEngine, C>(
178        &self,
179        _node: &NodeInner<SE, Self>,
180        cdata: &C,
181        _request: &proto::NodeRequest,
182    ) -> Result<Vec<proto::AuthData>, AccessDenied>
183    where
184        C: Iterable<Self::ContextData>,
185    {
186        debug!("PermissiveAgent sign_request: {:?}", _request);
187        // Create one AuthData per context (though PermissiveAgent doesn't really use them)
188        Ok(cdata.iterable().map(|_| proto::AuthData(vec![])).collect())
189    }
190
191    /// Validate auth data and yield the context data if valid
192    async fn check_request<SE: StorageEngine, A>(
193        &self,
194        _node: &Node<SE, Self>,
195        auth: &A,
196        _request: &proto::NodeRequest,
197    ) -> Result<Vec<Self::ContextData>, ValidationError>
198    where
199        A: Iterable<proto::AuthData> + Send + Sync,
200    {
201        // PermissiveAgent accepts all auth attempts and returns one context per auth
202        Ok(auth.iterable().map(|_| DEFAULT_CONTEXT).collect())
203    }
204
205    /// Create an attestation for an event
206    fn check_event<SE: StorageEngine>(
207        &self,
208        _node: &Node<SE, Self>,
209        _cdata: &Self::ContextData,
210        _entity_before: &Entity,
211        _entity_after: &Entity,
212        _event: &proto::Event,
213    ) -> Result<Option<proto::Attestation>, AccessDenied> {
214        Ok(None)
215    }
216
217    fn validate_received_event<SE: StorageEngine>(
218        &self,
219        _node: &Node<SE, Self>,
220        _from_node: &proto::EntityId,
221        _event: &proto::Attested<proto::Event>,
222    ) -> Result<(), AccessDenied> {
223        Ok(())
224    }
225
226    fn attest_state<SE: StorageEngine>(&self, _node: &Node<SE, Self>, _state: &proto::EntityState) -> Option<proto::Attestation> {
227        // This PolicyAgent does not require attestation, so we return None
228        // Client/Server policy agents may also return None and defer to the server identity to validate the received state
229        None
230    }
231
232    fn validate_received_state<SE: StorageEngine>(
233        &self,
234        _node: &Node<SE, Self>,
235        _from_node: &proto::EntityId,
236        _state: &Attested<proto::EntityState>,
237    ) -> Result<(), AccessDenied> {
238        // This PolicyAgent does not require validation, so we return Ok
239        // Client/Server policy agents may use the _from_node to validate the received state rather than an attestation
240        Ok(())
241    }
242
243    fn can_access_collection<C>(&self, _data: &C, _collection: &proto::CollectionId) -> Result<(), AccessDenied>
244    where C: Iterable<Self::ContextData> {
245        // PermissiveAgent allows access if any context is provided
246        Ok(())
247    }
248
249    fn check_read<C>(
250        &self,
251        _data: &C,
252        _id: &proto::EntityId,
253        _collection: &proto::CollectionId,
254        _state: &proto::State,
255    ) -> Result<(), AccessDenied>
256    where
257        C: Iterable<Self::ContextData>,
258    {
259        // PermissiveAgent allows access if any context is provided
260        Ok(())
261    }
262
263    fn check_read_event<C>(&self, _data: &C, _event: &Attested<proto::Event>) -> Result<(), AccessDenied>
264    where C: Iterable<Self::ContextData> {
265        // PermissiveAgent allows access if any context is provided
266        Ok(())
267    }
268
269    fn check_write(&self, _context: &Self::ContextData, _entity: &Entity, _event: Option<&proto::Event>) -> Result<(), AccessDenied> {
270        Ok(())
271    }
272
273    fn validate_causal_assertion<SE: StorageEngine>(
274        &self,
275        _node: &Node<SE, Self>,
276        _peer_id: &proto::EntityId,
277        _head_relation: &proto::CausalAssertion,
278    ) -> Result<(), AccessDenied> {
279        // PermissiveAgent trusts all causal assertions
280        Ok(())
281    }
282
283    fn filter_predicate<C>(&self, _data: &C, _collection: &proto::CollectionId, predicate: Predicate) -> Result<Predicate, AccessDenied>
284    where C: Iterable<Self::ContextData> {
285        // PermissiveAgent allows access if any context is provided
286        Ok(predicate)
287    }
288
289    // fn can_read_entity(&self, _context: &Self::ContextData, _entity: &Entity) -> AccessResult { AccessResult::Allow }
290
291    // fn can_modify_entity(&self, _context: &Self::ContextData, _collection: &CollectionId, _id: &ID) -> AccessResult { AccessResult::Allow }
292
293    // fn can_create_in_collection(&self, _context: &Self::ContextData, _collection: &CollectionId) -> AccessResult { AccessResult::Allow }
294
295    // fn can_subscribe(&self, _context: &Self::ContextData, _collection: &CollectionId, _predicate: &Predicate) -> AccessResult {
296    //     AccessResult::Allow
297    // }
298
299    // fn can_communicate_with_node(&self, _context: &Self::ContextData, _node_id: &ID) -> AccessResult { AccessResult::Allow }
300}
301
302/// A default context that is used when no context is needed
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
305pub struct DefaultContext {}
306pub static DEFAULT_CONTEXT: &DefaultContext = &DefaultContext {};
307
308impl Default for DefaultContext {
309    fn default() -> Self { Self::new() }
310}
311
312impl DefaultContext {
313    pub fn new() -> Self { Self {} }
314}
315
316#[async_trait]
317impl ContextData for &'static DefaultContext {}