Merge current v2.5 into master
This commit is contained in:
commit
f81ceaef8a
64 changed files with 949 additions and 208 deletions
|
@ -24,7 +24,7 @@ func DecodeToNode(pairs []*store.KVPair, rootName string, filters ...string) (*p
|
|||
return nil, fmt.Errorf("invalid label root %s", rootName)
|
||||
}
|
||||
|
||||
split := strings.Split(pair.Key[len(rootName)+1:], "/")
|
||||
split := strings.FieldsFunc(pair.Key[len(rootName)+1:], func(c rune) bool { return c == '/' })
|
||||
|
||||
parts := []string{rootName}
|
||||
for _, fragment := range split {
|
||||
|
|
|
@ -28,6 +28,7 @@ func TestDecode(t *testing.T) {
|
|||
"traefik/fieldf/Test2": "B",
|
||||
"traefik/fieldg/0/name": "A",
|
||||
"traefik/fieldg/1/name": "B",
|
||||
"traefik/fieldh/": "foo",
|
||||
},
|
||||
expected: &sample{
|
||||
FieldA: "bar",
|
||||
|
@ -45,6 +46,7 @@ func TestDecode(t *testing.T) {
|
|||
{Name: "A"},
|
||||
{Name: "B"},
|
||||
},
|
||||
FieldH: "foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -61,6 +63,7 @@ func TestDecode(t *testing.T) {
|
|||
"foo/bar/traefik/fieldf/Test2": "B",
|
||||
"foo/bar/traefik/fieldg/0/name": "A",
|
||||
"foo/bar/traefik/fieldg/1/name": "B",
|
||||
"foo/bar/traefik/fieldh/": "foo",
|
||||
},
|
||||
expected: &sample{
|
||||
FieldA: "bar",
|
||||
|
@ -78,6 +81,7 @@ func TestDecode(t *testing.T) {
|
|||
{Name: "A"},
|
||||
{Name: "B"},
|
||||
},
|
||||
FieldH: "foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -107,6 +111,7 @@ type sample struct {
|
|||
} `label:"allowEmpty"`
|
||||
FieldF map[string]string
|
||||
FieldG []sub
|
||||
FieldH string
|
||||
}
|
||||
|
||||
type sub struct {
|
||||
|
|
|
@ -27,6 +27,12 @@ func TestIsAuthorized(t *testing.T) {
|
|||
remoteAddr: "1.2.3.1:123",
|
||||
authorized: true,
|
||||
},
|
||||
{
|
||||
desc: "octal ip in remoteAddr",
|
||||
whiteList: []string{"127.2.3.4/24"},
|
||||
remoteAddr: "0127.2.3.1:123",
|
||||
authorized: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/connectionheader"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
"github.com/vulcand/oxy/forward"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
|
@ -89,7 +90,7 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
|
|||
fa.authResponseHeadersRegex = re
|
||||
}
|
||||
|
||||
return fa, nil
|
||||
return connectionheader.Remover(fa), nil
|
||||
}
|
||||
|
||||
func (fa *forwardAuth) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
|
|
46
pkg/middlewares/connectionheader/connectionheader.go
Normal file
46
pkg/middlewares/connectionheader/connectionheader.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package connectionheader
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
const (
|
||||
connectionHeader = "Connection"
|
||||
upgradeHeader = "Upgrade"
|
||||
)
|
||||
|
||||
// Remover removes hop-by-hop headers listed in the "Connection" header.
|
||||
// See RFC 7230, section 6.1.
|
||||
func Remover(next http.Handler) http.HandlerFunc {
|
||||
return func(rw http.ResponseWriter, req *http.Request) {
|
||||
var reqUpType string
|
||||
if httpguts.HeaderValuesContainsToken(req.Header[connectionHeader], upgradeHeader) {
|
||||
reqUpType = req.Header.Get(upgradeHeader)
|
||||
}
|
||||
|
||||
removeConnectionHeaders(req.Header)
|
||||
|
||||
if reqUpType != "" {
|
||||
req.Header.Set(connectionHeader, upgradeHeader)
|
||||
req.Header.Set(upgradeHeader, reqUpType)
|
||||
} else {
|
||||
req.Header.Del(connectionHeader)
|
||||
}
|
||||
|
||||
next.ServeHTTP(rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
func removeConnectionHeaders(h http.Header) {
|
||||
for _, f := range h[connectionHeader] {
|
||||
for _, sf := range strings.Split(f, ",") {
|
||||
if sf = textproto.TrimString(sf); sf != "" {
|
||||
h.Del(sf)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
71
pkg/middlewares/connectionheader/connectionheader_test.go
Normal file
71
pkg/middlewares/connectionheader/connectionheader_test.go
Normal file
|
@ -0,0 +1,71 @@
|
|||
package connectionheader
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRemover(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
reqHeaders map[string]string
|
||||
expected http.Header
|
||||
}{
|
||||
{
|
||||
desc: "simple remove",
|
||||
reqHeaders: map[string]string{
|
||||
"Foo": "bar",
|
||||
connectionHeader: "foo",
|
||||
},
|
||||
expected: http.Header{},
|
||||
},
|
||||
{
|
||||
desc: "remove and Upgrade",
|
||||
reqHeaders: map[string]string{
|
||||
upgradeHeader: "test",
|
||||
"Foo": "bar",
|
||||
connectionHeader: "Upgrade,foo",
|
||||
},
|
||||
expected: http.Header{
|
||||
upgradeHeader: []string{"test"},
|
||||
connectionHeader: []string{"Upgrade"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "no remove",
|
||||
reqHeaders: map[string]string{
|
||||
"Foo": "bar",
|
||||
connectionHeader: "fii",
|
||||
},
|
||||
expected: http.Header{
|
||||
"Foo": []string{"bar"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {})
|
||||
|
||||
h := Remover(next)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "https://localhost", nil)
|
||||
|
||||
for k, v := range test.reqHeaders {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
h.ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, test.expected, req.Header)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/connectionheader"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
)
|
||||
|
||||
|
@ -68,11 +69,12 @@ 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)
|
||||
var err error
|
||||
handler, err = NewHeader(nextHandler, cfg)
|
||||
h, err := NewHeader(nextHandler, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handler = connectionheader.Remover(h)
|
||||
}
|
||||
|
||||
return &headers{
|
||||
|
|
|
@ -22,13 +22,18 @@ type responseModifier struct {
|
|||
}
|
||||
|
||||
// modifier can be nil.
|
||||
func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) *responseModifier {
|
||||
return &responseModifier{
|
||||
func newResponseModifier(w http.ResponseWriter, r *http.Request, modifier func(*http.Response) error) http.ResponseWriter {
|
||||
rm := &responseModifier{
|
||||
req: r,
|
||||
rw: w,
|
||||
modifier: modifier,
|
||||
code: http.StatusOK,
|
||||
}
|
||||
|
||||
if _, ok := w.(http.CloseNotifier); ok {
|
||||
return responseModifierWithCloseNotify{responseModifier: rm}
|
||||
}
|
||||
return rm
|
||||
}
|
||||
|
||||
func (r *responseModifier) WriteHeader(code int) {
|
||||
|
@ -93,7 +98,11 @@ func (r *responseModifier) Flush() {
|
|||
}
|
||||
}
|
||||
|
||||
// CloseNotify implements http.CloseNotifier.
|
||||
func (r *responseModifier) CloseNotify() <-chan bool {
|
||||
return r.rw.(http.CloseNotifier).CloseNotify()
|
||||
type responseModifierWithCloseNotify struct {
|
||||
*responseModifier
|
||||
}
|
||||
|
||||
// CloseNotify implements http.CloseNotifier.
|
||||
func (r *responseModifierWithCloseNotify) CloseNotify() <-chan bool {
|
||||
return r.responseModifier.rw.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
|
|
@ -4,13 +4,17 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
schemeHTTP = "http"
|
||||
schemeHTTPS = "https"
|
||||
)
|
||||
|
||||
type redirect struct {
|
||||
next http.Handler
|
||||
regex *regexp.Regexp
|
||||
|
@ -18,10 +22,11 @@ type redirect struct {
|
|||
permanent bool
|
||||
errHandler utils.ErrorHandler
|
||||
name string
|
||||
rawURL func(*http.Request) string
|
||||
}
|
||||
|
||||
// New creates a Redirect middleware.
|
||||
func newRedirect(next http.Handler, regex, replacement string, permanent bool, name string) (http.Handler, error) {
|
||||
func newRedirect(next http.Handler, regex, replacement string, permanent bool, rawURL func(*http.Request) string, name string) (http.Handler, error) {
|
||||
re, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -34,6 +39,7 @@ func newRedirect(next http.Handler, regex, replacement string, permanent bool, n
|
|||
errHandler: utils.DefaultHandler,
|
||||
next: next,
|
||||
name: name,
|
||||
rawURL: rawURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -42,7 +48,7 @@ func (r *redirect) GetTracingInformation() (string, ext.SpanKindEnum) {
|
|||
}
|
||||
|
||||
func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
oldURL := rawURL(req)
|
||||
oldURL := r.rawURL(req)
|
||||
|
||||
// If the Regexp doesn't match, skip to the next handler.
|
||||
if !r.regex.MatchString(oldURL) {
|
||||
|
@ -98,33 +104,3 @@ func (m *moveHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func rawURL(req *http.Request) string {
|
||||
scheme := "http"
|
||||
host := req.Host
|
||||
port := ""
|
||||
uri := req.RequestURI
|
||||
|
||||
schemeRegex := `^(https?):\/\/(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$`
|
||||
re, _ := regexp.Compile(schemeRegex)
|
||||
if re.Match([]byte(req.RequestURI)) {
|
||||
match := re.FindStringSubmatch(req.RequestURI)
|
||||
scheme = match[1]
|
||||
|
||||
if len(match[2]) > 0 {
|
||||
host = match[2]
|
||||
}
|
||||
|
||||
if len(match[3]) > 0 {
|
||||
port = match[3]
|
||||
}
|
||||
|
||||
uri = match[4]
|
||||
}
|
||||
|
||||
if req.TLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
return strings.Join([]string{scheme, "://", host, port, uri}, "")
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package redirect
|
|||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
|
@ -19,5 +21,35 @@ func NewRedirectRegex(ctx context.Context, next http.Handler, conf dynamic.Redir
|
|||
logger.Debug("Creating middleware")
|
||||
logger.Debugf("Setting up redirection from %s to %s", conf.Regex, conf.Replacement)
|
||||
|
||||
return newRedirect(next, conf.Regex, conf.Replacement, conf.Permanent, name)
|
||||
return newRedirect(next, conf.Regex, conf.Replacement, conf.Permanent, rawURL, name)
|
||||
}
|
||||
|
||||
func rawURL(req *http.Request) string {
|
||||
scheme := schemeHTTP
|
||||
host := req.Host
|
||||
port := ""
|
||||
uri := req.RequestURI
|
||||
|
||||
schemeRegex := `^(https?):\/\/(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$`
|
||||
re, _ := regexp.Compile(schemeRegex)
|
||||
if re.Match([]byte(req.RequestURI)) {
|
||||
match := re.FindStringSubmatch(req.RequestURI)
|
||||
scheme = match[1]
|
||||
|
||||
if len(match[2]) > 0 {
|
||||
host = match[2]
|
||||
}
|
||||
|
||||
if len(match[3]) > 0 {
|
||||
port = match[3]
|
||||
}
|
||||
|
||||
uri = match[4]
|
||||
}
|
||||
|
||||
if req.TLS != nil {
|
||||
scheme = schemeHTTPS
|
||||
}
|
||||
|
||||
return strings.Join([]string{scheme, "://", host, port, uri}, "")
|
||||
}
|
||||
|
|
|
@ -3,7 +3,10 @@ package redirect
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
|
@ -26,9 +29,47 @@ func NewRedirectScheme(ctx context.Context, next http.Handler, conf dynamic.Redi
|
|||
}
|
||||
|
||||
port := ""
|
||||
if len(conf.Port) > 0 && !(conf.Scheme == "http" && conf.Port == "80" || conf.Scheme == "https" && conf.Port == "443") {
|
||||
if len(conf.Port) > 0 && !(conf.Scheme == schemeHTTP && conf.Port == "80" || conf.Scheme == schemeHTTPS && conf.Port == "443") {
|
||||
port = ":" + conf.Port
|
||||
}
|
||||
|
||||
return newRedirect(next, schemeRedirectRegex, conf.Scheme+"://${2}"+port+"${4}", conf.Permanent, name)
|
||||
return newRedirect(next, schemeRedirectRegex, conf.Scheme+"://${2}"+port+"${4}", conf.Permanent, rawURLScheme, name)
|
||||
}
|
||||
|
||||
func rawURLScheme(req *http.Request) string {
|
||||
scheme := schemeHTTP
|
||||
host, port, err := net.SplitHostPort(req.Host)
|
||||
if err != nil {
|
||||
host = req.Host
|
||||
} else {
|
||||
port = ":" + port
|
||||
}
|
||||
uri := req.RequestURI
|
||||
|
||||
schemeRegex := `^(https?):\/\/(\[[\w:.]+\]|[\w\._-]+)?(:\d+)?(.*)$`
|
||||
re, _ := regexp.Compile(schemeRegex)
|
||||
if re.Match([]byte(req.RequestURI)) {
|
||||
match := re.FindStringSubmatch(req.RequestURI)
|
||||
scheme = match[1]
|
||||
|
||||
if len(match[2]) > 0 {
|
||||
host = match[2]
|
||||
}
|
||||
|
||||
if len(match[3]) > 0 {
|
||||
port = match[3]
|
||||
}
|
||||
|
||||
uri = match[4]
|
||||
}
|
||||
|
||||
if req.TLS != nil {
|
||||
scheme = schemeHTTPS
|
||||
}
|
||||
|
||||
if scheme == schemeHTTP && port == ":80" || scheme == schemeHTTPS && port == ":443" || port == "" {
|
||||
port = ""
|
||||
}
|
||||
|
||||
return strings.Join([]string{scheme, "://", host, port, uri}, "")
|
||||
}
|
||||
|
|
|
@ -127,8 +127,18 @@ func TestRedirectSchemeHandler(t *testing.T) {
|
|||
Port: "80",
|
||||
},
|
||||
url: "http://foo:80",
|
||||
expectedURL: "http://foo",
|
||||
expectedStatus: http.StatusFound,
|
||||
expectedURL: "http://foo:80",
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "to HTTPS 443",
|
||||
config: dynamic.RedirectScheme{
|
||||
Scheme: "https",
|
||||
Port: "443",
|
||||
},
|
||||
url: "https://foo:443",
|
||||
expectedURL: "https://foo:443",
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "HTTP to wss",
|
||||
|
@ -248,6 +258,7 @@ func TestRedirectSchemeHandler(t *testing.T) {
|
|||
if test.method != "" {
|
||||
method = test.method
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(method, test.url, nil)
|
||||
|
||||
for k, v := range test.headers {
|
||||
|
|
|
@ -11,6 +11,15 @@ type statusCodeRecoder interface {
|
|||
Status() int
|
||||
}
|
||||
|
||||
// newStatusCodeRecoder returns an initialized statusCodeRecoder.
|
||||
func newStatusCodeRecoder(rw http.ResponseWriter, status int) statusCodeRecoder {
|
||||
recorder := &statusCodeWithoutCloseNotify{rw, status}
|
||||
if _, ok := rw.(http.CloseNotifier); ok {
|
||||
return &statusCodeWithCloseNotify{recorder}
|
||||
}
|
||||
return recorder
|
||||
}
|
||||
|
||||
type statusCodeWithoutCloseNotify struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
|
@ -46,12 +55,3 @@ type statusCodeWithCloseNotify struct {
|
|||
func (s *statusCodeWithCloseNotify) CloseNotify() <-chan bool {
|
||||
return s.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
// newStatusCodeRecoder returns an initialized statusCodeRecoder.
|
||||
func newStatusCodeRecoder(rw http.ResponseWriter, status int) statusCodeRecoder {
|
||||
recorder := &statusCodeWithoutCloseNotify{rw, status}
|
||||
if _, ok := rw.(http.CloseNotifier); ok {
|
||||
return &statusCodeWithCloseNotify{recorder}
|
||||
}
|
||||
return recorder
|
||||
}
|
||||
|
|
|
@ -28,6 +28,15 @@ spec:
|
|||
port: 80
|
||||
middlewares:
|
||||
- name: test-errorpage
|
||||
- match: Host(`foo.com`) && PathPrefix(`/bur`)
|
||||
kind: Rule
|
||||
priority: 12
|
||||
services:
|
||||
- name: whoami
|
||||
namespace: default
|
||||
port: 80
|
||||
middlewares:
|
||||
- name: cross-ns-stripprefix@kubernetescrd
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
|
|
|
@ -703,6 +703,12 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options
|
|||
id = tlsOption.Name
|
||||
nsDefault = append(nsDefault, tlsOption.Namespace)
|
||||
}
|
||||
|
||||
alpnProtocols := tls.DefaultTLSOptions.ALPNProtocols
|
||||
if len(tlsOption.Spec.ALPNProtocols) > 0 {
|
||||
alpnProtocols = tlsOption.Spec.ALPNProtocols
|
||||
}
|
||||
|
||||
tlsOptions[id] = tls.Options{
|
||||
MinVersion: tlsOption.Spec.MinVersion,
|
||||
MaxVersion: tlsOption.Spec.MaxVersion,
|
||||
|
@ -714,6 +720,7 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options
|
|||
},
|
||||
SniStrict: tlsOption.Spec.SniStrict,
|
||||
PreferServerCipherSuites: tlsOption.Spec.PreferServerCipherSuites,
|
||||
ALPNProtocols: alpnProtocols,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -147,13 +147,23 @@ func (p *Provider) makeMiddlewareKeys(ctx context.Context, ingRouteNamespace str
|
|||
var mds []string
|
||||
|
||||
for _, mi := range middlewares {
|
||||
if strings.Contains(mi.Name, providerNamespaceSeparator) {
|
||||
name := mi.Name
|
||||
|
||||
if !p.AllowCrossNamespace && strings.HasSuffix(mi.Name, providerNamespaceSeparator+providerName) {
|
||||
// Since we are not able to know if another namespace is in the name (namespace-name@kubernetescrd),
|
||||
// if the provider namespace kubernetescrd is used,
|
||||
// we don't allow this format to avoid cross namespace references.
|
||||
return nil, fmt.Errorf("invalid reference to middleware %s: with crossnamespace disallowed, the namespace field needs to be explicitly specified", mi.Name)
|
||||
}
|
||||
|
||||
if strings.Contains(name, providerNamespaceSeparator) {
|
||||
if len(mi.Namespace) > 0 {
|
||||
log.FromContext(ctx).
|
||||
WithField(log.MiddlewareName, mi.Name).
|
||||
Warnf("namespace %q is ignored in cross-provider context", mi.Namespace)
|
||||
}
|
||||
mds = append(mds, mi.Name)
|
||||
|
||||
mds = append(mds, name)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -166,7 +176,7 @@ func (p *Provider) makeMiddlewareKeys(ctx context.Context, ingRouteNamespace str
|
|||
ns = mi.Namespace
|
||||
}
|
||||
|
||||
mds = append(mds, makeID(ns, mi.Name))
|
||||
mds = append(mds, makeID(ns, name))
|
||||
}
|
||||
|
||||
return mds, nil
|
||||
|
|
|
@ -616,6 +616,11 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
|||
},
|
||||
SniStrict: true,
|
||||
PreferServerCipherSuites: true,
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -678,6 +683,11 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
|||
ClientAuthType: "VerifyClientCertIfGiven",
|
||||
},
|
||||
SniStrict: true,
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -739,6 +749,11 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
|||
ClientAuthType: "VerifyClientCertIfGiven",
|
||||
},
|
||||
SniStrict: true,
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -789,6 +804,11 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
|||
Options: map[string]tls.Options{
|
||||
"default-foo": {
|
||||
MinVersion: "VersionTLS12",
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -839,6 +859,11 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
|||
Options: map[string]tls.Options{
|
||||
"default-foo": {
|
||||
MinVersion: "VersionTLS12",
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2539,6 +2564,11 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
SniStrict: true,
|
||||
PreferServerCipherSuites: true,
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2648,6 +2678,11 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
SniStrict: true,
|
||||
PreferServerCipherSuites: true,
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2716,6 +2751,11 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
ClientAuthType: "VerifyClientCertIfGiven",
|
||||
},
|
||||
SniStrict: true,
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2779,6 +2819,11 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
ClientAuthType: "VerifyClientCertIfGiven",
|
||||
},
|
||||
SniStrict: true,
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2831,6 +2876,11 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
Options: map[string]tls.Options{
|
||||
"default-foo": {
|
||||
MinVersion: "VersionTLS12",
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2883,6 +2933,11 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
Options: map[string]tls.Options{
|
||||
"default-foo": {
|
||||
MinVersion: "VersionTLS12",
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -4313,6 +4368,13 @@ func TestCrossNamespace(t *testing.T) {
|
|||
Priority: 12,
|
||||
Middlewares: []string{"default-test-errorpage"},
|
||||
},
|
||||
"default-test-crossnamespace-route-a1963878aac7331b7950": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-crossnamespace-route-a1963878aac7331b7950",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bur`)",
|
||||
Priority: 12,
|
||||
Middlewares: []string{"cross-ns-stripprefix@kubernetescrd"},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"cross-ns-stripprefix": {
|
||||
|
@ -4369,6 +4431,19 @@ func TestCrossNamespace(t *testing.T) {
|
|||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"default-test-crossnamespace-route-a1963878aac7331b7950": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
|
|
|
@ -27,6 +27,7 @@ type TLSOptionSpec struct {
|
|||
ClientAuth ClientAuth `json:"clientAuth,omitempty"`
|
||||
SniStrict bool `json:"sniStrict,omitempty"`
|
||||
PreferServerCipherSuites bool `json:"preferServerCipherSuites,omitempty"`
|
||||
ALPNProtocols []string `json:"alpnProtocols,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
|
|
@ -1327,6 +1327,11 @@ func (in *TLSOptionSpec) DeepCopyInto(out *TLSOptionSpec) {
|
|||
copy(*out, *in)
|
||||
}
|
||||
in.ClientAuth.DeepCopyInto(&out.ClientAuth)
|
||||
if in.ALPNProtocols != nil {
|
||||
in, out := &in.ALPNProtocols, &out.ALPNProtocols
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -417,7 +417,7 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *
|
|||
}
|
||||
|
||||
if tlsModeType == v1alpha1.TLSModePassthrough && listener.TLS.CertificateRef != nil {
|
||||
// https://gateway-api.sigs.k8s.io/guides/tls/
|
||||
// https://gateway-api.sigs.k8s.io/v1alpha1/guides/tls/
|
||||
logger.Warnf("In case of Passthrough TLS mode, no TLS settings take effect as the TLS session from the client is NOT terminated at the Gateway")
|
||||
}
|
||||
|
||||
|
@ -900,7 +900,7 @@ func hostRule(httpRouteSpec v1alpha1.HTTPRouteSpec) (string, error) {
|
|||
continue
|
||||
}
|
||||
|
||||
// https://gateway-api.sigs.k8s.io/references/spec/#networking.x-k8s.io/v1alpha1.Hostname
|
||||
// https://gateway-api.sigs.k8s.io/v1alpha1/references/spec/#networking.x-k8s.io/v1alpha1.Hostname
|
||||
if !strings.HasPrefix(host, "*.") || wildcard > 1 {
|
||||
return "", fmt.Errorf("invalid rule: %q", host)
|
||||
}
|
||||
|
|
|
@ -48,12 +48,10 @@ func objChanged(oldObj, newObj interface{}) bool {
|
|||
}
|
||||
|
||||
if _, ok := oldObj.(*corev1.Endpoints); ok {
|
||||
if endpointsChanged(oldObj.(*corev1.Endpoints), newObj.(*corev1.Endpoints)) {
|
||||
return true
|
||||
}
|
||||
return endpointsChanged(oldObj.(*corev1.Endpoints), newObj.(*corev1.Endpoints))
|
||||
}
|
||||
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
func endpointsChanged(a, b *corev1.Endpoints) bool {
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
|
@ -60,6 +61,33 @@ func Test_detectChanges(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Ingress With same version",
|
||||
oldObj: &v1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
},
|
||||
newObj: &v1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Ingress With different version",
|
||||
oldObj: &v1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
ResourceVersion: "1",
|
||||
},
|
||||
},
|
||||
newObj: &v1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
ResourceVersion: "2",
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "With same annotations",
|
||||
oldObj: &corev1.Endpoints{
|
||||
|
|
|
@ -848,6 +848,11 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
ClientAuthType: "foobar",
|
||||
},
|
||||
SniStrict: true,
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
"Options1": {
|
||||
MinVersion: "foobar",
|
||||
|
@ -868,6 +873,11 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
ClientAuthType: "foobar",
|
||||
},
|
||||
SniStrict: true,
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
Stores: map[string]tls.Store{
|
||||
|
|
|
@ -182,7 +182,13 @@ func Test_mergeConfiguration_tlsOptions(t *testing.T) {
|
|||
desc: "Nil returns an empty configuration",
|
||||
given: nil,
|
||||
expected: map[string]tls.Options{
|
||||
"default": {},
|
||||
"default": {
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -199,7 +205,13 @@ func Test_mergeConfiguration_tlsOptions(t *testing.T) {
|
|||
},
|
||||
},
|
||||
expected: map[string]tls.Options{
|
||||
"default": {},
|
||||
"default": {
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
"foo@provider-1": {
|
||||
MinVersion: "VersionTLS12",
|
||||
},
|
||||
|
@ -228,7 +240,13 @@ func Test_mergeConfiguration_tlsOptions(t *testing.T) {
|
|||
},
|
||||
},
|
||||
expected: map[string]tls.Options{
|
||||
"default": {},
|
||||
"default": {
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
"foo@provider-1": {
|
||||
MinVersion: "VersionTLS13",
|
||||
},
|
||||
|
@ -334,7 +352,13 @@ func Test_mergeConfiguration_tlsOptions(t *testing.T) {
|
|||
},
|
||||
},
|
||||
expected: map[string]tls.Options{
|
||||
"default": {},
|
||||
"default": {
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
"foo@provider-1": {
|
||||
MinVersion: "VersionTLS12",
|
||||
},
|
||||
|
|
|
@ -76,7 +76,13 @@ func TestNewConfigurationWatcher(t *testing.T) {
|
|||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Options: map[string]tls.Options{
|
||||
"default": {},
|
||||
"default": {
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
Stores: map[string]tls.Store{},
|
||||
},
|
||||
|
@ -236,7 +242,13 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) {
|
|||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Options: map[string]tls.Options{
|
||||
"default": {},
|
||||
"default": {
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
Stores: map[string]tls.Store{},
|
||||
},
|
||||
|
@ -292,7 +304,13 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
|
|||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Options: map[string]tls.Options{
|
||||
"default": {},
|
||||
"default": {
|
||||
ALPNProtocols: []string{
|
||||
"h2",
|
||||
"http/1.1",
|
||||
"acme-tls/1",
|
||||
},
|
||||
},
|
||||
},
|
||||
Stores: map[string]tls.Store{},
|
||||
},
|
||||
|
|
|
@ -23,6 +23,13 @@ type Options struct {
|
|||
ClientAuth ClientAuth `json:"clientAuth,omitempty" toml:"clientAuth,omitempty" yaml:"clientAuth,omitempty"`
|
||||
SniStrict bool `json:"sniStrict,omitempty" toml:"sniStrict,omitempty" yaml:"sniStrict,omitempty" export:"true"`
|
||||
PreferServerCipherSuites bool `json:"preferServerCipherSuites,omitempty" toml:"preferServerCipherSuites,omitempty" yaml:"preferServerCipherSuites,omitempty" export:"true"`
|
||||
ALPNProtocols []string `json:"alpnProtocols,omitempty" toml:"alpnProtocols,omitempty" yaml:"alpnProtocols,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values for an Options struct.
|
||||
func (o *Options) SetDefaults() {
|
||||
// ensure http2 enabled
|
||||
o.ALPNProtocols = DefaultTLSOptions.ALPNProtocols
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
|
|
@ -24,7 +24,10 @@ const (
|
|||
)
|
||||
|
||||
// DefaultTLSOptions the default TLS options.
|
||||
var DefaultTLSOptions = Options{}
|
||||
var DefaultTLSOptions = Options{
|
||||
// ensure http2 enabled
|
||||
ALPNProtocols: []string{"h2", "http/1.1", tlsalpn01.ACMETLS1Protocol},
|
||||
}
|
||||
|
||||
// Manager is the TLS option/store/configuration factory.
|
||||
type Manager struct {
|
||||
|
@ -230,10 +233,9 @@ func buildCertificateStore(ctx context.Context, tlsStore Store, storename string
|
|||
|
||||
// creates a TLS config that allows terminating HTTPS for multiple domains using SNI.
|
||||
func buildTLSConfig(tlsOption Options) (*tls.Config, error) {
|
||||
conf := &tls.Config{}
|
||||
|
||||
// ensure http2 enabled
|
||||
conf.NextProtos = []string{"h2", "http/1.1", tlsalpn01.ACMETLS1Protocol}
|
||||
conf := &tls.Config{
|
||||
NextProtos: tlsOption.ALPNProtocols,
|
||||
}
|
||||
|
||||
if len(tlsOption.ClientAuth.CAFiles) > 0 {
|
||||
pool := x509.NewCertPool()
|
||||
|
|
|
@ -85,6 +85,11 @@ func (in *Options) DeepCopyInto(out *Options) {
|
|||
copy(*out, *in)
|
||||
}
|
||||
in.ClientAuth.DeepCopyInto(&out.ClientAuth)
|
||||
if in.ALPNProtocols != nil {
|
||||
in, out := &in.ALPNProtocols, &out.ALPNProtocols
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue