@@ -48,6 +48,12 @@ var (
4848 invalidRedirectRegex = regexp.MustCompile(`[/\\](?:[\s\v]*|\.{1,2})[/\\]`)
4949)
5050
51+// allowedRoute manages method + path based allowlists
52+type allowedRoute struct {
53+ method string
54+ pathRegex *regexp.Regexp
55+}
56+
5157// OAuthProxy is the main authentication proxy
5258type OAuthProxy struct {
5359 CookieSeed string
@@ -70,6 +76,7 @@ type OAuthProxy struct {
7076 AuthOnlyPath string
7177 UserInfoPath string
7278
79+ allowedRoutes []*allowedRoute
7380 redirectURL *url.URL // the url to receive requests at
7481 whitelistDomains []string
7582 provider providers.Provider
@@ -90,13 +97,11 @@ type OAuthProxy struct {
9097 SetAuthorization bool
9198 PassAuthorization bool
9299 PreferEmailToUser bool
93− skipAuthRegex []string
94100 skipAuthPreflight bool
95101 skipAuthStripHeaders bool
96102 skipJwtBearerTokens bool
97103 mainJwtBearerVerifier *oidc.IDTokenVerifier
98104 extraJwtBearerVerifiers []*oidc.IDTokenVerifier
99− compiledRegex []*regexp.Regexp
100105 templates *template.Template
101106 realClientIPParser ipapi.RealClientIPParser
102107 trustedIPs *ip.NetSet
@@ -121,10 +126,6 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
121126 return nil, fmt.Errorf("error initialising upstream proxy: %v", err)
122127 }
123128
124− for _, u := range opts.GetCompiledRegex() {
125− logger.Printf("compiled skip-auth-regex => %q", u)
126− }
127−
128129 if opts.SkipJwtBearerTokens {
129130 logger.Printf("Skipping JWT tokens from configured OIDC issuer: %q", opts.OIDCIssuerURL)
130131 for _, issuer := range opts.ExtraJwtIssuers {
@@ -163,6 +164,11 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
163164 }
164165 }
165166
167+ allowedRoutes, err := buildRoutesAllowlist(opts)
168+ if err != nil {
169+ return nil, err
170+ }
171+
166172 sessionChain := buildSessionChain(opts, sessionStore, basicAuthValidator)
167173
168174 return &OAuthProxy{
@@ -192,14 +198,13 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
192198 sessionStore: sessionStore,
193199 serveMux: upstreamProxy,
194200 redirectURL: redirectURL,
201+ allowedRoutes: allowedRoutes,
195202 whitelistDomains: opts.WhitelistDomains,
196− skipAuthRegex: opts.SkipAuthRegex,
197203 skipAuthPreflight: opts.SkipAuthPreflight,
198204 skipAuthStripHeaders: opts.SkipAuthStripHeaders,
199205 skipJwtBearerTokens: opts.SkipJwtBearerTokens,
200206 mainJwtBearerVerifier: opts.GetOIDCVerifier(),
201207 extraJwtBearerVerifiers: opts.GetJWTBearerVerifiers(),
202− compiledRegex: opts.GetCompiledRegex(),
203208 realClientIPParser: opts.GetRealClientIPParser(),
204209 SetXAuthRequest: opts.SetXAuthRequest,
205210 PassBasicAuth: opts.PassBasicAuth,
@@ -277,6 +282,51 @@ func buildSignInMessage(opts *options.Options) string {
277282 return msg
278283}
279284
285+// buildRoutesAllowlist builds an []allowedRoute list from either the legacy
286+// SkipAuthRegex option (paths only support) or newer SkipAuthRoutes option
287+// (method=path support)
288+func buildRoutesAllowlist(opts *options.Options) ([]*allowedRoute, error) {
289+ var routes []*allowedRoute
290+
291+ for _, path := range opts.SkipAuthRegex {
292+ compiledRegex, err := regexp.Compile(path)
293+ if err != nil {
294+ return nil, err
295+ }
296+ routes = append(routes, &allowedRoute{
297+ method: "",
298+ pathRegex: compiledRegex,
299+ })
300+ }
301+
302+ for _, methodPath := range opts.SkipAuthRoutes {
303+ var (
304+ method string
305+ path string
306+ )
307+
308+ parts := strings.Split(methodPath, "=")
309+ if len(parts) == 1 {
310+ method = ""
311+ path = parts[0]
312+ } else {
313+ method = strings.ToUpper(parts[0])
314+ path = strings.Join(parts[1:], "=")
315+ }
316+
317+ compiledRegex, err := regexp.Compile(path)
318+ if err != nil {
319+ return nil, err
320+ }
321+ routes = append(routes, &allowedRoute{
322+ method: method,
323+ pathRegex: compiledRegex,
324+ })
325+ }
326+
327+ return routes, nil
328+}
329+
280330// GetRedirectURI returns the redirectURL that the upstream OAuth Provider will
281331// redirect clients to once authenticated
282332func (p *OAuthProxy) GetRedirectURI(host string) string {
@@ -584,16 +634,16 @@ func (p *OAuthProxy) IsValidRedirect(redirect string) bool {
584634 }
585635}
586636
587−// IsWhitelistedRequest is used to check if auth should be skipped for this request
588−func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) bool {
637+// IsAllowedRequest is used to check if auth should be skipped for this request
638+func (p *OAuthProxy) IsAllowedRequest(req *http.Request) bool {
589639 isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS"
590− return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path) || p.IsTrustedIP(req)
640+ return isPreflightRequestAllowed || p.isAllowedRoute(req) || p.IsTrustedIP(req)
591641}
592642
593−// IsWhitelistedPath is used to check if the request path is allowed without auth
594−func (p *OAuthProxy) IsWhitelistedPath(path string) bool {
595− for _, u := range p.compiledRegex {
596− if u.MatchString(path) {
643+// IsAllowedRoute is used to check if the request method & path is allowed without auth
644+func (p *OAuthProxy) isAllowedRoute(req *http.Request) bool {
645+ for _, route := range p.allowedRoutes {
646+ if (route.method == "" || req.Method == route.method) && route.pathRegex.MatchString(req.URL.Path) {
597647 return true
598648 }
599649 }
@@ -643,7 +693,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
643693 switch path := req.URL.Path; {
644694 case path == p.RobotsPath:
645695 p.RobotsTxt(rw)
646− case p.IsWhitelistedRequest(req):
696+ case p.IsAllowedRequest(req):
647697 p.SkipAuthProxy(rw, req)
648698 case path == p.SignInPath:
649699 p.SignIn(rw, req)
@@ -831,7 +881,7 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request)
831881 rw.WriteHeader(http.StatusAccepted)
832882}
833883
834−// SkipAuthProxy proxies whitelisted requests and skips authentication
884+// SkipAuthProxy proxies allowlisted requests and skips authentication
835885func (p *OAuthProxy) SkipAuthProxy(rw http.ResponseWriter, req *http.Request) {
836886 if p.skipAuthStripHeaders {
837887 p.stripAuthHeaders(req)
@@ -1026,7 +1076,7 @@ func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Req
10261076 }
10271077}
10281078
1029−// stripAuthHeaders removes Auth headers for whitelisted routes from skipAuthRegex
1079+// stripAuthHeaders removes Auth headers for allowlisted routes from skipAuthRegex
10301080func (p *OAuthProxy) stripAuthHeaders(req *http.Request) {
10311081 if p.PassBasicAuth {
10321082 req.Header.Del("X-Forwarded-User")