Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use passwd symlinks instead of bind mounts #3200

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions mkosi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
chroot_cmd,
chroot_options,
finalize_interpreter,
finalize_passwd_mounts,
finalize_passwd_symlinks,
fork_and_wait,
run,
workdir,
Expand Down Expand Up @@ -2822,9 +2822,8 @@ def run_tmpfiles(context: Context) -> None:
options=[
"--bind", context.root, "/buildroot",
# systemd uses acl.h to parse ACLs in tmpfiles snippets which uses the host's
# passwd so we have to mount the image's passwd over it to make ACL parsing
# work.
*finalize_passwd_mounts(context.root),
# passwd so we have to symlink the image's passwd to make ACL parsing work.
*finalize_passwd_symlinks("/buildroot"),
# Sometimes directories are configured to be owned by root in tmpfiles snippets
# so we want to make sure those chown()'s succeed by making ourselves the root
# user so that the root user exists.
Expand Down
8 changes: 4 additions & 4 deletions mkosi/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from typing import Optional

from mkosi.log import log_step
from mkosi.run import SandboxProtocol, finalize_passwd_mounts, nosandbox, run, workdir
from mkosi.run import SandboxProtocol, finalize_passwd_symlinks, nosandbox, run, workdir
from mkosi.sandbox import umask
from mkosi.types import PathString
from mkosi.util import chdir
Expand Down Expand Up @@ -51,7 +51,7 @@ def make_tar(src: Path, dst: Path, *, sandbox: SandboxProtocol = nosandbox) -> N
# Make sure tar uses user/group information from the root directory instead of the host.
sandbox=sandbox(
binary="tar",
options=["--ro-bind", src, workdir(src), *finalize_passwd_mounts(src)],
options=["--ro-bind", src, workdir(src), *finalize_passwd_symlinks(workdir(src))],
),
) # fmt: skip

Expand Down Expand Up @@ -98,7 +98,7 @@ def extract_tar(
options=[
"--ro-bind", src, workdir(src),
"--bind", dst, workdir(dst),
*finalize_passwd_mounts(dst),
*finalize_passwd_symlinks(workdir(dst)),
],
),
) # fmt: skip
Expand Down Expand Up @@ -136,6 +136,6 @@ def make_cpio(
stdout=f,
sandbox=sandbox(
binary="cpio",
options=["--ro-bind", src, workdir(src), *finalize_passwd_mounts(src)],
options=["--ro-bind", src, workdir(src), *finalize_passwd_symlinks(workdir(src))],
),
) # fmt: skip
9 changes: 4 additions & 5 deletions mkosi/installer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from mkosi.config import Config, ConfigFeature, OutputFormat
from mkosi.context import Context
from mkosi.mounts import finalize_crypto_mounts
from mkosi.run import apivfs_options, finalize_interpreter, finalize_passwd_mounts, find_binary
from mkosi.run import apivfs_options, finalize_interpreter, finalize_passwd_symlinks, find_binary
from mkosi.tree import rmtree
from mkosi.types import PathString
from mkosi.util import flatten, startswith
Expand Down Expand Up @@ -109,10 +109,9 @@ def options(cls, *, root: PathString, apivfs: bool = True) -> list[PathString]:
"--suppress-chown",
# Make sure /etc/machine-id is not overwritten by any package manager post install scripts.
"--ro-bind-try", Path(root) / "etc/machine-id", "/buildroot/etc/machine-id",
# If we're already in the sandbox, we want to pick up use the passwd files from /buildroot since
# the original root won't be available anymore. If we're not in the sandbox yet, we want to pick
# up the passwd files from the original root.
*finalize_passwd_mounts(root),
# Some package managers (e.g. dpkg) read from the host's /etc/passwd instead of the buildroot's
# /etc/passwd so we symlink /etc/passwd from the buildroot to make sure it gets used.
*(finalize_passwd_symlinks("/buildroot") if apivfs else []),
] # fmt: skip

@classmethod
Expand Down
4 changes: 3 additions & 1 deletion mkosi/resources/man/mkosi-sandbox.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ host system.
: Like `--bind-try`, but does a recursive readonly bind mount.

`--symlink SRC DST`
: Creates a symlink at `DST` in the sandbox pointing to `SRC`.
: Creates a symlink at `DST` in the sandbox pointing to `SRC`. If `DST` already
exists and is a file or symlink, a temporary symlink is created and mounted on
top of `DST`.

`--write DATA DST`
: Writes the string from `DATA` to `DST` in the sandbox.
Expand Down
5 changes: 2 additions & 3 deletions mkosi/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,15 +415,14 @@ def workdir(path: Path, sandbox: Optional[SandboxProtocol] = None) -> str:
return joinpath(subdir, str(path))


def finalize_passwd_mounts(root: PathString) -> list[PathString]:
def finalize_passwd_symlinks(root: PathString) -> list[PathString]:
"""
If passwd or a related file exists in the apivfs directory, bind mount it over the host files while we
run the command, to make sure that the command we run uses user/group information from the apivfs
directory instead of from the host.
"""
return flatten(
("--ro-bind-try", Path(root) / "etc" / f, f"/etc/{f}")
for f in ("passwd", "group", "shadow", "gshadow")
("--symlink", Path(root) / "etc" / f, f"/etc/{f}") for f in ("passwd", "group", "shadow", "gshadow")
)


Expand Down
13 changes: 10 additions & 3 deletions mkosi/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,12 +560,19 @@ def __init__(self, src: str, dst: str) -> None:
def execute(self, oldroot: str, newroot: str) -> None:
dst = joinpath(newroot, self.dst)
try:
os.symlink(self.src, dst)
return os.symlink(self.src, dst)
except FileExistsError:
if os.readlink(dst) == self.src:
if os.path.islink(dst) and os.readlink(dst) == self.src:
return

raise
if os.path.isdir(dst):
raise

# If the target already exists and is not a directory, create the symlink somewhere else and mount
# it over the existing file or symlink.
os.symlink(self.src, "/symlink")
mount_rbind("/symlink", dst)
os.unlink("/symlink")


class WriteOperation(FSOperation):
Expand Down
Loading