github.com/emate/nomad@v0.8.2-wo-binpacking/nomad/structs/config/tls.go (about)

     1  package config
     2  
     3  import (
     4  	"crypto/md5"
     5  	"crypto/tls"
     6  	"encoding/hex"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"sync"
    11  )
    12  
    13  // TLSConfig provides TLS related configuration
    14  type TLSConfig struct {
    15  
    16  	// EnableHTTP enabled TLS for http traffic to the Nomad server and clients
    17  	EnableHTTP bool `mapstructure:"http"`
    18  
    19  	// EnableRPC enables TLS for RPC and Raft traffic to the Nomad servers
    20  	EnableRPC bool `mapstructure:"rpc"`
    21  
    22  	// VerifyServerHostname is used to enable hostname verification of servers. This
    23  	// ensures that the certificate presented is valid for server.<region>.nomad
    24  	// This prevents a compromised client from being restarted as a server, and then
    25  	// intercepting request traffic as well as being added as a raft peer. This should be
    26  	// enabled by default with VerifyOutgoing, but for legacy reasons we cannot break
    27  	// existing clients.
    28  	VerifyServerHostname bool `mapstructure:"verify_server_hostname"`
    29  
    30  	// CAFile is a path to a certificate authority file. This is used with VerifyIncoming
    31  	// or VerifyOutgoing to verify the TLS connection.
    32  	CAFile string `mapstructure:"ca_file"`
    33  
    34  	// CertFile is used to provide a TLS certificate that is used for serving TLS connections.
    35  	// Must be provided to serve TLS connections.
    36  	CertFile string `mapstructure:"cert_file"`
    37  
    38  	// KeyLoader is a helper to dynamically reload TLS configuration
    39  	KeyLoader *KeyLoader
    40  
    41  	keyloaderLock sync.Mutex
    42  
    43  	// KeyFile is used to provide a TLS key that is used for serving TLS connections.
    44  	// Must be provided to serve TLS connections.
    45  	KeyFile string `mapstructure:"key_file"`
    46  
    47  	// RPCUpgradeMode should be enabled when a cluster is being upgraded
    48  	// to TLS. Allows servers to accept both plaintext and TLS connections and
    49  	// should only be a temporary state.
    50  	RPCUpgradeMode bool `mapstructure:"rpc_upgrade_mode"`
    51  
    52  	// Verify connections to the HTTPS API
    53  	VerifyHTTPSClient bool `mapstructure:"verify_https_client"`
    54  
    55  	// Checksum is a MD5 hash of the certificate CA File, Certificate file, and
    56  	// key file.
    57  	Checksum string
    58  }
    59  
    60  type KeyLoader struct {
    61  	cacheLock   sync.Mutex
    62  	certificate *tls.Certificate
    63  }
    64  
    65  // LoadKeyPair reloads the TLS certificate based on the specified certificate
    66  // and key file. If successful, stores the certificate for further use.
    67  func (k *KeyLoader) LoadKeyPair(certFile, keyFile string) (*tls.Certificate, error) {
    68  	k.cacheLock.Lock()
    69  	defer k.cacheLock.Unlock()
    70  
    71  	// Allow downgrading
    72  	if certFile == "" && keyFile == "" {
    73  		k.certificate = nil
    74  		return nil, nil
    75  	}
    76  
    77  	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
    78  	if err != nil {
    79  		return nil, fmt.Errorf("Failed to load cert/key pair: %v", err)
    80  	}
    81  
    82  	k.certificate = &cert
    83  	return k.certificate, nil
    84  }
    85  
    86  // GetOutgoingCertificate fetches the currently-loaded certificate when
    87  // accepting a TLS connection. This currently does not consider information in
    88  // the ClientHello and only returns the certificate that was last loaded.
    89  func (k *KeyLoader) GetOutgoingCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
    90  	k.cacheLock.Lock()
    91  	defer k.cacheLock.Unlock()
    92  	return k.certificate, nil
    93  }
    94  
    95  // GetClientCertificate fetches the currently-loaded certificate when the Server
    96  // requests a certificate from the caller. This currently does not consider
    97  // information in the ClientHello and only returns the certificate that was last
    98  // loaded.
    99  func (k *KeyLoader) GetClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
   100  	k.cacheLock.Lock()
   101  	defer k.cacheLock.Unlock()
   102  	return k.certificate, nil
   103  }
   104  
   105  func (k *KeyLoader) Copy() *KeyLoader {
   106  	if k == nil {
   107  		return nil
   108  	}
   109  
   110  	new := KeyLoader{}
   111  	new.certificate = k.certificate
   112  	return &new
   113  }
   114  
   115  // GetKeyLoader returns the keyloader for a TLSConfig object. If the keyloader
   116  // has not been initialized, it will first do so.
   117  func (t *TLSConfig) GetKeyLoader() *KeyLoader {
   118  	t.keyloaderLock.Lock()
   119  	defer t.keyloaderLock.Unlock()
   120  
   121  	// If the keyloader has not yet been initialized, do it here
   122  	if t.KeyLoader == nil {
   123  		t.KeyLoader = &KeyLoader{}
   124  	}
   125  	return t.KeyLoader
   126  }
   127  
   128  // Copy copies the fields of TLSConfig to another TLSConfig object. Required as
   129  // to not copy mutexes between objects.
   130  func (t *TLSConfig) Copy() *TLSConfig {
   131  	if t == nil {
   132  		return t
   133  	}
   134  
   135  	new := &TLSConfig{}
   136  	new.EnableHTTP = t.EnableHTTP
   137  	new.EnableRPC = t.EnableRPC
   138  	new.VerifyServerHostname = t.VerifyServerHostname
   139  	new.CAFile = t.CAFile
   140  	new.CertFile = t.CertFile
   141  
   142  	t.keyloaderLock.Lock()
   143  	new.KeyLoader = t.KeyLoader.Copy()
   144  	t.keyloaderLock.Unlock()
   145  
   146  	new.KeyFile = t.KeyFile
   147  	new.RPCUpgradeMode = t.RPCUpgradeMode
   148  	new.VerifyHTTPSClient = t.VerifyHTTPSClient
   149  
   150  	new.SetChecksum()
   151  
   152  	return new
   153  }
   154  
   155  func (t *TLSConfig) IsEmpty() bool {
   156  	if t == nil {
   157  		return true
   158  	}
   159  
   160  	return t.EnableHTTP == false &&
   161  		t.EnableRPC == false &&
   162  		t.VerifyServerHostname == false &&
   163  		t.CAFile == "" &&
   164  		t.CertFile == "" &&
   165  		t.KeyFile == "" &&
   166  		t.VerifyHTTPSClient == false
   167  }
   168  
   169  // Merge is used to merge two TLS configs together
   170  func (t *TLSConfig) Merge(b *TLSConfig) *TLSConfig {
   171  	result := t.Copy()
   172  
   173  	if b.EnableHTTP {
   174  		result.EnableHTTP = true
   175  	}
   176  	if b.EnableRPC {
   177  		result.EnableRPC = true
   178  	}
   179  	if b.VerifyServerHostname {
   180  		result.VerifyServerHostname = true
   181  	}
   182  	if b.CAFile != "" {
   183  		result.CAFile = b.CAFile
   184  	}
   185  	if b.CertFile != "" {
   186  		result.CertFile = b.CertFile
   187  	}
   188  	if b.KeyFile != "" {
   189  		result.KeyFile = b.KeyFile
   190  	}
   191  	if b.VerifyHTTPSClient {
   192  		result.VerifyHTTPSClient = true
   193  	}
   194  	if b.RPCUpgradeMode {
   195  		result.RPCUpgradeMode = true
   196  	}
   197  	return result
   198  }
   199  
   200  // CertificateInfoIsEqual compares the fields of two TLS configuration objects
   201  // for the fields that are specific to configuring a TLS connection
   202  // It is possible for either the calling TLSConfig to be nil, or the TLSConfig
   203  // that it is being compared against, so we need to handle both places. See
   204  // server.go Reload for example.
   205  func (t *TLSConfig) CertificateInfoIsEqual(newConfig *TLSConfig) (bool, error) {
   206  	if t == nil || newConfig == nil {
   207  		return t == newConfig, nil
   208  	}
   209  
   210  	if t.IsEmpty() && newConfig.IsEmpty() {
   211  		return true, nil
   212  	} else if t.IsEmpty() || newConfig.IsEmpty() {
   213  		return false, nil
   214  	}
   215  
   216  	// Set the checksum if it hasn't yet been set (this should happen when the
   217  	// config is parsed but this provides safety in depth)
   218  	if newConfig.Checksum == "" {
   219  		err := newConfig.SetChecksum()
   220  		if err != nil {
   221  			return false, err
   222  		}
   223  	}
   224  
   225  	if t.Checksum == "" {
   226  		err := t.SetChecksum()
   227  		if err != nil {
   228  			return false, err
   229  		}
   230  	}
   231  
   232  	return t.Checksum == newConfig.Checksum, nil
   233  }
   234  
   235  // SetChecksum generates and sets the checksum for a TLS configuration
   236  func (t *TLSConfig) SetChecksum() error {
   237  	newCertChecksum, err := createChecksumOfFiles(t.CAFile, t.CertFile, t.KeyFile)
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	t.Checksum = newCertChecksum
   243  	return nil
   244  }
   245  
   246  func getFileChecksum(filepath string) (string, error) {
   247  	f, err := os.Open(filepath)
   248  	if err != nil {
   249  		return "", err
   250  	}
   251  	defer f.Close()
   252  
   253  	h := md5.New()
   254  	if _, err := io.Copy(h, f); err != nil {
   255  		return "", err
   256  	}
   257  
   258  	return hex.EncodeToString(h.Sum(nil)), nil
   259  }
   260  
   261  func createChecksumOfFiles(inputs ...string) (string, error) {
   262  	h := md5.New()
   263  
   264  	for _, input := range inputs {
   265  		checksum, err := getFileChecksum(input)
   266  		if err != nil {
   267  			return "", err
   268  		}
   269  		io.WriteString(h, checksum)
   270  	}
   271  
   272  	return hex.EncodeToString(h.Sum(nil)), nil
   273  }