diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f71fe4f66..716ff7d111 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,6 +147,10 @@ jobs: [Output] ManifestFormat=json + # TODO: Drop once distribution-gpg-keys is in noble-backports. + [Distribution] + RepositoryKeyFetch=yes + [Content] KernelCommandLine=systemd.default_device_timeout_sec=180 Environment=SYSTEMD_REPART_MKFS_OPTIONS_EROFS="--quiet" diff --git a/mkosi/config.py b/mkosi/config.py index 2e17be0b4f..96285ee733 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -1402,6 +1402,7 @@ class Config: mirror: Optional[str] local_mirror: Optional[str] repository_key_check: bool + repository_key_fetch: bool repositories: list[str] cacheonly: Cacheonly package_manager_trees: list[ConfigTree] @@ -1961,6 +1962,16 @@ def parse_ini(path: Path, only_sections: Collection[str] = ()) -> Iterator[tuple help="Controls signature and key checks on repositories", universal=True, ), + ConfigSetting( + dest="repository_key_fetch", + metavar="BOOL", + nargs="?", + section="Distribution", + default=False, + parse=config_parse_boolean, + help="Controls whether distribution GPG keys can be fetched remotely", + universal=True, + ), ConfigSetting( dest="repositories", metavar="REPOS", @@ -4066,6 +4077,7 @@ def bold(s: Any) -> str: Mirror: {none_to_default(config.mirror)} Local Mirror (build): {none_to_none(config.local_mirror)} Repo Signature/Key check: {yes_no(config.repository_key_check)} + Fetch Repository Keys: {yes_no(config.repository_key_fetch)} Repositories: {line_join_list(config.repositories)} Use Only Package Cache: {config.cacheonly} Package Manager Trees: {line_join_list(config.package_manager_trees)} diff --git a/mkosi/distributions/alma.py b/mkosi/distributions/alma.py index ac6285cacc..16345ebb7f 100644 --- a/mkosi/distributions/alma.py +++ b/mkosi/distributions/alma.py @@ -16,7 +16,8 @@ def gpgurls(context: Context) -> tuple[str, ...]: find_rpm_gpgkey( context, f"RPM-GPG-KEY-AlmaLinux-{context.config.release}", - ) or f"https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-{context.config.release}", + f"https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-{context.config.release}", + ), ) @classmethod diff --git a/mkosi/distributions/centos.py b/mkosi/distributions/centos.py index e6cd985de6..e324620bc7 100644 --- a/mkosi/distributions/centos.py +++ b/mkosi/distributions/centos.py @@ -100,7 +100,7 @@ def architecture(cls, arch: Architecture) -> str: def gpgurls(context: Context) -> tuple[str, ...]: rel = "RPM-GPG-KEY-CentOS-Official" if context.config.release == "9" else "RPM-GPG-KEY-CentOS-Official-SHA256" return tuple( - find_rpm_gpgkey(context, key) or f"https://www.centos.org/keys/{key}" + find_rpm_gpgkey(context, key, f"https://www.centos.org/keys/{key}") for key in (rel, "RPM-GPG-KEY-CentOS-SIG-Extras") ) @@ -196,7 +196,8 @@ def epel_repositories(cls, context: Context) -> Iterable[RpmRepository]: find_rpm_gpgkey( context, f"RPM-GPG-KEY-EPEL-{context.config.release}", - ) or f"https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-{context.config.release}", + f"https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-{context.config.release}", + ), ) if context.config.local_mirror: @@ -299,7 +300,7 @@ def sig_repositories(cls, context: Context) -> Iterable[RpmRepository]: ) for sig, components, keys in sigs: - gpgurls = tuple(find_rpm_gpgkey(context, key) or f"https://www.centos.org/keys/{key}" for key in keys) + gpgurls = tuple(find_rpm_gpgkey(context, key, f"https://www.centos.org/keys/{key}") for key in keys) for c in components: if mirror := context.config.mirror: diff --git a/mkosi/distributions/fedora.py b/mkosi/distributions/fedora.py index 551d319051..97008311a3 100644 --- a/mkosi/distributions/fedora.py +++ b/mkosi/distributions/fedora.py @@ -20,8 +20,8 @@ @tuplify def find_fedora_rpm_gpgkeys(context: Context) -> Iterable[str]: - key1 = find_rpm_gpgkey(context, key=f"RPM-GPG-KEY-fedora-{context.config.release}-primary") - key2 = find_rpm_gpgkey(context, key=f"RPM-GPG-KEY-fedora-{context.config.release}-secondary") + key1 = find_rpm_gpgkey(context, key=f"RPM-GPG-KEY-fedora-{context.config.release}-primary", required=False) + key2 = find_rpm_gpgkey(context, key=f"RPM-GPG-KEY-fedora-{context.config.release}-secondary", required=False) if key1: # During branching, there is always a kerfuffle with the key transition. @@ -38,9 +38,15 @@ def find_fedora_rpm_gpgkeys(context: Context) -> Iterable[str]: yield key3 yield key1 + if key2: yield key2 + if not key1 and not key2: + if not context.config.repository_key_fetch: + die("Fedora GPG keys not found in /usr/share/distribution-gpg-keys", + hint="Make sure the distribution-gpg-keys package is installed") + yield "https://fedoraproject.org/fedora.gpg" diff --git a/mkosi/distributions/mageia.py b/mkosi/distributions/mageia.py index c0e3eb2b7d..4b817fb519 100644 --- a/mkosi/distributions/mageia.py +++ b/mkosi/distributions/mageia.py @@ -34,7 +34,8 @@ def repositories(cls, context: Context) -> Iterable[RpmRepository]: find_rpm_gpgkey( context, "RPM-GPG-KEY-Mageia", - ) or "https://mirrors.kernel.org/mageia/distrib/$releasever/$basearch/media/core/release/media_info/pubkey", + "https://mirrors.kernel.org/mageia/distrib/$releasever/$basearch/media/core/release/media_info/pubkey", + ), ) if context.config.local_mirror: diff --git a/mkosi/distributions/openmandriva.py b/mkosi/distributions/openmandriva.py index 15a76c8c41..5238864a3a 100644 --- a/mkosi/distributions/openmandriva.py +++ b/mkosi/distributions/openmandriva.py @@ -36,7 +36,8 @@ def repositories(cls, context: Context) -> Iterable[RpmRepository]: find_rpm_gpgkey( context, "RPM-GPG-KEY-OpenMandriva", - ) or "https://raw.githubusercontent.com/OpenMandrivaAssociation/openmandriva-repos/master/RPM-GPG-KEY-OpenMandriva", + "https://raw.githubusercontent.com/OpenMandrivaAssociation/openmandriva-repos/master/RPM-GPG-KEY-OpenMandriva", + ), ) if context.config.local_mirror: diff --git a/mkosi/distributions/opensuse.py b/mkosi/distributions/opensuse.py index 382f54eb3b..f089a97ebe 100644 --- a/mkosi/distributions/opensuse.py +++ b/mkosi/distributions/opensuse.py @@ -94,11 +94,28 @@ def repositories(cls, context: Context) -> Iterable[RpmRepository]: mirror = context.config.mirror or "https://download.opensuse.org" if context.config.release == "tumbleweed" or context.config.release.isdigit(): - gpgkeys = ( - *([p] if (p := find_rpm_gpgkey(context, key="RPM-GPG-KEY-openSUSE-Tumbleweed")) else []), - *([p] if (p := find_rpm_gpgkey(context, key="RPM-GPG-KEY-openSUSE")) else []), + gpgkeys = tuple( + p + for key in ("RPM-GPG-KEY-openSUSE-Tumbleweed", "RPM-GPG-KEY-openSUSE") + if (p := find_rpm_gpgkey(context, key, required=False)) ) + if not gpgkeys and not context.config.repository_key_fetch: + die("OpenSUSE GPG keys not found in /usr/share/distribution-gpg-keys", + hint="Make sure the distribution-gpg-keys package is installed") + + if zypper and gpgkeys: + run( + ["rpm", "--root=/buildroot", "--import", *(key.removeprefix("file://") for key in gpgkeys)], + sandbox=context.sandbox( + binary="rpm", + mounts=[ + Mount(context.root, "/buildroot"), + *finalize_crypto_mounts(context.config) + ], + ) + ) + if context.config.release == "tumbleweed": if context.config.architecture == Architecture.x86_64: subdir = "" diff --git a/mkosi/distributions/rhel.py b/mkosi/distributions/rhel.py index a708751992..ccc1e4a27b 100644 --- a/mkosi/distributions/rhel.py +++ b/mkosi/distributions/rhel.py @@ -24,7 +24,8 @@ def gpgurls(context: Context) -> tuple[str, ...]: find_rpm_gpgkey( context, f"RPM-GPG-KEY-redhat{major}-release", - ) or "https://access.redhat.com/security/data/fd431d51.txt", + "https://access.redhat.com/security/data/fd431d51.txt", + ), ) @staticmethod diff --git a/mkosi/distributions/rhel_ubi.py b/mkosi/distributions/rhel_ubi.py index 78bfc233b8..c921ecd1fa 100644 --- a/mkosi/distributions/rhel_ubi.py +++ b/mkosi/distributions/rhel_ubi.py @@ -21,7 +21,8 @@ def gpgurls(context: Context) -> tuple[str, ...]: find_rpm_gpgkey( context, f"RPM-GPG-KEY-redhat{major}-release", - ) or "https://access.redhat.com/security/data/fd431d51.txt", + "https://access.redhat.com/security/data/fd431d51.txt", + ), ) @classmethod diff --git a/mkosi/distributions/rocky.py b/mkosi/distributions/rocky.py index af2c4d1862..0a2cdfd080 100644 --- a/mkosi/distributions/rocky.py +++ b/mkosi/distributions/rocky.py @@ -16,7 +16,8 @@ def gpgurls(context: Context) -> tuple[str, ...]: find_rpm_gpgkey( context, f"RPM-GPG-KEY-Rocky-{context.config.release}", - ) or f"https://download.rockylinux.org/pub/rocky/RPM-GPG-KEY-Rocky-{context.config.release}", + f"https://download.rockylinux.org/pub/rocky/RPM-GPG-KEY-Rocky-{context.config.release}", + ), ) @classmethod diff --git a/mkosi/installer/rpm.py b/mkosi/installer/rpm.py index 94d07697a0..8b82af4ee5 100644 --- a/mkosi/installer/rpm.py +++ b/mkosi/installer/rpm.py @@ -4,9 +4,10 @@ import subprocess import textwrap from pathlib import Path -from typing import Optional +from typing import Literal, Optional, overload from mkosi.context import Context +from mkosi.log import die from mkosi.run import run from mkosi.types import PathString @@ -22,8 +23,31 @@ class RpmRepository: sslclientcert: Optional[Path] = None priority: Optional[int] = None - -def find_rpm_gpgkey(context: Context, key: str) -> Optional[str]: +@overload +def find_rpm_gpgkey( + context: Context, + key: str, + fallback: Optional[str] = None, + *, + required: Literal[True] = True, +) -> str: ... + +@overload +def find_rpm_gpgkey( + context: Context, + key: str, + fallback: Optional[str] = None, + *, + required: Literal[False] +) -> Optional[str]: ... + +def find_rpm_gpgkey( + context: Context, + key: str, + fallback: Optional[str] = None, + *, + required: bool = True +) -> Optional[str]: root = context.config.tools() if context.config.tools_tree_certificates else Path("/") if gpgpath := next((root / "usr/share/distribution-gpg-keys").rglob(key), None): @@ -32,6 +56,13 @@ def find_rpm_gpgkey(context: Context, key: str) -> Optional[str]: if gpgpath := next(Path(context.pkgmngr / "etc/pki/rpm-gpg").rglob(key), None): return (Path("/") / gpgpath.relative_to(context.pkgmngr)).as_uri() + if context.config.repository_key_fetch: + return fallback + + if required: + die(f"{key} GPG key not found in /usr/share/distribution-gpg-keys", + hint="Make sure the distribution-gpg-keys package is installed") + return None diff --git a/mkosi/installer/zypper.py b/mkosi/installer/zypper.py index d58c6a6b88..90ef6ad349 100644 --- a/mkosi/installer/zypper.py +++ b/mkosi/installer/zypper.py @@ -109,9 +109,10 @@ def cmd(cls, context: Context) -> list[PathString]: "zypper", "--installroot=/buildroot", "--cache-dir=/var/cache/zypp", - "--gpg-auto-import-keys" if context.config.repository_key_check else "--no-gpg-checks", "--non-interactive", "--no-refresh", + *(["--gpg-auto-import-keys"] if context.config.repository_key_fetch else []), + *(["--no-gpg-checks"] if not context.config.repository_key_check else []), *([f"--plus-content={repo}" for repo in context.config.repositories]), ] diff --git a/mkosi/resources/mkosi.md b/mkosi/resources/mkosi.md index 41ee2b61d6..02fdd1fa6c 100644 --- a/mkosi/resources/mkosi.md +++ b/mkosi/resources/mkosi.md @@ -452,6 +452,19 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`, Useful to disable checks when combined with `--local-mirror=` and using only a repository from a local filesystem. +`RepositoryKeyFetch=`, `--repository-key-fetch=` +: Controls whether mkosi will fetch distribution GPG keys remotely. Disabled + by default. When disabled, the distribution GPG keys for the target distribution + have to be installed locally on the host system alongside the package manager for + that distribution. + + This setting is only implemented for distributions using dnf or zypper as their + package manager. For other distributions the distribution GPG keys are always looked + up locally regardless of the value of this setting. To make the distribution GPG keys + for distributions available without enabling this setting, the corresponding package + has to be installed on the host. This is usually one of `archlinux-keyring`, + `debian-keyring`, `ubuntu-keyring` or `distribution-gpg-keys` (for rpm-based distributions). + `Repositories=`, `--repositories=` : Enable package repositories that are disabled by default. This can be used to enable the EPEL repos for CentOS or different components of the Debian/Ubuntu repositories. diff --git a/tests/test_json.py b/tests/test_json.py index 43a51f1ab1..783790412c 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -264,6 +264,7 @@ def test_config() -> None: "RepartOffline": true, "Repositories": [], "RepositoryKeyCheck": false, + "RepositoryKeyFetch": true, "RootPassword": [ "test1234", false @@ -465,6 +466,7 @@ def test_config() -> None: repart_offline=True, repositories=[], repository_key_check=False, + repository_key_fetch=True, root_password=("test1234", False), root_shell="/bin/tcsh", runtime_build_sources=True,