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//manifests/, 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) }