container: Fail early on missing subuid/subgid ranges
Rootless Podman falls back to a single-UID user namespace when the invoking user has no /etc/subuid and /etc/subgid entry, and then any chown to an unmapped UID fails with EINVAL. Newer pacman versions hit this during builds and updates: their download sandbox chowns its temporary download directory to the 'alpm' user, which made image builds fail with a cryptic traceback after several minutes of output. Accounts created manually or before shadow 4.11.1-3 (October 2022) get no subordinate ID ranges automatically, while freshly created ones do. Rather than working around this in the image (disabling pacman's sandbox would silently drop a security feature), detect the missing ranges before building and stop with the exact fix, using the first unclaimed 65536-ID block so the printed command never collides with other users' ranges. Link the Arch Wiki section and Podman's rootless tutorial for background.
This commit is contained in:
parent
fa32cc4218
commit
8a7af06983
2 changed files with 83 additions and 1 deletions
|
|
@ -69,7 +69,7 @@ socket goes away.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
You need rootless Podman on the host. On Arch Linux the packages `podman` and `passt` (or `slirp4netns`) are enough; your user also needs subuid/subgid entries, which `usermod --add-subuids 100000-165535 --add-subgids 100000-165535 <user>` sets up if the package did not:
|
You need rootless Podman on the host. On Arch Linux the packages `podman` and `passt` (or `slirp4netns`) are enough; your user also needs subuid/subgid entries, which `usermod --add-subuids 100000-165535 --add-subgids 100000-165535 <user>` sets up if the package did not (accounts created before shadow 4.11.1-3 get no entries automatically — see [Podman: Set subuid and subgid](https://wiki.archlinux.org/title/Podman#Set_subuid_and_subgid)):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo pacman -S podman passt
|
sudo pacman -S podman passt
|
||||||
|
|
|
||||||
|
|
@ -363,7 +363,89 @@ class AgentContainer:
|
||||||
== 0
|
== 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Subordinate ID block size; rootless Podman needs at least 65536 IDs so
|
||||||
|
# every container UID is mappable. 100000 is the default first block the
|
||||||
|
# shadow suite allocates on Arch (and the number the Arch Wiki suggests).
|
||||||
|
SUBID_SIZE = 65536
|
||||||
|
SUBID_START = 100000
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _free_subid_range(cls) -> str:
|
||||||
|
"""Return the first unclaimed 65536-ID block as "start-end".
|
||||||
|
|
||||||
|
Ranges in /etc/subuid and /etc/subgid must never overlap between
|
||||||
|
users (a shared range would let one user impersonate another inside
|
||||||
|
containers), so both files' claims are considered.
|
||||||
|
"""
|
||||||
|
taken = []
|
||||||
|
for path in ("/etc/subuid", "/etc/subgid"):
|
||||||
|
try:
|
||||||
|
with open(path) as f:
|
||||||
|
for line in f:
|
||||||
|
parts = line.split(":")
|
||||||
|
if len(parts) < 3:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
taken.append(
|
||||||
|
(int(parts[1]), int(parts[1]) + int(parts[2]))
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
taken.sort()
|
||||||
|
start = cls.SUBID_START
|
||||||
|
for lo, hi in taken:
|
||||||
|
if hi <= start:
|
||||||
|
continue
|
||||||
|
if lo - start >= cls.SUBID_SIZE:
|
||||||
|
break
|
||||||
|
start = max(start, hi)
|
||||||
|
return f"{start}-{start + cls.SUBID_SIZE - 1}"
|
||||||
|
|
||||||
|
def _subid_guard(self) -> None:
|
||||||
|
"""Warn when rootless Podman has no subuid/subgid ranges for this user.
|
||||||
|
|
||||||
|
Without a range in /etc/subuid and /etc/subgid, Podman falls back to a
|
||||||
|
single-UID user namespace: the container only has the invoking user's
|
||||||
|
UID, and chown to any other UID fails with EINVAL. This breaks e.g.
|
||||||
|
pacman's download sandbox, which chowns its download directory to the
|
||||||
|
'alpm' user. Distros like Fedora allocate ranges for every user
|
||||||
|
automatically, but accounts created manually (e.g. on Arch) may not
|
||||||
|
have one.
|
||||||
|
"""
|
||||||
|
if self.RUNTIME != "podman" or os.geteuid() == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
def has_entry(path: str) -> bool:
|
||||||
|
try:
|
||||||
|
with open(path) as f:
|
||||||
|
return any(line.split(":", 1)[0] == self.host_username for line in f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
missing = [
|
||||||
|
p for p in ("/etc/subuid", "/etc/subgid") if not has_entry(p)
|
||||||
|
]
|
||||||
|
if not missing:
|
||||||
|
return
|
||||||
|
|
||||||
|
range_str = self._free_subid_range()
|
||||||
|
logger.error(
|
||||||
|
f"No subuid/subgid entry for user '{self.host_username}' in"
|
||||||
|
" /etc/subuid, /etc/subgid — rootless Podman has only a single UID"
|
||||||
|
" available, so operations inside containers that chown to other"
|
||||||
|
" UIDs (e.g. pacman's alpm download sandbox) fail with 'Invalid"
|
||||||
|
" argument'. Fix once per user:\n"
|
||||||
|
f" sudo usermod --add-subuids {range_str} --add-subgids {range_str} {self.host_username}\n"
|
||||||
|
" podman system migrate\n"
|
||||||
|
" See https://wiki.archlinux.org/title/Podman#Set_subuid_and_subgid"
|
||||||
|
" or https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md"
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
def build_image(self, no_cache: bool = False, pull: bool = False) -> None:
|
def build_image(self, no_cache: bool = False, pull: bool = False) -> None:
|
||||||
|
self._subid_guard()
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Building image '{self.IMAGE}' with user {self.host_username} ({self.host_uid}:{self.host_gid})"
|
f"Building image '{self.IMAGE}' with user {self.host_username} ({self.host_uid}:{self.host_gid})"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue