Skip to main content

oxios_kernel/kernel_handle/
security_api.rs

1//! Security API — authentication, audit trail, RBAC, approvals.
2
3use crate::access_manager::{
4    AccessManager, AgentPermissions, ApprovalStatus, PendingApproval, PermissionUpdate,
5};
6use crate::auth::AuthManager;
7use crate::state_store::StateStore;
8use oxi_sdk::observability::{AuditAction, AuditTrail, TrailEntry};
9use std::collections::HashMap;
10use std::sync::Arc;
11
12/// A one-time ticket for WebSocket authentication.
13/// Valid for 30 seconds after creation, single-use.
14struct WsTicket {
15    created_at: std::time::Instant,
16}
17
18/// Security system calls.
19pub struct SecurityApi {
20    pub(crate) auth_manager: Arc<parking_lot::Mutex<AuthManager>>,
21    pub(crate) audit_trail: Arc<AuditTrail>,
22    pub(crate) access_manager: Arc<parking_lot::Mutex<AccessManager>>,
23    pub(crate) state_store: Arc<StateStore>,
24    ws_tickets: Arc<parking_lot::Mutex<HashMap<String, WsTicket>>>,
25}
26
27impl SecurityApi {
28    /// Create a new SecurityApi.
29    pub fn new(
30        auth_manager: Arc<parking_lot::Mutex<AuthManager>>,
31        audit_trail: Arc<AuditTrail>,
32        access_manager: Arc<parking_lot::Mutex<AccessManager>>,
33        state_store: Arc<StateStore>,
34    ) -> Self {
35        Self {
36            auth_manager,
37            audit_trail,
38            access_manager,
39            state_store,
40            ws_tickets: Arc::new(parking_lot::Mutex::new(HashMap::new())),
41        }
42    }
43
44    /// Generate a one-time WebSocket ticket. Valid for 30 seconds, single-use.
45    pub fn generate_ws_ticket(&self) -> String {
46        let bytes: [u8; 16] = *uuid::Uuid::new_v4().as_bytes();
47        let ticket = format!("wst_{}", hex::encode(bytes));
48        let mut tickets = self.ws_tickets.lock();
49        // Prune expired tickets (older than 60s)
50        tickets.retain(|_, t| t.created_at.elapsed().as_secs() < 60);
51        tickets.insert(
52            ticket.clone(),
53            WsTicket {
54                created_at: std::time::Instant::now(),
55            },
56        );
57        ticket
58    }
59
60    /// Validate and consume a one-time WebSocket ticket. Returns false if invalid/expired/already used.
61    pub fn validate_ws_ticket(&self, ticket: &str) -> bool {
62        let mut tickets = self.ws_tickets.lock();
63        if let Some(t) = tickets.remove(ticket) {
64            t.created_at.elapsed().as_secs() < 30
65        } else {
66            false
67        }
68    }
69    /// Audit an action.
70    pub fn audit(&self, actor: &str, action: AuditAction, resource: &str) -> String {
71        self.audit_trail
72            .append(actor.to_string(), action, resource.to_string())
73    }
74
75    /// Verify audit chain integrity.
76    pub fn verify_chain(&self) -> anyhow::Result<bool> {
77        self.audit_trail
78            .verify()
79            .map_err(|e| anyhow::anyhow!("audit verify failed: {e:?}"))
80    }
81
82    /// Query audit entries by sequence range.
83    pub fn query_audit(&self, from_seq: u64, to_seq: u64) -> Vec<TrailEntry> {
84        self.audit_trail.entries(from_seq, to_seq)
85    }
86
87    /// Query audit by agent.
88    pub fn query_audit_by_agent(&self, agent_id: &str) -> Vec<TrailEntry> {
89        self.audit_trail.by_agent(agent_id)
90    }
91
92    /// Get audit entry count.
93    pub fn audit_count(&self) -> usize {
94        self.audit_trail.len()
95    }
96
97    /// Flush audit trail to disk and commit to git.
98    ///
99    /// Persists all in-memory audit entries to the state store,
100    /// then commits the audit file to git for versioning.
101    pub fn flush(&self, git: &crate::git_layer::GitLayer) -> anyhow::Result<()> {
102        // 1. Persist entries to state store via AuditPersistence trait
103        self.audit_trail.flush_to(self.state_store.as_ref())?;
104        // 2. Commit to git
105        if git.is_enabled() {
106            let _ = git.commit_file("audit", "audit trail flush");
107        }
108        Ok(())
109    }
110
111    /// Validate a bearer token.
112    pub fn validate_token(&self, token: &str) -> bool {
113        self.auth_manager.lock().validate(token)
114    }
115
116    /// Get audit log entries from access manager.
117    pub fn get_audit_log(&self) -> Vec<crate::access_manager::AuditEntry> {
118        self.access_manager.lock().audit_log().to_vec()
119    }
120
121    /// Get permissions for an agent.
122    pub fn get_permissions(&self, agent: &str) -> Option<AgentPermissions> {
123        self.access_manager.lock().get_permissions(agent).cloned()
124    }
125
126    /// Ensure permissions exist for an agent (get or create).
127    pub fn ensure_permissions(&self, agent: &str) -> AgentPermissions {
128        self.access_manager
129            .lock()
130            .get_or_create_permissions(agent)
131            .clone()
132    }
133
134    /// Update permissions for an agent.
135    pub fn update_permissions(&self, agent: &str, update: PermissionUpdate) -> anyhow::Result<()> {
136        self.access_manager.lock().update_permissions(agent, update)
137    }
138
139    /// Log an audit action.
140    pub fn log_action(&self, agent_name: &str, action: &str, resource: &str) {
141        let mut am = self.access_manager.lock();
142        am.log_access(agent_name, action, resource, true, None);
143    }
144
145    /// List all pending approvals.
146    pub fn list_approvals(&self) -> Vec<(PendingApproval, ApprovalStatus)> {
147        self.access_manager
148            .lock()
149            .rbac_manager()
150            .all_approvals()
151            .to_vec()
152    }
153
154    /// Approve a pending request.
155    pub fn approve(&self, id: uuid::Uuid) -> bool {
156        self.access_manager.lock().rbac_manager_mut().approve(id)
157    }
158
159    /// Reject a pending request.
160    pub fn reject(&self, id: uuid::Uuid) -> bool {
161        self.access_manager.lock().rbac_manager_mut().reject(id)
162    }
163}