Conversation
| "io" | ||
|
|
||
| "github.com/canonical/chisel/internal/archive" | ||
| "github.com/canonical/chisel/internal/cache" |
There was a problem hiding this comment.
[Note to reviewer]: Importing cache in more and more packages only to get access to the list of supported digestkinds looks increasingly wrong. Conceptually it does not make a lot of sense that archive or manifesutil depends on cache. I am tempted to extract the digestkind-related bits out of cache to a dedicated package. If we decide to proceed, I will do that in a follow-up as I don't want to pollute this PR with a refactor.
lczyk
left a comment
There was a problem hiding this comment.
ok, discussed this to exhaustion in a separate thread so i will just drop a digest (🥁🥁🐍) of that conversation here:
- fixed 26.10
- changes the manifest for older releases to sha512(!), even 20.04 because chisel resolves from -updates.
- chisel is release-agnostic so any special rules for e.g. 26.04 or earlier are off-the-table
- we could maintain backwards compatibility by choosing to always record sha256 preferentially over sha512 but then:
- we're weakening the manifest
- we're decoupling validation from the manifest, since validation would stay 512 ( or we'd flip validation too to prefer weaker sha. no. )
- we could take this PR and bump the schema version of the manifest 1.0 -> 1.1
atm it feels to me like the last option is the best, since all others introduce in-perpetuum compromises which go in hacky/insecure directions.
|
also, leaving a note here so we don't forget, this was not covered in #306 and, possibly, should have been. we might need to look into test coverage for chisel. |
lczyk
left a comment
There was a problem hiding this comment.
this version is almost ok imo. it does "kick the can down the road" regarding the breaking change -- at some point we'll have to flip the preference to recording the strongest supported digest, but we can do that in 1.6.0 rather than now and in a rush.
my one issue is that it's a regression from 1.5.0 in what we verify the .debs against -- the per-package digest inside Packages.gz, not the index itself, which this PR doesn't touch. 1.5.0 checks those with sha512, and ace8a36 (this PR's current HEAD) flips them back to sha256. all releases until 26.04 have only sha256 in InRelease, but their Packages.gz publish sha256 and sha512 (curl -fsSL http://archive.ubuntu.com/ubuntu/dists/noble-updates/main/binary-amd64/Packages.gz | gzcat | grep SHA512), so in 1.5.0 they are verified with sha512.
seemingly, if we wanted to address this, we'd need to decouple the verification (sha512) form what's recorded in the manifest (sha256) which completely defeats the purpose of the manifest. no. here is a proposed solution though: verify both shas. strongest-first as the primary, the way we want it, then, if sha256 is also published, verify that too since that's the one we will be recording. abit of a perf hit, but i think it will give us the best result we could get with the constraints we have:
- 26.10 works -- sha512 only, nothing to double-check
- manifests unchanged on 20.04..26.04, no schema bump
- what we record is what we checked
- we keep the strongest published digest for verification, so no regression from 1.5.0
lczyk
left a comment
There was a problem hiding this comment.
after a separate discussion, dropping to sha256 validation for now is also a good way forward. 👍
| SHA256 string `json:"sha256,omitempty"` | ||
| SHA512 string `json:"sha512,omitempty"` | ||
| SHA384 string `json:"sha384,omitempty"` |
There was a problem hiding this comment.
[Note to reviewer]: The set of supported digest kinds is repeated in several places in this package. I tried different approaches to avoid this and improve maintainability but none was notably better than the current approach here.
| for _, f := range order { | ||
| if d, _, ok := control.ParsePathInfo(release.Get(f.name), path); ok { | ||
| return d, f | ||
| func findDigest(release control.Section, path string) (digest string, field digestField, ok bool) { |
There was a problem hiding this comment.
[Note to reviewer]: This change in signature (returning a bool) addresses a comment of the previous PR that was missed. Same thing for packageDigests below.
| Name: "package1", | ||
| Version: "v1", | ||
| Arch: "a1", | ||
| Digests: map[cache.DigestKind]string{cache.SHA384: "s384"}, |
There was a problem hiding this comment.
note that cache.SHA384 caches with sha-3 (sha3.New384(), added in #298). there is also sha-2-384 and that could be a source of confusion. maybe the manifest key should be called SHA3_384 instead?
There was a problem hiding this comment.
great loc to comment on this, ik 😅
There was a problem hiding this comment.
I agree this can be a bit imprecise, but we discussed it with Gustavo and the name sha384 was chosen on purpose, to be consistent with sha256.
|
note that copa has chisel manifest support (project-copacetic/copacetic#1667) and atm it will hard-fail for rows w/out sha256. we should let them know and maybe even do a followup pr. they're using the |
|
|
||
| var digestKinds = []DigestKind{SHA256, SHA384, SHA512} | ||
| // digestKinds sorted in decreasing order of strength. | ||
| var digestKinds = []DigestKind{SHA384, SHA512, SHA256} |
There was a problem hiding this comment.
| var digestKinds = []DigestKind{SHA384, SHA512, SHA256} | |
| var digestKinds = []DigestKind{SHA512, SHA384, SHA256} |
There was a problem hiding this comment.
The existing order is correct. I know this can be debatable but as we are comparing SHA-512 and SHA3-384, I considered SHA3-384 to be "stronger".
lczyk
left a comment
There was a problem hiding this comment.
im not sold on the custom martial and unmartiallers.
also, since we're leaving Digest for compat, how about we just make it always sha256 ( yes, sometimes compute it ). then its 100% compatibility, and consumer like copa dont need patching
| @@ -12,13 +13,124 @@ import ( | |||
| const Schema = "1.0" | |||
|
|
|||
| type Package struct { | |||
There was a problem hiding this comment.
id love to avoid the custom marshall and unmarshall. what about something like:
type Package struct {
Kind string `json:"kind"`
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
}
func (p Package) Digests() map[string]string
??
There was a problem hiding this comment.
Can you elaborate? Do you suggest not exporting a Digest field? How would that not break compatibility?
There was a problem hiding this comment.
i forgot to add Digest to the enum. the point of the comment is to not add the Digests field with the custom marshallers, but add a method Digests which is the public api to these, and then store the rest in private fields for which we can use bock-standard Json marshaller
There was a problem hiding this comment.
I considered this but this has some drawbacks/blockers:
- The API is now slightly inconsistent: a Digest is accessible via a field, the rest via a method. Maybe not a big deal but still
- it does not really solve how to init a Package object. How to declare the map of digests?
- To use the JSON marshaller/unmarshaller the fields must be exported, and I want to avoid going back to one field per digest kind as this will need to change whenever we add/remove kinds. So I think what you describe is not possible unless I am missing something.
| case "sha384": | ||
| pj.SHA384 = digest | ||
| default: | ||
| return nil, fmt.Errorf("cannot marshal package %q: unsupported digest kind %q", p.Name, kind) |
There was a problem hiding this comment.
marshalling a Package couldn't fail before; with a free-form map it now can. :/
There was a problem hiding this comment.
This is consequence of having Digests map[string]string designed to avoid breaking compatibility of old readers when additional digest kinds are added. The marshaller can indeed fail but in a case where we really do not want to continue because we do not want to produce a manifest with arbitrary digest kinds.
| digests[kind] = digest | ||
| } | ||
| } | ||
| pkg, err := NewPackage(&PackageOptions{ |
There was a problem hiding this comment.
going through NewPackage here drops the decoded kind (NewPackage hardcodes "package"), and the error below can't happen. fill the struct directly instead?
There was a problem hiding this comment.
Unless I am missing something I think you mix 2 different kinds:
- the manifest entry kind, in that case "package". Other values for this are for example "slice" or "content".
- digest kinds, used above as key to the
digestsmap.
There was a problem hiding this comment.
Oh now I think I see what you meant.
You raised an interesting point. In a sense UnmarshalJSON was lying and dealing with something that is not its concern. I fixed it so the unmarshaling is properly representing the received data.
While digging on that I spotted a possible bug though: The current implementation of IteratePackages would match on entries like
{“kind”: “package-foo”, …} due to db.IteratePrefix(prefix) removing the ”, suffix of the prefix before iterating on the db. This works well for other kinds as the goal is to use the value as a prefix, but is probably not what was intended for package entries. At this point I also think that relying on this behavior is a misuse of the API.
| Digests map[string]string | ||
| } | ||
|
|
||
| func NewPackage(opts *PackageOptions) (*Package, error) { |
There was a problem hiding this comment.
NewPackage(nil) panics. do we need NewPackage / PackageOptions as public api anyway?
There was a problem hiding this comment.
I wanted an entry point to create "proper" Package object, with correct Digest/Digests. This is used in manifestutil and I expect consumers of the API to use it too, so it made sense to export the function and the PackageOptions struct.
|
@lczyk Thanks for the review.
We decided to record what the archive published, as we do not want to make assumptions on how these digests are used (integrity check? caching? something else?). I think we should be careful not being too influenced by a single use case, at the risk of putting us in a corner. |
yeah.. fair, fair. just a ponder to see what hard backwards compatibility would look there. the original comment is not a blocker from me by any means. |
Ubuntu archives publish multiple checksums per package (SHA256 and SHA512 today, SHA512-only on 26.10+), but the manifest recorded only a single SHA256 digest, which is wrong. Chisel now records digests published by the archive (and supported by Chisel) for each package in the manifest, while the strongest one continues to be used for fetch verification and content-addressable caching. The manifest can effectively contain an new
sha512field alongsidesha256, so manifests remain readable and byte-compatible across old and new readers.API changes:
manifest.PackagegainsDigests map[string]stringand aNewPackageconstructor that validates digest kinds and derives the legacyDigestfield (sha256 when present, sha512 otherwise), which is kept for source compatibility with existing consumers.manifestutil.PackageInforeplacesPkgDigest()/PkgDigestKind()withPkgDigests(). This requires at least a minor release.Also of note: fetching a package whose section advertises no supported digest is now an error rather than a silent unverified download.
Fixes #305