206 lines
5.8 KiB
Go
206 lines
5.8 KiB
Go
package config
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"fmt"
|
|
"sync"
|
|
)
|
|
|
|
// TLSConfig provides TLS related configuration
|
|
type TLSConfig struct {
|
|
|
|
// EnableHTTP enabled TLS for http traffic to the Nomad server and clients
|
|
EnableHTTP bool `mapstructure:"http"`
|
|
|
|
// EnableRPC enables TLS for RPC and Raft traffic to the Nomad servers
|
|
EnableRPC bool `mapstructure:"rpc"`
|
|
|
|
// VerifyServerHostname is used to enable hostname verification of servers. This
|
|
// ensures that the certificate presented is valid for server.<region>.nomad
|
|
// This prevents a compromised client from being restarted as a server, and then
|
|
// intercepting request traffic as well as being added as a raft peer. This should be
|
|
// enabled by default with VerifyOutgoing, but for legacy reasons we cannot break
|
|
// existing clients.
|
|
VerifyServerHostname bool `mapstructure:"verify_server_hostname"`
|
|
|
|
// CAFile is a path to a certificate authority file. This is used with VerifyIncoming
|
|
// or VerifyOutgoing to verify the TLS connection.
|
|
CAFile string `mapstructure:"ca_file"`
|
|
|
|
// CertFile is used to provide a TLS certificate that is used for serving TLS connections.
|
|
// Must be provided to serve TLS connections.
|
|
CertFile string `mapstructure:"cert_file"`
|
|
|
|
// KeyLoader is a helper to dynamically reload TLS configuration
|
|
KeyLoader *KeyLoader
|
|
|
|
keyloaderLock sync.Mutex
|
|
|
|
// KeyFile is used to provide a TLS key that is used for serving TLS connections.
|
|
// Must be provided to serve TLS connections.
|
|
KeyFile string `mapstructure:"key_file"`
|
|
|
|
// RPCUpgradeMode should be enabled when a cluster is being upgraded
|
|
// to TLS. Allows servers to accept both plaintext and TLS connections and
|
|
// should only be a temporary state.
|
|
RPCUpgradeMode bool `mapstructure:"rpc_upgrade_mode"`
|
|
|
|
// Verify connections to the HTTPS API
|
|
VerifyHTTPSClient bool `mapstructure:"verify_https_client"`
|
|
}
|
|
|
|
type KeyLoader struct {
|
|
cacheLock sync.Mutex
|
|
certificate *tls.Certificate
|
|
}
|
|
|
|
// LoadKeyPair reloads the TLS certificate based on the specified certificate
|
|
// and key file. If successful, stores the certificate for further use.
|
|
func (k *KeyLoader) LoadKeyPair(certFile, keyFile string) (*tls.Certificate, error) {
|
|
k.cacheLock.Lock()
|
|
defer k.cacheLock.Unlock()
|
|
|
|
// Allow downgrading
|
|
if certFile == "" && keyFile == "" {
|
|
k.certificate = nil
|
|
return nil, nil
|
|
}
|
|
|
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to load cert/key pair: %v", err)
|
|
}
|
|
|
|
k.certificate = &cert
|
|
return k.certificate, nil
|
|
}
|
|
|
|
// GetOutgoingCertificate fetches the currently-loaded certificate when
|
|
// accepting a TLS connection. This currently does not consider information in
|
|
// the ClientHello and only returns the certificate that was last loaded.
|
|
func (k *KeyLoader) GetOutgoingCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
k.cacheLock.Lock()
|
|
defer k.cacheLock.Unlock()
|
|
return k.certificate, nil
|
|
}
|
|
|
|
// GetClientCertificate fetches the currently-loaded certificate when the Server
|
|
// requests a certificate from the caller. This currently does not consider
|
|
// information in the ClientHello and only returns the certificate that was last
|
|
// loaded.
|
|
func (k *KeyLoader) GetClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) {
|
|
k.cacheLock.Lock()
|
|
defer k.cacheLock.Unlock()
|
|
return k.certificate, nil
|
|
}
|
|
|
|
func (k *KeyLoader) Copy() *KeyLoader {
|
|
if k == nil {
|
|
return nil
|
|
}
|
|
|
|
new := KeyLoader{}
|
|
new.certificate = k.certificate
|
|
return &new
|
|
}
|
|
|
|
// GetKeyLoader returns the keyloader for a TLSConfig object. If the keyloader
|
|
// has not been initialized, it will first do so.
|
|
func (t *TLSConfig) GetKeyLoader() *KeyLoader {
|
|
t.keyloaderLock.Lock()
|
|
defer t.keyloaderLock.Unlock()
|
|
|
|
// If the keyloader has not yet been initialized, do it here
|
|
if t.KeyLoader == nil {
|
|
t.KeyLoader = &KeyLoader{}
|
|
}
|
|
return t.KeyLoader
|
|
}
|
|
|
|
// Copy copies the fields of TLSConfig to another TLSConfig object. Required as
|
|
// to not copy mutexes between objects.
|
|
func (t *TLSConfig) Copy() *TLSConfig {
|
|
if t == nil {
|
|
return t
|
|
}
|
|
|
|
new := &TLSConfig{}
|
|
new.EnableHTTP = t.EnableHTTP
|
|
new.EnableRPC = t.EnableRPC
|
|
new.VerifyServerHostname = t.VerifyServerHostname
|
|
new.CAFile = t.CAFile
|
|
new.CertFile = t.CertFile
|
|
|
|
t.keyloaderLock.Lock()
|
|
new.KeyLoader = t.KeyLoader.Copy()
|
|
t.keyloaderLock.Unlock()
|
|
|
|
new.KeyFile = t.KeyFile
|
|
new.RPCUpgradeMode = t.RPCUpgradeMode
|
|
new.VerifyHTTPSClient = t.VerifyHTTPSClient
|
|
return new
|
|
}
|
|
|
|
func (t *TLSConfig) IsEmpty() bool {
|
|
if t == nil {
|
|
return true
|
|
}
|
|
|
|
return t.EnableHTTP == false &&
|
|
t.EnableRPC == false &&
|
|
t.VerifyServerHostname == false &&
|
|
t.CAFile == "" &&
|
|
t.CertFile == "" &&
|
|
t.KeyFile == "" &&
|
|
t.VerifyHTTPSClient == false
|
|
}
|
|
|
|
// Merge is used to merge two TLS configs together
|
|
func (t *TLSConfig) Merge(b *TLSConfig) *TLSConfig {
|
|
result := t.Copy()
|
|
|
|
if b.EnableHTTP {
|
|
result.EnableHTTP = true
|
|
}
|
|
if b.EnableRPC {
|
|
result.EnableRPC = true
|
|
}
|
|
if b.VerifyServerHostname {
|
|
result.VerifyServerHostname = true
|
|
}
|
|
if b.CAFile != "" {
|
|
result.CAFile = b.CAFile
|
|
}
|
|
if b.CertFile != "" {
|
|
result.CertFile = b.CertFile
|
|
}
|
|
if b.KeyFile != "" {
|
|
result.KeyFile = b.KeyFile
|
|
}
|
|
if b.VerifyHTTPSClient {
|
|
result.VerifyHTTPSClient = true
|
|
}
|
|
if b.RPCUpgradeMode {
|
|
result.RPCUpgradeMode = true
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Equals compares the fields of two TLS configuration objects, returning a
|
|
// boolean indicating if they are the same.
|
|
// It is possible for either the calling TLSConfig to be nil, or the TLSConfig
|
|
// that it is being compared against, so we need to handle both places. See
|
|
// server.go Reload for example.
|
|
func (t *TLSConfig) Equals(newConfig *TLSConfig) bool {
|
|
if t == nil || newConfig == nil {
|
|
return t == newConfig
|
|
}
|
|
|
|
return t.EnableRPC == newConfig.EnableRPC &&
|
|
t.CAFile == newConfig.CAFile &&
|
|
t.CertFile == newConfig.CertFile &&
|
|
t.KeyFile == newConfig.KeyFile &&
|
|
t.RPCUpgradeMode == newConfig.RPCUpgradeMode &&
|
|
t.VerifyHTTPSClient == newConfig.VerifyHTTPSClient
|
|
}
|