Engines listen on every address their node has, with no key. Checked on the live cluster: from a neighbouring node, a request straight to an engine's port was answered — past the gateway's authentication, quotas and accounting, by anyone who could reach the node. Each replica's engine is now started with a key and refuses requests without it. The key is derived from the replica id with an HMAC over a secret kept in the store, rather than stored per replica: the router has to send the right key to replicas this process never started — adopted after a restart, or placed by the other control plane — and a function of a shared secret gives that without a table to keep in step. A key taken from one node opens that replica only. The secret is made on first use and copied with the rest of the store when moving to PostgreSQL; a new one would lock the router out of every running engine at the moment of the move. The key reaches llama.cpp and vLLM through their environment (LLAMA_API_KEY, VLLM_API_KEY) rather than their command line, which every user on the node can read; SGLang only takes it as an argument. The agent's readiness check, the router's proxy and its metrics scrape send it, the client's own Authorization still never reaches the engine, and `ctl bench` fetches the key of the replica it is pointed at from the admin API. Engines started before this keep running without a key until their replica is next started. With no store there is nowhere to keep the secret, and engines start without one, with a warning. Also formats two files that went in unformatted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
87 lines
2.7 KiB
Go
87 lines
2.7 KiB
Go
package mirror
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/netip"
|
|
"net/url"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
// errPrivateSource is a fetch refused because it would reach this machine or
|
|
// the network behind it rather than the internet.
|
|
var errPrivateSource = errors.New("refusing to fetch from a loopback, private or link-local address")
|
|
|
|
// fetchClient downloads sources for the management API.
|
|
//
|
|
// The API fetches whatever URL it is given and the file server then hands the
|
|
// result to anyone. Without this, "fetch http://169.254.169.254/..." was a way
|
|
// to read a cloud's instance metadata — or anything else on the network the
|
|
// mirror sits in — through the mirror. The check is made on the address
|
|
// actually dialled, after resolution and on every redirect, so a name that
|
|
// resolves somewhere public and then somewhere private is caught too.
|
|
func fetchClient(allowPrivate bool) *http.Client {
|
|
d := &net.Dialer{Timeout: 30 * time.Second, KeepAlive: 30 * time.Second}
|
|
if !allowPrivate {
|
|
d.Control = func(_, address string, _ syscall.RawConn) error {
|
|
host, _, err := net.SplitHostPort(address)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a, err := netip.ParseAddr(host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if blockedAddr(a.Unmap()) {
|
|
return fmt.Errorf("%w (%s)", errPrivateSource, a)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
return &http.Client{
|
|
Transport: &http.Transport{
|
|
Proxy: http.ProxyFromEnvironment,
|
|
DialContext: d.DialContext,
|
|
TLSHandshakeTimeout: 30 * time.Second,
|
|
ResponseHeaderTimeout: 2 * time.Minute,
|
|
ForceAttemptHTTP2: true,
|
|
},
|
|
// No overall timeout: a model is tens of gigabytes. The dial, the
|
|
// handshake and the first byte are bounded above; after that a stall
|
|
// is the context's to end.
|
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
|
if len(via) >= 10 {
|
|
return errors.New("too many redirects")
|
|
}
|
|
return checkScheme(req.URL)
|
|
},
|
|
}
|
|
}
|
|
|
|
func blockedAddr(a netip.Addr) bool {
|
|
cgnat := netip.MustParsePrefix("100.64.0.0/10")
|
|
return a.IsLoopback() || a.IsPrivate() || a.IsLinkLocalUnicast() ||
|
|
a.IsLinkLocalMulticast() || a.IsMulticast() || a.IsUnspecified() || cgnat.Contains(a)
|
|
}
|
|
|
|
// checkScheme allows the two schemes a model is published over. file:// and
|
|
// the rest would read this machine's own disk.
|
|
func checkScheme(u *url.URL) error {
|
|
if u.Scheme != "http" && u.Scheme != "https" {
|
|
return fmt.Errorf("%q is not an http or https source", u.Redacted())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validSource checks a source before a download is started for it.
|
|
func validSource(source string) error {
|
|
u, err := url.Parse(source)
|
|
if err != nil || u.Host == "" {
|
|
return fmt.Errorf("%q is not a URL", source)
|
|
}
|
|
return checkScheme(u)
|
|
}
|