pub mod auth;
pub mod rbac;
pub mod secrets;
pub mod tenancy;
pub use auth::{AuthConfig, AuthProvider, AuthToken, TokenClaims};
pub use rbac::{KernelPermission, Permission, PermissionSet, Role, RoleBinding};
pub use secrets::{SecretRef, SecretStore, SecretValue};
pub use tenancy::{ResourceQuota, Tenant, TenantConfig, TenantId};
pub use ringkernel_core::auth as ring_auth;
pub use ringkernel_core::rbac as ring_rbac;
pub use ringkernel_core::secrets as ring_secrets;
pub use ringkernel_core::security as ring_security;
pub use ringkernel_core::tenancy as ring_tenancy;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityConfig {
pub auth: Option<AuthConfig>,
pub rbac_enabled: bool,
pub multi_tenancy_enabled: bool,
pub default_tenant: Option<TenantId>,
pub audit_logging: bool,
}
impl Default for SecurityConfig {
fn default() -> Self {
Self {
auth: None,
rbac_enabled: false,
multi_tenancy_enabled: false,
default_tenant: None,
audit_logging: true,
}
}
}
impl SecurityConfig {
pub fn new() -> Self {
Self::default()
}
pub fn development() -> Self {
Self::default()
}
pub fn production() -> Self {
Self {
auth: Some(AuthConfig::default()),
rbac_enabled: true,
multi_tenancy_enabled: true,
default_tenant: None,
audit_logging: true,
}
}
pub fn with_auth(mut self, config: AuthConfig) -> Self {
self.auth = Some(config);
self
}
pub fn with_rbac(mut self, enabled: bool) -> Self {
self.rbac_enabled = enabled;
self
}
pub fn with_multi_tenancy(mut self, enabled: bool) -> Self {
self.multi_tenancy_enabled = enabled;
self
}
pub fn with_default_tenant(mut self, tenant: TenantId) -> Self {
self.default_tenant = Some(tenant);
self
}
}
#[derive(Debug, Clone)]
pub struct SecurityContext {
pub user_id: Option<String>,
pub tenant_id: Option<TenantId>,
pub roles: HashSet<Role>,
pub permissions: PermissionSet,
pub claims: Option<TokenClaims>,
pub is_system: bool,
}
impl SecurityContext {
pub fn anonymous() -> Self {
Self {
user_id: None,
tenant_id: None,
roles: HashSet::new(),
permissions: PermissionSet::empty(),
claims: None,
is_system: false,
}
}
pub fn system() -> Self {
Self {
user_id: Some("system".to_string()),
tenant_id: None,
roles: {
let mut roles = HashSet::new();
roles.insert(Role::Admin);
roles
},
permissions: PermissionSet::all(),
claims: None,
is_system: true,
}
}
pub fn user(user_id: impl Into<String>, tenant_id: Option<TenantId>) -> Self {
Self {
user_id: Some(user_id.into()),
tenant_id,
roles: HashSet::new(),
permissions: PermissionSet::empty(),
claims: None,
is_system: false,
}
}
pub fn with_role(mut self, role: Role) -> Self {
let perms = role.permissions();
self.roles.insert(role);
self.permissions = self.permissions.union(&perms);
self
}
pub fn with_roles(mut self, roles: impl IntoIterator<Item = Role>) -> Self {
for role in roles {
let perms = role.permissions();
self.roles.insert(role);
self.permissions = self.permissions.union(&perms);
}
self
}
pub fn with_claims(mut self, claims: TokenClaims) -> Self {
self.claims = Some(claims);
self
}
pub fn is_authenticated(&self) -> bool {
self.user_id.is_some()
}
pub fn has_permission(&self, permission: Permission) -> bool {
self.is_system || self.permissions.contains(permission)
}
pub fn has_role(&self, role: &Role) -> bool {
self.is_system || self.roles.contains(role)
}
pub fn require_permission(&self, permission: Permission) -> Result<(), SecurityError> {
if self.has_permission(permission) {
Ok(())
} else {
Err(SecurityError::PermissionDenied {
permission: format!("{:?}", permission),
user_id: self.user_id.clone(),
})
}
}
pub fn require_authenticated(&self) -> Result<(), SecurityError> {
if self.is_authenticated() {
Ok(())
} else {
Err(SecurityError::Unauthenticated)
}
}
pub fn can_access_tenant(&self, tenant_id: &TenantId) -> bool {
self.is_system || self.tenant_id.as_ref() == Some(tenant_id) || self.has_role(&Role::Admin)
}
}
impl Default for SecurityContext {
fn default() -> Self {
Self::anonymous()
}
}
#[derive(Debug, thiserror::Error)]
pub enum SecurityError {
#[error("Authentication required")]
Unauthenticated,
#[error("Invalid token: {reason}")]
InvalidToken {
reason: String,
},
#[error("Token expired")]
TokenExpired,
#[error("Permission denied: {permission} for user {user_id:?}")]
PermissionDenied {
permission: String,
user_id: Option<String>,
},
#[error("Tenant access denied: {tenant_id}")]
TenantAccessDenied {
tenant_id: String,
},
#[error("Resource quota exceeded: {resource}")]
QuotaExceeded {
resource: String,
},
#[error("Secret not found: {name}")]
SecretNotFound {
name: String,
},
#[error("Encryption error: {reason}")]
EncryptionError {
reason: String,
},
#[error("Security configuration error: {reason}")]
ConfigError {
reason: String,
},
}
impl From<SecurityError> for crate::error::KernelError {
fn from(e: SecurityError) -> Self {
match e {
SecurityError::Unauthenticated
| SecurityError::InvalidToken { .. }
| SecurityError::TokenExpired
| SecurityError::PermissionDenied { .. }
| SecurityError::TenantAccessDenied { .. } => {
crate::error::KernelError::Unauthorized(e.to_string())
}
SecurityError::QuotaExceeded { .. } => {
crate::error::KernelError::ResourceExhausted(e.to_string())
}
_ => crate::error::KernelError::ConfigError(e.to_string()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_anonymous_context() {
let ctx = SecurityContext::anonymous();
assert!(!ctx.is_authenticated());
assert!(!ctx.is_system);
}
#[test]
fn test_system_context() {
let ctx = SecurityContext::system();
assert!(ctx.is_authenticated());
assert!(ctx.is_system);
assert!(ctx.has_permission(Permission::KernelExecute));
assert!(ctx.has_permission(Permission::KernelAdmin));
}
#[test]
fn test_user_context() {
let ctx = SecurityContext::user("user-123", Some(TenantId::new("tenant-456")))
.with_role(Role::User);
assert!(ctx.is_authenticated());
assert!(!ctx.is_system);
assert_eq!(ctx.user_id.as_deref(), Some("user-123"));
assert!(ctx.has_permission(Permission::KernelExecute));
assert!(!ctx.has_permission(Permission::KernelAdmin));
}
#[test]
fn test_permission_check() {
let ctx = SecurityContext::user("user-123", None).with_role(Role::User);
assert!(ctx.require_permission(Permission::KernelExecute).is_ok());
assert!(ctx.require_permission(Permission::KernelAdmin).is_err());
}
#[test]
fn test_tenant_access() {
let tenant = TenantId::new("tenant-123");
let ctx = SecurityContext::user("user-123", Some(tenant.clone()));
assert!(ctx.can_access_tenant(&tenant));
assert!(!ctx.can_access_tenant(&TenantId::new("other-tenant")));
}
}