github.com/quite/nomad@v0.8.6/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 // TLSCipherSuites are operator-defined ciphers to be used in Nomad TLS 60 // connections 61 TLSCipherSuites string `mapstructure:"tls_cipher_suites"` 62 63 // TLSMinVersion is used to set the minimum TLS version used for TLS 64 // connections. Should be either "tls10", "tls11", or "tls12". 65 TLSMinVersion string `mapstructure:"tls_min_version"` 66 67 // TLSPreferServerCipherSuites controls whether the server selects the 68 // client's most preferred ciphersuite, or the server's most preferred 69 // ciphersuite. If true then the server's preference, as expressed in 70 // the order of elements in CipherSuites, is used. 71 TLSPreferServerCipherSuites bool `mapstructure:"tls_prefer_server_cipher_suites"` 72 } 73 74 type KeyLoader struct { 75 cacheLock sync.Mutex 76 certificate *tls.Certificate 77 } 78 79 // LoadKeyPair reloads the TLS certificate based on the specified certificate 80 // and key file. If successful, stores the certificate for further use. 81 func (k *KeyLoader) LoadKeyPair(certFile, keyFile string) (*tls.Certificate, error) { 82 k.cacheLock.Lock() 83 defer k.cacheLock.Unlock() 84 85 // Allow downgrading 86 if certFile == "" && keyFile == "" { 87 k.certificate = nil 88 return nil, nil 89 } 90 91 cert, err := tls.LoadX509KeyPair(certFile, keyFile) 92 if err != nil { 93 return nil, fmt.Errorf("Failed to load cert/key pair: %v", err) 94 } 95 96 k.certificate = &cert 97 return k.certificate, nil 98 } 99 100 // GetOutgoingCertificate fetches the currently-loaded certificate when 101 // accepting a TLS connection. This currently does not consider information in 102 // the ClientHello and only returns the certificate that was last loaded. 103 func (k *KeyLoader) GetOutgoingCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) { 104 k.cacheLock.Lock() 105 defer k.cacheLock.Unlock() 106 return k.certificate, nil 107 } 108 109 // GetClientCertificate fetches the currently-loaded certificate when the Server 110 // requests a certificate from the caller. This currently does not consider 111 // information in the ClientHello and only returns the certificate that was last 112 // loaded. 113 func (k *KeyLoader) GetClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) { 114 k.cacheLock.Lock() 115 defer k.cacheLock.Unlock() 116 return k.certificate, nil 117 } 118 119 func (k *KeyLoader) Copy() *KeyLoader { 120 if k == nil { 121 return nil 122 } 123 124 new := KeyLoader{} 125 new.certificate = k.certificate 126 return &new 127 } 128 129 // GetKeyLoader returns the keyloader for a TLSConfig object. If the keyloader 130 // has not been initialized, it will first do so. 131 func (t *TLSConfig) GetKeyLoader() *KeyLoader { 132 t.keyloaderLock.Lock() 133 defer t.keyloaderLock.Unlock() 134 135 // If the keyloader has not yet been initialized, do it here 136 if t.KeyLoader == nil { 137 t.KeyLoader = &KeyLoader{} 138 } 139 return t.KeyLoader 140 } 141 142 // Copy copies the fields of TLSConfig to another TLSConfig object. Required as 143 // to not copy mutexes between objects. 144 func (t *TLSConfig) Copy() *TLSConfig { 145 if t == nil { 146 return t 147 } 148 149 new := &TLSConfig{} 150 new.EnableHTTP = t.EnableHTTP 151 new.EnableRPC = t.EnableRPC 152 new.VerifyServerHostname = t.VerifyServerHostname 153 new.CAFile = t.CAFile 154 new.CertFile = t.CertFile 155 156 t.keyloaderLock.Lock() 157 new.KeyLoader = t.KeyLoader.Copy() 158 t.keyloaderLock.Unlock() 159 160 new.KeyFile = t.KeyFile 161 new.RPCUpgradeMode = t.RPCUpgradeMode 162 new.VerifyHTTPSClient = t.VerifyHTTPSClient 163 164 new.TLSCipherSuites = t.TLSCipherSuites 165 new.TLSMinVersion = t.TLSMinVersion 166 167 new.TLSPreferServerCipherSuites = t.TLSPreferServerCipherSuites 168 169 new.SetChecksum() 170 171 return new 172 } 173 174 func (t *TLSConfig) IsEmpty() bool { 175 if t == nil { 176 return true 177 } 178 179 return t.EnableHTTP == false && 180 t.EnableRPC == false && 181 t.VerifyServerHostname == false && 182 t.CAFile == "" && 183 t.CertFile == "" && 184 t.KeyFile == "" && 185 t.VerifyHTTPSClient == false 186 } 187 188 // Merge is used to merge two TLS configs together 189 func (t *TLSConfig) Merge(b *TLSConfig) *TLSConfig { 190 result := t.Copy() 191 192 if b.EnableHTTP { 193 result.EnableHTTP = true 194 } 195 if b.EnableRPC { 196 result.EnableRPC = true 197 } 198 if b.VerifyServerHostname { 199 result.VerifyServerHostname = true 200 } 201 if b.CAFile != "" { 202 result.CAFile = b.CAFile 203 } 204 if b.CertFile != "" { 205 result.CertFile = b.CertFile 206 } 207 if b.KeyFile != "" { 208 result.KeyFile = b.KeyFile 209 } 210 if b.VerifyHTTPSClient { 211 result.VerifyHTTPSClient = true 212 } 213 if b.RPCUpgradeMode { 214 result.RPCUpgradeMode = true 215 } 216 if b.TLSCipherSuites != "" { 217 result.TLSCipherSuites = b.TLSCipherSuites 218 } 219 if b.TLSMinVersion != "" { 220 result.TLSMinVersion = b.TLSMinVersion 221 } 222 if b.TLSPreferServerCipherSuites { 223 result.TLSPreferServerCipherSuites = true 224 } 225 return result 226 } 227 228 // CertificateInfoIsEqual compares the fields of two TLS configuration objects 229 // for the fields that are specific to configuring a TLS connection 230 // It is possible for either the calling TLSConfig to be nil, or the TLSConfig 231 // that it is being compared against, so we need to handle both places. See 232 // server.go Reload for example. 233 func (t *TLSConfig) CertificateInfoIsEqual(newConfig *TLSConfig) (bool, error) { 234 if t == nil || newConfig == nil { 235 return t == newConfig, nil 236 } 237 238 if t.IsEmpty() && newConfig.IsEmpty() { 239 return true, nil 240 } else if t.IsEmpty() || newConfig.IsEmpty() { 241 return false, nil 242 } 243 244 // Set the checksum if it hasn't yet been set (this should happen when the 245 // config is parsed but this provides safety in depth) 246 if newConfig.Checksum == "" { 247 err := newConfig.SetChecksum() 248 if err != nil { 249 return false, err 250 } 251 } 252 253 if t.Checksum == "" { 254 err := t.SetChecksum() 255 if err != nil { 256 return false, err 257 } 258 } 259 260 return t.Checksum == newConfig.Checksum, nil 261 } 262 263 // SetChecksum generates and sets the checksum for a TLS configuration 264 func (t *TLSConfig) SetChecksum() error { 265 newCertChecksum, err := createChecksumOfFiles(t.CAFile, t.CertFile, t.KeyFile) 266 if err != nil { 267 return err 268 } 269 270 t.Checksum = newCertChecksum 271 return nil 272 } 273 274 func getFileChecksum(filepath string) (string, error) { 275 f, err := os.Open(filepath) 276 if err != nil { 277 return "", err 278 } 279 defer f.Close() 280 281 h := md5.New() 282 if _, err := io.Copy(h, f); err != nil { 283 return "", err 284 } 285 286 return hex.EncodeToString(h.Sum(nil)), nil 287 } 288 289 func createChecksumOfFiles(inputs ...string) (string, error) { 290 h := md5.New() 291 292 for _, input := range inputs { 293 checksum, err := getFileChecksum(input) 294 if err != nil { 295 return "", err 296 } 297 io.WriteString(h, checksum) 298 } 299 300 return hex.EncodeToString(h.Sum(nil)), nil 301 }