git.iko.soy/typeable/oauth2-proxy
on master help sign in
support TLS directly
commitf5b2b20f6784e1af4234144f92a688a95e53cc65 authorJehiah Czebotar <jehiah@gmail.com> dateSun 7 Jun 2015, 21:51 (11 years ago) parent5a5d6df
6 files changed+167−44
README.md+41−11 contrib/oauth2_proxy.cfg.example+6−1 http.go+106 main.go+8−30 oauthproxy.go+2−2 options.go+4
README.md+41−11
@@ -9,18 +9,18 @@ to validate accounts by email, domain or group.
99[![Build Status](https://secure.travis-ci.org/bitly/oauth2_proxy.png?branch=master)](http://travis-ci.org/bitly/oauth2_proxy)
1010
1111
12−![sign_in_page](https://cloud.githubusercontent.com/assets/45028/4970624/7feb7dd8-6886-11e4-93e0-c9904af44ea8.png)
12+![Sign In Page](https://cloud.githubusercontent.com/assets/45028/4970624/7feb7dd8-6886-11e4-93e0-c9904af44ea8.png)
1313
1414## Architecture
1515
16−![oauth2_proxy_arch](https://cloud.githubusercontent.com/assets/45028/7749664/35fef390-ff9d-11e4-8d51-21a7ba78f857.png)
16+![OAuth2 Proxy Architecture](https://cloud.githubusercontent.com/assets/45028/8027702/bd040b7a-0d6a-11e5-85b9-f8d953d04f39.png)
1717
1818## Installation
1919
20201. 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`
21−2. Register an OAuth Application with a Provider
22−3. Configure Oauth2 Proxy using config file, command line options, or environment variables
23−4. Deploy behind a SSL endpoint (example provided for Nginx)
21+2. Select a Provider and Register an OAuth Application with a Provider
22+3. Configure OAuth2 Proxy using config file, command line options, or environment variables
23+4. Configure SSL or Deploy behind a SSL endpoint (example provided for Nginx)
2424
2525## OAuth Provider Configuration
2626
@@ -76,6 +76,10 @@ For LinkedIn, the registration steps are:
7676
7777The [MyUSA](https://alpha.my.usa.gov) authentication service ([GitHub](https://github.com/18F/myusa))
7878
79+## Email Authentication
80+
81+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=*`.
82+
7983## Configuration
8084
8185`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:
107111 -github-team="": restrict logins to members of this team
108112 -htpasswd-file="": additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption
109113 -http-address="127.0.0.1:4180": [http://]<addr>:<port> or unix://<path> to listen on for HTTP clients
114+ -https-address=":443": <addr>:<port> to listen on for HTTPS clients
110115 -login-url="": Authentication endpoint
111116 -pass-access-token=false: pass OAuth access_token to upstream via X-Forwarded-Access-Token header
112117 -pass-basic-auth=true: pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream
113118 -pass-host-header=true: pass the request Host Header to upstream
114119 -profile-url="": Profile access endpoint
115− -provider="": Oauth provider (defaults to Google)
120+ -provider="google": OAuth provider
116121 -proxy-prefix="/oauth2": the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)
117122 -redeem-url="": Token redemption endpoint
118123 -redirect-url="": the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
119124 -request-logging=true: Log requests to stdout
120125 -scope="": Oauth scope specification
121126 -skip-auth-regex=: bypass authentication for requests path's that match (may be given multiple times)
127+ -tls-cert="": path to certificate file
128+ -tls-key="": path to private key file
122129 -upstream=: the http url(s) of the upstream endpoint. If multiple, routing is based on path
123130 -validate-url="": Access token validation endpoint
124131 -version=false: print version string
@@ -130,10 +137,32 @@ See below for provider specific options
130137
131138The 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.
132139
133−### Example Nginx Configuration
140+## SSL Configuration
141+
142+There are two recommended configurations.
143+
144+1) Configure SSL Terminiation with OAuth2 Proxy by providing a `--tls-cert=/path/to/cert.pem` and `--tls-key=/path/to/cert.key`.
145+
146+The command line to run `oauth2_proxy` in this configuration would look like this:
147+
148+```bash
149+./oauth2_proxy \
150+ --email-domain="yourcompany.com" \
151+ --upstream=http://127.0.0.1:8080/ \
152+ --tls-cert=/path/to/cert.pem \
153+ --tls-key=/path/to/cert.key \
154+ --cookie-secret=... \
155+ --cookie-secure=true \
156+ --provider=... \
157+ --client-id=... \
158+ --client-secret=...
159+```
160+
161+
162+2) Configure SSL Termination with [Nginx](http://nginx.org/) (example config below) or Amazon ELB, or ....
134163
135−This example has a [Nginx](http://nginx.org/) SSL endpoint proxying to `oauth2_proxy` on port `4180`.
136−`oauth2_proxy` then authenticates requests for an upstream application running on port `8080`. The external
164+Nginx will listen on port `443` and handle SSL connections while proxying to `oauth2_proxy` on port `4180`.
165+`oauth2_proxy` which will then authenticate requests for an upstream application. The external
137166endpoint for this example would be `https://internal.yourcompany.com/`.
138167
139168An example Nginx config follows. Note the use of `Strict-Transport-Security` header to pin requests to SSL
@@ -159,7 +188,7 @@ server {
159188}
160189```
161190
162−The command line to run `oauth2_proxy` would look like this:
191+The command line to run `oauth2_proxy` in this configuration would look like this:
163192
164193```bash
165194./oauth2_proxy \
@@ -167,6 +196,7 @@ The command line to run `oauth2_proxy` would look like this:
167196 --upstream=http://127.0.0.1:8080/ \
168197 --cookie-secret=... \
169198 --cookie-secure=true \
199+ --provider=... \
170200 --client-id=... \
171201 --client-secret=...
172202```
@@ -174,7 +204,7 @@ The command line to run `oauth2_proxy` would look like this:
174204
175205## Endpoint Documentation
176206
177−OAuth2 Proxy responds directly to the following endpoints. All other endpoints will be proxied upstream when authenticated.
207+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.
178208
179209* /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
180210* /ping - returns an 200 OK response
contrib/oauth2_proxy.cfg.example+6−1
@@ -1,8 +1,13 @@
11## OAuth2 Proxy Config File
22## https://github.com/bitly/oauth2_proxy
33
4−## <addr>:<port> to listen on for HTTP clients
4+## <addr>:<port> to listen on for HTTP/HTTPS clients
55# http_address = "127.0.0.1:4180"
6+# https_address = ":443"
7+
8+## TLS Settings
9+# tls_cert_file = ""
10+# tls_key_file = ""
611
712## the OAuth Redirect URL.
813# defaults to the "https://" + requested host header + "/oauth2/callback"
http.gonew file+106
@@ -0,0 +1,106 @@
1+package main
2+
3+import (
4+ "crypto/tls"
5+ "log"
6+ "net"
7+ "net/http"
8+ "net/url"
9+ "strings"
10+ "time"
11+)
12+
13+type Server struct {
14+ Handler http.Handler
15+ Opts *Options
16+}
17+
18+func (s *Server) ListenAndServe() {
19+ if s.Opts.TLSKeyFile != "" || s.Opts.TLSCertFile != "" {
20+ s.ServeHTTPS()
21+ } else {
22+ s.ServeHTTP()
23+ }
24+}
25+
26+func (s *Server) ServeHTTP() {
27+ u, err := url.Parse(s.Opts.HttpAddress)
28+ if err != nil {
29+ log.Fatalf("FATAL: could not parse %#v: %v", s.Opts.HttpAddress, err)
30+ }
31+
32+ var networkType string
33+ switch u.Scheme {
34+ case "", "http":
35+ networkType = "tcp"
36+ default:
37+ networkType = u.Scheme
38+ }
39+ listenAddr := strings.TrimPrefix(u.String(), u.Scheme+"://")
40+
41+ listener, err := net.Listen(networkType, listenAddr)
42+ if err != nil {
43+ log.Fatalf("FATAL: listen (%s, %s) failed - %s", networkType, listenAddr, err)
44+ }
45+ log.Printf("HTTP: listening on %s", listenAddr)
46+
47+ server := &http.Server{Handler: s.Handler}
48+ err = server.Serve(listener)
49+ if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
50+ log.Printf("ERROR: http.Serve() - %s", err)
51+ }
52+
53+ log.Printf("HTTP: closing %s", listener.Addr())
54+}
55+
56+func (s *Server) ServeHTTPS() {
57+ addr := s.Opts.HttpsAddress
58+ config := &tls.Config{
59+ MinVersion: tls.VersionTLS12,
60+ MaxVersion: tls.VersionTLS12,
61+ }
62+ if config.NextProtos == nil {
63+ config.NextProtos = []string{"http/1.1"}
64+ }
65+
66+ var err error
67+ config.Certificates = make([]tls.Certificate, 1)
68+ config.Certificates[0], err = tls.LoadX509KeyPair(s.Opts.TLSCertFile, s.Opts.TLSKeyFile)
69+ if err != nil {
70+ log.Fatalf("FATAL: loading tls config (%s, %s) failed - %s", s.Opts.TLSCertFile, s.Opts.TLSKeyFile, err)
71+ }
72+
73+ ln, err := net.Listen("tcp", addr)
74+ if err != nil {
75+ log.Fatalf("FATAL: listen (%s) failed - %s", addr, err)
76+ }
77+ log.Printf("HTTPS: listening on %s", ln.Addr())
78+
79+ tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config)
80+ srv := &http.Server{Handler: s.Handler}
81+ err = srv.Serve(tlsListener)
82+
83+ if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
84+ log.Printf("ERROR: https.Serve() - %s", err)
85+ }
86+
87+ log.Printf("HTTPS: closing %s", tlsListener.Addr())
88+}
89+
90+// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
91+// connections. It's used by ListenAndServe and ListenAndServeTLS so
92+// dead TCP connections (e.g. closing laptop mid-download) eventually
93+// go away.
94+type tcpKeepAliveListener struct {
95+ *net.TCPListener
96+}
97+
98+func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
99+ tc, err := ln.AcceptTCP()
100+ if err != nil {
101+ return
102+ }
103+ tc.SetKeepAlive(true)
104+ tc.SetKeepAlivePeriod(3 * time.Minute)
105+ return tc, nil
106+}
main.go+8−30
@@ -4,9 +4,6 @@ import (
44 "flag"
55 "fmt"
66 "log"
7− "net"
8− "net/http"
9− "net/url"
107 "os"
118 "runtime"
129 "strings"
@@ -28,6 +25,9 @@ func main() {
2825 showVersion := flagSet.Bool("version", false, "print version string")
2926
3027 flagSet.String("http-address", "127.0.0.1:4180", "[http://]<addr>:<port> or unix://<path> to listen on for HTTP clients")
28+ flagSet.String("https-address", ":443", "<addr>:<port> to listen on for HTTPS clients")
29+ flagSet.String("tls-cert", "", "path to certificate file")
30+ flagSet.String("tls-key", "", "path to private key file")
3131 flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
3232 flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint. If multiple, routing is based on path")
3333 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() {
5757
5858 flagSet.Bool("request-logging", true, "Log requests to stdout")
5959
60− flagSet.String("provider", "", "Oauth provider (defaults to Google)")
60+ flagSet.String("provider", "google", "OAuth provider")
6161 flagSet.String("login-url", "", "Authentication endpoint")
6262 flagSet.String("redeem-url", "", "Token redemption endpoint")
6363 flagSet.String("profile-url", "", "Profile access endpoint")
@@ -109,31 +109,9 @@ func main() {
109109 }
110110 }
111111
112− u, err := url.Parse(opts.HttpAddress)
113− if err != nil {
114− log.Fatalf("FATAL: could not parse %#v: %v", opts.HttpAddress, err)
115− }
116−
117− var networkType string
118− switch u.Scheme {
119− case "", "http":
120− networkType = "tcp"
121− default:
122− networkType = u.Scheme
112+ s := &Server{
113+ Handler: LoggingHandler(os.Stdout, oauthproxy, opts.RequestLogging),
114+ Opts: opts,
123115 }
124− listenAddr := strings.TrimPrefix(u.String(), u.Scheme+"://")
125−
126− listener, err := net.Listen(networkType, listenAddr)
127− if err != nil {
128− log.Fatalf("FATAL: listen (%s, %s) failed - %s", networkType, listenAddr, err)
129− }
130− log.Printf("listening on %s", listenAddr)
131−
132− server := &http.Server{Handler: LoggingHandler(os.Stdout, oauthproxy, opts.RequestLogging)}
133− err = server.Serve(listener)
134− if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
135− log.Printf("ERROR: http.Serve() - %s", err)
136− }
137−
138− log.Printf("HTTP: closing %s", listener.Addr())
116+ s.ListenAndServe()
139117}
oauthproxy.go+2−2
@@ -104,7 +104,7 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
104104 redirectUrl := opts.redirectUrl
105105 redirectUrl.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix)
106106
107− log.Printf("OauthProxy configured for %s", opts.ClientID)
107+ log.Printf("OauthProxy configured for %s Client ID: %s", opts.provider.Data().ProviderName, opts.ClientID)
108108 domain := opts.CookieDomain
109109 if domain == "" {
110110 domain = "<default>"
@@ -114,7 +114,7 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
114114 opts.CookieSecure = opts.CookieHttpsOnly
115115 }
116116
117− log.Printf("Cookie settings: secure (https):%v httponly:%v expiry:%s domain:%s", opts.CookieSecure, opts.CookieHttpOnly, opts.CookieExpire, domain)
117+ log.Printf("Cookie settings: name:%s secure (https):%v httponly:%v expiry:%s domain:%s", opts.CookieKey, opts.CookieSecure, opts.CookieHttpOnly, opts.CookieExpire, domain)
118118
119119 var aes_cipher cipher.Block
120120 if opts.PassAccessToken || (opts.CookieRefresh != time.Duration(0)) {
options.go+4
@@ -14,9 +14,12 @@ import (
1414type Options struct {
1515 ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy-prefix"`
1616 HttpAddress string `flag:"http-address" cfg:"http_address"`
17+ HttpsAddress string `flag:"https-address" cfg:"https_address"`
1718 RedirectUrl string `flag:"redirect-url" cfg:"redirect_url"`
1819 ClientID string `flag:"client-id" cfg:"client_id" env:"OAUTH2_PROXY_CLIENT_ID"`
1920 ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"OAUTH2_PROXY_CLIENT_SECRET"`
21+ TLSCertFile string `flag:"tls-cert" cfg:"tls_cert_file"`
22+ TLSKeyFile string `flag:"tls-key" cfg:"tls_key_file"`
2023
2124 AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"`
2225 EmailDomains []string `flag:"email-domain" cfg:"email_domains"`
@@ -63,6 +66,7 @@ func NewOptions() *Options {
6366 return &Options{
6467 ProxyPrefix: "/oauth2",
6568 HttpAddress: "127.0.0.1:4180",
69+ HttpsAddress: ":443",
6670 DisplayHtpasswdForm: true,
6771 CookieKey: "_oauthproxy",
6872 CookieHttpsOnly: true,