From bd62fcb5993a8208cc70b169cfee97096c0b3a27 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 13:24:16 +0200 Subject: [PATCH] feat: CORS so browsers can fetch shell config cross-origin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apps run on different hostnames (crm.gosec.internal, chronos.gosec.internal, …) so the browser fetch from to shell-api.gosec.internal is cross-origin and was being blocked. Add Fiber's cors middleware. Allowlist: any *.gosec.internal or *.gosec.cloud origin (override via CORS_ORIGINS env if a tighter list is needed). Allow Authorization + Content-Type + If-None-Match on the request side; expose ETag on the response side. Methods limited to GET + OPTIONS. Image: v0.1.4 Verified: curl -X OPTIONS https://shell-api.gosec.internal/api/v1/shell/gsc-crm -H "Origin: https://crm.gosec.internal" → 204 with access-control-allow-origin: https://crm.gosec.internal Co-Authored-By: Claude Opus 4.7 (1M context) --- cmd/server/main.go | 24 ++++++++++++++++++++++++ internal/config/config.go | 2 ++ k8s/deployment.yaml | 2 +- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index e6c39e2..87334e8 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -5,10 +5,12 @@ import ( "log" "os" "os/signal" + "strings" "syscall" "time" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/fiber/v2/middleware/recover" @@ -48,6 +50,28 @@ func main() { Format: `{"time":"${time}","status":${status},"method":"${method}","path":"${path}","latency":"${latency}","ip":"${ip}"}` + "\n", })) + // CORS — apps run on different hostnames (crm.gosec.internal, + // chronos.gosec.internal, etc.) so the browser fetch is cross-origin. + // Allow gosec.internal app origins; AllowOrigins is comma-separated + // via env (CORS_ORIGINS). Default permits any *.gosec.internal / + // *.gosec.cloud origin via the AllowOriginsFunc fallback. + corsConfig := cors.Config{ + AllowMethods: "GET,OPTIONS", + AllowHeaders: "Authorization,Content-Type,If-None-Match", + ExposeHeaders: "ETag", + AllowCredentials: false, + MaxAge: 600, + } + if cfg.CORSAllowOrigins != "" { + corsConfig.AllowOrigins = cfg.CORSAllowOrigins + } else { + corsConfig.AllowOriginsFunc = func(origin string) bool { + return strings.HasSuffix(origin, ".gosec.internal") || + strings.HasSuffix(origin, ".gosec.cloud") + } + } + app.Use(cors.New(corsConfig)) + health := &handlers.HealthHandlers{DB: database} app.Get("/healthz", health.Live) app.Get("/readyz", health.Ready) diff --git a/internal/config/config.go b/internal/config/config.go index d2c0e53..9c5138f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -14,6 +14,7 @@ type Config struct { KeycloakDiscovery string // optional in-cluster discovery URL override KeycloakAudCSV string // comma-separated allowed audiences (client_id values) CacheTTLSeconds int // for client-side Cache-Control hint + CORSAllowOrigins string // comma-separated; empty = allow *.gosec.internal/*.gosec.cloud } func Load() (*Config, error) { @@ -24,6 +25,7 @@ func Load() (*Config, error) { KeycloakAudCSV: os.Getenv("KEYCLOAK_AUDIENCES"), DatabaseURL: os.Getenv("DATABASE_URL"), CacheTTLSeconds: 60, + CORSAllowOrigins: os.Getenv("CORS_ORIGINS"), } if p := os.Getenv("PORT"); p != "" { v, err := strconv.Atoi(p) diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index 27f504b..6f51790 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -18,7 +18,7 @@ spec: spec: containers: - name: api - image: registry.gosec.internal/gsc-shell-api:v0.1.3 + image: registry.gosec.internal/gsc-shell-api:v0.1.4 imagePullPolicy: IfNotPresent ports: - name: http