Files
mesh-catalog/modules/distribution/internal/holder/front.go
T
jschoubben 1b485eb2aa F-catalog-B: distribution's front takes no bootstrap credential
The lead's ENG-5 ruling: on a fresh mesh the bootstrap pushes the holders' own images to the store on its
loopback with no credential, so the front's bootstrap token, its file, the bootstrap-push window and the
program's removal of the file go. Fewer secrets; nothing to refuse once a window closes.

Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
2026-11-04 17:05:24 +01:00

83 lines
3.0 KiB
Go

package holder
import (
"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. No credential opens it: on a fresh mesh the bootstrap pushes the holders' own images to the store on its
// loopback, never through the front (design 51 *On a fresh mesh*).
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)
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
}
if 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 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)
}
// 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)
}