@@ -7,6 +7,10 @@ import (
77 "errors"
88 "fmt"
99 "os"
10+
11+ "github.com/rs/zerolog"
12+
13+ "github.com/rs/zerolog/log"
1014)
1115
1216// TLSConfig is a common configuration for TLS.
@@ -15,52 +19,56 @@ import (
1519// 2. Base64 encoded PEM
1620// 3. Raw PEM
1721// It's up to the user to only use a single source of configured values. I.e. if both file and raw PEM are set
18- // the file will be used and raw PEM will be just ignored.
22+ // the file will be used and raw PEM will be just ignored. For certificate and key it's required to use the same
23+ // source type - whether set both from file, both from base64 or both from raw string.
1924type TLSConfig struct {
2025 // Enabled turns on using TLS.
2126 Enabled bool `mapstructure:"enabled" json:"enabled" yaml:"enabled" toml:"enabled" envconfig:"enabled"`
2227
23- // CertPem is a certificate in PEM format.
24- CertPem string `mapstructure:"cert_pem" json:"cert_pem" envconfig:"cert_pem" yaml:"cert_pem" toml:"cert_pem"`
25- // CertPemB64 is a certificate in base64 encoded PEM format.
26- CertPemB64 string `mapstructure:"cert_pem_b64" json:"cert_pem_b64" envconfig:"cert_pem_b64" yaml:"cert_pem_b64" toml:"cert_pem_b64"`
2728 // CertPemFile is a path to a file with certificate in PEM format.
2829 CertPemFile string `mapstructure:"cert_pem_file" json:"cert_pem_file" envconfig:"cert_pem_file" yaml:"cert_pem_file" toml:"cert_pem_file"`
30+ // KeyPemFile is a path to a file with key in PEM format.
31+ KeyPemFile string `mapstructure:"key_pem_file" json:"key_pem_file" envconfig:"key_pem_file" yaml:"key_pem_file" toml:"key_pem_file"`
2932
30- // KeyPem is a key in PEM format.
31- KeyPem string `mapstructure:"key_pem " json:"key_pem " envconfig:"key_pem " yaml:"key_pem " toml:"key_pem "`
33+ // CertPemB64 is a certificate in base64 encoded PEM format.
34+ CertPemB64 string `mapstructure:"cert_pem_b64 " json:"cert_pem_b64 " envconfig:"cert_pem_b64 " yaml:"cert_pem_b64 " toml:"cert_pem_b64 "`
3235 // KeyPemB64 is a key in base64 encoded PEM format.
3336 KeyPemB64 string `mapstructure:"key_pem_b64" json:"key_pem_b64" envconfig:"key_pem_b64" yaml:"key_pem_b64" toml:"key_pem_b64"`
34- // KeyPemFile is a path to a file with key in PEM format.
35- KeyPemFile string `mapstructure:"key_pem_file" json:"key_pem_file" envconfig:"key_pem_file" yaml:"key_pem_file" toml:"key_pem_file"`
3637
37- // ServerCAPem is a server root CA certificate in PEM format.
38+ // CertPem is a certificate in PEM format.
39+ CertPem string `mapstructure:"cert_pem" json:"cert_pem" envconfig:"cert_pem" yaml:"cert_pem" toml:"cert_pem"`
40+ // KeyPem is a key in PEM format.
41+ KeyPem string `mapstructure:"key_pem" json:"key_pem" envconfig:"key_pem" yaml:"key_pem" toml:"key_pem"`
42+
43+ // ServerCAPemFile is a path to a file with server root CA certificate in PEM format.
3844 // The client uses this certificate to verify the server's certificate during the TLS handshake.
39- ServerCAPem string `mapstructure:"server_ca_pem " json:"server_ca_pem " envconfig:"server_ca_pem " yaml:"server_ca_pem " toml:"server_ca_pem "`
45+ ServerCAPemFile string `mapstructure:"server_ca_pem_file " json:"server_ca_pem_file " envconfig:"server_ca_pem_file " yaml:"server_ca_pem_file " toml:"server_ca_pem_file "`
4046 // ServerCAPemB64 is a server root CA certificate in base64 encoded PEM format.
4147 ServerCAPemB64 string `mapstructure:"server_ca_pem_b64" json:"server_ca_pem_b64" envconfig:"server_ca_pem_b64" yaml:"server_ca_pem_b64" toml:"server_ca_pem_b64"`
42- // ServerCAPemFile is a path to a file with server root CA certificate in PEM format.
43- ServerCAPemFile string `mapstructure:"server_ca_pem_file " json:"server_ca_pem_file " envconfig:"server_ca_pem_file " yaml:"server_ca_pem_file " toml:"server_ca_pem_file "`
48+ // ServerCAPem is a server root CA certificate in PEM format.
49+ ServerCAPem string `mapstructure:"server_ca_pem " json:"server_ca_pem " envconfig:"server_ca_pem " yaml:"server_ca_pem " toml:"server_ca_pem "`
4450
45- // ClientCAPem is a client CA certificate in PEM format.
51+ // ClientCAPemFile is a path to a file with client CA certificate in PEM format.
4652 // The server uses this certificate to verify the client's certificate during the TLS handshake.
47- ClientCAPem string `mapstructure:"client_ca_pem " json:"client_ca_pem " envconfig:"client_ca_pem " yaml:"client_ca_pem " toml:"client_ca_pem "`
53+ ClientCAPemFile string `mapstructure:"client_ca_pem_file " json:"client_ca_pem_file " envconfig:"client_ca_pem_file " yaml:"client_ca_pem_file " toml:"client_ca_pem_file "`
4854 // ClientCAPemB64 is a client CA certificate in base64 encoded PEM format.
4955 ClientCAPemB64 string `mapstructure:"client_ca_pem_b64" json:"client_ca_pem_b64" envconfig:"client_ca_pem_b64" yaml:"client_ca_pem_b64" toml:"client_ca_pem_b64"`
50- // ClientCAPemFile is a path to a file with client CA certificate in PEM format.
51- ClientCAPemFile string `mapstructure:"client_ca_pem_file " json:"client_ca_pem_file " envconfig:"client_ca_pem_file " yaml:"client_ca_pem_file " toml:"client_ca_pem_file "`
56+ // ClientCAPem is a client CA certificate in PEM format.
57+ ClientCAPem string `mapstructure:"client_ca_pem " json:"client_ca_pem " envconfig:"client_ca_pem " yaml:"client_ca_pem " toml:"client_ca_pem "`
5258
5359 // InsecureSkipVerify turns off server certificate verification.
5460 InsecureSkipVerify bool `mapstructure:"insecure_skip_verify" json:"insecure_skip_verify" envconfig:"insecure_skip_verify" yaml:"insecure_skip_verify" toml:"insecure_skip_verify"`
5561 // ServerName is used to verify the hostname on the returned certificates.
5662 ServerName string `mapstructure:"server_name" json:"server_name" envconfig:"server_name" yaml:"server_name" toml:"server_name"`
5763}
5864
59- func (c TLSConfig ) ToGoTLSConfig () (* tls.Config , error ) {
65+ func (c TLSConfig ) ToGoTLSConfig (logTraceEntity string ) (* tls.Config , error ) {
6066 if ! c .Enabled {
6167 return nil , nil
6268 }
63- return makeTLSConfig (c , os .ReadFile )
69+ logger := log .With ().Str ("entity" , logTraceEntity ).Logger ()
70+ logger .Trace ().Msg ("TLS enabled" )
71+ return makeTLSConfig (c , logger , os .ReadFile )
6472}
6573
6674// ReadFileFunc is an abstraction for os.ReadFile but also io/fs.ReadFile
@@ -71,108 +79,129 @@ func (c TLSConfig) ToGoTLSConfig() (*tls.Config, error) {
7179type ReadFileFunc func (name string ) ([]byte , error )
7280
7381// makeTLSConfig constructs a tls.Config instance using the given configuration.
74- func makeTLSConfig (cfg TLSConfig , readFile ReadFileFunc ) (* tls.Config , error ) {
82+ func makeTLSConfig (cfg TLSConfig , logger zerolog. Logger , readFile ReadFileFunc ) (* tls.Config , error ) {
7583 tlsConfig := & tls.Config {}
84+ if err := loadCertificate (cfg , logger , tlsConfig , readFile ); err != nil {
85+ return nil , fmt .Errorf ("error load certificate: %w" , err )
86+ }
87+ if err := loadServerCA (cfg , logger , tlsConfig , readFile ); err != nil {
88+ return nil , fmt .Errorf ("error load server CA: %w" , err )
89+ }
90+ if err := loadClientCA (cfg , logger , tlsConfig , readFile ); err != nil {
91+ return nil , fmt .Errorf ("error load client CA: %w" , err )
92+ }
93+ tlsConfig .ServerName = cfg .ServerName
94+ tlsConfig .InsecureSkipVerify = cfg .InsecureSkipVerify
95+ logger .Trace ().Str ("server_name" , cfg .ServerName ).Bool ("insecure_skip_verify" , cfg .InsecureSkipVerify ).Msg ("TLS config options set" )
96+ logger .Trace ().Msg ("TLS config created" )
97+ return tlsConfig , nil
98+ }
7699
77- if cfg .CertPemFile != "" && cfg .KeyPemFile != "" {
78- certPEMBlock , err := readFile (cfg .CertPemFile )
79- if err != nil {
80- return nil , fmt .Errorf ("read TLS certificate for %s: %w" , cfg .CertPemFile , err )
81- }
82- keyPEMBlock , err := readFile (cfg .KeyPemFile )
83- if err != nil {
84- return nil , fmt .Errorf ("read TLS key for %s: %w" , cfg .KeyPemFile , err )
85- }
86- cert , err := tls .X509KeyPair (certPEMBlock , keyPEMBlock )
100+ // loadCertificate loads the TLS certificate from various sources.
101+ func loadCertificate (cfg TLSConfig , logger zerolog.Logger , tlsConfig * tls.Config , readFile ReadFileFunc ) error {
102+ var certPEMBlock , keyPEMBlock []byte
103+ var err error
104+
105+ switch {
106+ case cfg .CertPemFile != "" && cfg .KeyPemFile != "" :
107+ logger .Trace ().Str ("cert_pem_file" , cfg .CertPemFile ).Str ("key_pem_file" , cfg .KeyPemFile ).Msg ("load TLS certificate and key from files" )
108+ certPEMBlock , err = readFile (cfg .CertPemFile )
87109 if err != nil {
88- return nil , fmt .Errorf ("parse certificate/key pair for %s/%s : %w" , cfg .CertPemFile , cfg . KeyPemFile , err )
110+ return fmt .Errorf ("read TLS certificate for %s: %w" , cfg .CertPemFile , err )
89111 }
90- tlsConfig .Certificates = []tls.Certificate {cert }
91- } else if cfg .CertPemB64 != "" && cfg .KeyPemB64 != "" {
92- certPem , err := base64 .StdEncoding .DecodeString (cfg .CertPemB64 )
112+ keyPEMBlock , err = readFile (cfg .KeyPemFile )
93113 if err != nil {
94- return nil , fmt .Errorf ("error base64 decode certificate PEM : %w" , err )
114+ return fmt .Errorf ("read TLS key for %s : %w" , cfg . KeyPemFile , err )
95115 }
96- keyPem , err := base64 .StdEncoding .DecodeString (cfg .KeyPemB64 )
116+ case cfg .CertPemB64 != "" && cfg .KeyPemB64 != "" :
117+ logger .Trace ().Msg ("load TLS certificate and key from base64 encoded strings" )
118+ certPEMBlock , err = base64 .StdEncoding .DecodeString (cfg .CertPemB64 )
97119 if err != nil {
98- return nil , fmt .Errorf ("error base64 decode key PEM: %w" , err )
120+ return fmt .Errorf ("error base64 decode certificate PEM: %w" , err )
99121 }
100- cert , err := tls . X509KeyPair ( certPem , keyPem )
122+ keyPEMBlock , err = base64 . StdEncoding . DecodeString ( cfg . KeyPemB64 )
101123 if err != nil {
102- return nil , fmt .Errorf ("error parse certificate/ key pair : %w" , err )
124+ return fmt .Errorf ("error base64 decode key PEM : %w" , err )
103125 }
104- tlsConfig .Certificates = []tls.Certificate {cert }
105- } else if cfg .CertPem != "" && cfg .KeyPem != "" {
106- cert , err := tls .X509KeyPair ([]byte (cfg .CertPem ), []byte (cfg .KeyPem ))
126+ case cfg .CertPem != "" && cfg .KeyPem != "" :
127+ logger .Trace ().Msg ("load TLS certificate and key from raw strings" )
128+ certPEMBlock , keyPEMBlock = []byte (cfg .CertPem ), []byte (cfg .KeyPem )
129+ default :
130+ }
131+
132+ if len (certPEMBlock ) > 0 && len (keyPEMBlock ) > 0 {
133+ logger .Trace ().Msg ("create x509 key pair" )
134+ cert , err := tls .X509KeyPair (certPEMBlock , keyPEMBlock )
107135 if err != nil {
108- return nil , fmt .Errorf ("error parse certificate/ key pair: %w" , err )
136+ return fmt .Errorf ("error create x509 key pair: %w" , err )
109137 }
110138 tlsConfig .Certificates = []tls.Certificate {cert }
139+ } else {
140+ logger .Trace ().Msg ("no cert or key provided, skip loading x509 key pair" )
111141 }
142+ return nil
143+ }
112144
113- if cfg .ServerCAPemFile != "" {
114- caCert , err := readFile (cfg .ServerCAPemFile )
115- if err != nil {
116- return nil , fmt .Errorf ("read the root CA certificate for %s: %w" , cfg .ServerCAPemFile , err )
117- }
118- caCertPool , err := newCertPoolFromPEM (caCert )
119- if err != nil {
120- return nil , fmt .Errorf ("error parse root CA certificate: %w" , err )
121- }
122- tlsConfig .RootCAs = caCertPool
123- } else if cfg .ServerCAPemB64 != "" {
124- caCert , err := base64 .StdEncoding .DecodeString (cfg .ServerCAPemB64 )
125- if err != nil {
126- return nil , fmt .Errorf ("error base64 decode root CA PEM: %w" , err )
127- }
145+ // loadServerCA loads the root CA from various sources.
146+ func loadServerCA (cfg TLSConfig , logger zerolog.Logger , tlsConfig * tls.Config , readFile ReadFileFunc ) error {
147+ caCert , err := loadPEMBlock (cfg .ServerCAPemFile , cfg .ServerCAPemB64 , cfg .ServerCAPem , logger , "server CA" , readFile )
148+ if err != nil {
149+ return fmt .Errorf ("error load server CA certificate: %w" , err )
150+ }
151+ if len (caCert ) > 0 {
152+ logger .Trace ().Msg ("load server CA certificate" )
128153 caCertPool , err := newCertPoolFromPEM (caCert )
129154 if err != nil {
130- return nil , fmt .Errorf ("error parse root CA certificate: %w" , err )
131- }
132- tlsConfig .RootCAs = caCertPool
133- } else if cfg .ServerCAPem != "" {
134- caCertPool , err := newCertPoolFromPEM ([]byte (cfg .ServerCAPem ))
135- if err != nil {
136- return nil , fmt .Errorf ("error parse root CA certificate: %w" , err )
155+ return fmt .Errorf ("error create server CA certificate pool: %w" , err )
137156 }
138157 tlsConfig .RootCAs = caCertPool
158+ } else {
159+ logger .Trace ().Msg ("no server CA certificate provided" )
139160 }
161+ return nil
162+ }
140163
141- if cfg .ClientCAPemFile != "" {
142- caCert , err := readFile (cfg .ClientCAPemFile )
143- if err != nil {
144- return nil , fmt .Errorf ("read the client CA certificate for %s: %w" , cfg .ClientCAPemFile , err )
145- }
164+ // loadClientCA loads the client CA from various sources.
165+ func loadClientCA (cfg TLSConfig , logger zerolog.Logger , tlsConfig * tls.Config , readFile ReadFileFunc ) error {
166+ caCert , err := loadPEMBlock (cfg .ClientCAPemFile , cfg .ClientCAPemB64 , cfg .ClientCAPem , logger , "client CA" , readFile )
167+ if err != nil {
168+ return err
169+ }
170+ if len (caCert ) > 0 {
171+ logger .Trace ().Msg ("load client CA certificate" )
146172 caCertPool , err := newCertPoolFromPEM (caCert )
147173 if err != nil {
148- return nil , fmt .Errorf ("error parse client CA certificate: %w" , err )
174+ return fmt .Errorf ("error create client CA certificate pool : %w" , err )
149175 }
150176 tlsConfig .ClientCAs = caCertPool
151177 tlsConfig .ClientAuth = tls .RequireAndVerifyClientCert
152- } else if cfg .ClientCAPemB64 != "" {
153- caCert , err := base64 .StdEncoding .DecodeString (cfg .ClientCAPemB64 )
154- if err != nil {
155- return nil , fmt .Errorf ("error base64 decode client CA PEM: %w" , err )
156- }
157- caCertPool , err := newCertPoolFromPEM (caCert )
178+ } else {
179+ logger .Trace ().Msg ("no client CA certificate provided" )
180+ }
181+ return nil
182+ }
183+
184+ // loadPEMBlock attempts to load PEM data from a file, base64 string, or raw string.
185+ func loadPEMBlock (file , b64 , raw string , logger zerolog.Logger , certType string , readFile ReadFileFunc ) ([]byte , error ) {
186+ var pemBlock []byte
187+ var err error
188+ if file != "" {
189+ logger .Trace ().Str ("file" , file ).Msg ("load PEM block of " + certType + " from file" )
190+ pemBlock , err = readFile (file )
158191 if err != nil {
159- return nil , fmt .Errorf ("error parse client CA certificate : %w" , err )
192+ return nil , fmt .Errorf ("read PEM block for %s : %w" , file , err )
160193 }
161- tlsConfig .ClientCAs = caCertPool
162- tlsConfig .ClientAuth = tls .RequireAndVerifyClientCert
163- } else if cfg .ClientCAPem != "" {
164- caCertPool , err := newCertPoolFromPEM ([]byte (cfg .ClientCAPem ))
194+ } else if b64 != "" {
195+ logger .Trace ().Msg ("load PEM block of " + certType + " from base64 encoded string" )
196+ pemBlock , err = base64 .StdEncoding .DecodeString (b64 )
165197 if err != nil {
166- return nil , fmt .Errorf ("error parse client CA certificate : %w" , err )
198+ return nil , fmt .Errorf ("error base64 decode PEM block : %w" , err )
167199 }
168- tlsConfig .ClientCAs = caCertPool
169- tlsConfig .ClientAuth = tls .RequireAndVerifyClientCert
200+ } else if raw != "" {
201+ logger .Trace ().Msg ("load PEM block of " + certType + " from raw string" )
202+ pemBlock = []byte (raw )
170203 }
171-
172- tlsConfig .ServerName = cfg .ServerName
173- tlsConfig .InsecureSkipVerify = cfg .InsecureSkipVerify
174-
175- return tlsConfig , nil
204+ return pemBlock , nil
176205}
177206
178207// newCertPoolFromPEM returns certificate pool for the given PEM-encoded
0 commit comments