Distributions
A distribution is a build that generates values. In the build system, metadata about distributions can be collected in the values.jsonc files along with attestations:
- An attestation is a cryptographically verifiable statement (ex. "the build produced bundle A at time T") that is signed by a human (ex. you) or a machine (ex. GitHub Actions).
To increase supply chain security guarantees, the build system will reject assets and objects that are produced by humans or machines without attestations that you have explicitly trusted.
The following sources of attestation are recognized:
- A human can sign a build by using an OpenBSD signify key.
- GitHub Actions can sign a build using one of two SLSA security levels:
Unfortunately, GitLab does not yet provide a minimal level of attestation.
The build system maintains a trust store, with the dk OpenBSD signify key for the CommonsBase_Std packages as the only trusted entity by default.
Distributions are sealed
Section titled “Distributions are sealed”A published distribution is sealed: the exact set of value versions it contains (its bundles, forms and objects, each at a fixed version) is determined when the distribution is built and is signed by the distribution's attestation.
When a consumer imports a distribution, only the value versions that distribution sealed are resolvable. Requesting a module the imported distribution did not publish must fail. Requesting different versions of a module than the distribution sealed must also fail.
Sealing module versions provides the following security feature:
- The signify signature and SLSA provenance attest the precise values the distribution was built with. The attestation guarantees a consumer receives exactly the version that were attested.
- Because a consumer can only build and use the value versions the trusted signer explicitly attested, a compromised mirror, a tampered cache, or a later malicious build cannot introduce an unpublished or substituted module into the consumer's build graph.
A package that already has published releases cannot have a module added to an existing release: the module must be introduced by building and publishing a new distribution version.
Adding to the sealed set
Section titled “Adding to the sealed set”A new distribution version whose MAJOR.MINOR is strictly higher than the
latest sealed release may add to the sealed set:
- a brand-new module that no sealed release published, or
- a strictly higher
MAJOR.MINORmodule version of an already-sealed module.
Already-sealed (module, version) pairs are immutable for every version bump.
Patch-only releases (ie. the same MAJOR.MINOR) must not change the
sealed set.
Distributed Value Stores
Section titled “Distributed Value Stores”A distribution includes a .zip file of some or all of the value store. Entries in the zipfile must be ./{value_id};
for example, ./vnmfdhn7lw4wepx2qiunrmgm4o5lx4wwsf2yfj7xyxggkg5kdsltq.
The following values must be present:
- the "j" values file for any values.json with a form or bundle having a library identical to the distribution library
id - the "w" parsed values ast for any values.json with a form or bundle having a library identical to the distribution library
id - the "o" object file for any object having a library identical to the distribution library
id
The following values will be ignored if present:
- the "c" constants
A unoptimal implementation can simply zip up the value store directory, but done frequently that leads to wasted bandwidth, storage and lengthier builds.
An optimal implementation only includes the necessary values.
OpenBSD signify keys
Section titled “OpenBSD signify keys”The security policy is described in Securing OpenBSD From Us To You. Signify builds keys are automatically created specific to the user (you) on a machine. The private keys should not be shared except among a small set of trusted co-signers (ex. your manager and a peer).
Any OpenBSD signify key and any GitHub repository encountered by the build system is automatically denied. However, you are prompted if you want to accept or deny the key or repository, with the default as deny.
When accepting an OpenBSD signify public key embedded in a values.json build file, the
- accompanying continuations that were signed with the private key are accepted. These continuations are public keys for the next version, and imported once and never overwritten.
- accompanying builds that were signed with the private key are accepted
- all builds signed with the private key that with packages that share the major + minor parts of the accompanying build are accepted
Once a major and minor component of a package has been signed, no lower versioned distribution key can claim:
- that major and minor component
- any later versions to that major and minor component
That may be difficult to understand without an example. We'll use the Key Rotation example from Securing OpenBSD From Us To You, where OpenBSD is about to release OpenBSD version 5.6. They:
- have an existing public/private key pair for version 5.6
- have generated public/private key pairs for version 5.7 before 5.6 is released
- have generated public/private key pairs for version 5.8 before 5.6 is released
In the build system the version 5.6 would be a distribution; let's say OpenBSD_Std@5.6.0:
{ "distributions": { "id": "OpenBSD_Std@5.6.0", "producer": { "openbsd_signify": { // key from https://ftp.eu.openbsd.org/pub/OpenBSD/signify/openbsd-56-base.pub "public_key": "untrusted comment: openbsd 5.6 base public key\nRWR0EANmo9nqhpPbPUZDIBcRtrVcRwQxZ8UKGWY8Ui4RHi229KFL84wV" } }, "continuations": { "attestation": { "openbsd_signify": { // this is a (fake) signature of SHA256(plaintext), where // plaintext is `{"5.7":"...","5.8":"..."}` (see below) "signature": "untrusted comment: signature from signify secret key\nRWTAeKJJ1MTF3UpxzBCu6NaM6HPJNTj5CZ+M5XNJKNeEHBLQSsstzHGbSo8rPYNgw3Z98pN7WKiIwBIyRrKuIdKBRA6qlaci6wI=" } }, "continuations": { // https://ftp.eu.openbsd.org/pub/OpenBSD/signify/openbsd-57-base.pub "5.7": "untrusted comment: openbsd 5.7 base public key\nRWSvUZXnw9gUb70PdeSNnpSmodCyIPJEGN1wWr+6Time1eP7KiWJ5eAM", // https://ftp.eu.openbsd.org/pub/OpenBSD/signify/openbsd-58-base.pub "5.8": "untrusted comment: openbsd 5.8 base public key\nRWQNNZXtC/MqP3Eiu+6FBz/qrxiWQwDhd+9Yljzp62UP4KzFmmvzVk60" } } }}In the same distribution in values.jsonc we also must have at least one build:
{ "distributions": { "id": "OpenBSD_Std@5.6.0", "producer": { /* ... */ }, "continuations": { /* ... */ }, "build": { "attestation": { // this is a (fake) signature of SHA256(plaintext), where // plaintext is `{"modules":...,...}` (see below) "openbsd_signify": "untrusted comment: signed by key c078a249d4c4c5dd\nRWTAeKJJ1MTF3UpxzBCu6NaM6HPJNTj5CZ+M5XNJKNeEHBLQSsstzHGbSo8rPYNgw3Z98pN7WKiIwBIyRrKuIdKBRA6qlaci6wI=" }, "build_to_sign": { "modules": ["DkExe_Std.Form@1.0.202501010000"], /* other build fields described in the mlfront-values.json schema */ } } }}The OpenBSD_Std@5.6.0 public key has signed for DkExe_Std.Form@1.0.202501010000, but can also sign
for any DkExe_Std.Form@1.0.* (and no others!) since those all share the major and minor number of the DkExe_Std.Form in values.jsonc.
The OpenBSD_Std@5.7.0 public key can sign for either DkExe_Std.Form@1.0.* or a later version (ex. DkExe_Std.Form@1.1.*), but
cannot sign for both. That is, once a OpenBSD_Std@5.7.0 public key is seen to accompany DkExe_Std.Form@1.1.*, the association OpenBSD_Std@5.7.0 may-sign DkExe_Std.Form@1.1.* is stored in the trust store, and it can no longer
sign any other major + minor version.
Most important, the OpenBSD_Std@5.7.0 public key cannot sign for an earlier version (ex. DkExe_Std.Form@0.7.*) than OpenBSD_Std@5.6.0.
The net effect is a tendency of the trust store to increase versions over time, so that these OpenBSD signify keys have an implicit rotation policy as these versions increase. You choose how many keys you want to keep alive at any time (the current key plus the number of keys in your continuations), and do a rotation by doing a new build using a key from one of your continuations.
So, let's say you keep 2 continuations alive (plus the current key) like OpenBSD does. When you start using a key from one of your continuations (ex. OpenBSD_Std@5.7.0), you should will keep N-1 = 1 keys in rotation (ex. "continuations" should contain OpenBSD_Std@5.8.0 still) and create one new key (ex. "continuations" should have a new OpenBSD_Std@5.9.0 key).
GitHub SLSA Level 2
Section titled “GitHub SLSA Level 2”When accepting a GitHub repository (SLSA Level 2), the:
- accompanying builds attested by GitHub are accepted
- all builds attested by GitHub are accepted
The build system will download the GitHub CLI (using the default trusted CommonsBase_Std library) to do verification of the builds.
GitHub SLSA Level 3
Section titled “GitHub SLSA Level 3”When accepting a known, vetted GitHub Actions script (SLSA Level 3), the:
- accompanying builds attested by GitHub are accepted
- all builds produced by the GitHub Actions scripts that are attested by GitHub are accepted
The build system will download the GitHub CLI (using the default trusted CommonsBase_Std library) to do verification of the builds.
Distribution Scripts
Section titled “Distribution Scripts”🚧missing docs: make a section on cram tests independent of distributions
When writing distribution scripts:
-
Running one rule in a script module will bring in the entire script module. In particular, if a distribution script runs a function rule from a
*.values.luascriptmodule, the entire*.values.luascriptmodule is distributed. Function rules should follow theF_<Name>naming convention to avoid colliding with UI rule names in the same scriptmodule.It is best to run every function rule for lightweight testing during distribution.
-
Use
${CONFIG}for files in the cram test directory. -
Use
${RUNTIME}/<unique path to cram test>for -f and -d options since outputs are relative to the current dir likerun-function,dialog, andexec. It is set unique to the cram test. The<unique path to cram test>is to avoid race conditions on Windows where Windows Defender (etc.) may not make a file visible or rewritable immediately after creation. -
Declare each static rule input in the rule's
declareoutputresponse.