From 463bc9027e9428aa22659eed8e6cfb161795ad7b Mon Sep 17 00:00:00 2001 From: Harry Brundage Date: Thu, 27 Mar 2014 16:54:46 -0400 Subject: [PATCH 1/5] Add ability to proxy websocket connections as well as normal HTTP requests --- main.go | 2 +- oauthproxy.go | 5 +- websocket_reverse_proxy.go | 117 +++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 websocket_reverse_proxy.go diff --git a/main.go b/main.go index 7cf57771b..9855bf50f 100644 --- a/main.go +++ b/main.go @@ -5,9 +5,9 @@ import ( "fmt" "log" "net" - "os" "net/http" "net/url" + "os" "strings" ) diff --git a/oauthproxy.go b/oauthproxy.go index 83924893a..db737df28 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -9,7 +9,6 @@ import ( "io/ioutil" "log" "net/http" - "net/http/httputil" "net/url" "strings" "time" @@ -44,8 +43,8 @@ func NewOauthProxy(proxyUrls []*url.URL, clientID string, clientSecret string, v for _, u := range proxyUrls { path := u.Path u.Path = "" - log.Printf("mapping %s => %s", path, u) - serveMux.Handle(path, httputil.NewSingleHostReverseProxy(u)) + log.Printf("mapping %s => %s with websockets", path, u) + serveMux.Handle(path, NewWebsocketReverseProxy(u)) } return &OauthProxy{ CookieKey: "_oauthproxy", diff --git a/websocket_reverse_proxy.go b/websocket_reverse_proxy.go new file mode 100644 index 000000000..c7123ae49 --- /dev/null +++ b/websocket_reverse_proxy.go @@ -0,0 +1,117 @@ +package main + +import ( + "bufio" + "io" + "log" + "net" + "net/http" + "net/http/httputil" + "net/url" + "strings" + "sync" +) + +type WebsocketReverseProxy struct { + Proxy *httputil.ReverseProxy + Upstream string +} + +func NewWebsocketReverseProxy(target *url.URL) *WebsocketReverseProxy { + proxy := httputil.NewSingleHostReverseProxy(target) + return &WebsocketReverseProxy{Proxy: proxy, Upstream: target.Host} +} + +func (p *WebsocketReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + if websocketUpgradeRequest(req) { + p.hijackWebsocket(rw, req) + } else { + p.Proxy.ServeHTTP(rw, req) + } +} + +func (p *WebsocketReverseProxy) hijackWebsocket(rw http.ResponseWriter, req *http.Request) { + highjacker, ok := rw.(http.Hijacker) + + if !ok { + http.Error(rw, "webserver doesn't support hijacking", http.StatusInternalServerError) + return + } + + conn, bufrw, err := highjacker.Hijack() + defer conn.Close() + + conn2, err := net.Dial("tcp", p.Upstream) + if err != nil { + log.Printf("couldn't connect to backend websocket server: %v", err) + http.Error(rw, "couldn't connect to backend server", http.StatusServiceUnavailable) + return + } + defer conn2.Close() + + err = req.Write(conn2) + if err != nil { + log.Printf("writing WebSocket request to backend server failed: %v", err) + return + } + + bufferedBidirCopy(conn, bufrw, conn2, bufio.NewReadWriter(bufio.NewReader(conn2), bufio.NewWriter(conn2))) +} + +func websocketUpgradeRequest(req *http.Request) bool { + connection_headers, ok := req.Header["Connection"] + if !ok || len(connection_headers) <= 0 { + return false + } + + connection_header := connection_headers[0] + if strings.ToLower(connection_header) != "upgrade" { + return false + } + + upgrade_headers, ok := req.Header["Upgrade"] + if !ok || len(upgrade_headers) <= 0 { + return false + } + + return strings.ToLower(upgrade_headers[0]) == "websocket" +} + +func bufferedCopy(dest *bufio.ReadWriter, src *bufio.ReadWriter) { + buf := make([]byte, 40*1024) + for { + n, err := src.Read(buf) + if err != nil && err != io.EOF { + log.Printf("Upstream read failed: %v", err) + return + } + if n == 0 { + return + } + n, err = dest.Write(buf[0:n]) + if err != nil && err != io.EOF { + log.Printf("Downstream write failed: %v", err) + return + } + + err = dest.Flush() + if err != nil { + log.Printf("Downstream write flush failed: %v", err) + return + } + } +} + +func bufferedBidirCopy(conn1 io.ReadWriteCloser, rw1 *bufio.ReadWriter, conn2 io.ReadWriteCloser, rw2 *bufio.ReadWriter) { + wg := sync.WaitGroup{} + + copier := func(wg *sync.WaitGroup, rw1 *bufio.ReadWriter, rw2 *bufio.ReadWriter) { + defer wg.Done() + bufferedCopy(rw2, rw1) + } + + wg.Add(2) + go copier(&wg, rw1, rw2) + go copier(&wg, rw2, rw1) + wg.Wait() +} From 68db406c840f94273e672049de59a6b089a14850 Mon Sep 17 00:00:00 2001 From: Jens Henrik Hertz Date: Fri, 27 Feb 2015 15:19:44 +0100 Subject: [PATCH 2/5] Make redirection work properly --- oauthproxy.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/oauthproxy.go b/oauthproxy.go index 4ef4c8c7d..05d2ef869 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -97,9 +97,7 @@ func (p *OauthProxy) GetLoginURL(redirectUrl string) string { params.Add("scope", p.oauthScope) params.Add("client_id", p.clientID) params.Add("response_type", "code") - if strings.HasPrefix(redirectUrl, "/") { - params.Add("state", redirectUrl) - } + params.Add("state", redirectUrl) return fmt.Sprintf("%s?%s", p.oauthLoginUrl, params.Encode()) } @@ -253,9 +251,10 @@ func (p *OauthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code }{ SignInMessage: p.SignInMessage, CustomLogin: p.displayCustomLoginForm(), - Redirect: req.URL.RequestURI(), + Redirect: fmt.Sprintf("https://%s%s", req.Host, req.URL.RequestURI()), Version: VERSION, } + templates.ExecuteTemplate(rw, "sign_in.html", t) } From e8d8b6bc14479486cffb813e40be3806b6e4b93e Mon Sep 17 00:00:00 2001 From: Jens Henrik Hertz Date: Thu, 5 Mar 2015 14:39:49 +0100 Subject: [PATCH 3/5] Include the redirect everywhere --- gap.cfg | 46 ++++++++++++++++++++++++++++++++++++++++++++++ oauthproxy.go | 23 +++++++++++++++-------- templates.go | 2 +- 3 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 gap.cfg diff --git a/gap.cfg b/gap.cfg new file mode 100644 index 000000000..89559f697 --- /dev/null +++ b/gap.cfg @@ -0,0 +1,46 @@ +## Google Auth Proxy Config File +## https://github.com/bitly/google_auth_proxy + +## : to listen on for HTTP clients +# http_address = "127.0.0.1:4180" + +## the OAuth Redirect URL. +redirect_url = "https://auth.int.treatwell.com/oauth2/callback" + +## the http url(s) of the upstream endpoint. If multiple, routing is based on path +upstreams = [ + "http://127.0.0.1:8080/" +] + +## pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream +# pass_basic_auth = true + +## Google Apps Domains to allow authentication for +google_apps_domains = [ + "treatwell.nl" +] + + +## The Google OAuth Client ID, Secret +client_id = "598239317768-0i5vee2o45qpjqj4ivimdutnff79lqou.apps.googleusercontent.com" +client_secret = "sSzmqIahuWFKt9ghdYcE0zjr" + +## Authenticated Email Addresses File (one email per line) +# authenticated_emails_file = "" + +## Htpasswd File (optional) +## Additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption +## enabling exposes a username/login signin form +#htpasswd_file = "/opt/htpasswd" + + +## Cookie Settings +## Secret - the seed string for secure cookies +## Domain - optional cookie domain to force cookies to (ie: .yourcompany.com) +## Expire - expire timeframe for cookie +# cookie_secret = "" +cookie_domain = "int.treatwell.com" +cookie_expire = "168h" +# cookie_https_only = true +# cookie_httponly = true + diff --git a/oauthproxy.go b/oauthproxy.go index 05d2ef869..51ef91523 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -224,16 +224,18 @@ func (p *OauthProxy) PingPage(rw http.ResponseWriter) { fmt.Fprintf(rw, "OK") } -func (p *OauthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, message string) { +func (p *OauthProxy) ErrorPage(rw http.ResponseWriter, req *http.Request, code int, title string, message string) { log.Printf("ErrorPage %d %s %s", code, title, message) rw.WriteHeader(code) templates := getTemplates() t := struct { Title string Message string + Redirect string }{ Title: fmt.Sprintf("%d %s", code, title), Message: message, + Redirect: req.Form.Get("state"), } templates.ExecuteTemplate(rw, "error.html", t) } @@ -243,6 +245,11 @@ func (p *OauthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code rw.WriteHeader(code) templates := getTemplates() + redirect := req.FormValue("rd") + if redirect == "" { + redirect = fmt.Sprintf("https://%s%s", req.Host, req.URL.RequestURI()) + } + t := struct { SignInMessage string CustomLogin bool @@ -251,7 +258,7 @@ func (p *OauthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code }{ SignInMessage: p.SignInMessage, CustomLogin: p.displayCustomLoginForm(), - Redirect: fmt.Sprintf("https://%s%s", req.Host, req.URL.RequestURI()), + Redirect: redirect, Version: VERSION, } @@ -320,7 +327,7 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if req.URL.Path == signInPath { redirect, err := p.GetRedirect(req) if err != nil { - p.ErrorPage(rw, 500, "Internal Error", err.Error()) + p.ErrorPage(rw, req, 500, "Internal Error", err.Error()) return } @@ -336,7 +343,7 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if req.URL.Path == oauthStartPath { redirect, err := p.GetRedirect(req) if err != nil { - p.ErrorPage(rw, 500, "Internal Error", err.Error()) + p.ErrorPage(rw, req, 500, "Internal Error", err.Error()) return } http.Redirect(rw, req, p.GetLoginURL(redirect), 302) @@ -346,19 +353,19 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { // finish the oauth cycle err := req.ParseForm() if err != nil { - p.ErrorPage(rw, 500, "Internal Error", err.Error()) + p.ErrorPage(rw, req, 500, "Internal Error", err.Error()) return } errorString := req.Form.Get("error") if errorString != "" { - p.ErrorPage(rw, 403, "Permission Denied", errorString) + p.ErrorPage(rw, req, 403, "Permission Denied", errorString) return } _, email, err := p.redeemCode(req.Form.Get("code")) if err != nil { log.Printf("%s error redeeming code %s", remoteAddr, err) - p.ErrorPage(rw, 500, "Internal Error", err.Error()) + p.ErrorPage(rw, req, 500, "Internal Error", err.Error()) return } @@ -374,7 +381,7 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { http.Redirect(rw, req, redirect, 302) return } else { - p.ErrorPage(rw, 403, "Permission Denied", "Invalid Account") + p.ErrorPage(rw, req, 403, "Permission Denied", "Invalid Account") return } } diff --git a/templates.go b/templates.go index 202114cb6..556e4e580 100644 --- a/templates.go +++ b/templates.go @@ -137,7 +137,7 @@ func getTemplates() *template.Template {

{{.Title}}

{{.Message}}


-

Sign In

+

Sign In

{{end}}`) if err != nil { From ea47935c3407d70f569285f4066a0000f5669b17 Mon Sep 17 00:00:00 2001 From: Jens Henrik Hertz Date: Mon, 30 Mar 2015 11:41:35 +0200 Subject: [PATCH 4/5] Forward basic auth, if other means were used for authenticating --- oauthproxy.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/oauthproxy.go b/oauthproxy.go index 51ef91523..a3c7e2933 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -394,8 +394,11 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } } + authedByBasicAuth := false + if !ok { user, ok = p.CheckBasicAuth(req) + authedByBasicAuth = ok // if we want to promote basic auth requests to cookie'd requests, we could do that here // not sure that would be ideal in all circumstances though // if ok { @@ -411,7 +414,11 @@ func (p *OauthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { // At this point, the user is authenticated. proxy normally if p.PassBasicAuth { - req.SetBasicAuth(user, "") + if (authedByBasicAuth) { + // Strip the password if the basic auth was used to identify the google_auth_proxy user + // otherwise, just pass the basic auth information along. + req.SetBasicAuth(user, "") + } req.Header["X-Forwarded-User"] = []string{user} req.Header["X-Forwarded-Email"] = []string{email} } From 6f6eb28e17f23d1caaf8c941b84c55f61fd19b2d Mon Sep 17 00:00:00 2001 From: Steve Storey Date: Fri, 16 Mar 2018 11:59:49 +0000 Subject: [PATCH 5/5] Update the Google API keys to new Treatwell project keys --- gap.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gap.cfg b/gap.cfg index 89559f697..ef22cbb25 100644 --- a/gap.cfg +++ b/gap.cfg @@ -22,8 +22,8 @@ google_apps_domains = [ ## The Google OAuth Client ID, Secret -client_id = "598239317768-0i5vee2o45qpjqj4ivimdutnff79lqou.apps.googleusercontent.com" -client_secret = "sSzmqIahuWFKt9ghdYcE0zjr" +client_id = "772435130105-ppfk945bgmv2oejestd936cpkqgh2h6p.apps.googleusercontent.com" +client_secret = "G_fMH57Mp6gOh7M1XJyY5oie" ## Authenticated Email Addresses File (one email per line) # authenticated_emails_file = ""