Skip to content

cors: add Private Network Access support for BYOC deployments#2207

Open
birdayz wants to merge 1 commit intomasterfrom
jb/ciainfra-2284-pna-support
Open

cors: add Private Network Access support for BYOC deployments#2207
birdayz wants to merge 1 commit intomasterfrom
jb/ciainfra-2284-pna-support

Conversation

@birdayz
Copy link
Contributor

@birdayz birdayz commented Feb 3, 2026

What

Add Chrome Private Network Access (PNA) support via new server.allowPrivateNetwork config option.

Why

BYOC users with split-tunnel VPN report Console works in Safari/Firefox but fails in Chrome. Chrome's PNA enforcement blocks requests from public origins (cloud.redpanda.com) to private network addresses (100.64.0.0/10 range used by their VPN).

The fix is to respond with Access-Control-Allow-Private-Network: true on CORS preflight requests.

Implementation details

Config: New server.allowPrivateNetwork boolean option (default false). Environment variable: SERVER_ALLOWPRIVATENETWORK.

Middleware: The go-chi/cors library lacks native PNA support, so this wraps the CORS handler to inject the response header when browsers send Access-Control-Request-Private-Network: true.

BYOC deployments should set allowPrivateNetwork: true in their Console configuration.

References

@birdayz birdayz force-pushed the jb/ciainfra-2284-pna-support branch 4 times, most recently from 4f4c6fc to bbc32fc Compare February 3, 2026 18:44
@birdayz birdayz requested review from graham-rp and weeco February 3, 2026 18:46
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
@birdayz birdayz force-pushed the jb/ciainfra-2284-pna-support branch from bbc32fc to 6a79ea4 Compare February 3, 2026 18:47
@weeco
Copy link
Contributor

weeco commented Feb 3, 2026

@claude Please review

@claude
Copy link

claude bot commented Feb 3, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

@claude
Copy link

claude bot commented Feb 3, 2026

Claude finished @weeco's task in 1m 38s —— View job


Code Review Complete ✓

  • Read repository guidelines (CLAUDE.md)
  • Review configuration changes (server.go)
  • Review middleware implementation (routes.go)
  • Review test coverage (middlewares_test.go)
  • Review dependency changes (go.mod, go.sum)
  • Provide comprehensive feedback

Summary

This PR successfully implements Chrome Private Network Access (PNA) support for BYOC deployments. The implementation is clean, well-tested, and solves a real problem where Chrome blocks requests from public origins to private network addresses.

Positive Findings

  1. Excellent test coverage - The new test in middlewares_test.go:45-78 properly validates both enabled and disabled states of the PNA feature
  2. Good documentation - Clear inline comments explaining why this is needed and linking to Chrome's spec
  3. Sensible default - AllowPrivateNetwork defaults to false, requiring explicit opt-in for BYOC deployments
  4. Appropriate library change - Switching from go-chi/cors to rs/cors makes sense since rs/cors has native PNA support

Issues Found

1. Missing Default Value Initialization (Minor)

Location: backend/pkg/config/server.go:52-67

The SetDefaults() function doesn't explicitly set AllowPrivateNetwork to false. While Go defaults booleans to false, it's better to be explicit for consistency with the rest of the codebase pattern.

func (s *Server) SetDefaults() {
	s.Config.SetDefaults()
	// ... existing code ...
	s.HTTPServerWriteTimeout = 32 * time.Minute
	s.AllowedOrigins = nil
+	s.AllowPrivateNetwork = false  // Explicitly set default for clarity
	// Debug is enabled by default for backward compatibility.
	s.Debug.Enabled = true
	// ...
}

Recommendation: Add explicit initialization. Fix this →

2. Comment Placement Could Be Clearer (Nitpick)

Location: backend/pkg/api/routes.go:526-528

The comment about AllowPrivateNetwork is placed above the entire cors.New() middleware setup, but it only relates to one specific option. This could be confusing for future maintainers.

Current:

// AllowPrivateNetwork enables Chrome's Private Network Access support for BYOC
// deployments where browsers access cloud.redpanda.com but VPN routes traffic
// to private IP addresses.
baseRouter.Use(cors.New(cors.Options{
	AllowOriginRequestFunc: func(r *http.Request, _ string) bool {
		// ...
	},
	AllowedMethods:      []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"},
	AllowedHeaders:      []string{"*"},
	AllowCredentials:    true,
	MaxAge:              300, // Maximum value not ignored by any of major browsers
	AllowPrivateNetwork: api.Cfg.REST.AllowPrivateNetwork,
}).Handler)

Recommendation: Move the comment inline or closer to the specific field:

baseRouter.Use(cors.New(cors.Options{
	AllowOriginRequestFunc: func(r *http.Request, _ string) bool {
		// ...
	},
	AllowedMethods:      []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"},
	AllowedHeaders:      []string{"*"},
	AllowCredentials:    true,
	MaxAge:              300, // Maximum value not ignored by any of major browsers
	// AllowPrivateNetwork enables Chrome's Private Network Access support for BYOC
	// deployments where browsers access cloud.redpanda.com but VPN routes traffic
	// to private IP addresses. See: https://developer.chrome.com/blog/private-network-access-preflight
	AllowPrivateNetwork: api.Cfg.REST.AllowPrivateNetwork,
}).Handler)

Fix this →

3. Test Could Be More Comprehensive (Enhancement)

Location: backend/pkg/api/middlewares_test.go:45-78

The test validates the header is set correctly, but doesn't test:

  • Non-preflight requests (regular GET/POST) shouldn't get the PNA header
  • Requests without Access-Control-Request-Private-Network header shouldn't get PNA header even when enabled

Recommendation: Consider adding test cases for:

// Test regular request (non-OPTIONS) doesn't get PNA header
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
req.Header.Set("Origin", "https://cloud.redpanda.com")
// Should not have PNA header even when enabled

// Test OPTIONS without PNA request header
req := httptest.NewRequest(http.MethodOptions, "/", http.NoBody)
req.Header.Set("Origin", "https://cloud.redpanda.com")
req.Header.Set("Access-Control-Request-Method", "GET")
// No Access-Control-Request-Private-Network header
// Should not have PNA response header

This would ensure the feature only activates during proper PNA preflight requests.

Dependency Changes

The switch from go-chi/cors@v1.2.2 to rs/cors@v1.11.1 is appropriate:

  • rs/cors is well-maintained and widely used (4.3k+ stars)
  • It has native AllowPrivateNetwork support (added in v1.11.0)
  • The API is similar enough that migration is straightforward
  • No security advisories on either library

Security Considerations

Safe: The PNA feature is opt-in (defaults to false) and only affects CORS preflight responses. It doesn't weaken security—it explicitly opts into Chrome's Private Network Access enforcement, which is actually a security feature to protect against malicious public sites attacking private networks.

Recommendations Priority

  1. High: None - the implementation is production-ready
  2. Medium: Add explicit default initialization for AllowPrivateNetwork (issue Add readiness & liveness check #1)
  3. Low: Improve comment placement (issue Add prometheus metrics #2), enhance test coverage (issue Create helm chart #3)

Verdict

✅ LGTM - This PR is ready to merge. The minor issues identified are nice-to-haves but don't block the implementation. The core functionality is solid, well-tested, and solves the stated problem effectively.


| Branch: jb/ciainfra-2284-pna-support

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants