support TLS directly
This commit is contained in:
		
							parent
							
								
									5a5d6dff7e
								
							
						
					
					
						commit
						f5b2b20f67
					
				
							
								
								
									
										52
									
								
								README.md
								
								
								
								
							
							
						
						
									
										52
									
								
								README.md
								
								
								
								
							|  | @ -9,18 +9,18 @@ to validate accounts by email, domain or group. | |||
| [](http://travis-ci.org/bitly/oauth2_proxy) | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
|  | ||||
| 
 | ||||
| ## Architecture | ||||
| 
 | ||||
|  | ||||
|  | ||||
| 
 | ||||
| ## Installation | ||||
| 
 | ||||
| 1. Download [Prebuilt Binary](https://github.com/bitly/oauth2_proxy/releases) (current release is `v1.1.1`) or build with `$ go get github.com/bitly/oauth2_proxy` which will put the binary in `$GOROOT/bin` | ||||
| 2. Register an OAuth Application with a Provider | ||||
| 3. Configure Oauth2 Proxy using config file, command line options, or environment variables | ||||
| 4. Deploy behind a SSL endpoint (example provided for Nginx) | ||||
| 2. Select a Provider and Register an OAuth Application with a Provider | ||||
| 3. Configure OAuth2 Proxy using config file, command line options, or environment variables | ||||
| 4. Configure SSL or Deploy behind a SSL endpoint (example provided for Nginx) | ||||
| 
 | ||||
| ## OAuth Provider Configuration | ||||
| 
 | ||||
|  | @ -76,6 +76,10 @@ For LinkedIn, the registration steps are: | |||
| 
 | ||||
| The [MyUSA](https://alpha.my.usa.gov) authentication service ([GitHub](https://github.com/18F/myusa)) | ||||
| 
 | ||||
| ## Email Authentication | ||||
| 
 | ||||
| To authorize by email domain use `--email-domain=yourcompany.com`. To authorize individual email addresses use `--authenticated-emails-file=/path/to/file` with one email per line. To authorize all email addresse use `--email-domain=*`. | ||||
| 
 | ||||
| ## Configuration | ||||
| 
 | ||||
| `oauth2_proxy` can be configured via [config file](#config-file), [command line options](#command-line-options) or [environment variables](#environment-variables). | ||||
|  | @ -107,18 +111,21 @@ Usage of oauth2_proxy: | |||
|   -github-team="": restrict logins to members of this team | ||||
|   -htpasswd-file="": additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption | ||||
|   -http-address="127.0.0.1:4180": [http://]<addr>:<port> or unix://<path> to listen on for HTTP clients | ||||
|   -https-address=":443": <addr>:<port> to listen on for HTTPS clients | ||||
|   -login-url="": Authentication endpoint | ||||
|   -pass-access-token=false: pass OAuth access_token to upstream via X-Forwarded-Access-Token header | ||||
|   -pass-basic-auth=true: pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream | ||||
|   -pass-host-header=true: pass the request Host Header to upstream | ||||
|   -profile-url="": Profile access endpoint | ||||
|   -provider="": Oauth provider (defaults to Google) | ||||
|   -provider="google": OAuth provider | ||||
|   -proxy-prefix="/oauth2": the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in) | ||||
|   -redeem-url="": Token redemption endpoint | ||||
|   -redirect-url="": the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback" | ||||
|   -request-logging=true: Log requests to stdout | ||||
|   -scope="": Oauth scope specification | ||||
|   -skip-auth-regex=: bypass authentication for requests path's that match (may be given multiple times) | ||||
|   -tls-cert="": path to certificate file | ||||
|   -tls-key="": path to private key file | ||||
|   -upstream=: the http url(s) of the upstream endpoint. If multiple, routing is based on path | ||||
|   -validate-url="": Access token validation endpoint | ||||
|   -version=false: print version string | ||||
|  | @ -130,10 +137,32 @@ See below for provider specific options | |||
| 
 | ||||
| The environment variables `OAUTH2_PROXY_CLIENT_ID`, `OAUTH2_PROXY_CLIENT_SECRET`, `OAUTH2_PROXY_COOKIE_SECRET`, `OAUTH2_PROXY_COOKIE_DOMAIN` and `OAUTH2_PROXY_COOKIE_EXPIRE` can be used in place of the corresponding command-line arguments. | ||||
| 
 | ||||
| ### Example Nginx Configuration | ||||
| ## SSL Configuration | ||||
| 
 | ||||
| This example has a [Nginx](http://nginx.org/) SSL endpoint proxying to `oauth2_proxy` on port `4180`.  | ||||
| `oauth2_proxy` then authenticates requests for an upstream application running on port `8080`. The external  | ||||
| There are two recommended configurations.  | ||||
| 
 | ||||
| 1) Configure SSL Terminiation with OAuth2 Proxy by providing a `--tls-cert=/path/to/cert.pem` and `--tls-key=/path/to/cert.key`. | ||||
| 
 | ||||
| The command line to run `oauth2_proxy` in this configuration would look like this: | ||||
| 
 | ||||
| ```bash | ||||
| ./oauth2_proxy \ | ||||
|    --email-domain="yourcompany.com"  \ | ||||
|    --upstream=http://127.0.0.1:8080/ \ | ||||
|    --tls-cert=/path/to/cert.pem \ | ||||
|    --tls-key=/path/to/cert.key \ | ||||
|    --cookie-secret=... \ | ||||
|    --cookie-secure=true \ | ||||
|    --provider=... \ | ||||
|    --client-id=... \ | ||||
|    --client-secret=... | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
| 2) Configure SSL Termination with [Nginx](http://nginx.org/) (example config below) or Amazon ELB, or .... | ||||
| 
 | ||||
| Nginx will listen on port `443` and handle SSL connections while proxying to `oauth2_proxy` on port `4180`.  | ||||
| `oauth2_proxy` which will then authenticate requests for an upstream application. The external  | ||||
| endpoint for this example would be `https://internal.yourcompany.com/`. | ||||
| 
 | ||||
| An example Nginx config follows. Note the use of `Strict-Transport-Security` header to pin requests to SSL  | ||||
|  | @ -159,7 +188,7 @@ server { | |||
| } | ||||
| ``` | ||||
| 
 | ||||
| The command line to run `oauth2_proxy` would look like this: | ||||
| The command line to run `oauth2_proxy` in this configuration would look like this: | ||||
| 
 | ||||
| ```bash | ||||
| ./oauth2_proxy \ | ||||
|  | @ -167,6 +196,7 @@ The command line to run `oauth2_proxy` would look like this: | |||
|    --upstream=http://127.0.0.1:8080/ \ | ||||
|    --cookie-secret=... \ | ||||
|    --cookie-secure=true \ | ||||
|    --provider=... \ | ||||
|    --client-id=... \ | ||||
|    --client-secret=... | ||||
| ``` | ||||
|  | @ -174,7 +204,7 @@ The command line to run `oauth2_proxy` would look like this: | |||
| 
 | ||||
| ## Endpoint Documentation | ||||
| 
 | ||||
| OAuth2 Proxy responds directly to the following endpoints. All other endpoints will be proxied upstream when authenticated. | ||||
| OAuth2 Proxy responds directly to the following endpoints. All other endpoints will be proxied upstream when authenticated. The `/oauth2` prefix can be changed with the `--proxy-prefix` config variable. | ||||
| 
 | ||||
| * /robots.txt - returns a 200 OK response that disallows all User-agents from all paths; see [robotstxt.org](http://www.robotstxt.org/) for more info | ||||
| * /ping - returns an 200 OK response | ||||
|  |  | |||
|  | @ -1,8 +1,13 @@ | |||
| ## OAuth2 Proxy Config File | ||||
| ## https://github.com/bitly/oauth2_proxy | ||||
| 
 | ||||
| ## <addr>:<port> to listen on for HTTP clients | ||||
| ## <addr>:<port> to listen on for HTTP/HTTPS clients | ||||
| # http_address = "127.0.0.1:4180" | ||||
| # https_address = ":443" | ||||
| 
 | ||||
| ## TLS Settings | ||||
| # tls_cert_file = "" | ||||
| # tls_key_file = "" | ||||
| 
 | ||||
| ## the OAuth Redirect URL. | ||||
| # defaults to the "https://" + requested host header + "/oauth2/callback" | ||||
|  |  | |||
|  | @ -0,0 +1,106 @@ | |||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"crypto/tls" | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| type Server struct { | ||||
| 	Handler http.Handler | ||||
| 	Opts    *Options | ||||
| } | ||||
| 
 | ||||
| func (s *Server) ListenAndServe() { | ||||
| 	if s.Opts.TLSKeyFile != "" || s.Opts.TLSCertFile != "" { | ||||
| 		s.ServeHTTPS() | ||||
| 	} else { | ||||
| 		s.ServeHTTP() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (s *Server) ServeHTTP() { | ||||
| 	u, err := url.Parse(s.Opts.HttpAddress) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("FATAL: could not parse %#v: %v", s.Opts.HttpAddress, err) | ||||
| 	} | ||||
| 
 | ||||
| 	var networkType string | ||||
| 	switch u.Scheme { | ||||
| 	case "", "http": | ||||
| 		networkType = "tcp" | ||||
| 	default: | ||||
| 		networkType = u.Scheme | ||||
| 	} | ||||
| 	listenAddr := strings.TrimPrefix(u.String(), u.Scheme+"://") | ||||
| 
 | ||||
| 	listener, err := net.Listen(networkType, listenAddr) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("FATAL: listen (%s, %s) failed - %s", networkType, listenAddr, err) | ||||
| 	} | ||||
| 	log.Printf("HTTP: listening on %s", listenAddr) | ||||
| 
 | ||||
| 	server := &http.Server{Handler: s.Handler} | ||||
| 	err = server.Serve(listener) | ||||
| 	if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { | ||||
| 		log.Printf("ERROR: http.Serve() - %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	log.Printf("HTTP: closing %s", listener.Addr()) | ||||
| } | ||||
| 
 | ||||
| func (s *Server) ServeHTTPS() { | ||||
| 	addr := s.Opts.HttpsAddress | ||||
| 	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 { | ||||
| 		log.Fatalf("FATAL: loading tls config (%s, %s) failed - %s", s.Opts.TLSCertFile, s.Opts.TLSKeyFile, err) | ||||
| 	} | ||||
| 
 | ||||
| 	ln, err := net.Listen("tcp", addr) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("FATAL: listen (%s) failed - %s", addr, err) | ||||
| 	} | ||||
| 	log.Printf("HTTPS: listening on %s", ln.Addr()) | ||||
| 
 | ||||
| 	tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config) | ||||
| 	srv := &http.Server{Handler: s.Handler} | ||||
| 	err = srv.Serve(tlsListener) | ||||
| 
 | ||||
| 	if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { | ||||
| 		log.Printf("ERROR: https.Serve() - %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	log.Printf("HTTPS: closing %s", tlsListener.Addr()) | ||||
| } | ||||
| 
 | ||||
| // 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 | ||||
| } | ||||
							
								
								
									
										38
									
								
								main.go
								
								
								
								
							
							
						
						
									
										38
									
								
								main.go
								
								
								
								
							|  | @ -4,9 +4,6 @@ import ( | |||
| 	"flag" | ||||
| 	"fmt" | ||||
| 	"log" | ||||
| 	"net" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"runtime" | ||||
| 	"strings" | ||||
|  | @ -28,6 +25,9 @@ func main() { | |||
| 	showVersion := flagSet.Bool("version", false, "print version string") | ||||
| 
 | ||||
| 	flagSet.String("http-address", "127.0.0.1:4180", "[http://]<addr>:<port> or unix://<path> to listen on for HTTP clients") | ||||
| 	flagSet.String("https-address", ":443", "<addr>:<port> to listen on for HTTPS clients") | ||||
| 	flagSet.String("tls-cert", "", "path to certificate file") | ||||
| 	flagSet.String("tls-key", "", "path to private key file") | ||||
| 	flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"") | ||||
| 	flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint. If multiple, routing is based on path") | ||||
| 	flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream") | ||||
|  | @ -57,7 +57,7 @@ func main() { | |||
| 
 | ||||
| 	flagSet.Bool("request-logging", true, "Log requests to stdout") | ||||
| 
 | ||||
| 	flagSet.String("provider", "", "Oauth provider (defaults to Google)") | ||||
| 	flagSet.String("provider", "google", "OAuth provider") | ||||
| 	flagSet.String("login-url", "", "Authentication endpoint") | ||||
| 	flagSet.String("redeem-url", "", "Token redemption endpoint") | ||||
| 	flagSet.String("profile-url", "", "Profile access endpoint") | ||||
|  | @ -109,31 +109,9 @@ func main() { | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	u, err := url.Parse(opts.HttpAddress) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("FATAL: could not parse %#v: %v", opts.HttpAddress, err) | ||||
| 	s := &Server{ | ||||
| 		Handler: LoggingHandler(os.Stdout, oauthproxy, opts.RequestLogging), | ||||
| 		Opts:    opts, | ||||
| 	} | ||||
| 
 | ||||
| 	var networkType string | ||||
| 	switch u.Scheme { | ||||
| 	case "", "http": | ||||
| 		networkType = "tcp" | ||||
| 	default: | ||||
| 		networkType = u.Scheme | ||||
| 	} | ||||
| 	listenAddr := strings.TrimPrefix(u.String(), u.Scheme+"://") | ||||
| 
 | ||||
| 	listener, err := net.Listen(networkType, listenAddr) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("FATAL: listen (%s, %s) failed - %s", networkType, listenAddr, err) | ||||
| 	} | ||||
| 	log.Printf("listening on %s", listenAddr) | ||||
| 
 | ||||
| 	server := &http.Server{Handler: LoggingHandler(os.Stdout, oauthproxy, opts.RequestLogging)} | ||||
| 	err = server.Serve(listener) | ||||
| 	if err != nil && !strings.Contains(err.Error(), "use of closed network connection") { | ||||
| 		log.Printf("ERROR: http.Serve() - %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	log.Printf("HTTP: closing %s", listener.Addr()) | ||||
| 	s.ListenAndServe() | ||||
| } | ||||
|  |  | |||
|  | @ -104,7 +104,7 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy { | |||
| 	redirectUrl := opts.redirectUrl | ||||
| 	redirectUrl.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix) | ||||
| 
 | ||||
| 	log.Printf("OauthProxy configured for %s", opts.ClientID) | ||||
| 	log.Printf("OauthProxy configured for %s Client ID: %s", opts.provider.Data().ProviderName, opts.ClientID) | ||||
| 	domain := opts.CookieDomain | ||||
| 	if domain == "" { | ||||
| 		domain = "<default>" | ||||
|  | @ -114,7 +114,7 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy { | |||
| 		opts.CookieSecure = opts.CookieHttpsOnly | ||||
| 	} | ||||
| 
 | ||||
| 	log.Printf("Cookie settings: secure (https):%v httponly:%v expiry:%s domain:%s", opts.CookieSecure, opts.CookieHttpOnly, opts.CookieExpire, domain) | ||||
| 	log.Printf("Cookie settings: name:%s secure (https):%v httponly:%v expiry:%s domain:%s", opts.CookieKey, opts.CookieSecure, opts.CookieHttpOnly, opts.CookieExpire, domain) | ||||
| 
 | ||||
| 	var aes_cipher cipher.Block | ||||
| 	if opts.PassAccessToken || (opts.CookieRefresh != time.Duration(0)) { | ||||
|  |  | |||
|  | @ -14,9 +14,12 @@ import ( | |||
| type Options struct { | ||||
| 	ProxyPrefix  string `flag:"proxy-prefix" cfg:"proxy-prefix"` | ||||
| 	HttpAddress  string `flag:"http-address" cfg:"http_address"` | ||||
| 	HttpsAddress string `flag:"https-address" cfg:"https_address"` | ||||
| 	RedirectUrl  string `flag:"redirect-url" cfg:"redirect_url"` | ||||
| 	ClientID     string `flag:"client-id" cfg:"client_id" env:"OAUTH2_PROXY_CLIENT_ID"` | ||||
| 	ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"OAUTH2_PROXY_CLIENT_SECRET"` | ||||
| 	TLSCertFile  string `flag:"tls-cert" cfg:"tls_cert_file"` | ||||
| 	TLSKeyFile   string `flag:"tls-key" cfg:"tls_key_file"` | ||||
| 
 | ||||
| 	AuthenticatedEmailsFile string   `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"` | ||||
| 	EmailDomains            []string `flag:"email-domain" cfg:"email_domains"` | ||||
|  | @ -63,6 +66,7 @@ func NewOptions() *Options { | |||
| 	return &Options{ | ||||
| 		ProxyPrefix:         "/oauth2", | ||||
| 		HttpAddress:         "127.0.0.1:4180", | ||||
| 		HttpsAddress:        ":443", | ||||
| 		DisplayHtpasswdForm: true, | ||||
| 		CookieKey:           "_oauthproxy", | ||||
| 		CookieHttpsOnly:     true, | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue