Implement subdomain-based routing
This commit is contained in:
parent
a91cce7ab9
commit
cd93f4d947
@ -96,6 +96,7 @@
|
||||
- [#159](https://github.com/pusher/oauth2_proxy/pull/159) Add option to skip the OIDC provider verified email check: `--insecure-oidc-allow-unverified-email`
|
||||
- [#210](https://github.com/pusher/oauth2_proxy/pull/210) Update base image from Alpine 3.9 to 3.10 (@steakunderscore)
|
||||
- [#211](https://github.com/pusher/oauth2_proxy/pull/211) Switch from dep to go modules (@steakunderscore)
|
||||
- [#204](https://github.com/pusher/oauth2_proxy/pull/204) Add subdomain-based routing. It is now possible to route based on domain name and path.
|
||||
|
||||
# v3.2.0
|
||||
|
||||
|
@ -123,6 +123,17 @@ Static file paths are configured as a file:// URL. `file:///var/www/static/` wil
|
||||
|
||||
Multiple upstreams can either be configured by supplying a comma separated list to the `-upstream` parameter, supplying the parameter multiple times or provinding a list in the [config file](#config-file). When multiple upstreams are used routing to them will be based on the path they are set up with.
|
||||
|
||||
Subdomain-based routing is possible by prepending a subdomain followed by a `|` to the upstream URL, like this:
|
||||
|
||||
```
|
||||
test |http://127.0.0.1:8082/
|
||||
other|http://127.0.0.1:8083/
|
||||
other|http://127.0.0.1:8084/path/
|
||||
|http://127.0.0.1:8085/
|
||||
```
|
||||
|
||||
Assuming cookie domain is set to `.example.com`, the requests `test.example.com`, `other.example.com`, `other.example.com/path/` and `example.com` will all be routed to different upstreams. Any spacing around the `|` separator is ignored.
|
||||
|
||||
### Environment variables
|
||||
|
||||
Every command line argument can be specified as an environment variable by
|
||||
|
@ -84,7 +84,7 @@ type OAuthProxy struct {
|
||||
SignInMessage string
|
||||
HtpasswdFile *HtpasswdFile
|
||||
DisplayHtpasswdForm bool
|
||||
serveMux http.Handler
|
||||
serveMuxes map[string]http.Handler
|
||||
SetXAuthRequest bool
|
||||
PassBasicAuth bool
|
||||
SkipProviderButton bool
|
||||
@ -193,17 +193,27 @@ func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.Hma
|
||||
|
||||
// NewOAuthProxy creates a new instance of OOuthProxy from the options provided
|
||||
func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
||||
serveMux := http.NewServeMux()
|
||||
serveMuxes := map[string]http.Handler{}
|
||||
var auth hmacauth.HmacAuth
|
||||
if sigData := opts.signatureData; sigData != nil {
|
||||
auth = hmacauth.NewHmacAuth(sigData.hash, []byte(sigData.key),
|
||||
SignatureHeader, SignatureHeaders)
|
||||
}
|
||||
for _, u := range opts.proxyURLs {
|
||||
for host, urls := range opts.proxyURLs {
|
||||
if host != "*" && strings.HasPrefix(opts.CookieDomain, ".") && !strings.HasSuffix(host, opts.CookieDomain) {
|
||||
if host == "" {
|
||||
host = opts.CookieDomain[1:]
|
||||
} else {
|
||||
host = host + opts.CookieDomain
|
||||
}
|
||||
}
|
||||
serveMux := http.NewServeMux()
|
||||
serveMuxes[host] = serveMux
|
||||
for _, u := range urls {
|
||||
path := u.Path
|
||||
switch u.Scheme {
|
||||
case httpScheme, httpsScheme:
|
||||
logger.Printf("mapping path %q => upstream %q", path, u)
|
||||
logger.Printf("mapping path %q for %q => upstream %q", path, host, u)
|
||||
proxy := NewWebSocketOrRestReverseProxy(u, opts, auth)
|
||||
serveMux.Handle(path, proxy)
|
||||
|
||||
@ -224,6 +234,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
||||
panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, u := range opts.CompiledRegex {
|
||||
logger.Printf("compiled skip-auth-regex => %q", u)
|
||||
}
|
||||
@ -245,6 +256,12 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
||||
refresh = fmt.Sprintf("after %s", opts.CookieRefresh)
|
||||
}
|
||||
|
||||
whitelistDomains := opts.WhitelistDomains
|
||||
if len(serveMuxes) >= 2 && len(whitelistDomains) == 0 && opts.CookieDomain != "" {
|
||||
// by default the cookie-domain is allowed
|
||||
whitelistDomains = append(whitelistDomains, opts.CookieDomain)
|
||||
}
|
||||
|
||||
logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s path:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, opts.CookieDomain, opts.CookiePath, refresh)
|
||||
|
||||
return &OAuthProxy{
|
||||
@ -270,9 +287,9 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
||||
ProxyPrefix: opts.ProxyPrefix,
|
||||
provider: opts.provider,
|
||||
sessionStore: opts.sessionStore,
|
||||
serveMux: serveMux,
|
||||
serveMuxes: serveMuxes,
|
||||
redirectURL: redirectURL,
|
||||
whitelistDomains: opts.WhitelistDomains,
|
||||
whitelistDomains: whitelistDomains,
|
||||
skipAuthRegex: opts.SkipAuthRegex,
|
||||
skipAuthPreflight: opts.SkipAuthPreflight,
|
||||
skipJwtBearerTokens: opts.SkipJwtBearerTokens,
|
||||
@ -481,6 +498,9 @@ func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error)
|
||||
}
|
||||
|
||||
redirect = req.Form.Get("rd")
|
||||
if len(p.serveMuxes) >= 2 && strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//") {
|
||||
redirect = "https://" + req.Host + redirect
|
||||
}
|
||||
if !p.IsValidRedirect(redirect) {
|
||||
redirect = req.URL.Path
|
||||
if strings.HasPrefix(redirect, p.ProxyPrefix) {
|
||||
@ -543,7 +563,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
case path == p.PingPath:
|
||||
p.PingPage(rw)
|
||||
case p.IsWhitelistedRequest(req):
|
||||
p.serveMux.ServeHTTP(rw, req)
|
||||
p.serveRequest(rw, req)
|
||||
case path == p.SignInPath:
|
||||
p.SignIn(rw, req)
|
||||
case path == p.SignOutPath:
|
||||
@ -687,6 +707,25 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request)
|
||||
rw.WriteHeader(http.StatusAccepted)
|
||||
}
|
||||
|
||||
// serveRequest actually proxies the request to the upstream
|
||||
func (p *OAuthProxy) serveRequest(rw http.ResponseWriter, req *http.Request) {
|
||||
domain := req.Host
|
||||
if h, _, err := net.SplitHostPort(domain); err == nil {
|
||||
domain = h
|
||||
}
|
||||
var serveMux http.Handler
|
||||
if m, ok := p.serveMuxes[domain]; ok {
|
||||
serveMux = m
|
||||
} else {
|
||||
serveMux = p.serveMuxes["*"]
|
||||
}
|
||||
if serveMux == nil {
|
||||
p.ErrorPage(rw, 404, "Page not found", "Invalid Host")
|
||||
return
|
||||
}
|
||||
serveMux.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
// Proxy proxies the user request if the user is authenticated else it prompts
|
||||
// them to authenticate
|
||||
func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
||||
@ -695,7 +734,7 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
||||
case nil:
|
||||
// we are authenticated
|
||||
p.addHeadersForProxying(rw, req, session)
|
||||
p.serveMux.ServeHTTP(rw, req)
|
||||
p.serveRequest(rw, req)
|
||||
|
||||
case ErrNeedsLogin:
|
||||
// we need to send the user to a login screen
|
||||
|
11
options.go
11
options.go
@ -119,7 +119,7 @@ type Options struct {
|
||||
|
||||
// internal values that are set after config validation
|
||||
redirectURL *url.URL
|
||||
proxyURLs []*url.URL
|
||||
proxyURLs map[string][]*url.URL
|
||||
CompiledRegex []*regexp.Regexp
|
||||
provider providers.Provider
|
||||
sessionStore sessionsapi.SessionStore
|
||||
@ -284,7 +284,14 @@ func (o *Options) Validate() error {
|
||||
|
||||
o.redirectURL, msgs = parseURL(o.RedirectURL, "redirect", msgs)
|
||||
|
||||
domainUpstreamRegex := regexp.MustCompile(`^\s*([a-z0-9._-]*)\s*\|\s*(.*)$`)
|
||||
o.proxyURLs = map[string][]*url.URL{}
|
||||
for _, u := range o.Upstreams {
|
||||
subdomain := "*"
|
||||
m := domainUpstreamRegex.FindStringSubmatch(u)
|
||||
if m != nil {
|
||||
subdomain, u = m[1], m[2]
|
||||
}
|
||||
upstreamURL, err := url.Parse(u)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf("error parsing upstream: %s", err))
|
||||
@ -292,7 +299,7 @@ func (o *Options) Validate() error {
|
||||
if upstreamURL.Path == "" {
|
||||
upstreamURL.Path = "/"
|
||||
}
|
||||
o.proxyURLs = append(o.proxyURLs, upstreamURL)
|
||||
o.proxyURLs[subdomain] = append(o.proxyURLs[subdomain], upstreamURL)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,11 +86,25 @@ func TestRedirectURL(t *testing.T) {
|
||||
func TestProxyURLs(t *testing.T) {
|
||||
o := testOptions()
|
||||
o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8081")
|
||||
o.Upstreams = append(o.Upstreams, "|http://127.0.0.1:8082")
|
||||
o.Upstreams = append(o.Upstreams, " |http://127.0.0.1:8083/x/")
|
||||
o.Upstreams = append(o.Upstreams, "sub.domain|http://127.0.0.1:8082/abc")
|
||||
o.Upstreams = append(o.Upstreams, " sub.domain\t | http://127.0.0.1:8083/abc/")
|
||||
assert.Equal(t, nil, o.Validate())
|
||||
expected := []*url.URL{
|
||||
expected := map[string][]*url.URL{
|
||||
"*": {
|
||||
{Scheme: "http", Host: "127.0.0.1:8080", Path: "/"},
|
||||
// note the '/' was added
|
||||
{Scheme: "http", Host: "127.0.0.1:8081", Path: "/"},
|
||||
},
|
||||
"": {
|
||||
{Scheme: "http", Host: "127.0.0.1:8082", Path: "/"},
|
||||
{Scheme: "http", Host: "127.0.0.1:8083", Path: "/x/"},
|
||||
},
|
||||
"sub.domain": {
|
||||
{Scheme: "http", Host: "127.0.0.1:8082", Path: "/abc"},
|
||||
{Scheme: "http", Host: "127.0.0.1:8083", Path: "/abc/"},
|
||||
},
|
||||
}
|
||||
assert.Equal(t, expected, o.proxyURLs)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user