CEP about serving sigstore attestations in Conda repositories - #142
CEP about serving sigstore attestations in Conda repositories#142wolfv wants to merge 11 commits into
Conversation
| A package MAY have multiple attestations from different sources. Common scenarios include: | ||
|
|
||
| | Source | Purpose | | ||
| |--------|---------| | ||
| | Build system (e.g., GitHub Actions) | Proves the package was built from specific source code | | ||
| | Channel operator | Proves the channel accepted and published the package | | ||
| | Third-party auditor | Proves the package passed security review | |
There was a problem hiding this comment.
I would remove the "common scenarios" here, or maybe reword it as a list of examples of potential future usecases.
There was a problem hiding this comment.
this has not been addressed yet
evankanderson
left a comment
There was a problem hiding this comment.
I swung by, since this got posted to the OpenSSF #general channel, and I was curious. Feel free to disregard.
|
|
||
| 1. **Enables client verification**: Clients can fetch attestations alongside packages and verify them before installation. | ||
|
|
||
| 2. **Supports multiple attestations**: A single package may have multiple attestations (e.g., from the build system, from the channel on upload, from third-party auditors). |
There was a problem hiding this comment.
While this CEP describes the endpoint as "RESTful", it seems like it may be a read-only endpoint?
If you want to support additional attestations from third-parties, you'll need to figure out access control for users "vouching" for other packages. That seems like a diversion from the main goal of this CEP, so it might be worth calling out third-party auditors as a future capability that you don't want to prevent.
There was a problem hiding this comment.
While this CEP describes the endpoint as "RESTful", it seems like it may be a read-only endpoint?
These are not in contradiction, right?
If you want to support additional attestations from third-parties, you'll need to figure out access control for users "vouching" for other packages. That seems like a diversion from the main goal of this CEP, so it might be worth calling out third-party auditors as a future capability that you don't want to prevent.
Not necessarily? A third party could generate attestations, and a channel admin could upload them to their respective packages, without giving access to the third party. This section is characterizing the distribution mechanism, the "attestations from third-party auditors" is just an example of a type of attestation that may exist.
| "attestations": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570" | ||
| } | ||
| ``` |
There was a problem hiding this comment.
I'm assuming this is because of the discussion here: https://github.com/conda/ceps/pull/142/changes#r2816839339
It might be worth a sentence here to suggest the rationale:
| "attestations": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570" | |
| } | |
| ``` | |
| "attestations": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570" | |
| } | |
| \``` | |
| The signatures hash enables mirrors to detect and re-fetch signature bundles as they are added to the repo. |
(GitHub suggestions can't include the close-block, so I had to escape it. 😢 )
|
|
||
| Each attestation in the response MUST comply with [CEP 27]. Specifically: | ||
|
|
||
| 1. The in-toto statement's `subject[0].name` MUST match the artifact filename. |
There was a problem hiding this comment.
Does this need to be subject[0].name? I'm imagining that I might have a CI process which produces an attestation for all the artifacts produced in a single attestation, so I have N+1 outputs, rather than 2N outputs. Should 1. and 2. be changed to "One of the attestation subjects MUST match as follows: (a) subject[N].name MUST match the artifact filename AND (b) `subject[N].digest.sha256 MUST match the SHA256 hash of the artifact"?
There was a problem hiding this comment.
The reason for this is that having one attestation per artifact makes things simpler from a verification POV, and also prevents certain footguns (more context here)
| | `require` | `error` | Fail if attestations are missing or invalid | | ||
| | | `warn` | Log warning but continue if attestations are missing or invalid | | ||
| | | `ignore` | Silently continue (still verify if attestations exist) | |
There was a problem hiding this comment.
Is there a default value for require (or the other settings)? In the example, require is omitted for the "foobar" repository.
There was a problem hiding this comment.
I vote for making the 3 fields mandatory, with no default values
|
|
||
| 1. **Download package** from the channel | ||
| 2. **Fetch attestations** from `<package_url>.sigs` | ||
| 3. **Verify each attestation** against the configuration. |
There was a problem hiding this comment.
make explicit that the verification process is defined in CEP-27
| For offline verification, clients MAY cache `.sigs` files alongside packages in local repositories. | ||
| The Sigstore bundle format is self-contained and supports offline verification once the Sigstore trust root is available locally. | ||
|
|
||
| Note: clients MUST periodically update the sigstore trust root to ensure no keys were revoked. |
There was a problem hiding this comment.
the reason is more general, it's to ensure the client doesn't miss any trust root changes (which could be key revocation, but also new keys added)
| | --------------- | ------------------------------------------------------------ | | ||
| | `200 OK` | Attestations returned successfully (may be empty array) | | ||
|
|
||
| Channels that support attestations MUST always return `200 OK` with an empty array `[]`, even when the package does not exist. |
There was a problem hiding this comment.
My previous comment was about the ambiguity of defining a 404 error on the .sigs endpoint as "this package does not exist", when channels that don't implement this CEP would return 404 on the .sigs endpoint for every package.
I think we should return 404 when the package does not exist, but define the meaning of 404 as:
- For channels implementing this CEP, 404 means the package does not exist
- For channels not implementing this CEP,
.sigsalways returns 404
and make it clear that clients should not use the .sigs endpoint to determine if a package exists or not
| rules specified by any of the other all-capital terms MAY result in a warning, at discretion of the | ||
| implementation. | ||
|
|
||
| ## Abstract |
There was a problem hiding this comment.
Maybe we should update the abstract, since the CEP defines both the endpoint to distribute the attestations but also the new configuration clients should use to consume them.
| > More specifically, violations of a MUST or MUST NOT rule MUST result in an error. Violations of the | ||
| rules specified by any of the other all-capital terms MAY result in a warning, at discretion of the | ||
| implementation. |
There was a problem hiding this comment.
any particular reason for adding this?
| 1. **Discovery**: Clients learn from repodata alone whether a `.sigs` file exists, avoiding a network round-trip for packages without attestations. | ||
| 2. **Integrity**: Clients MUST verify that the fetched `.sigs` bytes hash to `sha256` before using the sidecar (see [Verification Workflow](#verification-workflow)). This prevents a mirror or intermediary from stripping or replacing attestations without detection. | ||
| 3. **Change detection**: When attestations are added after a package was first published, the channel publishes an updated `.sigs` file and updates the field. Mirrors and clients re-fetch the sidecar when the hash changes. | ||
| 4. **Resource bounds**: `size` allows clients to enforce a download limit before fetching. Clients MAY refuse to download sidecars larger than a locally configured limit; such a refusal MUST be handled like a retrieval failure (see [Configuration](#configuration)). |
There was a problem hiding this comment.
does this new size field add extra protection? AFAICT, the way the CEP says to use it is for clients to not download the .sigs file if the size field exceeds some limit.
What is it protecting against?
There was a problem hiding this comment.
I think it can help against accidentally downloading too large blobs (which could be a DOS for improper configured clients). Also helps with displaying a nice progress bar :) But not a huge security win.
There was a problem hiding this comment.
Are we talking about maliciously crafted blobs, which are big on purpose to cause a DOS? Or legitimate normal-sized blobs, which might be too big for a resource-constrained client?.
In the former case, the channel could check and reject attestations that exceed a reasonable size.
In the latter case, these are usually files in the tens of Kb. Is that still a concern?
|
|
||
| Each entry is a pattern matched against the certificate identity (the SubjectAlternativeName of the Sigstore signing certificate). Matching is case-sensitive and literal, except that `*` matches any sequence of characters, including `/`. | ||
| For example, `https://github.com/conda-forge/*` matches `https://github.com/conda-forge/numpy-feedstock/.github/workflows/build.yml@refs/heads/main` but not `https://github.com/conda-forge-evil/...`, because the literal prefix includes the trailing slash. | ||
| A future revision of this CEP MAY extend entries to additionally pin the OIDC issuer. |
There was a problem hiding this comment.
the configuration should include the issuer in some form. (issuerA, identityA) is different from (issuerB, identityA), and we should not allow both when identityA is specified.
Also, the paragraph below:
Sigstore verification binds each attestation to a signing identity. An attacker cannot forge attestations for identities they do not control, but an attacker who controls the distribution path can substitute attestations signed by an identity they do control. The
trusted_identitiespolicy is what turns bundle verification into a guarantee about who produced the package.
The policy is a guarantee about who produced the package only if we also check the OIDC issuer.
Co-authored-by: Travis Hathaway <travis.j.hathaway@gmail.com>
baszalmstra
left a comment
There was a problem hiding this comment.
A big gap I see with this proposal is how this should be handled in lock-files. Given that .sigs is not immutable should we store the attestation sha256 in the lock-file as well. But what happens if on the server/mirror the .sigs are updated? Should we store the original .sigs in the lock-file?
|
|
||
| ### Response Format | ||
|
|
||
| The `.sigs` file MUST contain a JSON array of one or more [Sigstore bundles][Sigstore Bundle]. Each bundle represents one attestation for the package. |
There was a problem hiding this comment.
Why an json array instead of a jsonl?
| The field is the single discovery and integrity mechanism for attestation sidecars: | ||
|
|
||
| 1. **Discovery**: Clients learn from repodata alone whether a `.sigs` file exists, avoiding a network round-trip for packages without attestations. | ||
| 2. **Integrity**: Clients MUST verify that the fetched `.sigs` bytes hash to `sha256` before using the sidecar (see [Verification Workflow](#verification-workflow)). This prevents a mirror or intermediary from stripping or replacing attestations without detection. |
There was a problem hiding this comment.
If the mirror or intermediary has access to the channel they can also just change the repodata itself no?
IMO lockfiles should lock the trusted identities, not the hashes of the attestations or |
This CEP adds specifications how to serve sigstore attestations in Conda repositories.
A preview implementation is available on beta.prefix.dev under the following URL:
https://beta.prefix.dev/wolf-channel/linux-64/signed-package-1.2.1-hb0f4dca_0.conda.v0.sigs