Merge pull request #68 from jehiah/upstream_host_header_68

Proxied requests use the upstream Host as Host Header
This commit is contained in:
Jehiah Czebotar 2015-03-17 16:03:53 -04:00
commit b2dfbd8564
8 changed files with 84 additions and 22 deletions

View File

@ -1,7 +1,7 @@
language: go
go:
- 1.2.2
- 1.3.3
- 1.4.2
script:
- curl -s https://raw.githubusercontent.com/pote/gpm/v1.3.1/bin/gpm > gpm
- chmod +x gpm

View File

@ -72,6 +72,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
-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
-redirect-url="": the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
-skip-auth-regex=: bypass authentication for requests path's that match (may be given multiple times)
-upstream=: the http url(s) of the upstream endpoint. If multiple, routing is based on path

View File

@ -14,6 +14,9 @@
## pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream
# pass_basic_auth = true
## pass the request Host Header to upstream
## when disabled the upstream Host is used as the Host Header
# pass_host_header = true
## Google Apps Domains to allow authentication for
# google_apps_domains = [

View File

@ -29,6 +29,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-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)")
flagSet.Var(&googleAppsDomains, "google-apps-domain", "authenticate against the given Google apps domain (may be given multiple times)")

View File

@ -46,6 +46,17 @@ type OauthProxy struct {
compiledRegex []*regexp.Regexp
}
func NewReverseProxy(target *url.URL) (proxy *httputil.ReverseProxy) {
return httputil.NewSingleHostReverseProxy(target)
}
func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
director := proxy.Director
proxy.Director = func(req *http.Request) {
director(req)
req.Host = target.Host
}
}
func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
login, _ := url.Parse("https://accounts.google.com/o/oauth2/auth")
redeem, _ := url.Parse("https://accounts.google.com/o/oauth2/token")
@ -54,7 +65,11 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
path := u.Path
u.Path = ""
log.Printf("mapping path %q => upstream %q", path, u)
serveMux.Handle(path, httputil.NewSingleHostReverseProxy(u))
proxy := NewReverseProxy(u)
if !opts.PassHostHeader {
setProxyUpstreamHostHeader(proxy, u)
}
serveMux.Handle(path, proxy)
}
for _, u := range opts.CompiledRegex {
log.Printf("compiled skip-auth-regex => %q", u)

37
oauthproxy_test.go Normal file
View File

@ -0,0 +1,37 @@
package main
import (
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"net/url"
"testing"
)
func TestNewReverseProxy(t *testing.T) {
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
hostname, _, _ := net.SplitHostPort(r.Host)
w.Write([]byte(hostname))
}))
defer backend.Close()
backendURL, _ := url.Parse(backend.URL)
backendHostname := "upstream.127.0.0.1.xip.io"
_, backendPort, _ := net.SplitHostPort(backendURL.Host)
backendHost := net.JoinHostPort(backendHostname, backendPort)
proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
proxyHandler := NewReverseProxy(proxyURL)
setProxyUpstreamHostHeader(proxyHandler, proxyURL)
frontend := httptest.NewServer(proxyHandler)
defer frontend.Close()
getReq, _ := http.NewRequest("GET", frontend.URL, nil)
res, _ := http.DefaultClient.Do(getReq)
bodyBytes, _ := ioutil.ReadAll(res.Body)
if g, e := string(bodyBytes), backendHostname; g != e {
t.Errorf("got body %q; expected %q", g, e)
}
}

View File

@ -10,22 +10,26 @@ import (
// Configuration Options that can be set by Command Line Flag, or Config File
type Options struct {
HttpAddress string `flag:"http-address" cfg:"http_address"`
RedirectUrl string `flag:"redirect-url" cfg:"redirect_url"`
ClientID string `flag:"client-id" cfg:"client_id" env:"GOOGLE_AUTH_PROXY_CLIENT_ID"`
ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"GOOGLE_AUTH_PROXY_CLIENT_SECRET"`
PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file"`
DisplayHtpasswdForm bool `flag:"display-htpasswd-form" cfg:"display_htpasswd_form"`
CookieSecret string `flag:"cookie-secret" cfg:"cookie_secret" env:"GOOGLE_AUTH_PROXY_COOKIE_SECRET"`
CookieDomain string `flag:"cookie-domain" cfg:"cookie_domain" env:"GOOGLE_AUTH_PROXY_COOKIE_DOMAIN"`
CookieExpire time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"GOOGLE_AUTH_PROXY_COOKIE_EXPIRE"`
CookieHttpsOnly bool `flag:"cookie-https-only" cfg:"cookie_https_only"`
CookieHttpOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly"`
AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"`
GoogleAppsDomains []string `flag:"google-apps-domain" cfg:"google_apps_domains"`
Upstreams []string `flag:"upstream" cfg:"upstreams"`
SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
HttpAddress string `flag:"http-address" cfg:"http_address"`
RedirectUrl string `flag:"redirect-url" cfg:"redirect_url"`
ClientID string `flag:"client-id" cfg:"client_id" env:"GOOGLE_AUTH_PROXY_CLIENT_ID"`
ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"GOOGLE_AUTH_PROXY_CLIENT_SECRET"`
AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"`
GoogleAppsDomains []string `flag:"google-apps-domain" cfg:"google_apps_domains"`
HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file"`
DisplayHtpasswdForm bool `flag:"display-htpasswd-form" cfg:"display_htpasswd_form"`
CookieSecret string `flag:"cookie-secret" cfg:"cookie_secret" env:"GOOGLE_AUTH_PROXY_COOKIE_SECRET"`
CookieDomain string `flag:"cookie-domain" cfg:"cookie_domain" env:"GOOGLE_AUTH_PROXY_COOKIE_DOMAIN"`
CookieExpire time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"GOOGLE_AUTH_PROXY_COOKIE_EXPIRE"`
CookieHttpsOnly bool `flag:"cookie-https-only" cfg:"cookie_https_only"`
CookieHttpOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly"`
Upstreams []string `flag:"upstream" cfg:"upstreams"`
SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"`
// internal values that are set after config validation
redirectUrl *url.URL
@ -39,8 +43,9 @@ func NewOptions() *Options {
DisplayHtpasswdForm: true,
CookieHttpsOnly: true,
CookieHttpOnly: true,
PassBasicAuth: true,
CookieExpire: time.Duration(168) * time.Hour,
PassBasicAuth: true,
PassHostHeader: true,
}
}

View File

@ -1,14 +1,14 @@
package main
import (
"net/url"
"strings"
"testing"
"net/url"
"github.com/bmizerany/assert"
)
func testOptions() (*Options) {
func testOptions() *Options {
o := NewOptions()
o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8080/")
o.CookieSecret = "foobar"
@ -17,7 +17,7 @@ func testOptions() (*Options) {
return o
}
func errorMsg(msgs []string)(string) {
func errorMsg(msgs []string) string {
result := make([]string, 0)
result = append(result, "Invalid configuration:")
result = append(result, msgs...)