Skip to content

Commit 6a79ea4

Browse files
committed
cors: add Private Network Access support for BYOC deployments
Chrome's Private Network Access (PNA) blocks requests from public origins to private network addresses. This breaks BYOC deployments where users access cloud.redpanda.com but their split-tunnel VPN routes Console traffic to private IP space (e.g., 100.64.0.0/10). Switch from go-chi/cors to rs/cors which has native AllowPrivateNetwork support. Add server.allowPrivateNetwork config option to enable it. Refs: CIAINFRA-2284
1 parent 64a7d66 commit 6a79ea4

5 files changed

Lines changed: 59 additions & 11 deletions

File tree

backend/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ require (
2424
github.com/fxamacker/cbor/v2 v2.9.0
2525
github.com/getkin/kin-openapi v0.133.0
2626
github.com/go-chi/chi/v5 v5.2.3
27-
github.com/go-chi/cors v1.2.2
2827
github.com/go-git/go-billy/v5 v5.6.2
2928
github.com/go-git/go-git/v5 v5.16.3
3029
github.com/go-viper/mapstructure/v2 v2.4.0
@@ -48,6 +47,7 @@ require (
4847
github.com/redpanda-data/common-go/net v0.1.1-0.20240429123545-4da3d2b371f7
4948
github.com/redpanda-data/common-go/rpadmin v0.2.0
5049
github.com/redpanda-data/common-go/rpsr v0.1.2
50+
github.com/rs/cors v1.11.1
5151
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
5252
github.com/stretchr/testify v1.11.1
5353
github.com/testcontainers/testcontainers-go v0.38.0

backend/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,6 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
162162
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
163163
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
164164
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
165-
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
166-
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
167165
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
168166
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
169167
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
@@ -422,6 +420,8 @@ github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe
422420
github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0=
423421
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
424422
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
423+
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
424+
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
425425
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
426426
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
427427
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=

backend/pkg/api/middlewares_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"net/http/httptest"
1515
"testing"
1616

17+
"github.com/rs/cors"
1718
"github.com/stretchr/testify/require"
1819
)
1920

@@ -40,3 +41,38 @@ func TestCreateHSTSHeaderMiddleware(t *testing.T) {
4041
})
4142
}
4243
}
44+
45+
func TestCORSPrivateNetworkAccess(t *testing.T) {
46+
// Test that rs/cors AllowPrivateNetwork option correctly handles
47+
// Chrome's Private Network Access preflight requests for BYOC deployments.
48+
for _, enabled := range []bool{true, false} {
49+
t.Run(fmt.Sprintf("AllowPrivateNetwork=%t", enabled), func(t *testing.T) {
50+
c := cors.New(cors.Options{
51+
AllowedOrigins: []string{"https://cloud.redpanda.com"},
52+
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
53+
AllowedHeaders: []string{"*"},
54+
AllowPrivateNetwork: enabled,
55+
})
56+
57+
handler := c.Handler(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
58+
w.Write([]byte("ok"))
59+
}))
60+
61+
// Simulate Chrome PNA preflight request
62+
req := httptest.NewRequest(http.MethodOptions, "/", http.NoBody)
63+
req.Header.Set("Origin", "https://cloud.redpanda.com")
64+
req.Header.Set("Access-Control-Request-Method", "GET")
65+
req.Header.Set("Access-Control-Request-Private-Network", "true")
66+
67+
rec := httptest.NewRecorder()
68+
handler.ServeHTTP(rec, req)
69+
70+
val := rec.Header().Get("Access-Control-Allow-Private-Network")
71+
if enabled {
72+
require.Equal(t, "true", val, "expected PNA header for BYOC deployments")
73+
} else {
74+
require.Empty(t, val, "should not set PNA header when disabled")
75+
}
76+
})
77+
}
78+
}

backend/pkg/api/routes.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ import (
2121
"github.com/cloudhut/common/rest"
2222
"github.com/go-chi/chi/v5"
2323
chimiddleware "github.com/go-chi/chi/v5/middleware"
24-
"github.com/go-chi/cors"
2524
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
2625
"github.com/prometheus/client_golang/prometheus"
2726
"github.com/prometheus/client_golang/prometheus/promhttp"
2827
commoninterceptor "github.com/redpanda-data/common-go/api/interceptor"
2928
"github.com/redpanda-data/common-go/api/metrics"
29+
"github.com/rs/cors"
3030
connectgateway "go.vallahaye.net/connect-gateway"
3131
"google.golang.org/protobuf/encoding/protojson"
3232

@@ -523,19 +523,23 @@ func (api *API) routes() *chi.Mux {
523523
baseRouter.Use(recoverer.Wrap)
524524
baseRouter.Use(chimiddleware.RealIP)
525525
baseRouter.Use(basePath.Wrap)
526-
baseRouter.Use(cors.Handler(cors.Options{
527-
AllowOriginFunc: func(r *http.Request, _ string) bool {
526+
// AllowPrivateNetwork enables Chrome's Private Network Access support for BYOC
527+
// deployments where browsers access cloud.redpanda.com but VPN routes traffic
528+
// to private IP addresses.
529+
baseRouter.Use(cors.New(cors.Options{
530+
AllowOriginRequestFunc: func(r *http.Request, _ string) bool {
528531
isAllowed := checkOriginFn(r)
529532
if !isAllowed {
530533
api.Logger.Debug("CORS check failed", slog.String("request_origin", r.Header.Get("Origin")))
531534
}
532535
return isAllowed
533536
},
534-
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"},
535-
AllowedHeaders: []string{"*"},
536-
AllowCredentials: true,
537-
MaxAge: 300, // Maximum value not ignored by any of major browsers
538-
}))
537+
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"},
538+
AllowedHeaders: []string{"*"},
539+
AllowCredentials: true,
540+
MaxAge: 300, // Maximum value not ignored by any of major browsers
541+
AllowPrivateNetwork: api.Cfg.REST.AllowPrivateNetwork,
542+
}).Handler)
539543

540544
// Fork a new router so that we can inject middlewares that are specific to the Connect API
541545
baseRouter.Group(func(router chi.Router) {

backend/pkg/config/server.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ type Server struct {
2727
// CSRF-attacks.
2828
AllowedOrigins []string `yaml:"allowedOrigins"`
2929

30+
// AllowPrivateNetwork enables Chrome's Private Network Access preflight handling.
31+
// When enabled, the server responds with Access-Control-Allow-Private-Network: true
32+
// for CORS preflight requests that include Access-Control-Request-Private-Network.
33+
// This is required for BYOC deployments where browsers access a public origin
34+
// (e.g., cloud.redpanda.com) but traffic routes to private IP addresses via VPN.
35+
// See: https://developer.chrome.com/blog/private-network-access-preflight
36+
AllowPrivateNetwork bool `yaml:"allowPrivateNetwork"`
37+
3038
// Debug allows to configure the pprof debug handler options.
3139
Debug DebugConfig `yaml:"debug"`
3240
}

0 commit comments

Comments
 (0)