Merge v2.10 into v3.0
This commit is contained in:
commit
286181aa61
62 changed files with 712 additions and 189 deletions
|
@ -228,15 +228,6 @@ func (c *Configuration) PopulateUsedBy() {
|
|||
}
|
||||
}
|
||||
|
||||
func contains(entryPoints []string, entryPointName string) bool {
|
||||
for _, name := range entryPoints {
|
||||
if name == entryPointName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getProviderName(elementName string) string {
|
||||
parts := strings.Split(elementName, "@")
|
||||
if len(parts) > 1 {
|
||||
|
|
|
@ -3,6 +3,7 @@ package runtime
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
|
@ -24,7 +25,7 @@ func (c *Configuration) GetRoutersByEntryPoints(ctx context.Context, entryPoints
|
|||
|
||||
entryPointsCount := 0
|
||||
for _, entryPointName := range rt.EntryPoints {
|
||||
if !contains(entryPoints, entryPointName) {
|
||||
if !slices.Contains(entryPoints, entryPointName) {
|
||||
rt.AddError(fmt.Errorf("entryPoint %q doesn't exist", entryPointName), false)
|
||||
logger.Error().Str(logs.EntryPointName, entryPointName).
|
||||
Msg("EntryPoint doesn't exist")
|
||||
|
|
|
@ -3,6 +3,7 @@ package runtime
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
|
@ -18,7 +19,7 @@ func (c *Configuration) GetTCPRoutersByEntryPoints(ctx context.Context, entryPoi
|
|||
|
||||
entryPointsCount := 0
|
||||
for _, entryPointName := range rt.EntryPoints {
|
||||
if !contains(entryPoints, entryPointName) {
|
||||
if !slices.Contains(entryPoints, entryPointName) {
|
||||
rt.AddError(fmt.Errorf("entryPoint %q doesn't exist", entryPointName), false)
|
||||
logger.Error().Str(logs.EntryPointName, entryPointName).
|
||||
Msg("EntryPoint doesn't exist")
|
||||
|
|
|
@ -3,6 +3,7 @@ package runtime
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
|
@ -24,7 +25,7 @@ func (c *Configuration) GetUDPRoutersByEntryPoints(ctx context.Context, entryPoi
|
|||
|
||||
entryPointsCount := 0
|
||||
for _, entryPointName := range eps {
|
||||
if !contains(entryPoints, entryPointName) {
|
||||
if !slices.Contains(entryPoints, entryPointName) {
|
||||
rt.AddError(fmt.Errorf("entryPoint %q doesn't exist", entryPointName), false)
|
||||
logger.Error().Str(logs.EntryPointName, entryPointName).
|
||||
Msg("EntryPoint doesn't exist")
|
||||
|
|
|
@ -42,8 +42,8 @@ func (f *FieldHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// AddOriginFields add origin fields.
|
||||
func AddOriginFields(rw http.ResponseWriter, req *http.Request, next http.Handler, data *LogData) {
|
||||
// AddServiceFields add service fields.
|
||||
func AddServiceFields(rw http.ResponseWriter, req *http.Request, next http.Handler, data *LogData) {
|
||||
start := time.Now().UTC()
|
||||
|
||||
next.ServeHTTP(rw, req)
|
||||
|
@ -65,3 +65,14 @@ func AddOriginFields(rw http.ResponseWriter, req *http.Request, next http.Handle
|
|||
data.Core[OriginStatus] = capt.StatusCode()
|
||||
data.Core[OriginContentSize] = capt.ResponseSize()
|
||||
}
|
||||
|
||||
// InitServiceFields init service fields.
|
||||
func InitServiceFields(rw http.ResponseWriter, req *http.Request, next http.Handler, data *LogData) {
|
||||
// Because they are expected to be initialized when the logger is processing the data table,
|
||||
// the origin fields are initialized in case the response is returned by Traefik itself, and not a service.
|
||||
data.Core[OriginDuration] = time.Duration(0)
|
||||
data.Core[OriginStatus] = 0
|
||||
data.Core[OriginContentSize] = int64(0)
|
||||
|
||||
next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
|
|
@ -40,8 +40,8 @@ func (f *CommonLogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
|||
toLog(entry.Data, RequestMethod, defaultValue, false),
|
||||
toLog(entry.Data, RequestPath, defaultValue, false),
|
||||
toLog(entry.Data, RequestProtocol, defaultValue, false),
|
||||
toLog(entry.Data, OriginStatus, defaultValue, true),
|
||||
toLog(entry.Data, OriginContentSize, defaultValue, true),
|
||||
toLog(entry.Data, DownstreamStatus, defaultValue, true),
|
||||
toLog(entry.Data, DownstreamContentSize, defaultValue, true),
|
||||
toLog(entry.Data, "request_Referer", `"-"`, true),
|
||||
toLog(entry.Data, "request_User-Agent", `"-"`, true),
|
||||
toLog(entry.Data, RequestCount, defaultValue, true),
|
||||
|
|
|
@ -18,7 +18,7 @@ func TestCommonLogFormatter_Format(t *testing.T) {
|
|||
expectedLog string
|
||||
}{
|
||||
{
|
||||
name: "OriginStatus & OriginContentSize are nil",
|
||||
name: "DownstreamStatus & DownstreamContentSize are nil",
|
||||
data: map[string]interface{}{
|
||||
StartUTC: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
|
||||
Duration: 123 * time.Second,
|
||||
|
@ -27,8 +27,8 @@ func TestCommonLogFormatter_Format(t *testing.T) {
|
|||
RequestMethod: http.MethodGet,
|
||||
RequestPath: "/foo",
|
||||
RequestProtocol: "http",
|
||||
OriginStatus: nil,
|
||||
OriginContentSize: nil,
|
||||
DownstreamStatus: nil,
|
||||
DownstreamContentSize: nil,
|
||||
RequestRefererHeader: "",
|
||||
RequestUserAgentHeader: "",
|
||||
RequestCount: 0,
|
||||
|
@ -48,8 +48,8 @@ func TestCommonLogFormatter_Format(t *testing.T) {
|
|||
RequestMethod: http.MethodGet,
|
||||
RequestPath: "/foo",
|
||||
RequestProtocol: "http",
|
||||
OriginStatus: 123,
|
||||
OriginContentSize: 132,
|
||||
DownstreamStatus: 123,
|
||||
DownstreamContentSize: 132,
|
||||
RequestRefererHeader: "referer",
|
||||
RequestUserAgentHeader: "agent",
|
||||
RequestCount: nil,
|
||||
|
@ -69,8 +69,8 @@ func TestCommonLogFormatter_Format(t *testing.T) {
|
|||
RequestMethod: http.MethodGet,
|
||||
RequestPath: "/foo",
|
||||
RequestProtocol: "http",
|
||||
OriginStatus: 123,
|
||||
OriginContentSize: 132,
|
||||
DownstreamStatus: 123,
|
||||
DownstreamContentSize: 132,
|
||||
RequestRefererHeader: "referer",
|
||||
RequestUserAgentHeader: "agent",
|
||||
RequestCount: nil,
|
||||
|
|
|
@ -125,6 +125,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer forwardResponse.Body.Close()
|
||||
|
||||
body, readError := io.ReadAll(forwardResponse.Body)
|
||||
if readError != nil {
|
||||
|
@ -135,7 +136,6 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer forwardResponse.Body.Close()
|
||||
|
||||
// Pass the forward response's body and selected headers if it
|
||||
// didn't return a response within the range of [200, 300).
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"mime"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/klauspost/compress/gzhttp"
|
||||
|
@ -87,7 +88,7 @@ func (c *compress) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
|
||||
// Notably for text/event-stream requests the response should not be compressed.
|
||||
// See https://github.com/traefik/traefik/issues/2576
|
||||
if contains(c.excludes, mediaType) {
|
||||
if slices.Contains(c.excludes, mediaType) {
|
||||
c.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
@ -158,13 +159,3 @@ func encodingAccepts(acceptEncoding []string, typ string) bool {
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
func contains(values []string, val string) bool {
|
||||
for _, v := range values {
|
||||
if v == val {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/vulcand/oxy/v2/forward"
|
||||
)
|
||||
|
||||
// Header is a middleware that helps setup a few basic security features.
|
||||
|
@ -47,6 +48,7 @@ func NewHeader(next http.Handler, cfg dynamic.Headers) (*Header, error) {
|
|||
func (s *Header) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
// Handle Cors headers and preflight if configured.
|
||||
if isPreflight := s.processCorsHeaders(rw, req); isPreflight {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -65,6 +67,10 @@ func (s *Header) modifyCustomRequestHeaders(req *http.Request) {
|
|||
// Loop through Custom request headers
|
||||
for header, value := range s.headers.CustomRequestHeaders {
|
||||
switch {
|
||||
// Handling https://github.com/golang/go/commit/ecdbffd4ec68b509998792f120868fec319de59b.
|
||||
case value == "" && header == forward.XForwardedFor:
|
||||
req.Header[header] = nil
|
||||
|
||||
case value == "":
|
||||
req.Header.Del(header)
|
||||
|
||||
|
|
|
@ -29,11 +29,14 @@ func TestNewHeader_customRequestHeader(t *testing.T) {
|
|||
desc: "delete a header",
|
||||
cfg: dynamic.Headers{
|
||||
CustomRequestHeaders: map[string]string{
|
||||
"X-Forwarded-For": "",
|
||||
"X-Custom-Request-Header": "",
|
||||
"Foo": "",
|
||||
},
|
||||
},
|
||||
expected: http.Header{},
|
||||
expected: http.Header{
|
||||
"X-Forwarded-For": nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "override a header",
|
||||
|
|
|
@ -2,6 +2,7 @@ package requestdecorator
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
|
@ -65,9 +66,7 @@ func (hr *Resolver) CNAMEFlatten(ctx context.Context, host string) string {
|
|||
request = resolv.Record
|
||||
}
|
||||
|
||||
if err := hr.cache.Add(host, result, cacheDuration); err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
}
|
||||
hr.cache.Set(host, result, cacheDuration)
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -79,6 +78,10 @@ func cnameResolve(ctx context.Context, host, resolvPath string) (*cnameResolv, e
|
|||
return nil, fmt.Errorf("invalid resolver configuration file: %s", resolvPath)
|
||||
}
|
||||
|
||||
if net.ParseIP(host) != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
client := &dns.Client{Timeout: 30 * time.Second}
|
||||
|
||||
m := &dns.Msg{}
|
||||
|
@ -88,7 +91,11 @@ func cnameResolve(ctx context.Context, host, resolvPath string) (*cnameResolv, e
|
|||
for _, server := range config.Servers {
|
||||
tempRecord, err := getRecord(client, m, server, config.Port)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Error().Err(err).Msgf("Failed to resolve host %s", host)
|
||||
if errors.Is(err, errNoCNAMERecord) {
|
||||
log.Ctx(ctx).Debug().Err(err).Msgf("CNAME lookup for hostname %q", host)
|
||||
continue
|
||||
}
|
||||
log.Ctx(ctx).Error().Err(err).Msgf("CNAME lookup for hostname %q", host)
|
||||
continue
|
||||
}
|
||||
result = append(result, tempRecord)
|
||||
|
@ -102,6 +109,8 @@ func cnameResolve(ctx context.Context, host, resolvPath string) (*cnameResolv, e
|
|||
return result[0], nil
|
||||
}
|
||||
|
||||
var errNoCNAMERecord = errors.New("no CNAME record for host")
|
||||
|
||||
func getRecord(client *dns.Client, msg *dns.Msg, server, port string) (*cnameResolv, error) {
|
||||
resp, _, err := client.Exchange(msg, net.JoinHostPort(server, port))
|
||||
if err != nil {
|
||||
|
@ -109,7 +118,7 @@ func getRecord(client *dns.Client, msg *dns.Msg, server, port string) (*cnameRes
|
|||
}
|
||||
|
||||
if resp == nil || len(resp.Answer) == 0 {
|
||||
return nil, fmt.Errorf("empty answer for server %s", server)
|
||||
return nil, fmt.Errorf("%w: %s", errNoCNAMERecord, server)
|
||||
}
|
||||
|
||||
rr, ok := resp.Answer[0].(*dns.CNAME)
|
||||
|
|
|
@ -86,7 +86,7 @@ type DNSChallenge struct {
|
|||
|
||||
// HTTPChallenge contains HTTP challenge configuration.
|
||||
type HTTPChallenge struct {
|
||||
EntryPoint string `description:"HTTP challenge EntryPoint" json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty" export:"true"`
|
||||
EntryPoint string `description:"HTTP challenge EntryPoint" json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// TLSChallenge contains TLS challenge configuration.
|
||||
|
|
|
@ -134,6 +134,16 @@ func (p *Provider) watchKv(ctx context.Context, configurationChan chan<- dynamic
|
|||
func (p *Provider) buildConfiguration(ctx context.Context) (*dynamic.Configuration, error) {
|
||||
pairs, err := p.kvClient.List(ctx, p.RootKey, nil)
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrKeyNotFound) {
|
||||
// This empty configuration satisfies the pkg/server/configurationwatcher.go isEmptyConfiguration func constraints,
|
||||
// and will not be discarded by the configuration watcher.
|
||||
return &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: make(map[string]*dynamic.Router),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
||||
"github.com/mitchellh/copystructure"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
|
@ -67,8 +66,7 @@ func do(baseConfig interface{}, tag string, redactByDefault, indent bool) (strin
|
|||
}
|
||||
|
||||
func doOnJSON(input string) string {
|
||||
mailExp := regexp.MustCompile(`\w[-.\w]*\w@\w[-.\w]*\w\.\w{2,3}"`)
|
||||
return xurls.Relaxed().ReplaceAllString(mailExp.ReplaceAllString(input, maskLarge+"\""), maskLarge)
|
||||
return xurls.Relaxed().ReplaceAllString(input, maskLarge)
|
||||
}
|
||||
|
||||
func doOnStruct(field reflect.Value, tag string, redactByDefault bool) error {
|
||||
|
|
|
@ -45,13 +45,15 @@ func Test_doOnJSON_simple(t *testing.T) {
|
|||
"URL": "foo domain.com foo",
|
||||
"URL": "foo sub.domain.com foo",
|
||||
"URL": "foo sub.sub.domain.com foo",
|
||||
"URL": "foo sub.sub.sub.domain.com.us foo"
|
||||
"URL": "foo sub.sub.sub.domain.com.us foo",
|
||||
"URL":"https://hub.example.com","foo":"bar"
|
||||
}`,
|
||||
expectedOutput: `{
|
||||
"URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo",
|
||||
"URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo",
|
||||
"URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo",
|
||||
"URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo"
|
||||
"URL": "foo xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx foo",
|
||||
"URL":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","foo":"bar"
|
||||
}`,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
|
|||
}
|
||||
|
||||
handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) {
|
||||
return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.AddOriginFields), nil
|
||||
return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil
|
||||
}).Then(handler)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Send()
|
||||
|
|
|
@ -73,7 +73,6 @@ func (s *Server) Wait() {
|
|||
|
||||
// Stop stops the server.
|
||||
func (s *Server) Stop() {
|
||||
//nolint:zerologlint // false-positive https://github.com/ykadowak/zerologlint/issues/3
|
||||
defer log.Info().Msg("Server stopped")
|
||||
|
||||
s.tcpEntryPoints.Stop()
|
||||
|
|
|
@ -301,7 +301,7 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
|
|||
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil)
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil)
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, nil)
|
||||
proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields)
|
||||
|
||||
if m.metricsRegistry != nil && m.metricsRegistry.IsSvcEnabled() {
|
||||
proxy = metricsMiddle.NewServiceMiddleware(ctx, proxy, m.metricsRegistry, serviceName)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue