Skip to main content

selium_kernel/
session.rs

1use std::{
2    collections::{HashMap, HashSet},
3    sync::Arc,
4};
5
6use thiserror::Error;
7use tracing::{debug, warn};
8use uuid::Uuid;
9
10use crate::{
11    drivers::{Capability, session::SessionLifecycleCapability},
12    guest_data::GuestError,
13    registry::ResourceId,
14};
15
16type Result<T, E = SessionError> = std::result::Result<T, E>;
17
18pub struct Session {
19    /// The registry ID for this session.
20    id: Uuid,
21    /// The registry ID for the session that created this one.
22    parent: Uuid,
23    /// Capabilities that this session is entitled to consume, and which resources it may
24    /// consume the capability for.
25    entitlements: HashMap<Capability, ResourceScope>,
26    /// Public key for this session holder; used for identifying valid payloads.
27    _pubkey: [u8; 32],
28}
29
30/// The resources accessible by a capability grant.
31/// None = "cannot use this capability on any resources",
32/// Some = "can only use this capability on the given resources",
33/// Any = "can use this capability on any resource"
34pub enum ResourceScope {
35    None,
36    Some(HashSet<ResourceId>),
37    Any,
38}
39
40#[derive(Error, Debug)]
41pub enum SessionError {
42    #[error("invalid payload signature")]
43    InvalidSignature,
44    #[error("session not authorised to perform this action")]
45    Unauthorised,
46    #[error("attempted to create a session with entitlements beyond its own")]
47    EntitlementScope,
48    #[error("attempted to revoke a resource from 'Any' scope")]
49    RevokeOnAny,
50}
51
52impl Session {
53    /// This is a highly privileged kernel function and should only be used by the
54    /// runtime to bootstrap the first guest session. This should *never* be exposed to
55    /// the userland. For userland session creation, use [`Self::create`] instead.
56    ///
57    /// Note that we don't accept any entitlement resource restrictions as they won't yet
58    /// exist. Best practice is to send every capability enabled in the current kernel.
59    pub fn bootstrap(entitlements: Vec<Capability>, pubkey: [u8; 32]) -> Self {
60        let entitlements =
61            HashMap::from_iter(entitlements.into_iter().map(|id| (id, ResourceScope::Any)));
62
63        Self {
64            id: Uuid::new_v4(),
65            parent: Uuid::nil(),
66            entitlements,
67            _pubkey: pubkey,
68        }
69    }
70
71    /// Create a new session, which will be linked to this one. Note that a session
72    /// cannot create a new session with privileges beyond its own.
73    ///
74    /// Note that sessions are mutable, so the privileges rule is only valid at creation
75    /// time. It is perfectly possible (and valid) for a session to have its scope
76    /// reduced subsequently, making the owning session _less than_ the child session.
77    pub fn create(
78        &self,
79        entitlements: HashMap<Capability, ResourceScope>,
80        pubkey: [u8; 32],
81    ) -> Result<Self> {
82        let mut entitlements = entitlements;
83
84        for (cap, child_scope) in entitlements.iter_mut() {
85            let parent_scope = self
86                .entitlements
87                .get(cap)
88                .ok_or(SessionError::EntitlementScope)?;
89
90            if !parent_scope.contains(child_scope) {
91                return Err(SessionError::EntitlementScope)?;
92            }
93        }
94
95        Ok(Self {
96            id: Uuid::new_v4(),
97            parent: self.id,
98            entitlements,
99            _pubkey: pubkey,
100        })
101    }
102
103    /// Authenticate a payload against this session's public key. If successful, the
104    /// payload is an authentic payload for this session and can be trusted. Otherwise
105    /// this payload is counterfit, meaning either that one or both of session Id and
106    /// request payload have been forged.
107    pub fn authenticate(&self, _payload: &[u8], _signature: &[u8]) -> bool {
108        let success = true; // ...do auth here
109
110        if success {
111            debug!(session = %self.id, status = "success", "authenticate");
112        } else {
113            warn!(session = %self.id, status = "fail", "authenticate");
114        }
115
116        // success
117        todo!()
118    }
119
120    /// Authorise the requested action against the set of entitlements for this session.
121    /// If successful, the action can safely be executed for the given resource.
122    /// Otherwise the action is outside the permission scope and should not be executed.
123    pub fn authorise(&self, capability: Capability, resource_id: ResourceId) -> bool {
124        let success = match self.entitlements.get(&capability) {
125            Some(ResourceScope::Any) => true,
126            Some(ResourceScope::Some(ids)) => ids.contains(&resource_id),
127            _ => false,
128        };
129
130        if success {
131            debug!(session = %self.id, capability = ?capability, resource = resource_id, status = "success", "authorise");
132        } else {
133            warn!(session = %self.id, capability = ?capability, resource = resource_id, status = "fail", "authorise");
134        }
135
136        success
137    }
138
139    fn upsert_entitlement(&mut self, entitlement: Capability) {
140        self.entitlements
141            .insert(entitlement, ResourceScope::Some(HashSet::new()));
142    }
143
144    fn remove_entitlement(&mut self, entitlement: Capability) -> Result<(), SessionError> {
145        self.entitlements
146            .remove(&entitlement)
147            .map(|_| ())
148            .ok_or(SessionError::EntitlementScope)
149    }
150
151    pub(crate) fn grant_resource(&mut self, entitlement: Capability, resource: ResourceId) -> bool {
152        match self.entitlements.get_mut(&entitlement) {
153            Some(ResourceScope::Some(scope)) => scope.insert(resource),
154            Some(scope) if matches!(scope, ResourceScope::None) => {
155                let mut set = HashSet::new();
156                set.insert(resource);
157                *scope = ResourceScope::Some(set);
158                true
159            }
160            _ => false,
161        }
162    }
163
164    pub(crate) fn revoke_resource(
165        &mut self,
166        entitlement: Capability,
167        resource: ResourceId,
168    ) -> Result<bool, SessionError> {
169        match self.entitlements.get_mut(&entitlement) {
170            Some(scope) if matches!(scope, ResourceScope::Some(_)) => {
171                let set = scope.get_set_mut().unwrap();
172                let r = set.remove(&resource);
173                if set.is_empty() {
174                    *scope = ResourceScope::None;
175                }
176                Ok(r)
177            }
178            Some(ResourceScope::Any) => Err(SessionError::RevokeOnAny),
179            _ => Ok(false),
180        }
181    }
182
183    fn ensure_removable(&self) -> Result<(), SessionError> {
184        if self.parent.is_nil() {
185            Err(SessionError::Unauthorised)
186        } else {
187            Ok(())
188        }
189    }
190}
191
192impl From<SessionError> for GuestError {
193    fn from(value: SessionError) -> Self {
194        GuestError::Subsystem(value.to_string())
195    }
196}
197
198impl ResourceScope {
199    fn contains(&self, other: &ResourceScope) -> bool {
200        match (self, other) {
201            (ResourceScope::Any, _) => true,
202            (ResourceScope::Some(_), ResourceScope::None) => true,
203            (ResourceScope::Some(self_ids), ResourceScope::Some(other_ids)) => {
204                other_ids.iter().all(|id| self_ids.contains(id))
205            }
206            (ResourceScope::None, ResourceScope::None) => true,
207            _ => false,
208        }
209    }
210
211    fn get_set_mut(&mut self) -> Option<&mut HashSet<ResourceId>> {
212        match self {
213            Self::Any | Self::None => None,
214            Self::Some(set) => Some(set),
215        }
216    }
217}
218
219impl From<SessionError> for i32 {
220    fn from(value: SessionError) -> Self {
221        match value {
222            SessionError::InvalidSignature => -110,
223            SessionError::Unauthorised => -111,
224            SessionError::EntitlementScope => -112,
225            SessionError::RevokeOnAny => -113,
226        }
227    }
228}
229
230/// Concrete driver for session lifecycle capability.
231#[derive(Clone)]
232pub struct SessionLifecycleDriver;
233
234impl SessionLifecycleDriver {
235    /// Create a new session lifecycle driver instance
236    pub fn new() -> Arc<Self> {
237        Arc::new(Self)
238    }
239}
240
241impl SessionLifecycleCapability for SessionLifecycleDriver {
242    type Error = SessionError;
243
244    fn create(&self, me: &Session, pubkey: [u8; 32]) -> Result<Session, Self::Error> {
245        me.create(HashMap::new(), pubkey)
246    }
247
248    fn add_entitlement(
249        &self,
250        target: &mut Session,
251        entitlement: Capability,
252    ) -> Result<(), Self::Error> {
253        target.upsert_entitlement(entitlement);
254        Ok(())
255    }
256
257    fn rm_entitlement(
258        &self,
259        target: &mut Session,
260        entitlement: Capability,
261    ) -> Result<(), Self::Error> {
262        target.remove_entitlement(entitlement)
263    }
264
265    fn add_resource(
266        &self,
267        target: &mut Session,
268        entitlement: Capability,
269        resource: ResourceId,
270    ) -> Result<bool, Self::Error> {
271        Ok(target.grant_resource(entitlement, resource))
272    }
273
274    fn rm_resource(
275        &self,
276        target: &mut Session,
277        entitlement: Capability,
278        resource: ResourceId,
279    ) -> Result<bool, Self::Error> {
280        target.revoke_resource(entitlement, resource)
281    }
282
283    fn remove(&self, target: &Session) -> Result<(), Self::Error> {
284        target.ensure_removable()
285    }
286}