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

Commit

Permalink
Merge pull request #80 from 18F/pass-access-token
Browse files Browse the repository at this point in the history
Pass the access token to the upstream server
  • Loading branch information
jehiah committed Apr 3, 2015
2 parents 66d4d72 + ad3c9a8 commit 864d478
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 9 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Usage of google_auth_proxy:
-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
-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
Expand Down
38 changes: 38 additions & 0 deletions cookies.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package main

import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"fmt"
"io"
"net/http"
"strconv"
"strings"
Expand Down Expand Up @@ -59,3 +63,37 @@ func checkHmac(input, expected string) bool {
}
return false
}

func encodeAccessToken(aes_cipher cipher.Block, access_token string) (string, error) {
ciphertext := make([]byte, aes.BlockSize+len(access_token))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return "", fmt.Errorf("failed to create access code initialization vector")
}

stream := cipher.NewCFBEncrypter(aes_cipher, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(access_token))
return base64.StdEncoding.EncodeToString(ciphertext), nil
}

func decodeAccessToken(aes_cipher cipher.Block, encoded_access_token string) (string, error) {
encrypted_access_token, err := base64.StdEncoding.DecodeString(
encoded_access_token)

if err != nil {
return "", fmt.Errorf("failed to decode access token")
}

if len(encrypted_access_token) < aes.BlockSize {
return "", fmt.Errorf("encrypted access token should be "+
"at least %d bytes, but is only %d bytes",
aes.BlockSize, len(encrypted_access_token))
}

iv := encrypted_access_token[:aes.BlockSize]
encrypted_access_token = encrypted_access_token[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(aes_cipher, iv)
stream.XORKeyStream(encrypted_access_token, encrypted_access_token)

return string(encrypted_access_token), nil
}
23 changes: 23 additions & 0 deletions cookies_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

import (
"crypto/aes"
"github.com/bmizerany/assert"
"testing"
)

func TestEncodeAndDecodeAccessToken(t *testing.T) {
const key = "0123456789abcdefghijklmnopqrstuv"
const access_token = "my access token"
c, err := aes.NewCipher([]byte(key))
assert.Equal(t, nil, err)

encoded_token, err := encodeAccessToken(c, access_token)
assert.Equal(t, nil, err)

decoded_token, err := decodeAccessToken(c, encoded_token)
assert.Equal(t, nil, err)

assert.NotEqual(t, access_token, encoded_token)
assert.Equal(t, access_token, decoded_token)
}
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func main() {
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")
flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header")
flagSet.Bool("pass-host-header", true, "pass the request Host Header to upstream")
flagSet.Var(&skipAuthRegex, "skip-auth-regex", "bypass authentication for requests path's that match (may be given multiple times)")

Expand Down
61 changes: 58 additions & 3 deletions oauthproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package main

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
"fmt"
Expand Down Expand Up @@ -45,6 +47,8 @@ type OauthProxy struct {
DisplayHtpasswdForm bool
serveMux http.Handler
PassBasicAuth bool
PassAccessToken bool
AesCipher cipher.Block
skipAuthRegex []string
compiledRegex []*regexp.Regexp
templates *template.Template
Expand Down Expand Up @@ -116,6 +120,29 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {

log.Printf("Cookie settings: secure (https):%v httponly:%v expiry:%s domain:%s", opts.CookieSecure, opts.CookieHttpOnly, opts.CookieExpire, domain)

var aes_cipher cipher.Block

if opts.PassAccessToken == true {
valid_cookie_secret_size := false
for _, i := range []int{16, 24, 32} {
if len(opts.CookieSecret) == i {
valid_cookie_secret_size = true
}
}
if valid_cookie_secret_size == false {
log.Fatal("cookie_secret must be 16, 24, or 32 bytes " +
"to create an AES cipher when " +
"pass_access_token == true")
}

var err error
aes_cipher, err = aes.NewCipher([]byte(opts.CookieSecret))
if err != nil {
log.Fatal("error creating AES cipher with "+
"pass_access_token == true: %s", err)
}
}

return &OauthProxy{
CookieKey: "_oauthproxy",
CookieSeed: opts.CookieSecret,
Expand All @@ -136,6 +163,8 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
skipAuthRegex: opts.SkipAuthRegex,
compiledRegex: opts.CompiledRegex,
PassBasicAuth: opts.PassBasicAuth,
PassAccessToken: opts.PassAccessToken,
AesCipher: aes_cipher,
templates: loadTemplates(opts.CustomTemplatesDir),
}
}
Expand Down Expand Up @@ -337,6 +366,7 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
var ok bool
var user string
var email string
var access_token string

if req.URL.Path == pingPath {
p.PingPage(rw)
Expand Down Expand Up @@ -390,7 +420,7 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
return
}

_, email, err := p.redeemCode(req.Host, req.Form.Get("code"))
access_token, email, err = p.redeemCode(req.Host, req.Form.Get("code"))
if err != nil {
log.Printf("%s error redeeming code %s", remoteAddr, err)
p.ErrorPage(rw, 500, "Internal Error", err.Error())
Expand All @@ -405,7 +435,20 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// set cookie, or deny
if p.Validator(email) {
log.Printf("%s authenticating %s completed", remoteAddr, email)
p.SetCookie(rw, req, email)
encoded_token := ""
if p.PassAccessToken {
encoded_token, err = encodeAccessToken(p.AesCipher, access_token)
if err != nil {
log.Printf("error encoding access token: %s", err)
}
}
access_token = ""

if encoded_token != "" {
p.SetCookie(rw, req, email+"|"+encoded_token)
} else {
p.SetCookie(rw, req, email)
}
http.Redirect(rw, req, redirect, 302)
return
} else {
Expand All @@ -417,7 +460,16 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if !ok {
cookie, err := req.Cookie(p.CookieKey)
if err == nil {
email, ok = validateCookie(cookie, p.CookieSeed)
var value string
value, ok = validateCookie(cookie, p.CookieSeed)
components := strings.Split(value, "|")
email = components[0]
if len(components) == 2 {
access_token, err = decodeAccessToken(p.AesCipher, components[1])
if err != nil {
log.Printf("error decoding access token: %s", err)
}
}
user = strings.Split(email, "@")[0]
}
}
Expand All @@ -437,6 +489,9 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
req.Header["X-Forwarded-User"] = []string{user}
req.Header["X-Forwarded-Email"] = []string{email}
}
if access_token != "" {
req.Header["X-Forwarded-Access-Token"] = []string{access_token}
}
if email == "" {
rw.Header().Set("GAP-Auth", user)
} else {
Expand Down
Loading

0 comments on commit 864d478

Please sign in to comment.