mirror of
https://github.com/pomerium/pomerium.git
synced 2025-06-09 06:12:42 +02:00
authorize: honor X-Forwarded-Uri in forward auth mode
Some ingress like traefik set the X-Forwarded-Uri header instead of passing the actual path in request, we should hornor and use that header in forward auth mode. While at it, refactoring the handleForwardAuth to return earlier instead of nested condition, and add more tests to cover all cases.
This commit is contained in:
parent
e482fef247
commit
48639a48fb
2 changed files with 188 additions and 49 deletions
|
@ -177,25 +177,33 @@ func (a *Authorize) handleForwardAuth(req *envoy_service_auth_v2.CheckRequest) b
|
||||||
}
|
}
|
||||||
|
|
||||||
checkURL := getCheckRequestURL(req)
|
checkURL := getCheckRequestURL(req)
|
||||||
if urlutil.StripPort(checkURL.Host) == urlutil.StripPort(opts.GetForwardAuthURL().Host) {
|
if urlutil.StripPort(checkURL.Host) != urlutil.StripPort(opts.GetForwardAuthURL().Host) {
|
||||||
if (checkURL.Path == "/" || checkURL.Path == "/verify") && checkURL.Query().Get("uri") != "" {
|
return false
|
||||||
verifyURL, err := url.Parse(checkURL.Query().Get("uri"))
|
|
||||||
if err != nil {
|
|
||||||
log.Warn().Str("uri", checkURL.Query().Get("uri")).Err(err).Msg("failed to parse uri for forward authentication")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
req.Attributes.Request.Http.Scheme = verifyURL.Scheme
|
|
||||||
req.Attributes.Request.Http.Host = verifyURL.Host
|
|
||||||
req.Attributes.Request.Http.Path = verifyURL.Path
|
|
||||||
// envoy sends the query string as part of the path
|
|
||||||
if verifyURL.RawQuery != "" {
|
|
||||||
req.Attributes.Request.Http.Path += "?" + verifyURL.RawQuery
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
uriQuery := checkURL.Query().Get("uri")
|
||||||
|
if (checkURL.Path != "/" && checkURL.Path != "/verify") || uriQuery == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
verifyURL, err := url.Parse(uriQuery)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Str("uri", checkURL.Query().Get("uri")).Err(err).Msg("failed to parse uri for forward authentication")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Attributes.Request.Http.Scheme = verifyURL.Scheme
|
||||||
|
req.Attributes.Request.Http.Host = verifyURL.Host
|
||||||
|
req.Attributes.Request.Http.Path = verifyURL.Path
|
||||||
|
if headers := req.GetAttributes().GetRequest().GetHttp().GetHeaders(); headers != nil {
|
||||||
|
if xfu := headers[http.CanonicalHeaderKey("x-forwarded-uri")]; xfu != "" {
|
||||||
|
req.Attributes.Request.Http.Path += xfu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// envoy sends the query string as part of the path
|
||||||
|
if verifyURL.RawQuery != "" {
|
||||||
|
req.Attributes.Request.Http.Path += "?" + verifyURL.RawQuery
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Authorize) getEvaluatorRequestFromCheckRequest(in *envoy_service_auth_v2.CheckRequest, sessionState *sessions.State) *evaluator.Request {
|
func (a *Authorize) getEvaluatorRequestFromCheckRequest(in *envoy_service_auth_v2.CheckRequest, sessionState *sessions.State) *evaluator.Request {
|
||||||
|
|
|
@ -78,44 +78,175 @@ func Test_getEvaluatorRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_handleForwardAuth(t *testing.T) {
|
func Test_handleForwardAuth(t *testing.T) {
|
||||||
checkReq := &envoy_service_auth_v2.CheckRequest{
|
tests := []struct {
|
||||||
Attributes: &envoy_service_auth_v2.AttributeContext{
|
name string
|
||||||
Source: &envoy_service_auth_v2.AttributeContext_Peer{
|
checkReq *envoy_service_auth_v2.CheckRequest
|
||||||
Certificate: url.QueryEscape(certPEM),
|
attrCtxHTTPReq *envoy_service_auth_v2.AttributeContext_HttpRequest
|
||||||
},
|
forwardAuthURL string
|
||||||
Request: &envoy_service_auth_v2.AttributeContext_Request{
|
isForwardAuth bool
|
||||||
Http: &envoy_service_auth_v2.AttributeContext_HttpRequest{
|
}{
|
||||||
Method: "GET",
|
{
|
||||||
Path: "/verify?uri=" + url.QueryEscape("https://example.com/some/path?qs=1"),
|
name: "enabled",
|
||||||
Host: "forward-auth.example.com",
|
checkReq: &envoy_service_auth_v2.CheckRequest{
|
||||||
Scheme: "https",
|
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{
|
||||||
|
Method: "GET",
|
||||||
|
Path: "/verify?uri=" + url.QueryEscape("https://example.com/some/path?qs=1"),
|
||||||
|
Host: "forward-auth.example.com",
|
||||||
|
Scheme: "https",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
attrCtxHTTPReq: &envoy_service_auth_v2.AttributeContext_HttpRequest{
|
||||||
|
Method: "GET",
|
||||||
|
Path: "/some/path?qs=1",
|
||||||
|
Host: "example.com",
|
||||||
|
Scheme: "https",
|
||||||
|
},
|
||||||
|
forwardAuthURL: "https://forward-auth.example.com",
|
||||||
|
isForwardAuth: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disabled",
|
||||||
|
checkReq: nil,
|
||||||
|
attrCtxHTTPReq: nil,
|
||||||
|
forwardAuthURL: "",
|
||||||
|
isForwardAuth: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "honor x-forwarded-uri set",
|
||||||
|
checkReq: &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{
|
||||||
|
Method: "GET",
|
||||||
|
Path: "/verify?uri=" + url.QueryEscape("https://example.com?q=foo"),
|
||||||
|
Host: "forward-auth.example.com",
|
||||||
|
Scheme: "https",
|
||||||
|
Headers: map[string]string{"X-Forwarded-Uri": "/foo/bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
attrCtxHTTPReq: &envoy_service_auth_v2.AttributeContext_HttpRequest{
|
||||||
|
Method: "GET",
|
||||||
|
Path: "/foo/bar?q=foo",
|
||||||
|
Host: "example.com",
|
||||||
|
Scheme: "https",
|
||||||
|
Headers: map[string]string{"X-Forwarded-Uri": "/foo/bar"},
|
||||||
|
},
|
||||||
|
forwardAuthURL: "https://forward-auth.example.com",
|
||||||
|
isForwardAuth: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "request with invalid forward auth url",
|
||||||
|
checkReq: &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{
|
||||||
|
Method: "GET",
|
||||||
|
Path: "/verify?uri=" + url.QueryEscape("https://example.com?q=foo"),
|
||||||
|
Host: "fake-forward-auth.example.com",
|
||||||
|
Scheme: "https",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
attrCtxHTTPReq: nil,
|
||||||
|
forwardAuthURL: "https://forward-auth.example.com",
|
||||||
|
isForwardAuth: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "request with invalid path",
|
||||||
|
checkReq: &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{
|
||||||
|
Method: "GET",
|
||||||
|
Path: "/foo?uri=" + url.QueryEscape("https://example.com?q=foo"),
|
||||||
|
Host: "forward-auth.example.com",
|
||||||
|
Scheme: "https",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
attrCtxHTTPReq: nil,
|
||||||
|
forwardAuthURL: "https://forward-auth.example.com",
|
||||||
|
isForwardAuth: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "request with empty uri",
|
||||||
|
checkReq: &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{
|
||||||
|
Method: "GET",
|
||||||
|
Path: "/verify?uri=",
|
||||||
|
Host: "forward-auth.example.com",
|
||||||
|
Scheme: "https",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
attrCtxHTTPReq: nil,
|
||||||
|
forwardAuthURL: "https://forward-auth.example.com",
|
||||||
|
isForwardAuth: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "request with invalid uri",
|
||||||
|
checkReq: &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{
|
||||||
|
Method: "GET",
|
||||||
|
Path: "/verify?uri= http://example.com/foo",
|
||||||
|
Host: "forward-auth.example.com",
|
||||||
|
Scheme: "https",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
attrCtxHTTPReq: nil,
|
||||||
|
forwardAuthURL: "https://forward-auth.example.com",
|
||||||
|
isForwardAuth: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("enabled", func(t *testing.T) {
|
for _, tc := range tests {
|
||||||
a := new(Authorize)
|
tc := tc
|
||||||
a.currentOptions.Store(config.Options{
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
ForwardAuthURL: mustParseURL("https://forward-auth.example.com"),
|
a := new(Authorize)
|
||||||
|
fau := new(url.URL)
|
||||||
|
if tc.forwardAuthURL != "" {
|
||||||
|
fau = mustParseURL(tc.forwardAuthURL)
|
||||||
|
}
|
||||||
|
a.currentOptions.Store(config.Options{ForwardAuthURL: fau})
|
||||||
|
assert.Equal(t, tc.isForwardAuth, a.handleForwardAuth(tc.checkReq))
|
||||||
|
if tc.attrCtxHTTPReq != nil {
|
||||||
|
assert.Equal(t, tc.attrCtxHTTPReq, tc.checkReq.Attributes.Request.Http)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
isForwardAuth := a.handleForwardAuth(checkReq)
|
}
|
||||||
assert.True(t, isForwardAuth)
|
|
||||||
assert.Equal(t, &envoy_service_auth_v2.AttributeContext_HttpRequest{
|
|
||||||
Method: "GET",
|
|
||||||
Path: "/some/path?qs=1",
|
|
||||||
Host: "example.com",
|
|
||||||
Scheme: "https",
|
|
||||||
}, checkReq.Attributes.Request.Http)
|
|
||||||
})
|
|
||||||
t.Run("disabled", func(t *testing.T) {
|
|
||||||
a := new(Authorize)
|
|
||||||
a.currentOptions.Store(config.Options{
|
|
||||||
ForwardAuthURL: nil,
|
|
||||||
})
|
|
||||||
isForwardAuth := a.handleForwardAuth(checkReq)
|
|
||||||
assert.False(t, isForwardAuth)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustParseURL(str string) *url.URL {
|
func mustParseURL(str string) *url.URL {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue