315 lines
9.8 KiB
Rust
315 lines
9.8 KiB
Rust
// Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
use std::collections::HashMap;
|
|
use std::fmt;
|
|
use std::hash::Hash;
|
|
use std::str::FromStr;
|
|
use std::time::{Duration, SystemTime};
|
|
|
|
use anyhow::Result;
|
|
use log::error;
|
|
use serde::{Deserialize, Serialize};
|
|
use thiserror::Error;
|
|
|
|
use crate::audit::audit::log_audit_event;
|
|
use crate::config::config::Config;
|
|
use crate::user_privilege::user::User;
|
|
use chrono::Timelike;
|
|
|
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Clone, Copy)]
|
|
pub enum PrivilegeLevel {
|
|
User, // Basic user privileges
|
|
Group, // Group-based privileges
|
|
Administrator, // Administrative privileges
|
|
Root, // Root-level access
|
|
Bell, // Highest level - system owner
|
|
}
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum PrivilegeLevelError {
|
|
#[error("Invalid privilege level: {0}")]
|
|
InvalidPrivilegeLevel(String),
|
|
#[error("Insufficient privileges")]
|
|
InsufficientPrivileges,
|
|
#[error("Expired privileges")]
|
|
ExpiredPrivileges,
|
|
#[error("Group not found: {0}")]
|
|
GroupNotFound(String),
|
|
#[error("Permission not found: {0}")]
|
|
PermissionNotFound(String),
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct PrivilegeConfig {
|
|
pub elevation_timeout: Duration,
|
|
pub require_mfa: bool,
|
|
pub allowed_elevation_hours: Vec<u8>,
|
|
pub max_concurrent_elevations: usize,
|
|
pub restricted_commands: HashMap<PrivilegeLevel, Vec<String>>,
|
|
}
|
|
|
|
impl Default for PrivilegeConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
elevation_timeout: Duration::from_secs(3600),
|
|
require_mfa: true,
|
|
allowed_elevation_hours: (0..24).collect(),
|
|
max_concurrent_elevations: 3,
|
|
restricted_commands: HashMap::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct PrivilegeManager {
|
|
config: PrivilegeConfig,
|
|
active_elevations: HashMap<String, Vec<PrivilegeElevation>>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct PrivilegeElevation {
|
|
level: PrivilegeLevel,
|
|
granted_at: SystemTime,
|
|
expires_at: SystemTime,
|
|
reason: String,
|
|
}
|
|
|
|
impl FromStr for PrivilegeLevel {
|
|
type Err = PrivilegeLevelError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s.to_lowercase().as_str() {
|
|
"user" => Ok(PrivilegeLevel::User),
|
|
"group" => Ok(PrivilegeLevel::Group),
|
|
"admin" | "administrator" => Ok(PrivilegeLevel::Administrator),
|
|
"root" => Ok(PrivilegeLevel::Root),
|
|
"bell" => Ok(PrivilegeLevel::Bell),
|
|
_ => Err(PrivilegeLevelError::InvalidPrivilegeLevel(s.to_string())),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for PrivilegeLevel {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
PrivilegeLevel::User => write!(f, "user"),
|
|
PrivilegeLevel::Group => write!(f, "group"),
|
|
PrivilegeLevel::Administrator => write!(f, "administrator"),
|
|
PrivilegeLevel::Root => write!(f, "root"),
|
|
PrivilegeLevel::Bell => write!(f, "bell"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PrivilegeManager {
|
|
pub fn new(config: PrivilegeConfig) -> Self {
|
|
Self {
|
|
config,
|
|
active_elevations: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub async fn check_permission(
|
|
&self,
|
|
user: &User,
|
|
required_privilege: PrivilegeLevel,
|
|
config: &Config,
|
|
) -> Result<bool> {
|
|
// Direct privilege level check
|
|
if user.privilege >= required_privilege {
|
|
log_audit_event(
|
|
"PRIVILEGE_CHECK",
|
|
&user.username,
|
|
&format!("Direct privilege granted: {:?}", required_privilege),
|
|
)
|
|
.await?;
|
|
return Ok(true);
|
|
}
|
|
|
|
// Check active elevations
|
|
if let Some(elevations) = self.active_elevations.get(&user.username) {
|
|
for elevation in elevations {
|
|
if elevation.level >= required_privilege && SystemTime::now() < elevation.expires_at
|
|
{
|
|
log_audit_event(
|
|
"PRIVILEGE_CHECK",
|
|
&user.username,
|
|
&format!("Elevation privilege granted: {:?}", required_privilege),
|
|
)
|
|
.await?;
|
|
return Ok(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check group permissions
|
|
for group_name in &user.groups {
|
|
if let Some(group) = config.groups.iter().find(|g| g.name == *group_name) {
|
|
if group.permissions.contains(&required_privilege.to_string()) {
|
|
log_audit_event(
|
|
"PRIVILEGE_CHECK",
|
|
&user.username,
|
|
&format!(
|
|
"Group privilege granted: {:?} from {}",
|
|
required_privilege, group_name
|
|
),
|
|
)
|
|
.await?;
|
|
return Ok(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
log_audit_event(
|
|
"PRIVILEGE_CHECK",
|
|
&user.username,
|
|
&format!("Permission denied for: {:?}", required_privilege),
|
|
)
|
|
.await?;
|
|
Ok(false)
|
|
}
|
|
|
|
pub async fn elevate_privilege(
|
|
&mut self,
|
|
user: &User,
|
|
requested_level: PrivilegeLevel,
|
|
reason: &str,
|
|
mfa_token: Option<&str>,
|
|
) -> Result<()> {
|
|
// Check if elevation is allowed at current hour
|
|
let current_hour = chrono::Local::now().hour() as u8;
|
|
if !self.config.allowed_elevation_hours.contains(¤t_hour) {
|
|
return Err(PrivilegeLevelError::InsufficientPrivileges.into());
|
|
}
|
|
|
|
// Check MFA requirement
|
|
if self.config.require_mfa && mfa_token.is_none() {
|
|
return Err(anyhow::anyhow!(
|
|
"MFA token required for privilege elevation"
|
|
));
|
|
}
|
|
|
|
// Check concurrent elevations
|
|
let user_elevations = self
|
|
.active_elevations
|
|
.entry(user.username.clone())
|
|
.or_default();
|
|
if user_elevations.len() >= self.config.max_concurrent_elevations {
|
|
return Err(anyhow::anyhow!("Maximum concurrent elevations reached"));
|
|
}
|
|
|
|
// Create new elevation
|
|
let elevation = PrivilegeElevation {
|
|
level: requested_level,
|
|
granted_at: SystemTime::now(),
|
|
expires_at: SystemTime::now() + self.config.elevation_timeout,
|
|
reason: reason.to_string(),
|
|
};
|
|
|
|
user_elevations.push(elevation);
|
|
|
|
log_audit_event(
|
|
"PRIVILEGE_ELEVATION",
|
|
&user.username,
|
|
&format!("Elevated to {:?} for reason: {}", requested_level, reason),
|
|
)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn revoke_elevation(&mut self, user: &str, level: PrivilegeLevel) -> Result<()> {
|
|
if let Some(elevations) = self.active_elevations.get_mut(user) {
|
|
elevations.retain(|e| e.level != level);
|
|
log_audit_event(
|
|
"PRIVILEGE_REVOCATION",
|
|
user,
|
|
&format!("Revoked elevation: {:?}", level),
|
|
)
|
|
.await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn cleanup_expired_elevations(&mut self) {
|
|
let now = SystemTime::now();
|
|
for elevations in self.active_elevations.values_mut() {
|
|
elevations.retain(|e| e.expires_at > now);
|
|
}
|
|
}
|
|
}
|
|
|
|
// OS-specific privilege checks
|
|
pub async fn check_os_specific_privileges(
|
|
user: &User,
|
|
required_privilege: PrivilegeLevel,
|
|
) -> Result<bool> {
|
|
match std::env::consts::OS {
|
|
"macos" => check_macos_privileges(user, required_privilege).await,
|
|
"linux" => check_linux_privileges(user, required_privilege).await,
|
|
"bellandeos" => check_bellande_privileges(user, required_privilege).await,
|
|
_ => Ok(false),
|
|
}
|
|
}
|
|
|
|
async fn check_macos_privileges(user: &User, required_privilege: PrivilegeLevel) -> Result<bool> {
|
|
// Check admin group membership
|
|
if required_privilege >= PrivilegeLevel::Administrator {
|
|
let output = std::process::Command::new("dseditgroup")
|
|
.args(&["-o", "checkmember", "-m", &user.username, "admin"])
|
|
.output()?;
|
|
|
|
if !output.status.success() {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
async fn check_linux_privileges(user: &User, required_privilege: PrivilegeLevel) -> Result<bool> {
|
|
// Check sudo group membership
|
|
if required_privilege >= PrivilegeLevel::Administrator {
|
|
let output = std::process::Command::new("groups")
|
|
.arg(&user.username)
|
|
.output()?;
|
|
|
|
let groups = String::from_utf8_lossy(&output.stdout);
|
|
if !groups.contains("sudo") && !groups.contains("wheel") {
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
async fn check_bellande_privileges(
|
|
user: &User,
|
|
required_privilege: PrivilegeLevel,
|
|
) -> Result<bool> {
|
|
// Check BellandeOS specific privileges
|
|
let output = std::process::Command::new("bellctl")
|
|
.args(&[
|
|
"user",
|
|
"check-privilege",
|
|
&user.username,
|
|
&required_privilege.to_string(),
|
|
])
|
|
.output()?;
|
|
|
|
Ok(output.status.success())
|
|
}
|