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