Skip to content

ci: build, sign and publish the ISO and [colony] from a release workflow - #7

Open
MotherSphere wants to merge 18 commits into
mainfrom
ci/signed-release-pipeline
Open

MotherSphere wants to merge 18 commits into
mainfrom
ci/signed-release-pipeline

Conversation

@MotherSphere

Copy link
Copy Markdown
Member

Releases of the installation image and of the [colony] repository move from two interactive scripts on the maintainer's machine to .github/workflows/release.yml, driven by release-please like every other Colony program.

Release chain

  • release-please (release-type: simple, bootstrap-sha = the adoption commit 48cd631, so the changelog starts there). The manifest starts at 0.1.0: the first release-please release is the next version after it. CHANGELOG.md is written by release-please only. Merging its pull request tags vX.Y.Z, and the release is held as a draft until the end.
  • build-packages builds only what [colony] does not serve yet, and refuses a version older than the published one (tools/pending-packages.sh). That replaces repo-add --prevent-downgrade, which never guarded anything: the old publish script deleted the database before calling it.
  • source packs the GPL corresponding source of what this repository adds to the image: the repository at the tag, every [colony] package as makepkg --allsource packs it, and the pinned installer inputs.
  • sign-packages and sign-iso are the only jobs that hold a key. They check out nothing and build nothing. sign-packages signs the new packages, the [colony] database built on the published one after verifying it, and the colony-offline repository the image carries for the installer's firewall step.
  • build-iso runs iso/build.sh with no key: it takes the signed [colony] database and the signed offline repository from the signing job, checks both signatures, and leaves the image, its sha256 and its package list for sign-iso. It refuses an image name that is already published, and an image over 1.9 GiB.
  • smoke gates publication: tools/test-repo.sh on the very databases about to be published, the live image booted under KVM and checked on its serial console, then an offline install from the image through the installer's own pages (tools/vm-headless.py autoinstall, no network card), whose first boot must run the firewall it got from the medium (check-firewall).
  • publish verifies every asset against colony.gpg at the tag, uploads the image assets, colony.gpg and the source bundle to the draft, updates the rolling repo release (packages first, databases last, superseded packages removed), adds the "Verify the image" and "Source code" notes, then publishes the draft.

The shared ed25519 sign-and-publish.yml is not called: its <asset>.sig would take the names pacman and gpg --verify read OpenPGP signatures from. releases.md makes OpenPGP the contract for distribution artefacts; the workflow keeps the shared workflow's isolation contract instead. The reasoning is in the new ADR-0010, which supersedes the publication part of ADR-0008.

A pull request that touches the workflow or the scripts it runs gets the same chain as a dry run, signed with a throwaway key and published nowhere. A dry-run image also carries that throwaway key in its pacman keyrings, so that the installer trusts the offline repository it signed.

Fails closed until the key is in place

The signing key is the ARCH_COLONY_GPG_SIGNING_SUBKEY secret of a release environment: the signing subkey alone (gpg --export-secret-subkeys --armor <subkey>!), with ARCH_COLONY_GPG_PASSPHRASE if the export keeps one. Until it exists, a release stops at sign-packages with an error saying so, nothing is signed or published, and the tag stays a draft. The signing jobs also refuse a secret that carries the certification primary key, or a key that is not a subkey of 5CD2 FCA1 3E69 1C65 A354 780D 80C1 18F7 74E6 C43F. Once the secret is added, a failed release is finished with gh workflow run release.yml --ref vX.Y.Z -f tag=vX.Y.Z.

Other changes

  • iso/publish.sh and repo/publish.sh are removed. repo/genkey.sh stays, documented as the offline tool that created the key.
  • README: ## Code signing policy (what is signed, keys, who signs and where, how to verify, source code) and ## Privacy (every host the image and an installed system contact).
  • SECURITY.md: the release workflow is in scope, and the subkey compromise procedure goes through CI.
  • tools/README.md, iso/README.md and repo/README.md describe the release checks, the keyless image build and the VM install.

Testing

CFC 0.5.0, 0.7.0 and 0.8.0 each carry security fixes; [colony] served 0.2.2.

- Install the polkit actions 0.8.0 asks about before the app or tray may
  pause, resume, import rules or store an Allow that names no program, and
  carry upstream's new optional dependencies (polkit, bubblewrap, libbpf).
- Rewrite the scriptlet for the 0.8.0 control model (sudo cfc for changes,
  restart the app and tray after an upgrade) and for the table that loads
  before the daemon and is required by the network managers. post_upgrade
  re-enables an enabled table so those links exist, and leaves the running
  units alone: a 0.8.0 table under an older daemon would queue every loopback
  flow, resolved's stub included, to a daemon that prompts for them.
- colony-firewall-defaults drops its nft drop-in. With the 0.7.0 units it
  closed an ordering cycle (table, daemon, seed, table) that systemd cannot
  break, so none of the three would start. The seed now runs before
  network-pre.target, after the daemon, and the package needs CFC >= 0.8.0.
- colonyctl status reports the table on its own: loaded with no daemon, it
  refuses every new connection without a prompt.
Partition fixes (/boot no longer marked as the EFI partition, which could
break systemd-gpt-auto-generator; current KPMcore) and Wayland keyboard input
in QML modules. Both downstream patches apply unchanged, and the users and
bootloader module defaults the installer overrides are identical to 3.4.2's.
The firewall reached the target only through a try_install from [colony]. An
offline install logged the failure as a warning and rebooted with networkd
and resolved enabled and no firewall.

The medium now carries colony-firewall-defaults, colony-firewall-control and
colony-firewall-control-ebpf as a small repository, with the signatures
repo/build.sh made and a colony-offline database that iso/offline-repo.sh
signs at build time. The cleanup step installs them into the target through
a pacman.conf that knows only that repository and requires every signature,
before it enables networking, checks that the three units are enabled, and
stops the install otherwise. Nothing installs it on the live session.

tools/test-repo.sh assembles that repository and installs through its
pacman.conf in CI, and refuses it unsigned. tools/vm-headless.py boots
without a network card (--offline) and checks the installed firewall through
the guest agent (check-firewall).
iso/build.sh signs nothing when COLONY_SIGNING_KEY is unset and builds from
the signed database repo/out already holds, so the release workflow's build
job never needs a key. It writes the image's package list next to it.

tools/test-repo.sh can test a prebuilt signed database (COLONY_PREBUILT_DB=1),
tools/pending-packages.sh names the packages newer than what [colony] serves
and refuses a downgrade, and tools/vm-headless.py smoke boots an image on a
serial console and checks the live system.
namcap's split PKGBUILD rule: makepkg -s installs only the global arrays, so
the depends of package_colony-firewall-control() belong in makedepends too.
release-please tags vX.Y.Z; the tag builds the packages newer than what
[colony] serves, signs them and the databases in a job that checks out
nothing, builds the image from that signed repository, signs the image in a
second isolated job, smoke-tests the repository and a KVM boot, then verifies
every signature against colony.gpg before it uploads the image and updates
[colony]. Only the signing subkey is in CI, in the release environment.

The hand publishing scripts are gone. The README gains its Code signing policy
and Privacy sections, and ADR-0010 supersedes ADR-0008's publication part.
Write the build artefacts inside the workspace rather than under runner.temp,
which names the host path in a container job, and avoid word splitting that
shellcheck would flag.
iso/build.sh without a key takes repo/out/offline, the colony-offline
repository sign-packages signed, after checking its signature. The
release smoke test runs test-repo.sh on that repository too, and a dry
run image trusts its throwaway key so that the installer can use it.
…ishing

tools/vm-headless.py autoinstall steps through the installer with the
keyboard, following its step list on the screen and its log, onto an
empty disk with no network card. The release smoke job then boots the
installed disk and runs check-firewall.
…eline

# Conflicts:
#	iso/README.md
#	iso/build.sh
#	tools/README.md
#	tools/test-repo.sh
#	tools/vm-headless.py
qemu-hw-display-virtio-vga does not pull in qemu-hw-display-virtio-gpu,
without which QEMU crashes at start with -vga virtio, and -daemonize turns
that crash into a bare exit status 1. Waiting for the live system to power
off also counts a zombie as gone: a container's init reaps nothing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant