git.iko.soy/typeable/oauth2-proxy
on master help sign in
Configure OAuth2 Proxy to use new upstreams package and LegacyConfig
commit5dbcd737229e0b12a2613e6455d609e2428d6ab6 authorJoel Speed <joel.speed@hotmail.co.uk> dateTue 26 May 2020, 20:06 (6 years ago) parente932381
3 files changed+71−330
main.go+8−2 oauthproxy.go+9−178 oauthproxy_test.go+54−150
main.go+8−2
@@ -32,13 +32,19 @@ func main() {
3232 return
3333 }
3434
35− opts := options.NewOptions()
36− err := options.Load(*config, flagSet, opts)
35+ legacyOpts := options.NewLegacyOptions()
36+ err := options.Load(*config, flagSet, legacyOpts)
3737 if err != nil {
3838 logger.Printf("ERROR: Failed to load config: %v", err)
3939 os.Exit(1)
4040 }
4141
42+ opts, err := legacyOpts.ToOptions()
43+ if err != nil {
44+ logger.Printf("ERROR: Failed to convert config: %v", err)
45+ os.Exit(1)
46+ }
47+
4248 err = validation.Validate(opts)
4349 if err != nil {
4450 logger.Printf("%s", err)
oauthproxy.go+9−178
@@ -2,7 +2,6 @@ package main
22
33import (
44 "context"
5− "crypto/tls"
65 b64 "encoding/base64"
76 "encoding/json"
87 "errors"
@@ -10,15 +9,12 @@ import (
109 "html/template"
1110 "net"
1211 "net/http"
13− "net/http/httputil"
1412 "net/url"
1513 "regexp"
16− "strconv"
1714 "strings"
1815 "time"
1916
2017 "github.com/coreos/go-oidc"
21− "github.com/mbland/hmacauth"
2218 ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip"
2319 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
2420 sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
@@ -28,37 +24,17 @@ import (
2824 "github.com/oauth2-proxy/oauth2-proxy/pkg/ip"
2925 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
3026 "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions"
27+ "github.com/oauth2-proxy/oauth2-proxy/pkg/upstream"
3128 "github.com/oauth2-proxy/oauth2-proxy/providers"
32− "github.com/yhat/wsutil"
3329)
3430
3531const (
36− // SignatureHeader is the name of the request header containing the GAP Signature
37− // Part of hmacauth
38− SignatureHeader = "GAP-Signature"
39−
4032 httpScheme = "http"
4133 httpsScheme = "https"
4234
4335 applicationJSON = "application/json"
4436)
4537
46−// SignatureHeaders contains the headers to be signed by the hmac algorithm
47−// Part of hmacauth
48−var SignatureHeaders = []string{
49− "Content-Length",
50− "Content-Md5",
51− "Content-Type",
52− "Date",
53− "Authorization",
54− "X-Forwarded-User",
55− "X-Forwarded-Email",
56− "X-Forwarded-Preferred-User",
57− "X-Forwarded-Access-Token",
58− "Cookie",
59− "Gap-Auth",
60−}
61−
6238var (
6339 // ErrNeedsLogin means the user should be redirected to the login page
6440 ErrNeedsLogin = errors.New("redirect to login page")
@@ -124,116 +100,6 @@ type OAuthProxy struct {
124100 Footer string
125101}
126102
127−// UpstreamProxy represents an upstream server to proxy to
128−type UpstreamProxy struct {
129− upstream string
130− handler http.Handler
131− wsHandler http.Handler
132− auth hmacauth.HmacAuth
133−}
134−
135−// ServeHTTP proxies requests to the upstream provider while signing the
136−// request headers
137−func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
138− w.Header().Set("GAP-Upstream-Address", u.upstream)
139− if u.auth != nil {
140− r.Header.Set("GAP-Auth", w.Header().Get("GAP-Auth"))
141− u.auth.SignRequest(r)
142− }
143− if u.wsHandler != nil && strings.EqualFold(r.Header.Get("Connection"), "upgrade") && r.Header.Get("Upgrade") == "websocket" {
144− u.wsHandler.ServeHTTP(w, r)
145− } else {
146− u.handler.ServeHTTP(w, r)
147− }
148−
149−}
150−
151−// NewReverseProxy creates a new reverse proxy for proxying requests to upstream
152−// servers
153−func NewReverseProxy(target *url.URL, opts *options.Options) (proxy *httputil.ReverseProxy) {
154− proxy = httputil.NewSingleHostReverseProxy(target)
155− proxy.FlushInterval = opts.FlushInterval
156− if opts.SSLUpstreamInsecureSkipVerify {
157− proxy.Transport = &http.Transport{
158− TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
159− }
160− }
161− setProxyErrorHandler(proxy, opts)
162− return proxy
163−}
164−
165−func setProxyErrorHandler(proxy *httputil.ReverseProxy, opts *options.Options) {
166− templates := loadTemplates(opts.CustomTemplatesDir)
167− proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, proxyErr error) {
168− logger.Printf("Error proxying to upstream server: %v", proxyErr)
169− w.WriteHeader(http.StatusBadGateway)
170− data := struct {
171− Title string
172− Message string
173− ProxyPrefix string
174− }{
175− Title: "Bad Gateway",
176− Message: "Error proxying to upstream server",
177− ProxyPrefix: opts.ProxyPrefix,
178− }
179− templates.ExecuteTemplate(w, "error.html", data)
180− }
181−}
182−
183−func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
184− director := proxy.Director
185− proxy.Director = func(req *http.Request) {
186− director(req)
187− // use RequestURI so that we aren't unescaping encoded slashes in the request path
188− req.Host = target.Host
189− req.URL.Opaque = req.RequestURI
190− req.URL.RawQuery = ""
191− }
192−}
193−
194−func setProxyDirector(proxy *httputil.ReverseProxy) {
195− director := proxy.Director
196− proxy.Director = func(req *http.Request) {
197− director(req)
198− // use RequestURI so that we aren't unescaping encoded slashes in the request path
199− req.URL.Opaque = req.RequestURI
200− req.URL.RawQuery = ""
201− }
202−}
203−
204−// NewFileServer creates a http.Handler to serve files from the filesystem
205−func NewFileServer(path string, filesystemPath string) (proxy http.Handler) {
206− return http.StripPrefix(path, http.FileServer(http.Dir(filesystemPath)))
207−}
208−
209−// NewWebSocketOrRestReverseProxy creates a reverse proxy for REST or websocket based on url
210−func NewWebSocketOrRestReverseProxy(u *url.URL, opts *options.Options, auth hmacauth.HmacAuth) http.Handler {
211− u.Path = ""
212− proxy := NewReverseProxy(u, opts)
213− if !opts.PassHostHeader {
214− setProxyUpstreamHostHeader(proxy, u)
215− } else {
216− setProxyDirector(proxy)
217− }
218−
219− // this should give us a wss:// scheme if the url is https:// based.
220− var wsProxy *wsutil.ReverseProxy
221− if opts.ProxyWebSockets {
222− wsScheme := "ws" + strings.TrimPrefix(u.Scheme, "http")
223− wsURL := &url.URL{Scheme: wsScheme, Host: u.Host}
224− wsProxy = wsutil.NewSingleHostReverseProxy(wsURL)
225− if opts.SSLUpstreamInsecureSkipVerify {
226− wsProxy.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
227− }
228− }
229− return &UpstreamProxy{
230− upstream: u.Host,
231− handler: proxy,
232− wsHandler: wsProxy,
233− auth: auth,
234− }
235−}
236−
237103// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
238104func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthProxy, error) {
239105 sessionStore, err := sessions.NewSessionStore(&opts.Session, &opts.Cookie)
@@ -241,48 +107,13 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
241107 return nil, fmt.Errorf("error initialising session store: %v", err)
242108 }
243109
244− serveMux := http.NewServeMux()
245− var auth hmacauth.HmacAuth
246− if sigData := opts.GetSignatureData(); sigData != nil {
247− auth = hmacauth.NewHmacAuth(sigData.Hash, []byte(sigData.Key),
248− SignatureHeader, SignatureHeaders)
249− }
250− for _, u := range opts.GetProxyURLs() {
251− path := u.Path
252− host := u.Host
253− switch u.Scheme {
254− case httpScheme, httpsScheme:
255− logger.Printf("mapping path %q => upstream %q", path, u)
256− proxy := NewWebSocketOrRestReverseProxy(u, opts, auth)
257− serveMux.Handle(path, proxy)
258− case "static":
259− responseCode, err := strconv.Atoi(host)
260− if err != nil {
261− logger.Printf("unable to convert %q to int, use default \"200\"", host)
262− responseCode = 200
263− }
264−
265− serveMux.HandleFunc(path, func(rw http.ResponseWriter, req *http.Request) {
266− rw.WriteHeader(responseCode)
267− fmt.Fprintf(rw, "Authenticated")
268− })
269− case "file":
270− if u.Fragment != "" {
271− path = u.Fragment
272− }
273− logger.Printf("mapping path %q => file system %q", path, u.Path)
274− proxy := NewFileServer(path, u.Path)
275− uProxy := UpstreamProxy{
276− upstream: path,
277− handler: proxy,
278− wsHandler: nil,
279− auth: nil,
280− }
281− serveMux.Handle(path, &uProxy)
282− default:
283− panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme))
284− }
110+ templates := loadTemplates(opts.CustomTemplatesDir)
111+ proxyErrorHandler := upstream.NewProxyErrorHandler(templates.Lookup("error.html"), opts.ProxyPrefix)
112+ upstreamProxy, err := upstream.NewProxy(opts.UpstreamServers, opts.GetSignatureData(), proxyErrorHandler)
113+ if err != nil {
114+ return nil, fmt.Errorf("error initialising upstream proxy: %v", err)
285115 }
116+
286117 for _, u := range opts.GetCompiledRegex() {
287118 logger.Printf("compiled skip-auth-regex => %q", u)
288119 }
@@ -350,7 +181,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
350181 provider: opts.GetProvider(),
351182 providerNameOverride: opts.ProviderName,
352183 sessionStore: sessionStore,
353− serveMux: serveMux,
184+ serveMux: upstreamProxy,
354185 redirectURL: redirectURL,
355186 whitelistDomains: opts.WhitelistDomains,
356187 skipAuthRegex: opts.SkipAuthRegex,
@@ -371,7 +202,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
371202 PassAuthorization: opts.PassAuthorization,
372203 PreferEmailToUser: opts.PreferEmailToUser,
373204 SkipProviderButton: opts.SkipProviderButton,
374− templates: loadTemplates(opts.CustomTemplatesDir),
205+ templates: templates,
375206 trustedIPs: trustedIPs,
376207 Banner: opts.Banner,
377208 Footer: opts.Footer,
oauthproxy_test.go+54−150
@@ -8,7 +8,6 @@ import (
88 "fmt"
99 "io"
1010 "io/ioutil"
11− "net"
1211 "net/http"
1312 "net/http/httptest"
1413 "net/url"
@@ -24,11 +23,11 @@ import (
2423 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
2524 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
2625 sessionscookie "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions/cookie"
26+ "github.com/oauth2-proxy/oauth2-proxy/pkg/upstream"
2727 "github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
2828 "github.com/oauth2-proxy/oauth2-proxy/providers"
2929 "github.com/stretchr/testify/assert"
3030 "github.com/stretchr/testify/require"
31− "golang.org/x/net/websocket"
3231)
3332
3433const (
@@ -44,143 +43,6 @@ func init() {
4443 logger.SetFlags(logger.Lshortfile)
4544}
4645
47−type WebSocketOrRestHandler struct {
48− restHandler http.Handler
49− wsHandler http.Handler
50−}
51−
52−func (h *WebSocketOrRestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
53− if r.Header.Get("Upgrade") == "websocket" {
54− h.wsHandler.ServeHTTP(w, r)
55− } else {
56− h.restHandler.ServeHTTP(w, r)
57− }
58−}
59−
60−func TestWebSocketProxy(t *testing.T) {
61− handler := WebSocketOrRestHandler{
62− restHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
63− w.WriteHeader(200)
64− hostname, _, _ := net.SplitHostPort(r.Host)
65− _, err := w.Write([]byte(hostname))
66− if err != nil {
67− t.Fatal(err)
68− }
69− }),
70− wsHandler: websocket.Handler(func(ws *websocket.Conn) {
71− defer func(t *testing.T) {
72− if err := ws.Close(); err != nil {
73− t.Fatal(err)
74− }
75− }(t)
76− var data []byte
77− err := websocket.Message.Receive(ws, &data)
78− if err != nil {
79− t.Fatal(err)
80− }
81− err = websocket.Message.Send(ws, data)
82− if err != nil {
83− t.Fatal(err)
84− }
85− }),
86− }
87− backend := httptest.NewServer(&handler)
88− t.Cleanup(backend.Close)
89−
90− backendURL, _ := url.Parse(backend.URL)
91−
92− opts := baseTestOptions()
93− var auth hmacauth.HmacAuth
94− opts.PassHostHeader = true
95− proxyHandler := NewWebSocketOrRestReverseProxy(backendURL, opts, auth)
96− frontend := httptest.NewServer(proxyHandler)
97− t.Cleanup(frontend.Close)
98−
99− frontendURL, _ := url.Parse(frontend.URL)
100− frontendWSURL := "ws://" + frontendURL.Host + "/"
101−
102− ws, err := websocket.Dial(frontendWSURL, "", "http://localhost/")
103− if err != nil {
104− t.Fatal(err)
105− }
106− request := []byte("hello, world!")
107− err = websocket.Message.Send(ws, request)
108− if err != nil {
109− t.Fatal(err)
110− }
111− var response = make([]byte, 1024)
112− err = websocket.Message.Receive(ws, &response)
113− if err != nil {
114− t.Fatal(err)
115− }
116− if g, e := string(request), string(response); g != e {
117− t.Errorf("got body %q; expected %q", g, e)
118− }
119−
120− getReq, _ := http.NewRequest("GET", frontend.URL, nil)
121− res, _ := http.DefaultClient.Do(getReq)
122− bodyBytes, _ := ioutil.ReadAll(res.Body)
123− backendHostname, _, _ := net.SplitHostPort(backendURL.Host)
124− if g, e := string(bodyBytes), backendHostname; g != e {
125− t.Errorf("got body %q; expected %q", g, e)
126− }
127−}
128−
129−func TestNewReverseProxy(t *testing.T) {
130− backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
131− w.WriteHeader(200)
132− hostname, _, _ := net.SplitHostPort(r.Host)
133− _, err := w.Write([]byte(hostname))
134− if err != nil {
135− t.Fatal(err)
136− }
137− }))
138− t.Cleanup(backend.Close)
139−
140− backendURL, _ := url.Parse(backend.URL)
141− backendHostname, backendPort, _ := net.SplitHostPort(backendURL.Host)
142− backendHost := net.JoinHostPort(backendHostname, backendPort)
143− proxyURL, _ := url.Parse(backendURL.Scheme + "://" + backendHost + "/")
144−
145− proxyHandler := NewReverseProxy(proxyURL, &options.Options{FlushInterval: time.Second})
146− setProxyUpstreamHostHeader(proxyHandler, proxyURL)
147− frontend := httptest.NewServer(proxyHandler)
148− t.Cleanup(frontend.Close)
149−
150− getReq, _ := http.NewRequest("GET", frontend.URL, nil)
151− res, _ := http.DefaultClient.Do(getReq)
152− bodyBytes, _ := ioutil.ReadAll(res.Body)
153− if g, e := string(bodyBytes), backendHostname; g != e {
154− t.Errorf("got body %q; expected %q", g, e)
155− }
156−}
157−
158−func TestEncodedSlashes(t *testing.T) {
159− var seen string
160− backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
161− w.WriteHeader(200)
162− seen = r.RequestURI
163− }))
164− t.Cleanup(backend.Close)
165−
166− b, _ := url.Parse(backend.URL)
167− proxyHandler := NewReverseProxy(b, &options.Options{FlushInterval: time.Second})
168− setProxyDirector(proxyHandler)
169− frontend := httptest.NewServer(proxyHandler)
170− t.Cleanup(frontend.Close)
171−
172− f, _ := url.Parse(frontend.URL)
173− encodedPath := "/a%2Fb/?c=1"
174− getReq := &http.Request{URL: &url.URL{Scheme: "http", Host: f.Host, Opaque: encodedPath}}
175− _, err := http.DefaultClient.Do(getReq)
176− if err != nil {
177− t.Fatal(err)
178− }
179− if seen != encodedPath {
180− t.Errorf("got bad request %q expected %q", seen, encodedPath)
181− }
182−}
183−
18446func TestRobotsTxt(t *testing.T) {
18547 opts := baseTestOptions()
18648 err := validation.Validate(opts)
@@ -562,7 +424,14 @@ func TestBasicAuthPassword(t *testing.T) {
562424 }
563425 }))
564426 opts := baseTestOptions()
565− opts.Upstreams = append(opts.Upstreams, providerServer.URL)
427+ opts.UpstreamServers = options.Upstreams{
428+ {
429+ ID: providerServer.URL,
430+ Path: "/",
431+ URI: providerServer.URL,
432+ },
433+ }
434+
566435 opts.Cookie.Secure = false
567436 opts.PassBasicAuth = true
568437 opts.SetBasicAuth = true
@@ -867,7 +736,7 @@ type PassAccessTokenTest struct {
867736
868737type PassAccessTokenTestOptions struct {
869738 PassAccessToken bool
870− ProxyUpstream string
739+ ProxyUpstream options.Upstream
871740}
872741
873742func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTest, error) {
@@ -893,10 +762,17 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTe
893762 }))
894763
895764 patt.opts = baseTestOptions()
896− patt.opts.Upstreams = append(patt.opts.Upstreams, patt.providerServer.URL)
897− if opts.ProxyUpstream != "" {
898− patt.opts.Upstreams = append(patt.opts.Upstreams, opts.ProxyUpstream)
765+ patt.opts.UpstreamServers = options.Upstreams{
766+ {
767+ ID: patt.providerServer.URL,
768+ Path: "/",
769+ URI: patt.providerServer.URL,
770+ },
899771 }
772+ if opts.ProxyUpstream.ID != "" {
773+ patt.opts.UpstreamServers = append(patt.opts.UpstreamServers, opts.ProxyUpstream)
774+ }
775+
900776 patt.opts.Cookie.Secure = false
901777 patt.opts.PassAccessToken = opts.PassAccessToken
902778 err := validation.Validate(patt.opts)
@@ -999,7 +875,11 @@ func TestForwardAccessTokenUpstream(t *testing.T) {
999875func TestStaticProxyUpstream(t *testing.T) {
1000876 patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
1001877 PassAccessToken: true,
1002− ProxyUpstream: "static://200/static-proxy",
878+ ProxyUpstream: options.Upstream{
879+ ID: "static-proxy",
880+ Path: "/static-proxy",
881+ Static: true,
882+ },
1003883 })
1004884 if err != nil {
1005885 t.Fatal(err)
@@ -1572,7 +1452,13 @@ func TestAuthSkippedForPreflightRequests(t *testing.T) {
15721452 t.Cleanup(upstream.Close)
15731453
15741454 opts := baseTestOptions()
1575− opts.Upstreams = append(opts.Upstreams, upstream.URL)
1455+ opts.UpstreamServers = options.Upstreams{
1456+ {
1457+ ID: upstream.URL,
1458+ Path: "/",
1459+ URI: upstream.URL,
1460+ },
1461+ }
15761462 opts.SkipAuthPreflight = true
15771463 err := validation.Validate(opts)
15781464 assert.NoError(t, err)
@@ -1641,7 +1527,13 @@ func NewSignatureTest() (*SignatureTest, error) {
16411527 if err != nil {
16421528 return nil, err
16431529 }
1644− opts.Upstreams = append(opts.Upstreams, upstream.URL)
1530+ opts.UpstreamServers = options.Upstreams{
1531+ {
1532+ ID: upstream.URL,
1533+ Path: "/",
1534+ URI: upstream.URL,
1535+ },
1536+ }
16451537
16461538 providerHandler := func(w http.ResponseWriter, r *http.Request) {
16471539 _, err := w.Write([]byte(`{"access_token": "my_auth_token"}`))
@@ -1716,7 +1608,7 @@ func (st *SignatureTest) MakeRequestWithExpectedKey(method, body, key string) er
17161608 }
17171609 // This is used by the upstream to validate the signature.
17181610 st.authenticator.auth = hmacauth.NewHmacAuth(
1719− crypto.SHA1, []byte(key), SignatureHeader, SignatureHeaders)
1611+ crypto.SHA1, []byte(key), upstream.SignatureHeader, upstream.SignatureHeaders)
17201612 proxy.ServeHTTP(st.rw, req)
17211613
17221614 return nil
@@ -2110,7 +2002,13 @@ func Test_noCacheHeaders(t *testing.T) {
21102002 t.Cleanup(upstream.Close)
21112003
21122004 opts := baseTestOptions()
2113− opts.Upstreams = []string{upstream.URL}
2005+ opts.UpstreamServers = options.Upstreams{
2006+ {
2007+ ID: upstream.URL,
2008+ Path: "/",
2009+ URI: upstream.URL,
2010+ },
2011+ }
21142012 opts.SkipAuthRegex = []string{".*"}
21152013 err := validation.Validate(opts)
21162014 assert.NoError(t, err)
@@ -2335,7 +2233,13 @@ func TestTrustedIPs(t *testing.T) {
23352233 for _, tt := range tests {
23362234 t.Run(tt.name, func(t *testing.T) {
23372235 opts := baseTestOptions()
2338− opts.Upstreams = []string{"static://200"}
2236+ opts.UpstreamServers = options.Upstreams{
2237+ {
2238+ ID: "static",
2239+ Path: "/",
2240+ Static: true,
2241+ },
2242+ }
23392243 opts.TrustedIPs = tt.trustedIPs
23402244 opts.ReverseProxy = tt.reverseProxy
23412245 opts.RealClientIPHeader = tt.realClientIPHeader