1504 lines
48 KiB
Rust
1504 lines
48 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 chrono::Utc;
|
|
use regex::Regex;
|
|
use std::collections::HashMap;
|
|
use std::fs::{self, File};
|
|
use std::io::{BufRead, BufReader};
|
|
use std::os::unix::fs::PermissionsExt;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::Command;
|
|
|
|
use anyhow::{Context, Result};
|
|
use log::{info, warn};
|
|
use serde::{Deserialize, Serialize};
|
|
use sha2::{Digest, Sha256};
|
|
use std::os::unix::fs::MetadataExt;
|
|
|
|
use crate::audit::audit::log_audit_event;
|
|
use crate::config::config::Config;
|
|
|
|
#[derive(Debug)]
|
|
pub struct NetworkRequirements {
|
|
pub required_protocols: Vec<String>,
|
|
pub minimum_networks: usize,
|
|
pub required_firewall: bool,
|
|
pub required_encryption: bool,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ComplianceConfig {
|
|
// Original fields
|
|
pub min_password_length: usize,
|
|
pub min_password_entropy: f64,
|
|
pub password_complexity_regex: String,
|
|
pub critical_files: Vec<PathBuf>,
|
|
pub required_services: Vec<String>,
|
|
pub required_kernel_params: Vec<String>,
|
|
pub audit_file_hashes: PathBuf,
|
|
pub network_requirements: NetworkRequirements,
|
|
|
|
// New password policy fields
|
|
pub password_max_days: u32,
|
|
pub password_min_days: u32,
|
|
pub password_warn_days: u32,
|
|
pub max_repeated_chars: usize,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct PasswordViolation {
|
|
description: String,
|
|
severity: ViolationSeverity,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum ViolationSeverity {
|
|
Low,
|
|
Medium,
|
|
High,
|
|
Critical,
|
|
}
|
|
|
|
impl Default for ComplianceConfig {
|
|
fn default() -> Self {
|
|
let security_paths = get_security_paths();
|
|
let critical_files = security_paths.get("critical").cloned().unwrap_or_default();
|
|
let services = get_security_services();
|
|
let required_services = services.get("required").cloned().unwrap_or_default();
|
|
|
|
ComplianceConfig {
|
|
min_password_length: 12,
|
|
min_password_entropy: 50.0,
|
|
max_repeated_chars: 3,
|
|
password_max_days: 90,
|
|
password_min_days: 1,
|
|
password_warn_days: 7,
|
|
password_complexity_regex: String::from(
|
|
r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{12,}$",
|
|
),
|
|
critical_files,
|
|
required_services,
|
|
// Fix the type mismatch by converting HashMap to Vec<String>
|
|
required_kernel_params: get_required_kernel_params()
|
|
.into_iter()
|
|
.map(|(key, value)| format!("{}={}", key, value))
|
|
.collect(),
|
|
audit_file_hashes: PathBuf::from("audit_hashes.db"),
|
|
network_requirements: NetworkRequirements {
|
|
required_protocols: vec![
|
|
"TLSv1.3".to_string(),
|
|
"SSHv2".to_string(),
|
|
"TLS_AES_256_GCM_SHA384".to_string(),
|
|
"TLS_CHACHA20_POLY1305_SHA256".to_string(),
|
|
],
|
|
minimum_networks: 1,
|
|
required_firewall: true,
|
|
required_encryption: true,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_security_paths() -> HashMap<String, Vec<PathBuf>> {
|
|
let mut paths = HashMap::new();
|
|
match std::env::consts::OS {
|
|
"linux" => {
|
|
paths.insert(
|
|
"critical".to_string(),
|
|
vec![
|
|
PathBuf::from("/etc/security/limits.conf"),
|
|
PathBuf::from("/etc/security/pwquality.conf"),
|
|
PathBuf::from("/etc/passwd"),
|
|
PathBuf::from("/etc/shadow"),
|
|
PathBuf::from("/etc/group"),
|
|
PathBuf::from("/etc/sudoers"),
|
|
PathBuf::from("/etc/ssh/sshd_config"),
|
|
],
|
|
);
|
|
}
|
|
"bellandeos" => {
|
|
paths.insert(
|
|
"critical".to_string(),
|
|
vec![
|
|
PathBuf::from("/bell/security/audit.conf"),
|
|
PathBuf::from("/bell/security/password.conf"),
|
|
PathBuf::from("/bell/security/users"),
|
|
PathBuf::from("/bell/security/access"),
|
|
PathBuf::from("/bell/security/keys"),
|
|
PathBuf::from("/bell/config/system"),
|
|
],
|
|
);
|
|
}
|
|
"macos" => {
|
|
paths.insert(
|
|
"critical".to_string(),
|
|
vec![
|
|
PathBuf::from("/etc/security/audit_control"),
|
|
PathBuf::from("/etc/security/pwpolicy"),
|
|
PathBuf::from("/etc/pam.d"),
|
|
PathBuf::from("/Library/Security"),
|
|
PathBuf::from("/etc/ssh/sshd_config"),
|
|
],
|
|
);
|
|
}
|
|
_ => {}
|
|
}
|
|
paths
|
|
}
|
|
|
|
fn get_required_services() -> Vec<String> {
|
|
match std::env::consts::OS {
|
|
"macos" => vec![
|
|
"com.apple.auditd".to_string(),
|
|
"com.apple.security".to_string(),
|
|
],
|
|
"linux" => vec!["auditd".to_string(), "sshd".to_string(), "ufw".to_string()],
|
|
"bellandeos" => vec![
|
|
"bell.audit".to_string(),
|
|
"bell.security".to_string(),
|
|
"bell.firewall".to_string(),
|
|
],
|
|
_ => vec![],
|
|
}
|
|
}
|
|
|
|
fn get_required_kernel_params() -> HashMap<String, String> {
|
|
let mut params = HashMap::new();
|
|
match std::env::consts::OS {
|
|
"linux" => {
|
|
// Memory protection
|
|
params.insert("kernel.randomize_va_space".to_string(), "2".to_string());
|
|
params.insert("kernel.kptr_restrict".to_string(), "1".to_string());
|
|
params.insert("kernel.yama.ptrace_scope".to_string(), "1".to_string());
|
|
params.insert("vm.mmap_min_addr".to_string(), "65536".to_string());
|
|
|
|
// Network security
|
|
params.insert("net.ipv4.tcp_syncookies".to_string(), "1".to_string());
|
|
params.insert("net.ipv4.conf.all.rp_filter".to_string(), "1".to_string());
|
|
params.insert(
|
|
"net.ipv4.conf.default.rp_filter".to_string(),
|
|
"1".to_string(),
|
|
);
|
|
params.insert(
|
|
"net.ipv4.conf.all.accept_redirects".to_string(),
|
|
"0".to_string(),
|
|
);
|
|
params.insert(
|
|
"net.ipv6.conf.all.accept_redirects".to_string(),
|
|
"0".to_string(),
|
|
);
|
|
params.insert(
|
|
"net.ipv4.conf.all.send_redirects".to_string(),
|
|
"0".to_string(),
|
|
);
|
|
params.insert(
|
|
"net.ipv4.conf.all.accept_source_route".to_string(),
|
|
"0".to_string(),
|
|
);
|
|
params.insert(
|
|
"net.ipv6.conf.all.accept_source_route".to_string(),
|
|
"0".to_string(),
|
|
);
|
|
|
|
// Core dumps
|
|
params.insert("kernel.core_pattern".to_string(), "|/bin/false".to_string());
|
|
params.insert("fs.suid_dumpable".to_string(), "0".to_string());
|
|
|
|
// System security
|
|
params.insert("kernel.sysrq".to_string(), "0".to_string());
|
|
params.insert("kernel.dmesg_restrict".to_string(), "1".to_string());
|
|
params.insert(
|
|
"kernel.unprivileged_bpf_disabled".to_string(),
|
|
"1".to_string(),
|
|
);
|
|
|
|
// Module loading
|
|
params.insert("kernel.modules_disabled".to_string(), "1".to_string());
|
|
|
|
// IPv6 security
|
|
params.insert(
|
|
"net.ipv6.conf.all.disable_ipv6".to_string(),
|
|
"1".to_string(),
|
|
);
|
|
params.insert(
|
|
"net.ipv6.conf.default.disable_ipv6".to_string(),
|
|
"1".to_string(),
|
|
);
|
|
}
|
|
"bellandeos" => {
|
|
// General security
|
|
params.insert("bell.security.level".to_string(), "high".to_string());
|
|
params.insert("bell.memory.protection".to_string(), "strict".to_string());
|
|
params.insert("bell.process.isolation".to_string(), "enforced".to_string());
|
|
|
|
// System hardening
|
|
params.insert("bell.kernel.hardening".to_string(), "maximum".to_string());
|
|
params.insert("bell.syscall.filtering".to_string(), "strict".to_string());
|
|
params.insert(
|
|
"bell.exploit.prevention".to_string(),
|
|
"aggressive".to_string(),
|
|
);
|
|
|
|
// Memory security
|
|
params.insert("bell.memory.aslr".to_string(), "full".to_string());
|
|
params.insert("bell.stack.protection".to_string(), "strong".to_string());
|
|
params.insert("bell.heap.protection".to_string(), "strict".to_string());
|
|
|
|
// Network security
|
|
params.insert("bell.network.filtering".to_string(), "strict".to_string());
|
|
params.insert("bell.network.isolation".to_string(), "enforced".to_string());
|
|
params.insert(
|
|
"bell.network.encryption".to_string(),
|
|
"required".to_string(),
|
|
);
|
|
|
|
// Access control
|
|
params.insert("bell.access.control".to_string(), "mandatory".to_string());
|
|
params.insert(
|
|
"bell.privilege.escalation".to_string(),
|
|
"restricted".to_string(),
|
|
);
|
|
params.insert("bell.capability.control".to_string(), "strict".to_string());
|
|
|
|
// Monitoring and auditing
|
|
params.insert("bell.audit.level".to_string(), "comprehensive".to_string());
|
|
params.insert("bell.monitoring.mode".to_string(), "active".to_string());
|
|
params.insert("bell.incident.detection".to_string(), "enabled".to_string());
|
|
}
|
|
"macos" => {
|
|
// While macOS doesn't use sysctl for all security settings,
|
|
params.insert("kern.sugid_coredump".to_string(), "0".to_string());
|
|
params.insert(
|
|
"kern.bootargs".to_string(),
|
|
"cs_enforcement_disable=0".to_string(),
|
|
);
|
|
params.insert("kern.secure_kernel".to_string(), "1".to_string());
|
|
params.insert("net.inet.tcp.blackhole".to_string(), "2".to_string());
|
|
params.insert("net.inet.udp.blackhole".to_string(), "1".to_string());
|
|
params.insert("net.inet.icmp.icmplim".to_string(), "50".to_string());
|
|
params.insert("net.inet.ip.forwarding".to_string(), "0".to_string());
|
|
params.insert("net.inet.ip.redirect".to_string(), "0".to_string());
|
|
params.insert("net.inet.tcp.always_keepalive".to_string(), "0".to_string());
|
|
params.insert("net.inet.tcp.drop_synfin".to_string(), "1".to_string());
|
|
}
|
|
_ => {}
|
|
}
|
|
params
|
|
}
|
|
|
|
fn get_security_services() -> HashMap<String, Vec<String>> {
|
|
let mut services = HashMap::new();
|
|
match std::env::consts::OS {
|
|
"linux" => {
|
|
services.insert(
|
|
"required".to_string(),
|
|
vec![
|
|
"auditd".to_string(),
|
|
"fail2ban".to_string(),
|
|
"ufw".to_string(),
|
|
"apparmor".to_string(),
|
|
"systemd-journald".to_string(),
|
|
],
|
|
);
|
|
services.insert(
|
|
"prohibited".to_string(),
|
|
vec![
|
|
"telnet".to_string(),
|
|
"rsh".to_string(),
|
|
"rlogin".to_string(),
|
|
"rexec".to_string(),
|
|
],
|
|
);
|
|
}
|
|
"bellandeos" => {
|
|
services.insert(
|
|
"required".to_string(),
|
|
vec![
|
|
"bell.audit".to_string(),
|
|
"bell.security".to_string(),
|
|
"bell.firewall".to_string(),
|
|
"bell.intrusion_detection".to_string(),
|
|
"bell.integrity_monitor".to_string(),
|
|
"bell.endpoint_protection".to_string(),
|
|
],
|
|
);
|
|
services.insert(
|
|
"prohibited".to_string(),
|
|
vec![
|
|
"bell.legacy_protocols".to_string(),
|
|
"bell.unsecured_services".to_string(),
|
|
],
|
|
);
|
|
}
|
|
"macos" => {
|
|
services.insert(
|
|
"required".to_string(),
|
|
vec![
|
|
"com.apple.auditd".to_string(),
|
|
"com.apple.security.firewall".to_string(),
|
|
"com.apple.security.fdesetup".to_string(),
|
|
"com.apple.security.SecureIO".to_string(),
|
|
],
|
|
);
|
|
services.insert(
|
|
"prohibited".to_string(),
|
|
vec!["com.apple.tftp".to_string(), "com.apple.ftp".to_string()],
|
|
);
|
|
}
|
|
_ => {}
|
|
}
|
|
services
|
|
}
|
|
|
|
pub async fn check_compliance(config: &Config) -> Result<()> {
|
|
let compliance_config = ComplianceConfig::default();
|
|
info!("Starting compliance check for {}", std::env::consts::OS);
|
|
|
|
// Password compliance
|
|
check_password_complexity(config, &compliance_config).await?;
|
|
|
|
// File permissions
|
|
check_file_permissions(&compliance_config).await?;
|
|
|
|
// System configurations
|
|
check_system_configurations(&compliance_config).await?;
|
|
|
|
// Audit log integrity
|
|
check_audit_log_integrity(&compliance_config).await?;
|
|
|
|
// Network configurations
|
|
check_network_configurations(config, &compliance_config).await?;
|
|
|
|
// OS-specific checks
|
|
perform_os_specific_checks(&compliance_config).await?;
|
|
|
|
log_audit_event(
|
|
"COMPLIANCE_CHECK",
|
|
"SYSTEM",
|
|
&format!("Completed compliance check on {}", std::env::consts::OS),
|
|
)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_password_complexity(
|
|
config: &Config,
|
|
compliance_config: &ComplianceConfig,
|
|
) -> Result<()> {
|
|
let regex = Regex::new(&compliance_config.password_complexity_regex)
|
|
.context("Failed to compile password complexity regex")?;
|
|
|
|
for user in &config.users {
|
|
let mut violations: Vec<PasswordViolation> = Vec::new();
|
|
|
|
// Check hash length (Argon2)
|
|
if user.password_hash.len() < 60 {
|
|
violations.push(PasswordViolation {
|
|
description: "Password hash does not meet length requirements".to_string(),
|
|
severity: ViolationSeverity::High,
|
|
});
|
|
}
|
|
|
|
// Check password expiry
|
|
let days_since_change = (Utc::now() - user.password_changed_at).num_days();
|
|
if days_since_change > compliance_config.password_max_days as i64 {
|
|
violations.push(PasswordViolation {
|
|
description: format!(
|
|
"Password expired {} days ago (max: {} days)",
|
|
days_since_change, compliance_config.password_max_days
|
|
),
|
|
severity: ViolationSeverity::Medium,
|
|
});
|
|
}
|
|
|
|
// Check if password will expire soon
|
|
let days_until_expiry = compliance_config.password_max_days as i64 - days_since_change;
|
|
if days_until_expiry <= compliance_config.password_warn_days as i64 && days_until_expiry > 0
|
|
{
|
|
violations.push(PasswordViolation {
|
|
description: format!("Password will expire in {} days", days_until_expiry),
|
|
severity: ViolationSeverity::Low,
|
|
});
|
|
}
|
|
|
|
// Check additional password requirements
|
|
if let Some(raw_password) = get_last_password_change(&user.username).await? {
|
|
if !regex.is_match(&raw_password) {
|
|
violations.push(PasswordViolation {
|
|
description: "Password does not meet complexity requirements".to_string(),
|
|
severity: ViolationSeverity::Critical,
|
|
});
|
|
}
|
|
|
|
// Check minimum length
|
|
if raw_password.len() < compliance_config.min_password_length {
|
|
violations.push(PasswordViolation {
|
|
description: format!(
|
|
"Password length ({}) below minimum required ({})",
|
|
raw_password.len(),
|
|
compliance_config.min_password_length
|
|
),
|
|
severity: ViolationSeverity::High,
|
|
});
|
|
}
|
|
|
|
// Add detailed password validation
|
|
let strength_violations = validate_password_strength(&raw_password, compliance_config);
|
|
violations.extend(strength_violations);
|
|
|
|
// Check for common passwords
|
|
if is_common_password(&raw_password).await? {
|
|
violations.push(PasswordViolation {
|
|
description: "Password found in common password list".to_string(),
|
|
severity: ViolationSeverity::Critical,
|
|
});
|
|
}
|
|
|
|
// Check password entropy
|
|
let entropy = calculate_password_entropy(&raw_password);
|
|
if entropy < compliance_config.min_password_entropy {
|
|
violations.push(PasswordViolation {
|
|
description: format!(
|
|
"Password entropy too low: {:.2} bits (minimum: {} bits)",
|
|
entropy, compliance_config.min_password_entropy
|
|
),
|
|
severity: ViolationSeverity::High,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Log all violations
|
|
for violation in violations {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
&user.username,
|
|
&format!(
|
|
"{} (Severity: {:?})",
|
|
violation.description, violation.severity
|
|
),
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
async fn is_common_password(password: &str) -> Result<bool> {
|
|
// This could be implemented by checking against a BellandeSQL of common passwords
|
|
// For now, we'll just check some basic patterns
|
|
let common_patterns = [
|
|
"password", "123456", "qwerty", "admin", "letmein", "welcome",
|
|
];
|
|
|
|
Ok(common_patterns
|
|
.iter()
|
|
.any(|&pattern| password.contains(pattern)))
|
|
}
|
|
|
|
fn validate_password_strength(password: &str, config: &ComplianceConfig) -> Vec<PasswordViolation> {
|
|
let mut violations: Vec<PasswordViolation> = Vec::new();
|
|
|
|
// Check length
|
|
if password.len() < config.min_password_length {
|
|
violations.push(PasswordViolation {
|
|
description: format!(
|
|
"Password too short: {} chars (minimum {})",
|
|
password.len(),
|
|
config.min_password_length
|
|
),
|
|
severity: ViolationSeverity::High,
|
|
});
|
|
}
|
|
|
|
// Check for uppercase letters
|
|
if !password.chars().any(|c| c.is_uppercase()) {
|
|
violations.push(PasswordViolation {
|
|
description: "Password must contain at least one uppercase letter".to_string(),
|
|
severity: ViolationSeverity::Medium,
|
|
});
|
|
}
|
|
|
|
// Check for lowercase letters
|
|
if !password.chars().any(|c| c.is_lowercase()) {
|
|
violations.push(PasswordViolation {
|
|
description: "Password must contain at least one lowercase letter".to_string(),
|
|
severity: ViolationSeverity::Medium,
|
|
});
|
|
}
|
|
|
|
// Check for numbers
|
|
if !password.chars().any(|c| c.is_numeric()) {
|
|
violations.push(PasswordViolation {
|
|
description: "Password must contain at least one number".to_string(),
|
|
severity: ViolationSeverity::Medium,
|
|
});
|
|
}
|
|
|
|
// Check for special characters
|
|
if !password.chars().any(|c| !c.is_alphanumeric()) {
|
|
violations.push(PasswordViolation {
|
|
description: "Password must contain at least one special character".to_string(),
|
|
severity: ViolationSeverity::Medium,
|
|
});
|
|
}
|
|
|
|
// Check for repeating characters
|
|
if has_repeating_chars(password, 3) {
|
|
violations.push(PasswordViolation {
|
|
description: "Password contains repeating characters".to_string(),
|
|
severity: ViolationSeverity::Low,
|
|
});
|
|
}
|
|
|
|
violations
|
|
}
|
|
|
|
// Helper function to check for repeating characters
|
|
fn has_repeating_chars(password: &str, max_repeats: usize) -> bool {
|
|
let chars: Vec<char> = password.chars().collect();
|
|
let mut repeat_count = 1;
|
|
|
|
for i in 1..chars.len() {
|
|
if chars[i] == chars[i - 1] {
|
|
repeat_count += 1;
|
|
if repeat_count > max_repeats {
|
|
return true;
|
|
}
|
|
} else {
|
|
repeat_count = 1;
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
// Entropy calculation for password strength
|
|
fn calculate_password_entropy(password: &str) -> f64 {
|
|
let mut charset_size = 0;
|
|
|
|
// Check what types of characters are used
|
|
let has_lowercase = password.chars().any(|c| c.is_ascii_lowercase());
|
|
let has_uppercase = password.chars().any(|c| c.is_ascii_uppercase());
|
|
let has_numbers = password.chars().any(|c| c.is_ascii_digit());
|
|
let has_symbols = password.chars().any(|c| !c.is_alphanumeric());
|
|
|
|
// Calculate charset size
|
|
if has_lowercase {
|
|
charset_size += 26;
|
|
}
|
|
if has_uppercase {
|
|
charset_size += 26;
|
|
}
|
|
if has_numbers {
|
|
charset_size += 10;
|
|
}
|
|
if has_symbols {
|
|
charset_size += 32;
|
|
}
|
|
|
|
// Calculate entropy
|
|
(password.len() as f64) * (charset_size as f64).log2()
|
|
}
|
|
|
|
#[cfg(target_family = "unix")]
|
|
async fn check_file_permissions(compliance_config: &ComplianceConfig) -> Result<()> {
|
|
for file_path in &compliance_config.critical_files {
|
|
let metadata = fs::metadata(file_path)
|
|
.context(format!("Failed to get metadata for {:?}", file_path))?;
|
|
|
|
// Use direct metadata mode() for Unix systems
|
|
let mode = metadata.mode() & 0o777;
|
|
|
|
// Check for secure permissions
|
|
if mode != 0o600 {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
&format!("Incorrect permissions on {:?}: {:o}", file_path, mode),
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
// Check ownership
|
|
let uid = metadata.uid();
|
|
if uid != 0 {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
&format!("Incorrect ownership on {:?}", file_path),
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
// Check group ownership
|
|
let gid = metadata.gid();
|
|
if gid != 0 {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
&format!("Incorrect group ownership on {:?}: GID {}", file_path, gid),
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(target_family = "unix")]
|
|
async fn check_single_file_permissions(file_path: &Path) -> Result<()> {
|
|
let metadata =
|
|
fs::metadata(file_path).context(format!("Failed to get metadata for {:?}", file_path))?;
|
|
|
|
let mode = metadata.mode() & 0o777;
|
|
let uid = metadata.uid();
|
|
let gid = metadata.gid();
|
|
|
|
let mut violations = Vec::new();
|
|
|
|
// Check basic permissions
|
|
if mode != 0o600 {
|
|
violations.push(format!("incorrect permissions: {:o}", mode));
|
|
}
|
|
|
|
// Check ownership
|
|
if uid != 0 {
|
|
violations.push(format!("incorrect owner UID: {}", uid));
|
|
}
|
|
|
|
// Check group
|
|
if gid != 0 {
|
|
violations.push(format!("incorrect group GID: {}", gid));
|
|
}
|
|
|
|
// Special bits check
|
|
if mode & 0o7000 != 0 {
|
|
violations.push(format!("special bits set: {:o}", mode & 0o7000));
|
|
}
|
|
|
|
// Log all violations if any found
|
|
if !violations.is_empty() {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
&format!(
|
|
"Security violations for {:?}: {}",
|
|
file_path,
|
|
violations.join(", ")
|
|
),
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_system_configurations(compliance_config: &ComplianceConfig) -> Result<()> {
|
|
match std::env::consts::OS {
|
|
"macos" => check_macos_configurations().await?,
|
|
"linux" => check_linux_configurations(compliance_config).await?,
|
|
"bellandeos" => check_bellande_configurations().await?,
|
|
_ => warn!("System configuration checking not implemented for this OS"),
|
|
}
|
|
|
|
// Check required services
|
|
for service in &compliance_config.required_services {
|
|
if !is_service_running(service).await? {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
&format!("Required service not running: {}", service),
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
// Check kernel parameters
|
|
for param in &compliance_config.required_kernel_params {
|
|
let expected_value = get_expected_value(param)?;
|
|
check_kernel_parameter(param, &expected_value).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_kernel_parameter(param: &str, expected_value: &str) -> Result<()> {
|
|
match std::env::consts::OS {
|
|
"linux" => {
|
|
let output = Command::new("sysctl")
|
|
.arg(param)
|
|
.output()
|
|
.context(format!("Failed to check kernel parameter: {}", param))?;
|
|
let value = String::from_utf8_lossy(&output.stdout);
|
|
if !value.contains(expected_value) {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
&format!("Kernel parameter {} has incorrect value", param),
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
"bellandeos" => {
|
|
let output = Command::new("bellctl")
|
|
.args(&["kernel", "param", param])
|
|
.output()
|
|
.context(format!("Failed to check BellandeOS parameter: {}", param))?;
|
|
let value = String::from_utf8_lossy(&output.stdout);
|
|
if !value.contains(expected_value) {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
&format!("BellandeOS parameter {} has incorrect value", param),
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
_ => warn!("Kernel parameter checking not implemented for this OS"),
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn get_expected_value(param: &str) -> Result<String> {
|
|
match param {
|
|
"kernel.randomize_va_space" => Ok("2".to_string()),
|
|
"net.ipv4.ip_forward" => Ok("0".to_string()),
|
|
"kernel.yama.ptrace_scope" => Ok("1".to_string()),
|
|
"kernel.kptr_restrict" => Ok("2".to_string()),
|
|
"net.ipv4.conf.all.accept_redirects" => Ok("0".to_string()),
|
|
"net.ipv4.conf.all.send_redirects" => Ok("0".to_string()),
|
|
_ => Ok("0".to_string()),
|
|
}
|
|
}
|
|
|
|
async fn get_kernel_parameter(param: &str) -> Result<String> {
|
|
match std::env::consts::OS {
|
|
"linux" => {
|
|
let path = format!("/proc/sys/{}", param.replace(".", "/"));
|
|
Ok(fs::read_to_string(path)?.trim().to_string())
|
|
}
|
|
_ => Ok("0".to_string()),
|
|
}
|
|
}
|
|
|
|
async fn check_audit_log_integrity(compliance_config: &ComplianceConfig) -> Result<()> {
|
|
// Load stored hashes
|
|
let stored_hashes = load_file_hashes(&compliance_config.audit_file_hashes)?;
|
|
|
|
// Check audit log file
|
|
let audit_log_path = Path::new("audit_log.txt");
|
|
if !audit_log_path.exists() {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"Audit log file is missing",
|
|
)
|
|
.await?;
|
|
return Ok(());
|
|
}
|
|
|
|
// Calculate current hash
|
|
let current_hash = calculate_file_hash(audit_log_path)?;
|
|
|
|
// Compare with stored hash
|
|
if let Some(stored_hash) = stored_hashes.get(audit_log_path) {
|
|
if current_hash != *stored_hash {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"Audit log integrity check failed",
|
|
)
|
|
.await?;
|
|
}
|
|
} else {
|
|
// Store initial hash
|
|
save_file_hash(
|
|
&compliance_config.audit_file_hashes,
|
|
audit_log_path,
|
|
¤t_hash,
|
|
)?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_network_configurations(
|
|
config: &Config,
|
|
compliance_config: &ComplianceConfig,
|
|
) -> Result<()> {
|
|
// Check network restrictions
|
|
if config.allowed_networks.len() < compliance_config.network_requirements.minimum_networks {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"Insufficient network restrictions configured",
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
// Check firewall
|
|
if compliance_config.network_requirements.required_firewall {
|
|
check_firewall_status().await?;
|
|
}
|
|
|
|
// Check encryption requirements
|
|
if compliance_config.network_requirements.required_encryption {
|
|
check_network_encryption(&compliance_config.network_requirements).await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn perform_os_specific_checks(compliance_config: &ComplianceConfig) -> Result<()> {
|
|
match std::env::consts::OS {
|
|
"macos" => {
|
|
// Check SIP status
|
|
let sip_output = Command::new("csrutil")
|
|
.arg("status")
|
|
.output()
|
|
.context("Failed to check SIP status")?;
|
|
|
|
if !String::from_utf8_lossy(&sip_output.stdout).contains("enabled") {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"System Integrity Protection is disabled",
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
// Check FileVault
|
|
let filevault_output = Command::new("fdesetup")
|
|
.arg("status")
|
|
.output()
|
|
.context("Failed to check FileVault status")?;
|
|
|
|
if !String::from_utf8_lossy(&filevault_output.stdout).contains("On") {
|
|
log_audit_event("COMPLIANCE_VIOLATION", "SYSTEM", "FileVault is not enabled")
|
|
.await?;
|
|
}
|
|
}
|
|
"linux" => {
|
|
// Check SELinux
|
|
if !Path::new("/sys/fs/selinux/enforce").exists() {
|
|
log_audit_event("COMPLIANCE_VIOLATION", "SYSTEM", "SELinux is not enabled").await?;
|
|
}
|
|
|
|
// Check AppArmor
|
|
let apparmor_output = Command::new("aa-status")
|
|
.output()
|
|
.context("Failed to check AppArmor status")?;
|
|
|
|
if !apparmor_output.status.success() {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"AppArmor is not properly configured",
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
"bellandeos" => {
|
|
// Check BellandeOS security module
|
|
let security_output = Command::new("bellctl")
|
|
.args(&["security", "status"])
|
|
.output()
|
|
.context("Failed to check BellandeOS security status")?;
|
|
|
|
if !String::from_utf8_lossy(&security_output.stdout).contains("enabled") {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"BellandeOS security module is not enabled",
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
// Check BellandeOS integrity
|
|
let integrity_output = Command::new("bellctl")
|
|
.args(&["verify", "system"])
|
|
.output()
|
|
.context("Failed to verify BellandeOS integrity")?;
|
|
|
|
if !integrity_output.status.success() {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"BellandeOS system integrity check failed",
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
_ => warn!("OS-specific checks not implemented for this operating system"),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_macos_configurations() -> Result<()> {
|
|
// Check System Integrity Protection (SIP)
|
|
let sip_output = Command::new("csrutil")
|
|
.arg("status")
|
|
.output()
|
|
.context("Failed to check SIP status")?;
|
|
|
|
if !String::from_utf8_lossy(&sip_output.stdout).contains("enabled") {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"System Integrity Protection (SIP) is disabled",
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
// Check FileVault encryption
|
|
let filevault_output = Command::new("fdesetup")
|
|
.arg("status")
|
|
.output()
|
|
.context("Failed to check FileVault status")?;
|
|
|
|
if !String::from_utf8_lossy(&filevault_output.stdout).contains("FileVault is On") {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"FileVault encryption is not enabled",
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
// Check Gatekeeper status
|
|
let gatekeeper_output = Command::new("spctl")
|
|
.args(&["--status"])
|
|
.output()
|
|
.context("Failed to check Gatekeeper status")?;
|
|
|
|
if !String::from_utf8_lossy(&gatekeeper_output.stdout).contains("assessments enabled") {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"Gatekeeper is not enabled",
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
// Check software update settings
|
|
let update_output = Command::new("softwareupdate")
|
|
.args(&["--schedule"])
|
|
.output()
|
|
.context("Failed to check software update schedule")?;
|
|
|
|
if !String::from_utf8_lossy(&update_output.stdout).contains("enabled") {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"Automatic software updates are not enabled",
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
// Check firewall status
|
|
let firewall_output = Command::new("defaults")
|
|
.args(&["read", "/Library/Preferences/com.apple.alf", "globalstate"])
|
|
.output()
|
|
.context("Failed to check firewall status")?;
|
|
|
|
if !String::from_utf8_lossy(&firewall_output.stdout)
|
|
.trim()
|
|
.eq("1")
|
|
{
|
|
log_audit_event("COMPLIANCE_VIOLATION", "SYSTEM", "Firewall is not enabled").await?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_linux_configurations(compliance_config: &ComplianceConfig) -> Result<()> {
|
|
// Check SELinux status
|
|
if Path::new("/etc/selinux/config").exists() {
|
|
let selinux_output = Command::new("getenforce")
|
|
.output()
|
|
.context("Failed to check SELinux status")?;
|
|
|
|
if !String::from_utf8_lossy(&selinux_output.stdout).contains("Enforcing") {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"SELinux is not in enforcing mode",
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
// Check firewall status (UFW)
|
|
let ufw_output = Command::new("ufw")
|
|
.arg("status")
|
|
.output()
|
|
.context("Failed to check UFW status")?;
|
|
|
|
if !String::from_utf8_lossy(&ufw_output.stdout).contains("active") {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"UFW firewall is not active",
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
// Check system security settings
|
|
check_sysctl_settings().await?;
|
|
|
|
// Check important security files
|
|
let security_files = ["/etc/shadow", "/etc/passwd", "/etc/group", "/etc/sudoers"];
|
|
|
|
for file in &security_files {
|
|
let metadata =
|
|
fs::metadata(file).context(format!("Failed to check permissions for {}", file))?;
|
|
|
|
let mode = metadata.permissions().mode();
|
|
if mode & 0o777 != 0o600 {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
&format!("Incorrect permissions on {}: {:o}", file, mode & 0o777),
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
// Check for password policies
|
|
check_password_policies().await?;
|
|
|
|
// Check for core dumps
|
|
check_core_dumps().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_bellande_configurations() -> Result<()> {
|
|
// Check BellandeOS security module status
|
|
let security_status = Command::new("bellctl")
|
|
.args(&["security", "status"])
|
|
.output()
|
|
.context("Failed to check BellandeOS security status")?;
|
|
|
|
if !String::from_utf8_lossy(&security_status.stdout).contains("enabled") {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"BellandeOS security module is not enabled",
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
// Check BellandeOS integrity
|
|
let integrity_check = Command::new("bellctl")
|
|
.args(&["verify", "system"])
|
|
.output()
|
|
.context("Failed to verify BellandeOS integrity")?;
|
|
|
|
if !integrity_check.status.success() {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"BellandeOS system integrity check failed",
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
// Check BellandeOS encryption status
|
|
let encryption_status = Command::new("bellctl")
|
|
.args(&["encryption", "status"])
|
|
.output()
|
|
.context("Failed to check encryption status")?;
|
|
|
|
if !String::from_utf8_lossy(&encryption_status.stdout).contains("enabled") {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"BellandeOS encryption is not enabled",
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
// Check BellandeOS firewall configuration
|
|
let firewall_status = Command::new("bellctl")
|
|
.args(&["firewall", "status"])
|
|
.output()
|
|
.context("Failed to check firewall status")?;
|
|
|
|
if !firewall_status.status.success() {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"BellandeOS firewall is not properly configured",
|
|
)
|
|
.await?;
|
|
}
|
|
|
|
// Check security policies
|
|
check_bellande_security_policies().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
async fn check_sysctl_settings() -> Result<()> {
|
|
let sysctl_checks = [
|
|
("kernel.randomize_va_space", "2"),
|
|
("kernel.kptr_restrict", "2"),
|
|
("kernel.dmesg_restrict", "1"),
|
|
("kernel.yama.ptrace_scope", "1"),
|
|
("net.ipv4.conf.all.rp_filter", "1"),
|
|
("net.ipv4.conf.default.rp_filter", "1"),
|
|
];
|
|
|
|
for (setting, expected_value) in &sysctl_checks {
|
|
let output = Command::new("sysctl")
|
|
.arg("-n")
|
|
.arg(setting)
|
|
.output()
|
|
.context(format!("Failed to check sysctl setting: {}", setting))?;
|
|
|
|
let value = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
if value != *expected_value {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
&format!(
|
|
"Incorrect sysctl setting {}: expected {}, got {}",
|
|
setting, expected_value, value
|
|
),
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_password_policies() -> Result<()> {
|
|
let login_defs_path = "/etc/login.defs";
|
|
if Path::new(login_defs_path).exists() {
|
|
let content = fs::read_to_string(login_defs_path).context("Failed to read login.defs")?;
|
|
|
|
let checks = [
|
|
("PASS_MAX_DAYS", "90"),
|
|
("PASS_MIN_DAYS", "1"),
|
|
("PASS_MIN_LEN", "12"),
|
|
("PASS_WARN_AGE", "7"),
|
|
];
|
|
|
|
for (setting, expected) in &checks {
|
|
if !content.lines().any(|line| {
|
|
line.starts_with(setting) && line.split_whitespace().nth(1) == Some(expected)
|
|
}) {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
&format!("Incorrect password policy setting: {}", setting),
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_core_dumps() -> Result<()> {
|
|
let limits_conf = "/etc/security/limits.conf";
|
|
if Path::new(limits_conf).exists() {
|
|
let content = fs::read_to_string(limits_conf).context("Failed to read limits.conf")?;
|
|
|
|
if !content.lines().any(|line| line.contains("* hard core 0")) {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"Core dumps are not disabled",
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_bellande_security_policies() -> Result<()> {
|
|
let policies = [
|
|
("password-complexity", "high"),
|
|
("session-timeout", "enabled"),
|
|
("audit-level", "full"),
|
|
("network-isolation", "enforced"),
|
|
];
|
|
|
|
for (policy, expected_value) in &policies {
|
|
let output = Command::new("bellctl")
|
|
.args(&["policy", "get", policy])
|
|
.output()
|
|
.context(format!("Failed to check policy: {}", policy))?;
|
|
|
|
let value = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
if value != *expected_value {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
&format!(
|
|
"Incorrect security policy {}: expected {}, got {}",
|
|
policy, expected_value, value
|
|
),
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
async fn is_service_running(service: &str) -> Result<bool> {
|
|
match std::env::consts::OS {
|
|
"macos" => {
|
|
let output = Command::new("launchctl")
|
|
.args(&["list", service])
|
|
.output()?;
|
|
Ok(output.status.success())
|
|
}
|
|
"linux" => {
|
|
let output = Command::new("systemctl")
|
|
.args(&["is-active", service])
|
|
.output()?;
|
|
Ok(output.status.success())
|
|
}
|
|
"bellandeos" => {
|
|
let output = Command::new("bellctl")
|
|
.args(&["service", "status", service])
|
|
.output()?;
|
|
Ok(output.status.success())
|
|
}
|
|
_ => Ok(false),
|
|
}
|
|
}
|
|
|
|
async fn check_firewall_status() -> Result<()> {
|
|
match std::env::consts::OS {
|
|
"macos" => {
|
|
let output = Command::new("defaults")
|
|
.args(&["read", "/Library/Preferences/com.apple.alf", "globalstate"])
|
|
.output()
|
|
.context("Failed to check macOS firewall status")?;
|
|
|
|
if !String::from_utf8_lossy(&output.stdout).contains("1") {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"macOS firewall is not enabled",
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
"linux" => {
|
|
let output = Command::new("ufw")
|
|
.arg("status")
|
|
.output()
|
|
.context("Failed to check UFW status")?;
|
|
|
|
if !String::from_utf8_lossy(&output.stdout).contains("Status: active") {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"UFW firewall is not active",
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
"bellandeos" => {
|
|
let output = Command::new("bellctl")
|
|
.args(&["firewall", "status"])
|
|
.output()
|
|
.context("Failed to check BellandeOS firewall status")?;
|
|
|
|
if !String::from_utf8_lossy(&output.stdout).contains("enabled") {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"BellandeOS firewall is not enabled",
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
_ => warn!("Firewall checking not implemented for this OS"),
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_network_encryption(network_requirements: &NetworkRequirements) -> Result<()> {
|
|
// Check SSL/TLS versions
|
|
check_ssl_versions(&network_requirements.required_protocols).await?;
|
|
|
|
// Check SSH configuration
|
|
check_ssh_configuration().await?;
|
|
|
|
// Check encrypted protocols
|
|
check_encrypted_protocols().await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_ssl_versions(required_protocols: &[String]) -> Result<()> {
|
|
match std::env::consts::OS {
|
|
"macos" | "linux" | "bellandeos" => {
|
|
let output = Command::new("openssl")
|
|
.args(&["version"])
|
|
.output()
|
|
.context("Failed to check OpenSSL version")?;
|
|
|
|
let version = String::from_utf8_lossy(&output.stdout);
|
|
|
|
for protocol in required_protocols {
|
|
if !version.contains(protocol) {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
&format!("Required protocol {} not available", protocol),
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
}
|
|
_ => warn!("SSL version checking not implemented for this OS"),
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_ssh_configuration() -> Result<()> {
|
|
let ssh_config_path = match std::env::consts::OS {
|
|
"macos" | "linux" => Path::new("/etc/ssh/sshd_config"),
|
|
"bellandeos" => Path::new("/bell/security/ssh/sshd_config"),
|
|
_ => return Ok(()),
|
|
};
|
|
|
|
if !ssh_config_path.exists() {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
"SSH configuration file not found",
|
|
)
|
|
.await?;
|
|
return Ok(());
|
|
}
|
|
|
|
let file = File::open(ssh_config_path)?;
|
|
let reader = BufReader::new(file);
|
|
|
|
let required_settings = [
|
|
("PermitRootLogin", "no"),
|
|
("PasswordAuthentication", "no"),
|
|
("X11Forwarding", "no"),
|
|
("Protocol", "2"),
|
|
];
|
|
|
|
for line in reader.lines() {
|
|
let line = line?;
|
|
for (setting, expected_value) in &required_settings {
|
|
if line.starts_with(setting) && !line.contains(expected_value) {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
&format!("SSH setting {} has incorrect value", setting),
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn check_encrypted_protocols() -> Result<()> {
|
|
// Check for unencrypted protocols
|
|
let unsafe_protocols = ["telnet", "ftp", "http"];
|
|
|
|
let output = Command::new("netstat")
|
|
.args(&["-tulpn"])
|
|
.output()
|
|
.context("Failed to check network protocols")?;
|
|
|
|
let output_str = String::from_utf8_lossy(&output.stdout);
|
|
|
|
for protocol in unsafe_protocols {
|
|
if output_str.contains(protocol) {
|
|
log_audit_event(
|
|
"COMPLIANCE_VIOLATION",
|
|
"SYSTEM",
|
|
&format!("Unsafe protocol in use: {}", protocol),
|
|
)
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn get_last_password_change(username: &str) -> Result<Option<String>> {
|
|
match std::env::consts::OS {
|
|
"macos" => {
|
|
let output = Command::new("dscl")
|
|
.args(&[
|
|
".",
|
|
"-read",
|
|
&format!("/Users/{}", username),
|
|
"passwordLastSetTime",
|
|
])
|
|
.output()?;
|
|
Ok(Some(String::from_utf8_lossy(&output.stdout).to_string()))
|
|
}
|
|
"linux" => {
|
|
let output = Command::new("chage").args(&["-l", username]).output()?;
|
|
Ok(Some(String::from_utf8_lossy(&output.stdout).to_string()))
|
|
}
|
|
"bellandeos" => {
|
|
let output = Command::new("bellctl")
|
|
.args(&["user", "password-info", username])
|
|
.output()?;
|
|
Ok(Some(String::from_utf8_lossy(&output.stdout).to_string()))
|
|
}
|
|
_ => Ok(None),
|
|
}
|
|
}
|
|
|
|
fn calculate_file_hash(path: &Path) -> Result<String> {
|
|
let mut file = File::open(path)?;
|
|
let mut hasher = Sha256::new();
|
|
std::io::copy(&mut file, &mut hasher)?;
|
|
Ok(format!("{:x}", hasher.finalize()))
|
|
}
|
|
|
|
fn load_file_hashes(path: &Path) -> Result<HashMap<PathBuf, String>> {
|
|
if path.exists() {
|
|
let file = File::open(path)?;
|
|
Ok(serde_json::from_reader(file)?)
|
|
} else {
|
|
Ok(HashMap::new())
|
|
}
|
|
}
|
|
|
|
fn save_file_hash(path: &Path, file_path: &Path, hash: &str) -> Result<()> {
|
|
let mut hashes = load_file_hashes(path)?;
|
|
hashes.insert(file_path.to_path_buf(), hash.to_string());
|
|
let file = File::create(path)?;
|
|
serde_json::to_writer_pretty(file, &hashes)?;
|
|
Ok(())
|
|
}
|