git.iko.soy/typeable/oauth2-proxy
on ea261ca help sign in
182 lines 7.1 kB history raw
1package upstream
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "net/url"
8 "regexp"
9 "sort"
10 "strings"
12 "github.com/gorilla/mux"
13 "github.com/justinas/alice"
14 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
15 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/app/pagewriter"
16 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
19// ProxyErrorHandler is a function that will be used to render error pages when
20// HTTP proxies fail to connect to upstream servers.
21type ProxyErrorHandler func(http.ResponseWriter, *http.Request, error)
23// NewProxy creates a new multiUpstreamProxy that can serve requests directed to
24// multiple upstreams.
25func NewProxy(upstreams options.UpstreamConfig, sigData *options.SignatureData, writer pagewriter.Writer) (http.Handler, error) {
26 m := &multiUpstreamProxy{
27 serveMux: mux.NewRouter(),
28 }
30 if upstreams.ProxyRawPath {
31 m.serveMux.UseEncodedPath()
32 }
34 for _, upstream := range sortByPathLongest(upstreams.Upstreams) {
35 if upstream.Static {
36 if err := m.registerStaticResponseHandler(upstream, writer); err != nil {
37 return nil, fmt.Errorf("could not register static upstream %q: %v", upstream.ID, err)
38 }
39 continue
40 }
42 u, err := url.Parse(upstream.URI)
43 if err != nil {
44 return nil, fmt.Errorf("error parsing URI for upstream %q: %w", upstream.ID, err)
45 }
46 switch u.Scheme {
47 case fileScheme:
48 if err := m.registerFileServer(upstream, u, writer); err != nil {
49 return nil, fmt.Errorf("could not register file upstream %q: %v", upstream.ID, err)
50 }
51 case httpScheme, httpsScheme:
52 if err := m.registerHTTPUpstreamProxy(upstream, u, sigData, writer); err != nil {
53 return nil, fmt.Errorf("could not register HTTP upstream %q: %v", upstream.ID, err)
54 }
55 default:
56 return nil, fmt.Errorf("unknown scheme for upstream %q: %q", upstream.ID, u.Scheme)
57 }
58 }
60 registerTrailingSlashHandler(m.serveMux)
61 return m, nil
64// multiUpstreamProxy will serve requests directed to multiple upstream servers
65// registered in the serverMux.
66type multiUpstreamProxy struct {
67 serveMux *mux.Router
70// ServerHTTP handles HTTP requests.
71func (m *multiUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
72 m.serveMux.ServeHTTP(rw, req)
75// registerStaticResponseHandler registers a static response handler with at the given path.
76func (m *multiUpstreamProxy) registerStaticResponseHandler(upstream options.Upstream, writer pagewriter.Writer) error {
77 logger.Printf("mapping path %q => static response %d", upstream.Path, derefStaticCode(upstream.StaticCode))
78 return m.registerHandler(upstream, newStaticResponseHandler(upstream.ID, upstream.StaticCode), writer)
81// registerFileServer registers a new fileServer based on the configuration given.
82func (m *multiUpstreamProxy) registerFileServer(upstream options.Upstream, u *url.URL, writer pagewriter.Writer) error {
83 logger.Printf("mapping path %q => file system %q", upstream.Path, u.Path)
84 return m.registerHandler(upstream, newFileServer(upstream.ID, upstream.Path, u.Path), writer)
87// registerHTTPUpstreamProxy registers a new httpUpstreamProxy based on the configuration given.
88func (m *multiUpstreamProxy) registerHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, writer pagewriter.Writer) error {
89 logger.Printf("mapping path %q => upstream %q", upstream.Path, upstream.URI)
90 return m.registerHandler(upstream, newHTTPUpstreamProxy(upstream, u, sigData, writer.ProxyErrorHandler), writer)
93// registerHandler ensures the given handler is regiestered with the serveMux.
94func (m *multiUpstreamProxy) registerHandler(upstream options.Upstream, handler http.Handler, writer pagewriter.Writer) error {
95 if upstream.RewriteTarget == "" {
96 m.registerSimpleHandler(upstream.Path, handler)
97 return nil
98 }
100 return m.registerRewriteHandler(upstream, handler, writer)
103// registerSimpleHandler maintains the behaviour of the go standard serveMux
104// by ensuring any path with a trailing `/` matches all paths under that prefix.
105func (m *multiUpstreamProxy) registerSimpleHandler(path string, handler http.Handler) {
106 if strings.HasSuffix(path, "/") {
107 m.serveMux.PathPrefix(path).Handler(handler)
108 } else {
109 m.serveMux.Path(path).Handler(handler)
110 }
113// registerRewriteHandler ensures the handler is registered for all paths
114// which match the regex defined in the Path.
115// Requests to the handler will have the request path rewritten before the
116// request is made to the next handler.
117func (m *multiUpstreamProxy) registerRewriteHandler(upstream options.Upstream, handler http.Handler, writer pagewriter.Writer) error {
118 rewriteRegExp, err := regexp.Compile(upstream.Path)
119 if err != nil {
120 return fmt.Errorf("invalid path %q for upstream: %v", upstream.Path, err)
121 }
123 rewrite := newRewritePath(rewriteRegExp, upstream.RewriteTarget, writer)
124 h := alice.New(rewrite).Then(handler)
125 m.serveMux.MatcherFunc(func(req *http.Request, match *mux.RouteMatch) bool {
126 return rewriteRegExp.MatchString(req.URL.Path)
127 }).Handler(h)
129 return nil
132// registerTrailingSlashHandler creates a new matcher that will check if the
133// requested path would match if it had a trailing slash appended.
134// If the path matches with a trailing slash, we send back a redirect.
135// This allows us to be consistent with the built in go servemux implementation.
136func registerTrailingSlashHandler(serveMux *mux.Router) {
137 serveMux.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
138 if strings.HasSuffix(req.URL.Path, "/") {
139 return false
140 }
142 // Use a separate RouteMatch so that we can redirect to the path + /.
143 // If we pass through the match then the matched backed will be served
144 // instead of the redirect handler.
145 m := &mux.RouteMatch{}
146 slashReq := req.Clone(context.Background())
147 slashReq.URL.Path += "/"
148 return serveMux.Match(slashReq, m)
149 }).Handler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
150 http.Redirect(rw, req, req.URL.String()+"/", http.StatusMovedPermanently)
151 }))
154// sortByPathLongest ensures that the upstreams are sorted by longest path.
155// If rewrites are involved, a rewrite takes precedence over a non-rewrite.
156// When two upstreams define rewrites, whichever has the longest path will take
157// precedence (note this is the input to the rewrite logic).
158// This does not account for when a rewrite would actually make the path shorter.
159// This should maintain the sorting behaviour of the standard go serve mux.
160func sortByPathLongest(in []options.Upstream) []options.Upstream {
161 sort.Slice(in, func(i, j int) bool {
162 iRW := in[i].RewriteTarget
163 jRW := in[j].RewriteTarget
165 switch {
166 case iRW != "" && jRW != "":
167 // If both have a rewrite target, whichever has the longest pattern
168 // should go first
169 return len(in[i].Path) > len(in[j].Path)
170 case iRW != "" && jRW == "":
171 // Only one has rewrite, it goes first
172 return true
173 case iRW == "" && jRW != "":
174 // Only one has rewrite, it goes first
175 return false
176 default:
177 // Default to longest Path wins
178 return len(in[i].Path) > len(in[j].Path)
179 }
180 })
181 return in