oxios_kernel/kernel_handle/
security_api.rs1use 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
12struct WsTicket {
15 created_at: std::time::Instant,
16}
17
18pub 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 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 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 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 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 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 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 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 pub fn query_audit_by_agent(&self, agent_id: &str) -> Vec<TrailEntry> {
89 self.audit_trail.by_agent(agent_id)
90 }
91
92 pub fn audit_count(&self) -> usize {
94 self.audit_trail.len()
95 }
96
97 pub fn flush(&self, git: &crate::git_layer::GitLayer) -> anyhow::Result<()> {
102 self.audit_trail.flush_to(self.state_store.as_ref())?;
104 if git.is_enabled() {
106 let _ = git.commit_file("audit", "audit trail flush");
107 }
108 Ok(())
109 }
110
111 pub fn validate_token(&self, token: &str) -> bool {
113 self.auth_manager.lock().validate(token)
114 }
115
116 pub fn get_audit_log(&self) -> Vec<crate::access_manager::AuditEntry> {
118 self.access_manager.lock().audit_log().to_vec()
119 }
120
121 pub fn get_permissions(&self, agent: &str) -> Option<AgentPermissions> {
123 self.access_manager.lock().get_permissions(agent).cloned()
124 }
125
126 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 pub fn update_permissions(&self, agent: &str, update: PermissionUpdate) -> anyhow::Result<()> {
136 self.access_manager.lock().update_permissions(agent, update)
137 }
138
139 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 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 pub fn approve(&self, id: uuid::Uuid) -> bool {
156 self.access_manager.lock().rbac_manager_mut().approve(id)
157 }
158
159 pub fn reject(&self, id: uuid::Uuid) -> bool {
161 self.access_manager.lock().rbac_manager_mut().reject(id)
162 }
163}