gui: respect explicit --rc-allow-origin instead of always deriving it from the bind address

Fixes #9640

The auto-derived CORS origin logic (added in #9603 for the wildcard-bind
case) ran unconditionally, so it clobbered an explicitly-passed
--rc-allow-origin too - the flag was silently ignored. That breaks the
documented Docker/remote-browser setup where the bind address and the
address the browser actually reaches the GUI at are different.

Now it only kicks in when the user hasn't set --rc-allow-origin
themselves.
This commit is contained in:
Kyue
2026-07-23 16:35:22 +01:00
committed by Nick Craig-Wood
parent 2f3895fa3c
commit 7de6d611ba
2 changed files with 85 additions and 14 deletions
+24 -14
View File
@@ -175,20 +175,8 @@ For more help see [the GUI docs](/gui/).
}
}
// When the GUI is bound to a wildcard address the bound origin
// (e.g. http://[::]:5522) is never what a browser sends in its
// Origin header, and the GUI may be reached via any number of
// hosts (localhost, a LAN IP, a Docker host), so no single
// origin can match them all.
switch addr, _ := guiServer.Addr().(*net.TCPAddr); {
case addr == nil || !addr.IP.IsUnspecified():
opt.HTTP.AllowOrigin = guiOrigin
case !opt.NoAuth:
opt.HTTP.AllowOrigin = "*"
default:
opt.HTTP.AllowOrigin = guiOrigin
fs.Logf(nil, "GUI bound to a wildcard address with --no-auth: browsers can only use the API from %s. Enable auth or bind --addr to a specific host.", guiOrigin)
}
addr, _ := guiServer.Addr().(*net.TCPAddr)
opt.HTTP.AllowOrigin = resolveAllowOrigin(command.Flags().Changed("rc-allow-origin"), opt.HTTP.AllowOrigin, guiOrigin, addr, opt.NoAuth)
// Start the RC server (unchanged rcserver.Start)
rcServer, err := rcserver.Start(ctx, &opt)
@@ -253,6 +241,28 @@ func originFromURL(rawURL string) string {
return u.Scheme + "://" + u.Host
}
// resolveAllowOrigin picks the Access-Control-Allow-Origin value for the RC
// API server. An explicit --rc-allow-origin always wins. Otherwise a value is
// derived from how the GUI is bound: the bound origin (e.g. http://[::]:5522)
// is never what a browser sends in its Origin header when the GUI is bound to
// a wildcard address, and the GUI may be reached via any number of hosts
// (localhost, a LAN IP, a Docker host), so no single origin can match them
// all.
func resolveAllowOrigin(explicitAllowOrigin bool, currentAllowOrigin, guiOrigin string, addr *net.TCPAddr, noAuth bool) string {
if explicitAllowOrigin {
return currentAllowOrigin
}
switch {
case addr == nil || !addr.IP.IsUnspecified():
return guiOrigin
case !noAuth:
return "*"
default:
fs.Logf(nil, "GUI bound to a wildcard address with --no-auth: browsers can only use the API from %s. Enable auth or bind --addr to a specific host.", guiOrigin)
return guiOrigin
}
}
// guiSourceFS opens the GUI bundle at the given path. An empty path
// returns the embedded bundle (read from the zip embedded in the binary).
// The returned cleanup func must be called on shutdown (no-op for the
+61
View File
@@ -5,6 +5,7 @@ import (
"compress/gzip"
"io"
iofs "io/fs"
"net"
"net/http"
"net/http/httptest"
"os"
@@ -99,6 +100,66 @@ func TestOriginFromURL(t *testing.T) {
}
}
func TestResolveAllowOrigin(t *testing.T) {
wildcard := &net.TCPAddr{IP: net.IPv4zero, Port: 5533}
specific := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 5533}
tests := []struct {
name string
explicitAllowOrigin bool
currentAllowOrigin string
guiOrigin string
addr *net.TCPAddr
noAuth bool
want string
}{
{
name: "explicit flag wins even on a wildcard bind",
explicitAllowOrigin: true,
currentAllowOrigin: "http://192.0.2.10:5522",
guiOrigin: "http://[::]:5580",
addr: wildcard,
noAuth: false,
want: "http://192.0.2.10:5522",
},
{
name: "explicit flag wins on a specific bind too",
explicitAllowOrigin: true,
currentAllowOrigin: "http://192.0.2.10:5522",
guiOrigin: "http://127.0.0.1:5580",
addr: specific,
noAuth: false,
want: "http://192.0.2.10:5522",
},
{
name: "specific bind falls back to the gui origin",
guiOrigin: "http://127.0.0.1:5580",
addr: specific,
want: "http://127.0.0.1:5580",
},
{
name: "wildcard bind with auth falls back to *",
guiOrigin: "http://[::]:5580",
addr: wildcard,
noAuth: false,
want: "*",
},
{
name: "wildcard bind without auth falls back to the gui origin",
guiOrigin: "http://[::]:5580",
addr: wildcard,
noAuth: true,
want: "http://[::]:5580",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := resolveAllowOrigin(tt.explicitAllowOrigin, tt.currentAllowOrigin, tt.guiOrigin, tt.addr, tt.noAuth)
assert.Equal(t, tt.want, got)
})
}
}
// newTestHandler returns a guiHandler backed by the embedded GUI
// bundle, or skips the test if it is not present (i.e. `make fetch-gui`
// has not been run).