git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge pull request #68 from jehiah/upstream_host_header_68
b2dfbd8Jehiah Czebotar
.travis.yml+1−1
@@ -1,7 +1,7 @@
1language: go
1language: go
2go:
2go:
3− - 1.2.2
4 - 1.3.3
3 - 1.3.3
4+ - 1.4.2
5script:
5script:
6 - curl -s https://raw.githubusercontent.com/pote/gpm/v1.3.1/bin/gpm > gpm
6 - curl -s https://raw.githubusercontent.com/pote/gpm/v1.3.1/bin/gpm > gpm
7 - chmod +x gpm
7 - chmod +x gpm
README.md+1
@@ -72,6 +72,7 @@ Usage of google_auth_proxy:
72 -htpasswd-file="": additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption
72 -htpasswd-file="": additionally authenticate against a htpasswd file. Entries must be created with "htpasswd -s" for SHA encryption
73 -http-address="127.0.0.1:4180": [http://]<addr>:<port> or unix://<path> to listen on for HTTP clients
73 -http-address="127.0.0.1:4180": [http://]<addr>:<port> or unix://<path> to listen on for HTTP clients
74 -pass-basic-auth=true: pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream
74 -pass-basic-auth=true: pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream
75+ -pass-host-header=true: pass the request Host Header to upstream
75 -redirect-url="": the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
76 -redirect-url="": the OAuth Redirect URL. ie: "https://internalapp.yourcompany.com/oauth2/callback"
76 -skip-auth-regex=: bypass authentication for requests path's that match (may be given multiple times)
77 -skip-auth-regex=: bypass authentication for requests path's that match (may be given multiple times)
77 -upstream=: the http url(s) of the upstream endpoint. If multiple, routing is based on path
78 -upstream=: the http url(s) of the upstream endpoint. If multiple, routing is based on path
contrib/google_auth_proxy.cfg.example+3
@@ -14,6 +14,9 @@
14
14
15## pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream
15## pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream
16# pass_basic_auth = true
16# pass_basic_auth = true
17+## pass the request Host Header to upstream
18+## when disabled the upstream Host is used as the Host Header
19+# pass_host_header = true
17
20
18## Google Apps Domains to allow authentication for
21## Google Apps Domains to allow authentication for
19# google_apps_domains = [
22# google_apps_domains = [
main.go+1
@@ -29,6 +29,7 @@ func main() {
29 flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
29 flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
30 flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint. If multiple, routing is based on path")
30 flagSet.Var(&upstreams, "upstream", "the http url(s) of the upstream endpoint. If multiple, routing is based on path")
31 flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream")
31 flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream")
32+ flagSet.Bool("pass-host-header", true, "pass the request Host Header to upstream")
32 flagSet.Var(&skipAuthRegex, "skip-auth-regex", "bypass authentication for requests path's that match (may be given multiple times)")
33 flagSet.Var(&skipAuthRegex, "skip-auth-regex", "bypass authentication for requests path's that match (may be given multiple times)")
33
34
34 flagSet.Var(&googleAppsDomains, "google-apps-domain", "authenticate against the given Google apps domain (may be given multiple times)")
35 flagSet.Var(&googleAppsDomains, "google-apps-domain", "authenticate against the given Google apps domain (may be given multiple times)")
oauthproxy.go+16−1
@@ -46,6 +46,17 @@ type OauthProxy struct {
46 compiledRegex []*regexp.Regexp
46 compiledRegex []*regexp.Regexp
47}
47}
48
48
49+func NewReverseProxy(target *url.URL) (proxy *httputil.ReverseProxy) {
50+ return httputil.NewSingleHostReverseProxy(target)
51+}
52+func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
53+ director := proxy.Director
54+ proxy.Director = func(req *http.Request) {
55+ director(req)
56+ req.Host = target.Host
57+ }
58+}
59+
49func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
60func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
50 login, _ := url.Parse("https://accounts.google.com/o/oauth2/auth")
61 login, _ := url.Parse("https://accounts.google.com/o/oauth2/auth")
51 redeem, _ := url.Parse("https://accounts.google.com/o/oauth2/token")
62 redeem, _ := url.Parse("https://accounts.google.com/o/oauth2/token")
@@ -54,7 +65,11 @@ func NewOauthProxy(opts *Options, validator func(string) bool) *OauthProxy {
54 path := u.Path
65 path := u.Path
55 u.Path = ""
66 u.Path = ""
56 log.Printf("mapping path %q => upstream %q", path, u)
67 log.Printf("mapping path %q => upstream %q", path, u)
68+ proxy := NewReverseProxy(u)
69+ if !opts.PassHostHeader {
70+ setProxyUpstreamHostHeader(proxy, u)
71+ }
57− serveMux.Handle(path, httputil.NewSingleHostReverseProxy(u))
72+ serveMux.Handle(path, proxy)
58 }
73 }
59 for _, u := range opts.CompiledRegex {
74 for _, u := range opts.CompiledRegex {
60 log.Printf("compiled skip-auth-regex => %q", u)
75 log.Printf("compiled skip-auth-regex => %q", u)
oauthproxy_test.gonew file+37
@@ -0,0 +1,37 @@
1+package main
2+
3+import (
4+ "io/ioutil"
5+ "net"
6+ "net/http"
7+ "net/http/httptest"
8+ "net/url"
9+ "testing"
10+)
11+
12+func TestNewReverseProxy(t *testing.T) {
13+ backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
14+ w.WriteHeader(200)
15+ hostname, _, _ := net.SplitHostPort(r.Host)
16+ w.Write([]byte(hostname))
17+ }))
18+ defer backend.Close()
19+
20+ backendURL, _ := url.Parse(backend.URL)
21+ backendHostname := "upstream.127.0.0.1.xip.io"
22+ _, backendPort, _ := net.SplitHostPort(backendURL.Host)
23+ backendHost := net.JoinHostPort(backendHostname, backendPort)
24+ proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
25+
26+ proxyHandler := NewReverseProxy(proxyURL)
27+ setProxyUpstreamHostHeader(proxyHandler, proxyURL)
28+ frontend := httptest.NewServer(proxyHandler)
29+ defer frontend.Close()
30+
31+ getReq, _ := http.NewRequest("GET", frontend.URL, nil)
32+ res, _ := http.DefaultClient.Do(getReq)
33+ bodyBytes, _ := ioutil.ReadAll(res.Body)
34+ if g, e := string(bodyBytes), backendHostname; g != e {
35+ t.Errorf("got body %q; expected %q", g, e)
36+ }
37+}
options.go+22−17
@@ -10,22 +10,26 @@ import (
10
10
11// Configuration Options that can be set by Command Line Flag, or Config File
11// Configuration Options that can be set by Command Line Flag, or Config File
12type Options struct {
12type Options struct {
13− HttpAddress string `flag:"http-address" cfg:"http_address"`
13+ HttpAddress string `flag:"http-address" cfg:"http_address"`
14− RedirectUrl string `flag:"redirect-url" cfg:"redirect_url"`
14+ RedirectUrl string `flag:"redirect-url" cfg:"redirect_url"`
15− ClientID string `flag:"client-id" cfg:"client_id" env:"GOOGLE_AUTH_PROXY_CLIENT_ID"`
15+ ClientID string `flag:"client-id" cfg:"client_id" env:"GOOGLE_AUTH_PROXY_CLIENT_ID"`
16− ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"GOOGLE_AUTH_PROXY_CLIENT_SECRET"`
16+ ClientSecret string `flag:"client-secret" cfg:"client_secret" env:"GOOGLE_AUTH_PROXY_CLIENT_SECRET"`
17− PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
17+
18+ AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"`
19+ GoogleAppsDomains []string `flag:"google-apps-domain" cfg:"google_apps_domains"`
18− HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file"`
20+ HtpasswdFile string `flag:"htpasswd-file" cfg:"htpasswd_file"`
19− DisplayHtpasswdForm bool `flag:"display-htpasswd-form" cfg:"display_htpasswd_form"`
21+ DisplayHtpasswdForm bool `flag:"display-htpasswd-form" cfg:"display_htpasswd_form"`
22+
20− CookieSecret string `flag:"cookie-secret" cfg:"cookie_secret" env:"GOOGLE_AUTH_PROXY_COOKIE_SECRET"`
23+ CookieSecret string `flag:"cookie-secret" cfg:"cookie_secret" env:"GOOGLE_AUTH_PROXY_COOKIE_SECRET"`
21− CookieDomain string `flag:"cookie-domain" cfg:"cookie_domain" env:"GOOGLE_AUTH_PROXY_COOKIE_DOMAIN"`
24+ CookieDomain string `flag:"cookie-domain" cfg:"cookie_domain" env:"GOOGLE_AUTH_PROXY_COOKIE_DOMAIN"`
22− CookieExpire time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"GOOGLE_AUTH_PROXY_COOKIE_EXPIRE"`
25+ CookieExpire time.Duration `flag:"cookie-expire" cfg:"cookie_expire" env:"GOOGLE_AUTH_PROXY_COOKIE_EXPIRE"`
23− CookieHttpsOnly bool `flag:"cookie-https-only" cfg:"cookie_https_only"`
26+ CookieHttpsOnly bool `flag:"cookie-https-only" cfg:"cookie_https_only"`
24− CookieHttpOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly"`
27+ CookieHttpOnly bool `flag:"cookie-httponly" cfg:"cookie_httponly"`
25− AuthenticatedEmailsFile string `flag:"authenticated-emails-file" cfg:"authenticated_emails_file"`
28+
26− GoogleAppsDomains []string `flag:"google-apps-domain" cfg:"google_apps_domains"`
27− Upstreams []string `flag:"upstream" cfg:"upstreams"`
29+ Upstreams []string `flag:"upstream" cfg:"upstreams"`
28− SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
30+ SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
31+ PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
32+ PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"`
29
33
30 // internal values that are set after config validation
34 // internal values that are set after config validation
31 redirectUrl *url.URL
35 redirectUrl *url.URL
@@ -39,8 +43,9 @@ func NewOptions() *Options {
39 DisplayHtpasswdForm: true,
43 DisplayHtpasswdForm: true,
40 CookieHttpsOnly: true,
44 CookieHttpsOnly: true,
41 CookieHttpOnly: true,
45 CookieHttpOnly: true,
42− PassBasicAuth: true,
43 CookieExpire: time.Duration(168) * time.Hour,
46 CookieExpire: time.Duration(168) * time.Hour,
47+ PassBasicAuth: true,
48+ PassHostHeader: true,
44 }
49 }
45}
50}
46
51
options_test.go+3−3
@@ -1,14 +1,14 @@
1package main
1package main
2
2
3import (
3import (
4+ "net/url"
4 "strings"
5 "strings"
5 "testing"
6 "testing"
6− "net/url"
7
7
8 "github.com/bmizerany/assert"
8 "github.com/bmizerany/assert"
9)
9)
10
10
11−func testOptions() (*Options) {
11+func testOptions() *Options {
12 o := NewOptions()
12 o := NewOptions()
13 o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8080/")
13 o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8080/")
14 o.CookieSecret = "foobar"
14 o.CookieSecret = "foobar"
@@ -17,7 +17,7 @@ func testOptions() (*Options) {
17 return o
17 return o
18}
18}
19
19
20−func errorMsg(msgs []string)(string) {
20+func errorMsg(msgs []string) string {
21 result := make([]string, 0)
21 result := make([]string, 0)
22 result = append(result, "Invalid configuration:")
22 result = append(result, "Invalid configuration:")
23 result = append(result, msgs...)
23 result = append(result, msgs...)