Skip to content
This repository was archived by the owner on Jan 24, 2019. It is now read-only.

Commit 1946739

Browse files
committed
Merge pull request #99 from jehiah/ssl_99
Native SSL support
2 parents 5a5d6df + f5b2b20 commit 1946739

6 files changed

Lines changed: 167 additions & 44 deletions

File tree

README.md

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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

2020
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`
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

7777
The [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

131138
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.
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
137166
endpoint for this example would be `https://internal.yourcompany.com/`.
138167

139168
An example Nginx config follows. Note the use of `Strict-Transport-Security` header to pin requests to SSL
@@ -159,22 +188,23 @@ 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 \
166195
--email-domain="yourcompany.com" \
167196
--upstream=http://127.0.0.1:8080/ \
168197
--cookie-secret=... \
169198
--cookie-secure=true \
199+
--provider=... \
170200
--client-id=... \
171201
--client-secret=...
172202
```
173203

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ import (
1414
type 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,

0 commit comments

Comments
 (0)