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