apt-ostree/src/permissions.rs

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)");
}
}
}