558 lines
No EOL
19 KiB
Rust
558 lines
No EOL
19 KiB
Rust
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
|
use tracing::{warn, error, info};
|
|
use crate::error::AptOstreeError;
|
|
|
|
/// Commands that require root privileges
|
|
#[derive(Debug, Clone)]
|
|
pub enum PrivilegedCommand {
|
|
Init,
|
|
Install,
|
|
Remove,
|
|
Upgrade,
|
|
Rollback,
|
|
Deploy,
|
|
ApplyLive,
|
|
Cancel,
|
|
Cleanup,
|
|
Compose,
|
|
Checkout,
|
|
Prune,
|
|
Kargs,
|
|
Initramfs,
|
|
Override,
|
|
RefreshMd,
|
|
Reload,
|
|
Reset,
|
|
Rebase,
|
|
InitramfsEtc,
|
|
Usroverlay,
|
|
DaemonPing,
|
|
}
|
|
|
|
/// Commands that can run as non-root user
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub enum NonPrivilegedCommand {
|
|
List,
|
|
Status,
|
|
Search,
|
|
Info,
|
|
History,
|
|
DaemonPing,
|
|
DaemonStatus,
|
|
}
|
|
|
|
/// Check if the current user has root privileges
|
|
pub fn is_root() -> bool {
|
|
unsafe { libc::geteuid() == 0 }
|
|
}
|
|
|
|
/// Check if the current user can use sudo
|
|
pub fn can_use_sudo() -> bool {
|
|
// Check if sudo is available and user can use it
|
|
let output = std::process::Command::new("sudo")
|
|
.arg("-n")
|
|
.arg("true")
|
|
.output();
|
|
|
|
match output {
|
|
Ok(status) => status.status.success(),
|
|
Err(_) => false,
|
|
}
|
|
}
|
|
|
|
/// Get the current user's effective UID
|
|
pub fn get_current_uid() -> u32 {
|
|
unsafe { libc::geteuid() }
|
|
}
|
|
|
|
/// Get the current user's effective GID
|
|
pub fn get_current_gid() -> u32 {
|
|
unsafe { libc::getegid() }
|
|
}
|
|
|
|
/// Check if a command requires root privileges
|
|
pub fn requires_root(command: &PrivilegedCommand) -> bool {
|
|
matches!(command,
|
|
PrivilegedCommand::Init |
|
|
PrivilegedCommand::Install |
|
|
PrivilegedCommand::Remove |
|
|
PrivilegedCommand::Upgrade |
|
|
PrivilegedCommand::Rollback |
|
|
PrivilegedCommand::Deploy |
|
|
PrivilegedCommand::ApplyLive |
|
|
PrivilegedCommand::Cancel |
|
|
PrivilegedCommand::Cleanup |
|
|
PrivilegedCommand::Compose |
|
|
PrivilegedCommand::Checkout |
|
|
PrivilegedCommand::Prune |
|
|
PrivilegedCommand::Kargs |
|
|
PrivilegedCommand::Initramfs |
|
|
PrivilegedCommand::Override |
|
|
PrivilegedCommand::RefreshMd |
|
|
PrivilegedCommand::Reload |
|
|
PrivilegedCommand::Reset |
|
|
PrivilegedCommand::Rebase |
|
|
PrivilegedCommand::InitramfsEtc |
|
|
PrivilegedCommand::Usroverlay
|
|
)
|
|
}
|
|
|
|
/// Validate permissions for a privileged command
|
|
pub fn validate_privileged_command(command: &PrivilegedCommand) -> Result<(), AptOstreeError> {
|
|
if !is_root() {
|
|
let error_msg = format!(
|
|
"Command '{:?}' requires root privileges. Please run with sudo or as root.",
|
|
command
|
|
);
|
|
|
|
error!("{}", error_msg);
|
|
eprintln!("Error: {}", error_msg);
|
|
|
|
if can_use_sudo() {
|
|
eprintln!("Hint: Try running with sudo: sudo apt-ostree {:?}", command);
|
|
} else {
|
|
eprintln!("Hint: Switch to root user or ensure sudo access is available");
|
|
}
|
|
|
|
return Err(AptOstreeError::PermissionDenied(error_msg));
|
|
}
|
|
|
|
info!("Root privileges validated for command: {:?}", command);
|
|
Ok(())
|
|
}
|
|
|
|
/// Validate permissions for a non-privileged command
|
|
pub fn validate_non_privileged_command(command: &NonPrivilegedCommand) -> Result<(), AptOstreeError> {
|
|
info!("Non-privileged command validated: {:?}", command);
|
|
Ok(())
|
|
}
|
|
|
|
/// Check if the user has permission to access OSTree repository
|
|
pub fn can_access_ostree_repo(repo_path: &std::path::Path) -> bool {
|
|
if !repo_path.exists() {
|
|
return false;
|
|
}
|
|
|
|
// Check read permissions
|
|
match std::fs::metadata(repo_path) {
|
|
Ok(metadata) => {
|
|
let permissions = metadata.permissions();
|
|
let current_uid = get_current_uid();
|
|
|
|
// If owned by current user, check user permissions
|
|
if metadata.uid() == current_uid {
|
|
return permissions.mode() & 0o400 != 0;
|
|
}
|
|
|
|
// If owned by root, check group permissions
|
|
if metadata.gid() == 0 {
|
|
return permissions.mode() & 0o040 != 0;
|
|
}
|
|
|
|
// Check other permissions
|
|
permissions.mode() & 0o004 != 0
|
|
},
|
|
Err(_) => false,
|
|
}
|
|
}
|
|
|
|
/// Check if the user has permission to write to OSTree repository
|
|
pub fn can_write_ostree_repo(repo_path: &std::path::Path) -> bool {
|
|
if !repo_path.exists() {
|
|
return false;
|
|
}
|
|
|
|
// Check write permissions
|
|
match std::fs::metadata(repo_path) {
|
|
Ok(metadata) => {
|
|
let permissions = metadata.permissions();
|
|
let current_uid = get_current_uid();
|
|
|
|
// If owned by current user, check user permissions
|
|
if metadata.uid() == current_uid {
|
|
return permissions.mode() & 0o200 != 0;
|
|
}
|
|
|
|
// If owned by root, check group permissions
|
|
if metadata.gid() == 0 {
|
|
return permissions.mode() & 0o020 != 0;
|
|
}
|
|
|
|
// Check other permissions
|
|
permissions.mode() & 0o002 != 0
|
|
},
|
|
Err(_) => false,
|
|
}
|
|
}
|
|
|
|
/// Check if the user has permission to access APT cache
|
|
pub fn can_access_apt_cache() -> bool {
|
|
let apt_cache_path = std::path::Path::new("/var/cache/apt");
|
|
|
|
if !apt_cache_path.exists() {
|
|
return false;
|
|
}
|
|
|
|
match std::fs::metadata(apt_cache_path) {
|
|
Ok(metadata) => {
|
|
let permissions = metadata.permissions();
|
|
let current_uid = get_current_uid();
|
|
|
|
// If owned by root, check group permissions
|
|
if metadata.uid() == 0 {
|
|
return permissions.mode() & 0o040 != 0;
|
|
}
|
|
|
|
// If owned by current user, check user permissions
|
|
if metadata.uid() == current_uid {
|
|
return permissions.mode() & 0o400 != 0;
|
|
}
|
|
|
|
// Check other permissions
|
|
permissions.mode() & 0o004 != 0
|
|
},
|
|
Err(_) => false,
|
|
}
|
|
}
|
|
|
|
/// Check if the user has permission to write to APT cache
|
|
pub fn can_write_apt_cache() -> bool {
|
|
let apt_cache_path = std::path::Path::new("/var/cache/apt");
|
|
|
|
if !apt_cache_path.exists() {
|
|
return false;
|
|
}
|
|
|
|
match std::fs::metadata(apt_cache_path) {
|
|
Ok(metadata) => {
|
|
let permissions = metadata.permissions();
|
|
let current_uid = get_current_uid();
|
|
|
|
// If owned by root, check group permissions and membership
|
|
if metadata.uid() == 0 {
|
|
// Check if group write permission is set
|
|
if permissions.mode() & 0o020 == 0 {
|
|
return false;
|
|
}
|
|
|
|
// Check if current user is in the adm group (which has APT access)
|
|
if let Ok(output) = std::process::Command::new("groups").output() {
|
|
if let Ok(groups_str) = String::from_utf8(output.stdout) {
|
|
return groups_str.contains("adm");
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// If owned by current user, check user permissions
|
|
if metadata.uid() == current_uid {
|
|
return permissions.mode() & 0o200 != 0;
|
|
}
|
|
|
|
// Check other permissions
|
|
permissions.mode() & 0o002 != 0
|
|
},
|
|
Err(_) => false,
|
|
}
|
|
}
|
|
|
|
/// Validate all required permissions for a command
|
|
pub fn validate_all_permissions(command: &PrivilegedCommand) -> Result<(), AptOstreeError> {
|
|
// First check root privileges
|
|
validate_privileged_command(command)?;
|
|
|
|
// Check specific permissions based on command
|
|
match command {
|
|
PrivilegedCommand::Init => {
|
|
// Check if we can create OSTree repository
|
|
let repo_path = std::path::Path::new("/var/lib/apt-ostree");
|
|
if repo_path.exists() && !can_write_ostree_repo(repo_path) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to OSTree repository".to_string()
|
|
));
|
|
}
|
|
},
|
|
PrivilegedCommand::Install | PrivilegedCommand::Remove | PrivilegedCommand::Upgrade => {
|
|
// Check APT cache permissions (temporarily relaxed for testing)
|
|
if !is_root() && !can_write_apt_cache() {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to APT cache".to_string()
|
|
));
|
|
}
|
|
|
|
// Check OSTree repository permissions
|
|
let repo_path = std::path::Path::new("/var/lib/apt-ostree");
|
|
if !can_write_ostree_repo(repo_path) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to OSTree repository".to_string()
|
|
));
|
|
}
|
|
},
|
|
PrivilegedCommand::Rollback | PrivilegedCommand::Checkout | PrivilegedCommand::Deploy | PrivilegedCommand::ApplyLive | PrivilegedCommand::Cancel | PrivilegedCommand::Cleanup | PrivilegedCommand::Compose => {
|
|
// Check OSTree repository permissions
|
|
let repo_path = std::path::Path::new("/var/lib/apt-ostree");
|
|
if !can_write_ostree_repo(repo_path) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to OSTree repository".to_string()
|
|
));
|
|
}
|
|
},
|
|
PrivilegedCommand::Prune => {
|
|
// Check OSTree repository permissions
|
|
let repo_path = std::path::Path::new("/var/lib/apt-ostree");
|
|
if !can_write_ostree_repo(repo_path) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to OSTree repository".to_string()
|
|
));
|
|
}
|
|
},
|
|
PrivilegedCommand::Kargs => {
|
|
// Check boot configuration permissions
|
|
let boot_path = std::path::Path::new("/boot");
|
|
if !can_write_ostree_repo(boot_path) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to boot configuration".to_string()
|
|
));
|
|
}
|
|
},
|
|
PrivilegedCommand::Initramfs => {
|
|
// Check initramfs and boot configuration permissions
|
|
let boot_path = std::path::Path::new("/boot");
|
|
if !can_write_ostree_repo(boot_path) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to boot configuration".to_string()
|
|
));
|
|
}
|
|
|
|
// Check initramfs directory permissions
|
|
let initramfs_path = std::path::Path::new("/boot/initrd.img");
|
|
if initramfs_path.exists() && !can_write_ostree_repo(initramfs_path.parent().unwrap()) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to initramfs directory".to_string()
|
|
));
|
|
}
|
|
},
|
|
PrivilegedCommand::Override => {
|
|
// Check OSTree repository permissions for package overrides
|
|
let repo_path = std::path::Path::new("/var/lib/apt-ostree");
|
|
if !can_write_ostree_repo(repo_path) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to OSTree repository for package overrides".to_string()
|
|
));
|
|
}
|
|
|
|
// Check APT cache permissions for package validation
|
|
if !can_access_apt_cache() {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot access APT cache for package validation".to_string()
|
|
));
|
|
}
|
|
},
|
|
PrivilegedCommand::RefreshMd => {
|
|
// Check APT cache permissions for metadata refresh
|
|
if !can_write_apt_cache() {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to APT cache for metadata refresh".to_string()
|
|
));
|
|
}
|
|
|
|
// Check network access for repository updates
|
|
// This is a basic check - in a real implementation, you might want to test network connectivity
|
|
},
|
|
PrivilegedCommand::Reload => {
|
|
// Check configuration file permissions for reload
|
|
let config_path = std::path::Path::new("/etc/apt-ostree");
|
|
if config_path.exists() && !can_write_ostree_repo(config_path) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to configuration directory".to_string()
|
|
));
|
|
}
|
|
},
|
|
PrivilegedCommand::Reset => {
|
|
// Check OSTree repository permissions for state reset
|
|
let repo_path = std::path::Path::new("/var/lib/apt-ostree");
|
|
if !can_write_ostree_repo(repo_path) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to OSTree repository for state reset".to_string()
|
|
));
|
|
}
|
|
|
|
// Check deployment directory permissions
|
|
let deployment_path = std::path::Path::new("/ostree/deploy");
|
|
if !can_write_ostree_repo(deployment_path) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to deployment directory for state reset".to_string()
|
|
));
|
|
}
|
|
},
|
|
PrivilegedCommand::Rebase => {
|
|
// Check OSTree repository permissions for rebase
|
|
let repo_path = std::path::Path::new("/var/lib/apt-ostree");
|
|
if !can_write_ostree_repo(repo_path) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to OSTree repository for rebase".to_string()
|
|
));
|
|
}
|
|
|
|
// Check deployment directory permissions
|
|
let deployment_path = std::path::Path::new("/ostree/deploy");
|
|
if !can_write_ostree_repo(deployment_path) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to deployment directory for rebase".to_string()
|
|
));
|
|
}
|
|
|
|
// Check network access for refspec validation
|
|
// This is a basic check - in a real implementation, you might want to test network connectivity
|
|
},
|
|
PrivilegedCommand::InitramfsEtc => {
|
|
// Check initramfs directory permissions
|
|
let initramfs_path = std::path::Path::new("/boot");
|
|
if !can_write_ostree_repo(initramfs_path) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to boot directory for initramfs-etc".to_string()
|
|
));
|
|
}
|
|
|
|
// Check /etc directory permissions for file tracking
|
|
let etc_path = std::path::Path::new("/etc");
|
|
if !can_write_ostree_repo(etc_path) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to /etc directory for initramfs-etc".to_string()
|
|
));
|
|
}
|
|
},
|
|
PrivilegedCommand::Usroverlay => {
|
|
// Check /usr directory permissions for overlayfs
|
|
let usr_path = std::path::Path::new("/usr");
|
|
if !can_write_ostree_repo(usr_path) {
|
|
return Err(AptOstreeError::PermissionDenied(
|
|
"Cannot write to /usr directory for usroverlay".to_string()
|
|
));
|
|
}
|
|
|
|
// Check overlayfs support
|
|
// This would typically involve checking if overlayfs is available
|
|
// For now, we'll just log the action
|
|
},
|
|
PrivilegedCommand::DaemonPing => {
|
|
// DaemonPing doesn't require special filesystem permissions
|
|
// Just basic environment validation
|
|
},
|
|
}
|
|
|
|
info!("All permissions validated for command: {:?}", command);
|
|
Ok(())
|
|
}
|
|
|
|
/// Suggest privilege escalation method
|
|
pub fn suggest_privilege_escalation(command: &PrivilegedCommand) {
|
|
if !is_root() {
|
|
eprintln!("To run this command, you need root privileges.");
|
|
|
|
if can_use_sudo() {
|
|
eprintln!("Try: sudo apt-ostree {:?}", command);
|
|
} else {
|
|
eprintln!("Switch to root user: sudo su -");
|
|
eprintln!("Then run: apt-ostree {:?}", command);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Check if running in a container environment
|
|
pub fn is_container_environment() -> bool {
|
|
// Check for common container indicators
|
|
let container_indicators = [
|
|
"/.dockerenv",
|
|
"/proc/1/cgroup",
|
|
"/proc/self/cgroup",
|
|
];
|
|
|
|
for indicator in &container_indicators {
|
|
if std::path::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
|
|
}
|
|
|
|
/// Validate environment for apt-ostree operations
|
|
pub fn validate_environment() -> Result<(), AptOstreeError> {
|
|
// Check if running in a supported environment
|
|
if is_container_environment() {
|
|
warn!("Running in container environment - some features may be limited");
|
|
}
|
|
|
|
// Check for required system components
|
|
let required_components = [
|
|
("ostree", "OSTree"),
|
|
("apt-get", "APT"),
|
|
("dpkg", "DPKG"),
|
|
];
|
|
|
|
for (binary, name) in &required_components {
|
|
if std::process::Command::new(binary)
|
|
.arg("--version")
|
|
.output()
|
|
.is_err() {
|
|
return Err(AptOstreeError::Configuration(
|
|
format!("Required component '{}' not found", name)
|
|
));
|
|
}
|
|
}
|
|
|
|
info!("Environment validation passed");
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_is_root() {
|
|
// This test will pass or fail depending on how it's run
|
|
let _root_status = is_root();
|
|
}
|
|
|
|
#[test]
|
|
fn test_requires_root() {
|
|
assert!(requires_root(&PrivilegedCommand::Install));
|
|
assert!(requires_root(&PrivilegedCommand::Remove));
|
|
assert!(requires_root(&PrivilegedCommand::Init));
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_current_uid_gid() {
|
|
let uid = get_current_uid();
|
|
let gid = get_current_gid();
|
|
|
|
assert!(uid > 0 || uid == 0); // Valid UID range
|
|
assert!(gid > 0 || gid == 0); // Valid GID range
|
|
}
|
|
|
|
#[test]
|
|
fn test_validate_non_privileged_command() {
|
|
let result = validate_non_privileged_command(&NonPrivilegedCommand::List);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_validate_environment() {
|
|
let result = validate_environment();
|
|
// This test may fail if required components are not installed
|
|
// but that's expected in some test environments
|
|
if result.is_err() {
|
|
println!("Environment validation failed (expected in some test environments)");
|
|
}
|
|
}
|
|
}
|