-
Notifications
You must be signed in to change notification settings - Fork 0
refactor: DependencyOrigin — one concept, no merging, clear siblings #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| package sdk | ||
|
|
||
| // PackageAttestation records a signed statement about how a package was built | ||
| // or published: an in-toto statement such as SLSA provenance, or a | ||
| // publish-time signature. | ||
| // | ||
| // Bomly does not fetch or verify attestations today. The type exists so a | ||
| // matcher that does can attach what it found without a model change, and so | ||
| // consumers can tell a verified statement from one that was merely present -- | ||
| // a distinction that matters more than the statement itself, and that is | ||
| // easily lost when provenance data is carried in untyped metadata. | ||
| type PackageAttestation struct { | ||
| // PredicateType identifies what the statement asserts, using the in-toto | ||
| // predicate vocabulary (for example "https://slsa.dev/provenance/v1"). | ||
| PredicateType string `json:"predicate_type,omitempty"` | ||
| // Source names the component or service that attached the statement, in | ||
| // the same style as PackageScorecard.Source. | ||
| Source string `json:"source,omitempty"` | ||
| // URL is where the statement can be fetched. | ||
| URL string `json:"url,omitempty"` | ||
| // Digest identifies the statement itself, so two fetches of one URL can be | ||
| // told apart. | ||
| Digest *Digest `json:"digest,omitempty"` | ||
| // Issuer is the identity that signed the statement -- an OIDC identity, a | ||
| // key id, or a registry account -- as reported by whatever verified it. | ||
| Issuer string `json:"issuer,omitempty"` | ||
| // Verified records that the component attaching this statement checked its | ||
| // signature. False means the statement was found but not verified, which is | ||
| // weaker evidence rather than evidence of tampering; consumers must not | ||
| // present an unverified statement as proof of provenance. | ||
| Verified bool `json:"verified,omitempty"` | ||
| } | ||
|
|
||
| // mergeAttestations folds incoming statements into p, keeping one record per | ||
| // distinct statement. Several components can attest to one package -- a build | ||
| // provenance statement from one, a publish signature from another -- so this | ||
| // unions rather than keeping whichever arrived first, the way vulnerabilities | ||
| // already do. When two records describe the same statement and either verified | ||
| // it, the merged record is verified: verification is a fact one component | ||
| // established, not an opinion. | ||
| func (p *Package) mergeAttestations(incoming []PackageAttestation) { | ||
| if len(incoming) == 0 { | ||
| return | ||
| } | ||
| for _, candidate := range incoming { | ||
| merged := false | ||
| for i := range p.Attestations { | ||
| if !p.Attestations[i].describesSame(candidate) { | ||
| continue | ||
| } | ||
| p.Attestations[i].absorb(candidate) | ||
| merged = true | ||
| break | ||
| } | ||
| if !merged { | ||
| p.Attestations = append(p.Attestations, candidate.Clone()) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // describesSame reports whether two records can be folded into one. Verification | ||
| // is a fact about a statement *and a signer*, so records fold only when they | ||
| // agree on the issuer -- or when one of them claims nothing that could be | ||
| // misattributed. | ||
| // | ||
| // A record with no issuer and no verification says only that the statement | ||
| // exists, which any other record for it already says, so it folds into | ||
| // anything. A record with no issuer that *was* verified is a real claim ("this | ||
| // was verified, signer unrecorded") and stays separate from a record naming an | ||
| // issuer: merging them would report that issuer as verified on the strength of | ||
| // a verification that may have been of someone else's signature. | ||
| func (a PackageAttestation) describesSame(other PackageAttestation) bool { | ||
| if a.key() != other.key() { | ||
| return false | ||
| } | ||
| switch { | ||
| case a.Issuer == other.Issuer: | ||
| return true | ||
| case a.claimsNothing(), other.claimsNothing(): | ||
| return true | ||
| default: | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| // claimsNothing reports whether a asserts anything beyond the statement's | ||
| // existence. | ||
| func (a PackageAttestation) claimsNothing() bool { | ||
| return a.Issuer == "" && !a.Verified | ||
| } | ||
|
|
||
| // absorb folds a record describing the same statement into a. Verification | ||
| // never moves between issuers: it travels only when this record claims nothing, | ||
| // in which case the other record replaces it wholesale. | ||
| func (a *PackageAttestation) absorb(other PackageAttestation) { | ||
| switch { | ||
| case a.claimsNothing(): | ||
| *a = other.Clone() | ||
| case other.claimsNothing(): | ||
| // Nothing to take. | ||
| case other.Verified: | ||
| // Same issuer, so the verification is this issuer's. | ||
| a.Verified = true | ||
| } | ||
| } | ||
|
|
||
| // attestationKey identifies one statement for deduplication. Issuer is | ||
| // deliberately absent: it is compared separately, because an unknown issuer is | ||
| // compatible with a known one while two known issuers are not. | ||
| type attestationKey struct { | ||
| source string | ||
| predicateType string | ||
| url string | ||
| digestAlgorithm DigestAlgorithm | ||
| digestValue string | ||
| digestSubject DigestSubject | ||
| } | ||
|
|
||
| // key returns a's deduplication identity. | ||
| func (a PackageAttestation) key() attestationKey { | ||
| key := attestationKey{source: a.Source, predicateType: a.PredicateType, url: a.URL} | ||
| if a.Digest != nil { | ||
| // The three parts stay separate rather than joined: plugin-supplied | ||
| // values can contain the separator, and joining lets two different | ||
| // digests produce one key. | ||
| // | ||
| // Subject is part of the identity: the same bytes hashed over a source | ||
| // tree and over an artifact are different claims. | ||
| key.digestAlgorithm = a.Digest.Algorithm | ||
| key.digestValue = a.Digest.Value | ||
| key.digestSubject = a.Digest.Subject | ||
| } | ||
| return key | ||
| } | ||
|
|
||
| // Clone returns a deep copy. | ||
| func (a PackageAttestation) Clone() PackageAttestation { | ||
| clone := a | ||
| if a.Digest != nil { | ||
| digest := *a.Digest | ||
| clone.Digest = &digest | ||
| } | ||
| return clone | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package sdk | ||
|
|
||
| // Digest captures integrity information for a package artifact. | ||
| type Digest struct { | ||
| Algorithm DigestAlgorithm `json:"algorithm,omitempty"` | ||
| Value string `json:"value,omitempty"` | ||
| // Subject says what the digest covers. Empty means the published artifact, | ||
| // which is what most ecosystems record and what a consumer should assume. | ||
| // It exists because some ecosystems record a hash that is not a hash of a | ||
| // file: a Go module's "h1:" value is SHA-256 over a manifest of the source | ||
| // tree's file hashes, not over the module zip, so a consumer that treats it | ||
| // as an artifact digest and compares it against a downloaded file will | ||
| // always find a mismatch. | ||
| Subject DigestSubject `json:"subject,omitempty"` | ||
| } | ||
|
|
||
| // DigestSubject identifies what a digest was computed over. | ||
| type DigestSubject string | ||
|
|
||
| const ( | ||
| // DigestSubjectArtifact is a digest of the published file itself. It is | ||
| // the zero value: a producer that does not say means the artifact. | ||
| DigestSubjectArtifact DigestSubject = "" | ||
| // DigestSubjectSourceTree is a digest over a source tree or over a | ||
| // manifest of its file hashes, such as a Go module "h1:" dirhash. | ||
| DigestSubjectSourceTree DigestSubject = "source-tree" | ||
| // DigestSubjectMetadata is a digest of a package's metadata document | ||
| // rather than of the package itself, such as a manifest or lockfile entry. | ||
| DigestSubjectMetadata DigestSubject = "metadata" | ||
| ) | ||
|
|
||
| // mergeDigests unions digests rather than keeping whichever record arrived | ||
| // first. Two records can carry genuinely different claims about one package -- | ||
| // a hash of the published artifact from one source and a hash over the source | ||
| // tree from another -- and Subject is what tells them apart, so dropping a | ||
| // later slice would lose provenance on merge order alone. | ||
| func (p *Package) mergeDigests(incoming []Digest) { | ||
| if len(incoming) == 0 { | ||
| return | ||
| } | ||
| seen := make(map[Digest]struct{}, len(p.Digests)+len(incoming)) | ||
| for _, digest := range p.Digests { | ||
| seen[digest] = struct{}{} | ||
| } | ||
| for _, digest := range incoming { | ||
| if _, found := seen[digest]; found { | ||
| continue | ||
| } | ||
| seen[digest] = struct{}{} | ||
| p.Digests = append(p.Digests, digest) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.