1
0
Fork 0

Merge branch v2.5 into master

This commit is contained in:
kevinpollet 2021-11-08 22:41:43 +01:00
commit ce47f200d5
No known key found for this signature in database
GPG key ID: 0C9A5DDD1B292453
70 changed files with 834 additions and 500 deletions

View file

@ -272,7 +272,7 @@ func TestDo_dynamicConfiguration(t *testing.T) {
},
ForwardAuth: &dynamic.ForwardAuth{
Address: "127.0.0.1",
TLS: &dynamic.ClientTLS{
TLS: &types.ClientTLS{
CA: "ca.pem",
CAOptional: true,
Cert: "cert.pem",
@ -314,7 +314,7 @@ func TestDo_dynamicConfiguration(t *testing.T) {
NotAfter: true,
NotBefore: true,
Sans: true,
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
Country: true,
Province: true,
Locality: true,
@ -324,7 +324,7 @@ func TestDo_dynamicConfiguration(t *testing.T) {
SerialNumber: true,
DomainComponent: true,
},
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
Country: true,
Province: true,
Locality: true,

View file

@ -1,14 +1,11 @@
package dynamic
import (
"crypto/tls"
"crypto/x509"
"fmt"
"os"
"time"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v2/pkg/ip"
"github.com/traefik/traefik/v2/pkg/types"
)
// +k8s:deepcopy-gen=true
@ -131,12 +128,12 @@ type ErrorPage struct {
// ForwardAuth holds the http forward authentication configuration.
type ForwardAuth struct {
Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"`
TLS *ClientTLS `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
TrustForwardHeader bool `json:"trustForwardHeader,omitempty" toml:"trustForwardHeader,omitempty" yaml:"trustForwardHeader,omitempty" export:"true"`
AuthResponseHeaders []string `json:"authResponseHeaders,omitempty" toml:"authResponseHeaders,omitempty" yaml:"authResponseHeaders,omitempty" export:"true"`
AuthResponseHeadersRegex string `json:"authResponseHeadersRegex,omitempty" toml:"authResponseHeadersRegex,omitempty" yaml:"authResponseHeadersRegex,omitempty" export:"true"`
AuthRequestHeaders []string `json:"authRequestHeaders,omitempty" toml:"authRequestHeaders,omitempty" yaml:"authRequestHeaders,omitempty" export:"true"`
Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"`
TLS *types.ClientTLS `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
TrustForwardHeader bool `json:"trustForwardHeader,omitempty" toml:"trustForwardHeader,omitempty" yaml:"trustForwardHeader,omitempty" export:"true"`
AuthResponseHeaders []string `json:"authResponseHeaders,omitempty" toml:"authResponseHeaders,omitempty" yaml:"authResponseHeaders,omitempty" export:"true"`
AuthResponseHeadersRegex string `json:"authResponseHeadersRegex,omitempty" toml:"authResponseHeadersRegex,omitempty" yaml:"authResponseHeadersRegex,omitempty" export:"true"`
AuthRequestHeaders []string `json:"authRequestHeaders,omitempty" toml:"authRequestHeaders,omitempty" yaml:"authRequestHeaders,omitempty" export:"true"`
}
// +k8s:deepcopy-gen=true
@ -403,16 +400,16 @@ type TLSClientCertificateInfo struct {
NotAfter bool `json:"notAfter,omitempty" toml:"notAfter,omitempty" yaml:"notAfter,omitempty" export:"true"`
NotBefore bool `json:"notBefore,omitempty" toml:"notBefore,omitempty" yaml:"notBefore,omitempty" export:"true"`
Sans bool `json:"sans,omitempty" toml:"sans,omitempty" yaml:"sans,omitempty" export:"true"`
Subject *TLSCLientCertificateSubjectDNInfo `json:"subject,omitempty" toml:"subject,omitempty" yaml:"subject,omitempty" export:"true"`
Issuer *TLSCLientCertificateIssuerDNInfo `json:"issuer,omitempty" toml:"issuer,omitempty" yaml:"issuer,omitempty" export:"true"`
Subject *TLSClientCertificateSubjectDNInfo `json:"subject,omitempty" toml:"subject,omitempty" yaml:"subject,omitempty" export:"true"`
Issuer *TLSClientCertificateIssuerDNInfo `json:"issuer,omitempty" toml:"issuer,omitempty" yaml:"issuer,omitempty" export:"true"`
SerialNumber bool `json:"serialNumber,omitempty" toml:"serialNumber,omitempty" yaml:"serialNumber,omitempty" export:"true"`
}
// +k8s:deepcopy-gen=true
// TLSCLientCertificateIssuerDNInfo holds the client TLS certificate distinguished name info configuration.
// TLSClientCertificateIssuerDNInfo holds the client TLS certificate distinguished name info configuration.
// cf https://tools.ietf.org/html/rfc3739
type TLSCLientCertificateIssuerDNInfo struct {
type TLSClientCertificateIssuerDNInfo struct {
Country bool `json:"country,omitempty" toml:"country,omitempty" yaml:"country,omitempty" export:"true"`
Province bool `json:"province,omitempty" toml:"province,omitempty" yaml:"province,omitempty" export:"true"`
Locality bool `json:"locality,omitempty" toml:"locality,omitempty" yaml:"locality,omitempty" export:"true"`
@ -424,9 +421,9 @@ type TLSCLientCertificateIssuerDNInfo struct {
// +k8s:deepcopy-gen=true
// TLSCLientCertificateSubjectDNInfo holds the client TLS certificate distinguished name info configuration.
// TLSClientCertificateSubjectDNInfo holds the client TLS certificate distinguished name info configuration.
// cf https://tools.ietf.org/html/rfc3739
type TLSCLientCertificateSubjectDNInfo struct {
type TLSClientCertificateSubjectDNInfo struct {
Country bool `json:"country,omitempty" toml:"country,omitempty" yaml:"country,omitempty" export:"true"`
Province bool `json:"province,omitempty" toml:"province,omitempty" yaml:"province,omitempty" export:"true"`
Locality bool `json:"locality,omitempty" toml:"locality,omitempty" yaml:"locality,omitempty" export:"true"`
@ -441,83 +438,3 @@ type TLSCLientCertificateSubjectDNInfo struct {
// Users holds a list of users.
type Users []string
// +k8s:deepcopy-gen=true
// ClientTLS holds the TLS specific configurations as client
// CA, Cert and Key can be either path or file contents.
type ClientTLS struct {
CA string `json:"ca,omitempty" toml:"ca,omitempty" yaml:"ca,omitempty"`
CAOptional bool `json:"caOptional,omitempty" toml:"caOptional,omitempty" yaml:"caOptional,omitempty" export:"true"`
Cert string `json:"cert,omitempty" toml:"cert,omitempty" yaml:"cert,omitempty"`
Key string `json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty"`
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"`
}
// CreateTLSConfig creates a TLS config from ClientTLS structures.
func (c *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
if c == nil {
return nil, nil
}
var err error
caPool := x509.NewCertPool()
clientAuth := tls.NoClientCert
if c.CA != "" {
var ca []byte
if _, errCA := os.Stat(c.CA); errCA == nil {
ca, err = os.ReadFile(c.CA)
if err != nil {
return nil, fmt.Errorf("failed to read CA. %w", err)
}
} else {
ca = []byte(c.CA)
}
if !caPool.AppendCertsFromPEM(ca) {
return nil, fmt.Errorf("failed to parse CA")
}
if c.CAOptional {
clientAuth = tls.VerifyClientCertIfGiven
} else {
clientAuth = tls.RequireAndVerifyClientCert
}
}
cert := tls.Certificate{}
_, errKeyIsFile := os.Stat(c.Key)
if !c.InsecureSkipVerify && (len(c.Cert) == 0 || len(c.Key) == 0) {
return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created")
}
if len(c.Cert) > 0 && len(c.Key) > 0 {
if _, errCertIsFile := os.Stat(c.Cert); errCertIsFile == nil {
if errKeyIsFile == nil {
cert, err = tls.LoadX509KeyPair(c.Cert, c.Key)
if err != nil {
return nil, fmt.Errorf("failed to load TLS keypair: %w", err)
}
} else {
return nil, fmt.Errorf("tls cert is a file, but tls key is not")
}
} else {
if errKeyIsFile != nil {
cert, err = tls.X509KeyPair([]byte(c.Cert), []byte(c.Key))
if err != nil {
return nil, fmt.Errorf("failed to load TLS keypair: %w", err)
}
} else {
return nil, fmt.Errorf("TLS key is a file, but tls cert is not")
}
}
}
return &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caPool,
InsecureSkipVerify: c.InsecureSkipVerify,
ClientAuth: clientAuth,
}, nil
}

View file

@ -124,22 +124,6 @@ func (in *CircuitBreaker) DeepCopy() *CircuitBreaker {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClientTLS) DeepCopyInto(out *ClientTLS) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientTLS.
func (in *ClientTLS) DeepCopy() *ClientTLS {
if in == nil {
return nil
}
out := new(ClientTLS)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Compress) DeepCopyInto(out *Compress) {
*out = *in
@ -306,7 +290,7 @@ func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) {
*out = *in
if in.TLS != nil {
in, out := &in.TLS, &out.TLS
*out = new(ClientTLS)
*out = new(types.ClientTLS)
**out = **in
}
if in.AuthResponseHeaders != nil {
@ -1535,49 +1519,17 @@ func (in *TCPWeightedRoundRobin) DeepCopy() *TCPWeightedRoundRobin {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSCLientCertificateIssuerDNInfo) DeepCopyInto(out *TLSCLientCertificateIssuerDNInfo) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSCLientCertificateIssuerDNInfo.
func (in *TLSCLientCertificateIssuerDNInfo) DeepCopy() *TLSCLientCertificateIssuerDNInfo {
if in == nil {
return nil
}
out := new(TLSCLientCertificateIssuerDNInfo)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSCLientCertificateSubjectDNInfo) DeepCopyInto(out *TLSCLientCertificateSubjectDNInfo) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSCLientCertificateSubjectDNInfo.
func (in *TLSCLientCertificateSubjectDNInfo) DeepCopy() *TLSCLientCertificateSubjectDNInfo {
if in == nil {
return nil
}
out := new(TLSCLientCertificateSubjectDNInfo)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSClientCertificateInfo) DeepCopyInto(out *TLSClientCertificateInfo) {
*out = *in
if in.Subject != nil {
in, out := &in.Subject, &out.Subject
*out = new(TLSCLientCertificateSubjectDNInfo)
*out = new(TLSClientCertificateSubjectDNInfo)
**out = **in
}
if in.Issuer != nil {
in, out := &in.Issuer, &out.Issuer
*out = new(TLSCLientCertificateIssuerDNInfo)
*out = new(TLSClientCertificateIssuerDNInfo)
**out = **in
}
return
@ -1593,6 +1545,38 @@ func (in *TLSClientCertificateInfo) DeepCopy() *TLSClientCertificateInfo {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSClientCertificateIssuerDNInfo) DeepCopyInto(out *TLSClientCertificateIssuerDNInfo) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSClientCertificateIssuerDNInfo.
func (in *TLSClientCertificateIssuerDNInfo) DeepCopy() *TLSClientCertificateIssuerDNInfo {
if in == nil {
return nil
}
out := new(TLSClientCertificateIssuerDNInfo)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSClientCertificateSubjectDNInfo) DeepCopyInto(out *TLSClientCertificateSubjectDNInfo) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSClientCertificateSubjectDNInfo.
func (in *TLSClientCertificateSubjectDNInfo) DeepCopy() *TLSClientCertificateSubjectDNInfo {
if in == nil {
return nil
}
out := new(TLSClientCertificateSubjectDNInfo)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSConfiguration) DeepCopyInto(out *TLSConfiguration) {
*out = *in

View file

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/types"
)
func TestDecodeConfiguration(t *testing.T) {
@ -367,7 +368,7 @@ func TestDecodeConfiguration(t *testing.T) {
NotAfter: true,
NotBefore: true,
SerialNumber: true,
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
Country: true,
Province: true,
Locality: true,
@ -377,7 +378,7 @@ func TestDecodeConfiguration(t *testing.T) {
SerialNumber: true,
DomainComponent: true,
},
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
Country: true,
Province: true,
Locality: true,
@ -505,7 +506,7 @@ func TestDecodeConfiguration(t *testing.T) {
"Middleware7": {
ForwardAuth: &dynamic.ForwardAuth{
Address: "foobar",
TLS: &dynamic.ClientTLS{
TLS: &types.ClientTLS{
CA: "foobar",
CAOptional: true,
Cert: "foobar",
@ -848,7 +849,7 @@ func TestEncodeConfiguration(t *testing.T) {
NotAfter: true,
NotBefore: true,
SerialNumber: true,
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
Country: true,
Province: true,
Locality: true,
@ -858,7 +859,7 @@ func TestEncodeConfiguration(t *testing.T) {
SerialNumber: true,
DomainComponent: true,
},
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
Country: true,
Province: true,
Locality: true,
@ -993,7 +994,7 @@ func TestEncodeConfiguration(t *testing.T) {
"Middleware7": {
ForwardAuth: &dynamic.ForwardAuth{
Address: "foobar",
TLS: &dynamic.ClientTLS{
TLS: &types.ClientTLS{
CA: "foobar",
CAOptional: true,
Cert: "foobar",

View file

@ -2,7 +2,6 @@ package accesslog
import (
"net/http"
"os"
"testing"
"time"
@ -84,7 +83,7 @@ func TestCommonLogFormatter_Format(t *testing.T) {
}
// Set timezone to Etc/GMT+9 to have a constant behavior
os.Setenv("TZ", "Etc/GMT+9")
t.Setenv("TZ", "Etc/GMT+9")
for _, test := range testCases {
test := test

View file

@ -72,9 +72,9 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
}
if config.TLS != nil {
tlsConfig, err := config.TLS.CreateTLSConfig()
tlsConfig, err := config.TLS.CreateTLSConfig(ctx)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to create client TLS configuration: %w", err)
}
tr := http.DefaultTransport.(*http.Transport).Clone()

View file

@ -48,7 +48,7 @@ type IssuerDistinguishedNameOptions struct {
StateOrProvinceName bool
}
func newIssuerDistinguishedNameOptions(info *dynamic.TLSCLientCertificateIssuerDNInfo) *IssuerDistinguishedNameOptions {
func newIssuerDistinguishedNameOptions(info *dynamic.TLSClientCertificateIssuerDNInfo) *IssuerDistinguishedNameOptions {
if info == nil {
return nil
}
@ -78,7 +78,7 @@ type SubjectDistinguishedNameOptions struct {
StateOrProvinceName bool
}
func newSubjectDistinguishedNameOptions(info *dynamic.TLSCLientCertificateSubjectDNInfo) *SubjectDistinguishedNameOptions {
func newSubjectDistinguishedNameOptions(info *dynamic.TLSClientCertificateSubjectDNInfo) *SubjectDistinguishedNameOptions {
if info == nil {
return nil
}

View file

@ -376,7 +376,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
desc: "No TLS, with subject info",
config: dynamic.PassTLSClientCert{
Info: &dynamic.TLSClientCertificateInfo{
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
CommonName: true,
Organization: true,
OrganizationalUnit: true,
@ -393,7 +393,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
config: dynamic.PassTLSClientCert{
PEM: false,
Info: &dynamic.TLSClientCertificateInfo{
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{},
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{},
},
},
},
@ -406,7 +406,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
NotBefore: true,
Sans: true,
SerialNumber: true,
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
CommonName: true,
Country: true,
DomainComponent: true,
@ -416,7 +416,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
Province: true,
SerialNumber: true,
},
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
CommonName: true,
Country: true,
DomainComponent: true,
@ -436,10 +436,10 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
Info: &dynamic.TLSClientCertificateInfo{
NotAfter: true,
Sans: true,
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
Organization: true,
},
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
Country: true,
},
},
@ -453,13 +453,13 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
Info: &dynamic.TLSClientCertificateInfo{
NotAfter: true,
Sans: true,
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
Organization: true,
// OrganizationalUnit is not set on this example certificate,
// so even though it's requested, it will be absent.
OrganizationalUnit: true,
},
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
Country: true,
},
},
@ -475,7 +475,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
NotBefore: true,
Sans: true,
SerialNumber: true,
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
Country: true,
Province: true,
Locality: true,
@ -485,7 +485,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
SerialNumber: true,
DomainComponent: true,
},
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
Country: true,
Province: true,
Locality: true,
@ -507,7 +507,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
NotBefore: true,
Sans: true,
SerialNumber: true,
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
Country: true,
Province: true,
Locality: true,
@ -517,7 +517,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
SerialNumber: true,
DomainComponent: true,
},
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
Country: true,
Province: true,
Locality: true,

View file

@ -41,12 +41,12 @@ func (r *replacePath) GetTracingInformation() (string, ext.SpanKindEnum) {
}
func (r *replacePath) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if req.URL.RawPath == "" {
req.Header.Add(ReplacedPathHeader, req.URL.Path)
} else {
req.Header.Add(ReplacedPathHeader, req.URL.RawPath)
currentPath := req.URL.RawPath
if currentPath == "" {
currentPath = req.URL.EscapedPath()
}
req.Header.Add(ReplacedPathHeader, currentPath)
req.URL.RawPath = r.path
var err error

View file

@ -60,6 +60,16 @@ func TestReplacePath(t *testing.T) {
expectedRawPath: "/foo%2Fbar",
expectedHeader: "/path",
},
{
desc: "replacement with percent encoded backspace char",
path: "/path/%08bar",
config: dynamic.ReplacePath{
Path: "/path/%08bar",
},
expectedPath: "/path/\bbar",
expectedRawPath: "/path/%08bar",
expectedHeader: "/path/%08bar",
},
}
for _, test := range testCases {

View file

@ -16,9 +16,7 @@ import (
"github.com/traefik/traefik/v2/pkg/tracing"
)
const (
typeName = "ReplacePathRegex"
)
const typeName = "ReplacePathRegex"
// ReplacePathRegex is a middleware used to replace the path of a URL request with a regular expression.
type replacePathRegex struct {
@ -50,16 +48,13 @@ func (rp *replacePathRegex) GetTracingInformation() (string, ext.SpanKindEnum) {
}
func (rp *replacePathRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
var currentPath string
if req.URL.RawPath == "" {
currentPath = req.URL.Path
} else {
currentPath = req.URL.RawPath
currentPath := req.URL.RawPath
if currentPath == "" {
currentPath = req.URL.EscapedPath()
}
if rp.regexp != nil && len(rp.replacement) > 0 && rp.regexp.MatchString(currentPath) {
req.Header.Add(replacepath.ReplacedPathHeader, currentPath)
req.URL.RawPath = rp.regexp.ReplaceAllString(currentPath, rp.replacement)
// as replacement can introduce escaped characters

View file

@ -106,6 +106,16 @@ func TestReplacePathRegex(t *testing.T) {
expectedPath: "/aaa/bbb",
expectedRawPath: "/aaa%2Fbbb",
},
{
desc: "path with percent encoded backspace char",
path: "/foo/%08bar",
config: dynamic.ReplacePathRegex{
Replacement: "/$1",
Regex: `^/foo/(.*)`,
},
expectedPath: "/\bbar",
expectedRawPath: "/%08bar",
},
}
for _, test := range testCases {

View file

@ -165,7 +165,7 @@ func (p *Provider) getClientOpts() ([]client.Opt, error) {
conf, err := p.TLS.CreateTLSConfig(ctx)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to create client TLS configuration: %w", err)
}
hostURL, err := client.ParseHostURL(p.Endpoint)

View file

@ -55,7 +55,7 @@ func (p *Provider) Init() error {
if p.TLS != nil {
tlsConfig, err := p.TLS.CreateTLSConfig(context.Background())
if err != nil {
return fmt.Errorf("unable to create TLS configuration: %w", err)
return fmt.Errorf("unable to create client TLS configuration: %w", err)
}
p.httpClient.Transport = &http.Transport{

View file

@ -0,0 +1,29 @@
apiVersion: traefik.containo.us/v1alpha1
kind: MiddlewareTCP
metadata:
name: multiple---hyphens
namespace: default
spec:
ipWhiteList:
sourceRange:
- 127.0.0.1/32
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- foo
routes:
- match: HostSNI(`foo.com`)
services:
- name: whoamitcp
port: 8000
middlewares:
- name: multiple---hyphens

View file

@ -0,0 +1,31 @@
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: multiple---hyphens
namespace: default
spec:
stripPrefix:
prefixes:
- /tobestripped
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test2.route
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`foo.com`) && PathPrefix(`/tobestripped`)
priority: 12
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: multiple---hyphens

View file

@ -25,6 +25,7 @@ import (
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
"github.com/traefik/traefik/v2/pkg/safe"
"github.com/traefik/traefik/v2/pkg/tls"
"github.com/traefik/traefik/v2/pkg/types"
corev1 "k8s.io/api/core/v1"
apiextensionv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/labels"
@ -483,7 +484,7 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *v1alp
return forwardAuth, nil
}
forwardAuth.TLS = &dynamic.ClientTLS{
forwardAuth.TLS = &types.ClientTLS{
CAOptional: auth.TLS.CAOptional,
InsecureSkipVerify: auth.TLS.InsecureSkipVerify,
}

View file

@ -183,7 +183,7 @@ func (p *Provider) makeMiddlewareKeys(ctx context.Context, ingRouteNamespace str
ns = mi.Namespace
}
mds = append(mds, makeID(ns, name))
mds = append(mds, provider.Normalize(makeID(ns, name)))
}
return mds, nil

View file

@ -10,6 +10,7 @@ import (
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/provider"
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
"github.com/traefik/traefik/v2/pkg/tls"
corev1 "k8s.io/api/core/v1"
@ -162,7 +163,7 @@ func (p *Provider) makeMiddlewareTCPKeys(ctx context.Context, ingRouteTCPNamespa
ns = mi.Namespace
}
mds = append(mds, makeID(ns, mi.Name))
mds = append(mds, provider.Normalize(makeID(ns, mi.Name)))
}
return mds, nil

View file

@ -11,13 +11,14 @@ import (
auth "github.com/abbot/go-http-auth"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/paerser/types"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/provider"
crdfake "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake"
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/k8s"
"github.com/traefik/traefik/v2/pkg/tls"
"github.com/traefik/traefik/v2/pkg/types"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
@ -151,6 +152,54 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Middlewares in ingress route config are normalized",
paths: []string{"tcp/services.yml", "tcp/with_middleware_multiple_hyphens.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{
"default-test.route-fdd3e9338e47a45efefc": {
EntryPoints: []string{"foo"},
Service: "default-test.route-fdd3e9338e47a45efefc",
Middlewares: []string{"default-multiple-hyphens"},
Rule: "HostSNI(`foo.com`)",
},
},
Middlewares: map[string]*dynamic.TCPMiddleware{
"default-multiple-hyphens": {
IPWhiteList: &dynamic.TCPIPWhiteList{
SourceRange: []string{"127.0.0.1/32"},
},
},
},
Services: map[string]*dynamic.TCPService{
"default-test.route-fdd3e9338e47a45efefc": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.1:8000",
},
{
Address: "10.10.0.2:8000",
},
},
},
},
},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Simple Ingress Route, with foo entrypoint and crossprovider middleware",
paths: []string{"tcp/services.yml", "tcp/with_middleware_crossprovider.yml"},
@ -1458,6 +1507,57 @@ func TestLoadIngressRoutes(t *testing.T) {
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Middlewares in ingress route config are normalized",
AllowCrossNamespace: true,
paths: []string{"services.yml", "with_middleware_multiple_hyphens.yml"},
expected: &dynamic.Configuration{
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-test2-route-23c7f4c450289ee29016": {
EntryPoints: []string{"web"},
Service: "default-test2-route-23c7f4c450289ee29016",
Rule: "Host(`foo.com`) && PathPrefix(`/tobestripped`)",
Priority: 12,
Middlewares: []string{"default-multiple-hyphens"},
},
},
Middlewares: map[string]*dynamic.Middleware{
"default-multiple-hyphens": {
StripPrefix: &dynamic.StripPrefix{
Prefixes: []string{"/tobestripped"},
},
},
},
Services: map[string]*dynamic.Service{
"default-test2-route-23c7f4c450289ee29016": {
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{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Simple Ingress Route with middleware crossprovider",
AllowCrossNamespace: true,
@ -3146,7 +3246,7 @@ func TestLoadIngressRoutes(t *testing.T) {
"default-forwardauth": {
ForwardAuth: &dynamic.ForwardAuth{
Address: "test.com",
TLS: &dynamic.ClientTLS{
TLS: &types.ClientTLS{
CA: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----",
Cert: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----",
Key: "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----",
@ -3515,17 +3615,17 @@ func TestLoadIngressRoutes(t *testing.T) {
MaxIdleConnsPerHost: 42,
DisableHTTP2: true,
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
DialTimeout: types.Duration(42 * time.Second),
ResponseHeaderTimeout: types.Duration(42 * time.Second),
IdleConnTimeout: types.Duration(42 * time.Millisecond),
DialTimeout: ptypes.Duration(42 * time.Second),
ResponseHeaderTimeout: ptypes.Duration(42 * time.Second),
IdleConnTimeout: ptypes.Duration(42 * time.Millisecond),
},
PeerCertURI: "foo://bar",
},
"default-test": {
ServerName: "test",
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
DialTimeout: types.Duration(30 * time.Second),
IdleConnTimeout: types.Duration(90 * time.Second),
DialTimeout: ptypes.Duration(30 * time.Second),
IdleConnTimeout: ptypes.Duration(90 * time.Second),
},
},
},

View file

@ -14,7 +14,7 @@ import (
func MustParseYaml(content []byte) []runtime.Object {
acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute)$`)
files := strings.Split(string(content), "---")
files := strings.Split(string(content), "---\n")
retVal := make([]runtime.Object, 0, len(files))
for _, file := range files {
if file == "\n" || file == "" {

View file

@ -170,7 +170,7 @@ func (p *Provider) createKVClient(ctx context.Context) (store.Store, error) {
var err error
storeConfig.TLS, err = p.TLS.CreateTLSConfig(ctx)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to create client TLS configuration: %w", err)
}
}

View file

@ -402,7 +402,7 @@ func Test_buildConfiguration(t *testing.T) {
"Middleware08": {
ForwardAuth: &dynamic.ForwardAuth{
Address: "foobar",
TLS: &dynamic.ClientTLS{
TLS: &types.ClientTLS{
CA: "foobar",
CAOptional: true,
Cert: "foobar",
@ -481,7 +481,7 @@ func Test_buildConfiguration(t *testing.T) {
NotAfter: true,
NotBefore: true,
Sans: true,
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
Country: true,
Province: true,
Locality: true,
@ -491,7 +491,7 @@ func Test_buildConfiguration(t *testing.T) {
SerialNumber: true,
DomainComponent: true,
},
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
Country: true,
Province: true,
Locality: true,

View file

@ -134,7 +134,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
}
TLSConfig, err := p.TLS.CreateTLSConfig(ctx)
if err != nil {
return err
return fmt.Errorf("unable to create client TLS configuration: %w", err)
}
confg.HTTPClient = &http.Client{
Transport: &http.Transport{

View file

@ -15,7 +15,7 @@ type server struct {
// WRRLoadBalancer is a naive RoundRobin load balancer for TCP services.
type WRRLoadBalancer struct {
servers []server
lock sync.RWMutex
lock sync.Mutex
currentWeight int
index int
}
@ -29,16 +29,16 @@ func NewWRRLoadBalancer() *WRRLoadBalancer {
// ServeTCP forwards the connection to the right service.
func (b *WRRLoadBalancer) ServeTCP(conn WriteCloser) {
if len(b.servers) == 0 {
log.WithoutContext().Error("no available server")
return
}
b.lock.Lock()
next, err := b.next()
b.lock.Unlock()
if err != nil {
log.WithoutContext().Errorf("Error during load balancing: %v", err)
conn.Close()
return
}
next.ServeTCP(conn)
}
@ -50,6 +50,9 @@ func (b *WRRLoadBalancer) AddServer(serverHandler Handler) {
// AddWeightServer appends a server to the existing list with a weight.
func (b *WRRLoadBalancer) AddWeightServer(serverHandler Handler, weight *int) {
b.lock.Lock()
defer b.lock.Unlock()
w := 1
if weight != nil {
w = *weight
@ -87,9 +90,6 @@ func gcd(a, b int) int {
}
func (b *WRRLoadBalancer) next() (Handler, error) {
b.lock.Lock()
defer b.lock.Unlock()
if len(b.servers) == 0 {
return nil, fmt.Errorf("no servers in the pool")
}
@ -98,10 +98,14 @@ func (b *WRRLoadBalancer) next() (Handler, error) {
// it calculates the GCD and subtracts it on every iteration, what interleaves servers
// and allows us not to build an iterator every time we readjust weights
// GCD across all enabled servers
gcd := b.weightGcd()
// Maximum weight across all enabled servers
max := b.maxWeight()
if max == 0 {
return nil, fmt.Errorf("all servers have 0 weight")
}
// GCD across all enabled servers
gcd := b.weightGcd()
for {
b.index = (b.index + 1) % len(b.servers)
@ -109,9 +113,6 @@ func (b *WRRLoadBalancer) next() (Handler, error) {
b.currentWeight -= gcd
if b.currentWeight <= 0 {
b.currentWeight = max
if b.currentWeight == 0 {
return nil, fmt.Errorf("all servers have 0 weight")
}
}
}
srv := b.servers[b.index]

View file

@ -10,7 +10,8 @@ import (
)
type fakeConn struct {
call map[string]int
writeCall map[string]int
closeCall int
}
func (f *fakeConn) Read(b []byte) (n int, err error) {
@ -18,12 +19,13 @@ func (f *fakeConn) Read(b []byte) (n int, err error) {
}
func (f *fakeConn) Write(b []byte) (n int, err error) {
f.call[string(b)]++
f.writeCall[string(b)]++
return len(b), nil
}
func (f *fakeConn) Close() error {
panic("implement me")
f.closeCall++
return nil
}
func (f *fakeConn) LocalAddr() net.Addr {
@ -55,7 +57,8 @@ func TestLoadBalancing(t *testing.T) {
desc string
serversWeight map[string]int
totalCall int
expected map[string]int
expectedWrite map[string]int
expectedClose int
}{
{
desc: "RoundRobin",
@ -64,7 +67,7 @@ func TestLoadBalancing(t *testing.T) {
"h2": 1,
},
totalCall: 4,
expected: map[string]int{
expectedWrite: map[string]int{
"h1": 2,
"h2": 2,
},
@ -76,7 +79,7 @@ func TestLoadBalancing(t *testing.T) {
"h2": 1,
},
totalCall: 4,
expected: map[string]int{
expectedWrite: map[string]int{
"h1": 3,
"h2": 1,
},
@ -88,22 +91,33 @@ func TestLoadBalancing(t *testing.T) {
"h2": 1,
},
totalCall: 16,
expected: map[string]int{
expectedWrite: map[string]int{
"h1": 12,
"h2": 4,
},
},
{
desc: "WeighedRoundRobin with 0 weight server",
desc: "WeighedRoundRobin with one 0 weight server",
serversWeight: map[string]int{
"h1": 3,
"h2": 0,
},
totalCall: 16,
expected: map[string]int{
expectedWrite: map[string]int{
"h1": 16,
},
},
{
desc: "WeighedRoundRobin with all servers with 0 weight",
serversWeight: map[string]int{
"h1": 0,
"h2": 0,
"h3": 0,
},
totalCall: 10,
expectedWrite: map[string]int{},
expectedClose: 10,
},
}
for _, test := range testCases {
@ -120,12 +134,13 @@ func TestLoadBalancing(t *testing.T) {
}), &weight)
}
conn := &fakeConn{call: make(map[string]int)}
conn := &fakeConn{writeCall: make(map[string]int)}
for i := 0; i < test.totalCall; i++ {
balancer.ServeTCP(conn)
}
assert.Equal(t, test.expected, conn.call)
assert.Equal(t, test.expectedWrite, conn.writeCall)
assert.Equal(t, test.expectedClose, conn.closeCall)
})
}
}

View file

@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB9jCCAV+gAwIBAgIQI3edJckNbicw4WIHs5Ws9TANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
iQKBgQCb8oWyME1QRBoMLFei3M8TVKwfZfW74cVjtcugCBMTTOTCouEIgjjmiMv6
FdMio2uBcgeD9R3dOtjjnA7N+xjwZ4vIPqDlJRE3YbfpV9igVX3sXU7ssHTSH0vs
R0TuYJwGReIFUnu5QIjGwVorodF+CQ8dTnyXVLeQVU9kvjohHwIDAQABo0swSTAO
BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIw
ADAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADgYEADqylUQ/4
lrxh4h8UUQ2wKATQ2kG2YvMGlaIhr2vPZo2QDBlmL2xzai7YXX3+JZyM15TNCamn
WtFR7WQIOHzKA1GkR9WkaXKmFbJjhGMSZVCG6ghhTjzB+stBYZXhBsdjCJbkZWBu
OeI73oivo0MdI+4iCYCo7TnoY4PZGObwcgI=
-----END CERTIFICATE-----

View file

@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJvyhbIwTVBEGgws
V6LczxNUrB9l9bvhxWO1y6AIExNM5MKi4QiCOOaIy/oV0yKja4FyB4P1Hd062OOc
Ds37GPBni8g+oOUlETdht+lX2KBVfexdTuywdNIfS+xHRO5gnAZF4gVSe7lAiMbB
Wiuh0X4JDx1OfJdUt5BVT2S+OiEfAgMBAAECgYA9+PbghQl0aFvhko2RDybLi86K
+73X2DTVFx3AjvTlqp0OLCQ5eWabVqmYzKuHDGJgoqwR6Irhq80dRpsriCm0YNui
mMV35bbimOKz9FoCTKx0ZB6xsqrVoFhjVmX3DOD9Txe41H42ZxmccOKZndR/QaXz
VV+1W/Wbz2VawnkyYQJBAMvF6w2eOJRRoN8e7GM7b7uqkupJPp9axgFREoJZb16W
mqXUZnH4Cydzc5keG4yknQRHdgz6RrQxnvR7GyKHLfUCQQDD6qG9D5BX0+mNW6TG
PRwW/L2qWgnmg9lxtSSQat9ZOnBhw2OLPi0zTu4p70oSmU67/YJr50HEoJpRccZJ
mnJDAkBdBTtY2xpe8qhqUjZ80hweYi5wzwDMQ+bRoQ2+/U6usjdkbgJaEm4dE0H4
6tqOqHKZCnokUHfIOEKkvjHT4DulAkBAgiJNSTGi6aDOLa28pGR6YS/mRo1Z/HH9
kcJ/VuFB1Q8p8Zb2QzvI2CVtY2AFbbtSBPALrXKnVqZZSNgcZiFXAkEAvcLKaEXE
haGMGwq2BLADPHqAR3hdCJL3ikMJwWUsTkTjm973iEIEZfF5j57EzRI4bASm4Zq5
Zt3BcblLODQ//w==
-----END PRIVATE KEY-----

View file

@ -4,12 +4,15 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"os"
"github.com/traefik/traefik/v2/pkg/log"
)
// +k8s:deepcopy-gen=true
// ClientTLS holds TLS specific configurations as client
// CA, Cert and Key can be either path or file contents.
type ClientTLS struct {
@ -27,7 +30,9 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
return nil, nil
}
caPool := x509.NewCertPool()
// Not initialized, to rely on system bundle.
var caPool *x509.CertPool
clientAuth := tls.NoClientCert
if clientTLS.CA != "" {
var ca []byte
@ -41,8 +46,9 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
ca = []byte(clientTLS.CA)
}
caPool = x509.NewCertPool()
if !caPool.AppendCertsFromPEM(ca) {
return nil, fmt.Errorf("failed to parse CA")
return nil, errors.New("failed to parse CA")
}
if clientTLS.CAOptional {
@ -52,34 +58,24 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
}
}
if !clientTLS.InsecureSkipVerify && (len(clientTLS.Cert) == 0 || len(clientTLS.Key) == 0) {
return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created")
hasCert := len(clientTLS.Cert) > 0
hasKey := len(clientTLS.Key) > 0
if hasCert != hasKey {
return nil, errors.New("both TLS cert and key must be defined")
}
cert := tls.Certificate{}
_, errKeyIsFile := os.Stat(clientTLS.Key)
if !hasCert || !hasKey {
return &tls.Config{
RootCAs: caPool,
InsecureSkipVerify: clientTLS.InsecureSkipVerify,
ClientAuth: clientAuth,
}, nil
}
if len(clientTLS.Cert) > 0 && len(clientTLS.Key) > 0 {
var err error
if _, errCertIsFile := os.Stat(clientTLS.Cert); errCertIsFile == nil {
if errKeyIsFile == nil {
cert, err = tls.LoadX509KeyPair(clientTLS.Cert, clientTLS.Key)
if err != nil {
return nil, fmt.Errorf("failed to load TLS keypair: %w", err)
}
} else {
return nil, fmt.Errorf("TLS cert is a file, but tls key is not")
}
} else {
if errKeyIsFile != nil {
cert, err = tls.X509KeyPair([]byte(clientTLS.Cert), []byte(clientTLS.Key))
if err != nil {
return nil, fmt.Errorf("failed to load TLS keypair: %w", err)
}
} else {
return nil, fmt.Errorf("TLS key is a file, but tls cert is not")
}
}
cert, err := loadKeyPair(clientTLS.Cert, clientTLS.Key)
if err != nil {
return nil, err
}
return &tls.Config{
@ -89,3 +85,27 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
ClientAuth: clientAuth,
}, nil
}
func loadKeyPair(cert, key string) (tls.Certificate, error) {
keyPair, err := tls.X509KeyPair([]byte(cert), []byte(key))
if err == nil {
return keyPair, nil
}
_, err = os.Stat(cert)
if err != nil {
return tls.Certificate{}, errors.New("cert file does not exist")
}
_, err = os.Stat(key)
if err != nil {
return tls.Certificate{}, errors.New("key file does not exist")
}
keyPair, err = tls.LoadX509KeyPair(cert, key)
if err != nil {
return tls.Certificate{}, err
}
return keyPair, nil
}

129
pkg/types/tls_test.go Normal file
View file

@ -0,0 +1,129 @@
package types
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// go run $GOROOT/src/crypto/tls/generate_cert.go --rsa-bits 1024 --host localhost --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var cert = `-----BEGIN CERTIFICATE-----
MIIB9jCCAV+gAwIBAgIQI3edJckNbicw4WIHs5Ws9TANBgkqhkiG9w0BAQsFADAS
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
iQKBgQCb8oWyME1QRBoMLFei3M8TVKwfZfW74cVjtcugCBMTTOTCouEIgjjmiMv6
FdMio2uBcgeD9R3dOtjjnA7N+xjwZ4vIPqDlJRE3YbfpV9igVX3sXU7ssHTSH0vs
R0TuYJwGReIFUnu5QIjGwVorodF+CQ8dTnyXVLeQVU9kvjohHwIDAQABo0swSTAO
BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIw
ADAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADgYEADqylUQ/4
lrxh4h8UUQ2wKATQ2kG2YvMGlaIhr2vPZo2QDBlmL2xzai7YXX3+JZyM15TNCamn
WtFR7WQIOHzKA1GkR9WkaXKmFbJjhGMSZVCG6ghhTjzB+stBYZXhBsdjCJbkZWBu
OeI73oivo0MdI+4iCYCo7TnoY4PZGObwcgI=
-----END CERTIFICATE-----`
var key = `-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJvyhbIwTVBEGgws
V6LczxNUrB9l9bvhxWO1y6AIExNM5MKi4QiCOOaIy/oV0yKja4FyB4P1Hd062OOc
Ds37GPBni8g+oOUlETdht+lX2KBVfexdTuywdNIfS+xHRO5gnAZF4gVSe7lAiMbB
Wiuh0X4JDx1OfJdUt5BVT2S+OiEfAgMBAAECgYA9+PbghQl0aFvhko2RDybLi86K
+73X2DTVFx3AjvTlqp0OLCQ5eWabVqmYzKuHDGJgoqwR6Irhq80dRpsriCm0YNui
mMV35bbimOKz9FoCTKx0ZB6xsqrVoFhjVmX3DOD9Txe41H42ZxmccOKZndR/QaXz
VV+1W/Wbz2VawnkyYQJBAMvF6w2eOJRRoN8e7GM7b7uqkupJPp9axgFREoJZb16W
mqXUZnH4Cydzc5keG4yknQRHdgz6RrQxnvR7GyKHLfUCQQDD6qG9D5BX0+mNW6TG
PRwW/L2qWgnmg9lxtSSQat9ZOnBhw2OLPi0zTu4p70oSmU67/YJr50HEoJpRccZJ
mnJDAkBdBTtY2xpe8qhqUjZ80hweYi5wzwDMQ+bRoQ2+/U6usjdkbgJaEm4dE0H4
6tqOqHKZCnokUHfIOEKkvjHT4DulAkBAgiJNSTGi6aDOLa28pGR6YS/mRo1Z/HH9
kcJ/VuFB1Q8p8Zb2QzvI2CVtY2AFbbtSBPALrXKnVqZZSNgcZiFXAkEAvcLKaEXE
haGMGwq2BLADPHqAR3hdCJL3ikMJwWUsTkTjm973iEIEZfF5j57EzRI4bASm4Zq5
Zt3BcblLODQ//w==
-----END PRIVATE KEY-----`
func TestClientTLS_CreateTLSConfig(t *testing.T) {
tests := []struct {
desc string
clientTLS ClientTLS
wantCertLen int
wantCALen int
wantErr bool
}{
{
desc: "Configure CA",
clientTLS: ClientTLS{CA: cert},
wantCALen: 1,
wantErr: false,
},
{
desc: "Configure the client keyPair from strings",
clientTLS: ClientTLS{Cert: cert, Key: key},
wantCertLen: 1,
wantErr: false,
},
{
desc: "Configure the client keyPair from files",
clientTLS: ClientTLS{Cert: "fixtures/cert.pem", Key: "fixtures/key.pem"},
wantCertLen: 1,
wantErr: false,
},
{
desc: "Configure InsecureSkipVerify",
clientTLS: ClientTLS{InsecureSkipVerify: true},
wantErr: false,
},
{
desc: "Return an error if only the client cert is provided",
clientTLS: ClientTLS{Cert: cert},
wantErr: true,
},
{
desc: "Return an error if only the client key is provided",
clientTLS: ClientTLS{Key: key},
wantErr: true,
},
{
desc: "Return an error if only the client cert is of type file",
clientTLS: ClientTLS{Cert: "fixtures/cert.pem", Key: key},
wantErr: true,
},
{
desc: "Return an error if only the client key is of type file",
clientTLS: ClientTLS{Cert: cert, Key: "fixtures/key.pem"},
wantErr: true,
},
{
desc: "Return an error if the client cert does not exist",
clientTLS: ClientTLS{Cert: "fixtures/cert2.pem", Key: "fixtures/key.pem"},
wantErr: true,
},
{
desc: "Return an error if the client key does not exist",
clientTLS: ClientTLS{Cert: "fixtures/cert.pem", Key: "fixtures/key2.pem"},
wantErr: true,
},
}
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
tlsConfig, err := test.clientTLS.CreateTLSConfig(context.Background())
if test.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Len(t, tlsConfig.Certificates, test.wantCertLen)
assert.Equal(t, test.clientTLS.InsecureSkipVerify, tlsConfig.InsecureSkipVerify)
if test.wantCALen > 0 {
assert.Len(t, tlsConfig.RootCAs.Subjects(), test.wantCALen)
return
}
assert.Nil(t, tlsConfig.RootCAs)
})
}
}

View file

@ -29,6 +29,22 @@ THE SOFTWARE.
package types
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClientTLS) DeepCopyInto(out *ClientTLS) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientTLS.
func (in *ClientTLS) DeepCopy() *ClientTLS {
if in == nil {
return nil
}
out := new(ClientTLS)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Domain) DeepCopyInto(out *Domain) {
*out = *in

View file

@ -15,7 +15,7 @@ type server struct {
// WRRLoadBalancer is a naive RoundRobin load balancer for UDP services.
type WRRLoadBalancer struct {
servers []server
lock sync.RWMutex
lock sync.Mutex
currentWeight int
index int
}
@ -29,16 +29,16 @@ func NewWRRLoadBalancer() *WRRLoadBalancer {
// ServeUDP forwards the connection to the right service.
func (b *WRRLoadBalancer) ServeUDP(conn *Conn) {
if len(b.servers) == 0 {
log.WithoutContext().Error("no available server")
return
}
b.lock.Lock()
next, err := b.next()
b.lock.Unlock()
if err != nil {
log.WithoutContext().Errorf("Error during load balancing: %v", err)
conn.Close()
return
}
next.ServeUDP(conn)
}
@ -50,6 +50,9 @@ func (b *WRRLoadBalancer) AddServer(serverHandler Handler) {
// AddWeightedServer appends a handler to the existing list with a weight.
func (b *WRRLoadBalancer) AddWeightedServer(serverHandler Handler, weight *int) {
b.lock.Lock()
defer b.lock.Unlock()
w := 1
if weight != nil {
w = *weight
@ -87,9 +90,6 @@ func gcd(a, b int) int {
}
func (b *WRRLoadBalancer) next() (Handler, error) {
b.lock.Lock()
defer b.lock.Unlock()
if len(b.servers) == 0 {
return nil, fmt.Errorf("no servers in the pool")
}
@ -98,10 +98,14 @@ func (b *WRRLoadBalancer) next() (Handler, error) {
// but is actually very simple it calculates the GCD and subtracts it on every iteration,
// what interleaves servers and allows us not to build an iterator every time we readjust weights.
// GCD across all enabled servers
gcd := b.weightGcd()
// Maximum weight across all enabled servers
max := b.maxWeight()
if max == 0 {
return nil, fmt.Errorf("all servers have 0 weight")
}
// GCD across all enabled servers
gcd := b.weightGcd()
for {
b.index = (b.index + 1) % len(b.servers)
@ -109,9 +113,6 @@ func (b *WRRLoadBalancer) next() (Handler, error) {
b.currentWeight -= gcd
if b.currentWeight <= 0 {
b.currentWeight = max
if b.currentWeight == 0 {
return nil, fmt.Errorf("all servers have 0 weight")
}
}
}
srv := b.servers[b.index]