rustkernel_core/security/
mod.rs1pub mod auth;
30pub mod rbac;
31pub mod secrets;
32pub mod tenancy;
33
34pub use auth::{AuthConfig, AuthProvider, AuthToken, TokenClaims};
35pub use rbac::{KernelPermission, Permission, PermissionSet, Role, RoleBinding};
36pub use secrets::{SecretRef, SecretStore, SecretValue};
37pub use tenancy::{ResourceQuota, Tenant, TenantConfig, TenantId};
38
39pub use ringkernel_core::auth as ring_auth;
42pub use ringkernel_core::rbac as ring_rbac;
43pub use ringkernel_core::secrets as ring_secrets;
44pub use ringkernel_core::security as ring_security;
45pub use ringkernel_core::tenancy as ring_tenancy;
46
47use serde::{Deserialize, Serialize};
48use std::collections::HashSet;
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct SecurityConfig {
53 pub auth: Option<AuthConfig>,
55 pub rbac_enabled: bool,
57 pub multi_tenancy_enabled: bool,
59 pub default_tenant: Option<TenantId>,
61 pub audit_logging: bool,
63}
64
65impl Default for SecurityConfig {
66 fn default() -> Self {
67 Self {
68 auth: None,
69 rbac_enabled: false,
70 multi_tenancy_enabled: false,
71 default_tenant: None,
72 audit_logging: true,
73 }
74 }
75}
76
77impl SecurityConfig {
78 pub fn new() -> Self {
80 Self::default()
81 }
82
83 pub fn development() -> Self {
85 Self::default()
86 }
87
88 pub fn production() -> Self {
90 Self {
91 auth: Some(AuthConfig::default()),
92 rbac_enabled: true,
93 multi_tenancy_enabled: true,
94 default_tenant: None,
95 audit_logging: true,
96 }
97 }
98
99 pub fn with_auth(mut self, config: AuthConfig) -> Self {
101 self.auth = Some(config);
102 self
103 }
104
105 pub fn with_rbac(mut self, enabled: bool) -> Self {
107 self.rbac_enabled = enabled;
108 self
109 }
110
111 pub fn with_multi_tenancy(mut self, enabled: bool) -> Self {
113 self.multi_tenancy_enabled = enabled;
114 self
115 }
116
117 pub fn with_default_tenant(mut self, tenant: TenantId) -> Self {
119 self.default_tenant = Some(tenant);
120 self
121 }
122}
123
124#[derive(Debug, Clone)]
126pub struct SecurityContext {
127 pub user_id: Option<String>,
129 pub tenant_id: Option<TenantId>,
131 pub roles: HashSet<Role>,
133 pub permissions: PermissionSet,
135 pub claims: Option<TokenClaims>,
137 pub is_system: bool,
139}
140
141impl SecurityContext {
142 pub fn anonymous() -> Self {
144 Self {
145 user_id: None,
146 tenant_id: None,
147 roles: HashSet::new(),
148 permissions: PermissionSet::empty(),
149 claims: None,
150 is_system: false,
151 }
152 }
153
154 pub fn system() -> Self {
156 Self {
157 user_id: Some("system".to_string()),
158 tenant_id: None,
159 roles: {
160 let mut roles = HashSet::new();
161 roles.insert(Role::Admin);
162 roles
163 },
164 permissions: PermissionSet::all(),
165 claims: None,
166 is_system: true,
167 }
168 }
169
170 pub fn user(user_id: impl Into<String>, tenant_id: Option<TenantId>) -> Self {
172 Self {
173 user_id: Some(user_id.into()),
174 tenant_id,
175 roles: HashSet::new(),
176 permissions: PermissionSet::empty(),
177 claims: None,
178 is_system: false,
179 }
180 }
181
182 pub fn with_role(mut self, role: Role) -> Self {
184 let perms = role.permissions();
185 self.roles.insert(role);
186 self.permissions = self.permissions.union(&perms);
188 self
189 }
190
191 pub fn with_roles(mut self, roles: impl IntoIterator<Item = Role>) -> Self {
193 for role in roles {
194 let perms = role.permissions();
195 self.roles.insert(role);
196 self.permissions = self.permissions.union(&perms);
197 }
198 self
199 }
200
201 pub fn with_claims(mut self, claims: TokenClaims) -> Self {
203 self.claims = Some(claims);
204 self
205 }
206
207 pub fn is_authenticated(&self) -> bool {
209 self.user_id.is_some()
210 }
211
212 pub fn has_permission(&self, permission: Permission) -> bool {
214 self.is_system || self.permissions.contains(permission)
215 }
216
217 pub fn has_role(&self, role: &Role) -> bool {
219 self.is_system || self.roles.contains(role)
220 }
221
222 pub fn require_permission(&self, permission: Permission) -> Result<(), SecurityError> {
224 if self.has_permission(permission) {
225 Ok(())
226 } else {
227 Err(SecurityError::PermissionDenied {
228 permission: format!("{:?}", permission),
229 user_id: self.user_id.clone(),
230 })
231 }
232 }
233
234 pub fn require_authenticated(&self) -> Result<(), SecurityError> {
236 if self.is_authenticated() {
237 Ok(())
238 } else {
239 Err(SecurityError::Unauthenticated)
240 }
241 }
242
243 pub fn can_access_tenant(&self, tenant_id: &TenantId) -> bool {
245 self.is_system || self.tenant_id.as_ref() == Some(tenant_id) || self.has_role(&Role::Admin)
246 }
247}
248
249impl Default for SecurityContext {
250 fn default() -> Self {
251 Self::anonymous()
252 }
253}
254
255#[derive(Debug, thiserror::Error)]
257pub enum SecurityError {
258 #[error("Authentication required")]
260 Unauthenticated,
261
262 #[error("Invalid token: {reason}")]
264 InvalidToken {
265 reason: String,
267 },
268
269 #[error("Token expired")]
271 TokenExpired,
272
273 #[error("Permission denied: {permission} for user {user_id:?}")]
275 PermissionDenied {
276 permission: String,
278 user_id: Option<String>,
280 },
281
282 #[error("Tenant access denied: {tenant_id}")]
284 TenantAccessDenied {
285 tenant_id: String,
287 },
288
289 #[error("Resource quota exceeded: {resource}")]
291 QuotaExceeded {
292 resource: String,
294 },
295
296 #[error("Secret not found: {name}")]
298 SecretNotFound {
299 name: String,
301 },
302
303 #[error("Encryption error: {reason}")]
305 EncryptionError {
306 reason: String,
308 },
309
310 #[error("Security configuration error: {reason}")]
312 ConfigError {
313 reason: String,
315 },
316}
317
318impl From<SecurityError> for crate::error::KernelError {
319 fn from(e: SecurityError) -> Self {
320 match e {
321 SecurityError::Unauthenticated
322 | SecurityError::InvalidToken { .. }
323 | SecurityError::TokenExpired
324 | SecurityError::PermissionDenied { .. }
325 | SecurityError::TenantAccessDenied { .. } => {
326 crate::error::KernelError::Unauthorized(e.to_string())
327 }
328 SecurityError::QuotaExceeded { .. } => {
329 crate::error::KernelError::ResourceExhausted(e.to_string())
330 }
331 _ => crate::error::KernelError::ConfigError(e.to_string()),
332 }
333 }
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339
340 #[test]
341 fn test_anonymous_context() {
342 let ctx = SecurityContext::anonymous();
343 assert!(!ctx.is_authenticated());
344 assert!(!ctx.is_system);
345 }
346
347 #[test]
348 fn test_system_context() {
349 let ctx = SecurityContext::system();
350 assert!(ctx.is_authenticated());
351 assert!(ctx.is_system);
352 assert!(ctx.has_permission(Permission::KernelExecute));
353 assert!(ctx.has_permission(Permission::KernelAdmin));
354 }
355
356 #[test]
357 fn test_user_context() {
358 let ctx = SecurityContext::user("user-123", Some(TenantId::new("tenant-456")))
359 .with_role(Role::User);
360
361 assert!(ctx.is_authenticated());
362 assert!(!ctx.is_system);
363 assert_eq!(ctx.user_id.as_deref(), Some("user-123"));
364 assert!(ctx.has_permission(Permission::KernelExecute));
365 assert!(!ctx.has_permission(Permission::KernelAdmin));
366 }
367
368 #[test]
369 fn test_permission_check() {
370 let ctx = SecurityContext::user("user-123", None).with_role(Role::User);
371
372 assert!(ctx.require_permission(Permission::KernelExecute).is_ok());
373 assert!(ctx.require_permission(Permission::KernelAdmin).is_err());
374 }
375
376 #[test]
377 fn test_tenant_access() {
378 let tenant = TenantId::new("tenant-123");
379 let ctx = SecurityContext::user("user-123", Some(tenant.clone()));
380
381 assert!(ctx.can_access_tenant(&tenant));
382 assert!(!ctx.can_access_tenant(&TenantId::new("other-tenant")));
383 }
384}