F-host 2, ENG-5: the bootstrap's one-off push credential goes to distribution's account, mode 0600, only once the node-engine made that account and its state directory, so the holder's program can read it (review of issue 1966)

The owner is the account the manifest names for the directory that holds the file; the bootstrap no longer makes that
directory itself, and a run that reaches it first is refused naming what it waits on.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
2026-11-04 16:24:14 +01:00
parent 9740238ec9
commit 20614822df
3 changed files with 103 additions and 20 deletions
+70 -11
View File
@@ -3,9 +3,13 @@ package bootstrap
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"os/user"
"path/filepath"
"strconv"
"syscall"
)
// PushCredential is the bootstrap's one-off credential for the holders' first images (design 51 *The move*; issue 1966 ENG-5):
@@ -26,26 +30,81 @@ 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) {
// lookupAccount gives an account's user id and group id. A variable so a test can name an account this machine has.
var lookupAccount = func(name string) (uid, gid int, err error) {
u, err := user.Lookup(name)
if err != nil {
return 0, 0, err
}
if uid, err = strconv.Atoi(u.Uid); err != nil {
return 0, 0, err
}
gid, err = strconv.Atoi(u.Gid)
return uid, gid, err
}
// WritePushCredential writes the one-off credential for the store's holder, read from its manifest in catalogue: the file
// goes into the holder's directory resource that holds file's path, given to the account that directory names as owner,
// and only once the node-engine has made that account and that directory, owned by it. Until then the holder's program,
// which runs as that account, could not read it, so it is refused rather than written as root's.
func WritePushCredential(catalogue, file string) (*PushCredential, error) {
manifest, err := readManifest(catalogue, RegistryModule)
if err != nil {
return nil, err
}
var m struct {
Resources []struct {
Type string `json:"type"`
Name string `json:"name"`
Path string `json:"path"`
Owner string `json:"owner"`
} `json:"resources"`
}
if err := json.Unmarshal(manifest, &m); err != nil {
return nil, fmt.Errorf("the %s manifest is not readable as JSON: %w", RegistryModule, err)
}
dir, owner, account := filepath.Dir(file), "", false
for _, r := range m.Resources {
if r.Type == "directory" && filepath.Clean(r.Path) == dir {
owner = r.Owner
}
}
for _, r := range m.Resources {
account = account || (owner != "" && r.Type == "user" && r.Name == owner)
}
if !account {
return nil, fmt.Errorf("the push credential %s has no account to go to: the %s manifest declares no directory %s "+
"owned by an account it declares, so its program could not read the file", file, RegistryModule, dir)
}
uid, gid, err := lookupAccount(owner)
if err != nil {
return nil, fmt.Errorf("the push credential waits on the account %s, which the node-engine has not made: %w", owner, err)
}
st, err := os.Stat(dir)
if err != nil {
return nil, fmt.Errorf("the push credential waits on %s's state directory %s, which the node-engine has not made: %w", owner, dir, err)
}
if sys, ok := st.Sys().(*syscall.Stat_t); !ok || int(sys.Uid) != uid {
return nil, fmt.Errorf("the push credential waits on %s owning its state directory %s, which the node-engine has not yet given it", owner, dir)
}
return MintPushCredential(file, uid, gid)
}
// MintPushCredential makes a token and writes it to file, mode 0600, owned by the user id and group id given, into a
// directory that must be there. A file that is there is replaced: a credential left by an earlier run is never reused.
func MintPushCredential(file string, uid, gid 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.Chown(tmp, uid, gid); 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)