1package types
2
3import (
4	"context"
5	"crypto/tls"
6	"crypto/x509"
7	"errors"
8	"fmt"
9	"os"
10
11	"github.com/traefik/traefik/v2/pkg/log"
12)
13
14// +k8s:deepcopy-gen=true
15
16// ClientTLS holds TLS specific configurations as client
17// CA, Cert and Key can be either path or file contents.
18type ClientTLS struct {
19	CA                 string `description:"TLS CA" json:"ca,omitempty" toml:"ca,omitempty" yaml:"ca,omitempty"`
20	CAOptional         bool   `description:"TLS CA.Optional" json:"caOptional,omitempty" toml:"caOptional,omitempty" yaml:"caOptional,omitempty" export:"true"`
21	Cert               string `description:"TLS cert" json:"cert,omitempty" toml:"cert,omitempty" yaml:"cert,omitempty"`
22	Key                string `description:"TLS key" json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty" loggable:"false"`
23	InsecureSkipVerify bool   `description:"TLS insecure skip verify" json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"`
24}
25
26// CreateTLSConfig creates a TLS config from ClientTLS structures.
27func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, error) {
28	if clientTLS == nil {
29		log.FromContext(ctx).Warnf("clientTLS is nil")
30		return nil, nil
31	}
32
33	// Not initialized, to rely on system bundle.
34	var caPool *x509.CertPool
35
36	clientAuth := tls.NoClientCert
37	if clientTLS.CA != "" {
38		var ca []byte
39		if _, errCA := os.Stat(clientTLS.CA); errCA == nil {
40			var err error
41			ca, err = os.ReadFile(clientTLS.CA)
42			if err != nil {
43				return nil, fmt.Errorf("failed to read CA. %w", err)
44			}
45		} else {
46			ca = []byte(clientTLS.CA)
47		}
48
49		caPool = x509.NewCertPool()
50		if !caPool.AppendCertsFromPEM(ca) {
51			return nil, errors.New("failed to parse CA")
52		}
53
54		if clientTLS.CAOptional {
55			clientAuth = tls.VerifyClientCertIfGiven
56		} else {
57			clientAuth = tls.RequireAndVerifyClientCert
58		}
59	}
60
61	hasCert := len(clientTLS.Cert) > 0
62	hasKey := len(clientTLS.Key) > 0
63
64	if hasCert != hasKey {
65		return nil, errors.New("both TLS cert and key must be defined")
66	}
67
68	if !hasCert || !hasKey {
69		return &tls.Config{
70			RootCAs:            caPool,
71			InsecureSkipVerify: clientTLS.InsecureSkipVerify,
72			ClientAuth:         clientAuth,
73		}, nil
74	}
75
76	cert, err := loadKeyPair(clientTLS.Cert, clientTLS.Key)
77	if err != nil {
78		return nil, err
79	}
80
81	return &tls.Config{
82		Certificates:       []tls.Certificate{cert},
83		RootCAs:            caPool,
84		InsecureSkipVerify: clientTLS.InsecureSkipVerify,
85		ClientAuth:         clientAuth,
86	}, nil
87}
88
89func loadKeyPair(cert, key string) (tls.Certificate, error) {
90	keyPair, err := tls.X509KeyPair([]byte(cert), []byte(key))
91	if err == nil {
92		return keyPair, nil
93	}
94
95	_, err = os.Stat(cert)
96	if err != nil {
97		return tls.Certificate{}, errors.New("cert file does not exist")
98	}
99
100	_, err = os.Stat(key)
101	if err != nil {
102		return tls.Certificate{}, errors.New("key file does not exist")
103	}
104
105	keyPair, err = tls.LoadX509KeyPair(cert, key)
106	if err != nil {
107		return tls.Certificate{}, err
108	}
109
110	return keyPair, nil
111}
112