mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2024-11-24 08:52:25 +02:00
Add upstream package with Proxy server implementation
This commit is contained in:
parent
b6b5194190
commit
e1c3e938cc
32
pkg/upstream/file.go
Normal file
32
pkg/upstream/file.go
Normal file
@ -0,0 +1,32 @@
|
||||
package upstream
|
||||
|
||||
import "net/http"
|
||||
|
||||
const fileScheme = "file"
|
||||
|
||||
// newFileServer creates a new fileServer that can serve requests
|
||||
// to a file system location.
|
||||
func newFileServer(id, path, fileSystemPath string) http.Handler {
|
||||
return &fileServer{
|
||||
upstream: id,
|
||||
handler: newFileServerForPath(path, fileSystemPath),
|
||||
}
|
||||
}
|
||||
|
||||
// newFileServerForPath creates a http.Handler to serve files from the filesystem
|
||||
func newFileServerForPath(path string, filesystemPath string) http.Handler {
|
||||
return http.StripPrefix(path, http.FileServer(http.Dir(filesystemPath)))
|
||||
}
|
||||
|
||||
// fileServer represents a single filesystem upstream proxy
|
||||
type fileServer struct {
|
||||
upstream string
|
||||
handler http.Handler
|
||||
}
|
||||
|
||||
// ServeHTTP proxies requests to the upstream provider while signing the
|
||||
// request headers
|
||||
func (u *fileServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("GAP-Upstream-Address", u.upstream)
|
||||
u.handler.ServeHTTP(rw, req)
|
||||
}
|
157
pkg/upstream/http.go
Normal file
157
pkg/upstream/http.go
Normal file
@ -0,0 +1,157 @@
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/mbland/hmacauth"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||
"github.com/yhat/wsutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// SignatureHeader is the name of the request header containing the GAP Signature
|
||||
// Part of hmacauth
|
||||
SignatureHeader = "GAP-Signature"
|
||||
|
||||
httpScheme = "http"
|
||||
httpsScheme = "https"
|
||||
)
|
||||
|
||||
// SignatureHeaders contains the headers to be signed by the hmac algorithm
|
||||
// Part of hmacauth
|
||||
var SignatureHeaders = []string{
|
||||
"Content-Length",
|
||||
"Content-Md5",
|
||||
"Content-Type",
|
||||
"Date",
|
||||
"Authorization",
|
||||
"X-Forwarded-User",
|
||||
"X-Forwarded-Email",
|
||||
"X-Forwarded-Preferred-User",
|
||||
"X-Forwarded-Access-Token",
|
||||
"Cookie",
|
||||
"Gap-Auth",
|
||||
}
|
||||
|
||||
// newHTTPUpstreamProxy creates a new httpUpstreamProxy that can serve requests
|
||||
// to a single upstream host.
|
||||
func newHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, errorHandler ProxyErrorHandler) http.Handler {
|
||||
// Set path to empty so that request paths start at the server root
|
||||
u.Path = ""
|
||||
|
||||
// Create a ReverseProxy
|
||||
proxy := newReverseProxy(u, upstream, errorHandler)
|
||||
|
||||
// Set up a WebSocket proxy if required
|
||||
var wsProxy http.Handler
|
||||
if upstream.ProxyWebSockets {
|
||||
wsProxy = newWebSocketReverseProxy(u, upstream.InsecureSkipTLSVerify)
|
||||
}
|
||||
|
||||
var auth hmacauth.HmacAuth
|
||||
if sigData != nil {
|
||||
auth = hmacauth.NewHmacAuth(sigData.Hash, []byte(sigData.Key), SignatureHeader, SignatureHeaders)
|
||||
}
|
||||
|
||||
return &httpUpstreamProxy{
|
||||
upstream: upstream.ID,
|
||||
handler: proxy,
|
||||
wsHandler: wsProxy,
|
||||
auth: auth,
|
||||
}
|
||||
}
|
||||
|
||||
// httpUpstreamProxy represents a single HTTP(S) upstream proxy
|
||||
type httpUpstreamProxy struct {
|
||||
upstream string
|
||||
handler http.Handler
|
||||
wsHandler http.Handler
|
||||
auth hmacauth.HmacAuth
|
||||
}
|
||||
|
||||
// ServeHTTP proxies requests to the upstream provider while signing the
|
||||
// request headers
|
||||
func (h *httpUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("GAP-Upstream-Address", h.upstream)
|
||||
if h.auth != nil {
|
||||
req.Header.Set("GAP-Auth", rw.Header().Get("GAP-Auth"))
|
||||
h.auth.SignRequest(req)
|
||||
}
|
||||
if h.wsHandler != nil && strings.EqualFold(req.Header.Get("Connection"), "upgrade") && req.Header.Get("Upgrade") == "websocket" {
|
||||
h.wsHandler.ServeHTTP(rw, req)
|
||||
} else {
|
||||
h.handler.ServeHTTP(rw, req)
|
||||
}
|
||||
}
|
||||
|
||||
// newReverseProxy creates a new reverse proxy for proxying requests to upstream
|
||||
// servers based on the upstream configuration provided.
|
||||
// The proxy should render an error page if there are failures connecting to the
|
||||
// upstream server.
|
||||
func newReverseProxy(target *url.URL, upstream options.Upstream, errorHandler ProxyErrorHandler) http.Handler {
|
||||
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||
|
||||
// Configure options on the SingleHostReverseProxy
|
||||
proxy.FlushInterval = *upstream.FlushInterval
|
||||
if upstream.InsecureSkipTLSVerify {
|
||||
proxy.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
}
|
||||
|
||||
// Set the request director based on the PassHostHeader option
|
||||
if !upstream.PassHostHeader {
|
||||
setProxyUpstreamHostHeader(proxy, target)
|
||||
} else {
|
||||
setProxyDirector(proxy)
|
||||
}
|
||||
|
||||
// Set the error handler so that upstream connection failures render the
|
||||
// error page instead of sending a empty response
|
||||
if errorHandler != nil {
|
||||
proxy.ErrorHandler = errorHandler
|
||||
}
|
||||
return proxy
|
||||
}
|
||||
|
||||
// setProxyUpstreamHostHeader sets the proxy.Director so that upstream requests
|
||||
// receive a host header matching the target URL.
|
||||
func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
|
||||
director := proxy.Director
|
||||
proxy.Director = func(req *http.Request) {
|
||||
director(req)
|
||||
// use RequestURI so that we aren't unescaping encoded slashes in the request path
|
||||
req.Host = target.Host
|
||||
req.URL.Opaque = req.RequestURI
|
||||
req.URL.RawQuery = ""
|
||||
}
|
||||
}
|
||||
|
||||
// setProxyDirector sets the proxy.Director so that request URIs are escaped
|
||||
// when proxying to usptream servers.
|
||||
func setProxyDirector(proxy *httputil.ReverseProxy) {
|
||||
director := proxy.Director
|
||||
proxy.Director = func(req *http.Request) {
|
||||
director(req)
|
||||
// use RequestURI so that we aren't unescaping encoded slashes in the request path
|
||||
req.URL.Opaque = req.RequestURI
|
||||
req.URL.RawQuery = ""
|
||||
}
|
||||
}
|
||||
|
||||
// newWebSocketReverseProxy creates a new reverse proxy for proxying websocket connections.
|
||||
func newWebSocketReverseProxy(u *url.URL, skipTLSVerify bool) http.Handler {
|
||||
// This should create the correct scheme for insecure vs secure connections
|
||||
wsScheme := "ws" + strings.TrimPrefix(u.Scheme, "http")
|
||||
wsURL := &url.URL{Scheme: wsScheme, Host: u.Host}
|
||||
|
||||
wsProxy := wsutil.NewSingleHostReverseProxy(wsURL)
|
||||
if skipTLSVerify {
|
||||
wsProxy.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
return wsProxy
|
||||
}
|
90
pkg/upstream/proxy.go
Normal file
90
pkg/upstream/proxy.go
Normal file
@ -0,0 +1,90 @@
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
|
||||
"github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
|
||||
)
|
||||
|
||||
// ProxyErrorHandler is a function that will be used to render error pages when
|
||||
// HTTP proxies fail to connect to upstream servers.
|
||||
type ProxyErrorHandler func(http.ResponseWriter, *http.Request, error)
|
||||
|
||||
// NewProxy creates a new multiUpstreamProxy that can serve requests directed to
|
||||
// multiple upstreams.
|
||||
func NewProxy(upstreams options.Upstreams, sigData *options.SignatureData, errorHandler ProxyErrorHandler) (http.Handler, error) {
|
||||
m := &multiUpstreamProxy{
|
||||
serveMux: http.NewServeMux(),
|
||||
}
|
||||
|
||||
for _, upstream := range upstreams {
|
||||
if upstream.Static {
|
||||
m.registerStaticResponseHandler(upstream)
|
||||
continue
|
||||
}
|
||||
|
||||
u, err := url.Parse(upstream.URI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing URI for upstream %q: %w", upstream.ID, err)
|
||||
}
|
||||
switch u.Scheme {
|
||||
case fileScheme:
|
||||
m.registerFileServer(upstream, u)
|
||||
case httpScheme, httpsScheme:
|
||||
m.registerHTTPUpstreamProxy(upstream, u, sigData, errorHandler)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown scheme for upstream %q: %q", upstream.ID, u.Scheme)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// multiUpstreamProxy will serve requests directed to multiple upstream servers
|
||||
// registered in the serverMux.
|
||||
type multiUpstreamProxy struct {
|
||||
serveMux *http.ServeMux
|
||||
}
|
||||
|
||||
// ServerHTTP handles HTTP requests.
|
||||
func (m *multiUpstreamProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
m.serveMux.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
// registerStaticResponseHandler registers a static response handler with at the given path.
|
||||
func (m *multiUpstreamProxy) registerStaticResponseHandler(upstream options.Upstream) {
|
||||
m.serveMux.Handle(upstream.Path, newStaticResponseHandler(upstream.ID, upstream.StaticCode))
|
||||
}
|
||||
|
||||
// registerFileServer registers a new fileServer based on the configuration given.
|
||||
func (m *multiUpstreamProxy) registerFileServer(upstream options.Upstream, u *url.URL) {
|
||||
logger.Printf("mapping path %q => file system %q", upstream.Path, u.Path)
|
||||
m.serveMux.Handle(upstream.Path, newFileServer(upstream.ID, upstream.Path, u.Path))
|
||||
}
|
||||
|
||||
// registerHTTPUpstreamProxy registers a new httpUpstreamProxy based on the configuration given.
|
||||
func (m *multiUpstreamProxy) registerHTTPUpstreamProxy(upstream options.Upstream, u *url.URL, sigData *options.SignatureData, errorHandler ProxyErrorHandler) {
|
||||
logger.Printf("mapping path %q => upstream %q", upstream.Path, upstream.URI)
|
||||
m.serveMux.Handle(upstream.Path, newHTTPUpstreamProxy(upstream, u, sigData, errorHandler))
|
||||
}
|
||||
|
||||
// NewProxyErrorHandler creates a ProxyErrorHandler using the template given.
|
||||
func NewProxyErrorHandler(errorTemplate *template.Template, proxyPrefix string) ProxyErrorHandler {
|
||||
return func(rw http.ResponseWriter, req *http.Request, proxyErr error) {
|
||||
logger.Printf("Error proxying to upstream server: %v", proxyErr)
|
||||
rw.WriteHeader(http.StatusBadGateway)
|
||||
data := struct {
|
||||
Title string
|
||||
Message string
|
||||
ProxyPrefix string
|
||||
}{
|
||||
Title: "Bad Gateway",
|
||||
Message: "Error proxying to upstream server",
|
||||
ProxyPrefix: proxyPrefix,
|
||||
}
|
||||
errorTemplate.Execute(rw, data)
|
||||
}
|
||||
}
|
34
pkg/upstream/static.go
Normal file
34
pkg/upstream/static.go
Normal file
@ -0,0 +1,34 @@
|
||||
package upstream
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const defaultStaticResponseCode = 200
|
||||
|
||||
// newStaticResponseHandler creates a new staticResponseHandler that serves a
|
||||
// a static response code.
|
||||
func newStaticResponseHandler(upstream string, code *int) http.Handler {
|
||||
if code == nil {
|
||||
c := defaultStaticResponseCode
|
||||
code = &c
|
||||
}
|
||||
return &staticResponseHandler{
|
||||
code: *code,
|
||||
upstream: upstream,
|
||||
}
|
||||
}
|
||||
|
||||
// staticResponseHandler responds with a static response with the given response code.
|
||||
type staticResponseHandler struct {
|
||||
code int
|
||||
upstream string
|
||||
}
|
||||
|
||||
// ServeHTTP serves a static response.
|
||||
func (s *staticResponseHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.Header().Set("GAP-Upstream-Address", s.upstream)
|
||||
rw.WriteHeader(s.code)
|
||||
fmt.Fprintf(rw, "Authenticated")
|
||||
}
|
Loading…
Reference in New Issue
Block a user