github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/helper/tlsutil/config.go (about) 1 package tlsutil 2 3 import ( 4 "crypto/tls" 5 "crypto/x509" 6 "fmt" 7 "io/ioutil" 8 "net" 9 "time" 10 11 "github.com/hashicorp/nomad/nomad/structs/config" 12 ) 13 14 // RegionSpecificWrapper is used to invoke a static Region and turns a 15 // RegionWrapper into a Wrapper type. 16 func RegionSpecificWrapper(region string, tlsWrap RegionWrapper) Wrapper { 17 if tlsWrap == nil { 18 return nil 19 } 20 return func(conn net.Conn) (net.Conn, error) { 21 return tlsWrap(region, conn) 22 } 23 } 24 25 // RegionWrapper is a function that is used to wrap a non-TLS connection and 26 // returns an appropriate TLS connection or error. This takes a Region as an 27 // argument. 28 type RegionWrapper func(region string, conn net.Conn) (net.Conn, error) 29 30 // Wrapper wraps a connection and enables TLS on it. 31 type Wrapper func(conn net.Conn) (net.Conn, error) 32 33 // Config used to create tls.Config 34 type Config struct { 35 // VerifyIncoming is used to verify the authenticity of incoming connections. 36 // This means that TCP requests are forbidden, only allowing for TLS. TLS connections 37 // must match a provided certificate authority. This can be used to force client auth. 38 VerifyIncoming bool 39 40 // VerifyOutgoing is used to verify the authenticity of outgoing connections. 41 // This means that TLS requests are used, and TCP requests are not made. TLS connections 42 // must match a provided certificate authority. This is used to verify authenticity of 43 // server nodes. 44 VerifyOutgoing bool 45 46 // VerifyServerHostname is used to enable hostname verification of servers. This 47 // ensures that the certificate presented is valid for server.<datacenter>.<domain>. 48 // This prevents a compromised client from being restarted as a server, and then 49 // intercepting request traffic as well as being added as a raft peer. This should be 50 // enabled by default with VerifyOutgoing, but for legacy reasons we cannot break 51 // existing clients. 52 VerifyServerHostname bool 53 54 // CAFile is a path to a certificate authority file. This is used with VerifyIncoming 55 // or VerifyOutgoing to verify the TLS connection. 56 CAFile string 57 58 // CertFile is used to provide a TLS certificate that is used for serving TLS connections. 59 // Must be provided to serve TLS connections. 60 CertFile string 61 62 // KeyFile is used to provide a TLS key that is used for serving TLS connections. 63 // Must be provided to serve TLS connections. 64 KeyFile string 65 66 // KeyLoader dynamically reloads TLS configuration. 67 KeyLoader *config.KeyLoader 68 } 69 70 // AppendCA opens and parses the CA file and adds the certificates to 71 // the provided CertPool. 72 func (c *Config) AppendCA(pool *x509.CertPool) error { 73 if c.CAFile == "" { 74 return nil 75 } 76 77 // Read the file 78 data, err := ioutil.ReadFile(c.CAFile) 79 if err != nil { 80 return fmt.Errorf("Failed to read CA file: %v", err) 81 } 82 83 if !pool.AppendCertsFromPEM(data) { 84 return fmt.Errorf("Failed to parse any CA certificates") 85 } 86 87 return nil 88 } 89 90 // LoadKeyPair is used to open and parse a certificate and key file 91 func (c *Config) LoadKeyPair() (*tls.Certificate, error) { 92 if c.CertFile == "" || c.KeyFile == "" { 93 return nil, nil 94 } 95 96 if c.KeyLoader == nil { 97 return nil, fmt.Errorf("No Keyloader object to perform LoadKeyPair") 98 } 99 100 cert, err := c.KeyLoader.LoadKeyPair(c.CertFile, c.KeyFile) 101 if err != nil { 102 return nil, fmt.Errorf("Failed to load cert/key pair: %v", err) 103 } 104 return cert, err 105 } 106 107 // OutgoingTLSConfig generates a TLS configuration for outgoing 108 // requests. It will return a nil config if this configuration should 109 // not use TLS for outgoing connections. Provides a callback to 110 // fetch certificates, allowing for reloading on the fly. 111 func (c *Config) OutgoingTLSConfig() (*tls.Config, error) { 112 // If VerifyServerHostname is true, that implies VerifyOutgoing 113 if c.VerifyServerHostname { 114 c.VerifyOutgoing = true 115 } 116 if !c.VerifyOutgoing { 117 return nil, nil 118 } 119 // Create the tlsConfig 120 tlsConfig := &tls.Config{ 121 RootCAs: x509.NewCertPool(), 122 InsecureSkipVerify: true, 123 } 124 if c.VerifyServerHostname { 125 tlsConfig.InsecureSkipVerify = false 126 } 127 128 // Ensure we have a CA if VerifyOutgoing is set 129 if c.VerifyOutgoing && c.CAFile == "" { 130 return nil, fmt.Errorf("VerifyOutgoing set, and no CA certificate provided!") 131 } 132 133 // Parse the CA cert if any 134 err := c.AppendCA(tlsConfig.RootCAs) 135 if err != nil { 136 return nil, err 137 } 138 139 cert, err := c.LoadKeyPair() 140 if err != nil { 141 return nil, err 142 } else if cert != nil { 143 tlsConfig.GetCertificate = c.KeyLoader.GetOutgoingCertificate 144 tlsConfig.GetClientCertificate = c.KeyLoader.GetClientCertificate 145 } 146 147 return tlsConfig, nil 148 } 149 150 // OutgoingTLSWrapper returns a a Wrapper based on the OutgoingTLS 151 // configuration. If hostname verification is on, the wrapper 152 // will properly generate the dynamic server name for verification. 153 func (c *Config) OutgoingTLSWrapper() (RegionWrapper, error) { 154 // Get the TLS config 155 tlsConfig, err := c.OutgoingTLSConfig() 156 if err != nil { 157 return nil, err 158 } 159 160 // Check if TLS is not enabled 161 if tlsConfig == nil { 162 return nil, nil 163 } 164 165 // Generate the wrapper based on hostname verification 166 if c.VerifyServerHostname { 167 wrapper := func(region string, conn net.Conn) (net.Conn, error) { 168 conf := tlsConfig.Clone() 169 conf.ServerName = "server." + region + ".nomad" 170 return WrapTLSClient(conn, conf) 171 } 172 return wrapper, nil 173 } else { 174 wrapper := func(dc string, c net.Conn) (net.Conn, error) { 175 return WrapTLSClient(c, tlsConfig) 176 } 177 return wrapper, nil 178 } 179 180 } 181 182 // Wrap a net.Conn into a client tls connection, performing any 183 // additional verification as needed. 184 // 185 // As of go 1.3, crypto/tls only supports either doing no certificate 186 // verification, or doing full verification including of the peer's 187 // DNS name. For consul, we want to validate that the certificate is 188 // signed by a known CA, but because consul doesn't use DNS names for 189 // node names, we don't verify the certificate DNS names. Since go 1.3 190 // no longer supports this mode of operation, we have to do it 191 // manually. 192 func WrapTLSClient(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) { 193 var err error 194 var tlsConn *tls.Conn 195 196 tlsConn = tls.Client(conn, tlsConfig) 197 198 // If crypto/tls is doing verification, there's no need to do 199 // our own. 200 if tlsConfig.InsecureSkipVerify == false { 201 return tlsConn, nil 202 } 203 204 if err = tlsConn.Handshake(); err != nil { 205 tlsConn.Close() 206 return nil, err 207 } 208 209 // The following is lightly-modified from the doFullHandshake 210 // method in crypto/tls's handshake_client.go. 211 opts := x509.VerifyOptions{ 212 Roots: tlsConfig.RootCAs, 213 CurrentTime: time.Now(), 214 DNSName: "", 215 Intermediates: x509.NewCertPool(), 216 } 217 218 certs := tlsConn.ConnectionState().PeerCertificates 219 for i, cert := range certs { 220 if i == 0 { 221 continue 222 } 223 opts.Intermediates.AddCert(cert) 224 } 225 226 _, err = certs[0].Verify(opts) 227 if err != nil { 228 tlsConn.Close() 229 return nil, err 230 } 231 232 return tlsConn, err 233 } 234 235 // IncomingTLSConfig generates a TLS configuration for incoming requests 236 func (c *Config) IncomingTLSConfig() (*tls.Config, error) { 237 // Create the tlsConfig 238 tlsConfig := &tls.Config{ 239 ClientCAs: x509.NewCertPool(), 240 ClientAuth: tls.NoClientCert, 241 } 242 243 // Parse the CA cert if any 244 err := c.AppendCA(tlsConfig.ClientCAs) 245 if err != nil { 246 return nil, err 247 } 248 249 // Add cert/key 250 cert, err := c.LoadKeyPair() 251 if err != nil { 252 return nil, err 253 } else if cert != nil { 254 tlsConfig.GetCertificate = c.KeyLoader.GetOutgoingCertificate 255 } 256 257 // Check if we require verification 258 if c.VerifyIncoming { 259 tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert 260 if c.CAFile == "" { 261 return nil, fmt.Errorf("VerifyIncoming set, and no CA certificate provided!") 262 } 263 if cert == nil { 264 return nil, fmt.Errorf("VerifyIncoming set, and no Cert/Key pair provided!") 265 } 266 } 267 268 return tlsConfig, nil 269 }