aranya_daemon_api/
service.rs

1#![allow(clippy::disallowed_macros)] // tarpc uses unreachable
2
3use core::{error, fmt, hash::Hash, time::Duration};
4
5pub use aranya_crypto::tls::CipherSuiteId;
6use aranya_crypto::{
7    dangerous::spideroak_crypto::hex::Hex,
8    default::DefaultEngine,
9    id::IdError,
10    subtle::{Choice, ConstantTimeEq},
11    zeroize::{Zeroize, ZeroizeOnDrop},
12    EncryptionPublicKey, Engine,
13};
14use aranya_id::custom_id;
15pub use aranya_policy_text::{text, InvalidText, Text};
16use aranya_util::{error::ReportExt, Addr};
17use buggy::Bug;
18pub use semver::Version;
19use serde::{Deserialize, Serialize};
20
21pub mod afc;
22pub mod quic_sync;
23
24#[cfg(feature = "afc")]
25pub use self::afc::*;
26pub use self::quic_sync::*;
27
28/// CE = Crypto Engine
29pub type CE = DefaultEngine;
30/// CS = Cipher Suite
31pub type CS = <DefaultEngine as Engine>::CS;
32
33/// An error returned by the API.
34// TODO: enum?
35#[derive(Serialize, Deserialize, Debug)]
36pub struct Error(String);
37
38impl Error {
39    pub fn from_msg(err: &str) -> Self {
40        Self(err.into())
41    }
42
43    pub fn from_err<E: error::Error>(err: E) -> Self {
44        Self(ReportExt::report(&err).to_string())
45    }
46}
47
48impl From<Bug> for Error {
49    fn from(err: Bug) -> Self {
50        Self::from_err(err)
51    }
52}
53
54impl From<anyhow::Error> for Error {
55    fn from(err: anyhow::Error) -> Self {
56        Self(format!("{err:?}"))
57    }
58}
59
60impl From<InvalidText> for Error {
61    fn from(err: InvalidText) -> Self {
62        Self(format!("{err:?}"))
63    }
64}
65
66impl From<semver::Error> for Error {
67    fn from(err: semver::Error) -> Self {
68        Self::from_err(err)
69    }
70}
71
72impl From<IdError> for Error {
73    fn from(err: IdError) -> Self {
74        Self::from_err(err)
75    }
76}
77
78impl fmt::Display for Error {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        self.0.fmt(f)
81    }
82}
83
84impl error::Error for Error {}
85
86pub type Result<T, E = Error> = core::result::Result<T, E>;
87
88custom_id! {
89    /// The Device ID.
90    pub struct DeviceId;
91}
92
93custom_id! {
94    /// The Team ID (a.k.a Graph ID).
95    pub struct TeamId;
96}
97
98custom_id! {
99    /// A label ID.
100    pub struct LabelId;
101}
102
103custom_id! {
104    /// A role ID.
105    pub struct RoleId;
106}
107
108#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
109pub struct Role {
110    /// Uniquely identifies the role.
111    pub id: RoleId,
112    /// The role's friendly name.
113    pub name: Text,
114    /// The author of the role.
115    pub author_id: DeviceId,
116    /// Is this a default role?
117    pub default: bool,
118}
119
120/// A device's public key bundle.
121#[derive(Clone, Serialize, Deserialize, Eq, PartialEq)]
122pub struct KeyBundle {
123    pub identity: Vec<u8>,
124    pub signing: Vec<u8>,
125    pub encryption: Vec<u8>,
126}
127
128impl fmt::Debug for KeyBundle {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        f.debug_struct("KeyBundle")
131            .field("identity", &Hex::new(&*self.identity))
132            .field("signing", &Hex::new(&*self.signing))
133            .field("encryption", &Hex::new(&*self.encryption))
134            .finish()
135    }
136}
137
138// Note: any fields added to this type should be public
139/// A configuration for adding a team in the daemon.
140#[derive(Debug, Serialize, Deserialize)]
141pub struct AddTeamConfig {
142    pub team_id: TeamId,
143    pub quic_sync: Option<AddTeamQuicSyncConfig>,
144}
145
146// Note: any fields added to this type should be public
147/// A configuration for creating a team in the daemon.
148#[derive(Debug, Serialize, Deserialize)]
149pub struct CreateTeamConfig {
150    pub quic_sync: Option<CreateTeamQuicSyncConfig>,
151}
152
153/// A label.
154#[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
155pub struct Label {
156    pub id: LabelId,
157    pub name: Text,
158    pub author_id: DeviceId,
159}
160
161/// A PSK IKM.
162#[derive(Clone, Serialize, Deserialize)]
163pub struct Ikm([u8; SEED_IKM_SIZE]);
164
165impl Ikm {
166    /// Provides access to the raw IKM bytes.
167    #[inline]
168    pub fn raw_ikm_bytes(&self) -> &[u8; SEED_IKM_SIZE] {
169        &self.0
170    }
171}
172
173impl From<[u8; SEED_IKM_SIZE]> for Ikm {
174    fn from(value: [u8; SEED_IKM_SIZE]) -> Self {
175        Self(value)
176    }
177}
178
179impl ConstantTimeEq for Ikm {
180    fn ct_eq(&self, other: &Self) -> Choice {
181        self.0.ct_eq(&other.0)
182    }
183}
184
185impl ZeroizeOnDrop for Ikm {}
186impl Drop for Ikm {
187    fn drop(&mut self) {
188        self.0.zeroize()
189    }
190}
191
192impl fmt::Debug for Ikm {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        f.debug_struct("Ikm").finish_non_exhaustive()
195    }
196}
197
198/// A secret.
199#[derive(Clone, Serialize, Deserialize)]
200pub struct Secret(Box<[u8]>);
201
202impl Secret {
203    /// Provides access to the raw secret bytes.
204    #[inline]
205    pub fn raw_secret_bytes(&self) -> &[u8] {
206        &self.0
207    }
208}
209
210impl<T> From<T> for Secret
211where
212    T: Into<Box<[u8]>>,
213{
214    fn from(value: T) -> Self {
215        Self(value.into())
216    }
217}
218
219impl ConstantTimeEq for Secret {
220    fn ct_eq(&self, other: &Self) -> Choice {
221        self.0.ct_eq(&other.0)
222    }
223}
224
225impl ZeroizeOnDrop for Secret {}
226impl Drop for Secret {
227    fn drop(&mut self) {
228        self.0.zeroize()
229    }
230}
231
232impl fmt::Debug for Secret {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        f.debug_struct("Secret").finish_non_exhaustive()
235    }
236}
237
238/// Configuration values for syncing with a peer
239#[derive(Clone, Debug, Serialize, Deserialize)]
240pub struct SyncPeerConfig {
241    /// The interval at which syncing occurs. If None, the peer will not be periodically synced.
242    pub interval: Option<Duration>,
243    /// Determines if a peer should be synced with immediately after they're added
244    pub sync_now: bool,
245    /// Determines if the peer should be synced with when a hello message is received
246    /// indicating they have a head that we don't have
247    #[cfg(feature = "preview")]
248    pub sync_on_hello: bool,
249}
250
251/// Valid channel operations for a label assignment.
252#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
253pub enum ChanOp {
254    /// The device can only receive data in channels with this
255    /// label.
256    RecvOnly,
257    /// The device can only send data in channels with this
258    /// label.
259    SendOnly,
260    /// The device can send or receive data in channels with this
261    /// label.
262    SendRecv,
263}
264
265// TODO(jdygert): tarpc does not cfg return types properly.
266#[cfg(not(feature = "afc"))]
267use afc_stub::{AfcReceiveChannelInfo, AfcSendChannelInfo, AfcShmInfo};
268#[cfg(not(feature = "afc"))]
269mod afc_stub {
270    #[derive(Debug, serde::Serialize, serde::Deserialize)]
271    pub enum Never {}
272    pub type AfcShmInfo = Never;
273    pub type AfcSendChannelInfo = Never;
274    pub type AfcReceiveChannelInfo = Never;
275}
276
277#[tarpc::service]
278pub trait DaemonApi {
279    //
280    // Misc
281    //
282
283    /// Returns the daemon's version.
284    async fn version() -> Result<Version>;
285    /// Gets local address the Aranya sync server is bound to.
286    async fn aranya_local_addr() -> Result<Addr>;
287
288    /// Gets the public key bundle for this device
289    async fn get_key_bundle() -> Result<KeyBundle>;
290    /// Gets the public device id.
291    async fn get_device_id() -> Result<DeviceId>;
292
293    //
294    // Syncing
295    //
296
297    /// Adds the peer for automatic periodic syncing.
298    async fn add_sync_peer(addr: Addr, team: TeamId, config: SyncPeerConfig) -> Result<()>;
299    /// Sync with peer immediately.
300    async fn sync_now(addr: Addr, team: TeamId, cfg: Option<SyncPeerConfig>) -> Result<()>;
301
302    /// Subscribe to hello notifications from a sync peer.
303    #[cfg(feature = "preview")]
304    async fn sync_hello_subscribe(
305        peer: Addr,
306        team: TeamId,
307        graph_change_delay: Duration,
308        duration: Duration,
309        schedule_delay: Duration,
310    ) -> Result<()>;
311
312    /// Unsubscribe from hello notifications from a sync peer.
313    #[cfg(feature = "preview")]
314    async fn sync_hello_unsubscribe(peer: Addr, team: TeamId) -> Result<()>;
315
316    /// Removes the peer from automatic syncing.
317    async fn remove_sync_peer(addr: Addr, team: TeamId) -> Result<()>;
318    /// add a team to the local device store that was created by someone else. Not an aranya action/command.
319    async fn add_team(cfg: AddTeamConfig) -> Result<()>;
320
321    /// Remove a team from local device storage.
322    async fn remove_team(team: TeamId) -> Result<()>;
323
324    /// Create a new graph/team with the current device as the owner.
325    async fn create_team(cfg: CreateTeamConfig) -> Result<TeamId>;
326    /// Close the team.
327    async fn close_team(team: TeamId) -> Result<()>;
328
329    /// Encrypts the team's syncing PSK(s) for the peer.
330    async fn encrypt_psk_seed_for_peer(
331        team: TeamId,
332        peer_enc_pk: EncryptionPublicKey<CS>,
333    ) -> Result<WrappedSeed>;
334
335    //
336    // Device onboarding
337    //
338
339    /// Adds a device to the team with optional initial roles.
340    async fn add_device_to_team(
341        team: TeamId,
342        keys: KeyBundle,
343        initial_role: Option<RoleId>,
344    ) -> Result<()>;
345    /// Remove device from the team.
346    async fn remove_device_from_team(team: TeamId, device: DeviceId) -> Result<()>;
347    /// Returns all the devices on the team.
348    async fn devices_on_team(team: TeamId) -> Result<Box<[DeviceId]>>;
349    /// Returns the device's key bundle.
350    async fn device_keybundle(team: TeamId, device: DeviceId) -> Result<KeyBundle>;
351
352    //
353    // Role creation
354    //
355
356    /// Configures the team with default roles from policy.
357    ///
358    /// It returns the default roles that were created.
359    async fn setup_default_roles(team: TeamId, owning_role: RoleId) -> Result<Box<[Role]>>;
360    /// Creates a new role.
361    #[cfg(feature = "preview")]
362    async fn create_role(team: TeamId, role_name: Text, owning_role: RoleId) -> Result<Role>;
363    /// Deletes a role.
364    #[cfg(feature = "preview")]
365    async fn delete_role(team: TeamId, role_id: RoleId) -> Result<()>;
366    /// Returns the current team roles.
367    async fn team_roles(team: TeamId) -> Result<Box<[Role]>>;
368
369    //
370    // Role management
371    //
372
373    /// Adds a permission to a role.
374    #[cfg(feature = "preview")]
375    async fn add_perm_to_role(team: TeamId, role: RoleId, perm: Text) -> Result<()>;
376    /// Removes a permission from a role.
377    #[cfg(feature = "preview")]
378    async fn remove_perm_from_role(team: TeamId, role: RoleId, perm: Text) -> Result<()>;
379    /// Adds an owning role to the target role.
380    #[cfg(feature = "preview")]
381    async fn add_role_owner(team: TeamId, role: RoleId, owning_role: RoleId) -> Result<()>;
382    /// Removes an owning role from the target role.
383    #[cfg(feature = "preview")]
384    async fn remove_role_owner(team: TeamId, role: RoleId, owning_role: RoleId) -> Result<()>;
385    /// Returns the roles that own the target role.
386    async fn role_owners(team: TeamId, role: RoleId) -> Result<Box<[Role]>>;
387    /// Assigns a role management permission to a role.
388    #[cfg(feature = "preview")]
389    async fn assign_role_management_perm(
390        team: TeamId,
391        role: RoleId,
392        managing_role: RoleId,
393        perm: Text,
394    ) -> Result<()>;
395    /// Revokes a role management permission from a role.
396    #[cfg(feature = "preview")]
397    async fn revoke_role_management_perm(
398        team: TeamId,
399        role: RoleId,
400        managing_role: RoleId,
401        perm: Text,
402    ) -> Result<()>;
403
404    //
405    // Role assignment
406    //
407
408    /// Assign a role to a device.
409    async fn assign_role(team: TeamId, device: DeviceId, role: RoleId) -> Result<()>;
410    /// Revoke a role from a device.
411    async fn revoke_role(team: TeamId, device: DeviceId, role: RoleId) -> Result<()>;
412    /// Changes the assigned role of a device.
413    async fn change_role(
414        team: TeamId,
415        device: DeviceId,
416        old_role: RoleId,
417        new_role: RoleId,
418    ) -> Result<()>;
419    /// Returns the role assigned to the device.
420    async fn device_role(team: TeamId, device: DeviceId) -> Result<Option<Role>>;
421
422    //
423    // Label creation
424    //
425
426    /// Create a label.
427    async fn create_label(team: TeamId, name: Text, managing_role_id: RoleId) -> Result<LabelId>;
428    /// Delete a label.
429    async fn delete_label(team: TeamId, label_id: LabelId) -> Result<()>;
430    /// Returns a specific label.
431    async fn label(team: TeamId, label: LabelId) -> Result<Option<Label>>;
432    /// Returns all labels on the team.
433    async fn labels(team: TeamId) -> Result<Vec<Label>>;
434
435    //
436    // Label management
437    //
438
439    async fn add_label_managing_role(
440        team: TeamId,
441        label_id: LabelId,
442        managing_role_id: RoleId,
443    ) -> Result<()>;
444
445    //
446    // Label assignments
447    //
448
449    /// Assigns a label to a device.
450    async fn assign_label_to_device(
451        team: TeamId,
452        device: DeviceId,
453        label: LabelId,
454        op: ChanOp,
455    ) -> Result<()>;
456    /// Revokes a label from a device.
457    async fn revoke_label_from_device(team: TeamId, device: DeviceId, label: LabelId)
458        -> Result<()>;
459    /// Returns all labels assigned to the device.
460    async fn labels_assigned_to_device(team: TeamId, device: DeviceId) -> Result<Box<[Label]>>;
461
462    /// Gets AFC shared-memory configuration info.
463    #[cfg(feature = "afc")]
464    #[cfg_attr(docsrs, doc(cfg(feature = "afc")))]
465    async fn afc_shm_info() -> Result<AfcShmInfo>;
466    /// Create a send-only AFC channel.
467    #[cfg(feature = "afc")]
468    #[cfg_attr(docsrs, doc(cfg(feature = "afc")))]
469    async fn create_afc_channel(
470        team: TeamId,
471        peer_id: DeviceId,
472        label_id: LabelId,
473    ) -> Result<AfcSendChannelInfo>;
474    /// Delete a AFC channel.
475    #[cfg(feature = "afc")]
476    #[cfg_attr(docsrs, doc(cfg(feature = "afc")))]
477    async fn delete_afc_channel(chan: AfcLocalChannelId) -> Result<()>;
478    /// Accept a receive-only AFC channel by processing a peer's ctrl message.
479    #[cfg(feature = "afc")]
480    #[cfg_attr(docsrs, doc(cfg(feature = "afc")))]
481    async fn accept_afc_channel(team: TeamId, ctrl: AfcCtrl) -> Result<AfcReceiveChannelInfo>;
482}