From 83a8afba2b01507135cc9d557e7833658f61b410 Mon Sep 17 00:00:00 2001 From: joerger Date: Fri, 22 Mar 2024 11:27:34 -0700 Subject: [PATCH] Move types.NewWebSessionRequest to lib/auth/sessions.go. Move related methods to lib/auth/sessions.go. --- api/types/session.go | 52 ----------- lib/auth/auth.go | 139 --------------------------- lib/auth/sessions.go | 217 +++++++++++++++++++++++++++++++++++++++---- 3 files changed, 200 insertions(+), 208 deletions(-) diff --git a/api/types/session.go b/api/types/session.go index 53fddb6fa8930..d5036167d35da 100644 --- a/api/types/session.go +++ b/api/types/session.go @@ -23,9 +23,6 @@ import ( "github.com/gogo/protobuf/proto" "github.com/gravitational/trace" - - "github.com/gravitational/teleport/api/defaults" - "github.com/gravitational/teleport/api/utils/keys" ) // WebSessionsGetter provides access to web sessions @@ -615,55 +612,6 @@ func (r *WebTokenV3) String() string { r.GetKind(), r.GetUser(), r.GetToken(), r.Expiry()) } -// CheckAndSetDefaults validates the request and sets defaults. -func (r *NewWebSessionRequest) CheckAndSetDefaults() error { - if r.User == "" { - return trace.BadParameter("user name required") - } - if len(r.Roles) == 0 { - return trace.BadParameter("roles required") - } - if len(r.Traits) == 0 { - return trace.BadParameter("traits required") - } - if r.SessionTTL == 0 { - r.SessionTTL = defaults.CertDuration - } - return nil -} - -// NewWebSessionRequest defines a request to create a new user -// web session -// TODO (Joerger): Remove this and replace it with lib/auth.NewWebSessionRequest -// once /e is no longer dependent on this. -type NewWebSessionRequest struct { - // User specifies the user this session is bound to - User string - // LoginIP is an observed IP of the client, it will be embedded into certificates. - LoginIP string - // Roles optionally lists additional user roles - Roles []string - // Traits optionally lists role traits - Traits map[string][]string - // SessionTTL optionally specifies the session time-to-live. - // If left unspecified, the default certificate duration is used. - SessionTTL time.Duration - // LoginTime is the time that this user recently logged in. - LoginTime time.Time - // AccessRequests contains the UUIDs of the access requests currently in use. - AccessRequests []string - // RequestedResourceIDs optionally lists requested resources - RequestedResourceIDs []ResourceID - // AttestWebSession optionally attests the web session to meet private key policy requirements. - // This should only be set to true for web sessions that are purely in the purview of the Proxy - // and Auth services. Users should never have direct access to attested web sessions. - AttestWebSession bool - // PrivateKey is a specific private key to use when generating the web sessions' certificates. - // This should be provided when extending an attested web session in order to maintain the - // session attested status. - PrivateKey *keys.PrivateKey -} - // Check validates the request. func (r *GetWebSessionRequest) Check() error { if r.User == "" { diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 0a0aca32f5350..a4999cb6a3ff8 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -4606,128 +4606,6 @@ func (a *Server) GetTokens(ctx context.Context, opts ...services.MarshalOption) return tokens, nil } -// newWebSessionOpts are WebSession creation options exclusive to Auth. -// These options complement [types.NewWebSessionRequest]. -// See [Server.newWebSession]. -type newWebSessionOpts struct { - // deviceExtensions are the device extensions to apply to the session. - // Only present on renewals, the original extensions are applied by - // [Server.AugmentWebSessionCertificates]. - deviceExtensions *tlsca.DeviceExtensions -} - -// newWebSession creates and returns a new web session for the specified request -func (a *Server) newWebSession( - ctx context.Context, - req NewWebSessionRequest, - opts *newWebSessionOpts, -) (types.WebSession, error) { - userState, err := a.GetUserOrLoginState(ctx, req.User) - if err != nil { - return nil, trace.Wrap(err) - } - if req.LoginIP == "" { - // TODO(antonam): consider turning this into error after all use cases are covered (before v14.0 testplan) - log.Debug("Creating new web session without login IP specified.") - } - clusterName, err := a.GetClusterName() - if err != nil { - return nil, trace.Wrap(err) - } - checker, err := services.NewAccessChecker(&services.AccessInfo{ - Roles: req.Roles, - Traits: req.Traits, - AllowedResourceIDs: req.RequestedResourceIDs, - }, clusterName.GetClusterName(), a) - if err != nil { - return nil, trace.Wrap(err) - } - - netCfg, err := a.GetClusterNetworkingConfig(ctx) - if err != nil { - return nil, trace.Wrap(err) - } - - if req.PrivateKey == nil { - req.PrivateKey, err = native.GeneratePrivateKey() - if err != nil { - return nil, trace.Wrap(err) - } - } - - sessionTTL := req.SessionTTL - if sessionTTL == 0 { - sessionTTL = checker.AdjustSessionTTL(apidefaults.CertDuration) - } - - if req.AttestWebSession { - // Upsert web session attestation data so that this key's certs - // will be marked with the web session private key policy. - webAttData, err := services.NewWebSessionAttestationData(req.PrivateKey.Public()) - if err != nil { - return nil, trace.Wrap(err) - } - if err = a.UpsertKeyAttestationData(ctx, webAttData, sessionTTL); err != nil { - return nil, trace.Wrap(err) - } - } - - certReq := certRequest{ - user: userState, - loginIP: req.LoginIP, - ttl: sessionTTL, - publicKey: req.PrivateKey.MarshalSSHPublicKey(), - checker: checker, - traits: req.Traits, - activeRequests: services.RequestIDs{AccessRequests: req.AccessRequests}, - } - var hasDeviceExtensions bool - if opts != nil && opts.deviceExtensions != nil { - // Apply extensions to request. - certReq.deviceExtensions = DeviceExtensions(*opts.deviceExtensions) - hasDeviceExtensions = true - } - - certs, err := a.generateUserCert(ctx, certReq) - if err != nil { - return nil, trace.Wrap(err) - } - token, err := utils.CryptoRandomHex(defaults.SessionTokenBytes) - if err != nil { - return nil, trace.Wrap(err) - } - bearerToken, err := utils.CryptoRandomHex(defaults.SessionTokenBytes) - if err != nil { - return nil, trace.Wrap(err) - } - bearerTokenTTL := min(sessionTTL, defaults.BearerTokenTTL) - - startTime := a.clock.Now() - if !req.LoginTime.IsZero() { - startTime = req.LoginTime - } - - sessionSpec := types.WebSessionSpecV2{ - User: req.User, - Priv: req.PrivateKey.PrivateKeyPEM(), - Pub: certs.SSH, - TLSCert: certs.TLS, - Expires: startTime.UTC().Add(sessionTTL), - BearerToken: bearerToken, - BearerTokenExpires: startTime.UTC().Add(bearerTokenTTL), - LoginTime: req.LoginTime, - IdleTimeout: types.Duration(netCfg.GetWebIdleTimeout()), - HasDeviceExtensions: hasDeviceExtensions, - } - UserLoginCount.Inc() - - sess, err := types.NewWebSession(token, types.KindWebSession, sessionSpec) - if err != nil { - return nil, trace.Wrap(err) - } - return sess, nil -} - // GetWebSessionInfo returns the web session specified with sessionID for the given user. // The session is stripped of any authentication details. // Implements auth.WebUIService @@ -6515,23 +6393,6 @@ func (a *Server) ValidateMFAAuthResponse(ctx context.Context, resp *proto.MFAAut } } -func (a *Server) upsertWebSession(ctx context.Context, session types.WebSession) error { - if err := a.WebSessions().Upsert(ctx, session); err != nil { - return trace.Wrap(err) - } - token, err := types.NewWebToken(session.GetBearerTokenExpiryTime(), types.WebTokenSpecV3{ - User: session.GetUser(), - Token: session.GetBearerToken(), - }) - if err != nil { - return trace.Wrap(err) - } - if err := a.WebTokens().Upsert(ctx, token); err != nil { - return trace.Wrap(err) - } - return nil -} - func mergeKeySets(a, b types.CAKeySet) types.CAKeySet { newKeySet := a.Clone() newKeySet.SSH = append(newKeySet.SSH, b.SSH...) diff --git a/lib/auth/sessions.go b/lib/auth/sessions.go index 84ef34a7ebe88..b4972a60a5660 100644 --- a/lib/auth/sessions.go +++ b/lib/auth/sessions.go @@ -28,6 +28,7 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client/proto" + apidefaults "github.com/gravitational/teleport/api/defaults" mfav1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/utils/keys" @@ -41,9 +42,205 @@ import ( "github.com/gravitational/teleport/lib/utils" ) -// TODO (Joerger): Replace this alias with the definition of types.NewWebSessionRequest -// once /e is no longer dependent on types.NewWebSessionRequest. -type NewWebSessionRequest = types.NewWebSessionRequest +// NewWebSessionRequest defines a request to create a new user +// web session +type NewWebSessionRequest struct { + // User specifies the user this session is bound to + User string + // LoginIP is an observed IP of the client, it will be embedded into certificates. + LoginIP string + // Roles optionally lists additional user roles + Roles []string + // Traits optionally lists role traits + Traits map[string][]string + // SessionTTL optionally specifies the session time-to-live. + // If left unspecified, the default certificate duration is used. + SessionTTL time.Duration + // LoginTime is the time that this user recently logged in. + LoginTime time.Time + // AccessRequests contains the UUIDs of the access requests currently in use. + AccessRequests []string + // RequestedResourceIDs optionally lists requested resources + RequestedResourceIDs []types.ResourceID + // AttestWebSession optionally attests the web session to meet private key policy requirements. + // This should only be set to true for web sessions that are purely in the purview of the Proxy + // and Auth services. Users should never have direct access to attested web sessions. + AttestWebSession bool + // PrivateKey is a specific private key to use when generating the web sessions' certificates. + // This should be provided when extending an attested web session in order to maintain the + // session attested status. + PrivateKey *keys.PrivateKey +} + +// CheckAndSetDefaults validates the request and sets defaults. +func (r *NewWebSessionRequest) CheckAndSetDefaults() error { + if r.User == "" { + return trace.BadParameter("user name required") + } + if len(r.Roles) == 0 { + return trace.BadParameter("roles required") + } + if len(r.Traits) == 0 { + return trace.BadParameter("traits required") + } + if r.SessionTTL == 0 { + r.SessionTTL = apidefaults.CertDuration + } + return nil +} + +func (a *Server) CreateWebSessionFromReq(ctx context.Context, req NewWebSessionRequest) (types.WebSession, error) { + session, err := a.newWebSession(ctx, req, nil /* opts */) + if err != nil { + return nil, trace.Wrap(err) + } + + err = a.upsertWebSession(ctx, session) + if err != nil { + return nil, trace.Wrap(err) + } + + return session, nil +} + +// newWebSessionOpts are WebSession creation options exclusive to Auth. +// These options complement [types.NewWebSessionRequest]. +// See [Server.newWebSession]. +type newWebSessionOpts struct { + // deviceExtensions are the device extensions to apply to the session. + // Only present on renewals, the original extensions are applied by + // [Server.AugmentWebSessionCertificates]. + deviceExtensions *tlsca.DeviceExtensions +} + +// newWebSession creates and returns a new web session for the specified request +func (a *Server) newWebSession( + ctx context.Context, + req NewWebSessionRequest, + opts *newWebSessionOpts, +) (types.WebSession, error) { + userState, err := a.GetUserOrLoginState(ctx, req.User) + if err != nil { + return nil, trace.Wrap(err) + } + if req.LoginIP == "" { + // TODO(antonam): consider turning this into error after all use cases are covered (before v14.0 testplan) + log.Debug("Creating new web session without login IP specified.") + } + clusterName, err := a.GetClusterName() + if err != nil { + return nil, trace.Wrap(err) + } + checker, err := services.NewAccessChecker(&services.AccessInfo{ + Roles: req.Roles, + Traits: req.Traits, + AllowedResourceIDs: req.RequestedResourceIDs, + }, clusterName.GetClusterName(), a) + if err != nil { + return nil, trace.Wrap(err) + } + + netCfg, err := a.GetClusterNetworkingConfig(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if req.PrivateKey == nil { + req.PrivateKey, err = native.GeneratePrivateKey() + if err != nil { + return nil, trace.Wrap(err) + } + } + + sessionTTL := req.SessionTTL + if sessionTTL == 0 { + sessionTTL = checker.AdjustSessionTTL(apidefaults.CertDuration) + } + + if req.AttestWebSession { + // Upsert web session attestation data so that this key's certs + // will be marked with the web session private key policy. + webAttData, err := services.NewWebSessionAttestationData(req.PrivateKey.Public()) + if err != nil { + return nil, trace.Wrap(err) + } + if err = a.UpsertKeyAttestationData(ctx, webAttData, sessionTTL); err != nil { + return nil, trace.Wrap(err) + } + } + + certReq := certRequest{ + user: userState, + loginIP: req.LoginIP, + ttl: sessionTTL, + publicKey: req.PrivateKey.MarshalSSHPublicKey(), + checker: checker, + traits: req.Traits, + activeRequests: services.RequestIDs{AccessRequests: req.AccessRequests}, + } + var hasDeviceExtensions bool + if opts != nil && opts.deviceExtensions != nil { + // Apply extensions to request. + certReq.deviceExtensions = DeviceExtensions(*opts.deviceExtensions) + hasDeviceExtensions = true + } + + certs, err := a.generateUserCert(ctx, certReq) + if err != nil { + return nil, trace.Wrap(err) + } + token, err := utils.CryptoRandomHex(defaults.SessionTokenBytes) + if err != nil { + return nil, trace.Wrap(err) + } + bearerToken, err := utils.CryptoRandomHex(defaults.SessionTokenBytes) + if err != nil { + return nil, trace.Wrap(err) + } + bearerTokenTTL := min(sessionTTL, defaults.BearerTokenTTL) + + startTime := a.clock.Now() + if !req.LoginTime.IsZero() { + startTime = req.LoginTime + } + + sessionSpec := types.WebSessionSpecV2{ + User: req.User, + Priv: req.PrivateKey.PrivateKeyPEM(), + Pub: certs.SSH, + TLSCert: certs.TLS, + Expires: startTime.UTC().Add(sessionTTL), + BearerToken: bearerToken, + BearerTokenExpires: startTime.UTC().Add(bearerTokenTTL), + LoginTime: req.LoginTime, + IdleTimeout: types.Duration(netCfg.GetWebIdleTimeout()), + HasDeviceExtensions: hasDeviceExtensions, + } + UserLoginCount.Inc() + + sess, err := types.NewWebSession(token, types.KindWebSession, sessionSpec) + if err != nil { + return nil, trace.Wrap(err) + } + return sess, nil +} + +func (a *Server) upsertWebSession(ctx context.Context, session types.WebSession) error { + if err := a.WebSessions().Upsert(ctx, session); err != nil { + return trace.Wrap(err) + } + token, err := types.NewWebToken(session.GetBearerTokenExpiryTime(), types.WebTokenSpecV3{ + User: session.GetUser(), + Token: session.GetBearerToken(), + }) + if err != nil { + return trace.Wrap(err) + } + if err := a.WebTokens().Upsert(ctx, token); err != nil { + return trace.Wrap(err) + } + return nil +} // CreateAppSession creates and inserts a services.WebSession into the // backend with the identity of the caller used to generate the certificate. @@ -269,20 +466,6 @@ func (a *Server) generateAppToken(ctx context.Context, username string, roles [] return token, nil } -func (a *Server) CreateWebSessionFromReq(ctx context.Context, req NewWebSessionRequest) (types.WebSession, error) { - session, err := a.newWebSession(ctx, req, nil /* opts */) - if err != nil { - return nil, trace.Wrap(err) - } - - err = a.upsertWebSession(ctx, session) - if err != nil { - return nil, trace.Wrap(err) - } - - return session, nil -} - func (a *Server) CreateSessionCert(user services.UserState, sessionTTL time.Duration, publicKey []byte, compatibility, routeToCluster, kubernetesCluster, loginIP string, attestationReq *keys.AttestationStatement) ([]byte, []byte, error) { // It's safe to extract the access info directly from services.User because // this occurs during the initial login before the first certs have been