fix: allow running gateway service as root for LXC/container environments (#4732)
Previously, `hermes gateway install --system` hard-refused to create a service running as root, even when explicitly requested via `--run-as-user root`. This forced LXC/container users (where root is the only user) to either create throwaway users or comment out the check in source. Changes: - Auto-detected root (no explicit --run-as-user) still raises, but with a message explaining how to override - Explicit `--run-as-user root` now allowed with a warning about security implications - Interactive setup wizard prompt accepts 'root' as a valid username (warning comes from _system_service_identity downstream) - Added tests for all three paths: auto-detected root rejection, explicit root allowance, and normal non-root passthrough
This commit is contained in:
@@ -258,8 +258,11 @@ def _system_service_identity(run_as_user: str | None = None) -> tuple[str, str,
|
|||||||
username = (run_as_user or os.getenv("SUDO_USER") or os.getenv("USER") or os.getenv("LOGNAME") or getpass.getuser()).strip()
|
username = (run_as_user or os.getenv("SUDO_USER") or os.getenv("USER") or os.getenv("LOGNAME") or getpass.getuser()).strip()
|
||||||
if not username:
|
if not username:
|
||||||
raise ValueError("Could not determine which user the gateway service should run as")
|
raise ValueError("Could not determine which user the gateway service should run as")
|
||||||
|
if username == "root" and not run_as_user:
|
||||||
|
raise ValueError("Refusing to install the gateway system service as root; pass --run-as-user root to override (e.g. in LXC containers)")
|
||||||
if username == "root":
|
if username == "root":
|
||||||
raise ValueError("Refusing to install the gateway system service as root; pass --run-as USER")
|
print_warning("Installing gateway service to run as root.")
|
||||||
|
print_info(" This is fine for LXC/container environments but not recommended on bare-metal hosts.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user_info = pwd.getpwnam(username)
|
user_info = pwd.getpwnam(username)
|
||||||
@@ -321,9 +324,9 @@ def install_linux_gateway_from_setup(force: bool = False) -> tuple[str | None, b
|
|||||||
while True:
|
while True:
|
||||||
run_as_user = prompt(" Run the system gateway service as which user?", default="")
|
run_as_user = prompt(" Run the system gateway service as which user?", default="")
|
||||||
run_as_user = (run_as_user or "").strip()
|
run_as_user = (run_as_user or "").strip()
|
||||||
if run_as_user and run_as_user != "root":
|
if run_as_user:
|
||||||
break
|
break
|
||||||
print_error(" Enter a non-root username.")
|
print_error(" Enter a username.")
|
||||||
|
|
||||||
systemd_install(force=force, system=True, run_as_user=run_as_user)
|
systemd_install(force=force, system=True, run_as_user=run_as_user)
|
||||||
return scope, True
|
return scope, True
|
||||||
|
|||||||
@@ -466,6 +466,51 @@ class TestGeneratedUnitIncludesLocalBin:
|
|||||||
assert "/.local/bin" in unit
|
assert "/.local/bin" in unit
|
||||||
|
|
||||||
|
|
||||||
|
class TestSystemServiceIdentityRootHandling:
|
||||||
|
"""Root user handling in _system_service_identity()."""
|
||||||
|
|
||||||
|
def test_auto_detected_root_is_rejected(self, monkeypatch):
|
||||||
|
"""When root is auto-detected (not explicitly requested), raise."""
|
||||||
|
import pwd
|
||||||
|
import grp
|
||||||
|
|
||||||
|
monkeypatch.delenv("SUDO_USER", raising=False)
|
||||||
|
monkeypatch.setenv("USER", "root")
|
||||||
|
monkeypatch.setenv("LOGNAME", "root")
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
with pytest.raises(ValueError, match="pass --run-as-user root to override"):
|
||||||
|
gateway_cli._system_service_identity(run_as_user=None)
|
||||||
|
|
||||||
|
def test_explicit_root_is_allowed(self, monkeypatch):
|
||||||
|
"""When root is explicitly passed via --run-as-user root, allow it."""
|
||||||
|
import pwd
|
||||||
|
import grp
|
||||||
|
|
||||||
|
root_info = pwd.getpwnam("root")
|
||||||
|
root_group = grp.getgrgid(root_info.pw_gid).gr_name
|
||||||
|
|
||||||
|
username, group, home = gateway_cli._system_service_identity(run_as_user="root")
|
||||||
|
assert username == "root"
|
||||||
|
assert home == root_info.pw_dir
|
||||||
|
|
||||||
|
def test_non_root_user_passes_through(self, monkeypatch):
|
||||||
|
"""Normal non-root user works as before."""
|
||||||
|
import pwd
|
||||||
|
import grp
|
||||||
|
|
||||||
|
monkeypatch.delenv("SUDO_USER", raising=False)
|
||||||
|
monkeypatch.setenv("USER", "nobody")
|
||||||
|
monkeypatch.setenv("LOGNAME", "nobody")
|
||||||
|
|
||||||
|
try:
|
||||||
|
username, group, home = gateway_cli._system_service_identity(run_as_user=None)
|
||||||
|
assert username == "nobody"
|
||||||
|
except ValueError as e:
|
||||||
|
# "nobody" might not exist on all systems
|
||||||
|
assert "Unknown user" in str(e)
|
||||||
|
|
||||||
|
|
||||||
class TestEnsureUserSystemdEnv:
|
class TestEnsureUserSystemdEnv:
|
||||||
"""Tests for _ensure_user_systemd_env() D-Bus session bus auto-detection."""
|
"""Tests for _ensure_user_systemd_env() D-Bus session bus auto-detection."""
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user