mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-29 10:26:29 +02:00
config: add prefix, path and regex options
proxy: support prefix, path and regex options
This commit is contained in:
parent
15972b9956
commit
7027f458dd
3 changed files with 106 additions and 2 deletions
|
@ -24,6 +24,11 @@ type Policy struct {
|
||||||
Source *HostnameURL `yaml:",omitempty" json:"source,omitempty"`
|
Source *HostnameURL `yaml:",omitempty" json:"source,omitempty"`
|
||||||
Destination *url.URL `yaml:",omitempty" json:"destination,omitempty"`
|
Destination *url.URL `yaml:",omitempty" json:"destination,omitempty"`
|
||||||
|
|
||||||
|
// Additional route matching options
|
||||||
|
Prefix string `mapstructure:"prefix" yaml:"prefix,omitempty" json:"prefix,omitempty"`
|
||||||
|
Path string `mapstructure:"path" yaml:"path,omitempty" json:"path,omitempty"`
|
||||||
|
Regex string `mapstructure:"regex" yaml:"regex,omitempty" json:"regex,omitempty"`
|
||||||
|
|
||||||
// Allow unauthenticated HTTP OPTIONS requests as per the CORS spec
|
// Allow unauthenticated HTTP OPTIONS requests as per the CORS spec
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests
|
||||||
CORSAllowPreflight bool `mapstructure:"cors_allow_preflight" yaml:"cors_allow_preflight,omitempty"`
|
CORSAllowPreflight bool `mapstructure:"cors_allow_preflight" yaml:"cors_allow_preflight,omitempty"`
|
||||||
|
|
|
@ -14,6 +14,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
stdhttputil "net/http/httputil"
|
stdhttputil "net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
@ -230,8 +232,8 @@ func (p *Proxy) reverseProxyHandler(r *mux.Router, policy config.Policy) *mux.Ro
|
||||||
|
|
||||||
// 4. Override any custom transport settings (e.g. TLS settings, etc)
|
// 4. Override any custom transport settings (e.g. TLS settings, etc)
|
||||||
proxy.Transport = p.roundTripperFromPolicy(&policy)
|
proxy.Transport = p.roundTripperFromPolicy(&policy)
|
||||||
// 5. Create a sub-router for a given route's hostname (`httpbin.corp.example.com`)
|
// 5. Create a sub-router with a matcher derived from the policy (host, path, etc...)
|
||||||
rp := r.Host(policy.Source.Host).Subrouter()
|
rp := r.MatcherFunc(routeMatcherFuncFromPolicy(policy)).Subrouter()
|
||||||
rp.PathPrefix("/").Handler(proxy)
|
rp.PathPrefix("/").Handler(proxy)
|
||||||
|
|
||||||
// Optional: If websockets are enabled, do not set a handler request timeout
|
// Optional: If websockets are enabled, do not set a handler request timeout
|
||||||
|
@ -323,3 +325,44 @@ func (p *Proxy) roundTripperFromPolicy(policy *config.Policy) http.RoundTripper
|
||||||
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
p.Handler.ServeHTTP(w, r)
|
p.Handler.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// routeMatcherFuncFromPolicy returns a mux matcher function which compares an http request with a policy.
|
||||||
|
//
|
||||||
|
// Routes can be filtered by the `source`, `prefix`, `path` and `regex` fields in the policy config.
|
||||||
|
func routeMatcherFuncFromPolicy(policy config.Policy) mux.MatcherFunc {
|
||||||
|
// match by source
|
||||||
|
sourceMatches := func(r *http.Request) bool {
|
||||||
|
return r.Host == policy.Source.Host &&
|
||||||
|
strings.HasPrefix(r.URL.Path, policy.Source.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// match by prefix
|
||||||
|
prefixMatches := func(r *http.Request) bool {
|
||||||
|
return policy.Prefix == "" ||
|
||||||
|
strings.HasPrefix(r.URL.Path, policy.Prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// match by path
|
||||||
|
pathMatches := func(r *http.Request) bool {
|
||||||
|
return policy.Path == "" ||
|
||||||
|
r.URL.Path == policy.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
// match by path regex
|
||||||
|
var regexMatches func(*http.Request) bool
|
||||||
|
if policy.Regex == "" {
|
||||||
|
regexMatches = func(r *http.Request) bool { return true }
|
||||||
|
} else if re, err := regexp.Compile(policy.Regex); err == nil {
|
||||||
|
regexMatches = func(r *http.Request) bool {
|
||||||
|
return re.MatchString(r.URL.Path)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Error().Err(err).Str("regex", policy.Regex).Msg("proxy: invalid regex in policy, ignoring route")
|
||||||
|
regexMatches = func(r *http.Request) bool { return false }
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(r *http.Request, rm *mux.RouteMatch) bool {
|
||||||
|
return sourceMatches(r) && prefixMatches(r) && pathMatches(r) && regexMatches(r)
|
||||||
|
}
|
||||||
|
}
|
|
@ -276,3 +276,59 @@ func TestNewReverseProxy(t *testing.T) {
|
||||||
t.Errorf("got body %q; expected %q", g, e)
|
t.Errorf("got body %q; expected %q", g, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteMatcherFuncFromPolicy(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
source, prefix, path, regex string
|
||||||
|
incomingURL string
|
||||||
|
expect bool
|
||||||
|
msg string
|
||||||
|
}{
|
||||||
|
// host in source
|
||||||
|
{"https://www.example.com", "", "", "",
|
||||||
|
"https://www.example.com", true,
|
||||||
|
"should match when host is the same as source host"},
|
||||||
|
{"https://www.example.com", "", "", "",
|
||||||
|
"https://www.google.com", false,
|
||||||
|
"should not match when host is different from source host"},
|
||||||
|
|
||||||
|
// path prefix in source
|
||||||
|
{"https://www.example.com/admin", "", "", "",
|
||||||
|
"https://www.example.com/admin/someaction", true,
|
||||||
|
"should match when path begins with source path"},
|
||||||
|
{"https://www.example.com/admin", "", "", "",
|
||||||
|
"https://www.example.com/notadmin", false,
|
||||||
|
"should not match when path does not begin with source path"},
|
||||||
|
|
||||||
|
// path prefix
|
||||||
|
{"https://www.example.com", "/admin", "", "",
|
||||||
|
"https://www.example.com/admin/someaction", true,
|
||||||
|
"should match when path begins with prefix"},
|
||||||
|
{"https://www.example.com", "/admin", "", "",
|
||||||
|
"https://www.example.com/notadmin", false,
|
||||||
|
"should not match when path does not begin with prefix"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
srcURL, err := url.Parse(tt.source)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
src := &config.HostnameURL{URL: srcURL}
|
||||||
|
matcher := routeMatcherFuncFromPolicy(config.Policy{
|
||||||
|
Source: src,
|
||||||
|
Prefix: tt.prefix,
|
||||||
|
Path: tt.path,
|
||||||
|
Regex: tt.regex,
|
||||||
|
})
|
||||||
|
req, err := http.NewRequest("GET", tt.incomingURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
actual := matcher(req, nil)
|
||||||
|
if actual != tt.expect {
|
||||||
|
t.Errorf("%s (source=%s prefix=%s path=%s regex=%s incoming-url=%s)",
|
||||||
|
tt.msg, tt.source, tt.prefix, tt.path, tt.regex, tt.incomingURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue