Files
mesh-catalog/modules/distribution/internal/holder/front.go
T
jschoubben 9ad22ad1e1 CAT-D1..D5 (holder core): distribution's holding of the two image seats, its caller list, incoming, the store's front and the two windows
The holder answers publish and publish-part into incoming (parts of at most 1 MiB, resumed from what is held,
hashed, only the missing layers sent, 20 GiB with its 80 % finding and a refusal at the cap, a transfer idle 24
hours dropped), asks builder_versions before it stores, and says published or refused (design 51 *Publishing*,
ADR 0605 §4, ADR 0611). versions, remove (a tagged version; untagged only for the sweep's retention, which a
hold keeps), hold and release (ADR 0617 §2: the sweep alone; the operator's session by hand; never
distribution's own account), tags and mirror. The caller list is a trusted config value checked against the
caller the tool runner proved. The store's port is the program's: reads open, writes refused but while the
direct-write window is open (never over a tag published through the seat) or with the bootstrap's one-off
credential. The old-name and direct-write windows read every node, away ones included, close once, and name a
node holding them seven days.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 11:52:37 +01:00

102 lines
3.9 KiB
Go

package holder
import (
"crypto/subtle"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"sync/atomic"
)
// Front is the store's port, served by distribution's program in front of the registry container, which listens on its
// node's loopback only (design 51 *No build module holds a registry's write credential*): reads (the OCI distribution
// protocol's GET and HEAD) stay open to every node's container runtime; a write reaches the store only from
// distribution's own process, which writes to the loopback directly and never through the front. While the direct-write
// window is open, a build module's old build still pushes through the front as today, but never to a tag the seat holds
// published.
type Front struct {
// Store is the registry container's loopback address.
Store *url.URL
// Window says whether the direct-write window is open.
Window *atomic.Bool
// Published says whether name holds a tag published through the seat.
Published func(name, tag string) (bool, error)
// Bootstrap is the bootstrap's one-off credential for the holders' first images (design 51 *On a fresh mesh*):
// read on each write, never kept, so the file's removal (BootstrapFile) refuses the next write that carries it.
Bootstrap func() (token string, ok bool)
Logf func(string, ...any)
proxy *httputil.ReverseProxy
}
// ServeHTTP answers one request of the store's port.
func (f *Front) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if f.proxy == nil {
f.proxy = httputil.NewSingleHostReverseProxy(f.Store)
}
switch r.Method {
case http.MethodGet, http.MethodHead:
f.proxy.ServeHTTP(w, r)
return
}
bootstrap := false
if f.Bootstrap != nil {
if token, ok := f.Bootstrap(); ok && token != "" {
got, has := strings.CutPrefix(r.Header.Get("Authorization"), "Bearer ")
bootstrap = has && subtle.ConstantTimeCompare([]byte(got), []byte(token)) == 1
}
}
if !bootstrap && (f.Window == nil || !f.Window.Load()) {
refuse(w, http.StatusForbidden, "DENIED", "the store takes writes from distribution's own process alone: publish through the seat's publish")
return
}
if name, tag, ok := manifestPut(r); ok && f.Published != nil {
held, err := f.Published(name, tag)
if err != nil {
refuse(w, http.StatusServiceUnavailable, "UNAVAILABLE", "whether "+name+":"+tag+" is published cannot be read, so nothing is written: "+err.Error())
return
}
if held {
refuse(w, http.StatusForbidden, "DENIED", fmt.Sprintf("%s:%s is published through the seat; a direct write cannot overwrite it: publish through the seat's publish", name, tag))
return
}
}
if bootstrap {
if f.Logf != nil {
f.Logf("a write %s %s from %s with the bootstrap's one-off credential", r.Method, r.URL.Path, r.RemoteAddr)
}
} else if f.Logf != nil {
f.Logf("a direct write %s %s from %s, accepted while the direct-write window is open", r.Method, r.URL.Path, r.RemoteAddr)
}
f.proxy.ServeHTTP(w, r)
}
// BootstrapFile is where the node-engine's bootstrap gives distribution its one-off credential, in distribution's
// state directory (0600, distribution's account): one line, the token. The tools bundle removes it when it answers its
// first publish, so the holders serve publish and the credential is gone (RemoveBootstrap).
const BootstrapFile = "/var/lib/mesh/registry/bootstrap-push"
// manifestPut is a PUT of /v2/<name>/manifests/<tag>, a tag and not a digest.
func manifestPut(r *http.Request) (name, tag string, ok bool) {
if r.Method != http.MethodPut {
return "", "", false
}
p := strings.TrimPrefix(r.URL.Path, "/v2/")
i := strings.LastIndex(p, "/manifests/")
if i <= 0 {
return "", "", false
}
name, tag = p[:i], p[i+len("/manifests/"):]
if strings.HasPrefix(tag, "sha256:") || tag == "" {
return "", "", false
}
return name, tag, true
}
func refuse(w http.ResponseWriter, code int, kind, msg string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
fmt.Fprintf(w, `{"errors":[{"code":%q,"message":%q}]}`, kind, msg)
}