authorize: strip port from host header if necessary (#1175)

After #1153, envoy can handle routes for `example.com` and `example.com:443`.
Authorize service should be updated to handle this case, too.

Fixes #959
This commit is contained in:
Cuong Manh Le 2020-07-31 21:41:58 +07:00 committed by GitHub
parent bc61206b78
commit f7ebf54305
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 81 additions and 25 deletions

View file

@ -301,7 +301,7 @@ func getCheckRequestURL(req *envoy_service_auth_v2.CheckRequest) *url.URL {
Scheme: h.GetScheme(),
Host: h.GetHost(),
}
u.Host = urlutil.GetDomainsForURL(u)[0]
// envoy sends the query string as part of the path
path := h.GetPath()
if idx := strings.Index(path, "?"); idx != -1 {

View file

@ -267,6 +267,56 @@ func Test_handleForwardAuth(t *testing.T) {
}
}
func Test_getEvaluatorRequestWithPortInHostHeader(t *testing.T) {
a := new(Authorize)
encoder, _ := jws.NewHS256Signer([]byte{0, 0, 0, 0}, "")
a.currentEncoder.Store(encoder)
a.currentOptions.Store(&config.Options{
Policies: []config.Policy{{
Source: &config.StringURL{URL: &url.URL{Host: "example.com"}},
SubPolicies: []config.SubPolicy{{
Rego: []string{"allow = true"},
}},
}},
})
actual := a.getEvaluatorRequestFromCheckRequest(&envoy_service_auth_v2.CheckRequest{
Attributes: &envoy_service_auth_v2.AttributeContext{
Source: &envoy_service_auth_v2.AttributeContext_Peer{
Certificate: url.QueryEscape(certPEM),
},
Request: &envoy_service_auth_v2.AttributeContext_Request{
Http: &envoy_service_auth_v2.AttributeContext_HttpRequest{
Id: "id-1234",
Method: "GET",
Headers: map[string]string{
"accept": "text/html",
"x-forwarded-proto": "https",
},
Path: "/some/path?qs=1",
Host: "example.com:80",
Scheme: "http",
Body: "BODY",
},
},
},
}, nil)
expect := &evaluator.Request{
Session: evaluator.RequestSession{},
HTTP: evaluator.RequestHTTP{
Method: "GET",
URL: "https://example.com/some/path?qs=1",
Headers: map[string]string{
"Accept": "text/html",
"X-Forwarded-Proto": "https",
},
ClientCertificate: certPEM,
},
CustomPolicies: []string{"allow = true"},
}
assert.Equal(t, expect, actual)
}
func mustParseURL(str string) *url.URL {
u, err := url.Parse(str)
if err != nil {

View file

@ -23,6 +23,7 @@ import (
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
)
@ -398,28 +399,28 @@ func buildDownstreamTLSContext(options *config.Options, domain string) *envoy_ex
func getAllRouteableDomains(options *config.Options, addr string) []string {
lookup := map[string]struct{}{}
if config.IsAuthenticate(options.Services) && addr == options.Addr {
for _, h := range getDomainsForURL(options.GetAuthenticateURL()) {
for _, h := range urlutil.GetDomainsForURL(options.GetAuthenticateURL()) {
lookup[h] = struct{}{}
}
}
if config.IsAuthorize(options.Services) && addr == options.GRPCAddr {
for _, h := range getDomainsForURL(options.GetAuthorizeURL()) {
for _, h := range urlutil.GetDomainsForURL(options.GetAuthorizeURL()) {
lookup[h] = struct{}{}
}
}
if config.IsCache(options.Services) && addr == options.GRPCAddr {
for _, h := range getDomainsForURL(options.GetDataBrokerURL()) {
for _, h := range urlutil.GetDomainsForURL(options.GetDataBrokerURL()) {
lookup[h] = struct{}{}
}
}
if config.IsProxy(options.Services) && addr == options.Addr {
for _, policy := range options.Policies {
for _, h := range getDomainsForURL(policy.Source.URL) {
for _, h := range urlutil.GetDomainsForURL(policy.Source.URL) {
lookup[h] = struct{}{}
}
}
if options.ForwardAuthURL != nil {
for _, h := range getDomainsForURL(options.GetForwardAuthURL()) {
for _, h := range urlutil.GetDomainsForURL(options.GetForwardAuthURL()) {
lookup[h] = struct{}{}
}
}
@ -434,25 +435,6 @@ func getAllRouteableDomains(options *config.Options, addr string) []string {
return domains
}
func getDomainsForURL(u *url.URL) []string {
var defaultPort string
if u.Scheme == "http" {
defaultPort = "80"
} else {
defaultPort = "443"
}
// for hosts like 'example.com:1234' we only return one route
if _, p, err := net.SplitHostPort(u.Host); err == nil {
if p != defaultPort {
return []string{u.Host}
}
}
// for everything else we return two routes: 'example.com' and 'example.com:443'
return []string{u.Hostname(), net.JoinHostPort(u.Hostname(), defaultPort)}
}
func hostMatchesDomain(u *url.URL, host string) bool {
var defaultPort string
if u.Scheme == "http" {

View file

@ -3,6 +3,7 @@ package urlutil
import (
"fmt"
"net"
"net/http"
"net/url"
"strings"
@ -77,3 +78,26 @@ func GetAbsoluteURL(r *http.Request) *url.URL {
u.Host = r.Host
return u
}
// GetDomainsForURL returns the available domains for given url.
//
// For standard HTTP (80)/HTTPS (443) ports, it returns `example.com` and `example.com:<port>`.
// Otherwise, return the URL.Host value.
func GetDomainsForURL(u *url.URL) []string {
var defaultPort string
if u.Scheme == "http" {
defaultPort = "80"
} else {
defaultPort = "443"
}
// for hosts like 'example.com:1234' we only return one route
if _, p, err := net.SplitHostPort(u.Host); err == nil {
if p != defaultPort {
return []string{u.Host}
}
}
// for everything else we return two routes: 'example.com' and 'example.com:443'
return []string{u.Hostname(), net.JoinHostPort(u.Hostname(), defaultPort)}
}