github.com/thanos-io/thanos@v0.32.5/pkg/exthttp/tlsconfig.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package exthttp
     5  
     6  import (
     7  	"crypto/tls"
     8  	"crypto/x509"
     9  	"fmt"
    10  	"os"
    11  )
    12  
    13  // TLSConfig configures the options for TLS connections.
    14  type TLSConfig struct {
    15  	// The CA cert to use for the targets.
    16  	CAFile string `yaml:"ca_file"`
    17  	// The client cert file for the targets.
    18  	CertFile string `yaml:"cert_file"`
    19  	// The client key file for the targets.
    20  	KeyFile string `yaml:"key_file"`
    21  	// Used to verify the hostname for the targets.
    22  	ServerName string `yaml:"server_name"`
    23  	// Disable target certificate validation.
    24  	InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
    25  }
    26  
    27  // NewTLSConfig creates a new tls.Config from the given TLSConfig.
    28  func NewTLSConfig(cfg *TLSConfig) (*tls.Config, error) {
    29  	tlsConfig := &tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify}
    30  
    31  	// If a CA cert is provided then let's read it in.
    32  	if len(cfg.CAFile) > 0 {
    33  		b, err := readCAFile(cfg.CAFile)
    34  		if err != nil {
    35  			return nil, err
    36  		}
    37  		if !updateRootCA(tlsConfig, b) {
    38  			return nil, fmt.Errorf("unable to use specified CA cert %s", cfg.CAFile)
    39  		}
    40  	}
    41  
    42  	if len(cfg.ServerName) > 0 {
    43  		tlsConfig.ServerName = cfg.ServerName
    44  	}
    45  	// If a client cert & key is provided then configure TLS config accordingly.
    46  	if len(cfg.CertFile) > 0 && len(cfg.KeyFile) == 0 {
    47  		return nil, fmt.Errorf("client cert file %q specified without client key file", cfg.CertFile)
    48  	} else if len(cfg.KeyFile) > 0 && len(cfg.CertFile) == 0 {
    49  		return nil, fmt.Errorf("client key file %q specified without client cert file", cfg.KeyFile)
    50  	} else if len(cfg.CertFile) > 0 && len(cfg.KeyFile) > 0 {
    51  		// Verify that client cert and key are valid.
    52  		if _, err := cfg.getClientCertificate(nil); err != nil {
    53  			return nil, err
    54  		}
    55  		tlsConfig.GetClientCertificate = cfg.getClientCertificate
    56  	}
    57  
    58  	return tlsConfig, nil
    59  }
    60  
    61  // readCAFile reads the CA cert file from disk.
    62  func readCAFile(f string) ([]byte, error) {
    63  	data, err := os.ReadFile(f)
    64  	if err != nil {
    65  		return nil, fmt.Errorf("unable to load specified CA cert %s: %s", f, err)
    66  	}
    67  	return data, nil
    68  }
    69  
    70  // updateRootCA parses the given byte slice as a series of PEM encoded certificates and updates tls.Config.RootCAs.
    71  func updateRootCA(cfg *tls.Config, b []byte) bool {
    72  	caCertPool := x509.NewCertPool()
    73  	if !caCertPool.AppendCertsFromPEM(b) {
    74  		return false
    75  	}
    76  	cfg.RootCAs = caCertPool
    77  	return true
    78  }
    79  
    80  // getClientCertificate reads the pair of client cert and key from disk and returns a tls.Certificate.
    81  func (c *TLSConfig) getClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
    82  	cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile)
    83  	if err != nil {
    84  		return nil, fmt.Errorf("unable to use specified client cert (%s) & key (%s): %s", c.CertFile, c.KeyFile, err)
    85  	}
    86  	return &cert, nil
    87  }