gateway: add CORS allow-list for the public REST surface
Tests · Go / test (push) Successful in 1m42s
Tests · Go / test (pull_request) Successful in 1m45s
Tests · Integration / integration (pull_request) Successful in 1m36s

Adds a `GATEWAY_PUBLIC_HTTP_CORS_ALLOWED_ORIGINS` env-driven allow-list
on the public REST server so the dev UI on https://www.galaxy.lan can
call https://api.galaxy.lan without the browser blocking the
cross-origin response. Defaults to empty (no CORS) so the production
posture stays closed.

The middleware mounts before route classification and anti-abuse, so
OPTIONS preflights never charge against per-class rate-limit buckets.

`tools/dev-deploy/docker-compose.yml` opts the dev gateway into a
single allowed origin (`https://www.galaxy.lan`); local-dev keeps the
defaults because Vite proxies through the same origin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilia Denisov
2026-05-15 07:58:14 +02:00
parent 7bce67462c
commit 1855e43699
6 changed files with 220 additions and 0 deletions
+18
View File
@@ -158,6 +158,7 @@ func TestLoadFromEnvAppliesPublicAndAuthGRPCDefaults(t *testing.T) {
assert.Equal(t, defaultPublicHTTPReadTimeout, cfg.PublicHTTP.ReadTimeout)
assert.Equal(t, defaultPublicHTTPIdleTimeout, cfg.PublicHTTP.IdleTimeout)
assert.Equal(t, defaultPublicAuthUpstreamTimeout, cfg.PublicHTTP.AuthUpstreamTimeout)
assert.Empty(t, cfg.PublicHTTP.CORSAllowedOrigins, "default disables CORS")
assert.Equal(t, defaultAuthenticatedGRPCAddr, cfg.AuthenticatedGRPC.Addr)
assert.Equal(t, defaultAuthenticatedGRPCConnectionTimeout, cfg.AuthenticatedGRPC.ConnectionTimeout)
@@ -165,6 +166,22 @@ func TestLoadFromEnvAppliesPublicAndAuthGRPCDefaults(t *testing.T) {
assert.Equal(t, defaultAuthenticatedGRPCFreshnessWindow, cfg.AuthenticatedGRPC.FreshnessWindow)
}
func TestLoadFromEnvParsesCORSAllowedOrigins(t *testing.T) {
configEnvMu.Lock()
defer configEnvMu.Unlock()
resetEnv(t)
setBaseRequiredEnv(t)
t.Setenv(publicHTTPCORSAllowedOriginsEnvVar, "https://www.galaxy.lan, , https://staging.galaxy.lan")
cfg, err := LoadFromEnv()
require.NoError(t, err)
assert.Equal(t,
[]string{"https://www.galaxy.lan", "https://staging.galaxy.lan"},
cfg.PublicHTTP.CORSAllowedOrigins,
"comma-separated list is split, whitespace-trimmed, and empty segments dropped")
}
// resetEnv clears every env var the gateway config might read so that
// individual tests can build the exact environment they need without
// leakage from a previous test.
@@ -179,6 +196,7 @@ func resetEnv(t *testing.T) {
publicHTTPReadTimeoutEnvVar,
publicHTTPIdleTimeoutEnvVar,
publicAuthUpstreamTimeoutEnvVar,
publicHTTPCORSAllowedOriginsEnvVar,
backendHTTPURLEnvVar,
backendGRPCPushURLEnvVar,
backendGatewayClientIDEnvVar,