Merge current branch master into v2.7
This commit is contained in:
commit
59e66dfce5
72 changed files with 1162 additions and 605 deletions
|
@ -93,7 +93,21 @@ type Chain struct {
|
|||
|
||||
// CircuitBreaker holds the circuit breaker configuration.
|
||||
type CircuitBreaker struct {
|
||||
// Expression is the condition that triggers the tripped state.
|
||||
Expression string `json:"expression,omitempty" toml:"expression,omitempty" yaml:"expression,omitempty" export:"true"`
|
||||
// CheckPeriod is the interval between successive checks of the circuit breaker condition (when in standby state).
|
||||
CheckPeriod ptypes.Duration `json:"checkPeriod,omitempty" toml:"checkPeriod,omitempty" yaml:"checkPeriod,omitempty" export:"true"`
|
||||
// FallbackDuration is the duration for which the circuit breaker will wait before trying to recover (from a tripped state).
|
||||
FallbackDuration ptypes.Duration `json:"fallbackDuration,omitempty" toml:"fallbackDuration,omitempty" yaml:"fallbackDuration,omitempty" export:"true"`
|
||||
// RecoveryDuration is the duration for which the circuit breaker will try to recover (as soon as it is in recovering state).
|
||||
RecoveryDuration ptypes.Duration `json:"recoveryDuration,omitempty" toml:"recoveryDuration,omitempty" yaml:"recoveryDuration,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values on a RateLimit.
|
||||
func (c *CircuitBreaker) SetDefaults() {
|
||||
c.CheckPeriod = ptypes.Duration(100 * time.Millisecond)
|
||||
c.FallbackDuration = ptypes.Duration(10 * time.Second)
|
||||
c.RecoveryDuration = ptypes.Duration(10 * time.Second)
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
|
|
@ -27,6 +27,9 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
"traefik.http.middlewares.Middleware2.buffering.retryexpression": "foobar",
|
||||
"traefik.http.middlewares.Middleware3.chain.middlewares": "foobar, fiibar",
|
||||
"traefik.http.middlewares.Middleware4.circuitbreaker.expression": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware4.circuitbreaker.checkperiod": "1s",
|
||||
"traefik.HTTP.Middlewares.Middleware4.circuitbreaker.fallbackduration": "1s",
|
||||
"traefik.HTTP.Middlewares.Middleware4.circuitbreaker.recoveryduration": "1s",
|
||||
"traefik.http.middlewares.Middleware5.digestauth.headerfield": "foobar",
|
||||
"traefik.http.middlewares.Middleware5.digestauth.realm": "foobar",
|
||||
"traefik.http.middlewares.Middleware5.digestauth.removeheader": "true",
|
||||
|
@ -488,7 +491,10 @@ func TestDecodeConfiguration(t *testing.T) {
|
|||
},
|
||||
"Middleware4": {
|
||||
CircuitBreaker: &dynamic.CircuitBreaker{
|
||||
Expression: "foobar",
|
||||
Expression: "foobar",
|
||||
CheckPeriod: ptypes.Duration(time.Second),
|
||||
FallbackDuration: ptypes.Duration(time.Second),
|
||||
RecoveryDuration: ptypes.Duration(time.Second),
|
||||
},
|
||||
},
|
||||
"Middleware5": {
|
||||
|
@ -983,7 +989,10 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
},
|
||||
"Middleware4": {
|
||||
CircuitBreaker: &dynamic.CircuitBreaker{
|
||||
Expression: "foobar",
|
||||
Expression: "foobar",
|
||||
CheckPeriod: ptypes.Duration(time.Second),
|
||||
FallbackDuration: ptypes.Duration(time.Second),
|
||||
RecoveryDuration: ptypes.Duration(time.Second),
|
||||
},
|
||||
},
|
||||
"Middleware5": {
|
||||
|
@ -1191,6 +1200,9 @@ func TestEncodeConfiguration(t *testing.T) {
|
|||
"traefik.HTTP.Middlewares.Middleware2.Buffering.RetryExpression": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware3.Chain.Middlewares": "foobar, fiibar",
|
||||
"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.Expression": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.CheckPeriod": "1000000000",
|
||||
"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.FallbackDuration": "1000000000",
|
||||
"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.RecoveryDuration": "1000000000",
|
||||
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.HeaderField": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.Realm": "foobar",
|
||||
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.RemoveHeader": "true",
|
||||
|
|
|
@ -16,7 +16,8 @@ type EntryPoint struct {
|
|||
ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
ForwardedHeaders *ForwardedHeaders `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty" export:"true"`
|
||||
HTTP HTTPConfig `description:"HTTP configuration." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"`
|
||||
HTTP3 *HTTP3Config `description:"HTTP3 configuration." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
HTTP2 *HTTP2Config `description:"HTTP/2 configuration." json:"http2,omitempty" toml:"http2,omitempty" yaml:"http2,omitempty" export:"true"`
|
||||
HTTP3 *HTTP3Config `description:"HTTP/3 configuration." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
UDP *UDPConfig `description:"UDP configuration." json:"udp,omitempty" toml:"udp,omitempty" yaml:"udp,omitempty"`
|
||||
}
|
||||
|
||||
|
@ -50,6 +51,8 @@ func (ep *EntryPoint) SetDefaults() {
|
|||
ep.ForwardedHeaders = &ForwardedHeaders{}
|
||||
ep.UDP = &UDPConfig{}
|
||||
ep.UDP.SetDefaults()
|
||||
ep.HTTP2 = &HTTP2Config{}
|
||||
ep.HTTP2.SetDefaults()
|
||||
}
|
||||
|
||||
// HTTPConfig is the HTTP configuration of an entry point.
|
||||
|
@ -59,9 +62,19 @@ type HTTPConfig struct {
|
|||
TLS *TLSConfig `description:"Default TLS configuration for the routers linked to the entry point." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
}
|
||||
|
||||
// HTTP2Config is the HTTP2 configuration of an entry point.
|
||||
type HTTP2Config struct {
|
||||
MaxConcurrentStreams int32 `description:"Specifies the number of concurrent streams per connection that each client is allowed to initiate." json:"maxConcurrentStreams,omitempty" toml:"maxConcurrentStreams,omitempty" yaml:"maxConcurrentStreams,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *HTTP2Config) SetDefaults() {
|
||||
c.MaxConcurrentStreams = 250 // https://cs.opensource.google/go/x/net/+/cd36cc07:http2/server.go;l=58
|
||||
}
|
||||
|
||||
// HTTP3Config is the HTTP3 configuration of an entry point.
|
||||
type HTTP3Config struct {
|
||||
AdvertisedPort int32 `description:"UDP port to advertise, on which HTTP/3 is available." json:"advertisedPort,omitempty" toml:"advertisedPort,omitempty" yaml:"advertisedPort,omitempty" export:"true"`
|
||||
AdvertisedPort int `description:"UDP port to advertise, on which HTTP/3 is available." json:"advertisedPort,omitempty" toml:"advertisedPort,omitempty" yaml:"advertisedPort,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// Redirections is a set of redirection for an entry point.
|
||||
|
|
|
@ -175,22 +175,22 @@ func (t *Tracing) SetDefaults() {
|
|||
type Providers struct {
|
||||
ProvidersThrottleDuration ptypes.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." json:"providersThrottleDuration,omitempty" toml:"providersThrottleDuration,omitempty" yaml:"providersThrottleDuration,omitempty" export:"true"`
|
||||
|
||||
Docker *docker.Provider `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
File *file.Provider `description:"Enable File backend with default settings." json:"file,omitempty" toml:"file,omitempty" yaml:"file,omitempty" export:"true"`
|
||||
Marathon *marathon.Provider `description:"Enable Marathon backend with default settings." json:"marathon,omitempty" toml:"marathon,omitempty" yaml:"marathon,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
KubernetesGateway *gateway.Provider `description:"Enable Kubernetes gateway api provider with default settings." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
Rancher *rancher.Provider `description:"Enable Rancher backend with default settings." json:"rancher,omitempty" toml:"rancher,omitempty" yaml:"rancher,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
ConsulCatalog *consulcatalog.Provider `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Ecs *ecs.Provider `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Docker *docker.Provider `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
File *file.Provider `description:"Enable File backend with default settings." json:"file,omitempty" toml:"file,omitempty" yaml:"file,omitempty" export:"true"`
|
||||
Marathon *marathon.Provider `description:"Enable Marathon backend with default settings." json:"marathon,omitempty" toml:"marathon,omitempty" yaml:"marathon,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
KubernetesGateway *gateway.Provider `description:"Enable Kubernetes gateway api provider with default settings." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
Rancher *rancher.Provider `description:"Enable Rancher backend with default settings." json:"rancher,omitempty" toml:"rancher,omitempty" yaml:"rancher,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
|
||||
ConsulCatalog *consulcatalog.ProviderBuilder `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Ecs *ecs.Provider `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
|
||||
Consul *consul.Provider `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Redis *redis.Provider `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
HTTP *http.Provider `description:"Enable HTTP backend with default settings." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Consul *consul.ProviderBuilder `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
Redis *redis.Provider `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
HTTP *http.Provider `description:"Enable HTTP backend with default settings." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
|
||||
Plugin map[string]PluginConf `description:"Plugins configuration." json:"plugin,omitempty" toml:"plugin,omitempty" yaml:"plugin,omitempty"`
|
||||
}
|
||||
|
@ -257,7 +257,7 @@ func (c *Configuration) SetEffectiveConfiguration() {
|
|||
c.Pilot.SetDefaults()
|
||||
}
|
||||
|
||||
// Disable Gateway API provider if not enabled in experimental
|
||||
// Disable Gateway API provider if not enabled in experimental.
|
||||
if c.Experimental == nil || !c.Experimental.KubernetesGateway {
|
||||
c.Providers.KubernetesGateway = nil
|
||||
}
|
||||
|
@ -328,6 +328,14 @@ func (c *Configuration) ValidateConfiguration() error {
|
|||
acmeEmail = resolver.ACME.Email
|
||||
}
|
||||
|
||||
if c.Providers.ConsulCatalog != nil && c.Providers.ConsulCatalog.Namespace != "" && len(c.Providers.ConsulCatalog.Namespaces) > 0 {
|
||||
return fmt.Errorf("consul catalog provider cannot have both namespace and namespaces options configured")
|
||||
}
|
||||
|
||||
if c.Providers.Consul != nil && c.Providers.Consul.Namespace != "" && len(c.Providers.Consul.Namespaces) > 0 {
|
||||
return fmt.Errorf("consul provider cannot have both namespace and namespaces options configured")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package circuitbreaker
|
|||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
|
@ -12,9 +13,7 @@ import (
|
|||
"github.com/vulcand/oxy/cbreaker"
|
||||
)
|
||||
|
||||
const (
|
||||
typeName = "CircuitBreaker"
|
||||
)
|
||||
const typeName = "CircuitBreaker"
|
||||
|
||||
type circuitBreaker struct {
|
||||
circuitBreaker *cbreaker.CircuitBreaker
|
||||
|
@ -27,9 +26,32 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ
|
|||
|
||||
logger := log.FromContext(middlewares.GetLoggerCtx(ctx, name, typeName))
|
||||
logger.Debug("Creating middleware")
|
||||
logger.Debug("Setting up with expression: %s", expression)
|
||||
logger.Debugf("Setting up with expression: %s", expression)
|
||||
|
||||
oxyCircuitBreaker, err := cbreaker.New(next, expression, createCircuitBreakerOptions(expression))
|
||||
cbOpts := []cbreaker.CircuitBreakerOption{
|
||||
cbreaker.Fallback(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
tracing.SetErrorWithEvent(req, "blocked by circuit-breaker (%q)", expression)
|
||||
rw.WriteHeader(http.StatusServiceUnavailable)
|
||||
|
||||
if _, err := rw.Write([]byte(http.StatusText(http.StatusServiceUnavailable))); err != nil {
|
||||
log.FromContext(req.Context()).Error(err)
|
||||
}
|
||||
})),
|
||||
}
|
||||
|
||||
if confCircuitBreaker.CheckPeriod > 0 {
|
||||
cbOpts = append(cbOpts, cbreaker.CheckPeriod(time.Duration(confCircuitBreaker.CheckPeriod)))
|
||||
}
|
||||
|
||||
if confCircuitBreaker.FallbackDuration > 0 {
|
||||
cbOpts = append(cbOpts, cbreaker.FallbackDuration(time.Duration(confCircuitBreaker.FallbackDuration)))
|
||||
}
|
||||
|
||||
if confCircuitBreaker.RecoveryDuration > 0 {
|
||||
cbOpts = append(cbOpts, cbreaker.RecoveryDuration(time.Duration(confCircuitBreaker.RecoveryDuration)))
|
||||
}
|
||||
|
||||
oxyCircuitBreaker, err := cbreaker.New(next, expression, cbOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -39,18 +61,6 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ
|
|||
}, nil
|
||||
}
|
||||
|
||||
// NewCircuitBreakerOptions returns a new CircuitBreakerOption.
|
||||
func createCircuitBreakerOptions(expression string) cbreaker.CircuitBreakerOption {
|
||||
return cbreaker.Fallback(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
tracing.SetErrorWithEvent(req, "blocked by circuit-breaker (%q)", expression)
|
||||
rw.WriteHeader(http.StatusServiceUnavailable)
|
||||
|
||||
if _, err := rw.Write([]byte(http.StatusText(http.StatusServiceUnavailable))); err != nil {
|
||||
log.FromContext(req.Context()).Error(err)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func (c *circuitBreaker) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||
return c.name, tracing.SpanKindNoneEnum
|
||||
}
|
||||
|
|
|
@ -93,6 +93,7 @@ func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
if len(c.backendQuery) > 0 {
|
||||
query = "/" + strings.TrimPrefix(c.backendQuery, "/")
|
||||
query = strings.ReplaceAll(query, "{status}", strconv.Itoa(code))
|
||||
query = strings.ReplaceAll(query, "{url}", url.QueryEscape(req.URL.String()))
|
||||
}
|
||||
|
||||
pageReq, err := newRequest("http://" + req.Host + query)
|
||||
|
|
|
@ -133,6 +133,24 @@ func TestHandler(t *testing.T) {
|
|||
assert.Contains(t, recorder.Body.String(), "localhost")
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "full query replacement",
|
||||
errorPage: &dynamic.ErrorPage{Service: "error", Query: "/?status={status}&url={url}", Status: []string{"503"}},
|
||||
backendCode: http.StatusServiceUnavailable,
|
||||
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.RequestURI != "/?status=503&url=http%3A%2F%2Flocalhost%2Ftest%3Ffoo%3Dbar%26baz%3Dbuz" {
|
||||
t.Log(r.RequestURI)
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(w, "My 503 page.")
|
||||
}),
|
||||
validate: func(t *testing.T, recorder *httptest.ResponseRecorder) {
|
||||
t.Helper()
|
||||
assert.Equal(t, http.StatusServiceUnavailable, recorder.Code, "HTTP status")
|
||||
assert.Contains(t, recorder.Body.String(), "My 503 page.")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
@ -153,7 +171,7 @@ func TestHandler(t *testing.T) {
|
|||
errorPageHandler, err := New(context.Background(), handler, *test.errorPage, serviceBuilderMock, "test")
|
||||
require.NoError(t, err)
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost/test", nil)
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost/test?foo=bar&baz=buz", nil)
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
errorPageHandler.ServeHTTP(recorder, req)
|
||||
|
|
|
@ -109,11 +109,15 @@ func NewProviderAggregator(conf static.Providers) ProviderAggregator {
|
|||
}
|
||||
|
||||
if conf.ConsulCatalog != nil {
|
||||
p.quietAddProvider(conf.ConsulCatalog)
|
||||
for _, pvd := range conf.ConsulCatalog.BuildProviders() {
|
||||
p.quietAddProvider(pvd)
|
||||
}
|
||||
}
|
||||
|
||||
if conf.Consul != nil {
|
||||
p.quietAddProvider(conf.Consul)
|
||||
for _, pvd := range conf.Consul.BuildProviders() {
|
||||
p.quietAddProvider(pvd)
|
||||
}
|
||||
}
|
||||
|
||||
if conf.Etcd != nil {
|
||||
|
|
|
@ -219,7 +219,7 @@ func TestDefaultRule(t *testing.T) {
|
|||
Status: api.HealthPassing,
|
||||
},
|
||||
},
|
||||
defaultRule: DefaultTemplateRule,
|
||||
defaultRule: defaultTemplateRule,
|
||||
expected: &dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
|
@ -262,8 +262,10 @@ func TestDefaultRule(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
p := Provider{
|
||||
ExposedByDefault: true,
|
||||
DefaultRule: test.defaultRule,
|
||||
Configuration: Configuration{
|
||||
ExposedByDefault: true,
|
||||
DefaultRule: test.defaultRule,
|
||||
},
|
||||
}
|
||||
|
||||
err := p.Init()
|
||||
|
@ -2618,10 +2620,12 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
p := Provider{
|
||||
ExposedByDefault: true,
|
||||
DefaultRule: "Host(`{{ normalize .Name }}.traefik.wtf`)",
|
||||
ConnectAware: test.ConnectAware,
|
||||
Constraints: test.constraints,
|
||||
Configuration: Configuration{
|
||||
ExposedByDefault: true,
|
||||
DefaultRule: "Host(`{{ normalize .Name }}.traefik.wtf`)",
|
||||
ConnectAware: test.ConnectAware,
|
||||
Constraints: test.constraints,
|
||||
},
|
||||
}
|
||||
|
||||
err := p.Init()
|
||||
|
@ -2651,3 +2655,55 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaces(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
namespace string
|
||||
namespaces []string
|
||||
expectedNamespaces []string
|
||||
}{
|
||||
{
|
||||
desc: "no defined namespaces",
|
||||
expectedNamespaces: []string{""},
|
||||
},
|
||||
{
|
||||
desc: "deprecated: use of defined namespace",
|
||||
namespace: "test-ns",
|
||||
expectedNamespaces: []string{"test-ns"},
|
||||
},
|
||||
{
|
||||
desc: "use of 1 defined namespaces",
|
||||
namespaces: []string{"test-ns"},
|
||||
expectedNamespaces: []string{"test-ns"},
|
||||
},
|
||||
{
|
||||
desc: "use of multiple defined namespaces",
|
||||
namespaces: []string{"test-ns1", "test-ns2", "test-ns3", "test-ns4"},
|
||||
expectedNamespaces: []string{"test-ns1", "test-ns2", "test-ns3", "test-ns4"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pb := &ProviderBuilder{
|
||||
Namespace: test.namespace,
|
||||
Namespaces: test.namespaces,
|
||||
}
|
||||
|
||||
assert.Equal(t, test.expectedNamespaces, extractNSFromProvider(pb.BuildProviders()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func extractNSFromProvider(providers []*Provider) []string {
|
||||
res := make([]string, len(providers))
|
||||
for i, p := range providers {
|
||||
res[i] = p.namespace
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
|
|
@ -22,8 +22,11 @@ import (
|
|||
"github.com/traefik/traefik/v2/pkg/types"
|
||||
)
|
||||
|
||||
// DefaultTemplateRule The default template for the default rule.
|
||||
const DefaultTemplateRule = "Host(`{{ normalize .Name }}`)"
|
||||
// defaultTemplateRule is the default template for the default rule.
|
||||
const defaultTemplateRule = "Host(`{{ normalize .Name }}`)"
|
||||
|
||||
// providerName is the Consul Catalog provider name.
|
||||
const providerName = "consulcatalog"
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
|
||||
|
@ -41,12 +44,50 @@ type itemData struct {
|
|||
ExtraConf configuration
|
||||
}
|
||||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
// ProviderBuilder is responsible for constructing namespaced instances of the Consul Catalog provider.
|
||||
type ProviderBuilder struct {
|
||||
Configuration `export:"true"`
|
||||
|
||||
// Deprecated: use Namespaces option instead.
|
||||
Namespace string `description:"Sets the namespace used to discover services (Consul Enterprise only)." json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
Namespaces []string `description:"Sets the namespaces used to discover services (Consul Enterprise only)." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty"`
|
||||
}
|
||||
|
||||
// BuildProviders builds Consul Catalog provider instances for the given namespaces configuration.
|
||||
func (p *ProviderBuilder) BuildProviders() []*Provider {
|
||||
// We can warn about that, because we've already made sure before that
|
||||
// Namespace and Namespaces are mutually exclusive.
|
||||
if p.Namespace != "" {
|
||||
log.WithoutContext().Warnf("Namespace option is deprecated, please use the Namespaces option instead.")
|
||||
}
|
||||
|
||||
if len(p.Namespaces) == 0 {
|
||||
return []*Provider{{
|
||||
Configuration: p.Configuration,
|
||||
name: providerName,
|
||||
// p.Namespace could very well be empty.
|
||||
namespace: p.Namespace,
|
||||
}}
|
||||
}
|
||||
|
||||
var providers []*Provider
|
||||
for _, namespace := range p.Namespaces {
|
||||
providers = append(providers, &Provider{
|
||||
Configuration: p.Configuration,
|
||||
name: providerName + "-" + namespace,
|
||||
namespace: namespace,
|
||||
})
|
||||
}
|
||||
|
||||
return providers
|
||||
}
|
||||
|
||||
// Configuration represents the Consul Catalog provider configuration.
|
||||
type Configuration struct {
|
||||
Constraints string `description:"Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true"`
|
||||
Endpoint *EndpointConfig `description:"Consul endpoint settings" json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" export:"true"`
|
||||
Prefix string `description:"Prefix for consul service tags. Default 'traefik'" json:"prefix,omitempty" toml:"prefix,omitempty" yaml:"prefix,omitempty" export:"true"`
|
||||
RefreshInterval ptypes.Duration `description:"Interval for check Consul API. Default 15s" json:"refreshInterval,omitempty" toml:"refreshInterval,omitempty" yaml:"refreshInterval,omitempty" export:"true"`
|
||||
Prefix string `description:"Prefix for consul service tags." json:"prefix,omitempty" toml:"prefix,omitempty" yaml:"prefix,omitempty" export:"true"`
|
||||
RefreshInterval ptypes.Duration `description:"Interval for check Consul API." json:"refreshInterval,omitempty" toml:"refreshInterval,omitempty" yaml:"refreshInterval,omitempty" export:"true"`
|
||||
RequireConsistent bool `description:"Forces the read to be fully consistent." json:"requireConsistent,omitempty" toml:"requireConsistent,omitempty" yaml:"requireConsistent,omitempty" export:"true"`
|
||||
Stale bool `description:"Use stale consistency for catalog reads." json:"stale,omitempty" toml:"stale,omitempty" yaml:"stale,omitempty" export:"true"`
|
||||
Cache bool `description:"Use local agent caching for catalog reads." json:"cache,omitempty" toml:"cache,omitempty" yaml:"cache,omitempty" export:"true"`
|
||||
|
@ -55,9 +96,25 @@ type Provider struct {
|
|||
ConnectAware bool `description:"Enable Consul Connect support." json:"connectAware,omitempty" toml:"connectAware,omitempty" yaml:"connectAware,omitempty" export:"true"`
|
||||
ConnectByDefault bool `description:"Consider every service as Connect capable by default." json:"connectByDefault,omitempty" toml:"connectByDefault,omitempty" yaml:"connectByDefault,omitempty" export:"true"`
|
||||
ServiceName string `description:"Name of the Traefik service in Consul Catalog (needs to be registered via the orchestrator or manually)." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"`
|
||||
Namespace string `description:"Sets the namespace used to discover services (Consul Enterprise only)." json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty" export:"true"`
|
||||
Watch bool `description:"Watch Consul API events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *Configuration) SetDefaults() {
|
||||
c.Endpoint = &EndpointConfig{}
|
||||
c.RefreshInterval = ptypes.Duration(15 * time.Second)
|
||||
c.Prefix = "traefik"
|
||||
c.ExposedByDefault = true
|
||||
c.DefaultRule = defaultTemplateRule
|
||||
c.ServiceName = "traefik"
|
||||
}
|
||||
|
||||
// Provider is the Consul Catalog provider implementation.
|
||||
type Provider struct {
|
||||
Configuration
|
||||
|
||||
name string
|
||||
namespace string
|
||||
client *api.Client
|
||||
defaultRuleTpl *template.Template
|
||||
certChan chan *connectCert
|
||||
|
@ -81,17 +138,6 @@ type EndpointHTTPAuthConfig struct {
|
|||
Password string `description:"Basic Auth password" json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" loggable:"false"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (p *Provider) SetDefaults() {
|
||||
endpoint := &EndpointConfig{}
|
||||
p.Endpoint = endpoint
|
||||
p.RefreshInterval = ptypes.Duration(15 * time.Second)
|
||||
p.Prefix = "traefik"
|
||||
p.ExposedByDefault = true
|
||||
p.DefaultRule = DefaultTemplateRule
|
||||
p.ServiceName = "traefik"
|
||||
}
|
||||
|
||||
// Init the provider.
|
||||
func (p *Provider) Init() error {
|
||||
defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, nil)
|
||||
|
@ -103,19 +149,24 @@ func (p *Provider) Init() error {
|
|||
p.certChan = make(chan *connectCert, 1)
|
||||
p.watchServicesChan = make(chan struct{}, 1)
|
||||
|
||||
// In case they didn't initialize Provider with BuildProviders.
|
||||
if p.name == "" {
|
||||
p.name = providerName
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide allows the consul catalog provider to provide configurations to traefik using the given configuration channel.
|
||||
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
|
||||
var err error
|
||||
p.client, err = createClient(p.Namespace, p.Endpoint)
|
||||
p.client, err = createClient(p.namespace, p.Endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create consul client: %w", err)
|
||||
}
|
||||
|
||||
pool.GoCtx(func(routineCtx context.Context) {
|
||||
ctxLog := log.With(routineCtx, log.Str(log.ProviderName, "consulcatalog"))
|
||||
ctxLog := log.With(routineCtx, log.Str(log.ProviderName, p.name))
|
||||
logger := log.FromContext(ctxLog)
|
||||
|
||||
operation := func() error {
|
||||
|
@ -210,7 +261,7 @@ func (p *Provider) loadConfiguration(ctx context.Context, certInfo *connectCert,
|
|||
}
|
||||
|
||||
configurationChan <- dynamic.Message{
|
||||
ProviderName: "consulcatalog",
|
||||
ProviderName: p.name,
|
||||
Configuration: p.buildConfiguration(ctx, data, certInfo),
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TLSStore
|
||||
metadata:
|
||||
name: default
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
certificates:
|
||||
- secretName: supersecret
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: supersecret
|
||||
namespace: default
|
||||
|
||||
data:
|
||||
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
|
||||
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
|
||||
routes:
|
||||
- match: Host(`foo.com`) && PathPrefix(`/bar`)
|
||||
kind: Rule
|
||||
priority: 12
|
||||
services:
|
||||
- name: whoami
|
||||
port: 80
|
||||
|
||||
tls:
|
||||
store:
|
||||
name: default
|
|
@ -179,18 +179,25 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
|||
}
|
||||
|
||||
func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) *dynamic.Configuration {
|
||||
tlsConfigs := make(map[string]*tls.CertAndStores)
|
||||
stores, tlsConfigs := buildTLSStores(ctx, client)
|
||||
if tlsConfigs == nil {
|
||||
tlsConfigs = make(map[string]*tls.CertAndStores)
|
||||
}
|
||||
|
||||
conf := &dynamic.Configuration{
|
||||
// TODO: choose between mutating and returning tlsConfigs
|
||||
HTTP: p.loadIngressRouteConfiguration(ctx, client, tlsConfigs),
|
||||
TCP: p.loadIngressRouteTCPConfiguration(ctx, client, tlsConfigs),
|
||||
UDP: p.loadIngressRouteUDPConfiguration(ctx, client),
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Certificates: getTLSConfig(tlsConfigs),
|
||||
Options: buildTLSOptions(ctx, client),
|
||||
Stores: buildTLSStores(ctx, client),
|
||||
Options: buildTLSOptions(ctx, client),
|
||||
Stores: stores,
|
||||
},
|
||||
}
|
||||
|
||||
// Done after because tlsConfigs is mutated by the others above.
|
||||
conf.TLS.Certificates = getTLSConfig(tlsConfigs)
|
||||
|
||||
for _, middleware := range client.GetMiddlewares() {
|
||||
id := provider.Normalize(makeID(middleware.Namespace, middleware.Name))
|
||||
ctxMid := log.With(ctx, log.Str(log.MiddlewareName, id))
|
||||
|
@ -243,6 +250,12 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
|||
continue
|
||||
}
|
||||
|
||||
circuitBreaker, err := createCircuitBreakerMiddleware(middleware.Spec.CircuitBreaker)
|
||||
if err != nil {
|
||||
log.FromContext(ctxMid).Errorf("Error while reading circuit breaker middleware: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
conf.HTTP.Middlewares[id] = &dynamic.Middleware{
|
||||
AddPrefix: middleware.Spec.AddPrefix,
|
||||
StripPrefix: middleware.Spec.StripPrefix,
|
||||
|
@ -261,7 +274,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
|||
ForwardAuth: forwardAuth,
|
||||
InFlightReq: middleware.Spec.InFlightReq,
|
||||
Buffering: middleware.Spec.Buffering,
|
||||
CircuitBreaker: middleware.Spec.CircuitBreaker,
|
||||
CircuitBreaker: circuitBreaker,
|
||||
Compress: middleware.Spec.Compress,
|
||||
PassTLSClientCert: middleware.Spec.PassTLSClientCert,
|
||||
Retry: retry,
|
||||
|
@ -425,6 +438,35 @@ func createPluginMiddleware(plugins map[string]apiextensionv1.JSON) (map[string]
|
|||
return pc, nil
|
||||
}
|
||||
|
||||
func createCircuitBreakerMiddleware(circuitBreaker *v1alpha1.CircuitBreaker) (*dynamic.CircuitBreaker, error) {
|
||||
if circuitBreaker == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cb := &dynamic.CircuitBreaker{Expression: circuitBreaker.Expression}
|
||||
cb.SetDefaults()
|
||||
|
||||
if circuitBreaker.CheckPeriod != nil {
|
||||
if err := cb.CheckPeriod.Set(circuitBreaker.CheckPeriod.String()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if circuitBreaker.FallbackDuration != nil {
|
||||
if err := cb.FallbackDuration.Set(circuitBreaker.FallbackDuration.String()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if circuitBreaker.RecoveryDuration != nil {
|
||||
if err := cb.RecoveryDuration.Set(circuitBreaker.RecoveryDuration.String()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return cb, nil
|
||||
}
|
||||
|
||||
func createRateLimitMiddleware(rateLimit *v1alpha1.RateLimit) (*dynamic.RateLimit, error) {
|
||||
if rateLimit == nil {
|
||||
return nil, nil
|
||||
|
@ -793,49 +835,60 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options
|
|||
return tlsOptions
|
||||
}
|
||||
|
||||
func buildTLSStores(ctx context.Context, client Client) map[string]tls.Store {
|
||||
func buildTLSStores(ctx context.Context, client Client) (map[string]tls.Store, map[string]*tls.CertAndStores) {
|
||||
tlsStoreCRD := client.GetTLSStores()
|
||||
var tlsStores map[string]tls.Store
|
||||
|
||||
if len(tlsStoreCRD) == 0 {
|
||||
return tlsStores
|
||||
return nil, nil
|
||||
}
|
||||
tlsStores = make(map[string]tls.Store)
|
||||
|
||||
var nsDefault []string
|
||||
tlsStores := make(map[string]tls.Store)
|
||||
tlsConfigs := make(map[string]*tls.CertAndStores)
|
||||
|
||||
for _, tlsStore := range tlsStoreCRD {
|
||||
namespace := tlsStore.Namespace
|
||||
secretName := tlsStore.Spec.DefaultCertificate.SecretName
|
||||
logger := log.FromContext(log.With(ctx, log.Str("tlsStore", tlsStore.Name), log.Str("namespace", namespace), log.Str("secretName", secretName)))
|
||||
for _, t := range tlsStoreCRD {
|
||||
logger := log.FromContext(log.With(ctx, log.Str("TLSStore", t.Name), log.Str("namespace", t.Namespace)))
|
||||
|
||||
secret, exists, err := client.GetSecret(namespace, secretName)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to fetch secret %s/%s: %v", namespace, secretName, err)
|
||||
continue
|
||||
}
|
||||
if !exists {
|
||||
logger.Errorf("Secret %s/%s does not exist", namespace, secretName)
|
||||
continue
|
||||
}
|
||||
id := makeID(t.Namespace, t.Name)
|
||||
|
||||
cert, key, err := getCertificateBlocks(secret, namespace, secretName)
|
||||
if err != nil {
|
||||
logger.Errorf("Could not get certificate blocks: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
id := makeID(tlsStore.Namespace, tlsStore.Name)
|
||||
// If the name is default, we override the default config.
|
||||
if tlsStore.Name == tls.DefaultTLSStoreName {
|
||||
id = tlsStore.Name
|
||||
nsDefault = append(nsDefault, tlsStore.Namespace)
|
||||
if t.Name == tls.DefaultTLSStoreName {
|
||||
id = t.Name
|
||||
nsDefault = append(nsDefault, t.Namespace)
|
||||
}
|
||||
tlsStores[id] = tls.Store{
|
||||
DefaultCertificate: &tls.Certificate{
|
||||
|
||||
var tlsStore tls.Store
|
||||
|
||||
if t.Spec.DefaultCertificate != nil {
|
||||
secretName := t.Spec.DefaultCertificate.SecretName
|
||||
|
||||
secret, exists, err := client.GetSecret(t.Namespace, secretName)
|
||||
if err != nil {
|
||||
logger.Errorf("Failed to fetch secret %s/%s: %v", t.Namespace, secretName, err)
|
||||
continue
|
||||
}
|
||||
if !exists {
|
||||
logger.Errorf("Secret %s/%s does not exist", t.Namespace, secretName)
|
||||
continue
|
||||
}
|
||||
|
||||
cert, key, err := getCertificateBlocks(secret, t.Namespace, secretName)
|
||||
if err != nil {
|
||||
logger.Errorf("Could not get certificate blocks: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
tlsStore.DefaultCertificate = &tls.Certificate{
|
||||
CertFile: tls.FileOrContent(cert),
|
||||
KeyFile: tls.FileOrContent(key),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if err := buildCertificates(client, id, t.Namespace, t.Spec.Certificates, tlsConfigs); err != nil {
|
||||
logger.Errorf("Failed to load certificates: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
tlsStores[id] = tlsStore
|
||||
}
|
||||
|
||||
if len(nsDefault) > 1 {
|
||||
|
@ -843,7 +896,25 @@ func buildTLSStores(ctx context.Context, client Client) map[string]tls.Store {
|
|||
log.FromContext(ctx).Errorf("Default TLS Stores defined in multiple namespaces: %v", nsDefault)
|
||||
}
|
||||
|
||||
return tlsStores
|
||||
return tlsStores, tlsConfigs
|
||||
}
|
||||
|
||||
// buildCertificates loads TLSStore certificates from secrets and sets them into tlsConfigs.
|
||||
func buildCertificates(client Client, tlsStore, namespace string, certificates []v1alpha1.Certificate, tlsConfigs map[string]*tls.CertAndStores) error {
|
||||
for _, c := range certificates {
|
||||
configKey := namespace + "/" + c.SecretName
|
||||
if _, tlsExists := tlsConfigs[configKey]; !tlsExists {
|
||||
certAndStores, err := getTLS(client, c.SecretName, namespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read secret %s: %w", configKey, err)
|
||||
}
|
||||
|
||||
certAndStores.Stores = []string{tlsStore}
|
||||
tlsConfigs[configKey] = certAndStores
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeServiceKey(rule, ingressName string) (string, error) {
|
||||
|
|
|
@ -495,6 +495,7 @@ func namespaceOrFallback(lb v1alpha1.LoadBalancerSpec, fallback string) string {
|
|||
return fallback
|
||||
}
|
||||
|
||||
// getTLSHTTP mutates tlsConfigs.
|
||||
func getTLSHTTP(ctx context.Context, ingressRoute *v1alpha1.IngressRoute, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error {
|
||||
if ingressRoute.Spec.TLS == nil {
|
||||
return nil
|
||||
|
|
|
@ -269,6 +269,7 @@ func (p *Provider) loadTCPServers(client Client, namespace string, svc v1alpha1.
|
|||
return servers, nil
|
||||
}
|
||||
|
||||
// getTLSTCP mutates tlsConfigs.
|
||||
func getTLSTCP(ctx context.Context, ingressRoute *v1alpha1.IngressRouteTCP, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error {
|
||||
if ingressRoute.Spec.TLS == nil {
|
||||
return nil
|
||||
|
|
|
@ -3480,6 +3480,63 @@ func TestLoadIngressRoutes(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TLS with tls store containing certificates",
|
||||
paths: []string{"services.yml", "with_tls_store_certificates.yml"},
|
||||
expected: &dynamic.Configuration{
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Certificates: []*tls.CertAndStores{
|
||||
{
|
||||
Certificate: tls.Certificate{
|
||||
CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"),
|
||||
KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"),
|
||||
},
|
||||
Stores: []string{"default"},
|
||||
},
|
||||
},
|
||||
Stores: map[string]tls.Store{
|
||||
"default": {},
|
||||
},
|
||||
},
|
||||
UDP: &dynamic.UDPConfiguration{
|
||||
Routers: map[string]*dynamic.UDPRouter{},
|
||||
Services: map[string]*dynamic.UDPService{},
|
||||
},
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
Routers: map[string]*dynamic.TCPRouter{},
|
||||
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||
Services: map[string]*dynamic.TCPService{},
|
||||
},
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"default-test-route-6b204d94623b3df4370c": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "default-test-route-6b204d94623b3df4370c",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
|
||||
Priority: 12,
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-test-route-6b204d94623b3df4370c": {
|
||||
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{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TLS with tls store default two times",
|
||||
paths: []string{"services.yml", "with_tls_store.yml", "with_default_tls_store.yml"},
|
||||
|
|
|
@ -40,7 +40,7 @@ type MiddlewareSpec struct {
|
|||
ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty"`
|
||||
InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty"`
|
||||
Buffering *dynamic.Buffering `json:"buffering,omitempty"`
|
||||
CircuitBreaker *dynamic.CircuitBreaker `json:"circuitBreaker,omitempty"`
|
||||
CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty"`
|
||||
Compress *dynamic.Compress `json:"compress,omitempty"`
|
||||
PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"`
|
||||
Retry *Retry `json:"retry,omitempty"`
|
||||
|
@ -59,6 +59,20 @@ type ErrorPage struct {
|
|||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// CircuitBreaker holds the circuit breaker configuration.
|
||||
type CircuitBreaker struct {
|
||||
// Expression is the condition that triggers the tripped state.
|
||||
Expression string `json:"expression,omitempty" toml:"expression,omitempty" yaml:"expression,omitempty" export:"true"`
|
||||
// CheckPeriod is the interval between successive checks of the circuit breaker condition (when in standby state).
|
||||
CheckPeriod *intstr.IntOrString `json:"checkPeriod,omitempty" toml:"checkPeriod,omitempty" yaml:"checkPeriod,omitempty" export:"true"`
|
||||
// FallbackDuration is the duration for which the circuit breaker will wait before trying to recover (from a tripped state).
|
||||
FallbackDuration *intstr.IntOrString `json:"fallbackDuration,omitempty" toml:"fallbackDuration,omitempty" yaml:"fallbackDuration,omitempty" export:"true"`
|
||||
// RecoveryDuration is the duration for which the circuit breaker will try to recover (as soon as it is in recovering state).
|
||||
RecoveryDuration *intstr.IntOrString `json:"recoveryDuration,omitempty" toml:"recoveryDuration,omitempty" yaml:"recoveryDuration,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// Chain holds a chain of middlewares.
|
||||
type Chain struct {
|
||||
Middlewares []MiddlewareRef `json:"middlewares,omitempty"`
|
||||
|
|
|
@ -20,13 +20,16 @@ type TLSStore struct {
|
|||
|
||||
// TLSStoreSpec configures a TLSStore resource.
|
||||
type TLSStoreSpec struct {
|
||||
DefaultCertificate DefaultCertificate `json:"defaultCertificate"`
|
||||
// DefaultCertificate is the name of the secret holding the default key/certificate pair for the store.
|
||||
DefaultCertificate *Certificate `json:"defaultCertificate,omitempty"`
|
||||
// Certificates is a list of secret names, each secret holding a key/certificate pair to add to the store.
|
||||
Certificates []Certificate `json:"certificates,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// DefaultCertificate holds a secret name for the TLSOption resource.
|
||||
type DefaultCertificate struct {
|
||||
// Certificate holds a secret name for the TLSStore resource.
|
||||
type Certificate struct {
|
||||
// SecretName is the name of the referenced Kubernetes Secret to specify the certificate details.
|
||||
SecretName string `json:"secretName"`
|
||||
}
|
||||
|
|
|
@ -53,6 +53,22 @@ func (in *BasicAuth) DeepCopy() *BasicAuth {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Certificate) DeepCopyInto(out *Certificate) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Certificate.
|
||||
func (in *Certificate) DeepCopy() *Certificate {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Certificate)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Chain) DeepCopyInto(out *Chain) {
|
||||
*out = *in
|
||||
|
@ -74,6 +90,37 @@ func (in *Chain) DeepCopy() *Chain {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CircuitBreaker) DeepCopyInto(out *CircuitBreaker) {
|
||||
*out = *in
|
||||
if in.CheckPeriod != nil {
|
||||
in, out := &in.CheckPeriod, &out.CheckPeriod
|
||||
*out = new(intstr.IntOrString)
|
||||
**out = **in
|
||||
}
|
||||
if in.FallbackDuration != nil {
|
||||
in, out := &in.FallbackDuration, &out.FallbackDuration
|
||||
*out = new(intstr.IntOrString)
|
||||
**out = **in
|
||||
}
|
||||
if in.RecoveryDuration != nil {
|
||||
in, out := &in.RecoveryDuration, &out.RecoveryDuration
|
||||
*out = new(intstr.IntOrString)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CircuitBreaker.
|
||||
func (in *CircuitBreaker) DeepCopy() *CircuitBreaker {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CircuitBreaker)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ClientAuth) DeepCopyInto(out *ClientAuth) {
|
||||
*out = *in
|
||||
|
@ -111,22 +158,6 @@ func (in *ClientTLS) DeepCopy() *ClientTLS {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DefaultCertificate) DeepCopyInto(out *DefaultCertificate) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DefaultCertificate.
|
||||
func (in *DefaultCertificate) DeepCopy() *DefaultCertificate {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(DefaultCertificate)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DigestAuth) DeepCopyInto(out *DigestAuth) {
|
||||
*out = *in
|
||||
|
@ -714,8 +745,8 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) {
|
|||
}
|
||||
if in.CircuitBreaker != nil {
|
||||
in, out := &in.CircuitBreaker, &out.CircuitBreaker
|
||||
*out = new(dynamic.CircuitBreaker)
|
||||
**out = **in
|
||||
*out = new(CircuitBreaker)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Compress != nil {
|
||||
in, out := &in.Compress, &out.Compress
|
||||
|
@ -1382,7 +1413,7 @@ func (in *TLSStore) DeepCopyInto(out *TLSStore) {
|
|||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Spec = in.Spec
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1456,7 +1487,16 @@ func (in *TLSStoreRef) DeepCopy() *TLSStoreRef {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TLSStoreSpec) DeepCopyInto(out *TLSStoreSpec) {
|
||||
*out = *in
|
||||
out.DefaultCertificate = in.DefaultCertificate
|
||||
if in.DefaultCertificate != nil {
|
||||
in, out := &in.DefaultCertificate, &out.DefaultCertificate
|
||||
*out = new(Certificate)
|
||||
**out = **in
|
||||
}
|
||||
if in.Certificates != nil {
|
||||
in, out := &in.Certificates, &out.Certificates
|
||||
*out = make([]Certificate, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -4,30 +4,80 @@ import (
|
|||
"errors"
|
||||
|
||||
"github.com/kvtools/valkeyrie/store"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/provider"
|
||||
"github.com/traefik/traefik/v2/pkg/provider/kv"
|
||||
)
|
||||
|
||||
// providerName is the Consul provider name.
|
||||
const providerName = "consul"
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
// ProviderBuilder is responsible for constructing namespaced instances of the Consul provider.
|
||||
type ProviderBuilder struct {
|
||||
kv.Provider `export:"true"`
|
||||
|
||||
// Deprecated: use Namespaces instead.
|
||||
Namespace string `description:"Sets the namespace used to discover the configuration (Consul Enterprise only)." json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
Namespaces []string `description:"Sets the namespaces used to discover the configuration (Consul Enterprise only)." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (p *Provider) SetDefaults() {
|
||||
func (p *ProviderBuilder) SetDefaults() {
|
||||
p.Provider.SetDefaults()
|
||||
p.Endpoints = []string{"127.0.0.1:8500"}
|
||||
}
|
||||
|
||||
// BuildProviders builds Consul provider instances for the given namespaces configuration.
|
||||
func (p *ProviderBuilder) BuildProviders() []*Provider {
|
||||
// We can warn about that, because we've already made sure before that
|
||||
// Namespace and Namespaces are mutually exclusive.
|
||||
if p.Namespace != "" {
|
||||
log.WithoutContext().Warnf("Namespace option is deprecated, please use the Namespaces option instead.")
|
||||
}
|
||||
|
||||
if len(p.Namespaces) == 0 {
|
||||
return []*Provider{{
|
||||
Provider: p.Provider,
|
||||
name: providerName,
|
||||
// p.Namespace could very well be empty.
|
||||
namespace: p.Namespace,
|
||||
}}
|
||||
}
|
||||
|
||||
var providers []*Provider
|
||||
for _, namespace := range p.Namespaces {
|
||||
providers = append(providers, &Provider{
|
||||
Provider: p.Provider,
|
||||
name: providerName + "-" + namespace,
|
||||
namespace: namespace,
|
||||
})
|
||||
}
|
||||
|
||||
return providers
|
||||
}
|
||||
|
||||
// Provider holds configurations of the provider.
|
||||
type Provider struct {
|
||||
kv.Provider
|
||||
|
||||
name string
|
||||
namespace string
|
||||
}
|
||||
|
||||
// Init the provider.
|
||||
func (p *Provider) Init() error {
|
||||
// Wildcard namespace allows fetching KV values from any namespace for recursive requests (see https://www.consul.io/api/kv#ns).
|
||||
// As we are not supporting multiple namespaces at the same time, wildcard namespace is not allowed.
|
||||
if p.Namespace == "*" {
|
||||
if p.namespace == "*" {
|
||||
return errors.New("wildcard namespace is not supported")
|
||||
}
|
||||
|
||||
return p.Provider.Init(store.CONSUL, "consul")
|
||||
// In case they didn't initialize with BuildProviders.
|
||||
if p.name == "" {
|
||||
p.name = providerName
|
||||
}
|
||||
|
||||
return p.Provider.Init(store.CONSUL, p.name, p.namespace)
|
||||
}
|
||||
|
|
59
pkg/provider/kv/consul/consul_test.go
Normal file
59
pkg/provider/kv/consul/consul_test.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package consul
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNamespaces(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
namespace string
|
||||
namespaces []string
|
||||
expectedNamespaces []string
|
||||
}{
|
||||
{
|
||||
desc: "no defined namespaces",
|
||||
expectedNamespaces: []string{""},
|
||||
},
|
||||
{
|
||||
desc: "deprecated: use of defined namespace",
|
||||
namespace: "test-ns",
|
||||
expectedNamespaces: []string{"test-ns"},
|
||||
},
|
||||
{
|
||||
desc: "use of 1 defined namespaces",
|
||||
namespaces: []string{"test-ns"},
|
||||
expectedNamespaces: []string{"test-ns"},
|
||||
},
|
||||
{
|
||||
desc: "use of multiple defined namespaces",
|
||||
namespaces: []string{"test-ns1", "test-ns2", "test-ns3", "test-ns4"},
|
||||
expectedNamespaces: []string{"test-ns1", "test-ns2", "test-ns3", "test-ns4"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pb := &ProviderBuilder{
|
||||
Namespace: test.namespace,
|
||||
Namespaces: test.namespaces,
|
||||
}
|
||||
|
||||
assert.Equal(t, test.expectedNamespaces, extractNSFromProvider(pb.BuildProviders()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func extractNSFromProvider(providers []*Provider) []string {
|
||||
res := make([]string, len(providers))
|
||||
for i, p := range providers {
|
||||
res[i] = p.namespace
|
||||
}
|
||||
return res
|
||||
}
|
|
@ -21,5 +21,5 @@ func (p *Provider) SetDefaults() {
|
|||
|
||||
// Init the provider.
|
||||
func (p *Provider) Init() error {
|
||||
return p.Provider.Init(store.ETCDV3, "etcd")
|
||||
return p.Provider.Init(store.ETCDV3, "etcd", "")
|
||||
}
|
||||
|
|
|
@ -30,12 +30,12 @@ type Provider struct {
|
|||
Username string `description:"KV Username" json:"username,omitempty" toml:"username,omitempty" yaml:"username,omitempty" loggable:"false"`
|
||||
Password string `description:"KV Password" json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" loggable:"false"`
|
||||
Token string `description:"KV Token" json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"`
|
||||
Namespace string `description:"KV Namespace" json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||
TLS *types.ClientTLS `description:"Enable TLS support" json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true" `
|
||||
|
||||
name string
|
||||
namespace string
|
||||
storeType store.Backend
|
||||
kvClient store.Store
|
||||
name string
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
|
@ -44,11 +44,12 @@ func (p *Provider) SetDefaults() {
|
|||
}
|
||||
|
||||
// Init the provider.
|
||||
func (p *Provider) Init(storeType store.Backend, name string) error {
|
||||
func (p *Provider) Init(storeType store.Backend, name, namespace string) error {
|
||||
ctx := log.With(context.Background(), log.Str(log.ProviderName, name))
|
||||
|
||||
p.storeType = storeType
|
||||
p.name = name
|
||||
p.namespace = namespace
|
||||
p.storeType = storeType
|
||||
|
||||
kvClient, err := p.createKVClient(ctx)
|
||||
if err != nil {
|
||||
|
@ -167,7 +168,7 @@ func (p *Provider) createKVClient(ctx context.Context) (store.Store, error) {
|
|||
Username: p.Username,
|
||||
Password: p.Password,
|
||||
Token: p.Token,
|
||||
Namespace: p.Namespace,
|
||||
Namespace: p.namespace,
|
||||
}
|
||||
|
||||
if p.TLS != nil {
|
||||
|
|
|
@ -173,6 +173,9 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
"traefik/http/middlewares/Middleware03/chain/middlewares/0": "foobar",
|
||||
"traefik/http/middlewares/Middleware03/chain/middlewares/1": "foobar",
|
||||
"traefik/http/middlewares/Middleware04/circuitBreaker/expression": "foobar",
|
||||
"traefik/http/middlewares/Middleware04/circuitBreaker/checkPeriod": "1s",
|
||||
"traefik/http/middlewares/Middleware04/circuitBreaker/fallbackDuration": "1s",
|
||||
"traefik/http/middlewares/Middleware04/circuitBreaker/recoveryDuration": "1s",
|
||||
"traefik/http/middlewares/Middleware07/errors/status/0": "foobar",
|
||||
"traefik/http/middlewares/Middleware07/errors/status/1": "foobar",
|
||||
"traefik/http/middlewares/Middleware07/errors/service": "foobar",
|
||||
|
@ -393,7 +396,10 @@ func Test_buildConfiguration(t *testing.T) {
|
|||
},
|
||||
"Middleware04": {
|
||||
CircuitBreaker: &dynamic.CircuitBreaker{
|
||||
Expression: "foobar",
|
||||
Expression: "foobar",
|
||||
CheckPeriod: ptypes.Duration(time.Second),
|
||||
FallbackDuration: ptypes.Duration(time.Second),
|
||||
RecoveryDuration: ptypes.Duration(time.Second),
|
||||
},
|
||||
},
|
||||
"Middleware05": {
|
||||
|
|
|
@ -21,5 +21,5 @@ func (p *Provider) SetDefaults() {
|
|||
|
||||
// Init the provider.
|
||||
func (p *Provider) Init() error {
|
||||
return p.Provider.Init(store.REDIS, "redis")
|
||||
return p.Provider.Init(store.REDIS, "redis", "")
|
||||
}
|
||||
|
|
|
@ -21,5 +21,5 @@ func (p *Provider) SetDefaults() {
|
|||
|
||||
// Init the provider.
|
||||
func (p *Provider) Init() error {
|
||||
return p.Provider.Init(store.ZK, "zookeeper")
|
||||
return p.Provider.Init(store.ZK, "zookeeper", "")
|
||||
}
|
||||
|
|
|
@ -682,33 +682,37 @@ func TestDo_staticConfiguration(t *testing.T) {
|
|||
Prefix: "MyPrefix",
|
||||
}
|
||||
|
||||
config.Providers.ConsulCatalog = &consulcatalog.Provider{
|
||||
Constraints: `Label("foo", "bar")`,
|
||||
Endpoint: &consulcatalog.EndpointConfig{
|
||||
Address: "MyAddress",
|
||||
Scheme: "MyScheme",
|
||||
DataCenter: "MyDatacenter",
|
||||
Token: "MyToken",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "myCa",
|
||||
CAOptional: true,
|
||||
Cert: "mycert.pem",
|
||||
Key: "mycert.key",
|
||||
InsecureSkipVerify: true,
|
||||
config.Providers.ConsulCatalog = &consulcatalog.ProviderBuilder{
|
||||
Configuration: consulcatalog.Configuration{
|
||||
Constraints: `Label("foo", "bar")`,
|
||||
Endpoint: &consulcatalog.EndpointConfig{
|
||||
Address: "MyAddress",
|
||||
Scheme: "MyScheme",
|
||||
DataCenter: "MyDatacenter",
|
||||
Token: "MyToken",
|
||||
TLS: &types.ClientTLS{
|
||||
CA: "myCa",
|
||||
CAOptional: true,
|
||||
Cert: "mycert.pem",
|
||||
Key: "mycert.key",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
HTTPAuth: &consulcatalog.EndpointHTTPAuthConfig{
|
||||
Username: "MyUsername",
|
||||
Password: "MyPassword",
|
||||
},
|
||||
EndpointWaitTime: 42,
|
||||
},
|
||||
HTTPAuth: &consulcatalog.EndpointHTTPAuthConfig{
|
||||
Username: "MyUsername",
|
||||
Password: "MyPassword",
|
||||
},
|
||||
EndpointWaitTime: 42,
|
||||
Prefix: "MyPrefix",
|
||||
RefreshInterval: 42,
|
||||
RequireConsistent: true,
|
||||
Stale: true,
|
||||
Cache: true,
|
||||
ExposedByDefault: true,
|
||||
DefaultRule: "PathPrefix(`/`)",
|
||||
},
|
||||
Prefix: "MyPrefix",
|
||||
RefreshInterval: 42,
|
||||
RequireConsistent: true,
|
||||
Stale: true,
|
||||
Cache: true,
|
||||
ExposedByDefault: true,
|
||||
DefaultRule: "PathPrefix(`/`)",
|
||||
Namespace: "ns",
|
||||
Namespaces: []string{"ns1", "ns2"},
|
||||
}
|
||||
|
||||
config.Providers.Ecs = &ecs.Provider{
|
||||
|
@ -723,7 +727,7 @@ func TestDo_staticConfiguration(t *testing.T) {
|
|||
SecretAccessKey: "AwsSecretAccessKey",
|
||||
}
|
||||
|
||||
config.Providers.Consul = &consul.Provider{
|
||||
config.Providers.Consul = &consul.ProviderBuilder{
|
||||
Provider: kv.Provider{
|
||||
RootKey: "RootKey",
|
||||
Endpoints: nil,
|
||||
|
@ -737,6 +741,8 @@ func TestDo_staticConfiguration(t *testing.T) {
|
|||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
Namespace: "ns",
|
||||
Namespaces: []string{"ns1", "ns2"},
|
||||
}
|
||||
|
||||
config.Providers.Etcd = &etcd.Provider{
|
||||
|
|
|
@ -206,7 +206,12 @@
|
|||
"stale": true,
|
||||
"cache": true,
|
||||
"exposedByDefault": true,
|
||||
"defaultRule": "xxxx"
|
||||
"defaultRule": "xxxx",
|
||||
"namespace": "xxxx",
|
||||
"namespaces": [
|
||||
"xxxx",
|
||||
"xxxx"
|
||||
]
|
||||
},
|
||||
"ecs": {
|
||||
"constraints": "Label(\"foo\", \"bar\")",
|
||||
|
@ -232,7 +237,12 @@
|
|||
"cert": "xxxx",
|
||||
"key": "xxxx",
|
||||
"insecureSkipVerify": true
|
||||
}
|
||||
},
|
||||
"namespace": "xxxx",
|
||||
"namespaces": [
|
||||
"xxxx",
|
||||
"xxxx"
|
||||
]
|
||||
},
|
||||
"etcd": {
|
||||
"rootKey": "xxxx",
|
||||
|
|
|
@ -7,6 +7,8 @@ import (
|
|||
stdlog "log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
@ -507,6 +509,10 @@ type httpServer struct {
|
|||
}
|
||||
|
||||
func createHTTPServer(ctx context.Context, ln net.Listener, configuration *static.EntryPoint, withH2c bool, reqDecorator *requestdecorator.RequestDecorator) (*httpServer, error) {
|
||||
if configuration.HTTP2.MaxConcurrentStreams < 0 {
|
||||
return nil, errors.New("max concurrent streams value must be greater than or equal to zero")
|
||||
}
|
||||
|
||||
httpSwitcher := middlewares.NewHandlerSwitcher(router.BuildDefaultHTTPRouter())
|
||||
|
||||
next, err := alice.New(requestdecorator.WrapHandler(reqDecorator)).Then(httpSwitcher)
|
||||
|
@ -524,7 +530,9 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
|
|||
}
|
||||
|
||||
if withH2c {
|
||||
handler = h2c.NewHandler(handler, &http2.Server{})
|
||||
handler = h2c.NewHandler(handler, &http2.Server{
|
||||
MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams),
|
||||
})
|
||||
}
|
||||
|
||||
serverHTTP := &http.Server{
|
||||
|
@ -535,6 +543,20 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
|
|||
IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout),
|
||||
}
|
||||
|
||||
// ConfigureServer configures HTTP/2 with the MaxConcurrentStreams option for the given server.
|
||||
// Also keeping behavior the same as
|
||||
// https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/net/http/server.go;l=3262
|
||||
if !strings.Contains(os.Getenv("GODEBUG"), "http2server=0") {
|
||||
err = http2.ConfigureServer(serverHTTP, &http2.Server{
|
||||
MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams),
|
||||
NewWriteScheduler: func() http2.WriteScheduler { return http2.NewPriorityWriteScheduler(nil) },
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("configure HTTP/2 server: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
listener := newHTTPForwarder(ln)
|
||||
go func() {
|
||||
err := serverHTTP.Serve(listener)
|
||||
|
|
|
@ -47,7 +47,7 @@ func newHTTP3Server(ctx context.Context, configuration *static.EntryPoint, https
|
|||
}
|
||||
|
||||
h3.Server = &http3.Server{
|
||||
Port: uint32(configuration.HTTP3.AdvertisedPort),
|
||||
Port: configuration.HTTP3.AdvertisedPort,
|
||||
Server: &http.Server{
|
||||
Addr: configuration.GetAddress(),
|
||||
Handler: httpsServer.Server.(*http.Server).Handler,
|
||||
|
|
|
@ -88,6 +88,7 @@ func TestHTTP3AdvertisedPort(t *testing.T) {
|
|||
Address: "127.0.0.1:8090",
|
||||
Transport: epConfig,
|
||||
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||
HTTP2: &static.HTTP2Config{},
|
||||
HTTP3: &static.HTTP3Config{
|
||||
AdvertisedPort: 8080,
|
||||
},
|
||||
|
|
|
@ -83,6 +83,7 @@ func testShutdown(t *testing.T, router *tcprouter.Router) {
|
|||
Address: "127.0.0.1:0",
|
||||
Transport: epConfig,
|
||||
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||
HTTP2: &static.HTTP2Config{},
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -166,6 +167,7 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) {
|
|||
Address: ":0",
|
||||
Transport: epConfig,
|
||||
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||
HTTP2: &static.HTTP2Config{},
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -202,6 +204,7 @@ func TestReadTimeoutWithFirstByte(t *testing.T) {
|
|||
Address: ":0",
|
||||
Transport: epConfig,
|
||||
ForwardedHeaders: &static.ForwardedHeaders{},
|
||||
HTTP2: &static.HTTP2Config{},
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ func NewProxy(address string, terminationDelay time.Duration, proxyProtocol *dyn
|
|||
|
||||
// ServeTCP forwards the connection to a service.
|
||||
func (p *Proxy) ServeTCP(conn WriteCloser) {
|
||||
log.WithoutContext().Debugf("Handling connection from %s", conn.RemoteAddr())
|
||||
log.WithoutContext().Debugf("Handling connection from %s to %s", conn.RemoteAddr(), p.address)
|
||||
|
||||
// needed because of e.g. server.trackedConnection
|
||||
defer conn.Close()
|
||||
|
|
|
@ -16,7 +16,8 @@ import (
|
|||
// ClientTLS holds TLS specific configurations as client
|
||||
// CA, Cert and Key can be either path or file contents.
|
||||
type ClientTLS struct {
|
||||
CA string `description:"TLS CA" json:"ca,omitempty" toml:"ca,omitempty" yaml:"ca,omitempty"`
|
||||
CA string `description:"TLS CA" json:"ca,omitempty" toml:"ca,omitempty" yaml:"ca,omitempty"`
|
||||
// Deprecated: TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634).
|
||||
CAOptional bool `description:"TLS CA.Optional" json:"caOptional,omitempty" toml:"caOptional,omitempty" yaml:"caOptional,omitempty" export:"true"`
|
||||
Cert string `description:"TLS cert" json:"cert,omitempty" toml:"cert,omitempty" yaml:"cert,omitempty"`
|
||||
Key string `description:"TLS key" json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty" loggable:"false"`
|
||||
|
@ -30,10 +31,13 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
if clientTLS.CAOptional {
|
||||
log.FromContext(ctx).Warn("CAOptional is deprecated, TLS client authentication is a server side option.")
|
||||
}
|
||||
|
||||
// Not initialized, to rely on system bundle.
|
||||
var caPool *x509.CertPool
|
||||
|
||||
clientAuth := tls.NoClientCert
|
||||
if clientTLS.CA != "" {
|
||||
var ca []byte
|
||||
if _, errCA := os.Stat(clientTLS.CA); errCA == nil {
|
||||
|
@ -50,12 +54,6 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
|
|||
if !caPool.AppendCertsFromPEM(ca) {
|
||||
return nil, errors.New("failed to parse CA")
|
||||
}
|
||||
|
||||
if clientTLS.CAOptional {
|
||||
clientAuth = tls.VerifyClientCertIfGiven
|
||||
} else {
|
||||
clientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
}
|
||||
|
||||
hasCert := len(clientTLS.Cert) > 0
|
||||
|
@ -69,7 +67,6 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
|
|||
return &tls.Config{
|
||||
RootCAs: caPool,
|
||||
InsecureSkipVerify: clientTLS.InsecureSkipVerify,
|
||||
ClientAuth: clientAuth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -82,7 +79,6 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
|
|||
Certificates: []tls.Certificate{cert},
|
||||
RootCAs: caPool,
|
||||
InsecureSkipVerify: clientTLS.InsecureSkipVerify,
|
||||
ClientAuth: clientAuth,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ func NewProxy(address string) (*Proxy, error) {
|
|||
|
||||
// ServeUDP implements the Handler interface.
|
||||
func (p *Proxy) ServeUDP(conn *Conn) {
|
||||
log.WithoutContext().Debugf("Handling connection from %s", conn.rAddr)
|
||||
log.WithoutContext().Debugf("Handling connection from %s to %s", conn.rAddr, p.target)
|
||||
|
||||
// needed because of e.g. server.trackedConnection
|
||||
defer conn.Close()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue