1
0
Fork 0

Allow to use regular expressions for AccessControlAllowOriginList

This commit is contained in:
Luca Guidi 2020-10-29 10:52:03 +01:00 committed by GitHub
parent 699cf71652
commit b5198e63c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 133 additions and 17 deletions

View file

@ -2,7 +2,9 @@ package headers
import (
"context"
"fmt"
"net/http"
"regexp"
"strconv"
"strings"
@ -14,26 +16,37 @@ import (
// 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
hasCustomHeaders bool
hasCorsHeaders bool
headers *dynamic.Headers
next http.Handler
hasCustomHeaders bool
hasCorsHeaders bool
headers *dynamic.Headers
allowOriginRegexes []*regexp.Regexp
}
// NewHeader constructs a new header instance from supplied frontend header struct.
func NewHeader(next http.Handler, cfg dynamic.Headers) *Header {
func NewHeader(next http.Handler, cfg dynamic.Headers) (*Header, error) {
hasCustomHeaders := cfg.HasCustomHeadersDefined()
hasCorsHeaders := cfg.HasCorsHeadersDefined()
ctx := log.With(context.Background(), log.Str(log.MiddlewareType, typeName))
handleDeprecation(ctx, &cfg)
return &Header{
next: next,
headers: &cfg,
hasCustomHeaders: hasCustomHeaders,
hasCorsHeaders: hasCorsHeaders,
regexes := make([]*regexp.Regexp, len(cfg.AccessControlAllowOriginListRegex))
for i, str := range cfg.AccessControlAllowOriginListRegex {
reg, err := regexp.Compile(str)
if err != nil {
return nil, fmt.Errorf("error occurred during origin parsing: %w", err)
}
regexes[i] = reg
}
return &Header{
next: next,
headers: &cfg,
hasCustomHeaders: hasCustomHeaders,
hasCorsHeaders: hasCorsHeaders,
allowOriginRegexes: regexes,
}, nil
}
func (s *Header) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
@ -166,5 +179,11 @@ func (s *Header) isOriginAllowed(origin string) (bool, string) {
}
}
for _, regex := range s.allowOriginRegexes {
if regex.MatchString(origin) {
return true, origin
}
}
return false, ""
}

View file

@ -6,6 +6,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
)
@ -52,7 +53,8 @@ func TestNewHeader_customRequestHeader(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
mid := NewHeader(emptyHandler, test.cfg)
mid, err := NewHeader(emptyHandler, test.cfg)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
req.Header.Set("Foo", "bar")
@ -94,7 +96,8 @@ func TestNewHeader_customRequestHeader_Host(t *testing.T) {
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
mid := NewHeader(emptyHandler, dynamic.Headers{CustomRequestHeaders: test.customHeaders})
mid, err := NewHeader(emptyHandler, dynamic.Headers{CustomRequestHeaders: test.customHeaders})
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "http://example.org/foo", nil)
@ -217,7 +220,8 @@ func TestNewHeader_CORSPreflights(t *testing.T) {
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
mid := NewHeader(emptyHandler, test.cfg)
mid, err := NewHeader(emptyHandler, test.cfg)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodOptions, "/foo", nil)
req.Header = test.requestHeaders
@ -240,6 +244,7 @@ func TestNewHeader_CORSResponses(t *testing.T) {
cfg dynamic.Headers
requestHeaders http.Header
expected http.Header
expectedError bool
}{
{
desc: "Test Simple Request",
@ -267,6 +272,54 @@ func TestNewHeader_CORSResponses(t *testing.T) {
"Access-Control-Allow-Origin": {"*"},
},
},
{
desc: "Regexp Origin Request",
next: emptyHandler,
cfg: dynamic.Headers{
AccessControlAllowOriginListRegex: []string{"^https?://([a-z]+)\\.bar\\.org$"},
},
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
},
},
{
desc: "Partial Regexp Origin Request",
next: emptyHandler,
cfg: dynamic.Headers{
AccessControlAllowOriginListRegex: []string{"([a-z]+)\\.bar"},
},
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expected: map[string][]string{
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
},
},
{
desc: "Regexp Malformed Origin Request",
next: emptyHandler,
cfg: dynamic.Headers{
AccessControlAllowOriginListRegex: []string{"a(b"},
},
requestHeaders: map[string][]string{
"Origin": {"https://foo.bar.org"},
},
expectedError: true,
},
{
desc: "Regexp Origin Request without matching",
next: emptyHandler,
cfg: dynamic.Headers{
AccessControlAllowOriginListRegex: []string{"([a-z]+)\\.bar\\.org"},
},
requestHeaders: map[string][]string{
"Origin": {"https://bar.org"},
},
expected: map[string][]string{},
},
{
desc: "Empty origin Request",
next: emptyHandler,
@ -416,7 +469,12 @@ func TestNewHeader_CORSResponses(t *testing.T) {
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
mid := NewHeader(test.next, test.cfg)
mid, err := NewHeader(test.next, test.cfg)
if test.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/foo", nil)
req.Header = test.requestHeaders
@ -478,7 +536,8 @@ func TestNewHeader_customResponseHeaders(t *testing.T) {
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
mid := NewHeader(emptyHandler, dynamic.Headers{CustomResponseHeaders: test.config})
mid, err := NewHeader(emptyHandler, dynamic.Headers{CustomResponseHeaders: test.config})
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/foo", nil)

View file

@ -58,7 +58,11 @@ func New(ctx context.Context, next http.Handler, cfg dynamic.Headers, name strin
if hasCustomHeaders || hasCorsHeaders {
logger.Debugf("Setting up customHeaders/Cors from %v", cfg)
handler = NewHeader(nextHandler, cfg)
var err error
handler, err = NewHeader(nextHandler, cfg)
if err != nil {
return nil, err
}
}
return &headers{