add ServersTransport on services

This commit is contained in:
Julien Salleyron 2020-09-11 15:40:03 +02:00 committed by GitHub
parent 6075f7e8fd
commit 76f42a3013
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 2359 additions and 242 deletions

View file

@ -10,10 +10,11 @@ import (
func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoints []string) dynamic.Configuration {
conf := dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: make(map[string]*dynamic.Router),
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: make(map[string]*dynamic.Model),
Routers: make(map[string]*dynamic.Router),
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: make(map[string]*dynamic.Model),
ServersTransports: make(map[string]*dynamic.ServersTransport),
},
TCP: &dynamic.TCPConfiguration{
Routers: make(map[string]*dynamic.TCPRouter),
@ -52,6 +53,9 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint
for modelName, model := range configuration.HTTP.Models {
conf.HTTP.Models[provider.MakeQualifiedName(pvd, modelName)] = model
}
for serversTransportName, serversTransport := range configuration.HTTP.ServersTransports {
conf.HTTP.ServersTransports[provider.MakeQualifiedName(pvd, serversTransportName)] = serversTransport
}
}
if configuration.TCP != nil {

View file

@ -18,10 +18,11 @@ func Test_mergeConfiguration(t *testing.T) {
desc: "Nil returns an empty configuration",
given: nil,
expected: &dynamic.HTTPConfiguration{
Routers: make(map[string]*dynamic.Router),
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: make(map[string]*dynamic.Model),
Routers: make(map[string]*dynamic.Router),
Middlewares: make(map[string]*dynamic.Middleware),
Services: make(map[string]*dynamic.Service),
Models: make(map[string]*dynamic.Model),
ServersTransports: make(map[string]*dynamic.ServersTransport),
},
},
{
@ -53,7 +54,8 @@ func Test_mergeConfiguration(t *testing.T) {
Services: map[string]*dynamic.Service{
"service-1@provider-1": {},
},
Models: make(map[string]*dynamic.Model),
Models: make(map[string]*dynamic.Model),
ServersTransports: make(map[string]*dynamic.ServersTransport),
},
},
{
@ -103,7 +105,8 @@ func Test_mergeConfiguration(t *testing.T) {
"service-1@provider-1": {},
"service-1@provider-2": {},
},
Models: make(map[string]*dynamic.Model),
Models: make(map[string]*dynamic.Model),
ServersTransports: make(map[string]*dynamic.ServersTransport),
},
},
}

View file

@ -287,7 +287,9 @@ func TestRouterManager_Get(t *testing.T) {
},
})
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
roundTripperManager := service.NewRoundTripperManager()
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
@ -391,7 +393,9 @@ func TestAccessLog(t *testing.T) {
},
})
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
roundTripperManager := service.NewRoundTripperManager()
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
@ -678,7 +682,9 @@ func TestRuntimeConfiguration(t *testing.T) {
},
})
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
roundTripperManager := service.NewRoundTripperManager()
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
@ -759,7 +765,9 @@ func TestProviderOnMiddlewares(t *testing.T) {
},
})
serviceManager := service.NewManager(rtConf.Services, http.DefaultTransport, nil, nil)
roundTripperManager := service.NewRoundTripperManager()
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
chainBuilder := middleware.NewChainBuilder(staticCfg, nil, nil)
@ -773,6 +781,14 @@ func TestProviderOnMiddlewares(t *testing.T) {
assert.Equal(t, []string{"m1@docker", "m2@docker", "m1@file"}, rtConf.Middlewares["chain@docker"].Chain.Middlewares)
}
type staticRoundTripperGetter struct {
res *http.Response
}
func (s staticRoundTripperGetter) Get(name string) (http.RoundTripper, error) {
return &staticTransport{res: s.res}, nil
}
type staticTransport struct {
res *http.Response
}
@ -819,7 +835,7 @@ func BenchmarkRouterServe(b *testing.B) {
},
})
serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil)
serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res})
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
chainBuilder := middleware.NewChainBuilder(static.Configuration{}, nil, nil)
@ -861,7 +877,7 @@ func BenchmarkService(b *testing.B) {
},
})
serviceManager := service.NewManager(rtConf.Services, &staticTransport{res}, nil, nil)
serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res})
w := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil)

View file

@ -48,7 +48,9 @@ func TestReuseService(t *testing.T) {
),
)
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry())
roundTripperManager := service.NewRoundTripperManager()
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager)
tlsManager := tls.NewManager()
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil), nil)
@ -182,7 +184,9 @@ func TestServerResponseEmptyBackend(t *testing.T) {
},
}
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry())
roundTripperManager := service.NewRoundTripperManager()
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager)
tlsManager := tls.NewManager()
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil), nil)
@ -221,7 +225,9 @@ func TestInternalServices(t *testing.T) {
),
)
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry())
roundTripperManager := service.NewRoundTripperManager()
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
managerFactory := service.NewManagerFactory(staticConfig, nil, metrics.NewVoidRegistry(), roundTripperManager)
tlsManager := tls.NewManager()
factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, middleware.NewChainBuilder(staticConfig, metrics.NewVoidRegistry(), nil), nil)

View file

@ -14,7 +14,7 @@ import (
type ManagerFactory struct {
metricsRegistry metrics.Registry
defaultRoundTripper http.RoundTripper
roundTripperManager *RoundTripperManager
api func(configuration *runtime.Configuration) http.Handler
restHandler http.Handler
@ -26,11 +26,11 @@ type ManagerFactory struct {
}
// NewManagerFactory creates a new ManagerFactory.
func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *safe.Pool, metricsRegistry metrics.Registry) *ManagerFactory {
func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *safe.Pool, metricsRegistry metrics.Registry, roundTripperManager *RoundTripperManager) *ManagerFactory {
factory := &ManagerFactory{
metricsRegistry: metricsRegistry,
defaultRoundTripper: setupDefaultRoundTripper(staticConfiguration.ServersTransport),
routinesPool: routinesPool,
roundTripperManager: roundTripperManager,
}
if staticConfiguration.API != nil {
@ -61,6 +61,6 @@ func NewManagerFactory(staticConfiguration static.Configuration, routinesPool *s
// Build creates a service manager.
func (f *ManagerFactory) Build(configuration *runtime.Configuration) *InternalHandlers {
svcManager := NewManager(configuration.Services, f.defaultRoundTripper, f.metricsRegistry, f.routinesPool)
svcManager := NewManager(configuration.Services, f.metricsRegistry, f.routinesPool, f.roundTripperManager)
return NewInternalHandlers(f.api, configuration, f.restHandler, f.metricsHandler, f.pingHandler, f.dashboardHandler, svcManager)
}

View file

@ -21,7 +21,7 @@ const StatusClientClosedRequest = 499
// StatusClientClosedRequestText non-standard HTTP status for client disconnection.
const StatusClientClosedRequestText = "Client Closed Request"
func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwarding, defaultRoundTripper http.RoundTripper, bufferPool httputil.BufferPool) (http.Handler, error) {
func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwarding, roundTripper http.RoundTripper, bufferPool httputil.BufferPool) (http.Handler, error) {
var flushInterval ptypes.Duration
if responseForwarding != nil {
err := flushInterval.Set(responseForwarding.FlushInterval)
@ -76,7 +76,7 @@ func buildProxy(passHostHeader *bool, responseForwarding *dynamic.ResponseForwar
delete(outReq.Header, "Sec-Websocket-Protocol")
delete(outReq.Header, "Sec-Websocket-Version")
},
Transport: defaultRoundTripper,
Transport: roundTripper,
FlushInterval: time.Duration(flushInterval),
BufferPool: bufferPool,
ErrorHandler: func(w http.ResponseWriter, request *http.Request, err error) {

View file

@ -4,11 +4,14 @@ import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net"
"net/http"
"reflect"
"sync"
"time"
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/config/dynamic"
"github.com/containous/traefik/v2/pkg/log"
traefiktls "github.com/containous/traefik/v2/pkg/tls"
"golang.org/x/net/http2"
@ -23,30 +26,100 @@ func (t *h2cTransportWrapper) RoundTrip(req *http.Request) (*http.Response, erro
return t.Transport.RoundTrip(req)
}
// createRoundtripper creates an http.Roundtripper configured with the Transport configuration settings.
// NewRoundTripperManager creates a new RoundTripperManager.
func NewRoundTripperManager() *RoundTripperManager {
return &RoundTripperManager{
roundTrippers: make(map[string]http.RoundTripper),
configs: make(map[string]*dynamic.ServersTransport),
}
}
// RoundTripperManager handles roundtripper for the reverse proxy.
type RoundTripperManager struct {
rtLock sync.RWMutex
roundTrippers map[string]http.RoundTripper
configs map[string]*dynamic.ServersTransport
}
// Update updates the roundtrippers configurations.
func (r *RoundTripperManager) Update(newConfigs map[string]*dynamic.ServersTransport) {
r.rtLock.Lock()
defer r.rtLock.Unlock()
for configName, config := range r.configs {
newConfig, ok := newConfigs[configName]
if !ok {
delete(r.configs, configName)
delete(r.roundTrippers, configName)
continue
}
if reflect.DeepEqual(newConfig, config) {
continue
}
var err error
r.roundTrippers[configName], err = createRoundTripper(newConfig)
if err != nil {
log.WithoutContext().Errorf("Could not configure HTTP Transport %s, fallback on default transport: %v", configName, err)
r.roundTrippers[configName] = http.DefaultTransport
}
}
for newConfigName, newConfig := range newConfigs {
if _, ok := r.configs[newConfigName]; ok {
continue
}
var err error
r.roundTrippers[newConfigName], err = createRoundTripper(newConfig)
if err != nil {
log.WithoutContext().Errorf("Could not configure HTTP Transport %s, fallback on default transport: %v", newConfigName, err)
r.roundTrippers[newConfigName] = http.DefaultTransport
}
}
r.configs = newConfigs
}
// Get get a roundtripper by name.
func (r *RoundTripperManager) Get(name string) (http.RoundTripper, error) {
if len(name) == 0 {
name = "default@internal"
}
r.rtLock.RLock()
defer r.rtLock.RUnlock()
if rt, ok := r.roundTrippers[name]; ok {
return rt, nil
}
return nil, fmt.Errorf("servers transport not found %s", name)
}
// createRoundTripper creates an http.RoundTripper configured with the Transport configuration settings.
// For the settings that can't be configured in Traefik it uses the default http.Transport settings.
// An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHost
// in Traefik at this point in time. Setting this value to the default of 100 could lead to confusing
// behavior and backwards compatibility issues.
func createRoundtripper(transportConfiguration *static.ServersTransport) (http.RoundTripper, error) {
if transportConfiguration == nil {
// An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHostin Traefik at this point in time.
// Setting this value to the default of 100 could lead to confusing behavior and backwards compatibility issues.
func createRoundTripper(cfg *dynamic.ServersTransport) (http.RoundTripper, error) {
if cfg == nil {
return nil, errors.New("no transport configuration given")
}
dialer := &net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}
if transportConfiguration.ForwardingTimeouts != nil {
dialer.Timeout = time.Duration(transportConfiguration.ForwardingTimeouts.DialTimeout)
if cfg.ForwardingTimeouts != nil {
dialer.Timeout = time.Duration(cfg.ForwardingTimeouts.DialTimeout)
}
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialer.DialContext,
MaxIdleConnsPerHost: transportConfiguration.MaxIdleConnsPerHost,
MaxIdleConnsPerHost: cfg.MaxIdleConnsPerHost,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
@ -61,24 +134,21 @@ func createRoundtripper(transportConfiguration *static.ServersTransport) (http.R
},
})
if transportConfiguration.ForwardingTimeouts != nil {
transport.ResponseHeaderTimeout = time.Duration(transportConfiguration.ForwardingTimeouts.ResponseHeaderTimeout)
transport.IdleConnTimeout = time.Duration(transportConfiguration.ForwardingTimeouts.IdleConnTimeout)
if cfg.ForwardingTimeouts != nil {
transport.ResponseHeaderTimeout = time.Duration(cfg.ForwardingTimeouts.ResponseHeaderTimeout)
transport.IdleConnTimeout = time.Duration(cfg.ForwardingTimeouts.IdleConnTimeout)
}
if transportConfiguration.InsecureSkipVerify || len(transportConfiguration.RootCAs) > 0 {
if cfg.InsecureSkipVerify || len(cfg.RootCAs) > 0 || len(cfg.ServerName) > 0 || len(cfg.Certificates) > 0 {
transport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: transportConfiguration.InsecureSkipVerify,
RootCAs: createRootCACertPool(transportConfiguration.RootCAs),
ServerName: cfg.ServerName,
InsecureSkipVerify: cfg.InsecureSkipVerify,
RootCAs: createRootCACertPool(cfg.RootCAs),
Certificates: cfg.Certificates.GetCertificates(),
}
}
smartTransport, err := newSmartRoundTripper(transport)
if err != nil {
return nil, err
}
return smartTransport, nil
return newSmartRoundTripper(transport)
}
func createRootCACertPool(rootCAs []traefiktls.FileOrContent) *x509.CertPool {
@ -99,13 +169,3 @@ func createRootCACertPool(rootCAs []traefiktls.FileOrContent) *x509.CertPool {
return roots
}
func setupDefaultRoundTripper(conf *static.ServersTransport) http.RoundTripper {
transport, err := createRoundtripper(conf)
if err != nil {
log.WithoutContext().Errorf("Could not configure HTTP Transport, fallbacking on default transport: %v", err)
return http.DefaultTransport
}
return transport
}

View file

@ -0,0 +1,229 @@
package service
import (
"crypto/tls"
"crypto/x509"
"net"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"github.com/containous/traefik/v2/pkg/config/dynamic"
traefiktls "github.com/containous/traefik/v2/pkg/tls"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Int32(i int32) *int32 {
return &i
}
// LocalhostCert is a PEM-encoded TLS cert
// for host example.com, www.example.com
// expiring at Jan 29 16:00:00 2084 GMT.
// go run $GOROOT/src/crypto/tls/generate_cert.go --rsa-bits 1024 --host example.com,www.example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var LocalhostCert = []byte(`-----BEGIN CERTIFICATE-----
MIICDDCCAXWgAwIBAgIQH20JmcOlcRWHNuf62SYwszANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
iQKBgQC0qINy3F4oq6viDnlpDDE5J08iSRGggg6EylJKBKZfphEG2ufgK78Dufl3
+7b0LlEY2AeZHwviHODqC9a6ihj1ZYQk0/djAh+OeOhFEWu+9T/VP8gVFarFqT8D
Opy+hrG7YJivUIzwb4fmJQRI7FajzsnGyM6LiXLU+0qzb7ZO/QIDAQABo2EwXzAO
BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw
AwEB/zAnBgNVHREEIDAeggtleGFtcGxlLmNvbYIPd3d3LmV4YW1wbGUuY29tMA0G
CSqGSIb3DQEBCwUAA4GBAB+eluoQYzyyMfeEEAOtlldevx5MtDENT05NB0WI+91R
we7mX8lv763u0XuCWPxbHszhclI6FFjoQef0Z1NYLRm8ZRq58QqWDFZ3E6wdDK+B
+OWvkW+hRavo6R9LzIZPfbv8yBo4M9PK/DXw8hLqH7VkkI+Gh793iH7Ugd4A7wvT
-----END CERTIFICATE-----`)
// LocalhostKey is the private key for localhostCert.
var LocalhostKey = []byte(`-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALSog3LcXiirq+IO
eWkMMTknTyJJEaCCDoTKUkoEpl+mEQba5+ArvwO5+Xf7tvQuURjYB5kfC+Ic4OoL
1rqKGPVlhCTT92MCH4546EURa771P9U/yBUVqsWpPwM6nL6GsbtgmK9QjPBvh+Yl
BEjsVqPOycbIzouJctT7SrNvtk79AgMBAAECgYB1wMT1MBgbkFIXpXGTfAP1id61
rUTVBxCpkypx3ngHLjo46qRq5Hi72BN4FlTY8fugIudI8giP2FztkMvkiLDc4m0p
Gn+QMJzjlBjjTuNLvLy4aSmNRLIC3mtbx9PdU71DQswEpJHFj/vmsxbuSrG1I1YE
r1reuSo2ow6fOAjXLQJBANpz+RkOiPSPuvl+gi1sp2pLuynUJVDVqWZi386YRpfg
DiKCLpqwqYDkOozm/fwFALvwXKGmsyyL43HO8eI+2NsCQQDTtY32V+02GPecdsyq
msK06EPVTSaYwj9Mm+q709KsmYFHLXDqXjcKV4UgKYKRPz7my1fXodMmGmfuh1a3
/HMHAkEAmOQKN0tA90mRJwUvvvMIyRBv0fq0kzq28P3KfiF9ZtZdjjFmxMVYHOmf
QPZ6VGR7+w1jB5BQXqEZcpHQIPSzeQJBAIy9tZJ/AYNlNbcegxEnsSjy/6VdlLsY
51vWi0Yym2uC4R6gZuBnoc+OP0ISVmqY0Qg9RjhjrCs4gr9f2ZaWjSECQCxqZMq1
3viJ8BGCC0m/5jv1EHur3YgwphYCkf4Li6DKwIdMLk1WXkTcPIY3V2Jqj8rPEB5V
rqPRSAtd/h6oZbs=
-----END PRIVATE KEY-----`)
// openssl req -newkey rsa:2048 \
// -new -nodes -x509 \
// -days 3650 \
// -out cert.pem \
// -keyout key.pem \
// -subj "/CN=example.com"
// -addext "subjectAltName = DNS:example.com"
var mTLSCert = []byte(`-----BEGIN CERTIFICATE-----
MIIDJTCCAg2gAwIBAgIUYKnGcLnmMosOSKqTn4ydAMURE4gwDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wHhcNMjAwODEzMDkyNzIwWhcNMzAw
ODExMDkyNzIwWjAWMRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAOAe+QM1c9lZ2TPRgoiuPAq2A3Pfu+i82lmqrTJ0
PR2Cx1fPbccCUTFJPlxSDiaMrwtvqw1yP9L2Pu/vJK5BY4YDVDtFGKjpRBau1otJ
iY50O5qMo3sfLqR4/1VsQGlLVZYLD3dyc4ZTmOp8+7tJ2SyGorojbIKfimZT7XD7
dzrVr4h4Gn+SzzOnoKyx29uaNRP+XuMYHmHyQcJE03pUGhkTOvMwBlF96QdQ9WG0
D+1CxRciEsZXE+imKBHoaTgrTkpnFHzsrIEw+OHQYf30zuT/k/lkgv1vqEwINHjz
W2VeTur5eqVvA7zZdoEXMRy7BUvh/nZk5AXkXAmZLn0eUg8CAwEAAaNrMGkwHQYD
VR0OBBYEFEDrbhPDt+hi3ZOzk6S/CFAVHwk0MB8GA1UdIwQYMBaAFEDrbhPDt+hi
3ZOzk6S/CFAVHwk0MA8GA1UdEwEB/wQFMAMBAf8wFgYDVR0RBA8wDYILZXhhbXBs
ZS5jb20wDQYJKoZIhvcNAQELBQADggEBAG/JRJWeUNx2mDJAk8W7Syq3gmQB7s9f
+yY/XVRJZGahOPilABqFpC6GVn2HWuvuOqy8/RGk9ja5abKVXqE6YKrljqo3XfzB
KQcOz4SFirpkHvNCiEcK3kggN3wJWqL2QyXAxWldBBBCO9yx7a3cux31C//sTUOG
xq4JZDg171U1UOpfN1t0BFMdt05XZFEM247N7Dcf7HoXwAa7eyLKgtKWqPDqGrFa
fvGDDKK9X/KVsU2x9V3pG+LsJg7ogUnSyD2r5G1F3Y8OVs2T/783PaN0M35fDL38
09VbsxA2GasOHZrghUzT4UvZWWZbWEmG975hFYvdj6DlK9K0s5TdKIs=
-----END CERTIFICATE-----`)
var mTLSKey = []byte(`-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDgHvkDNXPZWdkz
0YKIrjwKtgNz37vovNpZqq0ydD0dgsdXz23HAlExST5cUg4mjK8Lb6sNcj/S9j7v
7ySuQWOGA1Q7RRio6UQWrtaLSYmOdDuajKN7Hy6keP9VbEBpS1WWCw93cnOGU5jq
fPu7SdkshqK6I2yCn4pmU+1w+3c61a+IeBp/ks8zp6CssdvbmjUT/l7jGB5h8kHC
RNN6VBoZEzrzMAZRfekHUPVhtA/tQsUXIhLGVxPopigR6Gk4K05KZxR87KyBMPjh
0GH99M7k/5P5ZIL9b6hMCDR481tlXk7q+XqlbwO82XaBFzEcuwVL4f52ZOQF5FwJ
mS59HlIPAgMBAAECggEAAKLV3hZ2v7UrkqQTlMO50+X0WI3YAK8Yh4yedTgzPDQ0
0KD8FMaC6HrmvGhXNfDMRmIIwD8Ew1qDjzbEieIRoD2+LXTivwf6c34HidmplEfs
K2IezKin/zuArgNio2ndUlGxt4sRnN373x5/sGZjQWcYayLSmgRN5kByuhFco0Qa
oSrXcXNUlb+KgRQXPDU4+M35tPHvLdyg+tko/m/5uK9dc9MNvGZHOMBKg0VNURJb
V1l3dR+evwvpqHzBvWiqN/YOiUUvIxlFKA35hJkfCl7ivFs4CLqqFNCKDao95fWe
s0UR9iMakT48jXV76IfwZnyX10OhIWzKls5trjDL8QKBgQD3thQJ8e0FL9y1W+Ph
mCdEaoffSPkgSn64wIsQ9bMmv4y+KYBK5AhqaHgYm4LgW4x1+CURNFu+YFEyaNNA
kNCXFyRX3Em3vxcShP5jIqg+f07mtXPKntWP/zBeKQWgdHX371oFTfaAlNuKX/7S
n0jBYjr4Iof1bnquMQvUoHCYWwKBgQDnntFU9/AQGaQIvhfeU1XKFkQ/BfhIsd27
RlmiCi0ee9Ce74cMAhWr/9yg0XUxzrh+Ui1xnkMVTZ5P8tWIxROokznLUTGJA5rs
zB+ovCPFZcquTwNzn7SBnpHTR0OqJd8sd89P5ST2SqufeSF/gGi5sTs4EocOLCpZ
EPVIfm47XQKBgB4d5RHQeCDJUOw739jtxthqm1pqZN+oLwAHaOEG/mEXqOT15sM0
NlG5oeBcB+1/M/Sj1t3gn8blrvmSBR00fifgiGqmPdA5S3TU9pjW/d2bXNxv80QP
S6fWPusz0ZtQjYc3cppygCXh808/nJu/AfmBF+pTSHRumjvTery/RPFBAoGBAMi/
zCta4cTylEvHhqR5kiefePMu120aTFYeuV1KeKStJ7o5XNE5lVMIZk80e+D5jMpf
q2eIhhgWuBoPHKh4N3uqbzMbYlWgvEx09xOmTVKv0SWW8iTqzOZza2y1nZ4BSRcf
mJ1ku86EFZAYysHZp+saA3usA0ZzXRjpK87zVdM5AoGBAOSqI+t48PnPtaUDFdpd
taNNVDbcecJatm3w8VDWnarahfWe66FIqc9wUkqekqAgwZLa0AGdUalvXfGrHfNs
PtvuNc5EImfSkuPBYLBslNxtjbBvAYgacEdY+gRhn2TeIUApnND58lCWsKbNHLFZ
ajIPbTY+Fe9OTOFTN48ujXNn
-----END PRIVATE KEY-----`)
func TestKeepConnectionWhenSameConfiguration(t *testing.T) {
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
connCount := Int32(0)
srv.Config.ConnState = func(conn net.Conn, state http.ConnState) {
if state == http.StateNew {
atomic.AddInt32(connCount, 1)
}
}
cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey)
require.NoError(t, err)
srv.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
srv.StartTLS()
rtManager := NewRoundTripperManager()
dynamicConf := map[string]*dynamic.ServersTransport{
"test": {
ServerName: "example.com",
RootCAs: []traefiktls.FileOrContent{traefiktls.FileOrContent(LocalhostCert)},
},
}
for i := 0; i < 10; i++ {
rtManager.Update(dynamicConf)
tr, err := rtManager.Get("test")
require.NoError(t, err)
client := http.Client{Transport: tr}
resp, err := client.Get(srv.URL)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
}
count := atomic.LoadInt32(connCount)
require.EqualValues(t, 1, count)
dynamicConf = map[string]*dynamic.ServersTransport{
"test": {
ServerName: "www.example.com",
RootCAs: []traefiktls.FileOrContent{traefiktls.FileOrContent(LocalhostCert)},
},
}
rtManager.Update(dynamicConf)
tr, err := rtManager.Get("test")
require.NoError(t, err)
client := http.Client{Transport: tr}
resp, err := client.Get(srv.URL)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
count = atomic.LoadInt32(connCount)
assert.EqualValues(t, 2, count)
}
func TestMTLS(t *testing.T) {
srv := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey)
require.NoError(t, err)
clientPool := x509.NewCertPool()
clientPool.AppendCertsFromPEM(mTLSCert)
srv.TLS = &tls.Config{
// For TLS
Certificates: []tls.Certificate{cert},
// For mTLS
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: clientPool,
}
srv.StartTLS()
rtManager := NewRoundTripperManager()
dynamicConf := map[string]*dynamic.ServersTransport{
"test": {
ServerName: "example.com",
// For TLS
RootCAs: []traefiktls.FileOrContent{traefiktls.FileOrContent(LocalhostCert)},
// For mTLS
Certificates: traefiktls.Certificates{
traefiktls.Certificate{
CertFile: traefiktls.FileOrContent(mTLSCert),
KeyFile: traefiktls.FileOrContent(mTLSKey),
},
},
},
}
rtManager.Update(dynamicConf)
tr, err := rtManager.Get("test")
require.NoError(t, err)
client := http.Client{Transport: tr}
resp, err := client.Get(srv.URL)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
}

View file

@ -35,13 +35,18 @@ const (
const defaultMaxBodySize int64 = -1
// RoundTripperGetter is a roundtripper getter interface.
type RoundTripperGetter interface {
Get(name string) (http.RoundTripper, error)
}
// NewManager creates a new Manager.
func NewManager(configs map[string]*runtime.ServiceInfo, defaultRoundTripper http.RoundTripper, metricsRegistry metrics.Registry, routinePool *safe.Pool) *Manager {
func NewManager(configs map[string]*runtime.ServiceInfo, metricsRegistry metrics.Registry, routinePool *safe.Pool, roundTripperManager RoundTripperGetter) *Manager {
return &Manager{
routinePool: routinePool,
metricsRegistry: metricsRegistry,
bufferPool: newBufferPool(),
defaultRoundTripper: defaultRoundTripper,
roundTripperManager: roundTripperManager,
balancers: make(map[string]healthcheck.Balancers),
configs: configs,
}
@ -52,7 +57,7 @@ type Manager struct {
routinePool *safe.Pool
metricsRegistry metrics.Registry
bufferPool httputil.BufferPool
defaultRoundTripper http.RoundTripper
roundTripperManager RoundTripperGetter
// balancers is the map of all Balancers, keyed by service name.
// There is one Balancer per service handler, and there is one service handler per reference to a service
// (e.g. if 2 routers refer to the same service name, 2 service handlers are created),
@ -168,7 +173,16 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName
service.PassHostHeader = &defaultPassHostHeader
}
fwd, err := buildProxy(service.PassHostHeader, service.ResponseForwarding, m.defaultRoundTripper, m.bufferPool)
if len(service.ServersTransport) > 0 {
service.ServersTransport = provider.GetQualifiedName(ctx, service.ServersTransport)
}
roundTripper, err := m.roundTripperManager.Get(service.ServersTransport)
if err != nil {
return nil, err
}
fwd, err := buildProxy(service.PassHostHeader, service.ResponseForwarding, roundTripper, m.bufferPool)
if err != nil {
return nil, err
}
@ -213,7 +227,7 @@ func (m *Manager) LaunchHealthCheck() {
if hcOpts := buildHealthCheckOptions(ctx, balancers, serviceName, service.HealthCheck); hcOpts != nil {
log.FromContext(ctx).Debugf("Setting up healthcheck for service %s with %s", serviceName, *hcOpts)
hcOpts.Transport = m.defaultRoundTripper
hcOpts.Transport, _ = m.roundTripperManager.Get(service.ServersTransport)
backendHealthCheck = healthcheck.NewBackendConfig(*hcOpts, serviceName)
}

View file

@ -80,7 +80,11 @@ func TestGetLoadBalancer(t *testing.T) {
}
func TestGetLoadBalancerServiceHandler(t *testing.T) {
sm := NewManager(nil, http.DefaultTransport, nil, nil)
sm := NewManager(nil, nil, nil, &RoundTripperManager{
roundTrippers: map[string]http.RoundTripper{
"default@internal": http.DefaultTransport,
},
})
server1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-From", "first")
@ -332,7 +336,11 @@ func TestManager_Build(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
manager := NewManager(test.configs, http.DefaultTransport, nil, nil)
manager := NewManager(test.configs, nil, nil, &RoundTripperManager{
roundTrippers: map[string]http.RoundTripper{
"default@internal": http.DefaultTransport,
},
})
ctx := context.Background()
if len(test.providerName) > 0 {
@ -355,7 +363,11 @@ func TestMultipleTypeOnBuildHTTP(t *testing.T) {
},
}
manager := NewManager(services, http.DefaultTransport, nil, nil)
manager := NewManager(services, nil, nil, &RoundTripperManager{
roundTrippers: map[string]http.RoundTripper{
"default@internal": http.DefaultTransport,
},
})
_, err := manager.BuildHTTP(context.Background(), "test@file")
assert.Error(t, err, "cannot create service: multi-types service not supported, consider declaring two different pieces of service instead")