github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/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 func (k *KeyLoader) Copy() *KeyLoader { 129 if k == nil { 130 return nil 131 } 132 133 new := KeyLoader{} 134 new.certificate = k.certificate 135 return &new 136 } 137 138 // GetKeyLoader returns the keyloader for a TLSConfig object. If the keyloader 139 // has not been initialized, it will first do so. 140 func (t *TLSConfig) GetKeyLoader() *KeyLoader { 141 t.keyloaderLock.Lock() 142 defer t.keyloaderLock.Unlock() 143 144 // If the keyloader has not yet been initialized, do it here 145 if t.KeyLoader == nil { 146 t.KeyLoader = &KeyLoader{} 147 } 148 return t.KeyLoader 149 } 150 151 // Copy copies the fields of TLSConfig to another TLSConfig object. Required as 152 // to not copy mutexes between objects. 153 func (t *TLSConfig) Copy() *TLSConfig { 154 if t == nil { 155 return t 156 } 157 158 new := &TLSConfig{} 159 new.EnableHTTP = t.EnableHTTP 160 new.EnableRPC = t.EnableRPC 161 new.VerifyServerHostname = t.VerifyServerHostname 162 new.CAFile = t.CAFile 163 new.CertFile = t.CertFile 164 165 t.keyloaderLock.Lock() 166 new.KeyLoader = t.KeyLoader.Copy() 167 t.keyloaderLock.Unlock() 168 169 new.KeyFile = t.KeyFile 170 new.RPCUpgradeMode = t.RPCUpgradeMode 171 new.VerifyHTTPSClient = t.VerifyHTTPSClient 172 173 new.TLSCipherSuites = t.TLSCipherSuites 174 new.TLSMinVersion = t.TLSMinVersion 175 176 new.TLSPreferServerCipherSuites = t.TLSPreferServerCipherSuites 177 178 new.SetChecksum() 179 180 return new 181 } 182 183 func (t *TLSConfig) IsEmpty() bool { 184 if t == nil { 185 return true 186 } 187 188 return t.EnableHTTP == false && 189 t.EnableRPC == false && 190 t.VerifyServerHostname == false && 191 t.CAFile == "" && 192 t.CertFile == "" && 193 t.KeyFile == "" && 194 t.VerifyHTTPSClient == false 195 } 196 197 // Merge is used to merge two TLS configs together 198 func (t *TLSConfig) Merge(b *TLSConfig) *TLSConfig { 199 result := t.Copy() 200 201 if b.EnableHTTP { 202 result.EnableHTTP = true 203 } 204 if b.EnableRPC { 205 result.EnableRPC = true 206 } 207 if b.VerifyServerHostname { 208 result.VerifyServerHostname = true 209 } 210 if b.CAFile != "" { 211 result.CAFile = b.CAFile 212 } 213 if b.CertFile != "" { 214 result.CertFile = b.CertFile 215 } 216 if b.KeyFile != "" { 217 result.KeyFile = b.KeyFile 218 } 219 if b.VerifyHTTPSClient { 220 result.VerifyHTTPSClient = true 221 } 222 if b.RPCUpgradeMode { 223 result.RPCUpgradeMode = true 224 } 225 if b.TLSCipherSuites != "" { 226 result.TLSCipherSuites = b.TLSCipherSuites 227 } 228 if b.TLSMinVersion != "" { 229 result.TLSMinVersion = b.TLSMinVersion 230 } 231 if b.TLSPreferServerCipherSuites { 232 result.TLSPreferServerCipherSuites = true 233 } 234 return result 235 } 236 237 // CertificateInfoIsEqual compares the fields of two TLS configuration objects 238 // for the fields that are specific to configuring a TLS connection 239 // It is possible for either the calling TLSConfig to be nil, or the TLSConfig 240 // that it is being compared against, so we need to handle both places. See 241 // server.go Reload for example. 242 func (t *TLSConfig) CertificateInfoIsEqual(newConfig *TLSConfig) (bool, error) { 243 if t == nil || newConfig == nil { 244 return t == newConfig, nil 245 } 246 247 if t.IsEmpty() && newConfig.IsEmpty() { 248 return true, nil 249 } else if t.IsEmpty() || newConfig.IsEmpty() { 250 return false, nil 251 } 252 253 // Set the checksum if it hasn't yet been set (this should happen when the 254 // config is parsed but this provides safety in depth) 255 if newConfig.Checksum == "" { 256 err := newConfig.SetChecksum() 257 if err != nil { 258 return false, err 259 } 260 } 261 262 if t.Checksum == "" { 263 err := t.SetChecksum() 264 if err != nil { 265 return false, err 266 } 267 } 268 269 return t.Checksum == newConfig.Checksum, nil 270 } 271 272 // SetChecksum generates and sets the checksum for a TLS configuration 273 func (t *TLSConfig) SetChecksum() error { 274 newCertChecksum, err := createChecksumOfFiles(t.CAFile, t.CertFile, t.KeyFile) 275 if err != nil { 276 return err 277 } 278 279 t.Checksum = newCertChecksum 280 return nil 281 } 282 283 func getFileChecksum(filepath string) (string, error) { 284 f, err := os.Open(filepath) 285 if err != nil { 286 return "", err 287 } 288 defer f.Close() 289 290 h := md5.New() 291 if _, err := io.Copy(h, f); err != nil { 292 return "", err 293 } 294 295 return hex.EncodeToString(h.Sum(nil)), nil 296 } 297 298 func createChecksumOfFiles(inputs ...string) (string, error) { 299 h := md5.New() 300 301 for _, input := range inputs { 302 checksum, err := getFileChecksum(input) 303 if err != nil { 304 return "", err 305 } 306 io.WriteString(h, checksum) 307 } 308 309 return hex.EncodeToString(h.Sum(nil)), nil 310 }