feat(linux): run systemd service as unprivileged numa user #118
34
numa.service
34
numa.service
@@ -8,6 +8,40 @@ Type=simple
|
|||||||
ExecStart={{exe_path}}
|
ExecStart={{exe_path}}
|
||||||
Restart=always
|
Restart=always
|
||||||
RestartSec=2
|
RestartSec=2
|
||||||
|
|
||||||
|
User=numa
|
||||||
|
Group=numa
|
||||||
|
|
||||||
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
|
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||||
|
|
||||||
|
# StateDirectory maps to crate::data_dir() default on Linux (/var/lib/numa).
|
||||||
|
# systemd auto-creates + chowns on every start, fixing legacy root-owned trees.
|
||||||
|
StateDirectory=numa
|
||||||
|
StateDirectoryMode=0750
|
||||||
|
ConfigurationDirectory=numa
|
||||||
|
ConfigurationDirectoryMode=0755
|
||||||
|
|
||||||
|
# Sandboxing
|
||||||
|
NoNewPrivileges=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
PrivateTmp=true
|
||||||
|
PrivateDevices=true
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
LockPersonality=true
|
||||||
|
MemoryDenyWriteExecute=true
|
||||||
|
RestrictNamespaces=true
|
||||||
|
RestrictRealtime=true
|
||||||
|
RestrictSUIDSGID=true
|
||||||
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=@system-service
|
||||||
|
SystemCallFilter=~@privileged @resources
|
||||||
|
# AF_NETLINK for interface enumeration on network changes
|
||||||
|
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK
|
||||||
|
|
||||||
StandardOutput=journal
|
StandardOutput=journal
|
||||||
StandardError=journal
|
StandardError=journal
|
||||||
SyslogIdentifier=numa
|
SyslogIdentifier=numa
|
||||||
|
|||||||
@@ -1664,8 +1664,68 @@ fn uninstall_linux() -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
const NUMA_USER: &str = "numa";
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn ensure_numa_user_linux() -> Result<(), String> {
|
||||||
|
let _ = std::process::Command::new("groupadd")
|
||||||
|
.args(["-f", "-r", NUMA_USER])
|
||||||
|
.status();
|
||||||
|
|
||||||
|
let data_dir = crate::data_dir();
|
||||||
|
let status = std::process::Command::new("useradd")
|
||||||
|
.args([
|
||||||
|
"-r",
|
||||||
|
"-g",
|
||||||
|
NUMA_USER,
|
||||||
|
"-d",
|
||||||
|
&data_dir.to_string_lossy(),
|
||||||
|
"-s",
|
||||||
|
"/usr/sbin/nologin",
|
||||||
|
"-c",
|
||||||
|
"Numa DNS service",
|
||||||
|
NUMA_USER,
|
||||||
|
])
|
||||||
|
.status()
|
||||||
|
.map_err(|e| format!("failed to run useradd: {}", e))?;
|
||||||
|
|
||||||
|
// useradd exit 9 = "username already in use"; idempotent reinstall.
|
||||||
|
match status.code() {
|
||||||
|
Some(0) | Some(9) => Ok(()),
|
||||||
|
Some(code) => Err(format!("useradd {} failed (exit {})", NUMA_USER, code)),
|
||||||
|
None => Err(format!("useradd {} killed by signal", NUMA_USER)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn chown_data_dir_to_numa_linux() -> Result<(), String> {
|
||||||
|
let dir = crate::data_dir();
|
||||||
|
std::fs::create_dir_all(&dir)
|
||||||
|
.map_err(|e| format!("failed to create {}: {}", dir.display(), e))?;
|
||||||
|
let owner = format!("{0}:{0}", NUMA_USER);
|
||||||
|
let status = std::process::Command::new("chown")
|
||||||
|
.args(["-R", &owner, &dir.to_string_lossy()])
|
||||||
|
.status()
|
||||||
|
.map_err(|e| format!("failed to run chown: {}", e))?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(format!(
|
||||||
|
"chown {} failed (exit {})",
|
||||||
|
dir.display(),
|
||||||
|
status.code().unwrap_or(-1)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn install_service_linux() -> Result<(), String> {
|
fn install_service_linux() -> Result<(), String> {
|
||||||
|
// Create the numa account and hand it ownership of data_dir before the
|
||||||
|
// first start — TLS-cert generation and state writes happen on the
|
||||||
|
// unit's first launch and need to land on a numa-owned tree.
|
||||||
|
ensure_numa_user_linux()?;
|
||||||
|
chown_data_dir_to_numa_linux()?;
|
||||||
|
|
||||||
let unit = include_str!("../numa.service");
|
let unit = include_str!("../numa.service");
|
||||||
let unit = replace_exe_path(unit)?;
|
let unit = replace_exe_path(unit)?;
|
||||||
std::fs::write(SYSTEMD_UNIT, unit)
|
std::fs::write(SYSTEMD_UNIT, unit)
|
||||||
|
|||||||
Reference in New Issue
Block a user