F-catalog-B: distribution removes a tagged version's tag alone when another tag names its digest
Deleting the manifest by its digest removed every tag on it, and reproducible builds share a digest between a branch's tagged version and the trunk's numbered one, so a branch's end deleted the trunk's image (security review F1). The fake catalog now deletes by digest as the store does; a store that cannot delete a tag alone removes nothing and the removal is refused. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -89,6 +89,7 @@ type fakeCatalog struct {
|
||||
tags map[string]string // name:version -> digest
|
||||
removed []string
|
||||
mirrorErr error
|
||||
tagErr error
|
||||
indexes map[string][]string // name@digest -> the platform manifests it names
|
||||
}
|
||||
|
||||
@@ -104,8 +105,23 @@ func (c *fakeCatalog) Versions(name string) ([]string, error) {
|
||||
func (c *fakeCatalog) Digest(name, version string) (string, error) {
|
||||
return c.tags[name+":"+version], nil
|
||||
}
|
||||
|
||||
// Remove deletes by the digest, as the store does: every tag naming the digest goes with it.
|
||||
func (c *fakeCatalog) Remove(name, version string) error {
|
||||
c.removed = append(c.removed, name+":"+version)
|
||||
d := c.tags[name+":"+version]
|
||||
for k, v := range c.tags {
|
||||
if n, _, _ := strings.Cut(k, ":"); n == name && v == d {
|
||||
delete(c.tags, k)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (c *fakeCatalog) RemoveTag(name, version string) error {
|
||||
if c.tagErr != nil {
|
||||
return c.tagErr
|
||||
}
|
||||
c.removed = append(c.removed, name+":"+version+" (tag)")
|
||||
delete(c.tags, name+":"+version)
|
||||
return nil
|
||||
}
|
||||
@@ -368,6 +384,22 @@ func TestTheHolderOfTheTwoImageSeats(t *testing.T) {
|
||||
if err := b.v.Remove(ArtifactSeat, "novox.mesh-delivery-builder", RemoveRequest{Name: "m/p", Version: "0.1.5-feat-x.1", Reason: "branch merged"}); err != nil || b.cat.tags["m/p:0.1.5-feat-x.1"] != "" {
|
||||
t.Errorf("the builder's removal of a tagged version: %v", err)
|
||||
}
|
||||
// Security review F1: a reproducible build gives a branch's tagged version and the trunk's numbered one one
|
||||
// digest; the tagged one's removal takes its tag alone, and the numbered one stays pullable.
|
||||
b.cat.tags["m/p:0.1.6-feat-y.1"], b.cat.tags["m/p:0.1.6"] = part.Digest, part.Digest
|
||||
if err := b.v.Remove(ArtifactSeat, "novox.mesh-delivery-builder", RemoveRequest{Name: "m/p", Version: "0.1.6-feat-y.1", Reason: "branch merged"}); err != nil {
|
||||
t.Fatalf("the removal of a tagged version sharing its digest: %v", err)
|
||||
}
|
||||
if b.cat.tags["m/p:0.1.6-feat-y.1"] != "" || b.cat.tags["m/p:0.1.6"] != part.Digest {
|
||||
t.Errorf("tags after the tagged version's removal: %v; want the tag gone and 0.1.6 still at %s", b.cat.tags, part.Digest)
|
||||
}
|
||||
// A store that cannot delete a tag alone removes nothing, and the removal is refused naming the shared digest.
|
||||
b.cat.tags["m/p:0.1.7-feat-z.1"], b.cat.tags["m/p:0.1.7"] = part.Digest, part.Digest
|
||||
b.cat.tagErr = errors.New("UNSUPPORTED")
|
||||
if err := b.v.Remove(ArtifactSeat, "novox.mesh-delivery-builder", RemoveRequest{Name: "m/p", Version: "0.1.7-feat-z.1", Reason: "branch merged"}); err == nil || !strings.Contains(err.Error(), "shares its digest") || b.cat.tags["m/p:0.1.7"] != part.Digest {
|
||||
t.Errorf("a store that cannot delete a tag alone: %v, tags %v; want refused and 0.1.7 kept", err, b.cat.tags)
|
||||
}
|
||||
b.cat.tagErr = nil
|
||||
})
|
||||
// CAT-AS1: what the sweep did over the store's door is the holder's: a removal for retention of an index removes
|
||||
// its platform manifests first, but those a tagged or held index of the repository names; a manifest such an index
|
||||
|
||||
Reference in New Issue
Block a user