Merge pull request 'mesh-build-image names its copy wait's ownership mark nonce, so the state takes it and nats images are stored again (issue 1966)' (#645) from fix/1966-mirror-waits-token into main

This commit was merged in pull request #645.
This commit is contained in:
2026-11-09 22:00:32 +00:00
2 changed files with 46 additions and 7 deletions
@@ -99,7 +99,7 @@ func (m *Module) copy(ctx context.Context, args map[string]any) (any, error) {
now = m.Now
}
asked := CopyWait{Digest: digest, Module: module, Artifact: name, From: from, Node: m.Node,
Asked: now().UTC(), Token: strconv.FormatInt(now().UnixNano(), 36) + "-" + m.Node}
Asked: now().UTC(), Nonce: strconv.FormatInt(now().UnixNano(), 36) + "-" + m.Node}
// Waited for before the ask, in memory and in the state mirror-waits, so an outcome said at once, or heard by another
// instance of the module (the seat's events reach one of them), is not missed.
heard, stop := m.Outcomes.Expect(digest)
@@ -107,7 +107,7 @@ func (m *Module) copy(ctx context.Context, args map[string]any) (any, error) {
if _, err := m.Outcomes.Waits.Put(key, asked); err != nil {
return nil, fmt.Errorf("%s%s: keeping the copy's wait in %s: %v", builds.NotStored, name, MirrorWaitsState, err)
}
defer m.Outcomes.drop(key, asked.Token)
defer m.Outcomes.drop(key, asked.Nonce)
said := buildstep.NarrateSeat(ctx, buildstep.ImageSeat, "mirror of "+from)
raw, err := m.call()("seat:"+buildstep.ImageSeat+"."+reg.VerbMirror, reg.MirrorInput{Name: module + "/" + name, Source: from, Digest: digest})
if err != nil {
@@ -170,7 +170,8 @@ const MirrorWaitsState = "mirror-waits"
// MirrorWaitKey is a copy's key in mirror-waits: its digest, with the colon a key cannot hold as a dash.
func MirrorWaitKey(digest string) string { return strings.Replace(digest, ":", "-", 1) }
// CopyWait is one value of mirror-waits: the copy asked, and its outcome once heard.
// CopyWait is one value of mirror-waits: the copy asked, and its outcome once heard. Nonce tells this copy's wait from
// a later copy's of the same digest; it is no credential, and no field here may be named as one (the state refuses it).
type CopyWait struct {
Digest string `json:"digest"`
Module string `json:"module"`
@@ -178,7 +179,7 @@ type CopyWait struct {
From string `json:"from"`
Node string `json:"node"`
Asked time.Time `json:"asked"`
Token string `json:"token"`
Nonce string `json:"nonce"`
Outcome *MirrorOutcome `json:"outcome,omitempty"`
}
@@ -281,9 +282,9 @@ func (o *Outcomes) kept(key string) (MirrorOutcome, bool) {
return *w.Outcome, true
}
// drop removes key's wait when it is still the copy's own (token): a later copy of the same digest keeps its own.
func (o *Outcomes) drop(key, token string) {
if w, err := o.read(key); err == nil && w != nil && w.Token == token {
// drop removes key's wait when it is still the copy's own (nonce): a later copy of the same digest keeps its own.
func (o *Outcomes) drop(key, nonce string) {
if w, err := o.read(key); err == nil && w != nil && w.Nonce == nonce {
_ = o.Waits.Delete(key)
}
}
@@ -115,6 +115,9 @@ func (w *memWaits) Put(key string, value any) (uint64, error) {
if err != nil {
return 0, err
}
if f := credentialShaped(b); f != "" {
return 0, fmt.Errorf("the state refuses a field %q, which names a credential", f)
}
w.mu.Lock()
if w.m == nil {
w.m = map[string]json.RawMessage{}
@@ -339,3 +342,38 @@ func tool(t *testing.T, m *Module, name string) func(map[string]any) (any, error
t.Fatalf("no tool %s", name)
return nil
}
// credentialShaped is the first field name in a JSON value that ends as the state's guard refuses (the bus's
// credentialEndings), or "": the in-memory state refuses it as the bus does, so every copy test meets the guard.
func credentialShaped(value []byte) string {
var v any
if json.Unmarshal(value, &v) != nil {
return ""
}
var walk func(any) string
walk = func(v any) string {
switch t := v.(type) {
case map[string]any:
for k, x := range t {
norm := strings.NewReplacer("-", "", "_", "", " ", "").Replace(strings.ToLower(k))
for _, end := range []string{"password", "passwd", "secret", "token", "credential", "credentials",
"authorization", "apikey", "privatekey", "accesskey", "cookie"} {
if strings.HasSuffix(norm, end) {
return k
}
}
if f := walk(x); f != "" {
return f
}
}
case []any:
for _, x := range t {
if f := walk(x); f != "" {
return f
}
}
}
return ""
}
return walk(v)
}