What changed - lanGuard: a request whose peer isn't loopback (or that carries a forwarding header) is remote. While the gateway is shared, or MAGPIE_ADDR names a host other than loopback/localhost (OnNetwork: 0.0.0.0 in the Docker image, a server's address), a remote request is answered only with an enabled gateway key, on every route behind the guard; otherwise it is refused (403) as before. Before, a MAGPIE_ADDR on the network with sharing off took remote requests with any key. A remote request let in with a key is marked as shared-mode ones are, so quotas, quota cards, refresh and prices answer it. callerGuard follows the same condition. OpenToAnyone is replaced by OnNetwork. - Loopback is unchanged: any key still works on the gateway's own machine and inside the container (HEALTHCHECK included). - CLI keyNote and the console's Connect note no longer describe an open gateway; on the network they say other machines need a gateway key (`magpie gateway-key add <name>`). The console shows the Gateway keys block and network addresses when OnNetwork (gatewayJSON.onNetwork replaces open); the "Open to the network" strings are removed. - Docs: docker.html (en/zh/ja) and remote.html (en/zh/ja) say callers outside the container need a gateway key, how to make one, that sharing isn't needed for it, and what an upgrade means for agents that sent `magpie`; docs/reference.md and providers-accounts.md likewise. A MAGPIE_ADDR pinned to one interface's address now asks a key of this computer's agents calling that address too (documented). Tests - New TestNetworkGatewayGuardsEveryRoute: from 172.17.0.1 and 192.168.1.9 every registered route (model APIs, count_tokens, systemone, the Responses websocket GET, MCP, Codex backend and catalog, /v1/magpie/*, images, embeddings, rerank, videos, Gemini) gets 401 without a valid key; with one, models and quotas answer 200. - TestNetworkGatewayNeedsAKey (was TestExplicitOpenGatewayAccepts...), TestLANGuard, TestProxiedLoopbackNeedsTheKey, TestQuotasOverLAN, TestMCPProxyStaysHere, session route and remote prices tests, TestOnNetwork, serve_cli and gui public_url tests updated. With the old lanGuard restored, TestNetworkGatewayNeedsAKey, TestNetworkGatewayGuardsEveryRoute, TestLANGuard and TestProxiedLoopbackNeedsTheKey fail. Run - go test -tags nogui -count=1 ./internal/gateway ./internal/gui ./internal/provider . under a temp HOME: ok. - gofmt -l clean; GOOS=linux and GOOS=windows go vet -tags nogui on those packages: clean. - GUI: public-url and gateway-caller-keys tests, Chromium and WebKit: 22/22. - Locally built image (OrbStack), -p 127.0.0.1:3481:3425: from the host, /v1/models with no key, `magpie` and another made-up key -> 401; count_tokens and /mcp/x -> 401; a key from `docker exec ... magpie gateway-key add Host` -> models 200, quotas 200; inside the container wget /v1/models -> 200; healthcheck exits 0. `web --addr 0.0.0.0:3430`: /api/state 401 without the web key, 200 with its cookie.
84 lines
3.0 KiB
Go
84 lines
3.0 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/yetone/magpie/internal/settings"
|
|
)
|
|
|
|
// The serve banner says the gateway takes any key from this machine alone,
|
|
// and, when MAGPIE_ADDR puts it on the network (a Docker image) or it is
|
|
// shared from Settings, a gateway key from others: never from anyone.
|
|
func TestKeyNote(t *testing.T) {
|
|
t.Setenv("HOME", t.TempDir())
|
|
t.Setenv("USERPROFILE", os.Getenv("HOME"))
|
|
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
|
for _, c := range []struct{ addr, want string }{
|
|
{"", "only listens on localhost"},
|
|
{"127.0.0.1:3426", "only listens on localhost"},
|
|
{"localhost:3426", "only listens on localhost"},
|
|
{"[::1]:3426", "only listens on localhost"},
|
|
{"0.0.0.0:3425", "from others, an enabled gateway key"},
|
|
{":3425", "from others, an enabled gateway key"},
|
|
{"192.168.1.5:3425", "from others, an enabled gateway key"},
|
|
} {
|
|
t.Setenv("MAGPIE_ADDR", c.addr)
|
|
if n := keyNote(); !strings.Contains(n, c.want) || strings.Contains(n, "anyone") {
|
|
t.Errorf("MAGPIE_ADDR=%q: %q", c.addr, n)
|
|
}
|
|
}
|
|
for _, key := range []string{"", "sk-magpie-k"} {
|
|
if err := settings.Save(settings.Settings{LAN: true, LANKey: key}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, addr := range []string{"", "0.0.0.0:3425"} {
|
|
t.Setenv("MAGPIE_ADDR", addr)
|
|
if n := keyNote(); !strings.Contains(n, "gateway key") || strings.Contains(n, "only listens on localhost") {
|
|
t.Error("shared:", n)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The CLIs print the address other machines are told to reach the gateway
|
|
// at: a valid MAGPIE_PUBLIC_URL, otherwise the one reached from this
|
|
// machine, so `magpie models` and the serve banner never print a bad one.
|
|
func TestAdvertisedURL(t *testing.T) {
|
|
t.Setenv("HOME", t.TempDir())
|
|
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
|
t.Setenv("MAGPIE_ADDR", "127.0.0.1:3425")
|
|
for _, c := range []struct{ public, want string }{
|
|
{"", "http://127.0.0.1:3425"},
|
|
{"https://magpie.example.com", "https://magpie.example.com"},
|
|
{"nas.lan:3425/", "http://nas.lan:3425"},
|
|
{"ftp://magpie.example.com", "http://127.0.0.1:3425"},
|
|
{"https://magpie.example.com/magpie?x=1", "http://127.0.0.1:3425"},
|
|
} {
|
|
t.Setenv("MAGPIE_PUBLIC_URL", c.public)
|
|
if got := advertisedURL(); got != c.want {
|
|
t.Errorf("MAGPIE_PUBLIC_URL=%q: %q, want %q", c.public, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Shared, the serve banner (docker logs) says where other machines reach
|
|
// the gateway: MAGPIE_PUBLIC_URL when a container sets it.
|
|
func TestShareLines(t *testing.T) {
|
|
t.Setenv("HOME", t.TempDir())
|
|
t.Setenv("USERPROFILE", os.Getenv("HOME"))
|
|
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
|
|
t.Setenv("MAGPIE_ADDR", "0.0.0.0:3425")
|
|
t.Setenv("MAGPIE_PUBLIC_URL", "http://192.168.1.20:3425/")
|
|
if l := shareLines(); len(l) != 0 {
|
|
t.Error("unshared:", l)
|
|
}
|
|
if err := settings.Save(settings.Settings{LAN: true, LANKey: "sk-magpie-k"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if l := strings.Join(shareLines(), "\n"); !strings.Contains(l, "http://192.168.1.20:3425") || strings.Contains(l, "container") {
|
|
t.Error("shared:", l)
|
|
}
|
|
}
|