fix(s3api): missing Vary: Origin header on non-CORS and OPTIONS requests (#8072)
* fix: Refactor CORS middleware to consistently apply the `Vary: Origin` header when a configuration exists and streamline request processing logic. * fix: Add Vary: Origin header to CORS OPTIONS responses and refactor request handling for clarity and correctness. * fix: update CORS middleware tests to correctly parse and check for 'Origin' in Vary header. * refactor: extract `hasVaryOrigin` helper function to simplify Vary header checks in tests. * test: Remove `Vary: Origin` header from CORS test expectations. * refactor: consolidate CORS request handling into a new `processCORS` method using a `next` callback.
This commit is contained in:
@@ -69,103 +69,56 @@ func (m *Middleware) getCORSConfig(bucket string) (*CORSConfiguration, bool) {
|
||||
// Handler returns the CORS middleware handler
|
||||
func (m *Middleware) Handler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse CORS request
|
||||
corsReq := ParseRequest(r)
|
||||
|
||||
// If not a CORS request, continue normally
|
||||
if corsReq.Origin == "" {
|
||||
m.processCORS(w, r, func() {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract bucket from request
|
||||
bucket, _ := s3_constants.GetBucketAndObject(r)
|
||||
if bucket == "" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Get CORS configuration (bucket-specific or fallback) BEFORE checking bucket existence
|
||||
// This ensures CORS headers are applied consistently regardless of bucket existence,
|
||||
// preventing information disclosure about whether a bucket exists
|
||||
config, found := m.getCORSConfig(bucket)
|
||||
if !found {
|
||||
// No CORS configuration at all, handle based on request type
|
||||
if corsReq.IsPreflightRequest {
|
||||
// Preflight request without CORS config should fail
|
||||
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
|
||||
return
|
||||
}
|
||||
// Non-preflight request, continue normally
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Evaluate CORS request
|
||||
corsResp, err := EvaluateRequest(config, corsReq)
|
||||
if err != nil {
|
||||
glog.V(3).Infof("CORS evaluation failed for bucket %s: %v", bucket, err)
|
||||
if corsReq.IsPreflightRequest {
|
||||
// Preflight request that doesn't match CORS rules should fail
|
||||
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
|
||||
return
|
||||
}
|
||||
// Non-preflight request, continue normally but without CORS headers
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Apply CORS headers early, before bucket existence check
|
||||
// This ensures consistent CORS behavior and prevents information disclosure
|
||||
ApplyHeaders(w, corsResp)
|
||||
|
||||
// Handle preflight requests - return success immediately without checking bucket existence
|
||||
// This matches AWS S3 behavior where preflight requests succeed even for non-existent buckets
|
||||
if corsReq.IsPreflightRequest {
|
||||
// Preflight request should return 200 OK with just CORS headers
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
// For actual requests, continue with normal processing
|
||||
// The handler will check bucket existence and return appropriate errors (e.g., NoSuchBucket)
|
||||
// but CORS headers have already been applied above
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// HandleOptionsRequest handles OPTIONS requests for CORS preflight
|
||||
func (m *Middleware) HandleOptionsRequest(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse CORS request
|
||||
m.processCORS(w, r, func() {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
||||
// processCORS handles the common CORS logic for both regular and preflight requests.
|
||||
// It takes a next callback which is executed if the request flow proceeds (i.e., not short-circuited by CORS errors or preflight responses).
|
||||
func (m *Middleware) processCORS(w http.ResponseWriter, r *http.Request, next func()) {
|
||||
corsReq := ParseRequest(r)
|
||||
|
||||
// If not a CORS request, return OK
|
||||
if corsReq.Origin == "" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract bucket from request
|
||||
bucket, _ := s3_constants.GetBucketAndObject(r)
|
||||
|
||||
// 1. Basic Validation
|
||||
if bucket == "" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
// Get CORS configuration (bucket-specific or fallback) BEFORE checking bucket existence
|
||||
// This ensures CORS headers are applied consistently regardless of bucket existence
|
||||
config, found := m.getCORSConfig(bucket)
|
||||
if !found {
|
||||
// No CORS configuration at all for OPTIONS request should return access denied
|
||||
// 2. Load Configuration
|
||||
config, hasConfig := m.getCORSConfig(bucket)
|
||||
|
||||
// 3. Apply Vary Header (Always applied if config exists)
|
||||
if hasConfig {
|
||||
w.Header().Add("Vary", "Origin")
|
||||
}
|
||||
|
||||
// 4. Handle Non-CORS Requests
|
||||
if corsReq.Origin == "" {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
// 5. Handle Missing Configuration
|
||||
if !hasConfig {
|
||||
if corsReq.IsPreflightRequest {
|
||||
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
// Evaluate CORS request
|
||||
// 6. Evaluate CORS Request
|
||||
corsResp, err := EvaluateRequest(config, corsReq)
|
||||
if err != nil {
|
||||
glog.V(3).Infof("CORS evaluation failed for bucket %s: %v", bucket, err)
|
||||
@@ -173,11 +126,17 @@ func (m *Middleware) HandleOptionsRequest(w http.ResponseWriter, r *http.Request
|
||||
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
|
||||
return
|
||||
}
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
// 7. Success Case
|
||||
ApplyHeaders(w, corsResp)
|
||||
|
||||
if corsReq.IsPreflightRequest {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
// Apply CORS headers and return success
|
||||
ApplyHeaders(w, corsResp)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
next()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user