ENG-5: the bootstrap makes a one-off push credential for the holders' first images, writes it for the store's holder and forgets it when its run ends (design 51, issue 1966)

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 12:07:34 +01:00
parent 2a71a8ff76
commit 18c1735f7c
4 changed files with 98 additions and 5 deletions
+3 -1
View File
@@ -188,7 +188,9 @@ func parseArgs(args []string) (string, bootstrap.Options, bool, error) {
Out: defaultOut,
State: store.DefaultPath,
Registry: defaultRegistry,
Host: defaultHost,
// Where the store's holder reads the one-off credential the holders' first images are pushed with (design 51, ENG-5).
PushCredentialFile: "/var/lib/mesh/registry/bootstrap-push",
Host: defaultHost,
// The machine's own name, because that is what a person already calls it and an installer
// inventing a different one would leave the mesh naming a machine nobody recognises. It is
// read here rather than inside the bootstrap so that --node overrides a fact rather than a
+23 -4
View File
@@ -157,6 +157,12 @@ type Options struct {
// an address the others can reach.
Registry string
// PushCredentialFile is where the bootstrap writes its one-off push credential for the store's holder to find, the
// holder's state file `bootstrap-push` on the node that holds it (design 51 *The move*, issue 1966 ENG-5); empty writes
// none. PushCredentialOwner is the user id and group id the file is given when the holder's account is known.
PushCredentialFile string
PushCredentialOwner *[2]int
// Host is the `mesh-host` binary on this machine — the program that enrols and then holds the
// machine to what the mesh says. The installer runs it; it does not contain it.
Host string
@@ -296,10 +302,12 @@ type Result struct {
Agent string `json:"host-agent,omitempty"`
// Registry is the mesh's own artifact store, once it answers.
Registry string `json:"registry,omitempty"`
RegistryReplied int `json:"registry-replied,omitempty"`
RegistryKnown bool `json:"registry-already-registered,omitempty"`
RegistryRunning string `json:"registry-container,omitempty"`
Registry string `json:"registry,omitempty"`
// PushCredentialFile is where this run left its one-off push credential (never the credential itself).
PushCredentialFile string `json:"push-credential-file,omitempty"`
RegistryReplied int `json:"registry-replied,omitempty"`
RegistryKnown bool `json:"registry-already-registered,omitempty"`
RegistryRunning string `json:"registry-container,omitempty"`
// RegistryTrusted are the names the runtime was told to trust the registry by, before the push.
RegistryTrusted []string `json:"registry-trusted,omitempty"`
PublishedAs string `json:"control-plane-image,omitempty"`
@@ -815,6 +823,17 @@ func RegistryAndPublish(ctx context.Context, o Options, d Deps, sys system.Syste
if err != nil {
return Published{}, failed(StepRegistry, err)
}
// The one-off credential the holders' first images are pushed to the store's front with, written for its holder to find
// and forgotten when this run ends; the holder discards the file once it answers its first publish (ENG-5).
if o.PushCredentialFile != "" {
cred, err := MintPushCredential(o.PushCredentialFile, o.PushCredentialOwner)
if err != nil {
return Published{}, failed(StepRegistry, err)
}
defer cred.Forget()
result.PushCredentialFile = cred.File
say(" a one-off credential for the holders' first images is at " + cred.File + "; the store's holder discards it once it serves publish")
}
// ---- 8. publish -----------------------------------------------------------------------
say("publish — the control plane's image gets its first manifest digest")
+55
View File
@@ -0,0 +1,55 @@
package bootstrap
import (
"crypto/rand"
"encoding/hex"
"fmt"
"os"
"path/filepath"
)
// PushCredential is the bootstrap's one-off credential for the holders' first images (design 51 *The move*; issue 1966 ENG-5):
// a bearer token of 32 random bytes, hex, on one line, made for one fresh-mesh run. No vault and no bus holds it: this run
// holds it and writes it once, to the file the store's holder reads on each write (`Authorization: Bearer <token>` on an OCI
// write to the store's port, from any node); the holder removes the file when it answers its first `publish`, and this run
// forgets the token when it ends. On the holder's own node the bootstrap may push to the registry container's loopback with
// no credential at all.
type PushCredential struct {
// File is where the token was written.
File string
token string
}
// Token is the bearer token, for a push through the store's front from another node.
func (c *PushCredential) Token() string { return c.token }
// Forget drops the token from this run's memory. The file is the holder's to remove.
func (c *PushCredential) Forget() { c.token = "" }
// MintPushCredential makes a token and writes it to file, mode 0600, owned by owner (user id, group id) when given; the
// directory is made if it is not there. A file that is there is replaced: a credential left by an earlier run is never reused.
func MintPushCredential(file string, owner *[2]int) (*PushCredential, error) {
raw := make([]byte, 32)
if _, err := rand.Read(raw); err != nil {
return nil, fmt.Errorf("a push credential cannot be made: %w", err)
}
token := hex.EncodeToString(raw)
if err := os.MkdirAll(filepath.Dir(file), 0o755); err != nil {
return nil, fmt.Errorf("the push credential's directory %s cannot be made: %w", filepath.Dir(file), err)
}
tmp := file + ".new"
if err := os.WriteFile(tmp, []byte(token+"\n"), 0o600); err != nil {
return nil, fmt.Errorf("the push credential cannot be written to %s: %w", file, err)
}
if owner != nil {
if err := os.Chown(tmp, owner[0], owner[1]); err != nil {
os.Remove(tmp)
return nil, fmt.Errorf("the push credential cannot be given to the store's holder: %w", err)
}
}
if err := os.Rename(tmp, file); err != nil {
os.Remove(tmp)
return nil, fmt.Errorf("the push credential cannot be put at %s: %w", file, err)
}
return &PushCredential{File: file, token: token}, nil
}
+17
View File
@@ -7,6 +7,7 @@ import (
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
@@ -263,6 +264,9 @@ func TestNothingIsPushedToTheRegistryBeforeTheRuntimeTrustsIt(t *testing.T) {
return http.StatusNotFound, "", nil
}}
var result Result
// And the one-off credential for the holders' first images (ENG-5): written once, mode 0600, one line of 64 hex digits,
// named in the result and never put in it.
o.PushCredentialFile = filepath.Join(t.TempDir(), "registry", "bootstrap-push")
published, err := RegistryAndPublish(context.Background(), o, d, sys,
controlPlane{container: "temp-mesh-controller", run: run, timeout: time.Second}, held, &result, func(string) {})
if err != nil {
@@ -287,6 +291,19 @@ func TestNothingIsPushedToTheRegistryBeforeTheRuntimeTrustsIt(t *testing.T) {
t.Errorf("the trust was not written (%d) and confirmed (%d) before the first tag or push (%d): %v",
reloaded, confirmed, firstPush, runtime.commands)
}
if raw, err := os.ReadFile(o.PushCredentialFile); err != nil || !regexp.MustCompile(`^[0-9a-f]{64}\n$`).Match(raw) {
t.Errorf("the push credential is %q (%v)", raw, err)
} else if st, _ := os.Stat(o.PushCredentialFile); st.Mode().Perm() != 0o600 {
t.Errorf("the push credential is mode %v", st.Mode().Perm())
}
if result.PushCredentialFile != o.PushCredentialFile {
t.Errorf("the result names %q", result.PushCredentialFile)
}
if again, err := MintPushCredential(o.PushCredentialFile, nil); err != nil || again.Token() == "" {
t.Errorf("a credential of an earlier run was not replaced: %v", err)
} else if again.Forget(); again.Token() != "" {
t.Errorf("a forgotten credential still holds its token")
}
if fmt.Sprint(result.RegistryTrusted) != "[192.0.2.10:5100 anchor.internal:5100]" {
t.Errorf("the result says it trusted %v", result.RegistryTrusted)
}