From c6d5a3f2a588ff4ecabcddeefb874733aaee106b Mon Sep 17 00:00:00 2001 From: Christoffer Martinsson Date: Tue, 14 Oct 2025 11:35:42 +0200 Subject: [PATCH] Add sandbox exclusion list for system services Implement exclusion list for services that don't require sandboxing due to their nature (SSH, Docker, system services). These services now show "no(ok)" in SB column and maintain green status instead of warning. Changes: - Add is_sandbox_excluded field to ServiceData and ServiceInfo structs - Add is_sandbox_excluded() method with system service exclusions: - sshd/ssh (needs system access for auth/shell) - docker (needs broad system access) - systemd services, dbus, NetworkManager, etc. - Update status determination to accept excluded services as ok - Update format_sandbox_value to show "no(ok)" for excluded services - Update all ServiceData constructors with exclusion field Service status logic: - Sandboxed: Status=Running, SB="yes" - Excluded: Status=Running, SB="no(ok)" - Should be sandboxed but isn't: Status=Degraded, SB="no" This provides clear distinction between services that legitimately don't need sandboxing vs. those requiring security attention. --- agent/src/collectors/service.rs | 28 ++++++++++++++++++++++++---- dashboard/src/data/metrics.rs | 2 ++ dashboard/src/ui/services.rs | 8 +++++--- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/agent/src/collectors/service.rs b/agent/src/collectors/service.rs index 8374a31..947ebfb 100644 --- a/agent/src/collectors/service.rs +++ b/agent/src/collectors/service.rs @@ -81,8 +81,9 @@ impl ServiceCollector { // Check if service is sandboxed (needed for status determination) let is_sandboxed = self.check_service_sandbox(service).await.unwrap_or(false); + let is_sandbox_excluded = self.is_sandbox_excluded(service); - let status = self.determine_service_status(&active_state, &sub_state, is_sandboxed); + let status = self.determine_service_status(&active_state, &sub_state, is_sandboxed, service); // Get resource usage if service is running let (memory_used_mb, cpu_percent) = if let Some(pid) = main_pid { @@ -125,21 +126,36 @@ impl ServiceCollector { disk_used_gb, disk_quota_gb, is_sandboxed, + is_sandbox_excluded, description, sub_service: None, }) } + fn is_sandbox_excluded(&self, service: &str) -> bool { + // Services that don't need sandboxing due to their nature + matches!(service, + "sshd" | "ssh" | // SSH needs system access for auth/shell + "docker" | // Docker needs broad system access + "systemd-logind" | // System service + "systemd-resolved" | // System service + "dbus" | // System service + "NetworkManager" | // Network management + "wpa_supplicant" // WiFi management + ) + } + fn determine_service_status( &self, active_state: &Option, sub_state: &Option, is_sandboxed: bool, + service_name: &str, ) -> ServiceStatus { match (active_state.as_deref(), sub_state.as_deref()) { (Some("active"), Some("running")) => { - // Running services should be degraded if not sandboxed - if is_sandboxed { + // Check if service is excluded from sandbox requirements + if self.is_sandbox_excluded(service_name) || is_sandboxed { ServiceStatus::Running } else { ServiceStatus::Degraded // Warning status for unsandboxed running services @@ -147,7 +163,7 @@ impl ServiceCollector { }, (Some("active"), Some("exited")) => { // One-shot services should also be degraded if not sandboxed - if is_sandboxed { + if self.is_sandbox_excluded(service_name) || is_sandboxed { ServiceStatus::Running } else { ServiceStatus::Degraded @@ -1314,6 +1330,7 @@ impl Collector for ServiceCollector { disk_used_gb: 0.0, disk_quota_gb: 0.0, is_sandboxed: false, // Sub-services inherit parent sandbox status + is_sandbox_excluded: false, description: None, sub_service: Some("nginx".to_string()), }); @@ -1341,6 +1358,7 @@ impl Collector for ServiceCollector { disk_used_gb: 0.0, disk_quota_gb: 0.0, is_sandboxed: true, // Docker containers are inherently sandboxed + is_sandbox_excluded: false, description: None, sub_service: Some("docker".to_string()), }); @@ -1364,6 +1382,7 @@ impl Collector for ServiceCollector { disk_used_gb: 0.0, disk_quota_gb: 0.0, is_sandboxed: false, // Unknown for failed services + is_sandbox_excluded: false, description: None, sub_service: None, }); @@ -1425,6 +1444,7 @@ struct ServiceData { disk_used_gb: f32, disk_quota_gb: f32, is_sandboxed: bool, + is_sandbox_excluded: bool, #[serde(skip_serializing_if = "Option::is_none")] description: Option>, #[serde(default)] diff --git a/dashboard/src/data/metrics.rs b/dashboard/src/data/metrics.rs index decc624..110bbbd 100644 --- a/dashboard/src/data/metrics.rs +++ b/dashboard/src/data/metrics.rs @@ -121,6 +121,8 @@ pub struct ServiceInfo { #[serde(default)] pub is_sandboxed: bool, #[serde(default)] + pub is_sandbox_excluded: bool, + #[serde(default)] pub description: Option>, #[serde(default)] pub sub_service: Option, diff --git a/dashboard/src/ui/services.rs b/dashboard/src/ui/services.rs index 406d1d0..50aaf43 100644 --- a/dashboard/src/ui/services.rs +++ b/dashboard/src/ui/services.rs @@ -129,7 +129,7 @@ fn render_metrics( format_memory_value(svc.memory_used_mb, svc.memory_quota_mb), format_cpu_value(svc.cpu_percent), format_disk_value(svc.disk_used_gb, svc.disk_quota_gb), - format_sandbox_value(svc.is_sandboxed), + format_sandbox_value(svc.is_sandboxed, svc.is_sandbox_excluded), ], ); } @@ -171,11 +171,13 @@ fn format_disk_value(used: f32, quota: f32) -> String { } } -fn format_sandbox_value(is_sandboxed: bool) -> String { +fn format_sandbox_value(is_sandboxed: bool, is_excluded: bool) -> String { if is_sandboxed { "yes".to_string() + } else if is_excluded { + "no(ok)".to_string() // Excluded services don't need sandboxing } else { - "no".to_string() + "no".to_string() // Services that should be sandboxed but aren't } }