diff --git a/.gitignore b/.gitignore index d448b96..8ce5aac 100644 --- a/.gitignore +++ b/.gitignore @@ -122,3 +122,5 @@ ENV/ # Generated docs/source/history.rst docs/source/schema.rst + +tests/gpg-home/.#* diff --git a/docs/source/arch.rst b/docs/source/arch.rst index 552172d..9ec155d 100644 --- a/docs/source/arch.rst +++ b/docs/source/arch.rst @@ -33,3 +33,39 @@ package is no longer findable. Once this happens, your existing built packages are effectively useless and you need to rebuild and release then encourage your users to run ``pacman -Syu`` (upgrade all packages) before installing/upgrading your package in case they still have the previous version of Python installed. + + +Package Signing +............... + +Arch packages are optionally signed using a GnuPG_ detached signature. See +:ref:`gpg_signing` for the signing itself. + +**To consume** your signed package, downstream users will need to install your +public key into their ``pacman`` key stores. You can get your key to them in two +ways: + +1. The recommended way is to upload it to Arch's preferred keyserver:: + + gpg --armor --export 3CB69E1833270B714034B7558CA85BF8D96DB4E9 + # Copy/paste the output to http://keyserver.ubuntu.com/#submitKey + + Note that there will be around a one hour propagation delay before the next + steps can work. Installers of your package should be instructed to import your + key from the keyserver as follows:: + + sudo pacman-key --init + sudo pacman-key --recv-keys 3CB69E1833270B714034B7558CA85BF8D96DB4E9 + sudo pacman-key --lsign-key 3CB69E1833270B714034B7558CA85BF8D96DB4E9 + +2. Alternatively, you can boycott the keyserver and put the public key somewhere + on your website. Run:: + + gpg --armor --export 3CB69E1833270B714034B7558CA85BF8D96DB4E9 > 3CB69E1833270B714034B7558CA85BF8D96DB4E9.asc + + Then put the ``.asc`` file somewhere downloadable on your website, with + instructions to your users to run:: + + sudo pacman-key --init + curl https://your.website/downloads/3CB69E1833270B714034B7558CA85BF8D96DB4E9.asc | sudo pacman-key --add - + sudo pacman-key --lsign-key 3CB69E1833270B714034B7558CA85BF8D96DB4E9 diff --git a/docs/source/fedora.rst b/docs/source/fedora.rst index de09821..4859391 100644 --- a/docs/source/fedora.rst +++ b/docs/source/fedora.rst @@ -76,3 +76,34 @@ megabytes, subsequent updates require only a few megabytes. The complexity of * ``zchunk`` performs so badly under ``qemu`` architecture emulation that even a minimal package takes around 20 minutes to build. Building for non-native architectures is not recommended nor tested. + + +.. _fedora_signing: + +Package Signing +............... + +Fedora packages are optionally signed using an embedded GnuPG_ signature. See +:ref:`gpg_signing` for the signing itself. + +**To consume** your signed package, downstream users should (although ``rpm`` +strangely does nothing to prevent you from installing packages with untrusted +signatures) install your public key into their ``rpm`` key stores. + +Export your public key:: + + gpg --armor --export 3CB69E1833270B714034B7558CA85BF8D96DB4E9 > 3CB69E1833270B714034B7558CA85BF8D96DB4E9.asc + +Then put the ``.asc`` file somewhere downloadable on your website. Users can +then import the key using:: + + curl -O https://your.website/downloads/3CB69E1833270B714034B7558CA85BF8D96DB4E9.asc + sudo rpm --import 3CB69E1833270B714034B7558CA85BF8D96DB4E9.asc + +The should now be able to verify your package using:: + + rpm -K your-package-0.1.0-1.fc39.noarch.rpm + +Note that DNF will not block installation if the key is not imported. The only +indicator that something is wrong would be the message ``digests SIGNATURES NOT +OK`` from ``rpm -K``. diff --git a/docs/source/gpg.rst b/docs/source/gpg.rst new file mode 100644 index 0000000..3e8477c --- /dev/null +++ b/docs/source/gpg.rst @@ -0,0 +1,38 @@ +.. _gpg_signing: + +============= +GnuPG Signing +============= + +Some distributions, namely Arch, Fedora, Manjaro and OpenSUSE, optionally use +GnuPG_ to sign their packages. Other distributions either use their own wrappers +around OpenSSL, for which the signing process is documented under :ref:`each +distribution's quirks page `, or don't meaningfully support +signing. + +.. note:: + + Before embarking on signing, bear in mind that, without a web of trust based + or in-person public key verification, a signature is more or less a + meaningless exercise, providing less security than HTTPS. + +To sign your packages: + +* Generate an RSA signing key for yourself or your organisation using ``gpg + --generate-key``. + +* Run ``gpg --list-secret-keys`` to find the key key ID (a 40 character + hexadecimal string) of the key you just generated. + +* Pass that key ID to the ``--gpg-signing-id`` flag when building (replace + ``arch`` with whatever distribution you're building for):: + + polycotylus arch --gpg-signing-id 3CB69E1833270B714034B7558CA85BF8D96DB4E9 + +If your GnuPG key has a password, you will be prompted to enter it during the +build. There is currently no automation friendly way to pass the password through +`polycotylus` to GnuPG_. + +**To consume** your signed package, downstream users will need to install your +public key into their package manager's key stores. The process is different on +each distribution – see :ref:`each distribution's quirks page `. diff --git a/docs/source/index.rst b/docs/source/index.rst index aa32f87..5cf2033 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -12,6 +12,7 @@ requirements example-library example-gui/index + gpg .. _`building for`: diff --git a/docs/source/opensuse.rst b/docs/source/opensuse.rst index ec8eec3..a8c813c 100644 --- a/docs/source/opensuse.rst +++ b/docs/source/opensuse.rst @@ -40,3 +40,10 @@ Caveats ....... * Building for OpenSUSE is not supported with Podman_. + + +Package Signing +............... + +OpenSUSE's signing process is the same as Fedora's. See :ref:`Fedora package +signing `. diff --git a/docs/source/rst_prolog.txt b/docs/source/rst_prolog.txt index 3695704..e06749f 100644 --- a/docs/source/rst_prolog.txt +++ b/docs/source/rst_prolog.txt @@ -21,5 +21,6 @@ .. _Poetry: https://python-poetry.org/ .. _SPDX: https://spdx.org/licenses/ .. _qemu: https://www.qemu.org/ +.. _GnuPG: https://gnupg.org/ .. highlight:: bash diff --git a/docs/source/void.rst b/docs/source/void.rst index 3b0528b..2461841 100644 --- a/docs/source/void.rst +++ b/docs/source/void.rst @@ -18,3 +18,59 @@ following:: polycotylus void:glibc # The default, equivalent to `polycotylus void` polycotylus void:musl + + +Package Signing +............... + +Void Linux packages use a detached RSA signature. To generate an RSA key run either:: + + openssl genrsa -des3 -out privkey.pem 4096 + +Or if you want builds to be automatable, generate a password-less key using:: + + openssl genrsa -out privkey.pem 4096 + +Then to build a package with signing enabled, pass the path to the private key +to the ``--void-signing-certificate`` option:: + + polycotylus void --void-signing-certificate privkey.pem + +There are several files of interest produced: + +.. code-block:: console + + $ tree .polycotylus/void + .polycotylus/void + ├── 64:0c:81:d1:54:d6:6d:88:b4:49:4a:4e:c6:0a:1c:26.plist (1) + ├── Dockerfile + ├── hostdir + │   └── sources + │   └── dumb_text_viewer-0.1.0 + │   └── 0.1.0.tar.gz + ├── musl + │   ├── dumb_text_viewer-0.1.0_1.x86_64-musl.xbps (2) + │   ├── dumb_text_viewer-0.1.0_1.x86_64-musl.xbps.sig2 (3) + │   └── x86_64-musl-repodata (4) + └── srcpkgs + └── dumb_text_viewer + └── template + +1. Your public key, in the format that XBPS uses +2. The package itself +3. The detached signature for the package +4. The repository index, containing an embedded signature + + +To consume a package +-------------------- + +The preferred way for a user to import your public key is just to install a +signed package, which will display the signing key's fingerprint and ask if the +user wants to import the key. That fingerprint is the basename of the +``.polycotylus/void/{md5sum}.plist`` file – put that fingerprint somewhere +downloaders of your package can find it and know that it belongs to you. + +An automation friendly, albeit rarely used alternative is to copy the public key +into XBPS's key-store. Do this simply by having the user put the +``{md5sum}.plist`` file into their ``/var/db/xbps/keys/`` directory. diff --git a/polycotylus/__main__.py b/polycotylus/__main__.py index 194cf8b..1eb6c28 100644 --- a/polycotylus/__main__.py +++ b/polycotylus/__main__.py @@ -75,6 +75,8 @@ def __call__(self, parser, namespace, key, option_string=None): parser.add_argument("--list-localizations", action=ListLocalizationAction, choices=["language", "region", "modifier"]) parser.add_argument("--architecture") +parser.add_argument("--gpg-signing-id") +parser.add_argument("--void-signing-certificate") parser.add_argument("--post-mortem", action="store_true", help="Enter an in-container interactive shell whenever an " "error occurs in a docker container") @@ -90,7 +92,13 @@ def cli(argv=None): polycotylus._docker.post_mortem = options.post_mortem cls = polycotylus.distributions[options.distribution] - self = cls(polycotylus.Project.from_root("."), options.architecture) + signing_id = None + if issubclass(cls, polycotylus._base.GPGBased): + signing_id = options.gpg_signing_id + elif issubclass(cls, polycotylus.Void): # pragma: no branch + signing_id = options.void_signing_certificate + self = cls(polycotylus.Project.from_root("."), options.architecture, + signing_id) self.generate() artifacts = self.build() self.test(artifacts["main"]) @@ -100,6 +108,11 @@ def cli(argv=None): print(f"Built {len(set(artifacts.values()))} artifact{'s' if len(artifacts) != 1 else ''}:") for (variant, path) in artifacts.items(): print(f"{variant}: {path}") + return artifacts + + +def _console_script(): # pragma: no cover + cli() if __name__ == "__main__": diff --git a/polycotylus/_arch.py b/polycotylus/_arch.py index d7931cb..7b71756 100644 --- a/polycotylus/_arch.py +++ b/polycotylus/_arch.py @@ -6,12 +6,14 @@ from functools import lru_cache import contextlib import shutil +import os +from pathlib import Path from polycotylus import _misc, _docker -from polycotylus._base import BaseDistribution +from polycotylus._base import BaseDistribution, GPGBased -class Arch(BaseDistribution): +class Arch(GPGBased, BaseDistribution): base_image = "archlinux:base" python_prefix = "/usr" python_extras = { @@ -139,9 +141,11 @@ def pkgbuild(self): """) return out + patch_gpg_locale = "" + def dockerfile(self): dependencies = self.dependencies + self.build_dependencies + self.test_dependencies - return self._formatter(f""" + out = self._formatter(f""" FROM {self.base_image} AS base RUN {self.mirror.install_command} @@ -153,10 +157,17 @@ def dockerfile(self): FROM base as build RUN echo 'PACKAGER="{self.project.maintainer_slug}"' >> /etc/makepkg.conf RUN pacman -Syu --noconfirm --needed base-devel {shlex.join(dependencies)} + {self.patch_gpg_locale} FROM base AS test RUN pacman -Syu --noconfirm --needed {shlex.join(self.test_dependencies)} - """) + """) + if self.signing_id: + out += self._formatter(f""" + RUN pacman-key --init + RUN echo '{self.public_key}' | base64 -d | pacman-key --add - && pacman-key --lsign '{self.signing_id}' + """) + return out def generate(self): with contextlib.suppress(FileNotFoundError): @@ -169,10 +180,19 @@ def generate(self): _misc.unix_write(self.distro_root / "PKGBUILD", self.pkgbuild()) def build(self): + if self.signing_id: + signing_flags = ["--sign", "--key", self.signing_id] + gpg_home = os.environ.get("GNUPGHOME", Path.home() / ".gnupg") + gpg_volume = [(str(gpg_home), "/home/user/.gnupg")] + else: + signing_flags = [] + gpg_volume = [] with self.mirror: - _docker.run(self.build_builder_image(), "makepkg -fs --noconfirm", - volumes=[(self.distro_root, "/io")], root=False, - architecture=self.docker_architecture, tty=True, post_mortem=True) + _docker.run(self.build_builder_image(), + ["makepkg", "-fs", "--noconfirm", *signing_flags], + volumes=[(self.distro_root, "/io"), *gpg_volume], + root=False, architecture=self.docker_architecture, + tty=True, post_mortem=True, interactive=True) architecture = self.architecture if self.project.architecture != "none" else "any" package, = self.distro_root.glob( f"{self.package_name}-{self.project.version}-*-{architecture}.pkg.tar.zst") diff --git a/polycotylus/_base.py b/polycotylus/_base.py index 0788e29..d8b19ff 100644 --- a/polycotylus/_base.py +++ b/polycotylus/_base.py @@ -5,6 +5,8 @@ import platform import json from functools import lru_cache +import subprocess +import base64 from packaging.requirements import Requirement @@ -19,8 +21,9 @@ class BaseDistribution(abc.ABC): supported_architectures = abc.abstractproperty() _packages = abc.abstractproperty() tag = abc.abstractproperty() + signature_property = None - def __init__(self, project, architecture=None): + def __init__(self, project, architecture=None, signature=None): self.project = project self.architecture = architecture or self.preferred_architecture if self.architecture not in self.supported_architectures: @@ -45,6 +48,8 @@ def __init__(self, project, architecture=None): native package manager. """)) _docker.setup_binfmt() + if self.signature_property: + setattr(self, self.signature_property, signature) @property def distro_root(self): @@ -333,6 +338,44 @@ def update_artifacts_json(self, packages): _misc.unix_write(json_path, json.dumps(artifacts, indent=" ")) +class GPGBased(abc.ABC): + signature_property = "signing_id" + + @property + def signing_id(self): + return self._signing_id + + @signing_id.setter + def signing_id(self, id): + if id is None: + self._signing_id = None + return + # Normalise key identifier (name/email/abbreviated fingerprint) to full + # length fingerprint (which doubles as a check that the key exists). + p = subprocess.run(["gpg", "--with-colons", "--status-fd=2", "--list-secret-keys", id], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + if p.returncode: + assert "[GNUPG:] ERROR keylist.getkey 17" in p.stderr, p.stderr + "\nI am a bug in polycotylus, please report me!" + raise _exceptions.PolycotylusUsageError( + f'No private GPG key found with user ID or fingerprint "{id}"') + # For GPG's machine readable (--with-colons) mode, see: + # https://github.com/gpg/gnupg/blob/gnupg-2.5-base/doc/DETAILS#format-of-the-colon-listings + lines = p.stdout.splitlines() + keys = sorted(i.split(":")[4] for i in lines if i.startswith("sec:")) + if len(keys) > 1: + raise _exceptions.PolycotylusUsageError( + f'The GPG signing key identifier "{id}" is ambiguous. It could refer to either of {keys}') + fingerprints = [i.split(":")[9] for i in lines if i.startswith("fpr:")] + self._signing_id, = [i for i in fingerprints if i.endswith(keys[0])] + + @property + def public_key(self): + p = subprocess.run(["gpg", "--armor", "--export", self.signing_id], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + assert p.returncode == 0, p.stderr.decode() + return base64.b64encode(p.stdout).decode("ascii") + + def _deduplicate(array): """Remove duplicates, preserving order of first appearance.""" return list(dict.fromkeys(array)) diff --git a/polycotylus/_completions/polycotylus.fish b/polycotylus/_completions/polycotylus.fish index 4294cac..aee143c 100644 --- a/polycotylus/_completions/polycotylus.fish +++ b/polycotylus/_completions/polycotylus.fish @@ -28,6 +28,11 @@ complete -x -c polycotylus -n 'not __fish_seen_subcommand_from $all_variants && complete -x -c polycotylus -n 'not __fish_seen_subcommand_from $all_variants && string match -rq -- u (commandline -t)' -a "$ubuntu_variants" complete -x -c polycotylus -n 'not __fish_seen_subcommand_from $all_variants && string match -rq -- v (commandline -t)' -a "$void_variants" +# Suggest GPG signing only for distributions that use it or when no distribution is given. +complete -c polycotylus -x -l gpg-signing-id -n "not __fish_seen_subcommand_from $all_variants || __fish_seen_subcommand_from arch fedora $fedora_variants manjaro opensuse" -a '(type -q gpg && __fish_complete_gpg_key_id gpg --list-secret-keys)' +# Likewise with VoidLinux signing certificates. +complete -c polycotylus -r -l void-signing-certificate -n "not __fish_seen_subcommand_from $all_variants || __fish_seen_subcommand_from void $void_variants" + complete -c polycotylus -f -s q -l quiet -d 'Decrease verbosity' complete -c polycotylus -f -l post-mortem -d 'Enter container on error' complete -c polycotylus -f -s h -l help -d 'Show help' diff --git a/polycotylus/_debian.py b/polycotylus/_debian.py index 1157eff..7c66423 100644 --- a/polycotylus/_debian.py +++ b/polycotylus/_debian.py @@ -79,11 +79,11 @@ class Debian(BaseDistribution): tag = "13" mirror = _mirror.mirrors["debian13"] - def __init__(self, package, architecture=None): + def __init__(self, project, architecture=None, signing_id=None): if architecture is None: architecture = machine() architecture = {"x86_64": "amd64", "aarch64": "arm64"}.get(architecture, architecture) - super().__init__(package, architecture) + super().__init__(project, architecture, signing_id) if self.project.architecture == "none": self.architecture = "all" diff --git a/polycotylus/_docker.py b/polycotylus/_docker.py index 2d99436..c284d35 100644 --- a/polycotylus/_docker.py +++ b/polycotylus/_docker.py @@ -64,6 +64,8 @@ def __init__(self, base, command=None, *flags, volumes=(), check=True, interactive=False, tty=False, root=True, post_mortem=False, architecture=machine(), verbosity=None): tty = tty and sys.stdin.isatty() + if interactive: + verbosity = 2 if verbosity is None: verbosity = _verbosity() __tracebackhide__ = True @@ -99,41 +101,34 @@ def __init__(self, base, command=None, *flags, volumes=(), check=True, if p.returncode: # pragma: no cover raise SystemExit(p.stderr.decode()) self.id = p.stdout.decode().splitlines()[-1] - if interactive: - if _run([docker, "start", "-ia", self.id]).returncode: - logs = _run([docker, "logs", self.id], stderr=STDOUT, - stdout=PIPE, text=True).stdout - raise Error(human_friendly, logs) - - else: - p = _run([docker, "container", "start", "-a", self.id], - stdout=None if verbosity >= 2 else DEVNULL, - stderr=STDOUT if verbosity >= 2 else PIPE) - self.returncode = p.returncode - if check and self.returncode: - if post_mortem and globals()["post_mortem"]: - for shell in ["/usr/bin/fish", "/usr/bin/zsh", "/usr/sbin/bash", "/usr/bin/bash"]: # pragma: no branch - with contextlib.suppress(Exception): - self[shell] - break - else: # pragma: no cover - shell = "sh" - - print("Error occurred. Entering post-mortem debug shell.", - "The command polycotylus was trying to run was:", - shlex.join(command) if isinstance(command, list) else command, flush=True) - image = self.commit() - run(image, [shell], *flags, volumes=volumes, tty=True, - interactive=True, root=root, architecture=architecture, - verbosity=0) - _run([docker, "image", "rm", image], stderr=DEVNULL, stdout=DEVNULL) - (images_cache() / image).unlink() - - raise SystemExit(1) - - if not self.output and p.stderr: - raise Error(human_friendly, p.stderr.decode()) - raise Error(human_friendly, self.output) + p = _run([docker, "container", "start", "-ia" if interactive else "-a", self.id], + stdout=None if verbosity >= 2 else DEVNULL, + stderr=STDOUT if verbosity >= 2 else PIPE) + self.returncode = p.returncode + if check and self.returncode: + if post_mortem and globals()["post_mortem"]: + for shell in ["/usr/bin/fish", "/usr/bin/zsh", "/usr/sbin/bash", "/usr/bin/bash"]: # pragma: no branch + with contextlib.suppress(Exception): + self[shell] + break + else: # pragma: no cover + shell = "sh" + + print("Error occurred. Entering post-mortem debug shell.", + "The command polycotylus was trying to run was:", + shlex.join(command) if isinstance(command, list) else command, flush=True) + image = self.commit() + run(image, [shell], *flags, volumes=volumes, tty=True, + interactive=True, root=root, architecture=architecture, + verbosity=0) + _run([docker, "image", "rm", image], stderr=DEVNULL, stdout=DEVNULL) + (images_cache() / image).unlink() + + raise SystemExit(1) + + if not self.output and p.stderr: + raise Error(human_friendly, p.stderr.decode()) + raise Error(human_friendly, self.output) @property def output(self): diff --git a/polycotylus/_fedora.py b/polycotylus/_fedora.py index 939ff8c..62f3d37 100644 --- a/polycotylus/_fedora.py +++ b/polycotylus/_fedora.py @@ -11,15 +11,17 @@ import shlex from functools import lru_cache import platform +import os +from pathlib import Path from packaging.requirements import Requirement from polycotylus import _misc, _docker, _exceptions from polycotylus._mirror import cache_root -from polycotylus._base import BaseDistribution, _deduplicate +from polycotylus._base import BaseDistribution, _deduplicate, GPGBased -class Fedora(BaseDistribution): +class Fedora(GPGBased, BaseDistribution): name = "fedora" version = "39" base_image = "fedora:39" @@ -40,13 +42,13 @@ class Fedora(BaseDistribution): "font": "dejavu-fonts-all", } - def __init__(self, project, architecture=None): + def __init__(self, project, architecture=None, signing_id=None): if platform.system() == "Windows": # pragma: no cover # The mounting of dnf's cache onto the host filesystem requires UNIX # permissions that Windows filesystems lack support for. raise _exceptions.PolycotylusUsageError( "Building for Fedora is not supported on Windows.") - super().__init__(project, architecture) + super().__init__(project, architecture, signing_id) if self.project.architecture == "none": self.architecture = "noarch" @@ -260,7 +262,7 @@ def _mounted_caches(self): def build_builder_image(self): base = super().build_builder_image() - command = ["dnf", "install", "-y", "fedpkg", "python3dist(wheel)", "python3dist(pip)"] + \ + command = ["dnf", "install", "-y", "fedpkg", "python3dist(wheel)", "python3dist(pip)", "rpm-sign", "pinentry-tty"] + \ self.build_dependencies + self.dependencies + self.test_dependencies return _docker.lazy_run(base, command, tty=True, volumes=self._mounted_caches, architecture=self.docker_architecture) @@ -276,14 +278,26 @@ def build_test_image(self): architecture=self.docker_architecture) def build(self): + machine = self.architecture + command = ["fedpkg", "--release", "f" + self.version, "compile", "--", "-bb"] + if self.signing_id: + gpg_home = os.environ.get("GNUPGHOME", Path.home() / ".gnupg") + gpg_volume = [(str(gpg_home), "/home/user/.gnupg")] + command = self._formatter(f""" + {shlex.join(command)} + gpg --export -a '{self.signing_id}' > /tmp/key + sudo rpmkeys --import /tmp/key + echo '%_gpg_name {self.signing_id}' > ~/.rpmmacros + rpm --addsign {self.architecture}/{self.package_name}-*{self.project.version}-*.{machine}.rpm + """) + else: + gpg_volume = [] with self.mirror: - _docker.run(self.build_builder_image(), - ["fedpkg", "--release", "f" + self.version, "compile", "--", "-bb"], - tty=True, root=False, - volumes=[(self.distro_root, "/io")] + self._mounted_caches, + _docker.run(self.build_builder_image(), command, + tty=True, root=False, interactive=bool(self.signing_id), + volumes=[(self.distro_root, "/io")] + gpg_volume + self._mounted_caches, architecture=self.docker_architecture, post_mortem=True) rpms = {} - machine = self.architecture pattern = re.compile( fr"{re.escape(self.package_name)}(?:-([^-]+))?-{self.project.version}.*\.{machine}\.rpm") for path in (self.distro_root / machine).glob(f"*.fc{self.version}.*.rpm"): diff --git a/polycotylus/_manjaro.py b/polycotylus/_manjaro.py index fa47649..e9050bf 100644 --- a/polycotylus/_manjaro.py +++ b/polycotylus/_manjaro.py @@ -7,3 +7,11 @@ class Manjaro(Arch): "aarch64": "aarch64", "x86_64": "x86_64", } + + def _install_user(self): + # Manjaro docker images come with a user called builder preinstalled + # with the same UID that polycotylus normally uses. It moves the home + # directory – get rid of it. + return "RUN userdel builder\n" + super()._install_user() + + patch_gpg_locale = r"""RUN mv /usr/sbin/gpg /usr/sbin/_gpg && echo -e '#!/bin/sh\nLANG=C.UTF8 _gpg "$@"' > /usr/sbin/gpg && chmod +x /usr/sbin/gpg""" diff --git a/polycotylus/_opensuse.py b/polycotylus/_opensuse.py index efcce59..28ece1a 100644 --- a/polycotylus/_opensuse.py +++ b/polycotylus/_opensuse.py @@ -11,14 +11,15 @@ import platform import shlex import itertools +from pathlib import Path from packaging.requirements import Requirement from polycotylus import _docker, _misc, _exceptions -from polycotylus._base import BaseDistribution +from polycotylus._base import BaseDistribution, GPGBased -class OpenSUSE(BaseDistribution): +class OpenSUSE(GPGBased, BaseDistribution): base_image = "docker.io/opensuse/tumbleweed" tag = "tumbleweed" python_extras = { @@ -36,19 +37,19 @@ class OpenSUSE(BaseDistribution): _formatter = _misc.Formatter(" ") _packages = { "python": "python3", - "xvfb-run": "xvfb-run", + "xvfb-run": "xvfb-run awk", "imagemagick": "ImageMagick", "imagemagick_svg": "librsvg", "font": "dejavu-fonts", } - def __init__(self, project, architecture=None): + def __init__(self, project, architecture=None, signing_id=None): if _docker.docker.variant == "podman": # pragma: no cover # I think the incompatibility is in OpenSUSE's use of anonymous # UIDs. Leads to permission errors writing to /dev/null. raise _exceptions.PolycotylusUsageError( "Building for OpenSUSE is not supported with podman.") - super().__init__(project, architecture) + super().__init__(project, architecture, signing_id) if self.project.architecture == "none": self.architecture = "noarch" @@ -315,6 +316,8 @@ def dockerfile(self): """) if test_dependencies: out += f"RUN zypper install -y {shlex.join(test_dependencies)}\n" + if self.signing_id: + out += f"RUN echo '{self.public_key}' | base64 -d > /tmp/key.pub && rpm --import /tmp/key.pub\n" return out @property @@ -348,10 +351,20 @@ def build(self): command = ["build", f"--uid={uid}", "--dist=tumbleweed", "--vm-network"] volumes = [(self.distro_root, "/io"), (self.distro_root / "RPMS", "/var/tmp/build-root/home/abuild/rpmbuild/RPMS")] + if self.signing_id: + gpg_home = os.environ.get("GNUPGHOME", Path.home() / ".gnupg") + volumes.append((str(gpg_home), "/root/.gnupg")) + command = self._formatter(f""" + {shlex.join(command)} + gpg --export -a '{self.signing_id}' > /tmp/key + rpmkeys --import /tmp/key + echo '%_gpg_name {self.signing_id}' > ~/.rpmmacros + LANG=C.UTF8 rpm --addsign RPMS/{self.architecture}/*.rpm + """) with self.mirror: _docker.run(self.build_builder_image(), command, "--privileged", volumes=volumes, post_mortem=True, tty=True, - architecture=self.docker_architecture) + interactive=True, architecture=self.docker_architecture) rpms = {} for python in ["python3"] if self.project.frontend else self.active_python_abis(): arch = self.architecture diff --git a/polycotylus/_void.py b/polycotylus/_void.py index f141061..6dc04b8 100644 --- a/polycotylus/_void.py +++ b/polycotylus/_void.py @@ -8,8 +8,9 @@ import shlex from urllib.request import urlopen import json +from pathlib import Path, PurePosixPath -from polycotylus import _misc, _docker +from polycotylus import _misc, _docker, _exceptions from polycotylus._mirror import cache_root from polycotylus._base import BaseDistribution, _deduplicate @@ -35,6 +36,7 @@ class Void(BaseDistribution): } libc = "glibc" libc_tag = "" + signature_property = "private_key" @_misc.classproperty def base_image(self, cls): @@ -205,22 +207,36 @@ def build(self): (self.distro_root / "srcpkgs" / self.package_name, f"/io/srcpkgs/{self.package_name}"), (self.distro_root / "hostdir/sources", "/io/hostdir/sources"), ] + if self.private_key: + volumes.append((str(self.private_key), f"/key/{self.private_key.name}")) + mirror_url = "http://localhost:8902" if platform.system() == "Linux" else "http://host.docker.internal:8902" + script = self._formatter(f""" + git config --global --add safe.directory /io + git config core.symlinks true + git checkout {self._void_packages_head()} -- . + sed -r -i 's|https://repo-default.voidlinux.org|{mirror_url}|g' etc/xbps.d/repos-* + echo 'XBPS_CHROOT_CMD=ethereal\\nXBPS_ALLOW_CHROOT_BREAKOUT=yes' > etc/conf + ln -s / masterdir + ./xbps-src -1 pkg {self.package_name} + """) + if self.private_key: + script += self._formatter(f""" + xbps-rindex --sign --signedby "{self.project.maintainer}" --privkey /key/{self.private_key.name} $PWD/hostdir/binpkgs + xbps-rindex --sign-pkg --privkey /key/{self.private_key.name} $PWD/hostdir/binpkgs/*.xbps + """) with self.mirror: - container = _docker.run(self.build_builder_image(), f""" - git config --global --add safe.directory /io - git config core.symlinks true - git checkout {self._void_packages_head()} -- . - sed -r -i 's|https://repo-default.voidlinux.org|{mirror_url}|g' etc/xbps.d/repos-* - echo 'XBPS_CHROOT_CMD=ethereal\\nXBPS_ALLOW_CHROOT_BREAKOUT=yes' > etc/conf - ln -s / masterdir - ./xbps-src -1 pkg {self.package_name} - """, "--privileged", tty=True, post_mortem=True, volumes=volumes, - architecture=self.docker_architecture) + container = _docker.run(self.build_builder_image(), script, "--privileged", + tty=True, post_mortem=True, volumes=volumes, + architecture=self.docker_architecture, + interactive=bool(self.private_key)) name = f"{self.package_name}-{self.project.version}_1.{self.architecture}{self.libc_tag}.xbps" (self.distro_root / self.libc / name).write_bytes(container.file(f"/io/hostdir/binpkgs/{name}")) repodata = f"{self.architecture}{self.libc_tag}-repodata" (self.distro_root / self.libc / repodata).write_bytes(container.file(f"/io/hostdir/binpkgs/{repodata}")) + if self.private_key: + signature = name + ".sig2" + (self.distro_root / self.libc / signature).write_bytes(container.file(f"/io/hostdir/binpkgs/{signature}")) return {"main": self.distro_root / self.libc / name} def test(self, package): @@ -229,11 +245,21 @@ def test(self, package): for path in self.project.test_files: volumes.append((self.project.root / path, f"/io/{path}")) with self.mirror: - return _docker.run(base, f""" - sudo xbps-install -ySu -R /pkg/ xbps {self.package_name} + container = _docker.run(base, f""" + {"yes | " if self.private_key else ""} sudo xbps-install -ySu -R /pkg/ xbps {self.package_name} {self.project.test_command} """, volumes=volumes, tty=True, root=False, post_mortem=True, architecture=self.docker_architecture) + if self.private_key: + with container["/var/db/xbps/keys/"] as tar: + files = [i for i in tar.getmembers() if i.name.endswith(".plist")] + assert len(files) == 3, repr(files) + path = max(files, key=lambda x: x.mtime) + name = PurePosixPath(path.name).name + with tar.extractfile(path) as f: + contents = f.read() + (self.distro_root / name).write_bytes(contents) + return container @lru_cache() def _void_packages_head(self): @@ -273,6 +299,32 @@ def void_packages_repo(self): return cache + @property + def private_key(self): + return self._private_key + + @private_key.setter + def private_key(self, path): + if path is None: + self._private_key = None + return + private_key = Path(path) + try: + with open(private_key, "rb") as f: + header = f.readline() + if not re.match(b"-----BEGIN( RSA)?( ENCRYPTED)? PRIVATE KEY-----", header): + raise _exceptions.PolycotylusUsageError(_exceptions._unravel(f""" + Invalid Void signing private key file "{private_key}". + Create a signing certificate using one of: + + openssl genrsa -out privkey.pem 4096 # passwordless + openssl genrsa -des3 -out privkey.pem 4096 # password required + """)) + except OSError as ex: + raise _exceptions.PolycotylusUsageError( + f'Getting an {type(ex).__name__}() whilst accessing private key file "{private_key}"') + self._private_key = private_key + class VoidMusl(Void): libc = "musl" diff --git a/pyproject.toml b/pyproject.toml index c3541f2..e398972 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,7 @@ test = [ "homepage" = "https://github.com/bwoodsend/polycotylus" [project.scripts] -polycotylus = "polycotylus.__main__:cli" +polycotylus = "polycotylus.__main__:_console_script" [tool.setuptools] zip-safe = false diff --git a/tests/gpg-home/openpgp-revocs.d/582A6792B83A333D3B316677ED7C694736BC74B3.rev b/tests/gpg-home/openpgp-revocs.d/582A6792B83A333D3B316677ED7C694736BC74B3.rev new file mode 100644 index 0000000..4bf9982 --- /dev/null +++ b/tests/gpg-home/openpgp-revocs.d/582A6792B83A333D3B316677ED7C694736BC74B3.rev @@ -0,0 +1,35 @@ +This is a revocation certificate for the OpenPGP key: + +pub rsa3072 2023-11-09 [S] + 582A6792B83A333D3B316677ED7C694736BC74B3 +uid póĺýĉöţỹùṣ 🎩 + +A revocation certificate is a kind of "kill switch" to publicly +declare that a key shall not anymore be used. It is not possible +to retract such a revocation certificate once it has been published. + +Use it to revoke this key in case of a compromise or loss of +the secret key. However, if the secret key is still accessible, +it is better to generate a new revocation certificate and give +a reason for the revocation. For details see the description of +of the gpg command "--generate-revocation" in the GnuPG manual. + +To avoid an accidental use of this file, a colon has been inserted +before the 5 dashes below. Remove this colon with a text editor +before importing and publishing this revocation certificate. + +:-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: This is a revocation certificate + +iQG2BCABCAAgFiEEWCpnkrg6Mz07MWZ37XxpRza8dLMFAmVM6NoCHQAACgkQ7Xxp +Rza8dLMl+Av+NrJV4oLXR8YREppmS7r+w9ECyCHI2vS/2C5uUs2gY5m/ayJ7MdG5 +t5auu5JXDREnKAGnoBT582cDy2KeWJC8Li91n9xNEguao8QZZk34QDDzztNr1GRU +dqdpxZcRrw5CnH/Y0N/2EydWW5bhLQBT4v3h5oMln/29UMtaotUA0yYFgByq/uan +jb4XO7hNDo8pyrzAvZTHvO/7THKhH46vi5IaOA3NSUfA7ga9ugz8+EnMno7z27Oq +Y+gJNKwka8JooSWyJmh93Mcy/Sk75wYAThrgYDjopLFm41JcBloMnN1TmRbe0NBq +gc6y9bPLCkMZOKPwBXFHX4MyPSzxzqZ0rK7ObX/1a8Jr+j/PfCkACpiZP5q54k8F +YG5gCKjsZHB5cS9FOieBoiB+JHIUoFIgCB/gpqG3mS3gXOPs1ti1XbieQjH8L51R +a8pfIxVI/d07UjMWBhkByJhfza7HsKQKx9P0d8c9RDb+T8aqk8sa+H/5xQVTNz8Y +LwLykjRfreMY +=e5Rt +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/gpg-home/openpgp-revocs.d/AD4A871B79599B9DD0F62EBE2DD6A735C5B889E7.rev b/tests/gpg-home/openpgp-revocs.d/AD4A871B79599B9DD0F62EBE2DD6A735C5B889E7.rev new file mode 100644 index 0000000..6a5e640 --- /dev/null +++ b/tests/gpg-home/openpgp-revocs.d/AD4A871B79599B9DD0F62EBE2DD6A735C5B889E7.rev @@ -0,0 +1,35 @@ +This is a revocation certificate for the OpenPGP key: + +pub rsa3072 2023-11-19 [SC] [expires: 2025-11-18] + AD4A871B79599B9DD0F62EBE2DD6A735C5B889E7 +uid énć¶ỳþŧeð + +A revocation certificate is a kind of "kill switch" to publicly +declare that a key shall not anymore be used. It is not possible +to retract such a revocation certificate once it has been published. + +Use it to revoke this key in case of a compromise or loss of +the secret key. However, if the secret key is still accessible, +it is better to generate a new revocation certificate and give +a reason for the revocation. For details see the description of +of the gpg command "--generate-revocation" in the GnuPG manual. + +To avoid an accidental use of this file, a colon has been inserted +before the 5 dashes below. Remove this colon with a text editor +before importing and publishing this revocation certificate. + +:-----BEGIN PGP PUBLIC KEY BLOCK----- +Comment: This is a revocation certificate + +iQG2BCABCAAgFiEErUqHG3lZm53Q9i6+LdanNcW4iecFAmVaUKYCHQAACgkQLdan +NcW4iedi0wwAka6x7jWpsCm+E+TnfXl8StW2HtfozJWEg9/tKA2SiJcrAWlT7+wZ +RywWPM+y7M9CNLncMn0+FaMfBz6tB6xBlUqF/3ZqnFRo7qc67S1F78J1rrjhtr5q +hbVwr2Lv8/GnEQKZ6OQ9I16N6FpSmQpvo08YQO25Kvig/zN9X4R+nk3aRFAOdqX5 +FkFOOQQXE7ff2cjBcaxNT/lS+OtJtMLrG0n2G9ekQRLCos3YG6HqaV/WWSFMUK6m +efIvMXLaX2ZdxeIJpqC0HUhnazpejBtPSIYFm6JywXgItI2Fg34ff0h3Cj5KYO3/ +613SJfh9gjquAIjuMx5d8f7O8s18KMqPHTBUIdnARdydjFydRDHhxTZiiY/01ygH +pfOIQJoO/+SZ9cPlo5Eq7+gMvVD2Vg/SzxPZ4QXDjvjv9W80bj9xZOgl5Z+Gepvz +CZf+J3MbBZtRjNKAZvep4rJGN5kKZPg+Y74bFmrQNLGlE53qqCxXdqGPDcrRliHn +LfGwh4EBH6OG +=0eEf +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/gpg-home/private-keys-v1.d/2E9BFEBD0DD51ECD2D69481A85910BEB0180152D.key b/tests/gpg-home/private-keys-v1.d/2E9BFEBD0DD51ECD2D69481A85910BEB0180152D.key new file mode 100644 index 0000000..61ca39f --- /dev/null +++ b/tests/gpg-home/private-keys-v1.d/2E9BFEBD0DD51ECD2D69481A85910BEB0180152D.key @@ -0,0 +1,42 @@ +Created: 20231109T141232 +Key: (private-key (rsa (n #00B9FA12BCA746BC29251EA05CE829A46AADBCF3940A + 654E181BA130F90ED3940C48698D1AB2C8C2A809AA465AF38DE7F5A0FD6E35F5142CEE + 4DF97C9CB537BBA777896FCD1E2640AD16EED3232AFBA0237AF47DF2952E81146F4A81 + D2D418100482A2C90764271AFB6E8ACB1709E06E6624C6D8C194FEDC726580ADC276F3 + 03C1D25731B239FB94C9953AF48A050C7D382F7C5D467C6D27473BDEC4A0AC01AFD04B + 970B90E3ADF3A4E398533FE7103A0898A9F5BE6148A321924AE195DFDFB84C4A1D2657 + 0116DCB81432C7BCB9FDF65F8035CDD64DACF042DB77BADD3C88C7D786C63ABEE2EEF6 + 9377A3E4542710035EF2FF13F117874C80280315011C136C979FFDD581CE8CAAB93240 + 742AEC5CBF6AF4C528B10C9C3ADAC20B8E14A19F616DE4D0B335698C70535A9080441E + D5E6C55798503BE5852D897FACD5D2CF85AF48CE3023D4A6A05AE983AA8C2D5A88847E + E2E70C042EE0CB84F9FDCA6B8A2ED831C05663284418F9FDDB701C89935A4DED2E4B54 + F7F8D5097DBBEF43BCC75335DB#)(e #010001#)(d + #429A0A07C1A8154E6EDAAEF756B5430A6939D7C95687BD15E91F2AD8A6B4A2BA5B5C + 618A0428DD7D2C28EC9539CFC21837154B58CB71F43D4B73636B0382F8131B3DFEE9E8 + 382EC54E072D59D4D4D41EA0B3DA3EFBA9F1D40D9F132B1B859A86E2F9C5207E7F7AAC + 3C843C02FE287FA0D8DED5554E348F61F073CAC66FAECBD3A7AE4E3FD3800362C5AC9D + 52A84836B7DE599D55D6CD88597C5964F55D6425406531AC94B0D4E50251F78528C769 + 0A3F75DCB802FBF7F563DA6E32A767CB48C67CBB851E85F3F76D45BFDBCA3AA0C8370C + AF05C48AB18FB4EDAA036232F86C2235A13DD8D39E43FF4122A4C0A49DCD16B8764902 + CBC721FBB70B500797CA98363D851E9B55530728F5067DCC1AF04FAAB815CA6060A5C5 + 3EA51886E87794F8EBE68DD157DCF3C1F7FEA0E94E625201E736BD2BC13FAE5532919F + 6E23A16712755697D68A627E1EDC8138DFD81F83A34580AAEAB798FA8930A51FBED58D + 2F0AB0D1D69366E0AF95021A3A9752849389BCAAAAB8CB98DF7ECB364CBDBDE30F49E1 + #)(p #00CB01DA414812D6401ECBC2A65CC79125F624AB0C781BFD2C16F6CD9741AE00 + 4CEB128E76DB4762F9ECAE80D67BA6EFB51216186A51E68DDA9BFE0FB5D63CD5B731C4 + F22752680A2A3BD0A25408444221723EB8BAA1FF3B364F544DEF123BBE3536A5961976 + AC42929D061B4D71564DB2D37EEAEA134E882EC233E5C0F61BCA1FFB50BC648579D478 + A1C8D92C89526E396CC5E1D4B6F76EDB28BACF0E8671A049C63FD27660957EFAF538C8 + 6653243143EC3FB379381A5C4AB426408E00F83649#)(q + #00EA86262B75EDD303DE229CE03154D1791942A6B57F5FAA38A1A797F3C6DCCBD505 + A4522404DF46506D98B829BB6564ADDA929F52F97083A0ABC56B02050B9F2DDAEF0AEB + CBC3A1CD54773FE96D00A9256304C37272234B1E37581D6D96928E6C0F82A8A4555A66 + 09DBA84821B913F5BABF36C0E50EAAE925598405BE0A6CAACCB57F2A54DF060183CB8C + BD5ABEF9FA55E92253E3E77E881B2A48E4EC5C7F8C6B577104D2911758B31BA7CFE785 + B11A1B47F132632EA4BBF5DEED128309A9FB03#)(u + #5A9D93D6125D631026E66113CC4C540E2FFA5D35C6DBE19D25CF932D1481A30428F1 + CE6E1A611063A28C9B39659F49E5CD8A8B65803888C6345F1B2718690B8538C410CB3E + C001CF56D6FF9CB7468B736DA8E8619A5A98650F26D41680A63457111574A32F0C60D3 + C684D41876E482AB77E6823244CB7207AE9C4A4025BB6F20A82C13492EDDC5E288FCDB + AAC1ABACA7F553B8F9332CA249E9C035BAC3812562A2C7998423E7B3B96FAAE5CF3113 + 224851DAC154F57F97F9CDED7D6C29FE88FC#))) diff --git a/tests/gpg-home/private-keys-v1.d/795D7CFC35E27DB23C80CE9C689466C143F0D70F.key b/tests/gpg-home/private-keys-v1.d/795D7CFC35E27DB23C80CE9C689466C143F0D70F.key new file mode 100644 index 0000000..1a915f8 --- /dev/null +++ b/tests/gpg-home/private-keys-v1.d/795D7CFC35E27DB23C80CE9C689466C143F0D70F.key @@ -0,0 +1,45 @@ +Created: 20231119T181438 +Key: (protected-private-key (rsa (n #00ED80AA0321EB721F509FC65ABF037A35 + 13BE5E809AA2F5E330C4A1D714EBE3510AB644A0DC65C7C497131AA7193C1685BE7EEF + 32DD86A7C93A78C46096299AF6A3FF2E670001FB56E5EF8D0B0676E0270B0421B3D0CD + BFC0679DD352B744C808FCC8453A0F75DCBD1BC8461A5158DE71DE18FE66FF31367DAC + 8AB18FCD9B0820DDB509EC0D1B9EF2525E5D59592043BC205373703FF6352E4CCE88C3 + 36F6D1D4714241DEBCB88020F142A5A655A185B9176E6998C988B381D27E9C58173A7C + C18272E1F1CE90475121F9AF7B88E2307EDC54F214A4C17DA1713CE1AEB58030093D42 + 40F4F3273755D0CEDEE4907C1FE5E9178878A584C715A9E974090073C4672455A44A4A + CFEA5EB3F86CDAC7A7A12F7715912969406972385A52434BCF10AE83D9901F2F2FEE9B + 76F6A04658493EE49AD6DAEBDD3F4EC335D9FB821F2CA17892077C0FED6AD7B3D5B337 + 9C3A09CF60B042A80F67CC7C6E9846C0F058CE96F9C12722F710BE77002BE94A9E8E64 + 473D02D41593FA4B28D5D934916E03E402A7#)(e #010001#)(protected + openpgp-s2k3-ocb-aes ((sha1 #BA4D3299D2E9437A# + "68477952")#B78A9EFC48C652175524FE67#)#F106EC8FC07D6CA104E5FA169BC037 + FDF8FA833A71B65035691FE743419C6B870C9A15042B1B89A3DE933A0D77AAF3291CCD + C64FFF3C4C7F7FD9D4FD4A174FE0974448A08B092AF045BEC13662EEDFBB914EE5DCA9 + 527937C27402189103EA6ABFF5F9E70A4ECF65EAAB67B50A8688C51C1BA807960B7448 + 09BE9A3CFFE6D65A361956A546B83E320BE07C31AE3AEC41CA9218E96AE86C48803B0C + AE67784E240ED20BCE91D0DF950D32BD1C31A84C267E06A05632BE435A33852C29D368 + 964418E60E191A08EF28484A2FEDBB69C9B82BBB67068E7E4C0D3E1494A537B7911010 + 210D89B360DA0618278074BB35F66B4C7B6B61E5BA1F4CD61D1E80719CC55DCC2B0702 + 1E20467A3843DB527F9142390FCBF6A691D0528028F067C56749E4070988339393FD58 + 711C23EF9BDB81A5429C8255ED0E9D2F026F5505036E1880F11A5A2434F4D4B46B79C4 + F2FB547EB175664CA47928C29BF2B419D2D4834C369FF8504E6FDED91BD491FEE609E5 + 097DCCE0180812A07008BBDF446A949C7E36C8BE4210E5DA9BAE3CD3E540434EFBA27B + 142B8E66B963D64E8CE8867C22754B508D58AF736C08856FAA060F0AB30FEE8959F5AB + FA70550B976E6DC0CF9E513755508AE3FED4E16D31E634A83CD876A358217E9E0EC78D + 1AB5BED7AC6CD3CA051F2012FF40E59AFD9D8914381DD57C8A1A0642DFE3FEE0906793 + EA6B7E98E8899305CB2EE41D35D1A11213C5909E36078DB7DAF6F7E47B7392B1BE75C6 + 4C0D57941C2C96C618E429BAD922A07B2E434E66B52EB94CDEF5DC34F737A3F72F416F + 3B027D769A946DBD7C4959F5C4F8A5CC7B24AF96C0FC0849A692A5F88697F4233A27A2 + 3C19D5070352BFEC7EBEDBE4C71CB30985BE3DFE91CEF614E96C2B021343B20B6BE761 + B50C3F6DEDAF3B9957E994E8FA5D3F5D94D1FA5005A88BDFAD897F3A7F0D7EC0CEF22B + 358ED3385660815A5A89796706CB812C855CFAF5F4FF508AF94BFFAAD0D871F4B612C6 + 656C91DA476C6753DADF22B471E4DC60CCA73C69CFAC4DAABDA619E7DA61FE0BCDF6E0 + C58F9AE4B62E50559E5BFEEFB5B8DA8EAB3A855C04F3574317ED379477036D958D309E + 77B0ECD1DDB4BB925DDD07D6E019949C50B133167D019512109E24E0DA011372E6AB23 + 2293F4B7F9159806096146D59A03E9C3A7463C07AAC8609C3757B06C4F486F38EEDC7D + 8AC06DAFA24D04EFAE429F60453FB54E6E68EE2F27C29EFFED095169052C04FC1A0053 + D6618E38CD9B55AD6312BC0FF11224FD9ABD4F711B1D7E39DA7F3C681D0C86ABC8EA7F + 8E0E6F47BF4C7D5ED528BACB0A00D95A91D024F66C4D29CD48961F095474EB6C1B342F + 01556473A195449BCF410D3C6B8FFDCE38DEB5B73B904F7844F5E167D05EB5B282ACD2 + 5B503F446AC2BBE9B2DD1F2718785E1EDE6BB5D0BDDD13#)(protected-at + "20231119T181501"))) diff --git a/tests/gpg-home/private-keys-v1.d/C27D4525F2BEC44299AB6236F4AA74B2860CDA3D.key b/tests/gpg-home/private-keys-v1.d/C27D4525F2BEC44299AB6236F4AA74B2860CDA3D.key new file mode 100644 index 0000000..763ea6f --- /dev/null +++ b/tests/gpg-home/private-keys-v1.d/C27D4525F2BEC44299AB6236F4AA74B2860CDA3D.key @@ -0,0 +1,42 @@ +Created: 20231109T141232 +Key: (private-key (rsa (n #00C511B026309700927E54CBE0E7DCBADD4446FAA917 + 65CA06B85EEC132DD8EA73E5CBC424CC1B53145BB384F54F0F92592A459EDE105E571E + 6F91BAD6B09F20943F80D8160416078E30B7B89B65AB297E128CBE48FD9CE96552D582 + CEB26811ECFAD81EA7ABF7A2C9625E82631681E7810ABB7527B5BB99C56156028048AE + A40E3ED8DD5E311F8E2E2ED64DD3F6A87736BC8128999A59BB840F8FCF283ADBE311E9 + DA4C6E2F40854A66BD19659E2AA16BA24FA26757870D97174BD76C1A2FF1BA6453632B + 8F3999390D8657D42838E5634D626B30E95F57DC95EC6DD42E1A35D0C3D88A296A0B6D + A9CB17F82977C0BF8A3DBA8A796E47366421859C17CF87F21DF1F25761C0A07147CE51 + 05F39286307A29BFEDDAD470669BE944944982B2DEB1DDDA3965C9F3C47704583D9405 + 619A6B2D73C864620A8DA438E6BCC13715760329735FDACAD28F288F53CC196F89C594 + E3389132FDAEC58A30E38AC06B713440FBFE794FF09B83A0A1E19A0D7DE154FCB03BA3 + 76C8048947BA3D36BBE68D0ED1#)(e #010001#)(d + #10AB74B2B64CD4FA8F547843D7B91F2C1503755E94F66CF1713FC39E6D096543AA7E + 3862EDCA53411744B1E18244E1EF4843C80826C04ED99491C789339032544D4EDBCDCE + 48AD6E44B16071F20159802AECDC326BBEE32B49522594D06DE7339C40687A171CDA6D + F333E5644BAF6F62C602A20AB0350F4682A64B8DDDC2BABE8DBDBB1EA599C81020B176 + 0C910277C5C48A49B73C8C37456E2A39AEC07E807B051A0C79B6E6314AE03297670B75 + B5B3D770A9B4837DE4136775688D3765DE2A4240C77BECF067163C9E78265D67BE31FE + ED5E0BFC9D977B03D75CF478F2B19A6CA638537FEAF9C832A6A1ABBEADE26D67F691A9 + 7449FB1DC136DC26F64E3209B98517B700468C366F7D0D916FBB1078DE759AEE093418 + 33FDB83EFF0048902875449298A69C961541F2525EE2487C33BD0501F3F0645B699499 + 675AC022457BD01D058E98FF092AEE53FA6E472DFE3EFF4039465D47C909DDB7025FDA + AB3BC0380B1BDDAC6E0FC61782A36F7A2F8B96D769DFB010C41BF42E7C940E0184F315 + #)(p #00C993227A66D015808F57AC8DE8DCFD7F181969F06BBE8261928C1C961A56CF + B263F74C0DAF5575D5110F4F8D1FFA6FA0F96D06A17FA5E7D4C09385F336320C822FAE + 7F1FF5B81DBC0E910D702C119404EA9113D75E075164FDF780E7C06F4A584B1BB922FA + 746EE6F932031F997886E4EF3902EDC7E42CE8FE046F66889F9A6DCD05415FD5DB7D5C + 3D8EBE159761DB01E09D22EF2011DEF0EF84B0E05E320800356FB6949CD270C0705E47 + 4E34BB54B4C07E8952ECEB2965C9C747E811CEC52F#)(q + #00FA471F55714C483A6C435A390BA62E08A3385DC725FC23F0EC743EC74F4BA5ABAF + 7A4FA760D32ABE27F2823BEFA4A493086CE7342F2EF30BBD0753C4E53337554F751F42 + A3C0DA08E84D993D39B353F9C87F37302C41B44FDFEA2C7DE25CED3A4E4E43104FE4F5 + 067DA954ACC2A1F4307E81BFDCDB5F1C20BEF4C69CD93C8183F4716DA9ED0DFF57F0F7 + BD4C49402FCBF8CE9A763C8CB543D5FEBFB2B7B052879F992F95D4F1C755E3DE3896D9 + BFA9F0851428EF7BC7288C02CE0461A2946BFF#)(u + #009C17DFAFA2BD260EDCAB41672260222A664815E9729E61468D06866EFB8512CAE7 + 44E41F4534F8282D1E0E369FBF876C07E2D6542CA36EA953A46EB14423825101032DAE + E1C568EFB3AA1FFA78C1442023F3DCD62F21058D13DDA8241A405E200BAD945795DB7E + 6343E51ECFE1277090451FB102BC237B3DF02DB386600DB6F50ED540441EB27E77639E + E26312BF5B1B7432115007A6ACA90815119C76E2B351D74CF71C69C8D391EBD562EADD + 182D996F6CC90DAC0C9E3B99E4EB2300756ACE#))) diff --git a/tests/gpg-home/private-keys-v1.d/C4974F674FF8D78918C1E847E2BD0AF9C9E3D908.key b/tests/gpg-home/private-keys-v1.d/C4974F674FF8D78918C1E847E2BD0AF9C9E3D908.key new file mode 100644 index 0000000..77a5684 --- /dev/null +++ b/tests/gpg-home/private-keys-v1.d/C4974F674FF8D78918C1E847E2BD0AF9C9E3D908.key @@ -0,0 +1,45 @@ +Created: 20231119T181438 +Key: (protected-private-key (rsa (n #00A10F9695D61C0EA8991039A4982891CF + 8EC0318CA16F211EF2F6CB6F465CA39BCEBF6188D78FDD8896C201DED40EE0DC79559D + 3E965DB4A5409680F04173A8F3764B52270812FBF1EE891DEE5E03372C6222128F7F11 + 535C52082D292A8C3CEEC374E1A637CBC39623D6C00AD0AE8587724F6C27101EB2172C + 81E23D15FE1F90C5CED933AAC0433DEF7359EF39B045302244233B11CB6E1BBA7A2366 + 73EBDAD4B14451BD7369168C11065236C17995D48E1539E06F1755187ADD9236AD5A4D + 3A283010D93521EB0DA09FFFC1B2E45D9F39FC8EAD25C910176CFA3CC2C9C4F751547A + 5326BBE317189A15407368A0E95C61CA991871A86581728BBA0BF31F0714257716EDF1 + 7360F6A982A665CF94DA2F8E48FD9397B4377CCCDDCF2C2C056FB7A863AA896F032853 + 44F5DFAE25A0C71DA224DE6EE382577E9DE128BA206DB9F2A1D6005D83E93C6856E03F + 253AA6A5AC2093438D8B1794E6E8EB1AA7C205B54E1716D1445B32018E8F1F707A1B71 + 39FFDDAFFF16139BFFF62381527B37E8AB9D#)(e #010001#)(protected + openpgp-s2k3-ocb-aes ((sha1 #79D3E9535D33FB94# + "68477952")#9CBEFE9EF26F11E0521E13B0#)#808A3B077754DB8BCA49E8A196FF3E + EE5B4302BE18142B4B8E2441BDD6C809B965BAEF68D20FCC6F6D5BF09264B2B94A7951 + 671440F4305678DAF8D0FD413D5C36E8887A7854A09F8CDEA63DB6E9E545D0AB32FCCD + 68EAC9F900C1D0CE71727D6864202177DC5ED6839E2D60A2888471D0522D93875613AD + C846B33723A6014538AAD58B6CEE992D6A535E162E1CC5176C80D46FFFCCB3CD8258FA + D4BCC29D50E1570A45EAB59950F7A5239DE161630E2E9E25F8CFC13CCA0E79438B44AC + BC5B9C66FF8A6D6290B608602A81B6C4EC9954EF6ECDA0A963B5084F59059B118B299F + 76A7BC9CD253114B04850B1184E8609F8E6E6B2EC0CC221A7896C4AFB6F95FF06E65CF + BE225C0B61B4C2546A0F5AEE5D7F19FE27FE346A2D762014EAEDA5256624A76936C9CC + 91AB35B46FCE6DF281D0318E54881EBB04E7FD5A5B1100E78ED805A398B62BE351DD2A + DA3D12B1C88928BF61BA6838BDCFB4457A4424542749E4546BC93C4EF59E3BA22A8105 + 60E387C3BD9FCA3028CD0AC2725DC978B24F456DB6AA589579E80B3576B9014748A69D + B1A904A9881AD053F0043E881B3602FF90AC850EAC47F3699A9461EBAF7BDDA12CAF59 + 080198553D025490FEAD019F8D4A782A1818B28F8CFF67F633B1FC95EFB7B7E3A0A9D3 + C6C6DC21397C62F36B4E04AD4F406558CA97DB0849F7025294CA08AC91154B2C711E7E + 4C16CD8F2A279EE53CE4DC4EC213F114E30E48EC18CC2740AE3CECFBC0B58352DF806B + 6BECDF971FD710C7DA8C69D57379777A3D63F78B8A01DB6FC4B47C010FC3A73C4CB61C + EBBA76E12A72D0F2A6AD7610EEC668135C3DA67077B258B9A60F603D717FEB28FC0E83 + 0243AAF346A00096A95F5A810EC7A8EEC8D658B0F472ACA49E85EC2576CDD7E66EF90D + FD0BD0B623487ADCAE63649698EE908EBD048A719A3EFD3452CD7FDD91F0EE82AE302F + D96D2EFACF770E1E889EC2554775BC4715CE0C433CE7426088852D087A14D95830F574 + 35AC22704BABFD3B9D75533329284BBC0D6E9F7A2E3111C68176EAD894E59521570FC0 + 3BBAEB296B17DDC517ACD5E97087925BBD1347DD4AEAD97019A59CEFF9F876725EC999 + 3144319B611B75657A74B1E701F08F79DDDE7DEB2A471ECF6714D767EE05FDE934337B + 92E1642DBB81BD86663D0917837F1875A8E97FD0707F80F69E0CBE9856D162789D463B + CDA6BC67CF469666D07BB70F51949066D7CFC1CE01AA8E647A2480919C9D1BE923C4B0 + A6A0F35CF10586A53275D257FD64BCDAFF9D44747C3CD0B264A685ED0F653C32BE15B6 + B144172F6606AB9DE2EFD1601EC4C3EE82C96DC7CCA6DB08951F13FAF87EFEDA9F4F4B + 5D2C0F4893CBBB5756F9FB47638566BB649DD2D238EE4EB18DB809F1AE0522C07CF2FB + F8A55C5D5EB8FB2396B747612F051A2550525B1ABE9CEB#)(protected-at + "20231119T181502"))) diff --git a/tests/gpg-home/pubring.kbx b/tests/gpg-home/pubring.kbx new file mode 100644 index 0000000..94d9dfc Binary files /dev/null and b/tests/gpg-home/pubring.kbx differ diff --git "a/tests/gpg-home/p\303\263\304\272\303\275\304\211\303\266\305\243\341\273\271\303\271\341\271\243 \360\237\216\251.gpg" "b/tests/gpg-home/p\303\263\304\272\303\275\304\211\303\266\305\243\341\273\271\303\271\341\271\243 \360\237\216\251.gpg" new file mode 100644 index 0000000..9416083 --- /dev/null +++ "b/tests/gpg-home/p\303\263\304\272\303\275\304\211\303\266\305\243\341\273\271\303\271\341\271\243 \360\237\216\251.gpg" @@ -0,0 +1,82 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +IuyNnRSB +=WuJC +-----END PGP PRIVATE KEY BLOCK----- diff --git "a/tests/gpg-home/p\303\263\304\272\303\275\304\211\303\266\305\243\341\273\271\303\271\341\271\243 \360\237\216\251.gpg.pub" "b/tests/gpg-home/p\303\263\304\272\303\275\304\211\303\266\305\243\341\273\271\303\271\341\271\243 \360\237\216\251.gpg.pub" new file mode 100644 index 0000000..00b63b2 --- /dev/null +++ "b/tests/gpg-home/p\303\263\304\272\303\275\304\211\303\266\305\243\341\273\271\303\271\341\271\243 \360\237\216\251.gpg.pub" @@ -0,0 +1,41 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=e6HE +-----END PGP PUBLIC KEY BLOCK----- diff --git a/tests/gpg-home/sshcontrol b/tests/gpg-home/sshcontrol new file mode 100644 index 0000000..c61ee5c --- /dev/null +++ b/tests/gpg-home/sshcontrol @@ -0,0 +1,11 @@ +# List of allowed ssh keys. Only keys present in this file are used +# in the SSH protocol. The ssh-add tool may add new entries to this +# file to enable them; you may also add them manually. Comment +# lines, like this one, as well as empty lines are ignored. Lines do +# have a certain length limit but this is not serious limitation as +# the format of the entries is fixed and checked by gpg-agent. A +# non-comment line starts with optional white spaces, followed by the +# keygrip of the key given as 40 hex digits, optionally followed by a +# caching TTL in seconds, and another optional field for arbitrary +# flags. Prepend the keygrip with an '!' mark to disable it. + diff --git a/tests/gpg-home/trustdb.gpg b/tests/gpg-home/trustdb.gpg new file mode 100644 index 0000000..b008574 Binary files /dev/null and b/tests/gpg-home/trustdb.gpg differ diff --git a/tests/shared.py b/tests/shared.py index 8e6be38..3611ecd 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -18,6 +18,8 @@ fussy_arch = kitchen_sink.with_name("fussy_arch") poetry_based = kitchen_sink.with_name("poetry-based") +gpg_home = Path(__file__).with_name("gpg-home").resolve() + awkward_pypi_packages = [ "zope.event", # Contains a '.' "ruamel.yaml", diff --git a/tests/test_arch.py b/tests/test_arch.py index 1585dfd..52cce31 100644 --- a/tests/test_arch.py +++ b/tests/test_arch.py @@ -7,8 +7,9 @@ import shutil import pyzstd +import pytest -from polycotylus import _docker +from polycotylus import _docker, _exceptions from polycotylus._project import Project from polycotylus._mirror import mirrors from polycotylus._arch import Arch @@ -93,13 +94,15 @@ def test_ubrotli(): self.test(package) -def test_kitchen_sink(monkeypatch): +def test_kitchen_sink_signing(monkeypatch): monkeypatch.setenv("SETUPTOOLS_SCM_PRETEND_VERSION", "1.2.3") - self = Arch(Project.from_root(shared.kitchen_sink)) + monkeypatch.setenv("GNUPGHOME", str(shared.gpg_home)) + self = Arch(Project.from_root(shared.kitchen_sink), signature="póĺýĉöţỹùṣ 🎩") # Test for encoding surprises such as https://bugs.archlinux.org/task/40805#comment124197 - monkeypatch.setattr(self.dockerfile, lambda: Arch.dockerfile(self) + "ENV LANG=C\n") + monkeypatch.setattr(self, "dockerfile", lambda: Arch.dockerfile(self) + "ENV LANG=C\n") self.generate() package = self.build()["main"] + assert package.with_name(package.name + ".sig").exists() installed = self.test(package).commit() script = "pacman -Q --info python-99---s1lly---name---packag3--x--y--z" container = _docker.run(installed, script, architecture=self.docker_architecture) @@ -107,6 +110,27 @@ def test_kitchen_sink(monkeypatch): container.output) +def test_signing_id_normalisation(monkeypatch): + monkeypatch.setenv("GNUPGHOME", str(shared.gpg_home)) + self = Arch(Project.from_root(shared.bare_minimum)) + + self.signing_id = "ED7C694736BC74B3" + assert self.signing_id == "582A6792B83A333D3B316677ED7C694736BC74B3" + self.signing_id = "2DD6A735C5B889E7" + assert self.signing_id == "AD4A871B79599B9DD0F62EBE2DD6A735C5B889E7" + self.signing_id = "🎩" + assert self.signing_id == "582A6792B83A333D3B316677ED7C694736BC74B3" + self.signing_id = "encrypted@example.com" + assert self.signing_id == "AD4A871B79599B9DD0F62EBE2DD6A735C5B889E7" + + with pytest.raises(_exceptions.PolycotylusUsageError, + match="identifier \"example.com\" is ambiguous.* either of \\['2DD6A735C5B889E7', 'ED7C694736BC74B3'\\]"): + self.signing_id = "example.com" + with pytest.raises(_exceptions.PolycotylusUsageError, + match='No private GPG key .* fingerprint "KoЯn"'): + self.signing_id = "KoЯn" + + def test_post_mortem(polycotylus_yaml): script = textwrap.dedent(""" import polycotylus.__main__ diff --git a/tests/test_completion.py b/tests/test_completion.py index e018e54..e903741 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -1,12 +1,15 @@ import shutil import subprocess +import shlex +from pathlib import Path import pytest from polycotylus.__main__ import cli +import shared -def test_fish(capsys): +def test_fish(capsys, monkeypatch): with pytest.raises(SystemExit) as error: cli(["--completion=fish"]) assert error.value.code == 0 @@ -20,12 +23,29 @@ def test_fish(capsys): def _exec(code): return subprocess.check_output(["fish"], text=True, input=completions + code) + def _complete(command): + return _exec(shlex.join(["complete", "--do-complete", command])) + assert not _exec("") - assert "alpine" in _exec("complete --do-complete 'polycotylus '") - assert "alpine" not in _exec("complete --do-complete 'polycotylus arch '") - - assert "--architecture" in _exec("complete --do-complete 'polycotylus -'") - assert "--architecture" in _exec("complete --do-complete 'polycotylus manjaro -'") - assert "ppc64le" in _exec("complete --do-complete 'polycotylus alpine --architecture='") - assert "aarch64" in _exec("complete --do-complete 'polycotylus manjaro --architecture='") - assert "ppc64le" not in _exec("complete --do-complete 'polycotylus manjaro --architecture='") + assert "alpine" in _complete("polycotylus ") + assert "alpine" not in _complete("complete polycotylus arch ") + + assert "--architecture" in _complete("polycotylus -") + assert "--architecture" in _complete("polycotylus manjaro -") + assert "ppc64le" in _complete("polycotylus alpine --architecture=") + assert "aarch64" in _complete("polycotylus manjaro --architecture=") + assert "ppc64le" not in _complete("polycotylus manjaro --architecture=") + + assert "--void-signing-certificate" in _complete("polycotylus void -") + assert "--void-signing-certificate" in _complete("polycotylus void:musl -") + assert "--void-signing-certificate" in _complete("polycotylus -") + certificate = str(Path(__file__, "../void-keys/unencrypted-ssl.pem").resolve()) + assert certificate in _complete(f"polycotylus void --void-signing-certificate '{certificate[:-5]}") + + assert "--void-signing-certificate" not in _complete("polycotylus arch -") + assert "--gpg-signing-id" not in _complete("polycotylus void -") + assert "--gpg-signing-id" in _complete("polycotylus arch -") + + if shutil.which("gpg"): + monkeypatch.setenv("GNUPGHOME", str(shared.gpg_home)) + assert "ED7C694736BC74B3" in _complete("polycotylus arch --gpg-signing-id ") diff --git a/tests/test_fedora.py b/tests/test_fedora.py index 10cc9c0..bff0641 100644 --- a/tests/test_fedora.py +++ b/tests/test_fedora.py @@ -161,17 +161,23 @@ def test_test_command(polycotylus_yaml): assert "%global __pytest /usr/bin/xvfb-run %{python3} -c 'print(10)'" in spec -def test_cli(monkeypatch, capsys): +def test_cli_signing(monkeypatch, capsys): monkeypatch.chdir(shared.dumb_text_viewer) cli(["fedora"]) capture = capsys.readouterr() assert "Built 1 artifact:\n" in capture.out monkeypatch.chdir(shared.ubrotli) - cli(["fedora"]) + monkeypatch.setenv("GNUPGHOME", str(shared.gpg_home)) + artifacts = cli(["fedora", "--gpg-signing-id=ED7C694736BC74B3"]) capture = capsys.readouterr() assert "Built 3 artifacts:\n" in capture.out + rpm_info = _docker.run(Fedora.base_image, + ["rpm", "-qpi"] + ["/io/" + i.name for i in artifacts.values()], + volumes=[(artifacts["main"].parent, "/io")]).output + assert rpm_info.count("ED7C694736BC74B3".lower()) == 3 + with pytest.raises(SystemExit, match='^Error: Architecture "ppc64le" '): cli(["fedora", "--architecture=ppc64le"]) diff --git a/tests/test_manjaro.py b/tests/test_manjaro.py index 1db8d47..5110ba2 100644 --- a/tests/test_manjaro.py +++ b/tests/test_manjaro.py @@ -12,14 +12,6 @@ class TestCommon(shared.Base): package_install = "pacman -Sy --needed --noconfirm glibc" -def test_dumb_text_viewer(): - self = Manjaro(Project.from_root(shared.dumb_text_viewer)) - self.generate() - packages = self.build() - self.test(packages["main"]) - self.update_artifacts_json(packages) - - def test_mirror_detection(monkeypatch): with contextlib.suppress(FileNotFoundError): (cache_root / "manjaro-mirror").unlink() @@ -40,6 +32,20 @@ def test_mirror_detection(monkeypatch): test_multiarch = shared.qemu(Manjaro) +def test_dumb_text_viewer_signing(monkeypatch): + monkeypatch.setenv("GNUPGHOME", str(shared.gpg_home)) + self = Manjaro(Project.from_root(shared.dumb_text_viewer), None, "ED7C694736BC74B3") + self.generate() + packages = self.build() + self.test(packages["main"]) + self.update_artifacts_json(packages) + + # Test that GnuPG is running under C.UTF8 locale instead of C + container = _docker.run(self.build_builder_image(), ["gpg", "--list-secret-keys"], + volumes=[(shared.gpg_home, "/root/.gnupg")]) + assert "🎩" in container.output + + def test_fussy_arch(): self = Manjaro(Project.from_root(shared.fussy_arch)) assert "\narch=(aarch64)\n" in self.pkgbuild() diff --git a/tests/test_opensuse.py b/tests/test_opensuse.py index ceb9b51..0ffcb84 100644 --- a/tests/test_opensuse.py +++ b/tests/test_opensuse.py @@ -51,15 +51,6 @@ def test_test_command(): "xvfb-run $python -m unittest foo\n$python -c ''\n}" -def test_unittest(): - self = polycotylus.OpenSUSE(polycotylus.Project.from_root(shared.bare_minimum)) - self.generate() - rpms = self.build() - assert len(rpms) == 4 - container = self.test(rpms["main"]) - assert "Ran 1 test" in container.output - - def test_ubrotli(): self = polycotylus.OpenSUSE(polycotylus.Project.from_root(shared.ubrotli)) self.generate() @@ -105,3 +96,20 @@ def test_poetry(): python_version = polycotylus.OpenSUSE.python_version().rsplit(".", maxsplit=1)[0] script = container.file("/usr/bin/print_hello-" + python_version).decode() assert "python" + python_version in script.splitlines()[0] + + +def test_unittest(monkeypatch): + monkeypatch.setenv("GNUPGHOME", str(shared.gpg_home)) + self = polycotylus.OpenSUSE(polycotylus.Project.from_root(shared.bare_minimum), + None, "ED7C694736BC74B3") + self.generate() + rpms = self.build() + assert len(rpms) == 4 + container = self.test(rpms["main"]) + assert "Ran 1 test" in container.output + + rpm_info = polycotylus._docker.run( + polycotylus.OpenSUSE.base_image, + ["rpm", "-qpi"] + ["/io/" + i.name for i in set(rpms.values())], + volumes=[(rpms["main"].parent, "/io")]).output + assert rpm_info.count("ED7C694736BC74B3".lower()) == 3 diff --git a/tests/test_void.py b/tests/test_void.py index 51e2320..bb6c578 100644 --- a/tests/test_void.py +++ b/tests/test_void.py @@ -2,9 +2,13 @@ import io import os import re +from pathlib import Path +import contextlib +import pytest import pyzstd +from polycotylus._exceptions import PolycotylusUsageError from polycotylus._project import Project from polycotylus._void import Void, VoidMusl import shared @@ -68,7 +72,34 @@ def test_kitchen_sink(monkeypatch): self.update_artifacts_json(packages) -def test_poetry(): +def test_signing_poetry(monkeypatch, tmp_path): + with contextlib.suppress(FileNotFoundError): + (shared.poetry_based / ".polycotylus/void/musl/python3-poetry-based-0.1.0_1.x86_64-musl.xbps.sig2").unlink() + import polycotylus.__main__ + keys = Path(__file__).with_name("void-keys").resolve() + monkeypatch.chdir(shared.poetry_based) + polycotylus.__main__.cli(["void:musl", "--void-signing-certificate", str(keys / "unencrypted-ssl.pem")]) + repodata = shared.poetry_based / f".polycotylus/void/musl/{Void.preferred_architecture}-musl-repodata" + with tarfile.open("", "r", io.BytesIO(pyzstd.decompress(repodata.read_bytes()))) as tar: + with tar.extractfile("index-meta.plist") as f: + signing_info = f.read() + assert b"rsa" in signing_info + assert (shared.poetry_based / ".polycotylus/void/musl/python3-poetry-based-0.1.0_1.x86_64-musl.xbps.sig2").exists() + self = VoidMusl(Project.from_root(shared.poetry_based)) - self.generate() - self.test(self.build()["main"]) + for key in keys.glob("*.pem"): + self.private_key = key + + with pytest.raises(PolycotylusUsageError, + match=r'.*an IsADirectoryError\(\) whilst .* file ".*void-keys"'): + self.private_key = keys + + broken_permissions = tmp_path / "foo" + broken_permissions.touch() + broken_permissions.chmod(0) + with pytest.raises(PolycotylusUsageError, match="PermissionError"): + self.private_key = broken_permissions + + for name in ("pgp", "pgp-armor", "unencrypted-ssh.pem.pub"): + with pytest.raises(PolycotylusUsageError, match="Invalid"): + self.private_key = keys / name diff --git a/tests/void-keys/README.rst b/tests/void-keys/README.rst new file mode 100644 index 0000000..f913f58 --- /dev/null +++ b/tests/void-keys/README.rst @@ -0,0 +1,7 @@ +These are fake Void signing keys for testing. There is a key for each of the +four combinations of generating with versus without a password and using +``openssl`` versus ``ssh-keygen``. There are also PGP and public keys which may +be understandably mistaken for compatible inputs to ``polycotylus``\ 's +``--void-signing-certificate``. + +For the keys with passwords, the password is just ``password``. diff --git a/tests/void-keys/encrypted-ssh.pem b/tests/void-keys/encrypted-ssh.pem new file mode 100644 index 0000000..4dc37a6 --- /dev/null +++ b/tests/void-keys/encrypted-ssh.pem @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,B9F8C4CB3ABC18ED9761E5E641E596F7 + +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 +-----END RSA PRIVATE KEY----- diff --git a/tests/void-keys/encrypted-ssh.pem.pub b/tests/void-keys/encrypted-ssh.pem.pub new file mode 100644 index 0000000..23b05d7 --- /dev/null +++ b/tests/void-keys/encrypted-ssh.pem.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCak0i+YKYG1OFKJZnmri2yglDdeuM5kYtcPs8u7ew3CCcMxjwUo/Zr3WCQo/uCaTx0W8O9SS4aG+c+LDVcZZYnV8iPBQjNxjgB2PYhAxM/AWQaTm8FeBZzJlhRnrKEHu9el7o1JcQBndjk6N9wREaXhqIq115FaQ253KiTwnHBT1ivxQO1fSMfg0HiTOIK0UZPAmZtSmagEqVOBVUEgpzqwTQyOErbuLVyzhUvPnqqssaz2tyjCzNDk3+951qZ8QXj0pfMSP5ESWyKwYBapsGnyOl8PZFk2zdKW59qnwKtM5QQc0QTPPwz1g9KLrrmVeY+aGgp37/nE6wyqH3l6Zz4CDqbiD8zu4in2E5dSmrkNXt0S82LZee75gggiKrxZgMsrPyO8v8h3cA8FvUSRjdPFwktRLodYHj/Q3oavvXiABwzVIs7mbc9q27R7vDorynBC/S5r4XBAaUv0lZvfmu/I219m62OojOl7mU/Lau//aPy/chjGPNV7TyjbWsQqu2CbHDfIn0tkk54UpGg2+djPJJOgSVP7bYFZWKL5/3poHBYmIMNdIqwjqbqUedpsL1DquQqkXoIh2KnmuUCzkguHUEEHJRgzpphQc28DBCJWE20DoCcbQLBr/hP2C0QwGgsLRnERS4PaP50RImh8q3QUEZBQgKEndlM9yS8QQt13w== brenainn@manjaro-2212 diff --git a/tests/void-keys/encrypted-ssl.pem b/tests/void-keys/encrypted-ssl.pem new file mode 100644 index 0000000..6de706c --- /dev/null +++ b/tests/void-keys/encrypted-ssl.pem @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +Owtw8l05W6LsRdasvkxQ/g== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/tests/void-keys/pgp b/tests/void-keys/pgp new file mode 100644 index 0000000..b845a67 Binary files /dev/null and b/tests/void-keys/pgp differ diff --git a/tests/void-keys/pgp-armor b/tests/void-keys/pgp-armor new file mode 100644 index 0000000..90c62b3 --- /dev/null +++ b/tests/void-keys/pgp-armor @@ -0,0 +1,81 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +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 +=lq5m +-----END PGP PRIVATE KEY BLOCK----- diff --git a/tests/void-keys/unencrypted-ssh.pem b/tests/void-keys/unencrypted-ssh.pem new file mode 100644 index 0000000..ab7af4a --- /dev/null +++ b/tests/void-keys/unencrypted-ssh.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/tests/void-keys/unencrypted-ssh.pem.pub b/tests/void-keys/unencrypted-ssh.pem.pub new file mode 100644 index 0000000..b8a8bec --- /dev/null +++ b/tests/void-keys/unencrypted-ssh.pem.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQChLSVyI3pSx0QNK0c5rTkP9zvbZ+0pdxYlzBqBRWBGrNSkPFtIK2fTQuVxeAa585d5dLHsfVrssp6pVBcj3PKJAl/GBmfJczQRCVsgVGpsEQzq+9Ml8iPvgzpB2IXtxhKcz0sM8laA9zP5mcLH6+XceS+/5L34v2umt69+5yXHLa4s268XZ8j2v/Fn/kBmh72heMJmGqfaIryN4Mv+jogWwRVChGyqF4DsXGw7um/QbInX22lABuqK1UtCdGuPGU8xmPnu64AY/hOnf1LLZfUTK0jvsjinhiyrKXNaKMYDD1YWOE6VuHEtWQmWNjv301K4+pjO3Yjz4AR1k1vsaIqkcypHO9I8QN/bB/otYAgr3FnYtTiNzTYjbg36jdh0wRssxCOLx99oLyqmUdQtm8WixHWnrdr0llNBhOdiJEWQkdTxJygT2SvaG/1uIVpbIxWVtXHGBwUV8+NtxC8zO1ec+AJrhFvQLrYBIiJBvmr2FL7udeIGENhDRqgSnQ4Jri5gjRWhEJAembpXtQSDEpMR/Sr387NgCbiK2YsoH8qyXpW2i3KYIaghOEY/6pRBiCXOaV3s/PcsS+Xd1VWgQkqHa/jsSPKtWksgJBR5E1eO6nivAbMTSrtvupZ+yrlE4bdaBB21UNmg+UhJoGPX/xFvwgGeUWEbwM49dj5P1t1B7w== brenainn@manjaro-2212 diff --git a/tests/void-keys/unencrypted-ssl.pem b/tests/void-keys/unencrypted-ssl.pem new file mode 100644 index 0000000..f894550 --- /dev/null +++ b/tests/void-keys/unencrypted-ssl.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +yvM6kYI0ukg1cdXjaj1zvU7M6a2q +-----END PRIVATE KEY-----