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`
|
- [#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)
|
- [#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)
|
- [#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
|
# 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.
|
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
|
### Environment variables
|
||||||
|
|
||||||
Every command line argument can be specified as an environment variable by
|
Every command line argument can be specified as an environment variable by
|
||||||
|
@ -84,7 +84,7 @@ type OAuthProxy struct {
|
|||||||
SignInMessage string
|
SignInMessage string
|
||||||
HtpasswdFile *HtpasswdFile
|
HtpasswdFile *HtpasswdFile
|
||||||
DisplayHtpasswdForm bool
|
DisplayHtpasswdForm bool
|
||||||
serveMux http.Handler
|
serveMuxes map[string]http.Handler
|
||||||
SetXAuthRequest bool
|
SetXAuthRequest bool
|
||||||
PassBasicAuth bool
|
PassBasicAuth bool
|
||||||
SkipProviderButton 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
|
// NewOAuthProxy creates a new instance of OOuthProxy from the options provided
|
||||||
func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
||||||
serveMux := http.NewServeMux()
|
serveMuxes := map[string]http.Handler{}
|
||||||
var auth hmacauth.HmacAuth
|
var auth hmacauth.HmacAuth
|
||||||
if sigData := opts.signatureData; sigData != nil {
|
if sigData := opts.signatureData; sigData != nil {
|
||||||
auth = hmacauth.NewHmacAuth(sigData.hash, []byte(sigData.key),
|
auth = hmacauth.NewHmacAuth(sigData.hash, []byte(sigData.key),
|
||||||
SignatureHeader, SignatureHeaders)
|
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
|
path := u.Path
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
case httpScheme, httpsScheme:
|
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)
|
proxy := NewWebSocketOrRestReverseProxy(u, opts, auth)
|
||||||
serveMux.Handle(path, proxy)
|
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))
|
panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for _, u := range opts.CompiledRegex {
|
for _, u := range opts.CompiledRegex {
|
||||||
logger.Printf("compiled skip-auth-regex => %q", u)
|
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)
|
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)
|
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{
|
return &OAuthProxy{
|
||||||
@ -270,9 +287,9 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
|||||||
ProxyPrefix: opts.ProxyPrefix,
|
ProxyPrefix: opts.ProxyPrefix,
|
||||||
provider: opts.provider,
|
provider: opts.provider,
|
||||||
sessionStore: opts.sessionStore,
|
sessionStore: opts.sessionStore,
|
||||||
serveMux: serveMux,
|
serveMuxes: serveMuxes,
|
||||||
redirectURL: redirectURL,
|
redirectURL: redirectURL,
|
||||||
whitelistDomains: opts.WhitelistDomains,
|
whitelistDomains: whitelistDomains,
|
||||||
skipAuthRegex: opts.SkipAuthRegex,
|
skipAuthRegex: opts.SkipAuthRegex,
|
||||||
skipAuthPreflight: opts.SkipAuthPreflight,
|
skipAuthPreflight: opts.SkipAuthPreflight,
|
||||||
skipJwtBearerTokens: opts.SkipJwtBearerTokens,
|
skipJwtBearerTokens: opts.SkipJwtBearerTokens,
|
||||||
@ -481,6 +498,9 @@ func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
redirect = req.Form.Get("rd")
|
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) {
|
if !p.IsValidRedirect(redirect) {
|
||||||
redirect = req.URL.Path
|
redirect = req.URL.Path
|
||||||
if strings.HasPrefix(redirect, p.ProxyPrefix) {
|
if strings.HasPrefix(redirect, p.ProxyPrefix) {
|
||||||
@ -543,7 +563,7 @@ func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
case path == p.PingPath:
|
case path == p.PingPath:
|
||||||
p.PingPage(rw)
|
p.PingPage(rw)
|
||||||
case p.IsWhitelistedRequest(req):
|
case p.IsWhitelistedRequest(req):
|
||||||
p.serveMux.ServeHTTP(rw, req)
|
p.serveRequest(rw, req)
|
||||||
case path == p.SignInPath:
|
case path == p.SignInPath:
|
||||||
p.SignIn(rw, req)
|
p.SignIn(rw, req)
|
||||||
case path == p.SignOutPath:
|
case path == p.SignOutPath:
|
||||||
@ -687,6 +707,25 @@ func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request)
|
|||||||
rw.WriteHeader(http.StatusAccepted)
|
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
|
// Proxy proxies the user request if the user is authenticated else it prompts
|
||||||
// them to authenticate
|
// them to authenticate
|
||||||
func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
|
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:
|
case nil:
|
||||||
// we are authenticated
|
// we are authenticated
|
||||||
p.addHeadersForProxying(rw, req, session)
|
p.addHeadersForProxying(rw, req, session)
|
||||||
p.serveMux.ServeHTTP(rw, req)
|
p.serveRequest(rw, req)
|
||||||
|
|
||||||
case ErrNeedsLogin:
|
case ErrNeedsLogin:
|
||||||
// we need to send the user to a login screen
|
// 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
|
// internal values that are set after config validation
|
||||||
redirectURL *url.URL
|
redirectURL *url.URL
|
||||||
proxyURLs []*url.URL
|
proxyURLs map[string][]*url.URL
|
||||||
CompiledRegex []*regexp.Regexp
|
CompiledRegex []*regexp.Regexp
|
||||||
provider providers.Provider
|
provider providers.Provider
|
||||||
sessionStore sessionsapi.SessionStore
|
sessionStore sessionsapi.SessionStore
|
||||||
@ -284,7 +284,14 @@ func (o *Options) Validate() error {
|
|||||||
|
|
||||||
o.redirectURL, msgs = parseURL(o.RedirectURL, "redirect", msgs)
|
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 {
|
for _, u := range o.Upstreams {
|
||||||
|
subdomain := "*"
|
||||||
|
m := domainUpstreamRegex.FindStringSubmatch(u)
|
||||||
|
if m != nil {
|
||||||
|
subdomain, u = m[1], m[2]
|
||||||
|
}
|
||||||
upstreamURL, err := url.Parse(u)
|
upstreamURL, err := url.Parse(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msgs = append(msgs, fmt.Sprintf("error parsing upstream: %s", err))
|
msgs = append(msgs, fmt.Sprintf("error parsing upstream: %s", err))
|
||||||
@ -292,7 +299,7 @@ func (o *Options) Validate() error {
|
|||||||
if upstreamURL.Path == "" {
|
if upstreamURL.Path == "" {
|
||||||
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) {
|
func TestProxyURLs(t *testing.T) {
|
||||||
o := testOptions()
|
o := testOptions()
|
||||||
o.Upstreams = append(o.Upstreams, "http://127.0.0.1:8081")
|
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())
|
assert.Equal(t, nil, o.Validate())
|
||||||
expected := []*url.URL{
|
expected := map[string][]*url.URL{
|
||||||
|
"*": {
|
||||||
{Scheme: "http", Host: "127.0.0.1:8080", Path: "/"},
|
{Scheme: "http", Host: "127.0.0.1:8080", Path: "/"},
|
||||||
// note the '/' was added
|
// note the '/' was added
|
||||||
{Scheme: "http", Host: "127.0.0.1:8081", Path: "/"},
|
{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)
|
assert.Equal(t, expected, o.proxyURLs)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user