remove forward auth (#3628)

This commit is contained in:
Caleb Doxsey 2022-11-23 15:59:28 -07:00 committed by GitHub
parent ba07afc245
commit fa26587f19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 302 additions and 5072 deletions

View file

@ -32,25 +32,24 @@ func (a *Authorize) handleResult(
in *envoy_service_auth_v3.CheckRequest,
request *evaluator.Request,
result *evaluator.Result,
isForwardAuthVerify bool,
) (*envoy_service_auth_v3.CheckResponse, error) {
// when the user is unauthenticated it means they haven't
// logged in yet, so redirect to authenticate
if result.Allow.Reasons.Has(criteria.ReasonUserUnauthenticated) ||
result.Deny.Reasons.Has(criteria.ReasonUserUnauthenticated) {
return a.requireLoginResponse(ctx, in, request, isForwardAuthVerify)
return a.requireLoginResponse(ctx, in, request)
}
// when the user's device is unauthenticated it means they haven't
// registered a webauthn device yet, so redirect to the webauthn flow
if result.Allow.Reasons.Has(criteria.ReasonDeviceUnauthenticated) ||
result.Deny.Reasons.Has(criteria.ReasonDeviceUnauthenticated) {
return a.requireWebAuthnResponse(ctx, in, request, result, isForwardAuthVerify)
return a.requireWebAuthnResponse(ctx, in, request, result)
}
// if there's a deny, the result is denied using the deny reasons.
if result.Deny.Value {
return a.handleResultDenied(ctx, in, request, result, isForwardAuthVerify, result.Deny.Reasons)
return a.handleResultDenied(ctx, in, request, result, result.Deny.Reasons)
}
// if there's an allow, the result is allowed.
@ -59,7 +58,7 @@ func (a *Authorize) handleResult(
}
// otherwise, the result is denied using the allow reasons.
return a.handleResultDenied(ctx, in, request, result, isForwardAuthVerify, result.Allow.Reasons)
return a.handleResultDenied(ctx, in, request, result, result.Allow.Reasons)
}
func (a *Authorize) handleResultAllowed(
@ -75,7 +74,6 @@ func (a *Authorize) handleResultDenied(
in *envoy_service_auth_v3.CheckRequest,
request *evaluator.Request,
result *evaluator.Result,
isForwardAuthVerify bool,
reasons criteria.Reasons,
) (*envoy_service_auth_v3.CheckResponse, error) {
denyStatusCode := int32(http.StatusForbidden)
@ -83,7 +81,7 @@ func (a *Authorize) handleResultDenied(
switch {
case reasons.Has(criteria.ReasonDeviceUnauthenticated):
return a.requireWebAuthnResponse(ctx, in, request, result, isForwardAuthVerify)
return a.requireWebAuthnResponse(ctx, in, request, result)
case reasons.Has(criteria.ReasonDeviceUnauthorized):
denyStatusCode = httputil.StatusDeviceUnauthorized
denyStatusText = httputil.DetailsText(httputil.StatusDeviceUnauthorized)
@ -122,42 +120,36 @@ func (a *Authorize) deniedResponse(
in *envoy_service_auth_v3.CheckRequest,
code int32, reason string, headers map[string]string,
) (*envoy_service_auth_v3.CheckResponse, error) {
respBody := []byte(reason)
respHeader := []*envoy_config_core_v3.HeaderValueOption{}
forwardAuthURL, _ := a.currentOptions.Load().GetForwardAuthURL()
if forwardAuthURL == nil {
// create a http response writer recorder
w := httptest.NewRecorder()
r := getHTTPRequestFromCheckRequest(in)
// create a http response writer recorder
w := httptest.NewRecorder()
r := getHTTPRequestFromCheckRequest(in)
// build the user info / debug endpoint
debugEndpoint, _ := a.userInfoEndpointURL(in) // if there's an error, we just wont display it
// build the user info / debug endpoint
debugEndpoint, _ := a.userInfoEndpointURL(in) // if there's an error, we just wont display it
// run the request through our go error handler
httpErr := httputil.HTTPError{
Status: int(code),
Err: errors.New(reason),
DebugURL: debugEndpoint,
RequestID: requestid.FromContext(ctx),
BrandingOptions: a.currentOptions.Load().BrandingOptions,
}
httpErr.ErrorResponse(ctx, w, r)
// transpose the go http response writer into a envoy response
resp := w.Result()
defer resp.Body.Close()
var err error
respBody, err = io.ReadAll(resp.Body)
if err != nil {
log.Error(ctx).Err(err).Msg("error executing error template")
return nil, err
}
// convert go headers to envoy headers
respHeader = append(respHeader, toEnvoyHeaders(resp.Header)...)
} else {
respHeader = append(respHeader, mkHeader("Content-Type", "text/plain", false))
// run the request through our go error handler
httpErr := httputil.HTTPError{
Status: int(code),
Err: errors.New(reason),
DebugURL: debugEndpoint,
RequestID: requestid.FromContext(ctx),
BrandingOptions: a.currentOptions.Load().BrandingOptions,
}
httpErr.ErrorResponse(ctx, w, r)
// transpose the go http response writer into a envoy response
resp := w.Result()
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
log.Error(ctx).Err(err).Msg("error executing error template")
return nil, err
}
// convert go headers to envoy headers
respHeader = append(respHeader, toEnvoyHeaders(resp.Header)...)
// add any additional headers
for k, v := range headers {
@ -182,7 +174,6 @@ func (a *Authorize) requireLoginResponse(
ctx context.Context,
in *envoy_service_auth_v3.CheckRequest,
request *evaluator.Request,
isForwardAuthVerify bool,
) (*envoy_service_auth_v3.CheckResponse, error) {
opts := a.currentOptions.Load()
state := a.state.Load()
@ -191,7 +182,7 @@ func (a *Authorize) requireLoginResponse(
return nil, err
}
if !a.shouldRedirect(in) || isForwardAuthVerify {
if !a.shouldRedirect(in) {
return a.deniedResponse(ctx, in, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized), nil)
}
@ -223,7 +214,6 @@ func (a *Authorize) requireWebAuthnResponse(
in *envoy_service_auth_v3.CheckRequest,
request *evaluator.Request,
result *evaluator.Result,
isForwardAuthVerify bool,
) (*envoy_service_auth_v3.CheckResponse, error) {
opts := a.currentOptions.Load()
state := a.state.Load()
@ -232,7 +222,7 @@ func (a *Authorize) requireWebAuthnResponse(
return nil, err
}
if !a.shouldRedirect(in) || isForwardAuthVerify {
if !a.shouldRedirect(in) {
return a.deniedResponse(ctx, in, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized), nil)
}

View file

@ -37,8 +37,7 @@ func TestAuthorize_handleResult(t *testing.T) {
&evaluator.Request{},
&evaluator.Result{
Allow: evaluator.NewRuleResult(false, criteria.ReasonUserUnauthenticated),
},
false)
})
assert.NoError(t, err)
assert.Equal(t, 302, int(res.GetDeniedResponse().GetStatus().GetCode()))
@ -47,8 +46,7 @@ func TestAuthorize_handleResult(t *testing.T) {
&evaluator.Request{},
&evaluator.Result{
Deny: evaluator.NewRuleResult(false, criteria.ReasonUserUnauthenticated),
},
false)
})
assert.NoError(t, err)
assert.Equal(t, 302, int(res.GetDeniedResponse().GetStatus().GetCode()))
})
@ -191,8 +189,7 @@ func TestRequireLogin(t *testing.T) {
t.Run("accept empty", func(t *testing.T) {
res, err := a.requireLoginResponse(context.Background(),
&envoy_service_auth_v3.CheckRequest{},
&evaluator.Request{},
false)
&evaluator.Request{})
require.NoError(t, err)
assert.Equal(t, http.StatusFound, int(res.GetDeniedResponse().GetStatus().GetCode()))
})
@ -209,8 +206,7 @@ func TestRequireLogin(t *testing.T) {
},
},
},
&evaluator.Request{},
false)
&evaluator.Request{})
require.NoError(t, err)
assert.Equal(t, http.StatusFound, int(res.GetDeniedResponse().GetStatus().GetCode()))
})
@ -227,8 +223,7 @@ func TestRequireLogin(t *testing.T) {
},
},
},
&evaluator.Request{},
false)
&evaluator.Request{})
require.NoError(t, err)
assert.Equal(t, http.StatusUnauthorized, int(res.GetDeniedResponse().GetStatus().GetCode()))
})

View file

@ -43,18 +43,6 @@ func (a *Authorize) Check(ctx context.Context, in *envoy_service_auth_v3.CheckRe
hreq := getHTTPRequestFromCheckRequest(in)
ctx = requestid.WithValue(ctx, requestid.FromHTTPHeader(hreq.Header))
isForwardAuth := a.isForwardAuth(in)
if isForwardAuth {
// update the incoming http request's uri to match the forwarded URI
fwdAuthURI := urlutil.GetForwardAuthURL(hreq)
in.Attributes.Request.Http.Scheme = fwdAuthURI.Scheme
in.Attributes.Request.Http.Host = fwdAuthURI.Host
in.Attributes.Request.Http.Path = fwdAuthURI.EscapedPath()
if fwdAuthURI.RawQuery != "" {
in.Attributes.Request.Http.Path += "?" + fwdAuthURI.RawQuery
}
}
sessionState, _ := state.sessionStore.LoadSessionState(hreq)
var s sessionOrServiceAccount
@ -93,22 +81,7 @@ func (a *Authorize) Check(ctx context.Context, in *envoy_service_auth_v3.CheckRe
ctx = contextutil.WithPolicyEvaluationTraces(ctx, res.Traces)
}
isForwardAuthVerify := isForwardAuth && hreq.URL.Path == "/verify"
return a.handleResult(ctx, in, req, res, isForwardAuthVerify)
}
// isForwardAuth returns if the current request is a forward auth route.
func (a *Authorize) isForwardAuth(req *envoy_service_auth_v3.CheckRequest) bool {
opts := a.currentOptions.Load()
forwardAuthURL, err := opts.GetForwardAuthURL()
if err != nil || forwardAuthURL == nil {
return false
}
checkURL := getCheckRequestURL(req)
return urlutil.StripPort(checkURL.Host) == urlutil.StripPort(forwardAuthURL.Host)
return a.handleResult(ctx, in, req, res)
}
func (a *Authorize) getEvaluatorRequestFromCheckRequest(

View file

@ -6,17 +6,13 @@ import (
"testing"
envoy_service_auth_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/genproto/googleapis/rpc/status"
"google.golang.org/grpc"
"github.com/pomerium/pomerium/authorize/evaluator"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/atomicutil"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/sessions"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
)
@ -102,161 +98,6 @@ func Test_getEvaluatorRequest(t *testing.T) {
assert.Equal(t, expect, actual)
}
func Test_handleForwardAuth(t *testing.T) {
tests := []struct {
name string
checkReq *envoy_service_auth_v3.CheckRequest
forwardAuthURL string
want bool
}{
{
name: "enabled",
checkReq: &envoy_service_auth_v3.CheckRequest{
Attributes: &envoy_service_auth_v3.AttributeContext{
Source: &envoy_service_auth_v3.AttributeContext_Peer{
Certificate: url.QueryEscape(certPEM),
},
Request: &envoy_service_auth_v3.AttributeContext_Request{
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
Method: "GET",
Path: "/verify?uri=" + url.QueryEscape("https://example.com/some/path?qs=1"),
Host: "forward-auth.example.com",
Scheme: "https",
},
},
},
},
forwardAuthURL: "https://forward-auth.example.com",
want: true,
},
{
name: "disabled",
checkReq: nil,
forwardAuthURL: "",
want: false,
},
{
name: "honor x-forwarded-uri set",
checkReq: &envoy_service_auth_v3.CheckRequest{
Attributes: &envoy_service_auth_v3.AttributeContext{
Source: &envoy_service_auth_v3.AttributeContext_Peer{
Certificate: url.QueryEscape(certPEM),
},
Request: &envoy_service_auth_v3.AttributeContext_Request{
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
Method: "GET",
Path: "/",
Host: "forward-auth.example.com",
Scheme: "https",
Headers: map[string]string{
httputil.HeaderForwardedURI: "/foo/bar",
httputil.HeaderForwardedProto: "https",
httputil.HeaderForwardedHost: "example.com",
},
},
},
},
},
forwardAuthURL: "https://forward-auth.example.com",
want: true,
},
{
name: "request with invalid forward auth url",
checkReq: &envoy_service_auth_v3.CheckRequest{
Attributes: &envoy_service_auth_v3.AttributeContext{
Source: &envoy_service_auth_v3.AttributeContext_Peer{
Certificate: url.QueryEscape(certPEM),
},
Request: &envoy_service_auth_v3.AttributeContext_Request{
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
Method: "GET",
Path: "/verify?uri=" + url.QueryEscape("https://example.com?q=foo"),
Host: "fake-forward-auth.example.com",
Scheme: "https",
},
},
},
},
forwardAuthURL: "https://forward-auth.example.com",
want: false,
},
{
name: "request with invalid path",
checkReq: &envoy_service_auth_v3.CheckRequest{
Attributes: &envoy_service_auth_v3.AttributeContext{
Source: &envoy_service_auth_v3.AttributeContext_Peer{
Certificate: url.QueryEscape(certPEM),
},
Request: &envoy_service_auth_v3.AttributeContext_Request{
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
Method: "GET",
Path: "/foo?uri=" + url.QueryEscape("https://example.com?q=foo"),
Host: "forward-auth.example.com",
Scheme: "https",
},
},
},
},
forwardAuthURL: "https://forward-auth.example.com",
want: true,
},
{
name: "request with empty uri",
checkReq: &envoy_service_auth_v3.CheckRequest{
Attributes: &envoy_service_auth_v3.AttributeContext{
Source: &envoy_service_auth_v3.AttributeContext_Peer{
Certificate: url.QueryEscape(certPEM),
},
Request: &envoy_service_auth_v3.AttributeContext_Request{
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
Method: "GET",
Path: "/verify?uri=",
Host: "forward-auth.example.com",
Scheme: "https",
},
},
},
},
forwardAuthURL: "https://forward-auth.example.com",
want: true,
},
{
name: "request with invalid uri",
checkReq: &envoy_service_auth_v3.CheckRequest{
Attributes: &envoy_service_auth_v3.AttributeContext{
Source: &envoy_service_auth_v3.AttributeContext_Peer{
Certificate: url.QueryEscape(certPEM),
},
Request: &envoy_service_auth_v3.AttributeContext_Request{
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
Method: "GET",
Path: "/verify?uri= http://example.com/foo",
Host: "forward-auth.example.com",
Scheme: "https",
},
},
},
},
forwardAuthURL: "https://forward-auth.example.com",
want: true,
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
a := &Authorize{currentOptions: config.NewAtomicOptions(), state: atomicutil.NewValue(new(authorizeState))}
a.currentOptions.Store(&config.Options{ForwardAuthURLString: tc.forwardAuthURL})
got := a.isForwardAuth(tc.checkReq)
if diff := cmp.Diff(got, tc.want); diff != "" {
t.Errorf("Authorize.Check() = %s", diff)
}
})
}
}
func Test_getEvaluatorRequestWithPortInHostHeader(t *testing.T) {
a := &Authorize{currentOptions: config.NewAtomicOptions(), state: atomicutil.NewValue(new(authorizeState))}
a.currentOptions.Store(&config.Options{
@ -322,100 +163,6 @@ func (m mockDataBrokerServiceClient) Put(ctx context.Context, in *databroker.Put
return m.put(ctx, in, opts...)
}
func TestAuthorize_Check(t *testing.T) {
opt := config.NewDefaultOptions()
opt.AuthenticateURLString = "https://authenticate.example.com"
opt.DataBrokerURLString = "https://databroker.example.com"
opt.SharedKey = "E8wWIMnihUx+AUfRegAQDNs8eRb3UrB5G3zlJW9XJDM="
a, err := New(&config.Config{Options: opt})
if err != nil {
t.Fatal(err)
}
a.currentOptions.Store(&config.Options{ForwardAuthURLString: "https://forward-auth.example.com"})
cmpOpts := []cmp.Option{
cmpopts.IgnoreUnexported(envoy_service_auth_v3.CheckResponse{}),
cmpopts.IgnoreUnexported(status.Status{}),
cmpopts.IgnoreTypes(envoy_service_auth_v3.DeniedHttpResponse{}),
}
tests := []struct {
name string
in *envoy_service_auth_v3.CheckRequest
want *envoy_service_auth_v3.CheckResponse
wantErr bool
}{
{
"basic deny",
&envoy_service_auth_v3.CheckRequest{
Attributes: &envoy_service_auth_v3.AttributeContext{
Source: &envoy_service_auth_v3.AttributeContext_Peer{
Certificate: url.QueryEscape(certPEM),
},
Request: &envoy_service_auth_v3.AttributeContext_Request{
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
Id: "id-1234",
Method: "GET",
Headers: map[string]string{
"accept": "application/json",
"x-forwarded-proto": "https",
},
Path: "/some/path?qs=1",
Host: "example.com",
Scheme: "http",
Body: "BODY",
},
},
},
},
&envoy_service_auth_v3.CheckResponse{
Status: &status.Status{Code: 7, Message: "Access Denied"},
HttpResponse: &envoy_service_auth_v3.CheckResponse_DeniedResponse{
DeniedResponse: &envoy_service_auth_v3.DeniedHttpResponse{},
},
},
false,
},
{
"basic forward-auth deny",
&envoy_service_auth_v3.CheckRequest{
Attributes: &envoy_service_auth_v3.AttributeContext{
Source: &envoy_service_auth_v3.AttributeContext_Peer{
Certificate: url.QueryEscape(certPEM),
},
Request: &envoy_service_auth_v3.AttributeContext_Request{
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
Method: "GET",
Path: "/verify?uri=" + url.QueryEscape("https://example.com/some/path?qs=1"),
Host: "forward-auth.example.com",
Scheme: "https",
},
},
},
},
&envoy_service_auth_v3.CheckResponse{
Status: &status.Status{Code: 7, Message: "Access Denied"},
HttpResponse: &envoy_service_auth_v3.CheckResponse_DeniedResponse{
DeniedResponse: &envoy_service_auth_v3.DeniedHttpResponse{},
},
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := a.Check(context.TODO(), tt.in)
if (err != nil) != tt.wantErr {
t.Errorf("Authorize.Check() error = %v, wantErr %v", err, tt.wantErr)
return
}
if diff := cmp.Diff(got, tt.want, cmpOpts...); diff != "" {
t.Errorf("NewStore() = %s", diff)
}
})
}
}
func mustParseURL(rawURL string) url.URL {
u, err := url.Parse(rawURL)
if err != nil {