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