apt-ostree/src/security.rs
robojerk 0ba99d6195 OCI Integration & Container Image Generation Complete! 🎉
FEAT: Complete OCI integration with container image generation capabilities

- Add comprehensive OCI module (src/oci.rs) with full specification compliance
- Implement OciImageBuilder for OSTree commit to container image conversion
- Add OciRegistry for push/pull operations with authentication support
- Create OciUtils for image validation, inspection, and format conversion
- Support both OCI and Docker image formats with proper content addressing
- Add SHA256 digest calculation for all image components
- Implement gzip compression for filesystem layers

CLI: Add complete OCI command suite
- apt-ostree oci build - Build OCI images from OSTree commits
- apt-ostree oci push - Push images to container registries
- apt-ostree oci pull - Pull images from registries
- apt-ostree oci inspect - Inspect image information
- apt-ostree oci validate - Validate image integrity
- apt-ostree oci convert - Convert between image formats

COMPOSE: Enhance compose workflow with OCI integration
- apt-ostree compose build-image - Convert deployments to OCI images
- apt-ostree compose container-encapsulate - Generate container images from commits
- apt-ostree compose image - Generate container images from treefiles

ARCH: Add OCI layer to project architecture
- Integrate OCI manager into lib.rs and main.rs
- Add proper error handling and recovery mechanisms
- Include comprehensive testing and validation
- Create test script for OCI functionality validation

DEPS: Add sha256 crate for content addressing
- Update Cargo.toml with sha256 dependency
- Ensure proper async/await handling with tokio::process::Command
- Fix borrow checker issues and lifetime management

DOCS: Update project documentation
- Add OCI integration summary documentation
- Update todo.md with milestone 9 completion
- Include usage examples and workflow documentation
2025-07-19 23:05:39 +00:00

667 lines
No EOL
21 KiB
Rust

//! Security Hardening for APT-OSTree
//!
//! This module provides comprehensive security features including input validation,
//! privilege escalation protection, secure communication, and security scanning.
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::sync::Mutex;
use serde::{Serialize, Deserialize};
use tracing::{warn, error, debug, instrument};
use regex::Regex;
use lazy_static::lazy_static;
use std::os::unix::fs::PermissionsExt;
use crate::error::{AptOstreeError, AptOstreeResult};
/// Security configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityConfig {
/// Enable input validation
pub enable_input_validation: bool,
/// Enable privilege escalation protection
pub enable_privilege_protection: bool,
/// Enable secure communication
pub enable_secure_communication: bool,
/// Enable security scanning
pub enable_security_scanning: bool,
/// Allowed file paths for operations
pub allowed_paths: Vec<String>,
/// Blocked file paths
pub blocked_paths: Vec<String>,
/// Allowed package sources
pub allowed_sources: Vec<String>,
/// Blocked package sources
pub blocked_sources: Vec<String>,
/// Maximum file size for operations (bytes)
pub max_file_size: u64,
/// Maximum package count per operation
pub max_package_count: u32,
/// Security scan timeout (seconds)
pub security_scan_timeout: u64,
}
impl Default for SecurityConfig {
fn default() -> Self {
Self {
enable_input_validation: true,
enable_privilege_protection: true,
enable_secure_communication: true,
enable_security_scanning: true,
allowed_paths: vec![
"/var/lib/apt-ostree".to_string(),
"/etc/apt-ostree".to_string(),
"/var/cache/apt-ostree".to_string(),
"/var/log/apt-ostree".to_string(),
],
blocked_paths: vec![
"/etc/shadow".to_string(),
"/etc/passwd".to_string(),
"/etc/sudoers".to_string(),
"/root".to_string(),
"/home".to_string(),
],
allowed_sources: vec![
"deb.debian.org".to_string(),
"archive.ubuntu.com".to_string(),
"security.ubuntu.com".to_string(),
],
blocked_sources: vec![
"malicious.example.com".to_string(),
],
max_file_size: 1024 * 1024 * 100, // 100MB
max_package_count: 1000,
security_scan_timeout: 300, // 5 minutes
}
}
}
/// Security validation result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityValidationResult {
pub is_valid: bool,
pub warnings: Vec<String>,
pub errors: Vec<String>,
pub security_score: u8, // 0-100
}
/// Security scanner for packages and files
#[derive(Debug, Clone)]
pub struct SecurityScanner {
pub vulnerabilities: Vec<Vulnerability>,
pub malware_signatures: Vec<String>,
pub suspicious_patterns: Vec<Regex>,
}
/// Vulnerability information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Vulnerability {
pub id: String,
pub severity: VulnerabilitySeverity,
pub description: String,
pub cve_id: Option<String>,
pub affected_packages: Vec<String>,
pub remediation: String,
}
/// Vulnerability severity levels
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum VulnerabilitySeverity {
Low,
Medium,
High,
Critical,
}
/// Security manager
pub struct SecurityManager {
config: SecurityConfig,
scanner: SecurityScanner,
validation_cache: Arc<Mutex<HashMap<String, SecurityValidationResult>>>,
}
impl SecurityManager {
/// Create a new security manager
pub fn new(config: SecurityConfig) -> Self {
let scanner = SecurityScanner::new();
Self {
config,
scanner,
validation_cache: Arc::new(Mutex::new(HashMap::new())),
}
}
/// Validate input parameters
#[instrument(skip(self))]
pub async fn validate_input(&self, input: &str, input_type: &str) -> AptOstreeResult<SecurityValidationResult> {
debug!("Validating input: type={}, value={}", input_type, input);
let mut result = SecurityValidationResult {
is_valid: true,
warnings: Vec::new(),
errors: Vec::new(),
security_score: 100,
};
if !self.config.enable_input_validation {
return Ok(result);
}
// Check for path traversal attempts
if self.contains_path_traversal(input) {
result.is_valid = false;
result.errors.push("Path traversal attempt detected".to_string());
result.security_score = 0;
}
// Check for command injection attempts
if self.contains_command_injection(input) {
result.is_valid = false;
result.errors.push("Command injection attempt detected".to_string());
result.security_score = 0;
}
// Check for SQL injection attempts
if self.contains_sql_injection(input) {
result.is_valid = false;
result.errors.push("SQL injection attempt detected".to_string());
result.security_score = 0;
}
// Check for XSS attempts
if self.contains_xss(input) {
result.is_valid = false;
result.errors.push("XSS attempt detected".to_string());
result.security_score = 0;
}
// Validate file paths
if input_type == "file_path" {
if let Err(e) = self.validate_file_path(input) {
result.is_valid = false;
result.errors.push(format!("Invalid file path: {}", e));
result.security_score = 0;
}
}
// Validate package names
if input_type == "package_name" {
if let Err(e) = self.validate_package_name(input) {
result.is_valid = false;
result.errors.push(format!("Invalid package name: {}", e));
result.security_score = 0;
}
}
// Cache validation result
let cache_key = format!("{}:{}", input_type, input);
{
let mut cache = self.validation_cache.lock().await;
cache.insert(cache_key, result.clone());
}
if !result.is_valid {
error!("Input validation failed: {:?}", result);
}
Ok(result)
}
/// Validate file path security
pub fn validate_file_path(&self, path: &str) -> AptOstreeResult<()> {
let path_buf = PathBuf::from(path);
// Check for absolute path
if path_buf.is_absolute() {
// Check if path is in blocked paths
for blocked_path in &self.config.blocked_paths {
if path.starts_with(blocked_path) {
return Err(AptOstreeError::Security(
format!("Access to blocked path: {}", blocked_path)
));
}
}
// Check if path is in allowed paths
let mut allowed = false;
for allowed_path in &self.config.allowed_paths {
if path.starts_with(allowed_path) {
allowed = true;
break;
}
}
if !allowed {
return Err(AptOstreeError::Security(
format!("Access to unauthorized path: {}", path)
));
}
}
// Check for path traversal
if path.contains("..") || path.contains("//") {
return Err(AptOstreeError::Security(
"Path traversal attempt detected".to_string()
));
}
Ok(())
}
/// Validate package name security
pub fn validate_package_name(&self, package_name: &str) -> AptOstreeResult<()> {
lazy_static! {
static ref PACKAGE_NAME_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9][a-zA-Z0-9+.-]*$").unwrap();
}
if !PACKAGE_NAME_REGEX.is_match(package_name) {
return Err(AptOstreeError::Security(
format!("Invalid package name format: {}", package_name)
));
}
// Check for suspicious patterns
let suspicious_patterns = [
"..", "//", "\\", "|", "&", ";", "`", "$(", "eval", "exec",
];
for pattern in &suspicious_patterns {
if package_name.contains(pattern) {
return Err(AptOstreeError::Security(
format!("Suspicious pattern in package name: {}", pattern)
));
}
}
Ok(())
}
/// Check for path traversal attempts
fn contains_path_traversal(&self, input: &str) -> bool {
let traversal_patterns = [
"..", "//", "\\", "~", "..\\", "../", "..\\",
];
for pattern in &traversal_patterns {
if input.contains(pattern) {
return true;
}
}
false
}
/// Check for command injection attempts
fn contains_command_injection(&self, input: &str) -> bool {
let injection_patterns = [
"|", "&", ";", "`", "$(", "eval", "exec", "system", "popen",
"shell_exec", "passthru", "proc_open", "pcntl_exec",
];
for pattern in &injection_patterns {
if input.contains(pattern) {
return true;
}
}
false
}
/// Check for SQL injection attempts
fn contains_sql_injection(&self, input: &str) -> bool {
let sql_patterns = [
"SELECT", "INSERT", "UPDATE", "DELETE", "DROP", "CREATE",
"UNION", "OR", "AND", "WHERE", "FROM", "JOIN",
];
for pattern in &sql_patterns {
if input.to_uppercase().contains(pattern) {
return true;
}
}
false
}
/// Check for XSS attempts
fn contains_xss(&self, input: &str) -> bool {
let xss_patterns = [
"<script", "javascript:", "onload=", "onerror=", "onclick=",
"onmouseover=", "onfocus=", "onblur=", "onchange=",
];
for pattern in &xss_patterns {
if input.to_lowercase().contains(pattern) {
return true;
}
}
false
}
/// Protect against privilege escalation
#[instrument(skip(self))]
pub async fn protect_privilege_escalation(&self) -> AptOstreeResult<()> {
if !self.config.enable_privilege_protection {
return Ok(());
}
debug!("Checking privilege escalation protection");
// Check if running as root
if unsafe { libc::geteuid() == 0 } {
// Verify we're not in a privileged context that could be exploited
if self.is_in_dangerous_context() {
return Err(AptOstreeError::Security(
"Running in potentially dangerous privileged context".to_string()
));
}
}
// Check for setuid binaries
if self.has_setuid_binaries() {
warn!("Setuid binaries detected - potential security risk");
}
// Check for world-writable directories
if self.has_world_writable_dirs() {
warn!("World-writable directories detected - potential security risk");
}
Ok(())
}
/// Check if running in dangerous context
fn is_in_dangerous_context(&self) -> bool {
// Check environment variables
let dangerous_vars = [
"LD_PRELOAD", "LD_LIBRARY_PATH", "PYTHONPATH", "PERL5LIB",
];
for var in &dangerous_vars {
if std::env::var(var).is_ok() {
return true;
}
}
// Check if running in container
if self.is_container_environment() {
return true;
}
false
}
/// Check for setuid binaries
fn has_setuid_binaries(&self) -> bool {
let setuid_paths = [
"/usr/bin/sudo", "/usr/bin/su", "/usr/bin/passwd",
"/usr/bin/chsh", "/usr/bin/chfn", "/usr/bin/gpasswd",
];
for path in &setuid_paths {
if Path::new(path).exists() {
if let Ok(metadata) = std::fs::metadata(path) {
let mode = metadata.permissions().mode();
if (mode & 0o4000) != 0 {
return true;
}
}
}
}
false
}
/// Check for world-writable directories
fn has_world_writable_dirs(&self) -> bool {
let world_writable_paths = [
"/tmp", "/var/tmp", "/dev/shm",
];
for path in &world_writable_paths {
if let Ok(metadata) = std::fs::metadata(path) {
let mode = metadata.permissions().mode();
if (mode & 0o0002) != 0 {
return true;
}
}
}
false
}
/// Check if running in container environment
fn is_container_environment(&self) -> bool {
let container_indicators = [
"/.dockerenv",
"/proc/1/cgroup",
"/proc/self/cgroup",
];
for indicator in &container_indicators {
if Path::new(indicator).exists() {
return true;
}
}
// Check cgroup for container indicators
if let Ok(content) = std::fs::read_to_string("/proc/self/cgroup") {
if content.contains("docker") || content.contains("lxc") || content.contains("systemd") {
return true;
}
}
false
}
/// Scan package for security vulnerabilities
#[instrument(skip(self))]
pub async fn scan_package(&self, package_name: &str, package_path: &Path) -> AptOstreeResult<Vec<Vulnerability>> {
if !self.config.enable_security_scanning {
return Ok(Vec::new());
}
debug!("Scanning package for vulnerabilities: {}", package_name);
let mut vulnerabilities = Vec::new();
// Check file size
if let Ok(metadata) = std::fs::metadata(package_path) {
if metadata.len() > self.config.max_file_size {
vulnerabilities.push(Vulnerability {
id: "FILE_SIZE_EXCEEDED".to_string(),
severity: VulnerabilitySeverity::Medium,
description: format!("Package file size exceeds limit: {} bytes", metadata.len()),
cve_id: None,
affected_packages: vec![package_name.to_string()],
remediation: "Reduce package size or increase limit".to_string(),
});
}
}
// Check for known vulnerabilities (placeholder for real vulnerability database)
if let Some(vuln) = self.check_known_vulnerabilities(package_name).await {
vulnerabilities.push(vuln);
}
// Check for malware signatures
if let Some(vuln) = self.scan_for_malware(package_path).await {
vulnerabilities.push(vuln);
}
// Check for suspicious patterns
if let Some(vuln) = self.scan_for_suspicious_patterns(package_path).await {
vulnerabilities.push(vuln);
}
if !vulnerabilities.is_empty() {
warn!("Security vulnerabilities found in package {}: {:?}", package_name, vulnerabilities);
}
Ok(vulnerabilities)
}
/// Check for known vulnerabilities
async fn check_known_vulnerabilities(&self, package_name: &str) -> Option<Vulnerability> {
// This would integrate with a real vulnerability database
// For now, return None as placeholder
None
}
/// Scan for malware signatures
async fn scan_for_malware(&self, package_path: &Path) -> Option<Vulnerability> {
// This would integrate with malware scanning tools
// For now, return None as placeholder
None
}
/// Scan for suspicious patterns
async fn scan_for_suspicious_patterns(&self, package_path: &Path) -> Option<Vulnerability> {
// This would scan file contents for suspicious patterns
// For now, return None as placeholder
None
}
/// Validate secure communication
#[instrument(skip(self))]
pub async fn validate_secure_communication(&self, endpoint: &str) -> AptOstreeResult<()> {
if !self.config.enable_secure_communication {
return Ok(());
}
debug!("Validating secure communication to: {}", endpoint);
// Check for HTTPS
if !endpoint.starts_with("https://") {
return Err(AptOstreeError::Security(
"Non-HTTPS communication not allowed".to_string()
));
}
// Check for allowed sources
let mut allowed = false;
for allowed_source in &self.config.allowed_sources {
if endpoint.contains(allowed_source) {
allowed = true;
break;
}
}
if !allowed {
return Err(AptOstreeError::Security(
format!("Communication to unauthorized endpoint: {}", endpoint)
));
}
// Check for blocked sources
for blocked_source in &self.config.blocked_sources {
if endpoint.contains(blocked_source) {
return Err(AptOstreeError::Security(
format!("Communication to blocked endpoint: {}", blocked_source)
));
}
}
Ok(())
}
/// Get security report
pub async fn get_security_report(&self) -> AptOstreeResult<String> {
let mut report = String::new();
report.push_str("=== APT-OSTree Security Report ===\n\n");
// System security status
report.push_str("System Security Status:\n");
report.push_str(&format!("- Running as root: {}\n", unsafe { libc::geteuid() == 0 }));
report.push_str(&format!("- Container environment: {}\n", self.is_container_environment()));
report.push_str(&format!("- Setuid binaries detected: {}\n", self.has_setuid_binaries()));
report.push_str(&format!("- World-writable directories: {}\n", self.has_world_writable_dirs()));
// Configuration status
report.push_str("\nSecurity Configuration:\n");
report.push_str(&format!("- Input validation: {}\n", self.config.enable_input_validation));
report.push_str(&format!("- Privilege protection: {}\n", self.config.enable_privilege_protection));
report.push_str(&format!("- Secure communication: {}\n", self.config.enable_secure_communication));
report.push_str(&format!("- Security scanning: {}\n", self.config.enable_security_scanning));
// Validation cache statistics
{
let cache = self.validation_cache.lock().await;
report.push_str(&format!("\nValidation Cache:\n"));
report.push_str(&format!("- Cached validations: {}\n", cache.len()));
}
Ok(report)
}
}
impl SecurityScanner {
/// Create a new security scanner
pub fn new() -> Self {
let suspicious_patterns = vec![
Regex::new(r"\.\./").unwrap(),
Regex::new(r"\.\.\\").unwrap(),
Regex::new(r"[|&;`$]").unwrap(),
Regex::new(r"eval\s*\(").unwrap(),
Regex::new(r"exec\s*\(").unwrap(),
];
Self {
vulnerabilities: Vec::new(),
malware_signatures: Vec::new(),
suspicious_patterns,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_input_validation() {
let config = SecurityConfig::default();
let security_manager = SecurityManager::new(config);
// Test valid input
let result = security_manager.validate_input("valid-package-name", "package_name").await.unwrap();
assert!(result.is_valid);
// Test path traversal
let result = security_manager.validate_input("../../../etc/passwd", "file_path").await.unwrap();
assert!(!result.is_valid);
// Test command injection
let result = security_manager.validate_input("package; rm -rf /", "package_name").await.unwrap();
assert!(!result.is_valid);
}
#[tokio::test]
async fn test_file_path_validation() {
let config = SecurityConfig::default();
let security_manager = SecurityManager::new(config);
// Test allowed path
assert!(security_manager.validate_file_path("/var/lib/apt-ostree/test").is_ok());
// Test blocked path
assert!(security_manager.validate_file_path("/etc/shadow").is_err());
// Test path traversal
assert!(security_manager.validate_file_path("../../../etc/passwd").is_err());
}
#[tokio::test]
async fn test_package_name_validation() {
let config = SecurityConfig::default();
let security_manager = SecurityManager::new(config);
// Test valid package name
assert!(security_manager.validate_package_name("valid-package").is_ok());
// Test invalid package name
assert!(security_manager.validate_package_name("package; rm -rf /").is_err());
}
}