Plugin installation¶
Plugins are distributed as bomify packages whose SBOM lists the binaries
as bomify-plugin components (plugin.PurlType), one per platform:
pkg:bomify-plugin/oci@v1.2.0?os=linux&arch=amd64, with os/arch in
GOOS/GOARCH terms (plugin.Binary). bomify handles this purl type
itself:
- Publishing (
bomify build):plugin.PullBinarycopies the binary from the component'sdistributionexternal reference (a local path orfile://URL, relative to the SBOM) into its layer directory. It sharesplugin.Pull's bookkeeping, so reuse, concurrency, and hash verification behave the same;--check(plugin.CheckBinary) only hashes the source. - Installing (
bomify plugin install <name>,internal/plugin/install): pulls<registry>/<name>:<version>(--registrydefaults toghcr.io/alejandro-velasco/bomify/plugins) into a staging directory insideplugins/, never recording it as a local package.pull.PullLayersdownloads only this machine's OS/arch binaries. Each binary is checked against its declared SHA-256, and only once all pass are they renamed intoplugins/and recorded ininstalled.json. Any failure installs nothing.
Verification (pluginInstallPolicy, cmd/plugin.go)
fails closed: a package installs only if its signature verifies, against
--verify-option (always with bomify-plugin-sigstore) or the matching
trust rule, or if it's named by digest (<name>@sha256:...). bomify has
no built-in signer; users trust the release workflow's identity through
a trust rule, and bootstrap bomify-plugin-sigstore itself by digest.
Every binary must also declare a SHA-256. --verify=false drops the
signature requirement only; a mismatched checksum still fails. bomify
plugin list shows every installed binary and its installed.json
entry.
Contract versions¶
bomify refuses a plugin that doesn't speak the
contract version
it needs, which is pkg/plugin's (ContractVersions), both when installing
it and when calling it:
- Installing: each binary's component records the versions it
speaks as the
land.bomify.plugin.contractsproperty, e.g.{"component":1}, whichhack/pluginpackagesgets from the plugin'scontractsubcommand at release.Installchecks it once the binary is staged, before its SHA-256, withplugin.CheckCompatible: every contract bomify speaks that the binary records must be at bomify's version, since bomify can't yet tell which one it will be called through. A binary recording none predates contract versions and is refused. - Calling:
plugin.Findtakes the contract its caller is about to use, and before returning the binary asks it withcontractand checks that contract's version (plugin.CheckOffered). Each binary is asked once per run, cached by path, size, and modification time. A binary that can't answer predates contract versions, so it's refused with an upgrade hint, as is one installed some other way that speaks the wrong version.
Either error names the plugin, the versions it offers, and the version
bomify needs, and wraps plugin.ErrIncompatible. An incompatible
bomify-plugin-sigstore can't verify its own replacement, so
pluginInstallPolicy then only accepts a package pinned by digest, as
when bootstrapping it; any other failure to find it is an error.
Releases¶
plugins.yml
builds every first-party plugin for every platform, each in a job of
its own, whatever its language (see
Plugin languages).
make plugin-packages (hack/pluginpackages)
packages those binaries, compiling nothing, and writes each plugin's
SBOM (plus a docker alias in oci's). make push-plugin-packages
(hack/push-plugin-packages.sh) pushes
each as <registry>/<kind>:<version> and :latest, signed, with its
build provenance attached and signed the same way, and
writes the pinned references to plugin-digests.txt.
release.yml runs only after Build
passes on a release branch (alpha-release or beta-release; main
joins at the first GA release), in three jobs so that only one can sign and that one
runs no npm code:
release: semantic-release (.releaserc.json) tags, publishes the GitHub release, and pushes the image. It has noid-tokenpermission.sign-plugins: builds and pushes the plugin packages withPLUGIN_SIGN_KEYLESS=true, the only job withid-token: write. Before each push it fetches a fresh GitHub OIDC token (they expire in minutes) and passes it tobomify-plugin-sigstoreasSIGSTORE_ID_TOKEN, and setsBOMIFY_INVOCATION_IDto the run's URL. Fulcio issues a certificate forhttps://github.com/alejandro-velasco/bomify/.github/workflows/release.yml@refs/heads/main, logged in Rekor. There's no key to store or leak.publish-digests: attachesplugin-digests.txtto the release.
Actions are pinned to commit SHAs and npm packages to exact versions.
Changing release.yml, hack/, the Makefile, or the sigstore plugin
on main controls what that signature vouches for, so
CODEOWNERS requires the owner's review for them.