github.com/smithx10/nomad@v0.9.1-rc1/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 func (k *KeyLoader) GetCertificate() *tls.Certificate { 101 k.cacheLock.Lock() 102 defer k.cacheLock.Unlock() 103 return k.certificate 104 } 105 106 // GetOutgoingCertificate fetches the currently-loaded certificate when 107 // accepting a TLS connection. This currently does not consider information in 108 // the ClientHello and only returns the certificate that was last loaded. 109 func (k *KeyLoader) GetOutgoingCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) { 110 k.cacheLock.Lock() 111 defer k.cacheLock.Unlock() 112 return k.certificate, nil 113 } 114 115 // GetClientCertificate fetches the currently-loaded certificate when the Server 116 // requests a certificate from the caller. This currently does not consider 117 // information in the ClientHello and only returns the certificate that was last 118 // loaded. 119 func (k *KeyLoader) GetClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) { 120 k.cacheLock.Lock() 121 defer k.cacheLock.Unlock() 122 return k.certificate, nil 123 } 124 125 func (k *KeyLoader) Copy() *KeyLoader { 126 if k == nil { 127 return nil 128 } 129 130 new := KeyLoader{} 131 new.certificate = k.certificate 132 return &new 133 } 134 135 // GetKeyLoader returns the keyloader for a TLSConfig object. If the keyloader 136 // has not been initialized, it will first do so. 137 func (t *TLSConfig) GetKeyLoader() *KeyLoader { 138 t.keyloaderLock.Lock() 139 defer t.keyloaderLock.Unlock() 140 141 // If the keyloader has not yet been initialized, do it here 142 if t.KeyLoader == nil { 143 t.KeyLoader = &KeyLoader{} 144 } 145 return t.KeyLoader 146 } 147 148 // Copy copies the fields of TLSConfig to another TLSConfig object. Required as 149 // to not copy mutexes between objects. 150 func (t *TLSConfig) Copy() *TLSConfig { 151 if t == nil { 152 return t 153 } 154 155 new := &TLSConfig{} 156 new.EnableHTTP = t.EnableHTTP 157 new.EnableRPC = t.EnableRPC 158 new.VerifyServerHostname = t.VerifyServerHostname 159 new.CAFile = t.CAFile 160 new.CertFile = t.CertFile 161 162 t.keyloaderLock.Lock() 163 new.KeyLoader = t.KeyLoader.Copy() 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 == false && 186 t.EnableRPC == false && 187 t.VerifyServerHostname == false && 188 t.CAFile == "" && 189 t.CertFile == "" && 190 t.KeyFile == "" && 191 t.VerifyHTTPSClient == false 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 }