diff --git a/authorize/check_response.go b/authorize/check_response.go index a5829f02c..89f898f23 100644 --- a/authorize/check_response.go +++ b/authorize/check_response.go @@ -226,14 +226,20 @@ func (a *Authorize) requireWebAuthnResponse( opts := a.currentOptions.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 checkRequestURL := getCheckRequestURL(in) 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{} if deviceType, ok := result.Allow.AdditionalData["device_type"].(string); ok { q.Set(urlutil.QueryDeviceType, deviceType) diff --git a/authorize/check_response_test.go b/authorize/check_response_test.go index 4f9e93695..79c3ec23b 100644 --- a/authorize/check_response_test.go +++ b/authorize/check_response_test.go @@ -62,6 +62,36 @@ func TestAuthorize_handleResult(t *testing.T) { assert.NoError(t, err) 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) { diff --git a/config/envoyconfig/routes.go b/config/envoyconfig/routes.go index 15d9f7936..881119cc1 100644 --- a/config/envoyconfig/routes.go +++ b/config/envoyconfig/routes.go @@ -60,7 +60,7 @@ func (b *Builder) buildPomeriumHTTPRoutes(options *config.Options, host string) routes = append(routes, // enable ext_authz b.buildControlPlanePathRoute("/.pomerium/jwt", true), - b.buildControlPlanePathRoute("/.pomerium/webauthn", true), + b.buildControlPlanePathRoute(urlutil.WebAuthnURLPath, true), // disable ext_authz and passthrough to proxy handlers b.buildControlPlanePathRoute("/ping", false), b.buildControlPlanePathRoute("/healthz", false), diff --git a/config/envoyconfig/routes_test.go b/config/envoyconfig/routes_test.go index 246470df8..1916e13fa 100644 --- a/config/envoyconfig/routes_test.go +++ b/config/envoyconfig/routes_test.go @@ -16,6 +16,7 @@ import ( "github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/config/envoyconfig/filemgr" "github.com/pomerium/pomerium/internal/testutil" + "github.com/pomerium/pomerium/internal/urlutil" ) func policyNameFunc() func(*config.Policy) string { @@ -88,7 +89,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) { testutil.AssertProtoJSONEqual(t, `[ `+routeString("path", "/.pomerium/jwt", true)+`, - `+routeString("path", "/.pomerium/webauthn", true)+`, + `+routeString("path", urlutil.WebAuthnURLPath, true)+`, `+routeString("path", "/ping", false)+`, `+routeString("path", "/healthz", false)+`, `+routeString("path", "/.pomerium", false)+`, @@ -127,7 +128,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) { testutil.AssertProtoJSONEqual(t, `[ `+routeString("path", "/.pomerium/jwt", true)+`, - `+routeString("path", "/.pomerium/webauthn", true)+`, + `+routeString("path", urlutil.WebAuthnURLPath, true)+`, `+routeString("path", "/ping", false)+`, `+routeString("path", "/healthz", false)+`, `+routeString("path", "/.pomerium", false)+`, @@ -155,7 +156,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) { testutil.AssertProtoJSONEqual(t, `[ `+routeString("path", "/.pomerium/jwt", true)+`, - `+routeString("path", "/.pomerium/webauthn", true)+`, + `+routeString("path", urlutil.WebAuthnURLPath, true)+`, `+routeString("path", "/ping", false)+`, `+routeString("path", "/healthz", false)+`, `+routeString("path", "/.pomerium", false)+`, diff --git a/internal/urlutil/known.go b/internal/urlutil/known.go index 55c2f502b..6ef226cb9 100644 --- a/internal/urlutil/known.go +++ b/internal/urlutil/known.go @@ -34,15 +34,21 @@ func SignOutURL(r *http.Request, authenticateURL *url.URL, key []byte) string { return NewSignedURL(key, u).Sign().String() } +// Device paths +const ( + WebAuthnURLPath = "/.pomerium/webauthn" + DeviceEnrolledPath = "/.pomerium/device-enrolled" +) + // WebAuthnURL returns the /.pomerium/webauthn URL. func WebAuthnURL(r *http.Request, authenticateURL *url.URL, key []byte, values url.Values) string { u := authenticateURL.ResolveReference(&url.URL{ - Path: "/.pomerium/webauthn", + Path: WebAuthnURLPath, RawQuery: buildURLValues(values, url.Values{ QueryDeviceType: {DefaultDeviceType}, QueryEnrollmentToken: nil, QueryRedirectURI: {authenticateURL.ResolveReference(&url.URL{ - Path: "/.pomerium/device-enrolled", + Path: DeviceEnrolledPath, }).String()}, }).Encode(), }) diff --git a/pkg/policy/criteria/pomerium_routes.go b/pkg/policy/criteria/pomerium_routes.go index dd150dc8a..a1b6d6edd 100644 --- a/pkg/policy/criteria/pomerium_routes.go +++ b/pkg/policy/criteria/pomerium_routes.go @@ -3,6 +3,7 @@ package criteria import ( "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/parser" "github.com/pomerium/pomerium/pkg/policy/rules" @@ -34,7 +35,7 @@ func (c pomeriumRoutesCriterion) GenerateRule(_ string, _ parser.Value) (*ast.Ru r2.Body = ast.Body{ ast.MustParseExpr(`contains(input.http.url, "/.pomerium/")`), 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