2015-06-07 21:51:47 -04:00
package main
import (
2020-04-05 00:12:38 +09:00
"context"
2015-06-07 21:51:47 -04:00
"crypto/tls"
2020-04-05 00:12:38 +09:00
"errors"
2015-06-07 21:51:47 -04:00
"net"
"net/http"
"strings"
"time"
2019-02-10 08:37:45 -08:00
2020-04-13 13:50:34 +01:00
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
2020-03-29 14:54:36 +01:00
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
2015-06-07 21:51:47 -04:00
)
2018-12-20 09:30:42 +00:00
// Server represents an HTTP server
2015-06-07 21:51:47 -04:00
type Server struct {
Handler http . Handler
2020-04-13 13:50:34 +01:00
Opts * options . Options
2020-04-05 00:12:38 +09:00
stop chan struct { } // channel for waiting shutdown
2015-06-07 21:51:47 -04:00
}
2018-12-20 09:30:42 +00:00
// ListenAndServe will serve traffic on HTTP or HTTPS depending on TLS options
2015-06-07 21:51:47 -04:00
func ( s * Server ) ListenAndServe ( ) {
if s . Opts . TLSKeyFile != "" || s . Opts . TLSCertFile != "" {
s . ServeHTTPS ( )
} else {
s . ServeHTTP ( )
}
}
2019-03-25 10:32:29 -07:00
// Used with gcpHealthcheck()
const userAgentHeader = "User-Agent"
const googleHealthCheckUserAgent = "GoogleHC/1.0"
const rootPath = "/"
// gcpHealthcheck handles healthcheck queries from GCP.
2019-03-20 14:29:44 -07:00
func gcpHealthcheck ( h http . Handler ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2019-03-25 10:32:29 -07:00
// Check for liveness and readiness: used for Google App Engine
2019-03-20 14:29:44 -07:00
if r . URL . EscapedPath ( ) == "/liveness_check" {
w . WriteHeader ( http . StatusOK )
w . Write ( [ ] byte ( "OK" ) )
return
}
if r . URL . EscapedPath ( ) == "/readiness_check" {
w . WriteHeader ( http . StatusOK )
w . Write ( [ ] byte ( "OK" ) )
return
}
2019-03-25 10:32:29 -07:00
// Check for GKE ingress healthcheck: The ingress requires the root
// path of the target to return a 200 (OK) to indicate the service's good health. This can be quite a challenging demand
// depending on the application's path structure. This middleware filters out the requests from the health check by
//
// 1. checking that the request path is indeed the root path
// 2. ensuring that the User-Agent is "GoogleHC/1.0", the health checker
// 3. ensuring the request method is "GET"
if r . URL . Path == rootPath &&
r . Header . Get ( userAgentHeader ) == googleHealthCheckUserAgent &&
r . Method == http . MethodGet {
w . WriteHeader ( http . StatusOK )
return
}
2019-03-20 14:29:44 -07:00
h . ServeHTTP ( w , r )
} )
}
2018-12-20 09:30:42 +00:00
// ServeHTTP constructs a net.Listener and starts handling HTTP requests
2015-06-07 21:51:47 -04:00
func ( s * Server ) ServeHTTP ( ) {
2018-11-29 14:26:41 +00:00
HTTPAddress := s . Opts . HTTPAddress
2018-12-20 09:30:42 +00:00
var scheme string
2017-04-01 15:10:33 -04:00
2018-11-29 14:26:41 +00:00
i := strings . Index ( HTTPAddress , "://" )
2017-04-01 15:10:33 -04:00
if i > - 1 {
2018-11-29 14:26:41 +00:00
scheme = HTTPAddress [ 0 : i ]
2015-06-07 21:51:47 -04:00
}
var networkType string
2017-04-01 15:10:33 -04:00
switch scheme {
2015-06-07 21:51:47 -04:00
case "" , "http" :
networkType = "tcp"
default :
2017-04-01 15:10:33 -04:00
networkType = scheme
2015-06-07 21:51:47 -04:00
}
2017-04-01 15:10:33 -04:00
2018-11-29 14:26:41 +00:00
slice := strings . SplitN ( HTTPAddress , "//" , 2 )
2017-04-01 15:10:33 -04:00
listenAddr := slice [ len ( slice ) - 1 ]
2015-06-07 21:51:47 -04:00
listener , err := net . Listen ( networkType , listenAddr )
if err != nil {
2019-02-10 08:37:45 -08:00
logger . Fatalf ( "FATAL: listen (%s, %s) failed - %s" , networkType , listenAddr , err )
2015-06-07 21:51:47 -04:00
}
2019-02-10 08:37:45 -08:00
logger . Printf ( "HTTP: listening on %s" , listenAddr )
2020-04-05 00:12:38 +09:00
s . serve ( listener )
2019-02-10 08:37:45 -08:00
logger . Printf ( "HTTP: closing %s" , listener . Addr ( ) )
2015-06-07 21:51:47 -04:00
}
2018-12-20 09:30:42 +00:00
// ServeHTTPS constructs a net.Listener and starts handling HTTPS requests
2015-06-07 21:51:47 -04:00
func ( s * Server ) ServeHTTPS ( ) {
2018-11-29 14:26:41 +00:00
addr := s . Opts . HTTPSAddress
2015-06-07 21:51:47 -04:00
config := & tls . Config {
MinVersion : tls . VersionTLS12 ,
MaxVersion : tls . VersionTLS12 ,
}
if config . NextProtos == nil {
config . NextProtos = [ ] string { "http/1.1" }
}
var err error
config . Certificates = make ( [ ] tls . Certificate , 1 )
config . Certificates [ 0 ] , err = tls . LoadX509KeyPair ( s . Opts . TLSCertFile , s . Opts . TLSKeyFile )
if err != nil {
2019-02-10 08:37:45 -08:00
logger . Fatalf ( "FATAL: loading tls config (%s, %s) failed - %s" , s . Opts . TLSCertFile , s . Opts . TLSKeyFile , err )
2015-06-07 21:51:47 -04:00
}
ln , err := net . Listen ( "tcp" , addr )
if err != nil {
2019-02-10 08:37:45 -08:00
logger . Fatalf ( "FATAL: listen (%s) failed - %s" , addr , err )
2015-06-07 21:51:47 -04:00
}
2019-02-10 08:37:45 -08:00
logger . Printf ( "HTTPS: listening on %s" , ln . Addr ( ) )
2015-06-07 21:51:47 -04:00
tlsListener := tls . NewListener ( tcpKeepAliveListener { ln . ( * net . TCPListener ) } , config )
2020-04-05 00:12:38 +09:00
s . serve ( tlsListener )
logger . Printf ( "HTTPS: closing %s" , tlsListener . Addr ( ) )
}
func ( s * Server ) serve ( listener net . Listener ) {
2015-06-07 21:51:47 -04:00
srv := & http . Server { Handler : s . Handler }
2020-04-05 00:12:38 +09:00
// See https://golang.org/pkg/net/http/#Server.Shutdown
idleConnsClosed := make ( chan struct { } )
go func ( ) {
<- s . stop // wait notification for stopping server
2015-06-07 21:51:47 -04:00
2020-04-05 00:12:38 +09:00
// We received an interrupt signal, shut down.
if err := srv . Shutdown ( context . Background ( ) ) ; err != nil {
// Error from closing listeners, or context timeout:
logger . Printf ( "HTTP server Shutdown: %v" , err )
}
close ( idleConnsClosed )
} ( )
err := srv . Serve ( listener )
if err != nil && ! errors . Is ( err , http . ErrServerClosed ) {
logger . Printf ( "ERROR: http.Serve() - %s" , err )
}
<- idleConnsClosed
2015-06-07 21:51:47 -04:00
}
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
type tcpKeepAliveListener struct {
* net . TCPListener
}
func ( ln tcpKeepAliveListener ) Accept ( ) ( c net . Conn , err error ) {
tc , err := ln . AcceptTCP ( )
if err != nil {
return
}
tc . SetKeepAlive ( true )
tc . SetKeepAlivePeriod ( 3 * time . Minute )
return tc , nil
}
2019-10-17 16:30:48 +01:00
2020-04-13 13:50:34 +01:00
func redirectToHTTPS ( opts * options . Options , h http . Handler ) http . Handler {
2019-10-17 16:30:48 +01:00
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2019-10-21 23:21:35 +01:00
proto := r . Header . Get ( "X-Forwarded-Proto" )
2019-10-22 14:21:06 +01:00
if opts . ForceHTTPS && ( r . TLS == nil || ( proto != "" && strings . ToLower ( proto ) != "https" ) ) {
2019-10-17 16:30:48 +01:00
http . Redirect ( w , r , opts . HTTPSAddress , http . StatusPermanentRedirect )
}
h . ServeHTTP ( w , r )
} )
}