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

Commit

Permalink
support TLS directly
Browse files Browse the repository at this point in the history
  • Loading branch information
jehiah committed Jun 8, 2015
1 parent 5a5d6df commit f5b2b20
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 44 deletions.
52 changes: 41 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ to validate accounts by email, domain or group.
[![Build Status](https://secure.travis-ci.org/bitly/oauth2_proxy.png?branch=master)](http://travis-ci.org/bitly/oauth2_proxy)


![sign_in_page](https://cloud.githubusercontent.com/assets/45028/4970624/7feb7dd8-6886-11e4-93e0-c9904af44ea8.png)
![Sign In Page](https://cloud.githubusercontent.com/assets/45028/4970624/7feb7dd8-6886-11e4-93e0-c9904af44ea8.png)

## Architecture

![oauth2_proxy_arch](https://cloud.githubusercontent.com/assets/45028/7749664/35fef390-ff9d-11e4-8d51-21a7ba78f857.png)
![OAuth2 Proxy Architecture](https://cloud.githubusercontent.com/assets/45028/8027702/bd040b7a-0d6a-11e5-85b9-f8d953d04f39.png)

## 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

Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand All @@ -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

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 ....

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
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
Expand All @@ -159,22 +188,23 @@ 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 \
--email-domain="yourcompany.com" \
--upstream=http://127.0.0.1:8080/ \
--cookie-secret=... \
--cookie-secure=true \
--provider=... \
--client-id=... \
--client-secret=...
```


## 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
Expand Down
7 changes: 6 additions & 1 deletion contrib/oauth2_proxy.cfg.example
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
106 changes: 106 additions & 0 deletions http.go
Original file line number Diff line number Diff line change
@@ -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 changes: 8 additions & 30 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import (
"flag"
"fmt"
"log"
"net"
"net/http"
"net/url"
"os"
"runtime"
"strings"
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)
}

var networkType string
switch u.Scheme {
case "", "http":
networkType = "tcp"
default:
networkType = u.Scheme
s := &Server{
Handler: LoggingHandler(os.Stdout, oauthproxy, opts.RequestLogging),
Opts: opts,
}
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()
}
4 changes: 2 additions & 2 deletions oauthproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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>"
Expand All @@ -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)) {
Expand Down
4 changes: 4 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit f5b2b20

Please sign in to comment.