mirror of
https://github.com/pomerium/pomerium.git
synced 2025-04-28 18:06:34 +02:00
webauthn: require session when accessing /.pomerium/webauthn (#3814)
* webauthn: require session when accessing /.pomerium/webauthn * remove dead code * remove unusued PomeriumDomains field
This commit is contained in:
parent
44a5c1b2fb
commit
c86ca6f76f
8 changed files with 67 additions and 78 deletions
|
@ -649,17 +649,11 @@ func (a *Authenticate) getWebauthnState(r *http.Request) (*webauthn.State, error
|
|||
return nil, err
|
||||
}
|
||||
|
||||
pomeriumDomains, err := a.options.Load().GetAllRouteableHTTPDomains()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &webauthn.State{
|
||||
AuthenticateURL: authenticateURL,
|
||||
InternalAuthenticateURL: internalAuthenticateURL,
|
||||
SharedKey: state.sharedKey,
|
||||
Client: state.dataBrokerClient,
|
||||
PomeriumDomains: pomeriumDomains,
|
||||
Session: s,
|
||||
SessionState: ss,
|
||||
SessionStore: state.sessionStore,
|
||||
|
|
|
@ -253,6 +253,15 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
|
|||
"cluster": "pomerium-control-plane-http"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pomerium-path-/.pomerium/webauthn",
|
||||
"match": {
|
||||
"path": "/.pomerium/webauthn"
|
||||
},
|
||||
"route": {
|
||||
"cluster": "pomerium-control-plane-http"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pomerium-path-/ping",
|
||||
"match": {
|
||||
|
@ -394,6 +403,15 @@ func Test_buildMainHTTPConnectionManagerFilter(t *testing.T) {
|
|||
"cluster": "pomerium-control-plane-http"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pomerium-path-/.pomerium/webauthn",
|
||||
"match": {
|
||||
"path": "/.pomerium/webauthn"
|
||||
},
|
||||
"route": {
|
||||
"cluster": "pomerium-control-plane-http"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pomerium-path-/ping",
|
||||
"match": {
|
||||
|
|
|
@ -60,6 +60,7 @@ func (b *Builder) buildPomeriumHTTPRoutes(options *config.Options, domain string
|
|||
routes = append(routes,
|
||||
// enable ext_authz
|
||||
b.buildControlPlanePathRoute("/.pomerium/jwt", true),
|
||||
b.buildControlPlanePathRoute("/.pomerium/webauthn", true),
|
||||
// disable ext_authz and passthrough to proxy handlers
|
||||
b.buildControlPlanePathRoute("/ping", false),
|
||||
b.buildControlPlanePathRoute("/healthz", false),
|
||||
|
|
|
@ -88,6 +88,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
|
|||
|
||||
testutil.AssertProtoJSONEqual(t, `[
|
||||
`+routeString("path", "/.pomerium/jwt", true)+`,
|
||||
`+routeString("path", "/.pomerium/webauthn", true)+`,
|
||||
`+routeString("path", "/ping", false)+`,
|
||||
`+routeString("path", "/healthz", false)+`,
|
||||
`+routeString("path", "/.pomerium", false)+`,
|
||||
|
@ -126,6 +127,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
|
|||
|
||||
testutil.AssertProtoJSONEqual(t, `[
|
||||
`+routeString("path", "/.pomerium/jwt", true)+`,
|
||||
`+routeString("path", "/.pomerium/webauthn", true)+`,
|
||||
`+routeString("path", "/ping", false)+`,
|
||||
`+routeString("path", "/healthz", false)+`,
|
||||
`+routeString("path", "/.pomerium", false)+`,
|
||||
|
@ -153,6 +155,7 @@ func Test_buildPomeriumHTTPRoutes(t *testing.T) {
|
|||
|
||||
testutil.AssertProtoJSONEqual(t, `[
|
||||
`+routeString("path", "/.pomerium/jwt", true)+`,
|
||||
`+routeString("path", "/.pomerium/webauthn", true)+`,
|
||||
`+routeString("path", "/ping", false)+`,
|
||||
`+routeString("path", "/healthz", false)+`,
|
||||
`+routeString("path", "/.pomerium", false)+`,
|
||||
|
@ -249,7 +252,8 @@ func TestTimeouts(t *testing.T) {
|
|||
UpstreamTimeout: getDuration(tc.upstream),
|
||||
IdleTimeout: getDuration(tc.idle),
|
||||
AllowWebsockets: tc.allowWebsockets,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}, "example.com")
|
||||
if !assert.NoError(t, err, "%v", tc) || !assert.Len(t, routes, 1, tc) || !assert.NotNil(t, routes[0].GetRoute(), "%v", tc) {
|
||||
continue
|
||||
|
|
|
@ -58,8 +58,19 @@ default allow = [false, set()]
|
|||
default deny = [false, set()]
|
||||
|
||||
pomerium_routes_0 = [true, {"pomerium-route"}] {
|
||||
session := get_session(input.session.id)
|
||||
session.id != ""
|
||||
contains(input.http.url, "/.pomerium/")
|
||||
}
|
||||
|
||||
else = [true, {"pomerium-route"}] {
|
||||
contains(input.http.url, "/.pomerium/")
|
||||
not contains(input.http.url, "/.pomerium/jwt")
|
||||
not contains(input.http.url, "/.pomerium/webauthn")
|
||||
}
|
||||
|
||||
else = [false, {"user-unauthenticated"}] {
|
||||
contains(input.http.url, "/.pomerium/")
|
||||
}
|
||||
|
||||
else = [false, {"non-pomerium-route"}]
|
||||
|
|
|
@ -4,7 +4,6 @@ package webauthn
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -16,12 +15,10 @@ import (
|
|||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/pomerium/pomerium/internal/encoding/jws"
|
||||
"github.com/pomerium/pomerium/internal/httputil"
|
||||
"github.com/pomerium/pomerium/internal/middleware"
|
||||
"github.com/pomerium/pomerium/internal/sessions"
|
||||
"github.com/pomerium/pomerium/internal/urlutil"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
"github.com/pomerium/pomerium/pkg/grpc/databroker"
|
||||
"github.com/pomerium/pomerium/pkg/grpc/device"
|
||||
"github.com/pomerium/pomerium/pkg/grpc/session"
|
||||
|
@ -49,7 +46,6 @@ type State struct {
|
|||
AuthenticateURL *url.URL
|
||||
InternalAuthenticateURL *url.URL
|
||||
Client databroker.DataBrokerServiceClient
|
||||
PomeriumDomains []string
|
||||
RelyingParty *webauthn.RelyingParty
|
||||
Session *session.Session
|
||||
SessionState *sessions.State
|
||||
|
@ -423,39 +419,7 @@ func (h *Handler) saveSessionAndRedirect(w http.ResponseWriter, r *http.Request,
|
|||
return err
|
||||
}
|
||||
|
||||
// if the redirect URL is for a URL we don't control, just do a plain redirect
|
||||
if !isURLForPomerium(state.PomeriumDomains, rawRedirectURI) {
|
||||
httputil.Redirect(w, r, rawRedirectURI, http.StatusFound)
|
||||
return nil
|
||||
}
|
||||
|
||||
// sign+encrypt the session JWT
|
||||
encoder, err := jws.NewHS256Signer(state.SharedKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signedJWT, err := encoder.Marshal(state.SessionState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cipher, err := cryptutil.NewAEADCipher(state.SharedKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encryptedJWT := cryptutil.Encrypt(cipher, signedJWT, nil)
|
||||
encodedJWT := base64.URLEncoding.EncodeToString(encryptedJWT)
|
||||
|
||||
// redirect to the proxy callback URL with the session
|
||||
callbackURL, err := urlutil.GetCallbackURLForRedirectURI(r, encodedJWT, rawRedirectURI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signedCallbackURL := urlutil.NewSignedURL(state.SharedKey, callbackURL)
|
||||
httputil.Redirect(w, r, signedCallbackURL.String(), http.StatusFound)
|
||||
httputil.Redirect(w, r, rawRedirectURI, http.StatusFound)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -533,18 +497,3 @@ func getOrCreateDeviceEnrollment(
|
|||
}
|
||||
return deviceEnrollment, nil
|
||||
}
|
||||
|
||||
func isURLForPomerium(pomeriumDomains []string, rawURI string) bool {
|
||||
uri, err := urlutil.ParseAndValidateURL(rawURI)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, domain := range pomeriumDomains {
|
||||
if urlutil.StripPort(domain) == urlutil.StripPort(uri.Host) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -5,17 +5,9 @@ import (
|
|||
|
||||
"github.com/pomerium/pomerium/pkg/policy/generator"
|
||||
"github.com/pomerium/pomerium/pkg/policy/parser"
|
||||
"github.com/pomerium/pomerium/pkg/policy/rules"
|
||||
)
|
||||
|
||||
var pomeriumRoutesBody = ast.Body{
|
||||
ast.MustParseExpr(`
|
||||
contains(input.http.url, "/.pomerium/")
|
||||
`),
|
||||
ast.MustParseExpr(`
|
||||
not contains(input.http.url, "/.pomerium/jwt")
|
||||
`),
|
||||
}
|
||||
|
||||
type pomeriumRoutesCriterion struct {
|
||||
g *Generator
|
||||
}
|
||||
|
@ -29,11 +21,37 @@ func (pomeriumRoutesCriterion) Name() string {
|
|||
}
|
||||
|
||||
func (c pomeriumRoutesCriterion) GenerateRule(_ string, _ parser.Value) (*ast.Rule, []*ast.Rule, error) {
|
||||
rule := NewCriterionRule(c.g, c.Name(),
|
||||
ReasonPomeriumRoute, ReasonNonPomeriumRoute,
|
||||
pomeriumRoutesBody)
|
||||
r1 := c.g.NewRule(c.Name())
|
||||
r1.Head.Value = NewCriterionTerm(true, ReasonPomeriumRoute)
|
||||
r1.Body = ast.Body{
|
||||
ast.MustParseExpr(`session := get_session(input.session.id)`),
|
||||
ast.MustParseExpr(`session.id != ""`),
|
||||
ast.MustParseExpr(`contains(input.http.url, "/.pomerium/")`),
|
||||
}
|
||||
|
||||
return rule, nil, nil
|
||||
r2 := c.g.NewRule(c.Name())
|
||||
r2.Head.Value = NewCriterionTerm(true, ReasonPomeriumRoute)
|
||||
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")`),
|
||||
}
|
||||
r1.Else = r2
|
||||
|
||||
r3 := c.g.NewRule(c.Name())
|
||||
r3.Head.Value = NewCriterionTerm(false, ReasonUserUnauthenticated)
|
||||
r3.Body = ast.Body{
|
||||
ast.MustParseExpr(`contains(input.http.url, "/.pomerium/")`),
|
||||
}
|
||||
r2.Else = r3
|
||||
|
||||
r4 := c.g.NewRule(c.Name())
|
||||
r4.Head.Value = NewCriterionTerm(false, ReasonNonPomeriumRoute)
|
||||
r3.Else = r4
|
||||
|
||||
return r1, []*ast.Rule{
|
||||
rules.GetSession(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PomeriumRoutes returns a Criterion on that allows access to pomerium routes.
|
||||
|
|
|
@ -131,17 +131,11 @@ func (p *Proxy) getWebauthnState(r *http.Request) (*webauthn.State, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
pomeriumDomains, err := options.GetAllRouteableHTTPDomains()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &webauthn.State{
|
||||
AuthenticateURL: authenticateURL,
|
||||
InternalAuthenticateURL: internalAuthenticateURL,
|
||||
SharedKey: state.sharedKey,
|
||||
Client: state.dataBrokerClient,
|
||||
PomeriumDomains: pomeriumDomains,
|
||||
Session: s,
|
||||
SessionState: &ss,
|
||||
SessionStore: state.sessionStore,
|
||||
|
|
Loading…
Add table
Reference in a new issue