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 {
14 created_at: std::time::Instant,
15}
16
17const WS_TICKET_TTL_SECS: u64 = 30;
20const WS_TICKET_PRUNE_AFTER_SECS: u64 = 60;
25
26pub struct SecurityApi {
28 pub(crate) auth_manager: Arc<parking_lot::Mutex<AuthManager>>,
29 pub(crate) audit_trail: Arc<AuditTrail>,
30 pub(crate) access_manager: Arc<parking_lot::Mutex<AccessManager>>,
31 pub(crate) state_store: Arc<StateStore>,
32 ws_tickets: Arc<parking_lot::Mutex<HashMap<String, WsTicket>>>,
33}
34
35impl SecurityApi {
36 pub fn new(
38 auth_manager: Arc<parking_lot::Mutex<AuthManager>>,
39 audit_trail: Arc<AuditTrail>,
40 access_manager: Arc<parking_lot::Mutex<AccessManager>>,
41 state_store: Arc<StateStore>,
42 ) -> Self {
43 Self {
44 auth_manager,
45 audit_trail,
46 access_manager,
47 state_store,
48 ws_tickets: Arc::new(parking_lot::Mutex::new(HashMap::new())),
49 }
50 }
51
52 pub fn generate_ws_ticket(&self) -> String {
60 let bytes: [u8; 16] = *uuid::Uuid::new_v4().as_bytes();
61 let ticket = format!("wst_{}", hex::encode(bytes));
62 let mut tickets = self.ws_tickets.lock();
63 tickets.retain(|_, t| t.created_at.elapsed().as_secs() < WS_TICKET_PRUNE_AFTER_SECS);
65 tickets.insert(
66 ticket.clone(),
67 WsTicket {
68 created_at: std::time::Instant::now(),
69 },
70 );
71 ticket
72 }
73
74 pub fn validate_ws_ticket(&self, ticket: &str) -> bool {
77 let mut tickets = self.ws_tickets.lock();
78 if let Some(t) = tickets.remove(ticket) {
79 t.created_at.elapsed().as_secs() < WS_TICKET_TTL_SECS
80 } else {
81 false
82 }
83 }
84
85 pub fn audit(&self, actor: &str, action: AuditAction, resource: &str) -> String {
87 self.audit_trail
88 .append(actor.to_string(), action, resource.to_string())
89 }
90
91 pub fn verify_chain(&self) -> anyhow::Result<bool> {
93 self.audit_trail
94 .verify()
95 .map_err(|e| anyhow::anyhow!("audit verify failed: {e:?}"))
96 }
97
98 pub fn query_audit(&self, from_seq: u64, to_seq: u64) -> Vec<TrailEntry> {
100 self.audit_trail.entries(from_seq, to_seq)
101 }
102
103 pub fn query_audit_by_agent(&self, agent_id: &str) -> Vec<TrailEntry> {
107 self.audit_trail
108 .entries(0, u64::MAX)
109 .into_iter()
110 .filter(|e| {
111 serde_json::to_value(e)
112 .ok()
113 .and_then(|v| {
114 v.get("agent")
115 .or_else(|| v.get("subject"))
116 .or_else(|| v.get("agent_id"))
117 .and_then(|s| s.as_str())
118 .map(|s| s == agent_id)
119 })
120 .unwrap_or(false)
121 })
122 .collect()
123 }
124
125 pub fn audit_count(&self) -> usize {
127 self.audit_trail.len()
128 }
129
130 pub fn flush(&self, git: &crate::git_layer::GitLayer) -> anyhow::Result<()> {
135 self.audit_trail.flush_to(self.state_store.as_ref())?;
137 if git.is_enabled() {
142 git.commit_file("audit", "audit trail flush")?;
143 }
144 Ok(())
145 }
146
147 pub fn validate_token(&self, token: &str) -> bool {
149 self.auth_manager.lock().validate(token)
150 }
151
152 pub fn get_audit_log(&self) -> Vec<crate::access_manager::AuditEntry> {
154 self.access_manager.lock().audit_log().to_vec()
155 }
156
157 pub fn get_permissions(&self, agent: &str) -> Option<AgentPermissions> {
159 self.access_manager.lock().get_permissions(agent).cloned()
160 }
161
162 pub fn ensure_permissions(&self, agent: &str) -> AgentPermissions {
164 self.access_manager
165 .lock()
166 .get_or_create_permissions(agent)
167 .clone()
168 }
169
170 pub fn update_permissions(&self, agent: &str, update: PermissionUpdate) -> anyhow::Result<()> {
172 self.access_manager.lock().update_permissions(agent, update)
173 }
174
175 pub fn log_action(&self, agent_name: &str, action: &str, resource: &str) {
177 let mut am = self.access_manager.lock();
178 am.log_access(agent_name, action, resource, true, None);
179 }
180
181 pub fn list_approvals(&self) -> Vec<(PendingApproval, ApprovalStatus)> {
183 self.access_manager
184 .lock()
185 .rbac_manager()
186 .all_approvals()
187 .to_vec()
188 }
189
190 pub fn approve(&self, id: uuid::Uuid) -> bool {
192 self.access_manager.lock().rbac_manager_mut().approve(id)
193 }
194
195 pub fn reject(&self, id: uuid::Uuid) -> bool {
197 self.access_manager.lock().rbac_manager_mut().reject(id)
198 }
199}