Enable CORS configuration
This commit is contained in:
parent
883f90dded
commit
d2b38e6ac4
13 changed files with 888 additions and 89 deletions
|
@ -126,6 +126,21 @@ type Headers struct {
|
|||
CustomRequestHeaders map[string]string `json:"customRequestHeaders,omitempty"`
|
||||
CustomResponseHeaders map[string]string `json:"customResponseHeaders,omitempty"`
|
||||
|
||||
// AccessControlAllowCredentials is only valid if true. false is ignored.
|
||||
AccessControlAllowCredentials bool `json:"AccessControlAllowCredentials,omitempty"`
|
||||
// AccessControlAllowHeaders must be used in response to a preflight request with Access-Control-Request-Headers set.
|
||||
AccessControlAllowHeaders []string `json:"AccessControlAllowHeaders,omitempty"`
|
||||
// AccessControlAllowMethods must be used in response to a preflight request with Access-Control-Request-Method set.
|
||||
AccessControlAllowMethods []string `json:"AccessControlAllowMethods,omitempty"`
|
||||
// AccessControlAllowOrigin Can be "origin-list-or-null" or "*". From (https://www.w3.org/TR/cors/#access-control-allow-origin-response-header)
|
||||
AccessControlAllowOrigin string `json:"AccessControlAllowOrigin,omitempty"`
|
||||
// AccessControlExposeHeaders sets valid headers for the response.
|
||||
AccessControlExposeHeaders []string `json:"AccessControlExposeHeaders,omitempty"`
|
||||
// AccessControlMaxAge sets the time that a preflight request may be cached.
|
||||
AccessControlMaxAge int64 `json:"AccessControlMaxAge,omitempty"`
|
||||
// AddVaryHeader controls if the Vary header is automatically added/updated when the AccessControlAllowOrigin is set.
|
||||
AddVaryHeader bool `json:"AddVaryHeader,omitempty"`
|
||||
|
||||
AllowedHosts []string `json:"allowedHosts,omitempty"`
|
||||
HostsProxyHeaders []string `json:"hostsProxyHeaders,omitempty"`
|
||||
SSLRedirect bool `json:"sslRedirect,omitempty"`
|
||||
|
@ -154,6 +169,17 @@ func (h *Headers) HasCustomHeadersDefined() bool {
|
|||
len(h.CustomRequestHeaders) != 0)
|
||||
}
|
||||
|
||||
// HasCorsHeadersDefined checks to see if any of the cors header elements have been set
|
||||
func (h *Headers) HasCorsHeadersDefined() bool {
|
||||
return h != nil && (h.AccessControlAllowCredentials ||
|
||||
len(h.AccessControlAllowHeaders) != 0 ||
|
||||
len(h.AccessControlAllowMethods) != 0 ||
|
||||
h.AccessControlAllowOrigin != "" ||
|
||||
len(h.AccessControlExposeHeaders) != 0 ||
|
||||
h.AccessControlMaxAge != 0 ||
|
||||
h.AddVaryHeader)
|
||||
}
|
||||
|
||||
// HasSecureHeadersDefined checks to see if any of the secure header elements have been set
|
||||
func (h *Headers) HasSecureHeadersDefined() bool {
|
||||
return h != nil && (len(h.AllowedHosts) != 0 ||
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/middlewares"
|
||||
|
@ -14,7 +16,8 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
typeName = "Headers"
|
||||
typeName = "Headers"
|
||||
originHeaderKey = "X-Request-Origin"
|
||||
)
|
||||
|
||||
type headers struct {
|
||||
|
@ -28,22 +31,26 @@ func New(ctx context.Context, next http.Handler, config config.Headers, name str
|
|||
logger := middlewares.GetLogger(ctx, name, typeName)
|
||||
logger.Debug("Creating middleware")
|
||||
|
||||
if !config.HasSecureHeadersDefined() && !config.HasCustomHeadersDefined() {
|
||||
hasSecureHeaders := config.HasSecureHeadersDefined()
|
||||
hasCustomHeaders := config.HasCustomHeadersDefined()
|
||||
hasCorsHeaders := config.HasCorsHeadersDefined()
|
||||
|
||||
if !hasSecureHeaders && !hasCustomHeaders && !hasCorsHeaders {
|
||||
return nil, errors.New("headers configuration not valid")
|
||||
}
|
||||
|
||||
var handler http.Handler
|
||||
nextHandler := next
|
||||
|
||||
if config.HasSecureHeadersDefined() {
|
||||
if hasSecureHeaders {
|
||||
logger.Debug("Setting up secureHeaders from %v", config)
|
||||
handler = newSecure(next, config)
|
||||
nextHandler = handler
|
||||
}
|
||||
|
||||
if config.HasCustomHeadersDefined() {
|
||||
logger.Debug("Setting up customHeaders from %v", config)
|
||||
handler = newHeader(nextHandler, config)
|
||||
if hasCustomHeaders || hasCorsHeaders {
|
||||
logger.Debug("Setting up customHeaders/Cors from %v", config)
|
||||
handler = NewHeader(nextHandler, config)
|
||||
}
|
||||
|
||||
return &headers{
|
||||
|
@ -102,29 +109,67 @@ func (s secureHeader) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
|
||||
// Header is a middleware that helps setup a few basic security features. A single headerOptions struct can be
|
||||
// provided to configure which features should be enabled, and the ability to override a few of the default values.
|
||||
type header struct {
|
||||
next http.Handler
|
||||
// If Custom request headers are set, these will be added to the request
|
||||
customRequestHeaders map[string]string
|
||||
type Header struct {
|
||||
next http.Handler
|
||||
headers *config.Headers
|
||||
}
|
||||
|
||||
// NewHeader constructs a new header instance from supplied frontend header struct.
|
||||
func newHeader(next http.Handler, headers config.Headers) *header {
|
||||
return &header{
|
||||
next: next,
|
||||
customRequestHeaders: headers.CustomRequestHeaders,
|
||||
func NewHeader(next http.Handler, headers config.Headers) *Header {
|
||||
return &Header{
|
||||
next: next,
|
||||
headers: &headers,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *header) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
func (s *Header) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
reqAcMethod := req.Header.Get("Access-Control-Request-Method")
|
||||
reqAcHeaders := req.Header.Get("Access-Control-Request-Headers")
|
||||
originHeader := req.Header.Get("Origin")
|
||||
|
||||
if reqAcMethod != "" && reqAcHeaders != "" && originHeader != "" && req.Method == http.MethodOptions {
|
||||
// If the request is an OPTIONS request with an Access-Control-Request-Method header, and Access-Control-Request-Headers headers,
|
||||
// and Origin headers, then it is a CORS preflight request, and we need to build a custom response: https://www.w3.org/TR/cors/#preflight-request
|
||||
if s.headers.AccessControlAllowCredentials {
|
||||
rw.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
|
||||
allowHeaders := strings.Join(s.headers.AccessControlAllowHeaders, ",")
|
||||
if allowHeaders != "" {
|
||||
rw.Header().Set("Access-Control-Allow-Headers", allowHeaders)
|
||||
}
|
||||
|
||||
allowMethods := strings.Join(s.headers.AccessControlAllowMethods, ",")
|
||||
if allowMethods != "" {
|
||||
rw.Header().Set("Access-Control-Allow-Methods", allowMethods)
|
||||
}
|
||||
|
||||
allowOrigin := s.getAllowOrigin(originHeader)
|
||||
|
||||
if allowOrigin != "" {
|
||||
rw.Header().Set("Access-Control-Allow-Origin", allowOrigin)
|
||||
}
|
||||
|
||||
rw.Header().Set("Access-Control-Max-Age", strconv.Itoa(int(s.headers.AccessControlMaxAge)))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if len(originHeader) > 0 {
|
||||
rw.Header().Set(originHeaderKey, originHeader)
|
||||
}
|
||||
|
||||
s.modifyRequestHeaders(req)
|
||||
s.next.ServeHTTP(rw, req)
|
||||
// If there is a next, call it.
|
||||
if s.next != nil {
|
||||
s.next.ServeHTTP(rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
// modifyRequestHeaders set or delete request headers.
|
||||
func (s *header) modifyRequestHeaders(req *http.Request) {
|
||||
func (s *Header) modifyRequestHeaders(req *http.Request) {
|
||||
// Loop through Custom request headers
|
||||
for header, value := range s.customRequestHeaders {
|
||||
for header, value := range s.headers.CustomRequestHeaders {
|
||||
if value == "" {
|
||||
req.Header.Del(header)
|
||||
} else {
|
||||
|
@ -132,3 +177,57 @@ func (s *header) modifyRequestHeaders(req *http.Request) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ModifyResponseHeaders set or delete response headers
|
||||
func (s *Header) ModifyResponseHeaders(res *http.Response) error {
|
||||
// Loop through Custom response headers
|
||||
for header, value := range s.headers.CustomResponseHeaders {
|
||||
if value == "" {
|
||||
res.Header.Del(header)
|
||||
} else {
|
||||
res.Header.Set(header, value)
|
||||
}
|
||||
}
|
||||
originHeader := res.Header.Get(originHeaderKey)
|
||||
allowOrigin := s.getAllowOrigin(originHeader)
|
||||
// Delete the origin header key, since it is only used to pass data from the request for response handling
|
||||
res.Header.Del(originHeaderKey)
|
||||
if allowOrigin != "" {
|
||||
res.Header.Set("Access-Control-Allow-Origin", allowOrigin)
|
||||
|
||||
if s.headers.AddVaryHeader {
|
||||
varyHeader := res.Header.Get("Vary")
|
||||
if varyHeader != "" {
|
||||
varyHeader += ","
|
||||
}
|
||||
varyHeader += "Origin"
|
||||
|
||||
res.Header.Set("Vary", varyHeader)
|
||||
}
|
||||
}
|
||||
|
||||
if s.headers.AccessControlAllowCredentials {
|
||||
res.Header.Set("Access-Control-Allow-Credentials", "true")
|
||||
}
|
||||
|
||||
exposeHeaders := strings.Join(s.headers.AccessControlExposeHeaders, ",")
|
||||
if exposeHeaders != "" {
|
||||
res.Header.Set("Access-Control-Expose-Headers", exposeHeaders)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Header) getAllowOrigin(header string) string {
|
||||
switch s.headers.AccessControlAllowOrigin {
|
||||
case "origin-list-or-null":
|
||||
if len(header) == 0 {
|
||||
return "null"
|
||||
}
|
||||
return header
|
||||
case "*":
|
||||
return "*"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/testhelpers"
|
||||
"github.com/containous/traefik/pkg/tracing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -17,7 +18,7 @@ import (
|
|||
func TestCustomRequestHeader(t *testing.T) {
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
header := newHeader(emptyHandler, config.Headers{
|
||||
header := NewHeader(emptyHandler, config.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
"X-Custom-Request-Header": "test_request",
|
||||
},
|
||||
|
@ -35,7 +36,7 @@ func TestCustomRequestHeader(t *testing.T) {
|
|||
func TestCustomRequestHeaderEmptyValue(t *testing.T) {
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
header := newHeader(emptyHandler, config.Headers{
|
||||
header := NewHeader(emptyHandler, config.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
"X-Custom-Request-Header": "test_request",
|
||||
},
|
||||
|
@ -49,7 +50,7 @@ func TestCustomRequestHeaderEmptyValue(t *testing.T) {
|
|||
assert.Equal(t, http.StatusOK, res.Code)
|
||||
assert.Equal(t, "test_request", req.Header.Get("X-Custom-Request-Header"))
|
||||
|
||||
header = newHeader(emptyHandler, config.Headers{
|
||||
header = NewHeader(emptyHandler, config.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
"X-Custom-Request-Header": "",
|
||||
},
|
||||
|
@ -188,3 +189,312 @@ func TestSSLForceHost(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCORSPreflights(t *testing.T) {
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
header *Header
|
||||
requestHeaders http.Header
|
||||
expected http.Header
|
||||
}{
|
||||
{
|
||||
desc: "Test Simple Preflight",
|
||||
header: NewHeader(emptyHandler, config.Headers{
|
||||
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||
AccessControlAllowOrigin: "origin-list-or-null",
|
||||
AccessControlMaxAge: 600,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Headers": {"origin"},
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Wildcard origin Preflight",
|
||||
header: NewHeader(emptyHandler, config.Headers{
|
||||
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||
AccessControlAllowOrigin: "*",
|
||||
AccessControlMaxAge: 600,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Headers": {"origin"},
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Allow Credentials Preflight",
|
||||
header: NewHeader(emptyHandler, config.Headers{
|
||||
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||
AccessControlAllowOrigin: "*",
|
||||
AccessControlAllowCredentials: true,
|
||||
AccessControlMaxAge: 600,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Headers": {"origin"},
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
"Access-Control-Allow-Credentials": {"true"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Allow Headers Preflight",
|
||||
header: NewHeader(emptyHandler, config.Headers{
|
||||
AccessControlAllowMethods: []string{"GET", "OPTIONS", "PUT"},
|
||||
AccessControlAllowOrigin: "*",
|
||||
AccessControlAllowHeaders: []string{"origin", "X-Forwarded-For"},
|
||||
AccessControlMaxAge: 600,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Access-Control-Request-Headers": {"origin"},
|
||||
"Access-Control-Request-Method": {"GET", "OPTIONS"},
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
"Access-Control-Allow-Headers": {"origin,X-Forwarded-For"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
req := testhelpers.MustNewRequest(http.MethodOptions, "/foo", nil)
|
||||
req.Header = test.requestHeaders
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
test.header.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, test.expected, rw.Result().Header)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyHeaderObject(t *testing.T) {
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
_, err := New(context.Background(), next, config.Headers{}, "testing")
|
||||
require.Errorf(t, err, "headers configuration not valid")
|
||||
}
|
||||
|
||||
func TestCustomHeaderHandler(t *testing.T) {
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
header, _ := New(context.Background(), next, config.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
"X-Custom-Request-Header": "test_request",
|
||||
},
|
||||
}, "testing")
|
||||
|
||||
res := httptest.NewRecorder()
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
header.ServeHTTP(res, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, res.Code)
|
||||
assert.Equal(t, "test_request", req.Header.Get("X-Custom-Request-Header"))
|
||||
}
|
||||
|
||||
func TestGetTracingInformation(t *testing.T) {
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
header := &headers{
|
||||
handler: next,
|
||||
name: "testing",
|
||||
}
|
||||
|
||||
name, trace := header.GetTracingInformation()
|
||||
|
||||
assert.Equal(t, "testing", name)
|
||||
assert.Equal(t, tracing.SpanKindNoneEnum, trace)
|
||||
|
||||
}
|
||||
|
||||
func TestCORSResponses(t *testing.T) {
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
nonEmptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Vary", "Testing") })
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
header *Header
|
||||
requestHeaders http.Header
|
||||
expected http.Header
|
||||
}{
|
||||
{
|
||||
desc: "Test Simple Request",
|
||||
header: NewHeader(emptyHandler, config.Headers{
|
||||
AccessControlAllowOrigin: "origin-list-or-null",
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Wildcard origin Request",
|
||||
header: NewHeader(emptyHandler, config.Headers{
|
||||
AccessControlAllowOrigin: "*",
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Empty origin Request",
|
||||
header: NewHeader(emptyHandler, config.Headers{
|
||||
AccessControlAllowOrigin: "origin-list-or-null",
|
||||
}),
|
||||
requestHeaders: map[string][]string{},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"null"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Not Defined origin Request",
|
||||
header: NewHeader(emptyHandler, config.Headers{}),
|
||||
requestHeaders: map[string][]string{},
|
||||
expected: map[string][]string{},
|
||||
},
|
||||
{
|
||||
desc: "Allow Credentials Request",
|
||||
header: NewHeader(emptyHandler, config.Headers{
|
||||
AccessControlAllowOrigin: "*",
|
||||
AccessControlAllowCredentials: true,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Allow-Credentials": {"true"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Expose Headers Request",
|
||||
header: NewHeader(emptyHandler, config.Headers{
|
||||
AccessControlAllowOrigin: "*",
|
||||
AccessControlExposeHeaders: []string{"origin", "X-Forwarded-For"},
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Expose-Headers": {"origin,X-Forwarded-For"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test Simple Request with Vary Headers",
|
||||
header: NewHeader(emptyHandler, config.Headers{
|
||||
AccessControlAllowOrigin: "origin-list-or-null",
|
||||
AddVaryHeader: true,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
|
||||
"Vary": {"Origin"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Test Simple Request with Vary Headers and non-empty response",
|
||||
header: NewHeader(nonEmptyHandler, config.Headers{
|
||||
AccessControlAllowOrigin: "origin-list-or-null",
|
||||
AddVaryHeader: true,
|
||||
}),
|
||||
requestHeaders: map[string][]string{
|
||||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
|
||||
"Vary": {"Testing,Origin"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
|
||||
req.Header = test.requestHeaders
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
test.header.ServeHTTP(rw, req)
|
||||
err := test.header.ModifyResponseHeaders(rw.Result())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expected, rw.Result().Header)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomResponseHeaders(t *testing.T) {
|
||||
emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
header *Header
|
||||
expected http.Header
|
||||
}{
|
||||
{
|
||||
desc: "Test Simple Response",
|
||||
header: NewHeader(emptyHandler, config.Headers{
|
||||
CustomResponseHeaders: map[string]string{
|
||||
"Testing": "foo",
|
||||
"Testing2": "bar",
|
||||
},
|
||||
}),
|
||||
expected: map[string][]string{
|
||||
"Testing": {"foo"},
|
||||
"Testing2": {"bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Deleting Custom Header",
|
||||
header: NewHeader(emptyHandler, config.Headers{
|
||||
CustomResponseHeaders: map[string]string{
|
||||
"Testing": "foo",
|
||||
"Testing2": "",
|
||||
},
|
||||
}),
|
||||
expected: map[string][]string{
|
||||
"Testing": {"foo"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "/foo", nil)
|
||||
rw := httptest.NewRecorder()
|
||||
test.header.ServeHTTP(rw, req)
|
||||
err := test.header.ModifyResponseHeaders(rw.Result())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, test.expected, rw.Result().Header)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,14 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"traefik.http.middlewares.Middleware7.forwardauth.tls.insecureskipverify": "true",
|
||||
"traefik.http.middlewares.Middleware7.forwardauth.tls.key": "foobar",
|
||||
"traefik.http.middlewares.Middleware7.forwardauth.trustforwardheader": "true",
|
||||
"traefik.http.middlewares.Middleware8.headers.accesscontrolallowcredentials": "true",
|
||||
"traefik.http.middlewares.Middleware8.headers.allowedhosts": "foobar, fiibar",
|
||||
"traefik.http.middlewares.Middleware8.headers.accesscontrolallowheaders": "X-foobar, X-fiibar",
|
||||
"traefik.http.middlewares.Middleware8.headers.accesscontrolallowmethods": "GET, PUT",
|
||||
"traefik.http.middlewares.Middleware8.headers.accesscontrolalloworigin": "foobar",
|
||||
"traefik.http.middlewares.Middleware8.headers.accesscontrolexposeheaders": "X-foobar, X-fiibar",
|
||||
"traefik.http.middlewares.Middleware8.headers.accesscontrolmaxage": "200",
|
||||
"traefik.http.middlewares.Middleware8.headers.addvaryheader": "true",
|
||||
"traefik.http.middlewares.Middleware8.headers.browserxssfilter": "true",
|
||||
"traefik.http.middlewares.Middleware8.headers.contentsecuritypolicy": "foobar",
|
||||
"traefik.http.middlewares.Middleware8.headers.contenttypenosniff": "true",
|
||||
|
@ -377,6 +384,22 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
AccessControlAllowCredentials: true,
|
||||
AccessControlAllowHeaders: []string{
|
||||
"X-foobar",
|
||||
"X-fiibar",
|
||||
},
|
||||
AccessControlAllowMethods: []string{
|
||||
"GET",
|
||||
"PUT",
|
||||
},
|
||||
AccessControlAllowOrigin: "foobar",
|
||||
AccessControlExposeHeaders: []string{
|
||||
"X-foobar",
|
||||
"X-fiibar",
|
||||
},
|
||||
AccessControlMaxAge: 200,
|
||||
AddVaryHeader: true,
|
||||
AllowedHosts: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
|
@ -710,6 +733,22 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"name0": "foobar",
|
||||
"name1": "foobar",
|
||||
},
|
||||
AccessControlAllowCredentials: true,
|
||||
AccessControlAllowHeaders: []string{
|
||||
"X-foobar",
|
||||
"X-fiibar",
|
||||
},
|
||||
AccessControlAllowMethods: []string{
|
||||
"GET",
|
||||
"PUT",
|
||||
},
|
||||
AccessControlAllowOrigin: "foobar",
|
||||
AccessControlExposeHeaders: []string{
|
||||
"X-foobar",
|
||||
"X-fiibar",
|
||||
},
|
||||
AccessControlMaxAge: 200,
|
||||
AddVaryHeader: true,
|
||||
AllowedHosts: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
|
@ -854,6 +893,13 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.InsecureSkipVerify": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TLS.Key": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TrustForwardHeader": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowCredentials": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowHeaders": "X-foobar, X-fiibar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowMethods": "GET, PUT",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowOrigin": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlExposeHeaders": "X-foobar, X-fiibar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlMaxAge": "200",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AddVaryHeader": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AllowedHosts": "foobar, fiibar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.BrowserXSSFilter": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.ContentSecurityPolicy": "foobar",
|
||||
|
|
|
@ -4,46 +4,43 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/containous/traefik/pkg/config"
|
||||
"github.com/containous/traefik/pkg/middlewares/headers"
|
||||
"github.com/unrolled/secure"
|
||||
)
|
||||
|
||||
func buildHeaders(headers *config.Headers) func(*http.Response) error {
|
||||
func buildHeaders(hdrs *config.Headers) func(*http.Response) error {
|
||||
opt := secure.Options{
|
||||
BrowserXssFilter: headers.BrowserXSSFilter,
|
||||
ContentTypeNosniff: headers.ContentTypeNosniff,
|
||||
ForceSTSHeader: headers.ForceSTSHeader,
|
||||
FrameDeny: headers.FrameDeny,
|
||||
IsDevelopment: headers.IsDevelopment,
|
||||
SSLRedirect: headers.SSLRedirect,
|
||||
SSLForceHost: headers.SSLForceHost,
|
||||
SSLTemporaryRedirect: headers.SSLTemporaryRedirect,
|
||||
STSIncludeSubdomains: headers.STSIncludeSubdomains,
|
||||
STSPreload: headers.STSPreload,
|
||||
ContentSecurityPolicy: headers.ContentSecurityPolicy,
|
||||
CustomBrowserXssValue: headers.CustomBrowserXSSValue,
|
||||
CustomFrameOptionsValue: headers.CustomFrameOptionsValue,
|
||||
PublicKey: headers.PublicKey,
|
||||
ReferrerPolicy: headers.ReferrerPolicy,
|
||||
SSLHost: headers.SSLHost,
|
||||
AllowedHosts: headers.AllowedHosts,
|
||||
HostsProxyHeaders: headers.HostsProxyHeaders,
|
||||
SSLProxyHeaders: headers.SSLProxyHeaders,
|
||||
STSSeconds: headers.STSSeconds,
|
||||
BrowserXssFilter: hdrs.BrowserXSSFilter,
|
||||
ContentTypeNosniff: hdrs.ContentTypeNosniff,
|
||||
ForceSTSHeader: hdrs.ForceSTSHeader,
|
||||
FrameDeny: hdrs.FrameDeny,
|
||||
IsDevelopment: hdrs.IsDevelopment,
|
||||
SSLRedirect: hdrs.SSLRedirect,
|
||||
SSLForceHost: hdrs.SSLForceHost,
|
||||
SSLTemporaryRedirect: hdrs.SSLTemporaryRedirect,
|
||||
STSIncludeSubdomains: hdrs.STSIncludeSubdomains,
|
||||
STSPreload: hdrs.STSPreload,
|
||||
ContentSecurityPolicy: hdrs.ContentSecurityPolicy,
|
||||
CustomBrowserXssValue: hdrs.CustomBrowserXSSValue,
|
||||
CustomFrameOptionsValue: hdrs.CustomFrameOptionsValue,
|
||||
PublicKey: hdrs.PublicKey,
|
||||
ReferrerPolicy: hdrs.ReferrerPolicy,
|
||||
SSLHost: hdrs.SSLHost,
|
||||
AllowedHosts: hdrs.AllowedHosts,
|
||||
HostsProxyHeaders: hdrs.HostsProxyHeaders,
|
||||
SSLProxyHeaders: hdrs.SSLProxyHeaders,
|
||||
STSSeconds: hdrs.STSSeconds,
|
||||
}
|
||||
|
||||
return func(resp *http.Response) error {
|
||||
if headers.HasCustomHeadersDefined() {
|
||||
// Loop through Custom response headers
|
||||
for header, value := range headers.CustomResponseHeaders {
|
||||
if value == "" {
|
||||
resp.Header.Del(header)
|
||||
} else {
|
||||
resp.Header.Set(header, value)
|
||||
}
|
||||
if hdrs.HasCustomHeadersDefined() || hdrs.HasCorsHeadersDefined() {
|
||||
err := headers.NewHeader(nil, *hdrs).ModifyResponseHeaders(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if headers.HasSecureHeadersDefined() {
|
||||
if hdrs.HasSecureHeadersDefined() {
|
||||
err := secure.New(opt).ModifyResponseHeaders(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue