Skip to main content

aranya_daemon/
actions.rs

1//! Aranya graph actions/effects API.
2
3use std::{future::Future, marker::PhantomData};
4
5use anyhow::{Context, Result};
6use aranya_crypto::{
7    policy::{LabelId, RoleId},
8    DeviceId, Random as _, Rng,
9};
10use aranya_keygen::PublicKeys;
11use aranya_policy_ifgen::{Actionable, VmEffect};
12use aranya_policy_text::Text;
13#[cfg(feature = "afc")]
14use aranya_runtime::NullSink;
15use aranya_runtime::{GraphId, PolicyStore, Session, StorageProvider, VmPolicy};
16use futures_util::TryFutureExt as _;
17use tracing::{debug, instrument, warn, Instrument};
18
19use crate::{
20    aranya::Client,
21    policy::{self, ChanOp, Effect, PublicKeyBundle, RoleManagementPerm, SimplePerm},
22    vm_policy::{MsgSink, VecSink},
23};
24
25/// Container for complex AQC channel creation results.
26#[derive(Debug)]
27pub(crate) struct SessionData {
28    /// The serialized messages
29    #[cfg(feature = "afc")]
30    pub ctrl: Vec<Box<[u8]>>,
31    /// The effects produced
32    pub effects: Vec<Effect>,
33}
34
35/// Functions related to Aranya actions
36impl<PS, SP, CE> Client<PS, SP>
37where
38    PS: PolicyStore<Policy = VmPolicy<CE>, Effect = VmEffect> + Send + 'static,
39    SP: StorageProvider + Send + 'static,
40    CE: aranya_crypto::Engine + Send + Sync + 'static,
41{
42    /// Creates the team.
43    /// Creates a new graph, adds the `CreateTeam` command to the root of the graph.
44    /// Returns the [`GraphId`] of the newly created graph.
45    #[instrument(skip_all)]
46    pub async fn create_team(
47        &self,
48        owner_keys: PublicKeyBundle,
49        nonce: Option<&[u8]>,
50    ) -> Result<(GraphId, Vec<Effect>)> {
51        let mut sink = VecSink::new();
52        let policy_data = &[0u8];
53        let act = policy::create_team(
54            owner_keys,
55            nonce.unwrap_or(&<[u8; 16]>::random(Rng)).to_vec(),
56        );
57        let id = {
58            let mut client = self.lock_aranya().await;
59            act.with_action(|act| client.new_graph(policy_data, act, &mut sink))
60                .context("unable to create new team")?
61        };
62        Ok((id, sink.collect()?))
63    }
64
65    /// Returns an implementation of [`Actions`] for a particular
66    /// storage.
67    #[instrument(skip_all, fields(%graph_id))]
68    pub fn actions(&self, graph_id: GraphId) -> impl Actions<PS, SP, CE> {
69        ActionsImpl {
70            client: self.clone(),
71            graph_id,
72            _eng: PhantomData,
73        }
74    }
75
76    /// Create new ephemeral Session.
77    /// Once the Session has been created, call `session_receive` to add an ephemeral command to the Session.
78    #[instrument(skip_all, fields(%graph_id))]
79    pub(crate) async fn session_new(&self, graph_id: GraphId) -> Result<Session<SP, PS>> {
80        let session = self.lock_aranya().await.session(graph_id)?;
81        Ok(session)
82    }
83
84    /// Receives an ephemeral command from another ephemeral Session.
85    /// Assumes an ephemeral Session has already been created before adding an ephemeral command to the Session.
86    #[instrument(skip_all)]
87    pub(crate) async fn session_receive(
88        &self,
89        session: &mut Session<SP, PS>,
90        command: &[u8],
91    ) -> Result<Vec<Effect>> {
92        let client = self.lock_aranya().await;
93        let mut sink = VecSink::new();
94        session.receive(&client, &mut sink, command)?;
95        Ok(sink.collect()?)
96    }
97}
98
99/// Implements [`Actions`] for a particular storage.
100struct ActionsImpl<PS, SP, CE> {
101    /// Aranya client graph state.
102    client: Client<PS, SP>,
103    /// Aranya graph ID.
104    graph_id: GraphId,
105    /// Crypto engine.
106    _eng: PhantomData<CE>,
107}
108
109impl<PS, SP, CE> Actions<PS, SP, CE> for ActionsImpl<PS, SP, CE>
110where
111    PS: PolicyStore<Policy = VmPolicy<CE>, Effect = VmEffect> + Send + 'static,
112    SP: StorageProvider + Send + 'static,
113    CE: aranya_crypto::Engine + Send + Sync + 'static,
114{
115    async fn call_persistent_action(
116        &self,
117        act: impl Actionable<Interface = policy::Persistent> + Send,
118    ) -> Result<Vec<Effect>> {
119        let mut sink = VecSink::new();
120        // Make sure we drop the lock as quickly as possible.
121        {
122            let mut client = self.client.lock_aranya().await;
123            act.with_action(|act| client.action(self.graph_id, &mut sink, act))?;
124        }
125
126        let total = sink.effects.len();
127        for (i, effect) in sink.effects.iter().enumerate() {
128            debug!(i, total, effect = effect.name.as_str());
129        }
130
131        Ok(sink.collect()?)
132    }
133
134    async fn call_session_action(
135        &self,
136        act: impl Actionable<Interface = policy::Ephemeral> + Send,
137    ) -> Result<SessionData> {
138        let mut sink = VecSink::new();
139        let mut msg_sink = MsgSink::new();
140        {
141            let mut client = self.client.lock_aranya().await;
142            let mut session = client.session(self.graph_id)?;
143            act.with_action(|act| session.action(&client, &mut sink, &mut msg_sink, act))?;
144        }
145        Ok(SessionData {
146            #[cfg(feature = "afc")]
147            ctrl: msg_sink.into_cmds(),
148            effects: sink.collect()?,
149        })
150    }
151}
152
153/// A programmatic API for policy actions.
154pub trait Actions<PS, SP, CE>
155where
156    PS: PolicyStore<Policy = VmPolicy<CE>, Effect = VmEffect> + Send + 'static,
157    SP: StorageProvider + Send + 'static,
158    CE: aranya_crypto::Engine + Send + Sync + 'static,
159{
160    /// Perform a persistent action.
161    fn call_persistent_action(
162        &self,
163        act: impl Actionable<Interface = policy::Persistent> + Send,
164    ) -> impl Future<Output = Result<Vec<Effect>>> + Send;
165
166    #[allow(clippy::type_complexity)]
167    /// Performs a session action.
168    fn call_session_action(
169        &self,
170        act: impl Actionable<Interface = policy::Ephemeral> + Send,
171    ) -> impl Future<Output = Result<SessionData>> + Send;
172
173    /// Invokes `add_device`.
174    #[instrument(skip_all)]
175    fn add_device(
176        &self,
177        keys: PublicKeyBundle,
178        initial_role_id: Option<RoleId>,
179    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
180        self.call_persistent_action(policy::add_device(
181            keys,
182            initial_role_id.map(|id| id.as_base()),
183        ))
184        .in_current_span()
185    }
186
187    /// Invokes `create_role`.
188    #[cfg(feature = "preview")]
189    #[instrument(skip(self))]
190    fn create_role(
191        &self,
192        role_name: Text,
193        owning_role_id: RoleId,
194    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
195        self.call_persistent_action(policy::create_role(role_name, owning_role_id.as_base()))
196            .in_current_span()
197    }
198
199    /// Invokes `delete_role`.
200    #[cfg(feature = "preview")]
201    #[instrument(skip(self))]
202    fn delete_role(&self, role_id: RoleId) -> impl Future<Output = Result<Vec<Effect>>> + Send {
203        self.call_persistent_action(policy::delete_role(role_id.as_base()))
204            .in_current_span()
205    }
206
207    /// Invokes `add_label_managing_role`.
208    #[instrument(skip(self), fields(%label_id, %managing_role_id))]
209    fn add_label_managing_role(
210        &self,
211        label_id: LabelId,
212        managing_role_id: RoleId,
213    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
214        self.call_persistent_action(policy::add_label_managing_role(
215            label_id.as_base(),
216            managing_role_id.as_base(),
217        ))
218        .in_current_span()
219    }
220
221    /// Invokes `add_perm_to_role`.
222    #[instrument(skip(self), fields(%role_id, %perm))]
223    fn add_perm_to_role(
224        &self,
225        role_id: RoleId,
226        perm: SimplePerm,
227    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
228        self.call_persistent_action(policy::add_perm_to_role(role_id.as_base(), perm))
229            .in_current_span()
230    }
231
232    /// Invokes `add_role_owner`.
233    #[instrument(skip(self), fields(%role_id, %new_owning_role_id))]
234    fn add_role_owner(
235        &self,
236        role_id: RoleId,
237        new_owning_role_id: RoleId,
238    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
239        self.call_persistent_action(policy::add_role_owner(
240            role_id.as_base(),
241            new_owning_role_id.as_base(),
242        ))
243        .in_current_span()
244    }
245
246    /// Invokes `assign_label_to_device`.
247    #[instrument(skip(self), fields(%device_id, %label_id, %op))]
248    fn assign_label_to_device(
249        &self,
250        device_id: DeviceId,
251        label_id: LabelId,
252        op: ChanOp,
253    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
254        self.call_persistent_action(policy::assign_label_to_device(
255            device_id.as_base(),
256            label_id.as_base(),
257            op,
258        ))
259        .in_current_span()
260    }
261
262    /// Invokes `assign_role`.
263    #[instrument(skip(self), fields(%device_id, %role_id))]
264    fn assign_role(
265        &self,
266        device_id: DeviceId,
267        role_id: RoleId,
268    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
269        self.call_persistent_action(policy::assign_role(device_id.as_base(), role_id.as_base()))
270            .in_current_span()
271    }
272
273    /// Invokes `assign_role_management_perm`.
274    #[instrument(skip(self), fields(%target_role_id, %managing_role_id, %perm))]
275    fn assign_role_management_perm(
276        &self,
277        target_role_id: RoleId,
278        managing_role_id: RoleId,
279        perm: RoleManagementPerm,
280    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
281        self.call_persistent_action(policy::assign_role_management_perm(
282            target_role_id.as_base(),
283            managing_role_id.as_base(),
284            perm,
285        ))
286        .in_current_span()
287    }
288
289    /// Invokes `change_role`.
290    #[instrument(skip(self), fields(%device_id, %old_role_id, %new_role_id))]
291    fn change_role(
292        &self,
293        device_id: DeviceId,
294        old_role_id: RoleId,
295        new_role_id: RoleId,
296    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
297        self.call_persistent_action(policy::change_role(
298            device_id.as_base(),
299            old_role_id.as_base(),
300            new_role_id.as_base(),
301        ))
302        .in_current_span()
303    }
304
305    /// Invokes `create_label`.
306    #[instrument(skip(self), fields(%name, %managing_role_id))]
307    fn create_label(
308        &self,
309        name: Text,
310        managing_role_id: RoleId,
311    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
312        self.call_persistent_action(policy::create_label(name, managing_role_id.as_base()))
313            .in_current_span()
314    }
315
316    /// Creates a unidirectional AFC channel.
317    #[cfg(feature = "afc")]
318    #[allow(clippy::type_complexity)]
319    #[instrument(skip(self), fields(open_id = %open_id, label_id = %label_id))]
320    fn create_afc_uni_channel_off_graph(
321        &self,
322        open_id: DeviceId,
323        label_id: LabelId,
324    ) -> impl Future<Output = Result<SessionData>> + Send {
325        self.call_session_action(policy::create_afc_uni_channel(
326            open_id.as_base(),
327            label_id.as_base(),
328        ))
329        .in_current_span()
330    }
331
332    /// Invokes `delete_label`.
333    #[instrument(skip(self), fields(%label_id))]
334    fn delete_label(&self, label_id: LabelId) -> impl Future<Output = Result<Vec<Effect>>> + Send {
335        self.call_persistent_action(policy::delete_label(label_id.as_base()))
336            .in_current_span()
337    }
338
339    /// Invokes `query_device_public_key_bundle`.
340    #[allow(clippy::type_complexity)]
341    #[instrument(skip(self), fields(%device_id))]
342    fn query_device_public_key_bundle(
343        &self,
344        device_id: DeviceId,
345    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
346        self.call_session_action(policy::query_device_public_key_bundle(device_id.as_base()))
347            .map_ok(|SessionData { effects, .. }| effects)
348            .in_current_span()
349    }
350
351    /// Invokes `query_device_role`.
352    #[allow(clippy::type_complexity)]
353    #[instrument(skip(self), fields(%device_id))]
354    fn query_device_role(
355        &self,
356        device_id: DeviceId,
357    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
358        self.call_session_action(policy::query_device_role(device_id.as_base()))
359            .map_ok(|SessionData { effects, .. }| effects)
360            .in_current_span()
361    }
362
363    /// Invokes `query_devices_on_team`.
364    #[allow(clippy::type_complexity)]
365    #[instrument(skip(self))]
366    fn query_devices_on_team(&self) -> impl Future<Output = Result<Vec<Effect>>> + Send {
367        self.call_session_action(policy::query_devices_on_team())
368            .map_ok(|SessionData { effects, .. }| effects)
369            .in_current_span()
370    }
371
372    /// Invokes `query_label`.
373    #[allow(clippy::type_complexity)]
374    #[instrument(skip(self), fields(%label_id))]
375    fn query_label(&self, label_id: LabelId) -> impl Future<Output = Result<Vec<Effect>>> + Send {
376        self.call_session_action(policy::query_label(label_id.as_base()))
377            .map_ok(|SessionData { effects, .. }| effects)
378            .in_current_span()
379    }
380
381    /// Invokes `query_labels`.
382    #[allow(clippy::type_complexity)]
383    #[instrument(skip(self))]
384    fn query_labels(&self) -> impl Future<Output = Result<Vec<Effect>>> + Send {
385        self.call_session_action(policy::query_labels())
386            .map_ok(|SessionData { effects, .. }| effects)
387            .in_current_span()
388    }
389
390    /// Invokes `query_labels_assigned_to_device`.
391    #[instrument(skip(self), fields(%device))]
392    fn query_labels_assigned_to_device(
393        &self,
394        device: DeviceId,
395    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
396        self.call_session_action(policy::query_labels_assigned_to_device(device.as_base()))
397            .map_ok(|SessionData { effects, .. }| effects)
398            .in_current_span()
399    }
400
401    /// Invokes `query_team_roles`.
402    #[instrument(skip(self))]
403    fn query_team_roles(&self) -> impl Future<Output = Result<Vec<Effect>>> + Send {
404        self.call_session_action(policy::query_team_roles())
405            .map_ok(|SessionData { effects, .. }| effects)
406            .in_current_span()
407    }
408
409    /// Invokes `query_role_owners`.
410    #[instrument(skip(self), fields(%role_id))]
411    fn query_role_owners(
412        &self,
413        role_id: RoleId,
414    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
415        self.call_session_action(policy::query_role_owners(role_id.as_base()))
416            .map_ok(|SessionData { effects, .. }| effects)
417            .in_current_span()
418    }
419
420    /// Invokes `remove_device`.
421    #[instrument(skip(self), fields(%device_id))]
422    fn remove_device(
423        &self,
424        device_id: DeviceId,
425    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
426        self.call_persistent_action(policy::remove_device(device_id.as_base()))
427            .in_current_span()
428    }
429
430    /// Invokes `remove_perm_from_role`.
431    #[instrument(skip(self), fields(%role_id, %perm))]
432    fn remove_perm_from_role(
433        &self,
434        role_id: RoleId,
435        perm: SimplePerm,
436    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
437        self.call_persistent_action(policy::remove_perm_from_role(role_id.as_base(), perm))
438            .in_current_span()
439    }
440
441    /// Invokes `remove_role_owner`.
442    #[instrument(skip(self), fields(%role_id, %new_owning_role_id))]
443    fn remove_role_owner(
444        &self,
445        role_id: RoleId,
446        new_owning_role_id: RoleId,
447    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
448        self.call_persistent_action(policy::remove_role_owner(
449            role_id.as_base(),
450            new_owning_role_id.as_base(),
451        ))
452        .in_current_span()
453    }
454
455    /// Invokes `revoke_label_from_device`.
456    #[instrument(skip(self), fields(%device_id, %label_id))]
457    fn revoke_label_from_device(
458        &self,
459        device_id: DeviceId,
460        label_id: LabelId,
461    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
462        self.call_persistent_action(policy::revoke_label_from_device(
463            device_id.as_base(),
464            label_id.as_base(),
465        ))
466        .in_current_span()
467    }
468
469    /// Invokes `revoke_label_managing_role`.
470    #[instrument(skip(self), fields(%label_id, %managing_role_id))]
471    fn revoke_label_managing_role(
472        &self,
473        label_id: LabelId,
474        managing_role_id: RoleId,
475    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
476        self.call_persistent_action(policy::revoke_label_managing_role(
477            label_id.as_base(),
478            managing_role_id.as_base(),
479        ))
480        .in_current_span()
481    }
482
483    /// Invokes `revoke_role`.
484    #[instrument(skip(self), fields(%device_id, %role_id))]
485    fn revoke_role(
486        &self,
487        device_id: DeviceId,
488        role_id: RoleId,
489    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
490        self.call_persistent_action(policy::revoke_role(device_id.as_base(), role_id.as_base()))
491            .in_current_span()
492    }
493
494    /// Invokes `revoke_role_management_perm`.
495    #[instrument(skip(self), fields(%target_role_id, %managing_role_id, %perm))]
496    fn revoke_role_management_perm(
497        &self,
498        target_role_id: RoleId,
499        managing_role_id: RoleId,
500        perm: RoleManagementPerm,
501    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
502        self.call_persistent_action(policy::revoke_role_management_perm(
503            target_role_id.as_base(),
504            managing_role_id.as_base(),
505            perm,
506        ))
507        .in_current_span()
508    }
509
510    /// Invokes `setup_default_roles`.
511    #[instrument(skip(self), fields(%managing_role_id))]
512    fn setup_default_roles(
513        &self,
514        managing_role_id: RoleId,
515    ) -> impl Future<Output = Result<Vec<Effect>>> + Send {
516        self.call_persistent_action(policy::setup_default_roles(managing_role_id.as_base()))
517            .in_current_span()
518    }
519
520    /// Invokes `terminate_team`.
521    #[instrument(skip(self), fields(%team_id))]
522    fn terminate_team(&self, team_id: GraphId) -> impl Future<Output = Result<Vec<Effect>>> + Send {
523        self.call_persistent_action(policy::terminate_team(team_id.as_base()))
524            .in_current_span()
525    }
526}
527
528impl<CS: aranya_crypto::CipherSuite> TryFrom<&PublicKeys<CS>> for PublicKeyBundle {
529    type Error = postcard::Error;
530    fn try_from(pk: &PublicKeys<CS>) -> Result<Self, Self::Error> {
531        Ok(Self {
532            ident_key: postcard::to_allocvec(&pk.ident_pk)?,
533            enc_key: postcard::to_allocvec(&pk.enc_pk)?,
534            sign_key: postcard::to_allocvec(&pk.sign_pk)?,
535        })
536    }
537}
538
539#[cfg(feature = "afc")]
540pub(crate) fn query_afc_channel_is_valid<PS, SP, CE>(
541    aranya: &mut aranya_runtime::ClientState<PS, SP>,
542    graph_id: GraphId,
543    sender_id: DeviceId,
544    receiver_id: DeviceId,
545    label_id: LabelId,
546) -> Result<bool>
547where
548    PS: PolicyStore<Policy = VmPolicy<CE>, Effect = VmEffect>,
549    SP: StorageProvider,
550    CE: aranya_crypto::Engine,
551{
552    let mut session = aranya.session(graph_id)?;
553    let mut sink = VecSink::new();
554    policy::query_afc_channel_is_valid(
555        sender_id.as_base(),
556        receiver_id.as_base(),
557        label_id.as_base(),
558    )
559    .with_action(|act| session.action(aranya, &mut sink, &mut NullSink, act))?;
560    let effects = sink.collect()?;
561    Ok(effects.iter().any(|e| {
562        if let Effect::QueryAfcChannelIsValidResult(e) = e {
563            return e.is_valid;
564        }
565        false
566    }))
567}