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 in 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 😅
|
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} |
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
??
| return o, nil | ||
| } | ||
|
|
||
| func (p *Package) MarshalJSON() ([]byte, error) { |
There was a problem hiding this comment.
ince Package no longer has json tags, json.Marshal(pkg) on a value (not a pointer) skips this method and outputs {"Kind":"package","Name":...}.
| func (p *Package) MarshalJSON() ([]byte, error) { | |
| func (p Package) MarshalJSON() ([]byte, error) { |
| Version: p.Version, | ||
| Arch: p.Arch, | ||
| } | ||
| for kind, digest := range p.Digests { |
There was a problem hiding this comment.
Digest is never read here, so &manifest.Package{Digest: x} marshals with no digest.
| 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. :/
| 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?
| 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?
| }, nil | ||
| } | ||
|
|
||
| func getValidOptions(options *PackageOptions) (*PackageOptions, error) { |
There was a problem hiding this comment.
| func getValidOptions(options *PackageOptions) (*PackageOptions, error) { | |
| func validateDigests(digests map[string]string) error |
would be a bit closer to what the func is doing atm no? then ofc
if err := validateDigests(opts.Digests); err != nil {
...
}
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