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:
Jeff Mitchell authored and GitHub committed 2020-05-21 12:12:08 -04:00
1 parent 66978d637a
commit fafdcbbfa4
9 files changed
+423 -37

No files matched your search

+87 -3
View File
@@ -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) {