CORS support (#73)
This adds per-listener CORS support. Logic is ported from Vault, along with a fully new test suite.
This commit is contained in:
1 parent
66978d637a
commit
fafdcbbfa4
9 files changed
+423
-37
No files matched your search
@@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
@@ -9,6 +10,8 @@ import (
|
||||
|
||||
"github.com/grpc-ecosystem/grpc-gateway/runtime"
|
||||
"github.com/hashicorp/vault/internalshared/configutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/strutil"
|
||||
"github.com/hashicorp/watchtower/api"
|
||||
"github.com/hashicorp/watchtower/globals"
|
||||
"github.com/hashicorp/watchtower/internal/gen/controller/api/services"
|
||||
"github.com/hashicorp/watchtower/internal/servers/controller/handlers/host_catalogs"
|
||||
@@ -29,9 +32,10 @@ func (c *Controller) handler(props HandlerProperties) http.Handler {
|
||||
|
||||
mux.Handle("/v1/", handleGrpcGateway(c))
|
||||
|
||||
genericWrappedHandler := wrapGenericHandler(mux, c, props)
|
||||
corsWrappedHandler := wrapHandlerWithCors(mux, props)
|
||||
commonWrappedHandler := wrapHandlerWithCommonFuncs(corsWrappedHandler, c, props)
|
||||
|
||||
return genericWrappedHandler
|
||||
return commonWrappedHandler
|
||||
}
|
||||
|
||||
func handleGrpcGateway(c *Controller) http.Handler {
|
||||
@@ -47,7 +51,7 @@ func handleGrpcGateway(c *Controller) http.Handler {
|
||||
return mux
|
||||
}
|
||||
|
||||
func wrapGenericHandler(h http.Handler, c *Controller, props HandlerProperties) http.Handler {
|
||||
func wrapHandlerWithCommonFuncs(h http.Handler, c *Controller, props HandlerProperties) http.Handler {
|
||||
var maxRequestDuration time.Duration
|
||||
var maxRequestSize int64
|
||||
if props.ListenerConfig != nil {
|
||||
@@ -94,6 +98,86 @@ func wrapGenericHandler(h http.Handler, c *Controller, props HandlerProperties)
|
||||
})
|
||||
}
|
||||
|
||||
func wrapHandlerWithCors(h http.Handler, props HandlerProperties) http.Handler {
|
||||
allowedMethods := []string{
|
||||
http.MethodDelete,
|
||||
http.MethodGet,
|
||||
http.MethodOptions,
|
||||
http.MethodPost,
|
||||
http.MethodPatch,
|
||||
}
|
||||
|
||||
allowedOrigins := props.ListenerConfig.CorsAllowedOrigins
|
||||
|
||||
allowedHeaders := append([]string{
|
||||
"Content-Type",
|
||||
"X-Requested-With",
|
||||
"Authorization",
|
||||
}, props.ListenerConfig.CorsAllowedHeaders...)
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if !props.ListenerConfig.CorsEnabled {
|
||||
h.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
origin := req.Header.Get("Origin")
|
||||
|
||||
if origin == "" {
|
||||
// Serve directly
|
||||
h.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// Check origin
|
||||
var valid bool
|
||||
switch {
|
||||
case len(allowedOrigins) == 0:
|
||||
// not valid
|
||||
|
||||
case len(allowedOrigins) == 1 && allowedOrigins[0] == "*":
|
||||
valid = true
|
||||
|
||||
default:
|
||||
valid = strutil.StrListContains(allowedOrigins, origin)
|
||||
}
|
||||
if !valid {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
|
||||
err := &api.Error{
|
||||
Status: api.Int(http.StatusForbidden),
|
||||
Code: api.String("origin forbidden"),
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
enc.Encode(err)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Method == http.MethodOptions &&
|
||||
!strutil.StrListContains(allowedMethods, req.Header.Get("Access-Control-Request-Method")) {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
w.Header().Set("Vary", "Origin")
|
||||
|
||||
// Apply headers for preflight requests
|
||||
if req.Method == http.MethodOptions {
|
||||
w.Header().Set("Access-Control-Allow-Methods", strings.Join(allowedMethods, ", "))
|
||||
w.Header().Set("Access-Control-Allow-Headers", strings.Join(allowedHeaders, ", "))
|
||||
w.Header().Set("Access-Control-Max-Age", "300")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
h.ServeHTTP(w, req)
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
func WrapForwardedForHandler(h http.Handler, authorizedAddrs []*sockaddr.SockAddrMarshaler, rejectNotPresent, rejectNonAuthz bool, hopSkips int) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in new issue
Block a user