diff --git a/oauthproxy.go b/oauthproxy.go index d25c3c8e..df4dd997 100644 --- a/oauthproxy.go +++ b/oauthproxy.go @@ -78,6 +78,8 @@ type OAuthProxy struct { SignInPath string + enableAuthRouters bool + authRouters []allowedRoute allowedRoutes []allowedRoute apiRoutes []apiRoute redirectURL *url.URL // the url to receive requests at @@ -180,6 +182,11 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr return nil, err } + authRouters, err := buildProxyRoutersList(opts) + if err != nil { + return nil, err + } + apiRoutes, err := buildAPIRoutes(opts) if err != nil { return nil, err @@ -230,6 +237,8 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr upstreamProxy: upstreamProxy, redirectValidator: redirectValidator, appDirector: appDirector, + authRouters: authRouters, + enableAuthRouters: opts.EnableAuthRouters, } p.buildServeMux(opts.ProxyPrefix) @@ -485,6 +494,34 @@ func buildRoutesAllowlist(opts *options.Options) ([]allowedRoute, error) { return routes, nil } +func buildProxyRoutersList(opts *options.Options) ([]allowedRoute, error) { + routes := make([]allowedRoute, 0, len(opts.AuthRouters)) + + for _, mPath := range opts.AuthRouters { + method, path := "", "" + + ps := strings.SplitN(mPath, "=", 2) + if len(ps) == 1 { + path = ps[0] + } else { + method = strings.ToUpper(ps[0]) + path = ps[1] + } + + compiledRegex, err := regexp.Compile(path) + if err != nil { + return nil, err + } + + routes = append(routes, allowedRoute{ + method: method, + pathRegex: compiledRegex, + }) + } + + return routes, nil +} + // buildAPIRoutes builds an []apiRoute from ApiRoutes option func buildAPIRoutes(opts *options.Options) ([]apiRoute, error) { routes := make([]apiRoute, 0, len(opts.APIRoutes)) @@ -573,6 +610,16 @@ func (p *OAuthProxy) isAllowedRoute(req *http.Request) bool { return false } +func (p *OAuthProxy) isProxyRoute(req *http.Request) bool { + for _, route := range p.authRouters { + if (route.method == "" || req.Method == route.method) && route.pathRegex.MatchString(req.URL.Path) { + return true + } + } + + return false +} + func (p *OAuthProxy) isAPIPath(req *http.Request) bool { for _, route := range p.apiRoutes { if route.pathRegex.MatchString(req.URL.Path) { @@ -1045,6 +1092,10 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R return session, nil } + if p.enableAuthRouters && !p.isProxyRoute(req) { + return session, nil + } + if session == nil { return nil, ErrNeedsLogin } diff --git a/pkg/apis/options/options.go b/pkg/apis/options/options.go index 0af8df3f..f0733a35 100644 --- a/pkg/apis/options/options.go +++ b/pkg/apis/options/options.go @@ -60,6 +60,8 @@ type Options struct { SSLInsecureSkipVerify bool `flag:"ssl-insecure-skip-verify" cfg:"ssl_insecure_skip_verify"` SkipAuthPreflight bool `flag:"skip-auth-preflight" cfg:"skip_auth_preflight"` ForceJSONErrors bool `flag:"force-json-errors" cfg:"force_json_errors"` + EnableAuthRouters bool `flag:"enable-auth-routers" cfg:"enable_auth_routers"` + AuthRouters []string `flag:"auth-routers" cfg:"auth_routers"` SignatureKey string `flag:"signature-key" cfg:"signature_key"` GCPHealthChecks bool `flag:"gcp-healthchecks" cfg:"gcp_healthchecks"` @@ -124,6 +126,8 @@ func NewFlagSet() *pflag.FlagSet { flagSet.Bool("skip-auth-preflight", false, "will skip authentication for OPTIONS requests") flagSet.Bool("ssl-insecure-skip-verify", false, "skip validation of certificates presented when using HTTPS providers") flagSet.Bool("skip-jwt-bearer-tokens", false, "will skip requests that have verified JWT bearer tokens (default false)") + flagSet.Bool("enable-auth-routers", false, "will proxy only routes configured by authrouters") + flagSet.StringSlice("auth-routers", []string{}, "authentication for requests that match the method & path. Format: method=path_regex OR path_regex alone for all methods.") flagSet.Bool("force-json-errors", false, "will force JSON errors instead of HTTP error pages or redirects") 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)") diff --git a/pkg/validation/allowlist.go b/pkg/validation/allowlist.go index 7a36027a..af1999d9 100644 --- a/pkg/validation/allowlist.go +++ b/pkg/validation/allowlist.go @@ -67,6 +67,25 @@ func validateAPIRoutes(o *options.Options) []string { return validateRegexes(o.APIRoutes) } +// validateAuthRoutes validates method=path routes passed with options.AuthRouters +func validateAuthRoutes1(o *options.Options) []string { + msgs := []string{} + for _, route := range o.AuthRouters { + var regex string + parts := strings.SplitN(route, "=", 2) + if len(parts) == 1 { + regex = parts[0] + } else { + regex = parts[1] + } + _, err := regexp.Compile(regex) + if err != nil { + msgs = append(msgs, fmt.Sprintf("error compiling regex /%s/: %v", regex, err)) + } + } + return msgs +} + // validateRegexes validates all regexes and returns a list of messages in case of error func validateRegexes(regexes []string) []string { msgs := []string{} diff --git a/pkg/validation/options.go b/pkg/validation/options.go index a3ce0518..e0293b24 100644 --- a/pkg/validation/options.go +++ b/pkg/validation/options.go @@ -100,6 +100,8 @@ func Validate(o *options.Options) error { // Do this after ReverseProxy validation for TrustedIP coordinated checks msgs = append(msgs, validateAllowlists(o)...) + msgs = append(msgs, validateAuthRoutes1(o)...) + if len(msgs) != 0 { return fmt.Errorf("invalid configuration:\n %s", strings.Join(msgs, "\n "))