Document artifact origin and installer receipts

This commit is contained in:
2026-07-22 20:42:35 +02:00
parent 9c0650b2ed
commit 4e42911477
2 changed files with 131 additions and 36 deletions

View File

@@ -389,7 +389,7 @@ that observation to the assessment and signed catalog:
--output /var/tmp/govoplan-fit-review.json --output /var/tmp/govoplan-fit-review.json
``` ```
The collector follows the strict version `0.3.0` The collector follows the strict version `0.4.0`
[`installed-composition-evidence.schema.json`](installed-composition-evidence.schema.json) [`installed-composition-evidence.schema.json`](installed-composition-evidence.schema.json)
contract. It enumerates all installed distributions whose normalized name starts contract. It enumerates all installed distributions whose normalized name starts
with `govoplan-`, compares the enabled assessed package and module-manifest with `govoplan-`, compares the enabled assessed package and module-manifest
@@ -419,6 +419,9 @@ For each distribution, the collector also:
package-index/unknown origins and malformed metadata; package-index/unknown origins and malformed metadata;
- compares a non-editable VCS commit with the assessment commit and, when one is - compares a non-editable VCS commit with the assessment commit and, when one is
present, the signed catalog `selected_units` commit; present, the signed catalog `selected_units` commit;
- derives two content identities from bytes it actually verifies: an
install-stable identity for immutable wheel-declared files and a full
installed-RECORD identity, while retaining only SHA-256 digests and counts;
- records only package/module identifiers, versions, bounded counters, hashes - records only package/module identifiers, versions, bounded counters, hashes
and stable error codes. It never writes direct URLs, paths, hostnames, and stable error codes. It never writes direct URLs, paths, hostnames,
usernames, exception text or file contents. usernames, exception text or file contents.
@@ -436,25 +439,31 @@ remain mutable even when their small editable-install `RECORD` is valid. Archive
or index artifacts without a hash anchored by the assessed release remain or index artifacts without a hash anchored by the assessed release remain
unanchored. A matching declaration is reported under unanchored. A matching declaration is reported under
`installed_source_provenance` as local consistency only, with `installed_source_provenance` as local consistency only, with
`release_origin_bound: false`. The separate `installed_release_origin` scope `release_origin_bound: false` until the separate origin proof succeeds. A signed
remains unchecked until an independently anchored artifact digest or signed catalog can contain `release.artifacts` identities computed directly from built,
installer receipt binds each installed payload to the signed catalog. The tool bounded wheel files. Wheels with installer-transformed scripts or headers are
does not accept a digest merely asserted inside the installed evidence document. marked `requires_installer_receipt`; catalog metadata alone cannot attest those
post-install bytes. A role-scoped installer receipt instead binds the exact
installed-evidence digest, full installed payload identities, catalog archive
digests and canonical signed-catalog digest. The tool never accepts an installed
hash merely because the installed evidence asserted it.
Only evidence produced in-process by `--collect-installed-evidence` is a local Only evidence produced in-process by `--collect-installed-evidence` is a local
observation, and it must be no more than five minutes old (with at most 30 observation, and it must be no more than five minutes old (with at most 30
seconds of future clock skew) at the selected verification time. Use seconds of future clock skew) at the selected verification time. Use
`--installed-evidence PATH` to compare evidence collected in a separate `--installed-evidence PATH` to compare evidence collected in a separate
environment. Imported JSON is unsigned and therefore always carries a blocker: environment. Imported JSON without a valid independent installer receipt
it can identify differences but cannot establish checked or valid installed carries a blocker: it can identify differences but cannot establish checked or
proof. A future signed collector-attestation format is required to cross that valid installed proof. A receipt signed by a separately provisioned installer
authority authenticates that exact imported evidence across the process
boundary. The proof scope records observation mode, collection/evaluation time, boundary. The proof scope records observation mode, collection/evaluation time,
freshness, and whether evaluation used current time or an explicit historical receipt authentication, freshness, and whether evaluation used current time or
override. Evidence input and output are bounded to 4 MiB. Collection loads an explicit historical override. Evidence input and output are bounded to 4
the `govoplan.modules` entry-point factories in order to read module IDs and MiB. Collection loads the `govoplan.modules` entry-point factories in order to
manifest versions; that executes installed GovOPlaN manifest code. Run it only read module IDs and manifest versions; that executes installed GovOPlaN manifest
inside the installation being assessed and with the same isolation expected for code. Run it only inside the installation being assessed and with the same
other installed-artifact acceptance checks. This collector does not observe isolation expected for other installed-artifact acceptance checks. This
collector does not observe
which modules a running service activated, migrations, configuration, health, which modules a running service activated, migrations, configuration, health,
or reference-journey behavior. Runtime activation therefore remains an explicit or reference-journey behavior. Runtime activation therefore remains an explicit
unchecked boundary. unchecked boundary.
@@ -495,14 +504,50 @@ the tool's deterministic canonicalization.
``` ```
With `--installed-evidence`, this command performs comparison and proof-binding With `--installed-evidence`, this command performs comparison and proof-binding
diagnostics only: the imported installed document is unsigned, so neither it nor diagnostics. Without an installer receipt, the imported document remains
the boundary claim becomes accepted proof. Direct in-process collection removes unsigned, so neither it nor the boundary claim becomes accepted proof. Direct
that unsigned-import gap, but still establishes only local metadata consistency. in-process collection can match catalog-anchored artifact identities for wheels
No boundary claim is admitted by the current format because it has no separately without installer-transformed files. The installer-receipt flow below
anchored artifact digest or signed installer receipt. A future evidence contract authenticates a durable cross-process observation and is required for transformed
must bind such an independently trusted release-artifact receipt to the installed script/header payloads.
evidence digest; a signed collector-attestation format should also make that
exchange durable across processes. Issue a receipt only in the installation process performing the live collection.
The command refuses evidence supplied from a file and first validates the
assessment, independently trusted signed catalog, composition and RECORD bytes:
```bash
./.venv/bin/python tools/assessments/installer-receipt.py \
--assessment /srv/govoplan/assessment.json \
--catalog /srv/govoplan/catalogs/stable.json \
--keyring /srv/govoplan/catalogs/keyring.json \
--trusted-keyring /srv/govoplan/trust/catalog-keyring.json \
--receipt-id install:production:20260722 \
--signing-key installer-production=/run/secrets/installer-ed25519.pem \
--python-artifact govoplan-core=/srv/govoplan/staged/govoplan_core-0.1.13-py3-none-any.whl \
--python-artifact govoplan-campaign=/srv/govoplan/staged/govoplan_campaign-0.1.10-py3-none-any.whl \
--evidence-output /srv/govoplan/evidence/installed.json \
--receipt-output /srv/govoplan/evidence/installer-receipt.json
```
Repeat `--python-artifact PACKAGE=/exact/consumed.whl` for every enabled
GovOPlaN distribution. The issuer reopens each exact wheel, verifies its archive
and payload identities against the signed catalog, and refuses a different
build with the same name and version. Receipt issuance must follow collection
within five minutes, and live verification must still fall inside that bounded
window; retain the pair for a reproducible historical review at its explicit
verification time.
Verify that durable pair with its separately managed trust root:
```bash
./.venv/bin/python tools/assessments/capability-fit.py \
--catalog /srv/govoplan/catalogs/stable.json \
--keyring /srv/govoplan/catalogs/keyring.json \
--trusted-keyring /srv/govoplan/trust/catalog-keyring.json \
--installed-evidence /srv/govoplan/evidence/installed.json \
--installer-receipt /srv/govoplan/evidence/installer-receipt.json \
--installer-authority-keyring /srv/govoplan/trust/installer-authorities.json
```
Target-environment and production-approval claims must use the assessment's Target-environment and production-approval claims must use the assessment's
`deployment_profile.id` as `subject_id`. External-provider claims require the `deployment_profile.id` as `subject_id`. External-provider claims require the
@@ -510,11 +555,16 @@ operator to supply a bounded opaque expected subject with
`--expected-external-provider-subject`; without it, such a claim remains `--expected-external-provider-subject`; without it, such a claim remains
unchecked and blocks. Expected and observed IDs are retained in proof scope. unchecked and blocks. Expected and observed IDs are retained in proof scope.
The authority keyring is a governance trust root. Do not download or generate it Both authority keyrings are governance trust roots. Installer receipt keys use
from the proof bundle being checked. The checker rejects proof-authority public the strict
keys reused by either the published or independently trusted catalog keyring, [`installer-receipt-authority-keyring.schema.json`](installer-receipt-authority-keyring.schema.json)
and rejects the same proof public-key material assigned to multiple authority contract and may attest only `installed_release_origin`; their public material
IDs. A malformed key, an invalid or empty validity interval, or ambiguous key must not be reused by catalog or boundary-proof authorities. Do not download or
generate them from the proof bundle being checked. The checker rejects
proof-authority public keys reused by either the published or independently
trusted catalog keyring, and rejects the same proof public-key material assigned
to multiple authority IDs. A malformed key, an invalid or empty validity
interval, or ambiguous key
identity invalidates the supplied authority set. Authority `not_after` is identity invalidates the supplied authority set. Authority `not_after` is
exclusive. A target operator or approver must validate the referenced exclusive. A target operator or approver must validate the referenced
drill/result artifacts before signing. The rerun verifies the drill/result artifacts before signing. The rerun verifies the

View File

@@ -212,7 +212,9 @@ an alternative condition without `required_scopes` or `any_scopes`.
The catalog workflow panel can also operate on the same selected rows: The catalog workflow panel can also operate on the same selected rows:
- `Generate` creates a signed candidate below `runtime/release-candidates/`. - `Generate` creates a signed candidate in the operator's private XDG state
directory and records only an opaque candidate ID plus the canonical catalog
SHA-256 in durable run state.
- `Preview` validates a candidate and shows what would be copied into the - `Preview` validates a candidate and shows what would be copied into the
website repository. website repository.
- `Apply + Website Tag` requires `APPLY` in the confirmation field. - `Apply + Website Tag` requires `APPLY` in the confirmation field.
@@ -241,6 +243,30 @@ cannot be applied or published while any referenced source tag is absent or
inconsistent; the preview and API response list each repository and missing or inconsistent; the preview and API response list each repository and missing or
invalid ref so the operator can repair the exact source releases first. invalid ref so the operator can repair the exact source releases first.
Every selected Python release must also supply its exact built wheel. Candidate
generation computes the archive SHA-256 and an install-stable payload identity
from one bounded, regular-file descriptor and signs those values into
`release.artifacts`. Updating a Python version without a matching wheel removes
the stale identity. A source-only preview can still explain the gap, but apply,
commit, tag, and push fail closed until every selected Python unit has a matching
built-artifact identity. Repositories selected only for a meta/source tag do not
need a Python artifact.
Candidate directories and files are operator-owned `0700`/`0600` state. Before
any later release step consumes one, the executor re-resolves the opaque handle
below the configured root and re-hashes the signed catalog against its persisted
receipt. Shared roots, symlinks, channel path fragments, altered candidates, and
unowned files are rejected.
The server-owned wheel builds remain under the private candidate's `artifacts/`
directory. This slice signs their identities but does **not** upload the wheel or
add a download URL to the public catalog; the existing Git `python_ref` is source
provenance and rebuilding it is not an equivalent artifact. Keep the private
candidate until the exact wheels have been transferred through an approved
deployment channel, verified against `archive_sha256`, consumed by the installer,
and covered by its signed receipt. Public artifact transport and receipt-aware
candidate cleanup remain separate release-lifecycle work.
Read-only provenance checks derive a same-host HTTPS Git URL from conventional Read-only provenance checks derive a same-host HTTPS Git URL from conventional
SSH remotes when possible, with a non-interactive check of the configured remote SSH remotes when possible, with a non-interactive check of the configured remote
as fallback. Source tag creation and atomic publication always use the explicit as fallback. Source tag creation and atomic publication always use the explicit
@@ -269,18 +295,28 @@ Build a signed selective catalog candidate:
```sh ```sh
KEY_DIR="$HOME/.config/govoplan/release-keys" KEY_DIR="$HOME/.config/govoplan/release-keys"
ARTIFACT_DIR="$(mktemp -d)"
../govoplan-files/.venv/bin/python -m pip wheel \
--no-deps \
--no-build-isolation \
--wheel-dir "$ARTIFACT_DIR" \
../govoplan-files
./.venv/bin/python tools/release/release-catalog.py selective \ ./.venv/bin/python tools/release/release-catalog.py selective \
--repo-version govoplan-files=0.1.9 \ --repo-version govoplan-files=0.1.9 \
--python-artifact \
"govoplan-files=$ARTIFACT_DIR/govoplan_files-0.1.9-py3-none-any.whl" \
--channel stable \ --channel stable \
--catalog-signing-key "release-key-1=$KEY_DIR/release-key-1.pem" --catalog-signing-key "release-key-1=$KEY_DIR/release-key-1.pem"
``` ```
This writes candidate catalog/keyring files below `runtime/release-candidates/`, This writes candidate catalog/keyring files below
generates the browsable module directory below `modules/`, validates the signed `$XDG_STATE_HOME/govoplan/release-candidates/` (or
candidate with the module installer validator, and reports whether the candidate `~/.local/state/govoplan/release-candidates/`), generates the browsable module
catalog/keyring match the currently published channel. It does not publish to directory below `modules/`, validates the signed candidate with the module
the website repository. installer validator, and reports whether the candidate catalog/keyring match the
currently published channel. It does not publish to the website repository.
Regenerate the browsable module directory from an existing catalog/keyring: Regenerate the browsable module directory from an existing catalog/keyring:
@@ -295,8 +331,10 @@ Regenerate the browsable module directory from an existing catalog/keyring:
Preview publication of a reviewed candidate: Preview publication of a reviewed candidate:
```sh ```sh
CANDIDATE_ROOT="${XDG_STATE_HOME:-$HOME/.local/state}/govoplan/release-candidates"
./.venv/bin/python tools/release/release-catalog.py publish-candidate \ ./.venv/bin/python tools/release/release-catalog.py publish-candidate \
--candidate-dir runtime/release-candidates/stable-YYYYMMDD-HHMMSS \ --candidate-dir "$CANDIDATE_ROOT/stable-YYYYMMDD-HHMMSS" \
--channel stable --channel stable
``` ```
@@ -304,7 +342,7 @@ Apply the reviewed candidate into the website repository without pushing:
```sh ```sh
./.venv/bin/python tools/release/release-catalog.py publish-candidate \ ./.venv/bin/python tools/release/release-catalog.py publish-candidate \
--candidate-dir runtime/release-candidates/stable-YYYYMMDD-HHMMSS \ --candidate-dir "$CANDIDATE_ROOT/stable-YYYYMMDD-HHMMSS" \
--channel stable \ --channel stable \
--apply \ --apply \
--commit \ --commit \
@@ -315,7 +353,7 @@ Push is a separate explicit flag:
```sh ```sh
./.venv/bin/python tools/release/release-catalog.py publish-candidate \ ./.venv/bin/python tools/release/release-catalog.py publish-candidate \
--candidate-dir runtime/release-candidates/stable-YYYYMMDD-HHMMSS \ --candidate-dir "$CANDIDATE_ROOT/stable-YYYYMMDD-HHMMSS" \
--channel stable \ --channel stable \
--apply \ --apply \
--commit \ --commit \
@@ -323,6 +361,13 @@ Push is a separate explicit flag:
--push --push
``` ```
Publication validates the same in-memory catalog object that it writes; it does
not copy a path that can change after validation. On commit, the console reads
the catalog, keyring, and complete module directory back from the immutable Git
tree and requires byte-for-byte equality with those validated objects. Tags and
remote branch updates then reference that exact commit SHA rather than the
mutable worktree `HEAD`.
Published channels are expected below the public catalog base URL: Published channels are expected below the public catalog base URL:
- `https://govoplan.add-ideas.de/catalogs/v1/channels/stable.json` - `https://govoplan.add-ideas.de/catalogs/v1/channels/stable.json`