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

Merged
merged 10 commits into from
Nov 17, 2024
18 changes: 13 additions & 5 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 @@ -2631,7 +2631,15 @@ def check_tools(config: Config, verb: Verb) -> None:
reason="sign verity roothash signature with OpenSSL engine",
)

if want_efi(config) and config.secure_boot and config.secure_boot_auto_enroll:
if (
want_efi(config)
and config.secure_boot
and config.secure_boot_auto_enroll
and (
not config.find_binary("bootctl")
or systemd_tool_version("bootctl", sandbox=config.sandbox) < "257~devel"
)
):
check_tool(config, "sbsiglist", reason="set up systemd-boot secure boot auto-enrollment")
check_tool(config, "sbvarsign", reason="set up systemd-boot secure boot auto-enrollment")

Expand Down Expand Up @@ -2822,9 +2830,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 Expand Up @@ -4162,6 +4169,7 @@ def finalize_default_tools(args: Args, config: Config, *, resources: Path) -> Co
*(["--package-cache-dir", str(config.package_cache_dir)] if config.package_cache_dir else []),
"--incremental", str(config.incremental),
*([f"--package={package}" for package in config.tools_tree_packages]),
*([f"--package-directory={directory}" for directory in config.tools_tree_package_directories]),
"--output=tools",
*(["--source-date-epoch", str(config.source_date_epoch)] if config.source_date_epoch is not None else []), # noqa: E501
*([f"--environment={k}='{v}'" for k, v in config.environment.items()]),
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
12 changes: 11 additions & 1 deletion mkosi/bootloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,11 @@ def run_systemd_sign_tool(
sandbox=config.sandbox(
binary=cmd[0],
options=opt,
devices=devices or key_source.type != KeySourceType.file,
devices=(
devices
or key_source.type != KeySourceType.file
or certificate_source.type != CertificateSourceType.file
),
),
)

Expand Down Expand Up @@ -591,6 +595,9 @@ def sign_efi_binary(context: Context, input: Path, output: Path) -> Path:
or context.config.secure_boot_sign_tool == SecureBootSignTool.auto
and context.config.find_binary("sbsign") is not None
):
if context.config.secure_boot_certificate_source.type != CertificateSourceType.file:
die("Secure boot certificate source must be 'file' when using sbsign as the signing tool")

cmd = [
"sbsign",
"--cert", workdir(context.config.secure_boot_certificate),
Expand Down Expand Up @@ -629,6 +636,9 @@ def sign_efi_binary(context: Context, input: Path, output: Path) -> Path:
or context.config.secure_boot_sign_tool == SecureBootSignTool.auto
and context.config.find_binary("pesign") is not None
):
if context.config.secure_boot_certificate_source.type != CertificateSourceType.file:
die("Secure boot certificate source must be 'file' when using pesign as the signing tool")

pesign_prepare(context)
run(
[
Expand Down
10 changes: 10 additions & 0 deletions mkosi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1818,6 +1818,7 @@ class Config:
tools_tree_repositories: list[str]
tools_tree_sandbox_trees: list[ConfigTree]
tools_tree_packages: list[str]
tools_tree_package_directories: list[Path]
tools_tree_certificates: bool
incremental: Incremental
cacheonly: Cacheonly
Expand Down Expand Up @@ -3166,6 +3167,14 @@ def parse_ini(path: Path, only_sections: Collection[str] = ()) -> Iterator[tuple
parse=config_make_list_parser(delimiter=","),
help="Add additional packages to the default tools tree",
),
ConfigSetting(
dest="tools_tree_package_directories",
long="--tools-tree-package-directory",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on the direction of #3201, this should maybe be --tools-tree-package-dir.

metavar="PATH",
section="Build",
parse=config_make_list_parser(delimiter=",", parse=make_path_parser()),
help="Specify a directory containing extra tools tree packages",
),
ConfigSetting(
dest="tools_tree_certificates",
metavar="BOOL",
Expand Down Expand Up @@ -4813,6 +4822,7 @@ def summary(config: Config) -> str:
Tools Tree Repositories: {line_join_list(config.tools_tree_repositories)}
Tools Tree Sandbox Trees: {line_join_list(config.tools_tree_sandbox_trees)}
Tools Tree Packages: {line_join_list(config.tools_tree_packages)}
Tools Tree Package Directories: {line_join_list(config.tools_tree_package_directories)}
Tools Tree Certificates: {yes_no(config.tools_tree_certificates)}

Incremental: {config.incremental}
Expand Down
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
9 changes: 7 additions & 2 deletions mkosi/qemu.py
Original file line number Diff line number Diff line change
Expand Up @@ -1120,8 +1120,13 @@ def run_qemu(args: Args, config: Config) -> None:
if config.architecture.is_arm_variant():
cmdline += ["-device", "virtio-gpu-pci"]
else:
cmdline += ["-vga", "virtio"]
cmdline += ["-audio", "driver=pipewire,model=virtio"]
cmdline += ["-device", "virtio-vga"]

cmdline += [
"-nodefaults",
"-display", "sdl,gl=on",
"-audio", "driver=pipewire,model=virtio",
] # fmt: skip
else:
# -nodefaults removes the default CDROM device which avoids an error message during boot
# -serial mon:stdio adds back the serial device removed by -nodefaults.
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: 5 additions & 0 deletions mkosi/resources/man/mkosi.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -1282,8 +1282,10 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
| `less` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `mtools` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `nano` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `opensc` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `openssh` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `openssl` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `pkcs11-provider` | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ |
| `sed` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `pacman` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |
| `pesign` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
Expand Down Expand Up @@ -1331,6 +1333,9 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
separated list of package specifications. This option may be used
multiple times in which case the specified package lists are combined.

`ToolsTreePackageDirectories=`, `--tools-tree-package-directory=`
: Same as `PackageDirectories=`, but for the default tools tree.

`ToolsTreeCertificates=`, `--tools-tree-certificates=`
: Specify whether to use certificates and keys from the tools tree.
Enabled by default. If enabled, `/etc/pki`, `/etc/ssl`,
Expand Down
1 change: 1 addition & 0 deletions mkosi/resources/mkosi-tools/mkosi.conf
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Packages=
less
mtools
nano
opensc
openssl
sed
socat
Expand Down
10 changes: 10 additions & 0 deletions mkosi/resources/mkosi-tools/mkosi.conf.d/10-arch.conf
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,18 @@ Packages=
openssh
pacman
pesign
pipewire
pipewire-audio
pkcs11-provider
python-cryptography
qemu-audio-pipewire
qemu-base
qemu-hw-display-virtio-gpu
qemu-hw-display-virtio-gpu-gl
qemu-hw-display-virtio-vga
qemu-hw-display-virtio-vga-gl
qemu-ui-opengl
qemu-ui-sdl
reprepro
sbsigntools
shadow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Packages=
ovmf
pacman-package-manager
pesign
pkcs11-provider
policycoreutils
python3-cryptography
python3-pefile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Packages=
erofs-utils
fish
pacman
pkcs11-provider
qemu-system-aarch64-core
qemu-system-ppc-core
qemu-system-s390x-core
Expand Down
1 change: 1 addition & 0 deletions mkosi/resources/mkosi-tools/mkosi.conf.d/10-opensuse.conf
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Packages=
ovmf
patterns-base-minimal_base
pesign
pkcs11-provider
policycoreutils
python3-cryptography
python3-pefile
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
4 changes: 4 additions & 0 deletions tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,9 @@ def test_config() -> None:
"ToolsTreeCertificates": true,
"ToolsTreeDistribution": "fedora",
"ToolsTreeMirror": null,
"ToolsTreePackageDirectories": [
"/abc"
],
"ToolsTreePackages": [],
"ToolsTreeRelease": null,
"ToolsTreeRepositories": [
Expand Down Expand Up @@ -570,6 +573,7 @@ def test_config() -> None:
tools_tree_mirror=None,
tools_tree_sandbox_trees=[ConfigTree(Path("/a/b/c"), Path("/"))],
tools_tree_packages=[],
tools_tree_package_directories=[Path("/abc")],
tools_tree_release=None,
tools_tree_repositories=["abc"],
unified_kernel_image_format="myuki",
Expand Down
Loading