From f0eb28ffc24dbfbe926a5a2c4433ac3abe64f7f0 Mon Sep 17 00:00:00 2001 From: Julian Sommer <115516918+juliansommer@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:45:36 +1000 Subject: [PATCH] fix: remove gateway csp --- spinifex/gateway/auth.go | 6 --- spinifex/gateway/auth_test.go | 14 ------- spinifex/gateway/gateway.go | 64 ---------------------------- spinifex/gateway/gateway_test.go | 72 -------------------------------- 4 files changed, 156 deletions(-) diff --git a/spinifex/gateway/auth.go b/spinifex/gateway/auth.go index 65ee95592..6e01d7470 100644 --- a/spinifex/gateway/auth.go +++ b/spinifex/gateway/auth.go @@ -31,12 +31,6 @@ const ( func (gw *GatewayConfig) SigV4AuthMiddleware() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Skip OPTIONS requests (CORS preflight) - if r.Method == http.MethodOptions { - next.ServeHTTP(w, r) - return - } - authHeader := r.Header.Get("Authorization") if authHeader == "" { gw.writeSigV4Error(w, r, awserrors.ErrorMissingAuthenticationToken) diff --git a/spinifex/gateway/auth_test.go b/spinifex/gateway/auth_test.go index 1e96e3c42..68dffd944 100644 --- a/spinifex/gateway/auth_test.go +++ b/spinifex/gateway/auth_test.go @@ -333,20 +333,6 @@ func TestSigV4Auth_ValidSignature(t *testing.T) { } } -func TestSigV4Auth_OptionsSkipsAuth(t *testing.T) { - handler := setupTestApp(testAccessKey, testSecretKey) - - req := httptest.NewRequest(http.MethodOptions, "/", nil) - req.Host = "localhost:9999" - // No Authorization header - - resp := doRequest(handler, req) - - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected status 200 for OPTIONS, got %d", resp.StatusCode) - } -} - func TestSigV4Auth_ValidSignatureWithBody(t *testing.T) { handler := setupTestApp(testAccessKey, testSecretKey) diff --git a/spinifex/gateway/gateway.go b/spinifex/gateway/gateway.go index 6c0950f59..8548c0ced 100644 --- a/spinifex/gateway/gateway.go +++ b/spinifex/gateway/gateway.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "log/slog" - "net" "net/http" "net/url" "os" @@ -98,9 +97,6 @@ func (gw *GatewayConfig) SetupRoutes() http.Handler { r.Use(slogRequestLogger) } - // CORS middleware for browser requests - r.Use(corsMiddleware) - // AWS SigV4 authentication middleware r.Use(gw.SigV4AuthMiddleware()) @@ -110,66 +106,6 @@ func (gw *GatewayConfig) SetupRoutes() http.Handler { return r } -// corsAllowedOrigins builds the set of allowed CORS origins from localhost and -// all local non-loopback IPs on the spinifex-ui port (default 3000). This allows -// the UI to be accessed from any local address, not just localhost. -func corsAllowedOrigins() map[string]struct{} { - origins := map[string]struct{}{ - "https://localhost:3000": {}, - } - ifaces, err := net.Interfaces() - if err != nil { - return origins - } - for _, iface := range ifaces { - if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 { - continue - } - addrs, err := iface.Addrs() - if err != nil { - continue - } - for _, addr := range addrs { - var ip net.IP - switch v := addr.(type) { - case *net.IPNet: - ip = v.IP - case *net.IPAddr: - ip = v.IP - } - if ip == nil || ip.IsLoopback() || ip.To4() == nil { - continue - } - origins[fmt.Sprintf("https://%s:3000", ip.String())] = struct{}{} - } - } - return origins -} - -// corsMiddleware adds CORS headers for browser requests from the spinifex-ui. -func corsMiddleware(next http.Handler) http.Handler { - allowed := corsAllowedOrigins() - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - origin := r.Header.Get("Origin") - if origin == "" { - origin = "https://localhost:3000" - } - if _, ok := allowed[origin]; ok { - w.Header().Set("Access-Control-Allow-Origin", origin) - } else { - w.Header().Set("Access-Control-Allow-Origin", "https://localhost:3000") - } - w.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,HEAD,OPTIONS") - w.Header().Set("Access-Control-Allow-Headers", "*") - w.Header().Set("Access-Control-Allow-Credentials", "true") - if r.Method == http.MethodOptions { - w.WriteHeader(http.StatusNoContent) - return - } - next.ServeHTTP(w, r) - }) -} - // Note, custom endpoints can be configured via ENV vars to the AWS SDK/CLI tool, with individual endpoints depending the service // AWS_ENDPOINT_URL_EC2=https://localhost:9999/ aws --no-verify-ssl ec2 describe-instances // aws --endpoint-url https://localhost:9999/ --no-verify-ssl eks list-clusters diff --git a/spinifex/gateway/gateway_test.go b/spinifex/gateway/gateway_test.go index 38963ae33..0d0768f79 100644 --- a/spinifex/gateway/gateway_test.go +++ b/spinifex/gateway/gateway_test.go @@ -882,78 +882,6 @@ func TestIsNATSTransient(t *testing.T) { assert.False(t, isNATSTransient(errors.New("some other error"))) } -func TestCorsMiddleware_GETRequest(t *testing.T) { - nextCalled := false - next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - nextCalled = true - w.WriteHeader(http.StatusOK) - }) - - handler := corsMiddleware(next) - req := httptest.NewRequest(http.MethodGet, "/", nil) - w := httptest.NewRecorder() - handler.ServeHTTP(w, req) - - assert.True(t, nextCalled) - assert.Equal(t, "https://localhost:3000", w.Header().Get("Access-Control-Allow-Origin")) - assert.Equal(t, "GET,POST,PUT,DELETE,HEAD,OPTIONS", w.Header().Get("Access-Control-Allow-Methods")) - assert.Equal(t, "*", w.Header().Get("Access-Control-Allow-Headers")) - assert.Equal(t, "true", w.Header().Get("Access-Control-Allow-Credentials")) -} - -func TestCorsMiddleware_OPTIONSRequest(t *testing.T) { - nextCalled := false - next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - nextCalled = true - }) - - handler := corsMiddleware(next) - req := httptest.NewRequest(http.MethodOptions, "/", nil) - w := httptest.NewRecorder() - handler.ServeHTTP(w, req) - - assert.False(t, nextCalled, "next handler should not be called for OPTIONS") - assert.Equal(t, http.StatusNoContent, w.Code) - assert.Equal(t, "https://localhost:3000", w.Header().Get("Access-Control-Allow-Origin")) -} - -func TestCorsMiddleware_LocalIPOrigin(t *testing.T) { - next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - - handler := corsMiddleware(next) - - // Request from a local IP should be reflected - allowed := corsAllowedOrigins() - for origin := range allowed { - if origin == "https://localhost:3000" { - continue - } - req := httptest.NewRequest(http.MethodGet, "/", nil) - req.Header.Set("Origin", origin) - w := httptest.NewRecorder() - handler.ServeHTTP(w, req) - assert.Equal(t, origin, w.Header().Get("Access-Control-Allow-Origin")) - return // test one IP is enough - } -} - -func TestCorsMiddleware_UnknownOrigin(t *testing.T) { - next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - - handler := corsMiddleware(next) - req := httptest.NewRequest(http.MethodGet, "/", nil) - req.Header.Set("Origin", "https://evil.example.com") - w := httptest.NewRecorder() - handler.ServeHTTP(w, req) - - // Unknown origins fall back to localhost - assert.Equal(t, "https://localhost:3000", w.Header().Get("Access-Control-Allow-Origin")) -} - func TestSlogRequestLogger_CallsNext(t *testing.T) { nextCalled := false next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {