Skip to content

Commit

Permalink
Introduce RepositoryKeyFetch=
Browse files Browse the repository at this point in the history
This setting controls whether we'll fetch GPG keys remotely or not.
We disable it by default so that we only rely on locally available GPG
keys for checking package and repository metadata signatures.

This new setting only affects dnf/zypper based distributions as apt
and pacman do not support retrieving GPG keys remotely in the first
place.

zypper does not trust GPG keys listed in gpgkey= by default so we import
local GPG keys manually with rpm to work around that.

Fixes #757
  • Loading branch information
DaanDeMeyer committed Aug 2, 2024
1 parent a94f3d0 commit 35889a9
Show file tree
Hide file tree
Showing 17 changed files with 120 additions and 17 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ jobs:
QemuKvm=yes
EOF
# TODO: Drop once distribution-gpg-keys is in noble-backports.
if [[ "${{ matrix.tools }}" =~ opensuse|fedora|ubuntu ]]; then
tee --append mkosi.local.conf <<EOF
[Distribution]
RepositoryKeyFetch=yes
EOF
fi
# TODO: Remove once all distros have recent enough systemd that knows systemd.default_device_timeout_sec.
mkdir -p mkosi-initrd/mkosi.extra/usr/lib/systemd/system.conf.d
tee mkosi-initrd/mkosi.extra/usr/lib/systemd/system.conf.d/device-timeout.conf <<EOF
Expand Down
2 changes: 2 additions & 0 deletions mkosi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1807,6 +1807,7 @@ def finalize_default_initrd(
"--architecture", str(config.architecture),
*(["--mirror", config.mirror] if config.mirror else []),
"--repository-key-check", str(config.repository_key_check),
"--repository-key-fetch", str(config.repository_key_fetch),
"--repositories", ",".join(config.repositories),
"--package-manager-tree", ",".join(str(t) for t in config.package_manager_trees),
# Note that when compress_output == Compression.none == 0 we don't pass --compress-output which means the
Expand Down Expand Up @@ -4477,6 +4478,7 @@ def finalize_default_tools(args: Args, config: Config, *, resources: Path) -> Co
"--repositories", ",".join(config.tools_tree_repositories),
"--package-manager-tree", ",".join(str(t) for t in config.tools_tree_package_manager_trees),
"--repository-key-check", str(config.repository_key_check),
"--repository-key-fetch", str(config.repository_key_fetch),
"--cache-only", str(config.cacheonly),
*(["--output-dir", str(config.output_dir)] if config.output_dir else []),
*(["--workspace-dir", str(config.workspace_dir)] if config.workspace_dir else []),
Expand Down
12 changes: 12 additions & 0 deletions mkosi/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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)}
Expand Down
3 changes: 2 additions & 1 deletion mkosi/distributions/alma.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions mkosi/distributions/centos.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
)

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
10 changes: 8 additions & 2 deletions mkosi/distributions/fedora.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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"


Expand Down
3 changes: 2 additions & 1 deletion mkosi/distributions/mageia.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion mkosi/distributions/openmandriva.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
23 changes: 20 additions & 3 deletions mkosi/distributions/opensuse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand Down
3 changes: 2 additions & 1 deletion mkosi/distributions/rhel.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion mkosi/distributions/rhel_ubi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion mkosi/distributions/rocky.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 36 additions & 2 deletions mkosi/installer/rpm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -23,7 +24,33 @@ class RpmRepository:
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: ...

Check notice

Code scanning / CodeQL

Statement has no effect Note

This statement has no effect.


@overload
def find_rpm_gpgkey(
context: Context,
key: str,
fallback: Optional[str] = None,
*,
required: Literal[False]
) -> Optional[str]: ...

Check notice

Code scanning / CodeQL

Statement has no effect Note

This statement has no effect.


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):
Expand All @@ -32,6 +59,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


Expand Down
3 changes: 2 additions & 1 deletion mkosi/installer/zypper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]),
]

Expand Down
13 changes: 13 additions & 0 deletions mkosi/resources/mkosi.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions tests/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ def test_config() -> None:
"RepartOffline": true,
"Repositories": [],
"RepositoryKeyCheck": false,
"RepositoryKeyFetch": true,
"RootPassword": [
"test1234",
false
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions tests/test_sysext.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def test_sysext(config: ImageConfig) -> None:
options=[
"--directory", "",
"--incremental=no",
"--repository-key-fetch=yes",
"--base-tree", Path(image.output_dir) / "image",
"--overlay",
"--package=dnsmasq",
Expand Down

0 comments on commit 35889a9

Please sign in to comment.