github.com/djenriquez/nomad-1@v0.8.1/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 func NewTLSConfiguration(newConf *config.TLSConfig) *Config { 71 return &Config{ 72 VerifyIncoming: true, 73 VerifyOutgoing: true, 74 VerifyServerHostname: newConf.VerifyServerHostname, 75 CAFile: newConf.CAFile, 76 CertFile: newConf.CertFile, 77 KeyFile: newConf.KeyFile, 78 KeyLoader: newConf.GetKeyLoader(), 79 } 80 } 81 82 // AppendCA opens and parses the CA file and adds the certificates to 83 // the provided CertPool. 84 func (c *Config) AppendCA(pool *x509.CertPool) error { 85 if c.CAFile == "" { 86 return nil 87 } 88 89 // Read the file 90 data, err := ioutil.ReadFile(c.CAFile) 91 if err != nil { 92 return fmt.Errorf("Failed to read CA file: %v", err) 93 } 94 95 if !pool.AppendCertsFromPEM(data) { 96 return fmt.Errorf("Failed to parse any CA certificates") 97 } 98 99 return nil 100 } 101 102 // LoadKeyPair is used to open and parse a certificate and key file 103 func (c *Config) LoadKeyPair() (*tls.Certificate, error) { 104 if c.CertFile == "" || c.KeyFile == "" { 105 return nil, nil 106 } 107 108 if c.KeyLoader == nil { 109 return nil, fmt.Errorf("No Keyloader object to perform LoadKeyPair") 110 } 111 112 cert, err := c.KeyLoader.LoadKeyPair(c.CertFile, c.KeyFile) 113 if err != nil { 114 return nil, fmt.Errorf("Failed to load cert/key pair: %v", err) 115 } 116 return cert, err 117 } 118 119 // OutgoingTLSConfig generates a TLS configuration for outgoing 120 // requests. It will return a nil config if this configuration should 121 // not use TLS for outgoing connections. Provides a callback to 122 // fetch certificates, allowing for reloading on the fly. 123 func (c *Config) OutgoingTLSConfig() (*tls.Config, error) { 124 // If VerifyServerHostname is true, that implies VerifyOutgoing 125 if c.VerifyServerHostname { 126 c.VerifyOutgoing = true 127 } 128 if !c.VerifyOutgoing { 129 return nil, nil 130 } 131 // Create the tlsConfig 132 tlsConfig := &tls.Config{ 133 RootCAs: x509.NewCertPool(), 134 InsecureSkipVerify: true, 135 } 136 if c.VerifyServerHostname { 137 tlsConfig.InsecureSkipVerify = false 138 } 139 140 // Ensure we have a CA if VerifyOutgoing is set 141 if c.VerifyOutgoing && c.CAFile == "" { 142 return nil, fmt.Errorf("VerifyOutgoing set, and no CA certificate provided!") 143 } 144 145 // Parse the CA cert if any 146 err := c.AppendCA(tlsConfig.RootCAs) 147 if err != nil { 148 return nil, err 149 } 150 151 cert, err := c.LoadKeyPair() 152 if err != nil { 153 return nil, err 154 } else if cert != nil { 155 tlsConfig.GetCertificate = c.KeyLoader.GetOutgoingCertificate 156 tlsConfig.GetClientCertificate = c.KeyLoader.GetClientCertificate 157 } 158 159 return tlsConfig, nil 160 } 161 162 // OutgoingTLSWrapper returns a a Wrapper based on the OutgoingTLS 163 // configuration. If hostname verification is on, the wrapper 164 // will properly generate the dynamic server name for verification. 165 func (c *Config) OutgoingTLSWrapper() (RegionWrapper, error) { 166 // Get the TLS config 167 tlsConfig, err := c.OutgoingTLSConfig() 168 if err != nil { 169 return nil, err 170 } 171 172 // Check if TLS is not enabled 173 if tlsConfig == nil { 174 return nil, nil 175 } 176 177 // Generate the wrapper based on hostname verification 178 if c.VerifyServerHostname { 179 wrapper := func(region string, conn net.Conn) (net.Conn, error) { 180 conf := tlsConfig.Clone() 181 conf.ServerName = "server." + region + ".nomad" 182 return WrapTLSClient(conn, conf) 183 } 184 return wrapper, nil 185 } else { 186 wrapper := func(dc string, c net.Conn) (net.Conn, error) { 187 return WrapTLSClient(c, tlsConfig) 188 } 189 return wrapper, nil 190 } 191 192 } 193 194 // Wrap a net.Conn into a client tls connection, performing any 195 // additional verification as needed. 196 // 197 // As of go 1.3, crypto/tls only supports either doing no certificate 198 // verification, or doing full verification including of the peer's 199 // DNS name. For consul, we want to validate that the certificate is 200 // signed by a known CA, but because consul doesn't use DNS names for 201 // node names, we don't verify the certificate DNS names. Since go 1.3 202 // no longer supports this mode of operation, we have to do it 203 // manually. 204 func WrapTLSClient(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) { 205 var err error 206 var tlsConn *tls.Conn 207 208 tlsConn = tls.Client(conn, tlsConfig) 209 210 // If crypto/tls is doing verification, there's no need to do 211 // our own. 212 if tlsConfig.InsecureSkipVerify == false { 213 return tlsConn, nil 214 } 215 216 if err = tlsConn.Handshake(); err != nil { 217 tlsConn.Close() 218 return nil, err 219 } 220 221 // The following is lightly-modified from the doFullHandshake 222 // method in crypto/tls's handshake_client.go. 223 opts := x509.VerifyOptions{ 224 Roots: tlsConfig.RootCAs, 225 CurrentTime: time.Now(), 226 DNSName: "", 227 Intermediates: x509.NewCertPool(), 228 } 229 230 certs := tlsConn.ConnectionState().PeerCertificates 231 for i, cert := range certs { 232 if i == 0 { 233 continue 234 } 235 opts.Intermediates.AddCert(cert) 236 } 237 238 _, err = certs[0].Verify(opts) 239 if err != nil { 240 tlsConn.Close() 241 return nil, err 242 } 243 244 return tlsConn, err 245 } 246 247 // IncomingTLSConfig generates a TLS configuration for incoming requests 248 func (c *Config) IncomingTLSConfig() (*tls.Config, error) { 249 // Create the tlsConfig 250 tlsConfig := &tls.Config{ 251 ClientCAs: x509.NewCertPool(), 252 ClientAuth: tls.NoClientCert, 253 } 254 255 // Parse the CA cert if any 256 err := c.AppendCA(tlsConfig.ClientCAs) 257 if err != nil { 258 return nil, err 259 } 260 261 // Add cert/key 262 cert, err := c.LoadKeyPair() 263 if err != nil { 264 return nil, err 265 } else if cert != nil { 266 tlsConfig.GetCertificate = c.KeyLoader.GetOutgoingCertificate 267 } 268 269 // Check if we require verification 270 if c.VerifyIncoming { 271 tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert 272 if c.CAFile == "" { 273 return nil, fmt.Errorf("VerifyIncoming set, and no CA certificate provided!") 274 } 275 if cert == nil { 276 return nil, fmt.Errorf("VerifyIncoming set, and no Cert/Key pair provided!") 277 } 278 } 279 280 return tlsConfig, nil 281 }