git.iko.soy/typeable/oauth2-proxy
on master help sign in
Merge pull request #593 from oauth2-proxy/proxy-refactor

Integrate upstream package with OAuth2 Proxy

commit1aac37d2b1887cb7a11bf82d83bd60c11dea2d45 authorJoel Speed <Joel.speed@hotmail.co.uk> dateSun 19 Jul 2020, 20:10 (6 years ago) parente02f99ed43b372
16 files changed+462−422
CHANGELOG.md+1 main.go+8−2 oauthproxy.go+9−178 oauthproxy_test.go+54−150 pkg/apis/options/legacy_options.go+117 pkg/apis/options/legacy_options_test.go+203 pkg/apis/options/load_test.go+5−6 pkg/apis/options/options.go+21−34 pkg/apis/options/options_suite_test.go+16 pkg/apis/options/upstreams.go+2−2 pkg/upstream/http.go+2−2 pkg/upstream/http_test.go+10−7 pkg/validation/options.go+1−11 pkg/validation/options_test.go+5−21 pkg/validation/upstreams.go+2−2 pkg/validation/upstreams_test.go+6−7
CHANGELOG.md+1
@@ -11,6 +11,7 @@
1111
1212## Changes since v6.0.0
1313
14+- [#593](https://github.com/oauth2-proxy/oauth2-proxy/pull/593) Integrate upstream package with OAuth2 Proxy (@JoelSpeed)
1415- [#687](https://github.com/oauth2-proxy/oauth2-proxy/pull/687) Refactor HTPasswd Validator (@JoelSpeed)
1516- [#624](https://github.com/oauth2-proxy/oauth2-proxy/pull/624) Allow stripping authentication headers from whitelisted requests with `--skip-auth-strip-headers` (@NickMeves)
1617- [#673](https://github.com/oauth2-proxy/oauth2-proxy/pull/673) Add --session-cookie-minimal option to create session cookies with no tokens (@NickMeves)
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
pkg/apis/options/legacy_options.gonew file+117
@@ -0,0 +1,117 @@
1+package options
2+
3+import (
4+ "fmt"
5+ "net/url"
6+ "strconv"
7+ "time"
8+
9+ "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
10+ "github.com/spf13/pflag"
11+)
12+
13+type LegacyOptions struct {
14+ // Legacy options related to upstream servers
15+ LegacyUpstreams LegacyUpstreams `cfg:",squash"`
16+
17+ Options Options `cfg:",squash"`
18+}
19+
20+func NewLegacyOptions() *LegacyOptions {
21+ return &LegacyOptions{
22+ LegacyUpstreams: LegacyUpstreams{
23+ PassHostHeader: true,
24+ ProxyWebSockets: true,
25+ FlushInterval: time.Duration(1) * time.Second,
26+ },
27+
28+ Options: *NewOptions(),
29+ }
30+}
31+
32+func (l *LegacyOptions) ToOptions() (*Options, error) {
33+ upstreams, err := l.LegacyUpstreams.convert()
34+ if err != nil {
35+ return nil, fmt.Errorf("error converting upstreams: %v", err)
36+ }
37+ l.Options.UpstreamServers = upstreams
38+
39+ return &l.Options, nil
40+}
41+
42+type LegacyUpstreams struct {
43+ FlushInterval time.Duration `flag:"flush-interval" cfg:"flush_interval"`
44+ PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"`
45+ ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets"`
46+ SSLUpstreamInsecureSkipVerify bool `flag:"ssl-upstream-insecure-skip-verify" cfg:"ssl_upstream_insecure_skip_verify"`
47+ Upstreams []string `flag:"upstream" cfg:"upstreams"`
48+}
49+
50+func legacyUpstreamsFlagSet() *pflag.FlagSet {
51+ flagSet := pflag.NewFlagSet("upstreams", pflag.ExitOnError)
52+
53+ flagSet.Duration("flush-interval", time.Duration(1)*time.Second, "period between response flushing when streaming responses")
54+ flagSet.Bool("pass-host-header", true, "pass the request Host Header to upstream")
55+ flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying")
56+ flagSet.Bool("ssl-upstream-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS upstreams")
57+ flagSet.StringSlice("upstream", []string{}, "the http url(s) of the upstream endpoint, file:// paths for static files or static://<status_code> for static response. Routing is based on the path")
58+
59+ return flagSet
60+}
61+
62+func (l *LegacyUpstreams) convert() (Upstreams, error) {
63+ upstreams := Upstreams{}
64+
65+ for _, upstreamString := range l.Upstreams {
66+ u, err := url.Parse(upstreamString)
67+ if err != nil {
68+ return nil, fmt.Errorf("could not parse upstream %q: %v", upstreamString, err)
69+ }
70+
71+ if u.Path == "" {
72+ u.Path = "/"
73+ }
74+
75+ upstream := Upstream{
76+ ID: u.Path,
77+ Path: u.Path,
78+ URI: upstreamString,
79+ InsecureSkipTLSVerify: l.SSLUpstreamInsecureSkipVerify,
80+ PassHostHeader: &l.PassHostHeader,
81+ ProxyWebSockets: &l.ProxyWebSockets,
82+ FlushInterval: &l.FlushInterval,
83+ }
84+
85+ switch u.Scheme {
86+ case "file":
87+ if u.Fragment != "" {
88+ upstream.ID = u.Fragment
89+ upstream.Path = u.Fragment
90+ }
91+ case "static":
92+ responseCode, err := strconv.Atoi(u.Host)
93+ if err != nil {
94+ logger.Printf("unable to convert %q to int, use default \"200\"", u.Host)
95+ responseCode = 200
96+ }
97+ upstream.Static = true
98+ upstream.StaticCode = &responseCode
99+
100+ // These are not allowed to be empty and must be unique
101+ upstream.ID = upstreamString
102+ upstream.Path = upstreamString
103+
104+ // Force defaults compatible with static responses
105+ upstream.URI = ""
106+ upstream.InsecureSkipTLSVerify = false
107+ upstream.PassHostHeader = nil
108+ upstream.ProxyWebSockets = nil
109+ flush := 1 * time.Second
110+ upstream.FlushInterval = &flush
111+ }
112+
113+ upstreams = append(upstreams, upstream)
114+ }
115+
116+ return upstreams, nil
117+}
pkg/apis/options/legacy_options_test.gonew file+203
@@ -0,0 +1,203 @@
1+package options
2+
3+import (
4+ "time"
5+
6+ . "github.com/onsi/ginkgo"
7+ . "github.com/onsi/ginkgo/extensions/table"
8+ . "github.com/onsi/gomega"
9+)
10+
11+var _ = Describe("Legacy Options", func() {
12+ Context("ToOptions", func() {
13+ It("converts the options as expected", func() {
14+ opts := NewOptions()
15+
16+ legacyOpts := NewLegacyOptions()
17+
18+ // Set upstreams and related options to test their conversion
19+ flushInterval := 5 * time.Second
20+ legacyOpts.LegacyUpstreams.FlushInterval = flushInterval
21+ legacyOpts.LegacyUpstreams.PassHostHeader = true
22+ legacyOpts.LegacyUpstreams.ProxyWebSockets = true
23+ legacyOpts.LegacyUpstreams.SSLUpstreamInsecureSkipVerify = true
24+ legacyOpts.LegacyUpstreams.Upstreams = []string{"http://foo.bar/baz", "file://var/lib/website#/bar"}
25+
26+ truth := true
27+ opts.UpstreamServers = Upstreams{
28+ {
29+ ID: "/baz",
30+ Path: "/baz",
31+ URI: "http://foo.bar/baz",
32+ FlushInterval: &flushInterval,
33+ InsecureSkipTLSVerify: true,
34+ PassHostHeader: &truth,
35+ ProxyWebSockets: &truth,
36+ },
37+ {
38+ ID: "/bar",
39+ Path: "/bar",
40+ URI: "file://var/lib/website#/bar",
41+ FlushInterval: &flushInterval,
42+ InsecureSkipTLSVerify: true,
43+ PassHostHeader: &truth,
44+ ProxyWebSockets: &truth,
45+ },
46+ }
47+
48+ converted, err := legacyOpts.ToOptions()
49+ Expect(err).ToNot(HaveOccurred())
50+ Expect(converted).To(Equal(opts))
51+ })
52+ })
53+
54+ Context("Legacy Upstreams", func() {
55+ type convertUpstreamsTableInput struct {
56+ upstreamStrings []string
57+ expectedUpstreams Upstreams
58+ errMsg string
59+ }
60+
61+ defaultFlushInterval := 1 * time.Second
62+
63+ // Non defaults for these options
64+ skipVerify := true
65+ passHostHeader := false
66+ proxyWebSockets := true
67+ flushInterval := 5 * time.Second
68+
69+ // Test cases and expected outcomes
70+ validHTTP := "http://foo.bar/baz"
71+ validHTTPUpstream := Upstream{
72+ ID: "/baz",
73+ Path: "/baz",
74+ URI: validHTTP,
75+ InsecureSkipTLSVerify: skipVerify,
76+ PassHostHeader: &passHostHeader,
77+ ProxyWebSockets: &proxyWebSockets,
78+ FlushInterval: &flushInterval,
79+ }
80+
81+ // Test cases and expected outcomes
82+ emptyPathHTTP := "http://foo.bar"
83+ emptyPathHTTPUpstream := Upstream{
84+ ID: "/",
85+ Path: "/",
86+ URI: emptyPathHTTP,
87+ InsecureSkipTLSVerify: skipVerify,
88+ PassHostHeader: &passHostHeader,
89+ ProxyWebSockets: &proxyWebSockets,
90+ FlushInterval: &flushInterval,
91+ }
92+
93+ validFileWithFragment := "file://var/lib/website#/bar"
94+ validFileWithFragmentUpstream := Upstream{
95+ ID: "/bar",
96+ Path: "/bar",
97+ URI: validFileWithFragment,
98+ InsecureSkipTLSVerify: skipVerify,
99+ PassHostHeader: &passHostHeader,
100+ ProxyWebSockets: &proxyWebSockets,
101+ FlushInterval: &flushInterval,
102+ }
103+
104+ validStatic := "static://204"
105+ validStaticCode := 204
106+ validStaticUpstream := Upstream{
107+ ID: validStatic,
108+ Path: validStatic,
109+ URI: "",
110+ Static: true,
111+ StaticCode: &validStaticCode,
112+ InsecureSkipTLSVerify: false,
113+ PassHostHeader: nil,
114+ ProxyWebSockets: nil,
115+ FlushInterval: &defaultFlushInterval,
116+ }
117+
118+ invalidStatic := "static://abc"
119+ invalidStaticCode := 200
120+ invalidStaticUpstream := Upstream{
121+ ID: invalidStatic,
122+ Path: invalidStatic,
123+ URI: "",
124+ Static: true,
125+ StaticCode: &invalidStaticCode,
126+ InsecureSkipTLSVerify: false,
127+ PassHostHeader: nil,
128+ ProxyWebSockets: nil,
129+ FlushInterval: &defaultFlushInterval,
130+ }
131+
132+ invalidHTTP := ":foo"
133+ invalidHTTPErrMsg := "could not parse upstream \":foo\": parse \":foo\": missing protocol scheme"
134+
135+ DescribeTable("convertLegacyUpstreams",
136+ func(o *convertUpstreamsTableInput) {
137+ legacyUpstreams := LegacyUpstreams{
138+ Upstreams: o.upstreamStrings,
139+ SSLUpstreamInsecureSkipVerify: skipVerify,
140+ PassHostHeader: passHostHeader,
141+ ProxyWebSockets: proxyWebSockets,
142+ FlushInterval: flushInterval,
143+ }
144+
145+ upstreams, err := legacyUpstreams.convert()
146+
147+ if o.errMsg != "" {
148+ Expect(err).To(HaveOccurred())
149+ Expect(err.Error()).To(Equal(o.errMsg))
150+ } else {
151+ Expect(err).ToNot(HaveOccurred())
152+ }
153+
154+ Expect(upstreams).To(ConsistOf(o.expectedUpstreams))
155+ },
156+ Entry("with no upstreams", &convertUpstreamsTableInput{
157+ upstreamStrings: []string{},
158+ expectedUpstreams: Upstreams{},
159+ errMsg: "",
160+ }),
161+ Entry("with a valid HTTP upstream", &convertUpstreamsTableInput{
162+ upstreamStrings: []string{validHTTP},
163+ expectedUpstreams: Upstreams{validHTTPUpstream},
164+ errMsg: "",
165+ }),
166+ Entry("with a HTTP upstream with an empty path", &convertUpstreamsTableInput{
167+ upstreamStrings: []string{emptyPathHTTP},
168+ expectedUpstreams: Upstreams{emptyPathHTTPUpstream},
169+ errMsg: "",
170+ }),
171+ Entry("with a valid File upstream with a fragment", &convertUpstreamsTableInput{
172+ upstreamStrings: []string{validFileWithFragment},
173+ expectedUpstreams: Upstreams{validFileWithFragmentUpstream},
174+ errMsg: "",
175+ }),
176+ Entry("with a valid static upstream", &convertUpstreamsTableInput{
177+ upstreamStrings: []string{validStatic},
178+ expectedUpstreams: Upstreams{validStaticUpstream},
179+ errMsg: "",
180+ }),
181+ Entry("with an invalid static upstream, code is 200", &convertUpstreamsTableInput{
182+ upstreamStrings: []string{invalidStatic},
183+ expectedUpstreams: Upstreams{invalidStaticUpstream},
184+ errMsg: "",
185+ }),
186+ Entry("with an invalid HTTP upstream", &convertUpstreamsTableInput{
187+ upstreamStrings: []string{invalidHTTP},
188+ expectedUpstreams: Upstreams{},
189+ errMsg: invalidHTTPErrMsg,
190+ }),
191+ Entry("with an invalid HTTP upstream and other upstreams", &convertUpstreamsTableInput{
192+ upstreamStrings: []string{validHTTP, invalidHTTP},
193+ expectedUpstreams: Upstreams{},
194+ errMsg: invalidHTTPErrMsg,
195+ }),
196+ Entry("with multiple valid upstreams", &convertUpstreamsTableInput{
197+ upstreamStrings: []string{validHTTP, validFileWithFragment, validStatic},
198+ expectedUpstreams: Upstreams{validHTTPUpstream, validFileWithFragmentUpstream, validStaticUpstream},
199+ errMsg: "",
200+ }),
201+ )
202+ })
203+})
pkg/apis/options/load_test.go+5−6
@@ -4,7 +4,6 @@ import (
44 "fmt"
55 "io/ioutil"
66 "os"
7− "testing"
87
98 . "github.com/onsi/ginkgo"
109 . "github.com/onsi/ginkgo/extensions/table"
@@ -12,11 +11,6 @@ import (
1211 "github.com/spf13/pflag"
1312)
1413
15−func TestOptionsSuite(t *testing.T) {
16− RegisterFailHandler(Fail)
17− RunSpecs(t, "Options Suite")
18−}
19−
2014var _ = Describe("Load", func() {
2115 Context("with a testOptions structure", func() {
2216 type TestOptionSubStruct struct {
@@ -300,6 +294,11 @@ var _ = Describe("Load", func() {
300294 input: &Options{},
301295 expectedOutput: NewOptions(),
302296 }),
297+ Entry("with an empty LegacyOptions struct, should return default values", &testOptionsTableInput{
298+ flagSet: NewFlagSet,
299+ input: &LegacyOptions{},
300+ expectedOutput: NewLegacyOptions(),
301+ }),
303302 )
304303 })
305304})
pkg/apis/options/options.go+21−34
@@ -4,7 +4,6 @@ import (
44 "crypto"
55 "net/url"
66 "regexp"
7− "time"
87
98 oidc "github.com/coreos/go-oidc"
109 ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip"
@@ -24,7 +23,6 @@ type Options struct {
2423 ProxyPrefix string `flag:"proxy-prefix" cfg:"proxy_prefix"`
2524 PingPath string `flag:"ping-path" cfg:"ping_path"`
2625 PingUserAgent string `flag:"ping-user-agent" cfg:"ping_user_agent"`
27− ProxyWebSockets bool `flag:"proxy-websockets" cfg:"proxy_websockets"`
2826 HTTPAddress string `flag:"http-address" cfg:"http_address"`
2927 HTTPSAddress string `flag:"https-address" cfg:"https_address"`
3028 ReverseProxy bool `flag:"reverse-proxy" cfg:"reverse_proxy"`
@@ -64,26 +62,26 @@ type Options struct {
6462 Session SessionOptions `cfg:",squash"`
6563 Logging Logging `cfg:",squash"`
6664
67− Upstreams []string `flag:"upstream" cfg:"upstreams"`
68− SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
69− SkipAuthStripHeaders bool `flag:"skip-auth-strip-headers" cfg:"skip_auth_strip_headers"`
70− SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"`
71− ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"`
72− PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
73− SetBasicAuth bool `flag:"set-basic-auth" cfg:"set_basic_auth"`
74− PreferEmailToUser bool `flag:"prefer-email-to-user" cfg:"prefer_email_to_user"`
75− BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"`
76− PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"`
77− PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"`
78− SkipProviderButton bool `flag:"skip-provider-button" cfg:"skip_provider_button"`
79− PassUserHeaders bool `flag:"pass-user-headers" cfg:"pass_user_headers"`
80− SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify"`
81− SSLUpstreamInsecureSkipVerify bool `flag:"ssl-upstream-insecure-skip-verify" cfg:"ssl_upstream_insecure_skip_verify"`
82− SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest"`
83− SetAuthorization bool `flag:"set-authorization-header" cfg:"set_authorization_header"`
84− PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header"`
85− SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight"`
86− FlushInterval time.Duration `flag:"flush-interval" cfg:"flush_interval"`
65+ // Not used in the legacy config, name not allowed to match an external key (upstreams)
66+ // TODO(JoelSpeed): Rename when legacy config is removed
67+ UpstreamServers Upstreams `cfg:",internal"`
68+
69+ SkipAuthRegex []string `flag:"skip-auth-regex" cfg:"skip_auth_regex"`
70+ SkipAuthStripHeaders bool `flag:"skip-auth-strip-headers" cfg:"skip_auth_strip_headers"`
71+ SkipJwtBearerTokens bool `flag:"skip-jwt-bearer-tokens" cfg:"skip_jwt_bearer_tokens"`
72+ ExtraJwtIssuers []string `flag:"extra-jwt-issuers" cfg:"extra_jwt_issuers"`
73+ PassBasicAuth bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
74+ SetBasicAuth bool `flag:"set-basic-auth" cfg:"set_basic_auth"`
75+ PreferEmailToUser bool `flag:"prefer-email-to-user" cfg:"prefer_email_to_user"`
76+ BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"`
77+ PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"`
78+ SkipProviderButton bool `flag:"skip-provider-button" cfg:"skip_provider_button"`
79+ PassUserHeaders bool `flag:"pass-user-headers" cfg:"pass_user_headers"`
80+ SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify"`
81+ SetXAuthRequest bool `flag:"set-xauthrequest" cfg:"set_xauthrequest"`
82+ SetAuthorization bool `flag:"set-authorization-header" cfg:"set_authorization_header"`
83+ PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header"`
84+ SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight"`
8785
8886 // These options allow for other providers besides Google, with
8987 // potential overrides.
@@ -114,7 +112,6 @@ type Options struct {
114112
115113 // internal values that are set after config validation
116114 redirectURL *url.URL
117− proxyURLs []*url.URL
118115 compiledRegex []*regexp.Regexp
119116 provider providers.Provider
120117 signatureData *SignatureData
@@ -125,7 +122,6 @@ type Options struct {
125122
126123// Options for Getting internal values
127124func (o *Options) GetRedirectURL() *url.URL { return o.redirectURL }
128−func (o *Options) GetProxyURLs() []*url.URL { return o.proxyURLs }
129125func (o *Options) GetCompiledRegex() []*regexp.Regexp { return o.compiledRegex }
130126func (o *Options) GetProvider() providers.Provider { return o.provider }
131127func (o *Options) GetSignatureData() *SignatureData { return o.signatureData }
@@ -135,7 +131,6 @@ func (o *Options) GetRealClientIPParser() ipapi.RealClientIPParser { return o.re
135131
136132// Options for Setting internal values
137133func (o *Options) SetRedirectURL(s *url.URL) { o.redirectURL = s }
138−func (o *Options) SetProxyURLs(s []*url.URL) { o.proxyURLs = s }
139134func (o *Options) SetCompiledRegex(s []*regexp.Regexp) { o.compiledRegex = s }
140135func (o *Options) SetProvider(s providers.Provider) { o.provider = s }
141136func (o *Options) SetSignatureData(s *SignatureData) { o.signatureData = s }
@@ -149,7 +144,6 @@ func NewOptions() *Options {
149144 ProxyPrefix: "/oauth2",
150145 ProviderType: "google",
151146 PingPath: "/ping",
152− ProxyWebSockets: true,
153147 HTTPAddress: "127.0.0.1:4180",
154148 HTTPSAddress: ":443",
155149 RealClientIPHeader: "X-Real-IP",
@@ -160,13 +154,10 @@ func NewOptions() *Options {
160154 AzureTenant: "common",
161155 SetXAuthRequest: false,
162156 SkipAuthPreflight: false,
163− SkipAuthStripHeaders: false,
164− FlushInterval: time.Duration(1) * time.Second,
165157 PassBasicAuth: true,
166158 SetBasicAuth: false,
167159 PassUserHeaders: true,
168160 PassAccessToken: false,
169− PassHostHeader: true,
170161 SetAuthorization: false,
171162 PassAuthorization: false,
172163 PreferEmailToUser: false,
@@ -193,14 +184,12 @@ func NewFlagSet() *pflag.FlagSet {
193184 flagSet.String("tls-key-file", "", "path to private key file")
194185 flagSet.String("redirect-url", "", "the OAuth Redirect URL. ie: \"https://internalapp.yourcompany.com/oauth2/callback\"")
195186 flagSet.Bool("set-xauthrequest", false, "set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode)")
196− flagSet.StringSlice("upstream", []string{}, "the http url(s) of the upstream endpoint, file:// paths for static files or static://<status_code> for static response. Routing is based on the path")
197187 flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream")
198188 flagSet.Bool("set-basic-auth", false, "set HTTP Basic Auth information in response (useful in Nginx auth_request mode)")
199189 flagSet.Bool("prefer-email-to-user", false, "Prefer to use the Email address as the Username when passing information to upstream. Will only use Username if Email is unavailable, eg. htaccess authentication. Used in conjunction with -pass-basic-auth and -pass-user-headers")
200190 flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream")
201191 flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header")
202192 flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header")
203− flagSet.Bool("pass-host-header", true, "pass the request Host Header to upstream")
204193 flagSet.Bool("pass-authorization-header", false, "pass the Authorization Header to upstream")
205194 flagSet.Bool("set-authorization-header", false, "set Authorization response headers (useful in Nginx auth_request mode)")
206195 flagSet.StringSlice("skip-auth-regex", []string{}, "bypass authentication for requests path's that match (may be given multiple times)")
@@ -208,8 +197,6 @@ func NewFlagSet() *pflag.FlagSet {
208197 flagSet.Bool("skip-provider-button", false, "will skip sign-in-page to directly reach the next step: oauth/start")
209198 flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests")
210199 flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS providers")
211− flagSet.Bool("ssl-upstream-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS upstreams")
212− flagSet.Duration("flush-interval", time.Duration(1)*time.Second, "period between response flushing when streaming responses")
213200 flagSet.Bool("skip-jwt-bearer-tokens", false, "will skip requests that have verified JWT bearer tokens (default false)")
214201 flagSet.StringSlice("extra-jwt-issuers", []string{}, "if skip-jwt-bearer-tokens is set, a list of extra JWT issuer=audience pairs (where the issuer URL has a .well-known/openid-configuration or a .well-known/jwks.json)")
215202
@@ -240,7 +227,6 @@ func NewFlagSet() *pflag.FlagSet {
240227 flagSet.String("proxy-prefix", "/oauth2", "the url root path that this proxy should be nested under (e.g. /<oauth2>/sign_in)")
241228 flagSet.String("ping-path", "/ping", "the ping endpoint that can be used for basic health checks")
242229 flagSet.String("ping-user-agent", "", "special User-Agent that will be used for basic health checks")
243− flagSet.Bool("proxy-websockets", true, "enables WebSocket proxying")
244230 flagSet.String("session-store-type", "cookie", "the session storage provider to use")
245231 flagSet.Bool("session-cookie-minimal", false, "strip OAuth tokens from cookie session stores if they aren't needed (cookie session store only)")
246232 flagSet.String("redis-connection-url", "", "URL of redis server for redis session storage (eg: redis://HOST[:PORT])")
@@ -280,6 +266,7 @@ func NewFlagSet() *pflag.FlagSet {
280266
281267 flagSet.AddFlagSet(cookieFlagSet())
282268 flagSet.AddFlagSet(loggingFlagSet())
269+ flagSet.AddFlagSet(legacyUpstreamsFlagSet())
283270
284271 return flagSet
285272}
pkg/apis/options/options_suite_test.gonew file+16
@@ -0,0 +1,16 @@
1+package options
2+
3+import (
4+ "testing"
5+
6+ "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
7+ . "github.com/onsi/ginkgo"
8+ . "github.com/onsi/gomega"
9+)
10+
11+func TestOptionsSuite(t *testing.T) {
12+ logger.SetOutput(GinkgoWriter)
13+
14+ RegisterFailHandler(Fail)
15+ RunSpecs(t, "Options Suite")
16+}
pkg/apis/options/upstreams.go+2−2
@@ -52,9 +52,9 @@ type Upstream struct {
5252 // PassHostHeader determines whether the request host header should be proxied
5353 // to the upstream server.
5454 // Defaults to true.
55− PassHostHeader bool `json:"passHostHeader"`
55+ PassHostHeader *bool `json:"passHostHeader"`
5656
5757 // ProxyWebSockets enables proxying of websockets to upstream servers
5858 // Defaults to true.
59− ProxyWebSockets bool `json:"proxyWebSockets"`
59+ ProxyWebSockets *bool `json:"proxyWebSockets"`
6060}
pkg/upstream/http.go+2−2
@@ -49,7 +49,7 @@ func newHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *option
4949
5050 // Set up a WebSocket proxy if required
5151 var wsProxy http.Handler
52− if upstream.ProxyWebSockets {
52+ if upstream.ProxyWebSockets == nil || *upstream.ProxyWebSockets {
5353 wsProxy = newWebSocketReverseProxy(u, upstream.InsecureSkipTLSVerify)
5454 }
5555
@@ -110,7 +110,7 @@ func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler Pr
110110 }
111111
112112 // Set the request director based on the PassHostHeader option
113− if !upstream.PassHostHeader {
113+ if upstream.PassHostHeader != nil && !*upstream.PassHostHeader {
114114 setProxyUpstreamHostHeader(proxy, target)
115115 } else {
116116 setProxyDirector(proxy)
pkg/upstream/http_test.go+10−7
@@ -24,6 +24,8 @@ var _ = Describe("HTTP Upstream Suite", func() {
2424
2525 const flushInterval5s = 5 * time.Second
2626 const flushInterval1s = 1 * time.Second
27+ truth := true
28+ falsum := false
2729
2830 type httpUpstreamTableInput struct {
2931 id string
@@ -51,10 +53,11 @@ var _ = Describe("HTTP Upstream Suite", func() {
5153 rw := httptest.NewRecorder()
5254
5355 flush := 1 * time.Second
56+
5457 upstream := options.Upstream{
5558 ID: in.id,
56− PassHostHeader: true,
57− ProxyWebSockets: false,
59+ PassHostHeader: &truth,
60+ ProxyWebSockets: &falsum,
5861 InsecureSkipTLSVerify: false,
5962 FlushInterval: &flush,
6063 }
@@ -258,8 +261,8 @@ var _ = Describe("HTTP Upstream Suite", func() {
258261 flush := 1 * time.Second
259262 upstream := options.Upstream{
260263 ID: "noPassHost",
261− PassHostHeader: false,
262− ProxyWebSockets: false,
264+ PassHostHeader: &falsum,
265+ ProxyWebSockets: &falsum,
263266 InsecureSkipTLSVerify: false,
264267 FlushInterval: &flush,
265268 }
@@ -302,7 +305,7 @@ var _ = Describe("HTTP Upstream Suite", func() {
302305 ID: "foo123",
303306 FlushInterval: &in.flushInterval,
304307 InsecureSkipTLSVerify: in.skipVerify,
305− ProxyWebSockets: in.proxyWebSockets,
308+ ProxyWebSockets: &in.proxyWebSockets,
306309 }
307310
308311 handler := newHTTPUpstreamProxy(upstream, u, in.sigData, in.errorHandler)
@@ -370,8 +373,8 @@ var _ = Describe("HTTP Upstream Suite", func() {
370373 flush := 1 * time.Second
371374 upstream := options.Upstream{
372375 ID: "websocketProxy",
373− PassHostHeader: true,
374− ProxyWebSockets: true,
376+ PassHostHeader: &truth,
377+ ProxyWebSockets: &truth,
375378 InsecureSkipTLSVerify: false,
376379 FlushInterval: &flush,
377380 }
pkg/validation/options.go+1−11
@@ -176,17 +176,7 @@ func Validate(o *options.Options) error {
176176 redirectURL, msgs = parseURL(o.RawRedirectURL, "redirect", msgs)
177177 o.SetRedirectURL(redirectURL)
178178
179− for _, u := range o.Upstreams {
180− upstreamURL, err := url.Parse(u)
181− if err != nil {
182− msgs = append(msgs, fmt.Sprintf("error parsing upstream: %s", err))
183− } else {
184− if upstreamURL.Path == "" {
185− upstreamURL.Path = "/"
186− }
187− o.SetProxyURLs(append(o.GetProxyURLs(), upstreamURL))
188− }
189− }
179+ msgs = append(msgs, validateUpstreams(o.UpstreamServers)...)
190180
191181 for _, u := range o.SkipAuthRegex {
192182 compiledRegex, err := regexp.Compile(u)
pkg/validation/options_test.go+5−21
@@ -22,7 +22,11 @@ const (
2222
2323func testOptions() *options.Options {
2424 o := options.NewOptions()
25− o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8080/")
25+ o.UpstreamServers = append(o.UpstreamServers, options.Upstream{
26+ ID: "upstream",
27+ Path: "/",
28+ URI: "http://127.0.0.1:8080/",
29+ })
2630 o.Cookie.Secret = cookieSecret
2731 o.ClientID = clientID
2832 o.ClientSecret = clientSecret
@@ -140,26 +144,6 @@ func TestRedirectURL(t *testing.T) {
140144 assert.Equal(t, expected, o.GetRedirectURL())
141145}
142146
143−func TestProxyURLs(t *testing.T) {
144− o := testOptions()
145− o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8081")
146− assert.Equal(t, nil, Validate(o))
147− expected := []*url.URL{
148− {Scheme: "http", Host: "127.0.0.1:8080", Path: "/"},
149− // note the '/' was added
150− {Scheme: "http", Host: "127.0.0.1:8081", Path: "/"},
151− }
152− assert.Equal(t, expected, o.GetProxyURLs())
153−}
154−
155−func TestProxyURLsError(t *testing.T) {
156− o := testOptions()
157− o.Upstreams = append(o.Upstreams, "127.0.0.1:8081")
158− err := Validate(o)
159− assert.NotEqual(t, nil, err)
160− assert.Contains(t, err.Error(), "error parsing upstream")
161−}
162−
163147func TestCompiledRegex(t *testing.T) {
164148 o := testOptions()
165149 regexps := []string{"/foo/.*", "/ba[rz]/quux"}
pkg/validation/upstreams.go+2−2
@@ -73,10 +73,10 @@ func validateStaticUpstream(upstream options.Upstream) []string {
7373 if upstream.FlushInterval != nil && *upstream.FlushInterval != time.Second {
7474 msgs = append(msgs, fmt.Sprintf("upstream %q has flushInterval, but is a static upstream, this will have no effect.", upstream.ID))
7575 }
76− if !upstream.PassHostHeader {
76+ if upstream.PassHostHeader != nil {
7777 msgs = append(msgs, fmt.Sprintf("upstream %q has passHostHeader, but is a static upstream, this will have no effect.", upstream.ID))
7878 }
79− if !upstream.ProxyWebSockets {
79+ if upstream.ProxyWebSockets != nil {
8080 msgs = append(msgs, fmt.Sprintf("upstream %q has proxyWebSockets, but is a static upstream, this will have no effect.", upstream.ID))
8181 }
8282
pkg/validation/upstreams_test.go+6−7
@@ -17,6 +17,7 @@ var _ = Describe("Upstreams", func() {
1717
1818 flushInterval := 5 * time.Second
1919 staticCode200 := 200
20+ truth := true
2021
2122 validHTTPUpstream := options.Upstream{
2223 ID: "validHTTPUpstream",
@@ -24,11 +25,9 @@ var _ = Describe("Upstreams", func() {
2425 URI: "http://localhost:8080",
2526 }
2627 validStaticUpstream := options.Upstream{
27− ID: "validStaticUpstream",
28− Path: "/validStaticUpstream",
29− Static: true,
30− PassHostHeader: true, // This would normally be defaulted
31− ProxyWebSockets: true, // this would normally be defaulted
28+ ID: "validStaticUpstream",
29+ Path: "/validStaticUpstream",
30+ Static: true,
3231 }
3332 validFileUpstream := options.Upstream{
3433 ID: "validFileUpstream",
@@ -134,8 +133,8 @@ var _ = Describe("Upstreams", func() {
134133 URI: "ftp://foo",
135134 Static: true,
136135 FlushInterval: &flushInterval,
137− PassHostHeader: false,
138− ProxyWebSockets: false,
136+ PassHostHeader: &truth,
137+ ProxyWebSockets: &truth,
139138 InsecureSkipTLSVerify: true,
140139 },
141140 },