authenticate: remove databroker dependency (#3820)

This commit is contained in:
Caleb Doxsey 2022-12-17 09:03:46 -07:00 committed by GitHub
parent c3b9adff20
commit 539fd51579
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 14 additions and 202 deletions

View file

@ -9,7 +9,6 @@ import (
"github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/atomicutil" "github.com/pomerium/pomerium/internal/atomicutil"
"github.com/pomerium/pomerium/internal/handlers/webauthn"
"github.com/pomerium/pomerium/internal/log" "github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/pkg/cryptutil" "github.com/pomerium/pomerium/pkg/cryptutil"
) )
@ -42,7 +41,6 @@ type Authenticate struct {
cfg *authenticateConfig cfg *authenticateConfig
options *atomicutil.Value[*config.Options] options *atomicutil.Value[*config.Options]
state *atomicutil.Value[*authenticateState] state *atomicutil.Value[*authenticateState]
webauthn *webauthn.Handler
} }
// New validates and creates a new authenticate service from a set of Options. // New validates and creates a new authenticate service from a set of Options.
@ -52,7 +50,6 @@ func New(cfg *config.Config, options ...Option) (*Authenticate, error) {
options: config.NewAtomicOptions(), options: config.NewAtomicOptions(),
state: atomicutil.NewValue(newAuthenticateState()), state: atomicutil.NewValue(newAuthenticateState()),
} }
a.webauthn = webauthn.New(a.getWebauthnState)
state, err := newAuthenticateStateFromConfig(cfg) state, err := newAuthenticateStateFromConfig(cfg)
if err != nil { if err != nil {

View file

@ -3,6 +3,7 @@ package authenticate
import ( import (
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
@ -13,14 +14,12 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/rs/cors" "github.com/rs/cors"
"golang.org/x/oauth2"
"github.com/pomerium/csrf" "github.com/pomerium/csrf"
"github.com/pomerium/datasource/pkg/directory"
"github.com/pomerium/pomerium/internal/handlers" "github.com/pomerium/pomerium/internal/handlers"
"github.com/pomerium/pomerium/internal/handlers/webauthn"
"github.com/pomerium/pomerium/internal/httputil" "github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/identity" "github.com/pomerium/pomerium/internal/identity"
"github.com/pomerium/pomerium/internal/identity/manager"
"github.com/pomerium/pomerium/internal/identity/oidc" "github.com/pomerium/pomerium/internal/identity/oidc"
"github.com/pomerium/pomerium/internal/log" "github.com/pomerium/pomerium/internal/log"
"github.com/pomerium/pomerium/internal/middleware" "github.com/pomerium/pomerium/internal/middleware"
@ -28,11 +27,7 @@ import (
"github.com/pomerium/pomerium/internal/telemetry/trace" "github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil" "github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil" "github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
"github.com/pomerium/pomerium/pkg/grpc/session"
"github.com/pomerium/pomerium/pkg/grpc/user"
"github.com/pomerium/pomerium/pkg/hpke" "github.com/pomerium/pomerium/pkg/hpke"
"github.com/pomerium/pomerium/pkg/webauthnutil"
) )
// Handler returns the authenticate service's handler chain. // Handler returns the authenticate service's handler chain.
@ -96,7 +91,6 @@ func (a *Authenticate) mountDashboard(r *mux.Router) {
sr.Path("/").Handler(a.requireValidSignatureOnRedirect(a.userInfo)) sr.Path("/").Handler(a.requireValidSignatureOnRedirect(a.userInfo))
sr.Path("/sign_in").Handler(httputil.HandlerFunc(a.SignIn)) sr.Path("/sign_in").Handler(httputil.HandlerFunc(a.SignIn))
sr.Path("/sign_out").Handler(httputil.HandlerFunc(a.SignOut)) sr.Path("/sign_out").Handler(httputil.HandlerFunc(a.SignOut))
sr.Path("/webauthn").Handler(a.webauthn)
sr.Path("/device-enrolled").Handler(httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { sr.Path("/device-enrolled").Handler(httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
userInfoData, err := a.getUserInfoData(r) userInfoData, err := a.getUserInfoData(r)
if err != nil { if err != nil {
@ -496,74 +490,22 @@ func (a *Authenticate) userInfo(w http.ResponseWriter, r *http.Request) error {
func (a *Authenticate) getUserInfoData(r *http.Request) (handlers.UserInfoData, error) { func (a *Authenticate) getUserInfoData(r *http.Request) (handlers.UserInfoData, error) {
state := a.state.Load() state := a.state.Load()
authenticateURL, err := a.options.Load().GetAuthenticateURL()
if err != nil {
return handlers.UserInfoData{}, err
}
s, err := a.getSessionFromCtx(r.Context()) s, err := a.getSessionFromCtx(r.Context())
if err != nil { if err != nil {
s.ID = uuid.New().String() s.ID = uuid.New().String()
} }
pbSession, isImpersonated, err := a.getCurrentSession(r.Context())
if err != nil {
pbSession = &session.Session{
Id: s.ID,
}
}
pbUser, err := a.getUser(r.Context(), pbSession.GetUserId())
if err != nil {
pbUser = &user.User{
Id: pbSession.GetUserId(),
}
}
creationOptions, requestOptions, _ := a.webauthn.GetOptions(r)
profile, _ := loadIdentityProfile(r, state.cookieCipher) profile, _ := loadIdentityProfile(r, state.cookieCipher)
data := handlers.UserInfoData{ data := handlers.UserInfoData{
CSRFToken: csrf.Token(r), CSRFToken: csrf.Token(r),
IsImpersonated: isImpersonated,
Session: pbSession,
User: pbUser,
Profile: profile, Profile: profile,
WebAuthnCreationOptions: creationOptions,
WebAuthnRequestOptions: requestOptions,
WebAuthnURL: urlutil.WebAuthnURL(r, authenticateURL, state.sharedKey, r.URL.Query()),
BrandingOptions: a.options.Load().BrandingOptions, BrandingOptions: a.options.Load().BrandingOptions,
} }
a.fillEnterpriseUserInfoData(r.Context(), &data, pbSession.GetUserId())
return data, nil return data, nil
} }
func (a *Authenticate) fillEnterpriseUserInfoData(
ctx context.Context,
dst *handlers.UserInfoData,
userID string,
) {
client := a.state.Load().dataBrokerClient
res, _ := client.Get(ctx, &databroker.GetRequest{Type: "type.googleapis.com/pomerium.config.Config", Id: "dashboard"})
dst.IsEnterprise = res.GetRecord() != nil
if !dst.IsEnterprise {
return
}
dst.DirectoryUser, _ = databroker.GetViaJSON[directory.User](ctx, client, directory.UserRecordType, userID)
if dst.DirectoryUser != nil {
for _, groupID := range dst.DirectoryUser.GroupIDs {
directoryGroup, _ := databroker.GetViaJSON[directory.Group](ctx, client, directory.GroupRecordType, groupID)
if directoryGroup != nil {
dst.DirectoryGroups = append(dst.DirectoryGroups, directoryGroup)
}
}
}
}
// revokeSession always clears the local session and tries to revoke the associated session stored in the // revokeSession always clears the local session and tries to revoke the associated session stored in the
// databroker. If successful, it returns the original `id_token` of the session, if failed, returns // databroker. If successful, it returns the original `id_token` of the session, if failed, returns
// and empty string. // and empty string.
@ -584,82 +526,18 @@ func (a *Authenticate) revokeSession(ctx context.Context, w http.ResponseWriter,
return "" return ""
} }
var rawIDToken string profile, err := loadIdentityProfile(r, a.state.Load().cookieCipher)
sessionState, err := a.getSessionFromCtx(ctx)
if err != nil { if err != nil {
return rawIDToken return ""
} }
if s, _ := session.Get(ctx, state.dataBrokerClient, sessionState.ID); s != nil && s.OauthToken != nil { oauthToken := new(oauth2.Token)
rawIDToken = s.GetIdToken().GetRaw() _ = json.Unmarshal(profile.GetOauthToken(), oauthToken)
if err := authenticator.Revoke(ctx, manager.FromOAuthToken(s.OauthToken)); err != nil { if err := authenticator.Revoke(ctx, oauthToken); err != nil {
log.Ctx(ctx).Warn().Err(err).Msg("authenticate: failed to revoke access token") log.Ctx(ctx).Warn().Err(err).Msg("authenticate: failed to revoke access token")
} }
}
if err := session.Delete(ctx, state.dataBrokerClient, sessionState.ID); err != nil {
log.Ctx(ctx).Warn().Err(err).Msg("authenticate: failed to delete session from session store")
}
return rawIDToken return string(profile.GetIdToken())
}
func (a *Authenticate) getCurrentSession(ctx context.Context) (s *session.Session, isImpersonated bool, err error) {
client := a.state.Load().dataBrokerClient
sessionState, err := a.getSessionFromCtx(ctx)
if err != nil {
return nil, false, err
}
isImpersonated = false
s, err = session.Get(ctx, client, sessionState.ID)
if s.GetImpersonateSessionId() != "" {
s, err = session.Get(ctx, client, s.GetImpersonateSessionId())
isImpersonated = true
}
return s, isImpersonated, err
}
func (a *Authenticate) getUser(ctx context.Context, userID string) (*user.User, error) {
client := a.state.Load().dataBrokerClient
return user.Get(ctx, client, userID)
}
func (a *Authenticate) getWebauthnState(r *http.Request) (*webauthn.State, error) {
state := a.state.Load()
s, _, err := a.getCurrentSession(r.Context())
if err != nil {
return nil, err
}
ss, err := a.getSessionFromCtx(r.Context())
if err != nil {
return nil, err
}
authenticateURL, err := a.options.Load().GetAuthenticateURL()
if err != nil {
return nil, err
}
internalAuthenticateURL, err := a.options.Load().GetInternalAuthenticateURL()
if err != nil {
return nil, err
}
return &webauthn.State{
AuthenticateURL: authenticateURL,
InternalAuthenticateURL: internalAuthenticateURL,
SharedKey: state.sharedKey,
Client: state.dataBrokerClient,
Session: s,
SessionState: ss,
SessionStore: state.sessionStore,
RelyingParty: webauthnutil.GetRelyingParty(r, state.dataBrokerClient),
BrandingOptions: a.options.Load().BrandingOptions,
}, nil
} }
// Callback handles the result of a successful call to the authenticate service // Callback handles the result of a successful call to the authenticate service

View file

@ -19,13 +19,11 @@ import (
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/atomicutil" "github.com/pomerium/pomerium/internal/atomicutil"
"github.com/pomerium/pomerium/internal/encoding/jws" "github.com/pomerium/pomerium/internal/encoding/jws"
"github.com/pomerium/pomerium/internal/encoding/mock" "github.com/pomerium/pomerium/internal/encoding/mock"
"github.com/pomerium/pomerium/internal/handlers/webauthn"
"github.com/pomerium/pomerium/internal/httputil" "github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/identity" "github.com/pomerium/pomerium/internal/identity"
"github.com/pomerium/pomerium/internal/identity/oidc" "github.com/pomerium/pomerium/internal/identity/oidc"
@ -34,7 +32,6 @@ import (
"github.com/pomerium/pomerium/internal/urlutil" "github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil" "github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc/databroker" "github.com/pomerium/pomerium/pkg/grpc/databroker"
"github.com/pomerium/pomerium/pkg/grpc/session"
) )
func testAuthenticate() *Authenticate { func testAuthenticate() *Authenticate {
@ -221,18 +218,6 @@ func TestAuthenticate_SignOut(t *testing.T) {
state: atomicutil.NewValue(&authenticateState{ state: atomicutil.NewValue(&authenticateState{
sessionStore: tt.sessionStore, sessionStore: tt.sessionStore,
sharedEncoder: mock.Encoder{}, sharedEncoder: mock.Encoder{},
dataBrokerClient: mockDataBrokerServiceClient{
get: func(ctx context.Context, in *databroker.GetRequest, opts ...grpc.CallOption) (*databroker.GetResponse, error) {
return &databroker.GetResponse{
Record: databroker.NewRecord(&session.Session{
Id: "SESSION_ID",
}),
}, nil
},
put: func(ctx context.Context, in *databroker.PutRequest, opts ...grpc.CallOption) (*databroker.PutResponse, error) {
return nil, nil
},
},
}), }),
options: config.NewAtomicOptions(), options: config.NewAtomicOptions(),
} }
@ -326,14 +311,6 @@ func TestAuthenticate_OAuthCallback(t *testing.T) {
return tt.provider, nil return tt.provider, nil
})), })),
state: atomicutil.NewValue(&authenticateState{ state: atomicutil.NewValue(&authenticateState{
dataBrokerClient: mockDataBrokerServiceClient{
get: func(ctx context.Context, in *databroker.GetRequest, opts ...grpc.CallOption) (*databroker.GetResponse, error) {
return nil, fmt.Errorf("not implemented")
},
put: func(ctx context.Context, in *databroker.PutRequest, opts ...grpc.CallOption) (*databroker.PutResponse, error) {
return nil, nil
},
},
redirectURL: authURL, redirectURL: authURL,
sessionStore: tt.session, sessionStore: tt.session,
cookieCipher: aead, cookieCipher: aead,
@ -450,15 +427,6 @@ func TestAuthenticate_SessionValidatorMiddleware(t *testing.T) {
sessionStore: tt.session, sessionStore: tt.session,
cookieCipher: aead, cookieCipher: aead,
sharedEncoder: signer, sharedEncoder: signer,
dataBrokerClient: mockDataBrokerServiceClient{
get: func(ctx context.Context, in *databroker.GetRequest, opts ...grpc.CallOption) (*databroker.GetResponse, error) {
return &databroker.GetResponse{
Record: databroker.NewRecord(&session.Session{
Id: "SESSION_ID",
}),
}, nil
},
},
}), }),
options: config.NewAtomicOptions(), options: config.NewAtomicOptions(),
} }
@ -563,20 +531,8 @@ func TestAuthenticate_userInfo(t *testing.T) {
state: atomicutil.NewValue(&authenticateState{ state: atomicutil.NewValue(&authenticateState{
sessionStore: tt.sessionStore, sessionStore: tt.sessionStore,
sharedEncoder: signer, sharedEncoder: signer,
dataBrokerClient: mockDataBrokerServiceClient{
get: func(ctx context.Context, in *databroker.GetRequest, opts ...grpc.CallOption) (*databroker.GetResponse, error) {
return &databroker.GetResponse{
Record: databroker.NewRecord(&session.Session{
Id: "SESSION_ID",
UserId: "USER_ID",
IdToken: &session.IDToken{IssuedAt: timestamppb.New(now)},
}),
}, nil
},
},
}), }),
} }
a.webauthn = webauthn.New(a.getWebauthnState)
r := httptest.NewRequest(tt.method, tt.url.String(), nil) r := httptest.NewRequest(tt.method, tt.url.String(), nil)
state, err := tt.sessionStore.LoadSession(r) state, err := tt.sessionStore.LoadSession(r)
if err != nil { if err != nil {

View file

@ -1,7 +1,6 @@
package authenticate package authenticate
import ( import (
"context"
"crypto/cipher" "crypto/cipher"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
@ -16,13 +15,9 @@ import (
"github.com/pomerium/pomerium/internal/sessions/cookie" "github.com/pomerium/pomerium/internal/sessions/cookie"
"github.com/pomerium/pomerium/internal/urlutil" "github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil" "github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
"github.com/pomerium/pomerium/pkg/hpke" "github.com/pomerium/pomerium/pkg/hpke"
) )
var outboundGRPCConnection = new(grpc.CachedOutboundGRPClientConn)
type authenticateState struct { type authenticateState struct {
redirectURL *url.URL redirectURL *url.URL
// sharedEncoder is the encoder to use to serialize data to be consumed // sharedEncoder is the encoder to use to serialize data to be consumed
@ -44,8 +39,6 @@ type authenticateState struct {
hpkePrivateKey *hpke.PrivateKey hpkePrivateKey *hpke.PrivateKey
jwk *jose.JSONWebKeySet jwk *jose.JSONWebKeySet
dataBrokerClient databroker.DataBrokerServiceClient
} }
func newAuthenticateState() *authenticateState { func newAuthenticateState() *authenticateState {
@ -141,17 +134,5 @@ func newAuthenticateStateFromConfig(cfg *config.Config) (*authenticateState, err
state.hpkePrivateKey = hpke.DerivePrivateKey(sharedKey) state.hpkePrivateKey = hpke.DerivePrivateKey(sharedKey)
dataBrokerConn, err := outboundGRPCConnection.Get(context.Background(), &grpc.OutboundOptions{
OutboundPort: cfg.OutboundPort,
InstallationID: cfg.Options.InstallationID,
ServiceName: cfg.Options.Services,
SignedJWTKey: sharedKey,
})
if err != nil {
return nil, err
}
state.dataBrokerClient = databroker.NewDataBrokerServiceClient(dataBrokerConn)
return state, nil return state, nil
} }