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.
This commit is contained in:
Christoffer Martinsson 2025-10-14 11:35:42 +02:00
parent 4fa2b079f1
commit c6d5a3f2a5
3 changed files with 31 additions and 7 deletions

View File

@ -81,8 +81,9 @@ impl ServiceCollector {
// Check if service is sandboxed (needed for status determination) // Check if service is sandboxed (needed for status determination)
let is_sandboxed = self.check_service_sandbox(service).await.unwrap_or(false); 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 // Get resource usage if service is running
let (memory_used_mb, cpu_percent) = if let Some(pid) = main_pid { let (memory_used_mb, cpu_percent) = if let Some(pid) = main_pid {
@ -125,21 +126,36 @@ impl ServiceCollector {
disk_used_gb, disk_used_gb,
disk_quota_gb, disk_quota_gb,
is_sandboxed, is_sandboxed,
is_sandbox_excluded,
description, description,
sub_service: None, 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( fn determine_service_status(
&self, &self,
active_state: &Option<String>, active_state: &Option<String>,
sub_state: &Option<String>, sub_state: &Option<String>,
is_sandboxed: bool, is_sandboxed: bool,
service_name: &str,
) -> ServiceStatus { ) -> ServiceStatus {
match (active_state.as_deref(), sub_state.as_deref()) { match (active_state.as_deref(), sub_state.as_deref()) {
(Some("active"), Some("running")) => { (Some("active"), Some("running")) => {
// Running services should be degraded if not sandboxed // Check if service is excluded from sandbox requirements
if is_sandboxed { if self.is_sandbox_excluded(service_name) || is_sandboxed {
ServiceStatus::Running ServiceStatus::Running
} else { } else {
ServiceStatus::Degraded // Warning status for unsandboxed running services ServiceStatus::Degraded // Warning status for unsandboxed running services
@ -147,7 +163,7 @@ impl ServiceCollector {
}, },
(Some("active"), Some("exited")) => { (Some("active"), Some("exited")) => {
// One-shot services should also be degraded if not sandboxed // One-shot services should also be degraded if not sandboxed
if is_sandboxed { if self.is_sandbox_excluded(service_name) || is_sandboxed {
ServiceStatus::Running ServiceStatus::Running
} else { } else {
ServiceStatus::Degraded ServiceStatus::Degraded
@ -1314,6 +1330,7 @@ impl Collector for ServiceCollector {
disk_used_gb: 0.0, disk_used_gb: 0.0,
disk_quota_gb: 0.0, disk_quota_gb: 0.0,
is_sandboxed: false, // Sub-services inherit parent sandbox status is_sandboxed: false, // Sub-services inherit parent sandbox status
is_sandbox_excluded: false,
description: None, description: None,
sub_service: Some("nginx".to_string()), sub_service: Some("nginx".to_string()),
}); });
@ -1341,6 +1358,7 @@ impl Collector for ServiceCollector {
disk_used_gb: 0.0, disk_used_gb: 0.0,
disk_quota_gb: 0.0, disk_quota_gb: 0.0,
is_sandboxed: true, // Docker containers are inherently sandboxed is_sandboxed: true, // Docker containers are inherently sandboxed
is_sandbox_excluded: false,
description: None, description: None,
sub_service: Some("docker".to_string()), sub_service: Some("docker".to_string()),
}); });
@ -1364,6 +1382,7 @@ impl Collector for ServiceCollector {
disk_used_gb: 0.0, disk_used_gb: 0.0,
disk_quota_gb: 0.0, disk_quota_gb: 0.0,
is_sandboxed: false, // Unknown for failed services is_sandboxed: false, // Unknown for failed services
is_sandbox_excluded: false,
description: None, description: None,
sub_service: None, sub_service: None,
}); });
@ -1425,6 +1444,7 @@ struct ServiceData {
disk_used_gb: f32, disk_used_gb: f32,
disk_quota_gb: f32, disk_quota_gb: f32,
is_sandboxed: bool, is_sandboxed: bool,
is_sandbox_excluded: bool,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
description: Option<Vec<String>>, description: Option<Vec<String>>,
#[serde(default)] #[serde(default)]

View File

@ -121,6 +121,8 @@ pub struct ServiceInfo {
#[serde(default)] #[serde(default)]
pub is_sandboxed: bool, pub is_sandboxed: bool,
#[serde(default)] #[serde(default)]
pub is_sandbox_excluded: bool,
#[serde(default)]
pub description: Option<Vec<String>>, pub description: Option<Vec<String>>,
#[serde(default)] #[serde(default)]
pub sub_service: Option<String>, pub sub_service: Option<String>,

View File

@ -129,7 +129,7 @@ fn render_metrics(
format_memory_value(svc.memory_used_mb, svc.memory_quota_mb), format_memory_value(svc.memory_used_mb, svc.memory_quota_mb),
format_cpu_value(svc.cpu_percent), format_cpu_value(svc.cpu_percent),
format_disk_value(svc.disk_used_gb, svc.disk_quota_gb), 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 { if is_sandboxed {
"yes".to_string() "yes".to_string()
} else if is_excluded {
"no(ok)".to_string() // Excluded services don't need sandboxing
} else { } else {
"no".to_string() "no".to_string() // Services that should be sandboxed but aren't
} }
} }