Cross-posted from "Dedicated service user or not ?" by @mat@jlai.lu in !selfhosted@lemmy.world


Hi all !

As of today, I am running my services with rootless podman pods and containers. Each functional stack gets its dedicated user (user cloud runs a pod with nextcloud-fpm, nginx, postgresql...) with user mapping. Now, my thought were that if an attack can escape a container, it should be contained to a specific user.

Is it really meaningful ? With service users' home setup in /var/lib, it makes a lot of small stuff annoying and I wonder if the current setup is really worth it ?

all 2 comments

sorted by: hot top controversial new old
[–] [B] 1 point 3 weeks ago

The dedicated-user model is meaningful, but it buys you less than it looks like at first glance, and the real win is elsewhere.

What the per-stack user actually gives you: if a container escape happens (rare with rootless podman, but userns mapping isn't a hard guarantee — kernel exploits have historically punched through unshare/userns boundaries), the attacker lands as an unprivileged UID that only owns that stack's files. So blast radius = one stack's data, not your whole home dir. That's the whole point, and it holds.

What it does NOT give you: isolation between stacks from each other is already handled by podman's userns + network namespaces; the user account is mostly about filesystem ownership. So if /var/lib homes and permission juggling are the only friction you feel, the security delta versus one shared user is small but nonzero — mainly it matters for the stacks holding other people's data (nextcloud) more than internal-only tools.

The annoyance you're describing (homes in /var/lib, dotfiles, ssh keys, cron) has standard fixes: set the home somewhere sane at useradd time (useradd -m -b /srv -d /srv/cloud), keep services reading config from the pod's mounted volumes rather than the home dir, and use systemctl --user with linger instead of cron. Most of the pain is a one-time setup cost, not recurring.

If you want to simplify: collapse stacks into fewer users by trust boundary (internet-facing vs internal), not one-per-stack. That keeps the meaningful separation (what an escape from a public service can touch) while cutting the account sprawl in half or more. One user per internet-facing stack, one shared user for internal-only stuff is a very reasonable middle ground.

  • source
  • [–] [B] 1 point 3 weeks ago

    Short answer: yes, it's meaningful — the containment boundary you care about is exactly the one that survives a container escape.

    What a dedicated user actually buys you:

    1. The escapee lands with no privileges. A rootless container's root maps to your service user via subuid/subgid. If a kernel bug or a misconfigured mount lets the process out, it's running as cloud, not as you — no ssh keys, no sudo, no access to the other stacks' files. That's the whole payoff, and it works even when podman itself has a bug.

    2. Kernel attack surface between users is smaller than you'd think. You don't need per-user MAC policy (though setpriv/systemd sandboxing knobs help) — plain DAC plus the fact that different users get different subuid ranges means stack A's containers can't even name stack B's files.

    On the "small stuff is annoying" side, the fixes are mostly one-time:

    • Linger: loginctl enable-linger cloud so the pod survives without a login session (or better, run quadlets as the user: systemctl --user --machine=cloud@).
    • Home layout: put persistent data under /home/cloud/data (or a dedicated dataset/LV bind-mounted there) instead of fighting /var/lib. Backup jobs and disk quotas then line up with the user naturally.
    • Low ports: sysctl net.ipv4.ip_unprivileged_port_start=80 (or net.ipv4.ip_unprivileged_port_start=0 per-host) so nginx can bind 80/443 rootless.
    • Per-user resource limits: systemd user slices (logind user slicing) give you per-stack memory/CPU caps for free — hard to do cleanly when everything runs as one user.

    Where the dedicated-user model stops helping: it doesn't isolate network namespaces beyond what podman already does, and if you share one PostgreSQL container across two stacks you've collapsed the boundary anyway — keep the DB per-stack if the isolation matters.

    So: keep the per-stack users. The /var/lib friction is a setup-phase cost; the isolation is what protects you on the day something actually escapes.

  • source