use std::collections::HashSet;
use std::fs::{self, OpenOptions};
use std::io::Write;
use std::os::unix::fs::OpenOptionsExt;
use std::path::PathBuf;
use std::time::Duration;
use anyhow::{Context, Result};
use log::warn;
use serde::{Deserialize, Serialize};
use tokio::runtime::Runtime;
use crate::hsm::hsm::{decrypt_data, encrypt_data};
use crate::user_privilege::user::User;
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
pub users: Vec<User>,
pub groups: Vec<Group>,
pub session_duration: u64,
pub allowed_commands: Vec<String>,
pub denied_commands: Vec<String>,
pub allowed_networks: Vec<String>,
pub hsm_slot: u64,
pub hsm_pin: String,
pub security_settings: SecuritySettings,
pub os_specific: OsSpecificConfig,
pub paths: ConfigPaths,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Group {
pub name: String,
pub permissions: Vec<String>,
pub members: Vec<String>,
pub description: Option<String>,
pub created_at: chrono::DateTime<chrono::Utc>,
pub modified_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SecuritySettings {
pub password_min_length: usize,
pub password_require_special: bool,
pub password_require_numbers: bool,
pub password_require_uppercase: bool,
pub max_login_attempts: usize,
pub lockout_duration: Duration,
pub session_timeout: Duration,
pub mfa_required: bool,
pub allowed_ip_ranges: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct OsSpecificConfig {
pub macos: MacOSConfig,
pub linux: LinuxConfig,
pub bellandeos: BellandeOSConfig,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct MacOSConfig {
pub require_filevault: bool,
pub require_sip: bool,
pub allowed_applications: Vec<String>,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct LinuxConfig {
pub selinux_mode: String,
pub require_apparmor: bool,
pub kernel_hardening: bool,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct BellandeOSConfig {
pub security_level: String,
pub require_secure_boot: bool,
pub enable_kernel_protection: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ConfigPaths {
pub config_dir: PathBuf,
pub log_dir: PathBuf,
pub backup_dir: PathBuf,
}
impl Default for Config {
fn default() -> Self {
let os_paths = match std::env::consts::OS {
"macos" => ConfigPaths {
config_dir: PathBuf::from("/Library/Application Support/bell"),
log_dir: PathBuf::from("/var/log/bell"),
backup_dir: PathBuf::from("/var/backup/bell"),
},
"linux" => ConfigPaths {
config_dir: PathBuf::from("/etc/bell"),
log_dir: PathBuf::from("/var/log/bell"),
backup_dir: PathBuf::from("/var/backup/bell"),
},
"bellandeos" => ConfigPaths {
config_dir: PathBuf::from("/bell/etc/bell"),
log_dir: PathBuf::from("/bell/log/bell"),
backup_dir: PathBuf::from("/bell/backup/bell"),
},
_ => ConfigPaths {
config_dir: PathBuf::from("./config"),
log_dir: PathBuf::from("./log"),
backup_dir: PathBuf::from("./backup"),
},
};
Config {
users: Vec::new(),
groups: Vec::new(),
session_duration: 3600,
allowed_commands: get_default_allowed_commands(),
denied_commands: get_default_denied_commands(),
allowed_networks: vec!["127.0.0.1/8".to_string()],
hsm_slot: 0,
hsm_pin: String::new(),
security_settings: SecuritySettings {
password_min_length: 12,
password_require_special: true,
password_require_numbers: true,
password_require_uppercase: true,
max_login_attempts: 3,
lockout_duration: Duration::from_secs(300),
session_timeout: Duration::from_secs(3600),
mfa_required: true,
allowed_ip_ranges: vec!["192.168.0.0/16".to_string()],
},
os_specific: OsSpecificConfig {
macos: MacOSConfig {
require_filevault: true,
require_sip: true,
allowed_applications: vec![],
},
linux: LinuxConfig {
selinux_mode: "enforcing".to_string(),
require_apparmor: true,
kernel_hardening: true,
},
bellandeos: BellandeOSConfig {
security_level: "high".to_string(),
require_secure_boot: true,
enable_kernel_protection: true,
},
},
paths: os_paths,
}
}
}
impl Config {
pub fn load() -> Result<Self> {
let rt = Runtime::new()?;
rt.block_on(async {
let config_path = Self::get_config_path()?;
Self::ensure_directories_exist()?;
let encrypted_config =
fs::read_to_string(&config_path).context("Failed to read config file")?;
let decrypted_config = decrypt_data(&encrypted_config)
.await
.context("Failed to decrypt config file")?;
let mut config: Config =
toml::from_str(&decrypted_config).context("Failed to parse config file")?;
config.verify_integrity()?;
config.update_os_settings()?;
Ok(config)
})
}
pub fn save(&self) -> Result<()> {
let rt = Runtime::new()?;
rt.block_on(async {
self.verify_integrity()?;
self.create_backup().await?;
let config_str = toml::to_string(self).context("Failed to serialize config")?;
let encrypted_config = encrypt_data(&config_str)
.await
.context("Failed to encrypt config")?;
let config_path = Self::get_config_path()?;
let mut file = OpenOptions::new()
.write(true)
.create(true)
.mode(0o600)
.open(&config_path)
.context("Failed to open config file for writing")?;
file.write_all(encrypted_config.as_bytes())
.context("Failed to write config file")?;
Ok(())
})
}
fn get_config_path() -> Result<PathBuf> {
let config = Config::default();
let config_file = config.paths.config_dir.join("config.toml");
Ok(config_file)
}
fn ensure_directories_exist() -> Result<()> {
let config = Config::default();
fs::create_dir_all(&config.paths.config_dir)?;
fs::create_dir_all(&config.paths.log_dir)?;
fs::create_dir_all(&config.paths.backup_dir)?;
Ok(())
}
fn verify_integrity(&self) -> Result<()> {
if self.users.is_empty() {
warn!("No users defined in configuration");
}
for group in &self.groups {
for permission in &group.permissions {
if !is_valid_permission(permission) {
return Err(anyhow::anyhow!("Invalid permission: {}", permission));
}
}
}
let mut seen_users = HashSet::new();
for user in &self.users {
if !seen_users.insert(&user.username) {
return Err(anyhow::anyhow!("Duplicate user: {}", user.username));
}
}
Ok(())
}
async fn create_backup(&self) -> Result<()> {
let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
let backup_path = self
.paths
.backup_dir
.join(format!("config_backup_{}.toml", timestamp));
let config_str = toml::to_string(self)?;
let encrypted_backup = encrypt_data(&config_str).await?;
fs::write(backup_path, encrypted_backup)?;
Ok(())
}
fn update_os_settings(&mut self) -> Result<()> {
match std::env::consts::OS {
"macos" => {
self.os_specific.macos = MacOSConfig {
require_filevault: true,
require_sip: true,
allowed_applications: get_default_macos_applications(),
};
}
"linux" => {
self.os_specific.linux = LinuxConfig {
selinux_mode: "enforcing".to_string(),
require_apparmor: true,
kernel_hardening: true,
};
}
"bellandeos" => {
self.os_specific.bellandeos = BellandeOSConfig {
security_level: "high".to_string(),
require_secure_boot: true,
enable_kernel_protection: true,
};
}
_ => warn!("Unsupported operating system"),
}
Ok(())
}
}
fn get_default_allowed_commands() -> Vec<String> {
match std::env::consts::OS {
"macos" => vec!["ls".to_string(), "cd".to_string(), "pwd".to_string()],
"linux" => vec!["ls".to_string(), "cd".to_string(), "pwd".to_string()],
"bellandeos" => vec!["bellctl".to_string(), "ls".to_string(), "cd".to_string()],
_ => vec![],
}
}
fn get_default_denied_commands() -> Vec<String> {
match std::env::consts::OS {
"macos" => vec!["rm -rf /*".to_string(), "sudo su -".to_string()],
"linux" => vec!["rm -rf /*".to_string(), "dd".to_string()],
"bellandeos" => vec![
"bellctl system reset".to_string(),
"bellctl security disable".to_string(),
],
_ => vec![],
}
}
fn get_default_macos_applications() -> Vec<String> {
vec![
"/Applications/Terminal.app".to_string(),
"/Applications/Utilities/Terminal.app".to_string(),
]
}
fn is_valid_permission(permission: &str) -> bool {
matches!(
permission,
"read" | "write" | "execute" | "admin" | "system"
)
}