1// Copyright (c) 2017 Couchbase, Inc. 2// Licensed under the Apache License, Version 2.0 (the "License"); 3// you may not use this file except in compliance with the 4// License. You may obtain a copy of the License at 5// http://www.apache.org/licenses/LICENSE-2.0 6// Unless required by applicable law or agreed to in writing, 7// software distributed under the License is distributed on an "AS 8// IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 9// express or implied. See the License for the specific language 10// governing permissions and limitations under the License. 11 12package main 13 14import ( 15 "crypto/tls" 16 "crypto/x509" 17 "io/ioutil" 18 "net" 19 "net/http" 20 "strconv" 21 "strings" 22 "sync" 23 "sync/atomic" 24 "time" 25 26 "github.com/couchbase/cbauth" 27 "github.com/couchbase/cbft" 28 log "github.com/couchbase/clog" 29 30 "golang.org/x/net/netutil" 31) 32 33type httpsServer struct { 34 server *http.Server 35 listener net.Listener 36} 37 38// List of active https servers 39var httpsServers []*httpsServer 40var httpsServersMutex sync.Mutex 41 42// AuthType used for HTTPS connections 43var authType string 44 45// Use IPv6 46var ipv6 string 47 48func setupHTTPListenersAndServ(routerInUse http.Handler, bindHTTPList []string, 49 options map[string]string) { 50 http.Handle("/", routerInUse) 51 ipv6 = options["ipv6"] 52 53 anyHostPorts := map[string]bool{} 54 // Bind to 0.0.0.0's (IPv4) or [::]'s (IPv6) first for http listening. 55 for _, bindHTTP := range bindHTTPList { 56 if strings.HasPrefix(bindHTTP, "0.0.0.0:") || 57 strings.HasPrefix(bindHTTP, "[::]:") { 58 go mainServeHTTP("http", bindHTTP, nil, "", "") 59 60 anyHostPorts[bindHTTP] = true 61 } 62 } 63 64 for i := len(bindHTTPList) - 1; i >= 1; i-- { 65 go mainServeHTTP("http", bindHTTPList[i], anyHostPorts, "", "") 66 } 67 68 authType = options["authType"] 69 70 if authType == "cbauth" { 71 // Registering a TLS refresh callback with cbauth, which 72 // will be responsible for updating https listeners, 73 // whenever ssl certificates or the client cert auth settings 74 // are changed. 75 cbauth.RegisterTLSRefreshCallback(setupHTTPSListeners) 76 } else { 77 setupHTTPSListeners() 78 } 79 80 mainServeHTTP("http", bindHTTPList[0], anyHostPorts, "", "") 81} 82 83// Add to HTTPS Server list serially 84func addToHTTPSServerList(server *http.Server, listener net.Listener) { 85 httpsServersMutex.Lock() 86 entry := &httpsServer{ 87 server: server, 88 listener: listener, 89 } 90 httpsServers = append(httpsServers, entry) 91 httpsServersMutex.Unlock() 92} 93 94// Close all HTTPS Servers and clear HTTPS Server list 95func closeAndClearHTTPSServerList() { 96 httpsServersMutex.Lock() 97 defer httpsServersMutex.Unlock() 98 99 for _, entry := range httpsServers { 100 // Close the listener associated with the server first. 101 entry.listener.Close() 102 // Then Close the server. 103 entry.server.Close() 104 } 105 httpsServers = nil 106} 107 108func setupHTTPSListeners() error { 109 // Close any previously open https servers 110 closeAndClearHTTPSServerList() 111 112 anyHostPorts := map[string]bool{} 113 114 if flags.BindHTTPS != "" { 115 bindHTTPSList := strings.Split(flags.BindHTTPS, ",") 116 117 // Bind to 0.0.0.0's first for https listening. 118 for _, bindHTTPS := range bindHTTPSList { 119 if strings.HasPrefix(bindHTTPS, "0.0.0.0:") || 120 strings.HasPrefix(bindHTTPS, "[::]:") { 121 go mainServeHTTP("https", bindHTTPS, nil, 122 flags.TLSCertFile, flags.TLSKeyFile) 123 124 anyHostPorts[bindHTTPS] = true 125 } 126 } 127 128 for _, bindHTTPS := range bindHTTPSList { 129 go mainServeHTTP("https", bindHTTPS, anyHostPorts, 130 flags.TLSCertFile, flags.TLSKeyFile) 131 } 132 } 133 134 return nil 135} 136 137// mainServeHTTP starts the http/https servers for cbft. 138// The proto may be "http" or "https". 139func mainServeHTTP(proto, bindHTTP string, anyHostPorts map[string]bool, 140 certFile, keyFile string) { 141 if bindHTTP[0] == ':' && proto == "http" { 142 bindHTTP = "localhost" + bindHTTP 143 } 144 145 bar := "main: ------------------------------------------------------" 146 147 if anyHostPorts != nil && len(bindHTTP) > 0 { 148 // If we've already bound to 0.0.0.0 or [::] on the same port, then 149 // skip this hostPort. 150 portIndex := strings.LastIndex(bindHTTP, ":") + 1 151 if portIndex > 0 && portIndex < len(bindHTTP) { 152 // Possibly valid port available. 153 port := bindHTTP[portIndex:] 154 if _, err := strconv.Atoi(port); err == nil { 155 // Valid port. 156 host := "0.0.0.0" 157 if net.ParseIP(bindHTTP[:portIndex-1]).To4() == nil && // Not an IPv4 158 ipv6 == "true" { 159 host = "[::]" 160 } 161 162 anyHostPort := host + ":" + port 163 if anyHostPorts[anyHostPort] { 164 if anyHostPort != bindHTTP { 165 log.Printf(bar) 166 log.Printf("init_http: web UI / REST API is available"+ 167 " (via %v): %s://%s", host, proto, bindHTTP) 168 log.Printf(bar) 169 } 170 return 171 } 172 } 173 } // Else port not found. 174 } 175 176 log.Printf(bar) 177 log.Printf("init_http: web UI / REST API is available: %s://%s", proto, bindHTTP) 178 log.Printf(bar) 179 180 listener, err := net.Listen("tcp", bindHTTP) 181 if err != nil { 182 log.Fatalf("init_http: listen, err: %v", err) 183 } 184 server := &http.Server{Addr: bindHTTP, 185 Handler: routerInUse, 186 ReadTimeout: httpReadTimeout, 187 WriteTimeout: httpWriteTimeout} 188 189 if proto == "http" { 190 limitListener := netutil.LimitListener(listener, httpMaxConnections) 191 log.Printf("init_http: Setting up a http limit listener over %q", bindHTTP) 192 atomic.AddUint64(&cbft.TotHTTPLimitListenersOpened, 1) 193 err = server.Serve(limitListener) 194 if err != nil { 195 log.Fatalf("init_http: Serve, err: %v;\n"+ 196 " Please check that your -bindHttp(s) parameter (%q)\n"+ 197 " is correct and available.", err, bindHTTP) 198 } 199 atomic.AddUint64(&cbft.TotHTTPLimitListenersClosed, 1) 200 } else { 201 addToHTTPSServerList(server, listener) 202 // Initialize server.TLSConfig to the listener's TLS Config before calling 203 // server for HTTP/2 support. 204 // See: https://golang.org/pkg/net/http/#Server.Serve 205 config := cloneTLSConfig(server.TLSConfig) 206 if !strSliceContains(config.NextProtos, "http/1.1") { 207 config.NextProtos = append(config.NextProtos, "http/1.1") 208 } 209 if !strSliceContains(config.NextProtos, "h2") { 210 config.NextProtos = append(config.NextProtos, "h2") 211 } 212 213 config.Certificates = make([]tls.Certificate, 1) 214 config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) 215 if err != nil { 216 log.Fatalf("init_http: LoadX509KeyPair, err: %v", err) 217 } 218 219 if authType == "cbauth" { 220 // Set MinTLSVersion and CipherSuites to what is provided by 221 // cbauth if authType were cbauth. 222 config.MinVersion = cbauth.MinTLSVersion() 223 config.CipherSuites = cbauth.CipherSuites() 224 225 clientAuthType, er := cbauth.GetClientCertAuthType() 226 if er != nil { 227 log.Fatalf("init_http: GetClientCertAuthType, err: %v", err) 228 } 229 230 if clientAuthType != tls.NoClientCert { 231 caCert, er := ioutil.ReadFile(certFile) 232 if er != nil { 233 log.Fatalf("init_http: ReadFile of cacert, err: %v", err) 234 } 235 caCertPool := x509.NewCertPool() 236 caCertPool.AppendCertsFromPEM(caCert) 237 config.ClientCAs = caCertPool 238 config.ClientAuth = clientAuthType 239 } 240 } 241 242 keepAliveListener := tcpKeepAliveListener{listener.(*net.TCPListener)} 243 limitListener := netutil.LimitListener(keepAliveListener, httpMaxConnections) 244 tlsListener := tls.NewListener(limitListener, config) 245 log.Printf("init_http: Setting up a https limit listener over %q", bindHTTP) 246 atomic.AddUint64(&cbft.TotHTTPSLimitListenersOpened, 1) 247 err = server.Serve(tlsListener) 248 if err != nil { 249 log.Printf("init_http: Serve, err: %v;\n"+ 250 " HTTPS listeners closed, likely to be re-initialized, "+ 251 " -bindHttp(s) (%q)\n", err, bindHTTP) 252 } 253 atomic.AddUint64(&cbft.TotHTTPSLimitListenersClosed, 1) 254 } 255} 256 257// Helper function to determine if the provided string is already 258// present in the provided array of strings. 259func strSliceContains(ss []string, s string) bool { 260 for _, v := range ss { 261 if v == s { 262 return true 263 } 264 } 265 return false 266} 267 268// cloneTLSConfig returns a clone of the tls.Config. 269func cloneTLSConfig(cfg *tls.Config) *tls.Config { 270 if cfg == nil { 271 return &tls.Config{} 272 } 273 return cfg.Clone() 274} 275 276// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted 277// connections. It's used by ListenAndServe and ListenAndServeTLS so 278// dead TCP connections (e.g. closing laptop mid-download) eventually 279// go away. 280type tcpKeepAliveListener struct { 281 *net.TCPListener 282} 283 284func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { 285 tc, err := ln.AcceptTCP() 286 if err != nil { 287 return 288 } 289 tc.SetKeepAlive(true) 290 tc.SetKeepAlivePeriod(3 * time.Minute) 291 return tc, nil 292} 293