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