diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml index 5129a5342..c94412d37 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml @@ -398,7 +398,7 @@ spec: info configuration. properties: issuer: - description: TLSCLientCertificateDNInfo holds the client TLS + description: TLSClientCertificateDNInfo holds the client TLS certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739 properties: commonName: @@ -425,7 +425,7 @@ spec: serialNumber: type: boolean subject: - description: TLSCLientCertificateDNInfo holds the client TLS + description: TLSClientCertificateDNInfo holds the client TLS certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739 properties: commonName: diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 44212b9f0..824cdb24d 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -840,7 +840,7 @@ spec: info configuration. properties: issuer: - description: TLSCLientCertificateDNInfo holds the client TLS + description: TLSClientCertificateDNInfo holds the client TLS certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739 properties: commonName: @@ -867,7 +867,7 @@ spec: serialNumber: type: boolean subject: - description: TLSCLientCertificateDNInfo holds the client TLS + description: TLSClientCertificateDNInfo holds the client TLS certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739 properties: commonName: diff --git a/pkg/anonymize/anonymize_config_test.go b/pkg/anonymize/anonymize_config_test.go index 941fef1b6..020d08a80 100644 --- a/pkg/anonymize/anonymize_config_test.go +++ b/pkg/anonymize/anonymize_config_test.go @@ -273,7 +273,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", @@ -315,7 +315,7 @@ func TestDo_dynamicConfiguration(t *testing.T) { NotAfter: true, NotBefore: true, Sans: true, - Subject: &dynamic.TLSCLientCertificateDNInfo{ + Subject: &dynamic.TLSClientCertificateDNInfo{ Country: true, Province: true, Locality: true, @@ -324,7 +324,7 @@ func TestDo_dynamicConfiguration(t *testing.T) { SerialNumber: true, DomainComponent: true, }, - Issuer: &dynamic.TLSCLientCertificateDNInfo{ + Issuer: &dynamic.TLSClientCertificateDNInfo{ Country: true, Province: true, Locality: true, diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index 0b3452f0a..d47ef8b22 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -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 @@ -130,12 +127,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 @@ -402,16 +399,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 *TLSCLientCertificateDNInfo `json:"subject,omitempty" toml:"subject,omitempty" yaml:"subject,omitempty" export:"true"` - Issuer *TLSCLientCertificateDNInfo `json:"issuer,omitempty" toml:"issuer,omitempty" yaml:"issuer,omitempty" export:"true"` + Subject *TLSClientCertificateDNInfo `json:"subject,omitempty" toml:"subject,omitempty" yaml:"subject,omitempty" export:"true"` + Issuer *TLSClientCertificateDNInfo `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 -// TLSCLientCertificateDNInfo holds the client TLS certificate distinguished name info configuration. +// TLSClientCertificateDNInfo holds the client TLS certificate distinguished name info configuration. // cf https://tools.ietf.org/html/rfc3739 -type TLSCLientCertificateDNInfo struct { +type TLSClientCertificateDNInfo 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"` @@ -425,83 +422,3 @@ type TLSCLientCertificateDNInfo 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 -} diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index fb21edc0f..1f39e6a1f 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -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 { @@ -1536,17 +1520,17 @@ func (in *TCPWeightedRoundRobin) DeepCopy() *TCPWeightedRoundRobin { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TLSCLientCertificateDNInfo) DeepCopyInto(out *TLSCLientCertificateDNInfo) { +func (in *TLSClientCertificateDNInfo) DeepCopyInto(out *TLSClientCertificateDNInfo) { *out = *in return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSCLientCertificateDNInfo. -func (in *TLSCLientCertificateDNInfo) DeepCopy() *TLSCLientCertificateDNInfo { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSClientCertificateDNInfo. +func (in *TLSClientCertificateDNInfo) DeepCopy() *TLSClientCertificateDNInfo { if in == nil { return nil } - out := new(TLSCLientCertificateDNInfo) + out := new(TLSClientCertificateDNInfo) in.DeepCopyInto(out) return out } @@ -1556,12 +1540,12 @@ func (in *TLSClientCertificateInfo) DeepCopyInto(out *TLSClientCertificateInfo) *out = *in if in.Subject != nil { in, out := &in.Subject, &out.Subject - *out = new(TLSCLientCertificateDNInfo) + *out = new(TLSClientCertificateDNInfo) **out = **in } if in.Issuer != nil { in, out := &in.Issuer, &out.Issuer - *out = new(TLSCLientCertificateDNInfo) + *out = new(TLSClientCertificateDNInfo) **out = **in } return diff --git a/pkg/config/label/label_test.go b/pkg/config/label/label_test.go index 3abbd57ea..1a7f459e7 100644 --- a/pkg/config/label/label_test.go +++ b/pkg/config/label/label_test.go @@ -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) { @@ -366,7 +367,7 @@ func TestDecodeConfiguration(t *testing.T) { NotAfter: true, NotBefore: true, SerialNumber: true, - Subject: &dynamic.TLSCLientCertificateDNInfo{ + Subject: &dynamic.TLSClientCertificateDNInfo{ Country: true, Province: true, Locality: true, @@ -375,7 +376,7 @@ func TestDecodeConfiguration(t *testing.T) { SerialNumber: true, DomainComponent: true, }, - Issuer: &dynamic.TLSCLientCertificateDNInfo{ + Issuer: &dynamic.TLSClientCertificateDNInfo{ Country: true, Province: true, Locality: true, @@ -501,7 +502,7 @@ func TestDecodeConfiguration(t *testing.T) { "Middleware7": { ForwardAuth: &dynamic.ForwardAuth{ Address: "foobar", - TLS: &dynamic.ClientTLS{ + TLS: &types.ClientTLS{ CA: "foobar", CAOptional: true, Cert: "foobar", @@ -844,7 +845,7 @@ func TestEncodeConfiguration(t *testing.T) { NotAfter: true, NotBefore: true, SerialNumber: true, - Subject: &dynamic.TLSCLientCertificateDNInfo{ + Subject: &dynamic.TLSClientCertificateDNInfo{ Country: true, Province: true, Locality: true, @@ -853,7 +854,7 @@ func TestEncodeConfiguration(t *testing.T) { SerialNumber: true, DomainComponent: true, }, - Issuer: &dynamic.TLSCLientCertificateDNInfo{ + Issuer: &dynamic.TLSClientCertificateDNInfo{ Country: true, Province: true, Locality: true, @@ -986,7 +987,7 @@ func TestEncodeConfiguration(t *testing.T) { "Middleware7": { ForwardAuth: &dynamic.ForwardAuth{ Address: "foobar", - TLS: &dynamic.ClientTLS{ + TLS: &types.ClientTLS{ CA: "foobar", CAOptional: true, Cert: "foobar", diff --git a/pkg/middlewares/auth/forward.go b/pkg/middlewares/auth/forward.go index df1895f09..a0cee86ce 100644 --- a/pkg/middlewares/auth/forward.go +++ b/pkg/middlewares/auth/forward.go @@ -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() diff --git a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go index 682836d56..c5f0ad4fd 100644 --- a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go +++ b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go @@ -46,7 +46,7 @@ type DistinguishedNameOptions struct { StateOrProvinceName bool } -func newDistinguishedNameOptions(info *dynamic.TLSCLientCertificateDNInfo) *DistinguishedNameOptions { +func newDistinguishedNameOptions(info *dynamic.TLSClientCertificateDNInfo) *DistinguishedNameOptions { if info == nil { return nil } diff --git a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go index 934dbff1a..e85bf81df 100644 --- a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go +++ b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go @@ -376,7 +376,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { desc: "No TLS, with subject info", config: dynamic.PassTLSClientCert{ Info: &dynamic.TLSClientCertificateInfo{ - Subject: &dynamic.TLSCLientCertificateDNInfo{ + Subject: &dynamic.TLSClientCertificateDNInfo{ CommonName: true, Organization: true, Locality: true, @@ -392,7 +392,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { config: dynamic.PassTLSClientCert{ PEM: false, Info: &dynamic.TLSClientCertificateInfo{ - Subject: &dynamic.TLSCLientCertificateDNInfo{}, + Subject: &dynamic.TLSClientCertificateDNInfo{}, }, }, }, @@ -405,7 +405,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { NotBefore: true, Sans: true, SerialNumber: true, - Subject: &dynamic.TLSCLientCertificateDNInfo{ + Subject: &dynamic.TLSClientCertificateDNInfo{ CommonName: true, Country: true, DomainComponent: true, @@ -414,7 +414,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { Province: true, SerialNumber: true, }, - Issuer: &dynamic.TLSCLientCertificateDNInfo{ + Issuer: &dynamic.TLSClientCertificateDNInfo{ CommonName: true, Country: true, DomainComponent: true, @@ -434,10 +434,10 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { Info: &dynamic.TLSClientCertificateInfo{ NotAfter: true, Sans: true, - Subject: &dynamic.TLSCLientCertificateDNInfo{ + Subject: &dynamic.TLSClientCertificateDNInfo{ Organization: true, }, - Issuer: &dynamic.TLSCLientCertificateDNInfo{ + Issuer: &dynamic.TLSClientCertificateDNInfo{ Country: true, }, }, @@ -453,7 +453,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { NotBefore: true, Sans: true, SerialNumber: true, - Subject: &dynamic.TLSCLientCertificateDNInfo{ + Subject: &dynamic.TLSClientCertificateDNInfo{ Country: true, Province: true, Locality: true, @@ -462,7 +462,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { SerialNumber: true, DomainComponent: true, }, - Issuer: &dynamic.TLSCLientCertificateDNInfo{ + Issuer: &dynamic.TLSClientCertificateDNInfo{ Country: true, Province: true, Locality: true, @@ -484,7 +484,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { NotBefore: true, Sans: true, SerialNumber: true, - Subject: &dynamic.TLSCLientCertificateDNInfo{ + Subject: &dynamic.TLSClientCertificateDNInfo{ Country: true, Province: true, Locality: true, @@ -493,7 +493,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { SerialNumber: true, DomainComponent: true, }, - Issuer: &dynamic.TLSCLientCertificateDNInfo{ + Issuer: &dynamic.TLSClientCertificateDNInfo{ Country: true, Province: true, Locality: true, diff --git a/pkg/provider/docker/docker.go b/pkg/provider/docker/docker.go index 10ba1709c..6f87cfcd8 100644 --- a/pkg/provider/docker/docker.go +++ b/pkg/provider/docker/docker.go @@ -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) diff --git a/pkg/provider/http/http.go b/pkg/provider/http/http.go index 8d1ce329e..c7caf7fdb 100644 --- a/pkg/provider/http/http.go +++ b/pkg/provider/http/http.go @@ -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{ diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 5671e586b..37c56952e 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -23,6 +23,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" @@ -481,7 +482,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, } diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index 310ae48bd..a84e2f540 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -9,13 +9,14 @@ import ( "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" @@ -3243,7 +3244,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-----", @@ -3612,17 +3613,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), }, }, }, diff --git a/pkg/provider/kv/kv.go b/pkg/provider/kv/kv.go index d62b142c5..4eee4ab63 100644 --- a/pkg/provider/kv/kv.go +++ b/pkg/provider/kv/kv.go @@ -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) } } diff --git a/pkg/provider/kv/kv_test.go b/pkg/provider/kv/kv_test.go index ef6fde11c..f1a50ef06 100644 --- a/pkg/provider/kv/kv_test.go +++ b/pkg/provider/kv/kv_test.go @@ -399,7 +399,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", @@ -478,7 +478,7 @@ func Test_buildConfiguration(t *testing.T) { NotAfter: true, NotBefore: true, Sans: true, - Subject: &dynamic.TLSCLientCertificateDNInfo{ + Subject: &dynamic.TLSClientCertificateDNInfo{ Country: true, Province: true, Locality: true, @@ -487,7 +487,7 @@ func Test_buildConfiguration(t *testing.T) { SerialNumber: true, DomainComponent: true, }, - Issuer: &dynamic.TLSCLientCertificateDNInfo{ + Issuer: &dynamic.TLSClientCertificateDNInfo{ Country: true, Province: true, Locality: true, diff --git a/pkg/provider/marathon/marathon.go b/pkg/provider/marathon/marathon.go index d0405b6db..c78413ad7 100644 --- a/pkg/provider/marathon/marathon.go +++ b/pkg/provider/marathon/marathon.go @@ -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{ diff --git a/pkg/types/fixtures/cert.pem b/pkg/types/fixtures/cert.pem new file mode 100644 index 000000000..b0261ccd6 --- /dev/null +++ b/pkg/types/fixtures/cert.pem @@ -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----- diff --git a/pkg/types/fixtures/key.pem b/pkg/types/fixtures/key.pem new file mode 100644 index 000000000..fd1d7e728 --- /dev/null +++ b/pkg/types/fixtures/key.pem @@ -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----- diff --git a/pkg/types/tls.go b/pkg/types/tls.go index 8760a7217..3867d8097 100644 --- a/pkg/types/tls.go +++ b/pkg/types/tls.go @@ -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 { @@ -42,7 +45,7 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e } 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 +55,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 +82,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 +} diff --git a/pkg/types/tls_test.go b/pkg/types/tls_test.go new file mode 100644 index 000000000..dbc1e6334 --- /dev/null +++ b/pkg/types/tls_test.go @@ -0,0 +1,123 @@ +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.RootCAs.Subjects(), test.wantCALen) + assert.Len(t, tlsConfig.Certificates, test.wantCertLen) + assert.Equal(t, test.clientTLS.InsecureSkipVerify, tlsConfig.InsecureSkipVerify) + }) + } +} diff --git a/pkg/types/zz_generated.deepcopy.go b/pkg/types/zz_generated.deepcopy.go index 06daf6fa7..91c7716b3 100644 --- a/pkg/types/zz_generated.deepcopy.go +++ b/pkg/types/zz_generated.deepcopy.go @@ -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