git.iko.soy/typeable/oauth2-proxy
on master help sign in
Add upstream package with Proxy server implementation
commite1c3e938cc80a2827d2b84767189102eda0f5d35 authorJoel Speed <joel.speed@hotmail.co.uk> dateTue 26 May 2020, 19:53 (6 years ago) parentb6b5194
4 files changed+313
pkg/upstream/file.go+32 pkg/upstream/http.go+157 pkg/upstream/proxy.go+90 pkg/upstream/static.go+34
pkg/upstream/file.gonew file+32
@@ -0,0 +1,32 @@
1+package upstream
2+
3+import "net/http"
4+
5+const fileScheme = "file"
6+
7+// newFileServer creates a new fileServer that can serve requests
8+// to a file system location.
9+func newFileServer(id, path, fileSystemPath string) http.Handler {
10+ return &fileServer{
11+ upstream: id,
12+ handler: newFileServerForPath(path, fileSystemPath),
13+ }
14+}
15+
16+// newFileServerForPath creates a http.Handler to serve files from the filesystem
17+func newFileServerForPath(path string, filesystemPath string) http.Handler {
18+ return http.StripPrefix(path, http.FileServer(http.Dir(filesystemPath)))
19+}
20+
21+// fileServer represents a single filesystem upstream proxy
22+type fileServer struct {
23+ upstream string
24+ handler http.Handler
25+}
26+
27+// ServeHTTP proxies requests to the upstream provider while signing the
28+// request headers
29+func (u *fileServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
30+ rw.Header().Set("GAP-Upstream-Address", u.upstream)
31+ u.handler.ServeHTTP(rw, req)
32+}
pkg/upstream/http.gonew file+157
@@ -0,0 +1,157 @@
1+package upstream
2+
3+import (
4+ "crypto/tls"
5+ "net/http"
6+ "net/http/httputil"
7+ "net/url"
8+ "strings"
9+
10+ "github.com/mbland/hmacauth"
11+ "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
12+ "github.com/yhat/wsutil"
13+)
14+
15+const (
16+ // SignatureHeader is the name of the request header containing the GAP Signature
17+ // Part of hmacauth
18+ SignatureHeader = "GAP-Signature"
19+
20+ httpScheme = "http"
21+ httpsScheme = "https"
22+)
23+
24+// SignatureHeaders contains the headers to be signed by the hmac algorithm
25+// Part of hmacauth
26+var SignatureHeaders = []string{
27+ "Content-Length",
28+ "Content-Md5",
29+ "Content-Type",
30+ "Date",
31+ "Authorization",
32+ "X-Forwarded-User",
33+ "X-Forwarded-Email",
34+ "X-Forwarded-Preferred-User",
35+ "X-Forwarded-Access-Token",
36+ "Cookie",
37+ "Gap-Auth",
38+}
39+
40+// newHTTPUpstreamProxy creates a new httpUpstreamProxy that can serve requests
41+// to a single upstream host.
42+func newHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, errorHandler ProxyErrorHandler) http.Handler {
43+ // Set path to empty so that request paths start at the server root
44+ u.Path = ""
45+
46+ // Create a ReverseProxy
47+ proxy := newReverseProxy(u, upstream, errorHandler)
48+
49+ // Set up a WebSocket proxy if required
50+ var wsProxy http.Handler
51+ if upstream.ProxyWebSockets {
52+ wsProxy = newWebSocketReverseProxy(u, upstream.InsecureSkipTLSVerify)
53+ }
54+
55+ var auth hmacauth.HmacAuth
56+ if sigData != nil {
57+ auth = hmacauth.NewHmacAuth(sigData.Hash, []byte(sigData.Key), SignatureHeader, SignatureHeaders)
58+ }
59+
60+ return &httpUpstreamProxy{
61+ upstream: upstream.ID,
62+ handler: proxy,
63+ wsHandler: wsProxy,
64+ auth: auth,
65+ }
66+}
67+
68+// httpUpstreamProxy represents a single HTTP(S) upstream proxy
69+type httpUpstreamProxy struct {
70+ upstream string
71+ handler http.Handler
72+ wsHandler http.Handler
73+ auth hmacauth.HmacAuth
74+}
75+
76+// ServeHTTP proxies requests to the upstream provider while signing the
77+// request headers
78+func (h *httpUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
79+ rw.Header().Set("GAP-Upstream-Address", h.upstream)
80+ if h.auth != nil {
81+ req.Header.Set("GAP-Auth", rw.Header().Get("GAP-Auth"))
82+ h.auth.SignRequest(req)
83+ }
84+ if h.wsHandler != nil && strings.EqualFold(req.Header.Get("Connection"), "upgrade") && req.Header.Get("Upgrade") == "websocket" {
85+ h.wsHandler.ServeHTTP(rw, req)
86+ } else {
87+ h.handler.ServeHTTP(rw, req)
88+ }
89+}
90+
91+// newReverseProxy creates a new reverse proxy for proxying requests to upstream
92+// servers based on the upstream configuration provided.
93+// The proxy should render an error page if there are failures connecting to the
94+// upstream server.
95+func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler ProxyErrorHandler) http.Handler {
96+ proxy := httputil.NewSingleHostReverseProxy(target)
97+
98+ // Configure options on the SingleHostReverseProxy
99+ proxy.FlushInterval = *upstream.FlushInterval
100+ if upstream.InsecureSkipTLSVerify {
101+ proxy.Transport = &http.Transport{
102+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
103+ }
104+ }
105+
106+ // Set the request director based on the PassHostHeader option
107+ if !upstream.PassHostHeader {
108+ setProxyUpstreamHostHeader(proxy, target)
109+ } else {
110+ setProxyDirector(proxy)
111+ }
112+
113+ // Set the error handler so that upstream connection failures render the
114+ // error page instead of sending a empty response
115+ if errorHandler != nil {
116+ proxy.ErrorHandler = errorHandler
117+ }
118+ return proxy
119+}
120+
121+// setProxyUpstreamHostHeader sets the proxy.Director so that upstream requests
122+// receive a host header matching the target URL.
123+func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
124+ director := proxy.Director
125+ proxy.Director = func(req *http.Request) {
126+ director(req)
127+ // use RequestURI so that we aren't unescaping encoded slashes in the request path
128+ req.Host = target.Host
129+ req.URL.Opaque = req.RequestURI
130+ req.URL.RawQuery = ""
131+ }
132+}
133+
134+// setProxyDirector sets the proxy.Director so that request URIs are escaped
135+// when proxying to usptream servers.
136+func setProxyDirector(proxy *httputil.ReverseProxy) {
137+ director := proxy.Director
138+ proxy.Director = func(req *http.Request) {
139+ director(req)
140+ // use RequestURI so that we aren't unescaping encoded slashes in the request path
141+ req.URL.Opaque = req.RequestURI
142+ req.URL.RawQuery = ""
143+ }
144+}
145+
146+// newWebSocketReverseProxy creates a new reverse proxy for proxying websocket connections.
147+func newWebSocketReverseProxy(u *url.URL, skipTLSVerify bool) http.Handler {
148+ // This should create the correct scheme for insecure vs secure connections
149+ wsScheme := "ws" + strings.TrimPrefix(u.Scheme, "http")
150+ wsURL := &url.URL{Scheme: wsScheme, Host: u.Host}
151+
152+ wsProxy := wsutil.NewSingleHostReverseProxy(wsURL)
153+ if skipTLSVerify {
154+ wsProxy.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
155+ }
156+ return wsProxy
157+}
pkg/upstream/proxy.gonew file+90
@@ -0,0 +1,90 @@
1+package upstream
2+
3+import (
4+ "fmt"
5+ "html/template"
6+ "net/http"
7+ "net/url"
8+
9+ "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
10+ "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
11+)
12+
13+// ProxyErrorHandler is a function that will be used to render error pages when
14+// HTTP proxies fail to connect to upstream servers.
15+type ProxyErrorHandler func(http.ResponseWriter, *http.Request, error)
16+
17+// NewProxy creates a new multiUpstreamProxy that can serve requests directed to
18+// multiple upstreams.
19+func NewProxy(upstreams options.Upstreams, sigData *options.SignatureData, errorHandler ProxyErrorHandler) (http.Handler, error) {
20+ m := &multiUpstreamProxy{
21+ serveMux: http.NewServeMux(),
22+ }
23+
24+ for _, upstream := range upstreams {
25+ if upstream.Static {
26+ m.registerStaticResponseHandler(upstream)
27+ continue
28+ }
29+
30+ u, err := url.Parse(upstream.URI)
31+ if err != nil {
32+ return nil, fmt.Errorf("error parsing URI for upstream %q: %w", upstream.ID, err)
33+ }
34+ switch u.Scheme {
35+ case fileScheme:
36+ m.registerFileServer(upstream, u)
37+ case httpScheme, httpsScheme:
38+ m.registerHTTPUpstreamProxy(upstream, u, sigData, errorHandler)
39+ default:
40+ return nil, fmt.Errorf("unknown scheme for upstream %q: %q", upstream.ID, u.Scheme)
41+ }
42+ }
43+ return m, nil
44+}
45+
46+// multiUpstreamProxy will serve requests directed to multiple upstream servers
47+// registered in the serverMux.
48+type multiUpstreamProxy struct {
49+ serveMux *http.ServeMux
50+}
51+
52+// ServerHTTP handles HTTP requests.
53+func (m *multiUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
54+ m.serveMux.ServeHTTP(rw, req)
55+}
56+
57+// registerStaticResponseHandler registers a static response handler with at the given path.
58+func (m *multiUpstreamProxy) registerStaticResponseHandler(upstream options.Upstream) {
59+ m.serveMux.Handle(upstream.Path, newStaticResponseHandler(upstream.ID, upstream.StaticCode))
60+}
61+
62+// registerFileServer registers a new fileServer based on the configuration given.
63+func (m *multiUpstreamProxy) registerFileServer(upstream options.Upstream, u *url.URL) {
64+ logger.Printf("mapping path %q => file system %q", upstream.Path, u.Path)
65+ m.serveMux.Handle(upstream.Path, newFileServer(upstream.ID, upstream.Path, u.Path))
66+}
67+
68+// registerHTTPUpstreamProxy registers a new httpUpstreamProxy based on the configuration given.
69+func (m *multiUpstreamProxy) registerHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, errorHandler ProxyErrorHandler) {
70+ logger.Printf("mapping path %q => upstream %q", upstream.Path, upstream.URI)
71+ m.serveMux.Handle(upstream.Path, newHTTPUpstreamProxy(upstream, u, sigData, errorHandler))
72+}
73+
74+// NewProxyErrorHandler creates a ProxyErrorHandler using the template given.
75+func NewProxyErrorHandler(errorTemplate *template.Template, proxyPrefix string) ProxyErrorHandler {
76+ return func(rw http.ResponseWriter, req *http.Request, proxyErr error) {
77+ logger.Printf("Error proxying to upstream server: %v", proxyErr)
78+ rw.WriteHeader(http.StatusBadGateway)
79+ data := struct {
80+ Title string
81+ Message string
82+ ProxyPrefix string
83+ }{
84+ Title: "Bad Gateway",
85+ Message: "Error proxying to upstream server",
86+ ProxyPrefix: proxyPrefix,
87+ }
88+ errorTemplate.Execute(rw, data)
89+ }
90+}
pkg/upstream/static.gonew file+34
@@ -0,0 +1,34 @@
1+package upstream
2+
3+import (
4+ "fmt"
5+ "net/http"
6+)
7+
8+const defaultStaticResponseCode = 200
9+
10+// newStaticResponseHandler creates a new staticResponseHandler that serves a
11+// a static response code.
12+func newStaticResponseHandler(upstream string, code *int) http.Handler {
13+ if code == nil {
14+ c := defaultStaticResponseCode
15+ code = &c
16+ }
17+ return &staticResponseHandler{
18+ code: *code,
19+ upstream: upstream,
20+ }
21+}
22+
23+// staticResponseHandler responds with a static response with the given response code.
24+type staticResponseHandler struct {
25+ code int
26+ upstream string
27+}
28+
29+// ServeHTTP serves a static response.
30+func (s *staticResponseHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
31+ rw.Header().Set("GAP-Upstream-Address", s.upstream)
32+ rw.WriteHeader(s.code)
33+ fmt.Fprintf(rw, "Authenticated")
34+}