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