authorize: allow access to /.pomerium/webauthn when policy denies access

This commit is contained in:
Caleb Doxsey 2023-02-24 15:58:48 -07:00
parent 88915a79c1
commit 2d194e728b
6 changed files with 55 additions and 11 deletions

View file

@ -226,14 +226,20 @@ func (a *Authorize) requireWebAuthnResponse(
opts := a.currentOptions.Load() opts := a.currentOptions.Load()
state := a.state.Load() state := a.state.Load()
if !a.shouldRedirect(in) {
return a.deniedResponse(ctx, in, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized), nil)
}
// always assume https scheme // always assume https scheme
checkRequestURL := getCheckRequestURL(in) checkRequestURL := getCheckRequestURL(in)
checkRequestURL.Scheme = "https" checkRequestURL.Scheme = "https"
// If we're already on a webauthn route, return OK.
// https://github.com/pomerium/pomerium-console/issues/3210
if checkRequestURL.Path == urlutil.WebAuthnURLPath || checkRequestURL.Path == urlutil.DeviceEnrolledPath {
return a.okResponse(result.Headers), nil
}
if !a.shouldRedirect(in) {
return a.deniedResponse(ctx, in, http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized), nil)
}
q := url.Values{} q := url.Values{}
if deviceType, ok := result.Allow.AdditionalData["device_type"].(string); ok { if deviceType, ok := result.Allow.AdditionalData["device_type"].(string); ok {
q.Set(urlutil.QueryDeviceType, deviceType) q.Set(urlutil.QueryDeviceType, deviceType)

View file

@ -62,6 +62,36 @@ func TestAuthorize_handleResult(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, 302, int(res.GetDeniedResponse().GetStatus().GetCode())) assert.Equal(t, 302, int(res.GetDeniedResponse().GetStatus().GetCode()))
}) })
t.Run("device-unauthenticated", func(t *testing.T) {
res, err := a.handleResult(context.Background(),
&envoy_service_auth_v3.CheckRequest{},
&evaluator.Request{},
&evaluator.Result{
Allow: evaluator.NewRuleResult(false, criteria.ReasonDeviceUnauthenticated),
})
assert.NoError(t, err)
assert.Equal(t, 302, int(res.GetDeniedResponse().GetStatus().GetCode()))
t.Run("webauthn path", func(t *testing.T) {
res, err := a.handleResult(context.Background(),
&envoy_service_auth_v3.CheckRequest{
Attributes: &envoy_service_auth_v3.AttributeContext{
Request: &envoy_service_auth_v3.AttributeContext_Request{
Http: &envoy_service_auth_v3.AttributeContext_HttpRequest{
Path: "/.pomerium/webauthn",
},
},
},
},
&evaluator.Request{},
&evaluator.Result{
Allow: evaluator.NewRuleResult(true, criteria.ReasonPomeriumRoute),
Deny: evaluator.NewRuleResult(false, criteria.ReasonDeviceUnauthenticated),
})
assert.NoError(t, err)
assert.NotNil(t, res.GetOkResponse())
})
})
} }
func TestAuthorize_okResponse(t *testing.T) { func TestAuthorize_okResponse(t *testing.T) {

View file

@ -60,7 +60,7 @@ func (b *Builder) buildPomeriumHTTPRoutes(options *config.Options, host string)
routes = append(routes, routes = append(routes,
// enable ext_authz // enable ext_authz
b.buildControlPlanePathRoute("/.pomerium/jwt", true), b.buildControlPlanePathRoute("/.pomerium/jwt", true),
b.buildControlPlanePathRoute("/.pomerium/webauthn", true), b.buildControlPlanePathRoute(urlutil.WebAuthnURLPath, true),
// disable ext_authz and passthrough to proxy handlers // disable ext_authz and passthrough to proxy handlers
b.buildControlPlanePathRoute("/ping", false), b.buildControlPlanePathRoute("/ping", false),
b.buildControlPlanePathRoute("/healthz", false), b.buildControlPlanePathRoute("/healthz", false),

View file

@ -16,6 +16,7 @@ import (
"github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/config/envoyconfig/filemgr" "github.com/pomerium/pomerium/config/envoyconfig/filemgr"
"github.com/pomerium/pomerium/internal/testutil" "github.com/pomerium/pomerium/internal/testutil"
"github.com/pomerium/pomerium/internal/urlutil"
) )
func policyNameFunc() func(*config.Policy) string { func policyNameFunc() func(*config.Policy) string {
@ -88,7 +89,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
testutil.AssertProtoJSONEqual(t, `[ testutil.AssertProtoJSONEqual(t, `[
`+routeString("path", "/.pomerium/jwt", true)+`, `+routeString("path", "/.pomerium/jwt", true)+`,
`+routeString("path", "/.pomerium/webauthn", true)+`, `+routeString("path", urlutil.WebAuthnURLPath, true)+`,
`+routeString("path", "/ping", false)+`, `+routeString("path", "/ping", false)+`,
`+routeString("path", "/healthz", false)+`, `+routeString("path", "/healthz", false)+`,
`+routeString("path", "/.pomerium", false)+`, `+routeString("path", "/.pomerium", false)+`,
@ -127,7 +128,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
testutil.AssertProtoJSONEqual(t, `[ testutil.AssertProtoJSONEqual(t, `[
`+routeString("path", "/.pomerium/jwt", true)+`, `+routeString("path", "/.pomerium/jwt", true)+`,
`+routeString("path", "/.pomerium/webauthn", true)+`, `+routeString("path", urlutil.WebAuthnURLPath, true)+`,
`+routeString("path", "/ping", false)+`, `+routeString("path", "/ping", false)+`,
`+routeString("path", "/healthz", false)+`, `+routeString("path", "/healthz", false)+`,
`+routeString("path", "/.pomerium", false)+`, `+routeString("path", "/.pomerium", false)+`,
@ -155,7 +156,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
testutil.AssertProtoJSONEqual(t, `[ testutil.AssertProtoJSONEqual(t, `[
`+routeString("path", "/.pomerium/jwt", true)+`, `+routeString("path", "/.pomerium/jwt", true)+`,
`+routeString("path", "/.pomerium/webauthn", true)+`, `+routeString("path", urlutil.WebAuthnURLPath, true)+`,
`+routeString("path", "/ping", false)+`, `+routeString("path", "/ping", false)+`,
`+routeString("path", "/healthz", false)+`, `+routeString("path", "/healthz", false)+`,
`+routeString("path", "/.pomerium", false)+`, `+routeString("path", "/.pomerium", false)+`,

View file

@ -34,15 +34,21 @@ func SignOutURL(r *http.Request, authenticateURL *url.URL, key []byte) string {
return NewSignedURL(key, u).Sign().String() return NewSignedURL(key, u).Sign().String()
} }
// Device paths
const (
WebAuthnURLPath = "/.pomerium/webauthn"
DeviceEnrolledPath = "/.pomerium/device-enrolled"
)
// WebAuthnURL returns the /.pomerium/webauthn URL. // WebAuthnURL returns the /.pomerium/webauthn URL.
func WebAuthnURL(r *http.Request, authenticateURL *url.URL, key []byte, values url.Values) string { func WebAuthnURL(r *http.Request, authenticateURL *url.URL, key []byte, values url.Values) string {
u := authenticateURL.ResolveReference(&url.URL{ u := authenticateURL.ResolveReference(&url.URL{
Path: "/.pomerium/webauthn", Path: WebAuthnURLPath,
RawQuery: buildURLValues(values, url.Values{ RawQuery: buildURLValues(values, url.Values{
QueryDeviceType: {DefaultDeviceType}, QueryDeviceType: {DefaultDeviceType},
QueryEnrollmentToken: nil, QueryEnrollmentToken: nil,
QueryRedirectURI: {authenticateURL.ResolveReference(&url.URL{ QueryRedirectURI: {authenticateURL.ResolveReference(&url.URL{
Path: "/.pomerium/device-enrolled", Path: DeviceEnrolledPath,
}).String()}, }).String()},
}).Encode(), }).Encode(),
}) })

View file

@ -3,6 +3,7 @@ package criteria
import ( import (
"github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/ast"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/policy/generator" "github.com/pomerium/pomerium/pkg/policy/generator"
"github.com/pomerium/pomerium/pkg/policy/parser" "github.com/pomerium/pomerium/pkg/policy/parser"
"github.com/pomerium/pomerium/pkg/policy/rules" "github.com/pomerium/pomerium/pkg/policy/rules"
@ -34,7 +35,7 @@ func (c pomeriumRoutesCriterion) GenerateRule(_ string, _ parser.Value) (*ast.Ru
r2.Body = ast.Body{ r2.Body = ast.Body{
ast.MustParseExpr(`contains(input.http.url, "/.pomerium/")`), ast.MustParseExpr(`contains(input.http.url, "/.pomerium/")`),
ast.MustParseExpr(`not contains(input.http.url, "/.pomerium/jwt")`), ast.MustParseExpr(`not contains(input.http.url, "/.pomerium/jwt")`),
ast.MustParseExpr(`not contains(input.http.url, "/.pomerium/webauthn")`), ast.MustParseExpr(`not contains(input.http.url, "` + urlutil.WebAuthnURLPath + `")`),
} }
r1.Else = r2 r1.Else = r2