2020-09-16 12:02:02 -04:00
package session
import (
"context"
"crypto/ed25519"
2020-09-19 00:18:17 -04:00
"crypto/subtle"
2020-09-16 12:02:02 -04:00
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
2020-09-19 21:05:56 -04:00
"github.com/hashicorp/boundary/internal/db/timestamp"
2020-11-05 16:54:03 -05:00
"github.com/hashicorp/boundary/internal/errors"
2020-09-16 12:02:02 -04:00
"github.com/hashicorp/boundary/internal/kms"
wrapping "github.com/hashicorp/go-kms-wrapping"
2020-09-20 12:51:08 -04:00
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
2020-09-16 12:02:02 -04:00
)
// CreateSession inserts into the repository and returns the new Session with
// its State of "Pending". The following fields must be empty when creating a
// session: ServerId, ServerType, and PublicId. No options are
// currently supported.
2021-02-03 16:25:56 -08:00
func ( r * Repository ) CreateSession ( ctx context . Context , sessionWrapper wrapping . Wrapper , newSession * Session , _ ... Option ) ( * Session , ed25519 . PrivateKey , error ) {
const op = "session.(Repository).CreateSession"
2020-09-16 12:02:02 -04:00
if newSession == nil {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing session" )
2020-09-16 12:02:02 -04:00
}
if newSession . PublicId != "" {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "public id is not empty" )
2020-09-16 12:02:02 -04:00
}
if len ( newSession . Certificate ) != 0 {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "certificate is not empty" )
2020-09-16 12:02:02 -04:00
}
if newSession . TargetId == "" {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing target id" )
2020-09-16 12:02:02 -04:00
}
if newSession . HostId == "" {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing host id" )
2020-09-16 12:02:02 -04:00
}
if newSession . UserId == "" {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing user id" )
2020-09-16 12:02:02 -04:00
}
if newSession . HostSetId == "" {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing host set id" )
2020-09-16 12:02:02 -04:00
}
if newSession . AuthTokenId == "" {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing auth token id" )
2020-09-16 12:02:02 -04:00
}
if newSession . ScopeId == "" {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing scope id" )
2020-09-16 12:02:02 -04:00
}
if newSession . ServerId != "" {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "server id is not empty" )
2020-09-16 12:02:02 -04:00
}
if newSession . ServerType != "" {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "server type is not empty" )
2020-09-16 12:02:02 -04:00
}
if newSession . CtTofuToken != nil {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "ct is not empty" )
2020-09-16 12:02:02 -04:00
}
if newSession . TofuToken != nil {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "tofu token is not empty" )
2020-09-16 12:02:02 -04:00
}
2020-09-19 10:31:49 -04:00
if newSession . ExpirationTime == nil || newSession . ExpirationTime . Timestamp . AsTime (). IsZero () {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing expiration time" )
2020-09-19 10:31:49 -04:00
}
2020-09-16 12:02:02 -04:00
id , err := newId ()
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
2020-09-19 10:31:49 -04:00
privKey , certBytes , err := newCert ( sessionWrapper , newSession . UserId , id , newSession . ExpirationTime . Timestamp . AsTime ())
2020-09-16 12:02:02 -04:00
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
newSession . Certificate = certBytes
newSession . PublicId = id
2021-03-03 11:23:00 -05:00
newSession . KeyId = sessionWrapper . KeyID ()
2020-09-16 12:02:02 -04:00
var returnedSession * Session
_ , err = r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( read db . Reader , w db . Writer ) error {
returnedSession = newSession . Clone ().( * Session )
2021-06-22 22:26:26 -04:00
returnedSession . DynamicCredentials = nil
2020-09-16 12:02:02 -04:00
if err = w . Create ( ctx , returnedSession ); err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
2021-06-22 22:26:26 -04:00
for _ , cred := range newSession . DynamicCredentials {
cred . SessionId = newSession . PublicId
returnedCred := cred . clone ()
if err = w . Create ( ctx , returnedCred ); err != nil {
return errors . Wrap ( err , op )
}
returnedSession . DynamicCredentials = append ( returnedSession . DynamicCredentials , returnedCred )
}
2020-09-16 12:02:02 -04:00
var foundStates [] * State
// trigger will create new "Pending" state
if foundStates , err = fetchStates ( ctx , read , returnedSession . PublicId ); err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
if len ( foundStates ) != 1 {
2021-02-03 16:25:56 -08:00
return errors . New ( errors . MultipleRecords , op , fmt . Sprintf ( "%d states found for new session %s" , len ( foundStates ), returnedSession . PublicId ))
2020-09-16 12:02:02 -04:00
}
2020-09-20 15:54:39 -04:00
if len ( foundStates ) == 0 {
2021-02-03 16:25:56 -08:00
return errors . New ( errors . SessionNotFound , op , fmt . Sprintf ( "no states found for new session %s" , returnedSession . PublicId ))
2020-09-20 15:54:39 -04:00
}
returnedSession . States = foundStates
if returnedSession . States [ 0 ]. Status != StatusPending {
2021-02-03 16:25:56 -08:00
return errors . New ( errors . InvalidSessionState , op , fmt . Sprintf ( "new session %s state is not valid: %s" , returnedSession . PublicId , returnedSession . States [ 0 ]. Status ))
2020-09-16 12:02:02 -04:00
}
return nil
},
)
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
2021-02-03 16:25:56 -08:00
return returnedSession , privKey , nil
2020-09-16 12:02:02 -04:00
}
// LookupSession will look up a session in the repository and return the session
// with its states. Returned States are ordered by start time descending. If the
// session is not found, it will return nil, nil, nil. No options are currently
// supported.
2021-02-03 16:25:56 -08:00
func ( r * Repository ) LookupSession ( ctx context . Context , sessionId string , _ ... Option ) ( * Session , * ConnectionAuthzSummary , error ) {
const op = "session.(Repository).LookupSession"
2020-09-16 12:02:02 -04:00
if sessionId == "" {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing session id" )
2020-09-16 12:02:02 -04:00
}
session := AllocSession ()
session . PublicId = sessionId
_ , err := r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( read db . Reader , w db . Writer ) error {
if err := read . LookupById ( ctx , & session ); err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , sessionId )))
2020-09-16 12:02:02 -04:00
}
2020-09-20 15:54:39 -04:00
states , err := fetchStates ( ctx , read , sessionId , db . WithOrder ( "start_time desc" ))
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
2020-09-20 15:54:39 -04:00
session . States = states
2021-06-22 22:26:26 -04:00
var creds [] * DynamicCredential
if err := read . SearchWhere ( ctx , & creds , "session_id = ?" , [] interface {}{ sessionId }); err != nil {
return errors . Wrap ( err , op )
}
if len ( creds ) > 0 {
session . DynamicCredentials = creds
}
2020-09-16 12:02:02 -04:00
return nil
},
)
if err != nil {
2020-12-21 10:07:34 -08:00
if errors . IsNotFoundError ( err ) {
2020-09-20 21:18:07 -04:00
return nil , nil , nil
2020-09-16 12:02:02 -04:00
}
2021-02-03 16:25:56 -08:00
return nil , nil , errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
if len ( session . CtTofuToken ) > 0 {
2021-03-04 20:45:27 -05:00
databaseWrapper , err := r . kms . GetWrapper ( ctx , session . ScopeId , kms . KeyPurposeDatabase )
2020-09-16 12:02:02 -04:00
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . Wrap ( err , op , errors . WithMsg ( "unable to get database wrapper" ))
2020-09-16 12:02:02 -04:00
}
if err := session . decrypt ( ctx , databaseWrapper ); err != nil {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . Wrap ( err , op , errors . WithMsg ( "cannot decrypt session value" ))
2020-09-16 12:02:02 -04:00
}
} else {
session . CtTofuToken = nil
}
2020-09-20 15:54:39 -04:00
2020-09-20 21:18:07 -04:00
authzSummary , err := r . sessionAuthzSummary ( ctx , sessionId )
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . Wrap ( err , op , errors . WithMsg ( "failed to get authz summary" ))
2020-09-20 21:18:07 -04:00
}
return & session , authzSummary , nil
2020-09-16 12:02:02 -04:00
}
2021-05-14 12:23:12 -05:00
// ListSessions will sessions. Supports the WithLimit, WithScopeId, WithSessionIds, and WithServerId options.
2020-09-16 12:02:02 -04:00
func ( r * Repository ) ListSessions ( ctx context . Context , opt ... Option ) ([] * Session , error ) {
2021-02-03 16:25:56 -08:00
const op = "session.(Repository).ListSessions"
2020-09-16 12:02:02 -04:00
opts := getOpts ( opt ... )
var where [] string
var args [] interface {}
2020-09-20 15:54:39 -04:00
inClauseCnt := 0
2021-02-02 13:37:32 -07:00
if len ( opts . withScopeIds ) != 0 {
2021-01-25 19:37:40 -05:00
switch len ( opts . withScopeIds ) {
case 1 :
inClauseCnt += 1
where , args = append ( where , fmt . Sprintf ( "scope_id = $%d" , inClauseCnt )), append ( args , opts . withScopeIds [ 0 ])
default :
idsInClause := make ([] string , 0 , len ( opts . withScopeIds ))
for _ , id := range opts . withScopeIds {
inClauseCnt += 1
idsInClause , args = append ( idsInClause , fmt . Sprintf ( "$%d" , inClauseCnt )), append ( args , id )
}
where = append ( where , fmt . Sprintf ( "scope_id in (%s)" , strings . Join ( idsInClause , "," )))
}
2021-02-02 13:37:32 -07:00
}
if opts . withUserId != "" {
2020-09-20 15:54:39 -04:00
inClauseCnt += 1
where , args = append ( where , fmt . Sprintf ( "user_id = $%d" , inClauseCnt )), append ( args , opts . withUserId )
}
2020-09-22 10:41:42 -04:00
if len ( opts . withSessionIds ) > 0 {
idsInClause := make ([] string , 0 , len ( opts . withSessionIds ))
for _ , id := range opts . withSessionIds {
inClauseCnt += 1
idsInClause , args = append ( idsInClause , fmt . Sprintf ( "$%d" , inClauseCnt )), append ( args , id )
}
2021-01-25 19:37:40 -05:00
where = append ( where , fmt . Sprintf ( "s.public_id in (%s)" , strings . Join ( idsInClause , "," )))
2020-09-22 10:41:42 -04:00
}
2021-05-14 12:23:12 -05:00
if opts . withServerId != "" {
inClauseCnt += 1
where , args = append ( where , fmt . Sprintf ( "server_id = $%d" , inClauseCnt )), append ( args , opts . withServerId )
}
2020-09-22 10:41:42 -04:00
2020-09-20 15:54:39 -04:00
var limit string
switch {
case opts . withLimit < 0 : // any negative number signals unlimited results
case opts . withLimit == 0 : // zero signals the default value and default limits
limit = fmt . Sprintf ( "limit %d" , r . defaultLimit )
default :
// non-zero signals an override of the default limit for the repo.
limit = fmt . Sprintf ( "limit %d" , opts . withLimit )
}
2021-04-02 19:13:48 -04:00
var withOrder string
switch opts . withOrderByCreateTime {
case db . AscendingOrderBy :
withOrder = "order by create_time asc"
case db . DescendingOrderBy :
withOrder = "order by create_time"
2020-09-20 15:54:39 -04:00
}
var whereClause string
if len ( where ) > 0 {
2021-02-02 13:37:32 -07:00
whereClause = " and " + strings . Join ( where , " and " )
2020-09-16 12:02:02 -04:00
}
2020-09-20 15:54:39 -04:00
q := sessionList
2021-04-02 19:13:48 -04:00
query := fmt . Sprintf ( q , limit , whereClause , withOrder )
2020-09-16 12:02:02 -04:00
2020-09-30 18:11:59 -04:00
rows , err := r . reader . Query ( ctx , query , args )
2020-09-20 15:54:39 -04:00
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
2020-09-20 15:54:39 -04:00
defer rows . Close ()
var sessionsWithState [] * sessionView
for rows . Next () {
var s sessionView
if err := r . reader . ScanRows ( rows , & s ); err != nil {
2021-02-03 16:25:56 -08:00
return nil , errors . Wrap ( err , op , errors . WithMsg ( "scan row failed" ))
2020-09-20 15:54:39 -04:00
}
sessionsWithState = append ( sessionsWithState , & s )
}
sessions , err := r . convertToSessions ( ctx , sessionsWithState , withListingConvert ( true ))
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
return sessions , nil
}
// DeleteSession will delete a session from the repository.
2021-02-03 16:25:56 -08:00
func ( r * Repository ) DeleteSession ( ctx context . Context , publicId string , _ ... Option ) ( int , error ) {
const op = "session.(Repository).DeleteSession"
2020-09-16 12:02:02 -04:00
if publicId == "" {
2021-02-03 16:25:56 -08:00
return db . NoRowsAffected , errors . New ( errors . InvalidParameter , op , "missing public id" )
2020-09-16 12:02:02 -04:00
}
session := AllocSession ()
session . PublicId = publicId
if err := r . reader . LookupByPublicId ( ctx , & session ); err != nil {
2021-02-03 16:25:56 -08:00
return db . NoRowsAffected , errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , publicId )))
2020-09-16 12:02:02 -04:00
}
var rowsDeleted int
_ , err := r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( _ db . Reader , w db . Writer ) error {
deleteSession := session . Clone ()
var err error
rowsDeleted , err = w . Delete (
ctx ,
deleteSession ,
)
2021-02-03 16:25:56 -08:00
if err != nil {
return errors . Wrap ( err , op )
}
if rowsDeleted > 1 {
2020-09-16 12:02:02 -04:00
// return err, which will result in a rollback of the delete
2021-02-03 16:25:56 -08:00
return errors . New ( errors . MultipleRecords , op , "more than 1 resource would have been deleted" )
2020-09-16 12:02:02 -04:00
}
2021-02-03 16:25:56 -08:00
return nil
2020-09-16 12:02:02 -04:00
},
)
if err != nil {
2021-02-03 16:25:56 -08:00
return db . NoRowsAffected , errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , publicId )))
2020-09-16 12:02:02 -04:00
}
return rowsDeleted , nil
}
2020-09-21 16:33:28 -04:00
// CancelSession sets a session's state to "canceling" in the repo. It's called
// when the user cancels a session and the controller wants to update the
// session state to "canceling" for the given reason, so the workers can get the
// "canceling signal" during their next status heartbeat. CancelSession is
2020-09-21 11:14:30 -04:00
// idempotent.
2020-09-20 15:54:39 -04:00
func ( r * Repository ) CancelSession ( ctx context . Context , sessionId string , sessionVersion uint32 ) ( * Session , error ) {
2021-02-03 16:25:56 -08:00
const op = "session.(Repository).CancelSession"
2020-09-18 18:12:41 -04:00
if sessionId == "" {
2021-02-03 16:25:56 -08:00
return nil , errors . New ( errors . InvalidParameter , op , "missing session id" )
2020-09-16 12:02:02 -04:00
}
2020-09-18 18:12:41 -04:00
if sessionVersion == 0 {
2021-02-03 16:25:56 -08:00
return nil , errors . New ( errors . InvalidParameter , op , "missing session version" )
2020-09-16 12:02:02 -04:00
}
2020-09-21 16:33:28 -04:00
s , ss , err := r . updateState ( ctx , sessionId , sessionVersion , StatusCanceling )
2020-09-18 18:12:41 -04:00
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
2020-09-20 15:54:39 -04:00
s . States = ss
return s , nil
2020-09-18 18:12:41 -04:00
}
2020-09-16 12:02:02 -04:00
2020-10-01 10:13:57 -04:00
// TerminateSession sets a session's termination reason and it's state to
// "terminated" Sessions cannot be terminated which still have connections that
// are not closed.
2020-09-20 15:54:39 -04:00
func ( r * Repository ) TerminateSession ( ctx context . Context , sessionId string , sessionVersion uint32 , reason TerminationReason ) ( * Session , error ) {
2021-02-03 16:25:56 -08:00
const op = "session.(Repository).TerminateSession"
2020-09-18 18:12:41 -04:00
if sessionId == "" {
2021-02-03 16:25:56 -08:00
return nil , errors . New ( errors . InvalidParameter , op , "missing session id" )
2020-09-16 12:02:02 -04:00
}
2020-09-18 18:12:41 -04:00
if sessionVersion == 0 {
2021-02-03 16:25:56 -08:00
return nil , errors . New ( errors . InvalidParameter , op , "missing session version" )
2020-09-16 12:02:02 -04:00
}
2020-09-18 18:12:41 -04:00
updatedSession := AllocSession ()
updatedSession . PublicId = sessionId
updatedSession . TerminationReason = reason . String ()
_ , err := r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( reader db . Reader , w db . Writer ) error {
2020-09-30 18:11:59 -04:00
rowsAffected , err := w . Exec ( ctx , terminateSessionCte , [] interface {}{ sessionId , sessionVersion })
2020-09-18 18:12:41 -04:00
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "unable to terminate session %s" , sessionId )))
2020-09-18 18:12:41 -04:00
}
if rowsAffected == 0 {
2021-02-03 16:25:56 -08:00
return errors . New ( errors . InvalidSessionState , op , fmt . Sprintf ( "unable to terminate session %s" , sessionId ))
2020-09-18 18:12:41 -04:00
}
rowsUpdated , err := w . Update ( ctx , & updatedSession , [] string { "TerminationReason" }, nil , db . WithVersion ( & sessionVersion ))
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , sessionId )))
2020-09-18 18:12:41 -04:00
}
if rowsUpdated != 1 {
2021-02-03 16:25:56 -08:00
return errors . New ( errors . MultipleRecords , op , fmt . Sprintf ( "update to session %s would have updated %d session" , updatedSession . PublicId , rowsUpdated ))
2020-09-18 18:12:41 -04:00
}
2020-09-20 15:54:39 -04:00
states , err := fetchStates ( ctx , reader , sessionId , db . WithOrder ( "start_time desc" ))
2020-09-18 18:12:41 -04:00
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-09-18 18:12:41 -04:00
}
2020-09-20 15:54:39 -04:00
updatedSession . States = states
2020-09-18 18:12:41 -04:00
return nil
2020-09-16 12:02:02 -04:00
},
)
2020-09-18 18:12:41 -04:00
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
2020-09-20 15:54:39 -04:00
return & updatedSession , nil
2020-09-18 18:12:41 -04:00
}
2020-09-16 12:02:02 -04:00
2020-10-01 10:13:57 -04:00
// TerminateCompletedSessions will terminate sessions in the repo based on:
// * sessions that have exhausted their connection limit and all their connections are closed.
// * sessions that are expired and all their connections are closed.
// * sessions that are canceling and all their connections are closed
// This function should called on a periodic basis a Controllers via it's
// "ticker" pattern.
func ( r * Repository ) TerminateCompletedSessions ( ctx context . Context ) ( int , error ) {
2021-02-03 16:25:56 -08:00
const op = "session.(Repository).TerminateCompletedSessions"
2020-10-01 10:13:57 -04:00
var rowsAffected int
_ , err := r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( reader db . Reader , w db . Writer ) error {
var err error
rowsAffected , err = w . Exec ( ctx , termSessionsUpdate , nil )
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-10-01 10:13:57 -04:00
}
return nil
},
)
if err != nil {
2021-02-03 16:25:56 -08:00
return db . NoRowsAffected , errors . Wrap ( err , op )
2020-10-01 10:13:57 -04:00
}
return rowsAffected , nil
}
2020-09-19 14:21:09 -04:00
// AuthorizeConnection will check to see if a connection is allowed. Currently,
// that authorization checks:
2020-09-19 21:05:56 -04:00
// * the hasn't expired based on the session.Expiration
// * number of connections already created is less than session.ConnectionLimit
2020-09-19 14:21:09 -04:00
// If authorization is success, it creates/stores a new connection in the repo
2021-05-14 12:23:12 -05:00
// and returns it, along with its states. If the authorization fails, it
2021-03-05 06:34:06 -08:00
// an error with Code InvalidSessionState.
2021-05-14 12:23:12 -05:00
func ( r * Repository ) AuthorizeConnection ( ctx context . Context , sessionId , workerId string ) ( * Connection , [] * ConnectionState , * ConnectionAuthzSummary , error ) {
2021-02-03 16:25:56 -08:00
const op = "session.(Repository).AuthorizeConnection"
2020-09-19 14:21:09 -04:00
if sessionId == "" {
2021-03-05 06:34:06 -08:00
return nil , nil , nil , errors . Wrap ( status . Error ( codes . FailedPrecondition , "missing session id" ), op , errors . WithCode ( errors . InvalidParameter ))
2020-09-18 18:12:41 -04:00
}
connectionId , err := newConnectionId ()
if err != nil {
2021-03-05 06:34:06 -08:00
return nil , nil , nil , errors . Wrap ( err , op )
2020-09-18 18:12:41 -04:00
}
connection := AllocConnection ()
connection . PublicId = connectionId
var connectionStates [] * ConnectionState
_ , err = r . writer . DoTx (
2020-09-16 12:02:02 -04:00
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( reader db . Reader , w db . Writer ) error {
2021-05-14 12:23:12 -05:00
rowsAffected , err := w . Exec ( ctx , authorizeConnectionCte , [] interface {}{ sessionId , connectionId , workerId })
2020-09-16 12:02:02 -04:00
if err != nil {
2021-03-05 06:34:06 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "unable to authorize connection %s" , sessionId )))
2020-09-16 12:02:02 -04:00
}
2020-09-18 18:12:41 -04:00
if rowsAffected == 0 {
2021-03-05 06:34:06 -08:00
return errors . Wrap ( status . Errorf ( codes . PermissionDenied , "session %s is not authorized (not active, expired or connection limit reached)" , sessionId ), op , errors . WithCode ( errors . InvalidSessionState ))
2020-09-18 18:12:41 -04:00
}
if err := reader . LookupById ( ctx , & connection ); err != nil {
2021-03-05 06:34:06 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "failed for session %s" , sessionId )))
2020-09-16 12:02:02 -04:00
}
2020-09-18 18:12:41 -04:00
connectionStates , err = fetchConnectionStates ( ctx , reader , connectionId , db . WithOrder ( "start_time desc" ))
2020-09-16 12:02:02 -04:00
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
return nil
},
)
2020-09-19 14:21:09 -04:00
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , nil , nil , errors . Wrap ( err , op )
2020-09-19 14:21:09 -04:00
}
2020-09-19 21:05:56 -04:00
authzSummary , err := r . sessionAuthzSummary ( ctx , connection . SessionId )
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , nil , nil , errors . Wrap ( err , op )
2020-09-19 21:05:56 -04:00
}
return & connection , connectionStates , authzSummary , nil
}
type ConnectionAuthzSummary struct {
ExpirationTime * timestamp . Timestamp
2020-09-20 15:53:49 -04:00
ConnectionLimit int32
2020-09-19 21:05:56 -04:00
CurrentConnectionCount uint32
}
func ( r * Repository ) sessionAuthzSummary ( ctx context . Context , sessionId string ) ( * ConnectionAuthzSummary , error ) {
2021-02-03 16:25:56 -08:00
const op = "session.(Repository).sessionAuthzSummary"
2020-09-30 18:11:59 -04:00
rows , err := r . reader . Query ( ctx , remainingConnectionsCte , [] interface {}{ sessionId })
2020-09-19 21:05:56 -04:00
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , errors . Wrap ( err , op )
2020-09-19 21:05:56 -04:00
}
defer rows . Close ()
var info * ConnectionAuthzSummary
for rows . Next () {
if info != nil {
2021-02-03 16:25:56 -08:00
return nil , errors . New ( errors . MultipleRecords , op , "query returned more than one row" )
2020-09-19 21:05:56 -04:00
}
info = & ConnectionAuthzSummary {}
if err := r . reader . ScanRows ( rows , info ); err != nil {
2021-02-03 16:25:56 -08:00
return nil , errors . Wrap ( err , op , errors . WithMsg ( "scan row failed" ))
2020-09-19 21:05:56 -04:00
}
}
return info , nil
2020-09-19 14:21:09 -04:00
}
2020-09-21 00:09:50 -04:00
// ConnectConnection updates a connection in the repo with a state of "connected".
func ( r * Repository ) ConnectConnection ( ctx context . Context , c ConnectWith ) ( * Connection , [] * ConnectionState , error ) {
2021-02-03 16:25:56 -08:00
const op = "session.(Repository).ConnectConnection"
2020-09-19 14:21:09 -04:00
// ConnectWith.validate will check all the fields...
if err := c . validate (); err != nil {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . Wrap ( err , op )
2020-09-19 14:21:09 -04:00
}
var connection Connection
var connectionStates [] * ConnectionState
_ , err := r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( reader db . Reader , w db . Writer ) error {
connection = AllocConnection ()
connection . PublicId = c . ConnectionId
connection . ClientTcpAddress = c . ClientTcpAddress
connection . ClientTcpPort = c . ClientTcpPort
connection . EndpointTcpAddress = c . EndpointTcpAddress
connection . EndpointTcpPort = c . EndpointTcpPort
fieldMask := [] string {
"ClientTcpAddress" ,
"ClientTcpPort" ,
"EndpointTcpAddress" ,
"EndpointTcpPort" ,
}
rowsUpdated , err := w . Update ( ctx , & connection , fieldMask , nil )
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-09-19 14:21:09 -04:00
}
if err == nil && rowsUpdated > 1 {
// return err, which will result in a rollback of the update
2021-02-03 16:25:56 -08:00
return errors . New ( errors . MultipleRecords , op , "more than 1 resource would have been updated" )
2020-09-19 14:21:09 -04:00
}
newState , err := NewConnectionState ( connection . PublicId , StatusConnected )
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-09-19 14:21:09 -04:00
}
if err := w . Create ( ctx , newState ); err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-09-19 14:21:09 -04:00
}
connectionStates , err = fetchConnectionStates ( ctx , reader , c . ConnectionId , db . WithOrder ( "start_time desc" ))
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-09-19 14:21:09 -04:00
}
return nil
},
)
2020-09-16 12:02:02 -04:00
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
2020-09-18 18:12:41 -04:00
return & connection , connectionStates , nil
}
2021-05-04 10:38:33 -04:00
// CloseConnectionResp is just a wrapper for the response from CloseConnections.
2020-09-18 18:12:41 -04:00
// It wraps the connection and its states for each connection closed.
type CloseConnectionResp struct {
Connection * Connection
ConnectionStates [] * ConnectionState
}
// CloseConnections set's a connection's state to "closed" in the repo. It's
// called by a worker after it's closed a connection between the client and the
// endpoint
2021-02-03 16:25:56 -08:00
func ( r * Repository ) CloseConnections ( ctx context . Context , closeWith [] CloseWith , _ ... Option ) ([] CloseConnectionResp , error ) {
const op = "session.(Repository).CloseConnections"
2020-09-18 18:12:41 -04:00
if len ( closeWith ) == 0 {
2021-02-03 16:25:56 -08:00
return nil , errors . New ( errors . InvalidParameter , op , "missing connections" )
2020-09-18 18:12:41 -04:00
}
for _ , cw := range closeWith {
if err := cw . validate (); err != nil {
2021-02-03 16:25:56 -08:00
return nil , errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "%s was invalid" , cw . ConnectionId )))
2020-09-18 18:12:41 -04:00
}
}
var resp [] CloseConnectionResp
_ , err := r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( reader db . Reader , w db . Writer ) error {
for _ , cw := range closeWith {
updateConnection := AllocConnection ()
updateConnection . PublicId = cw . ConnectionId
updateConnection . BytesUp = cw . BytesUp
updateConnection . BytesDown = cw . BytesDown
updateConnection . ClosedReason = cw . ClosedReason . String ()
// updating the ClosedReason will trigger an insert into the
// session_connection_state with a state of closed.
rowsUpdated , err := w . Update (
ctx ,
& updateConnection ,
[] string { "BytesUp" , "BytesDown" , "ClosedReason" },
nil ,
)
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "unable to update connection %s" , cw . ConnectionId )))
2020-09-18 18:12:41 -04:00
}
if rowsUpdated != 1 {
2021-02-03 16:25:56 -08:00
return errors . New ( errors . MultipleRecords , op , fmt . Sprintf ( "%d would have been updated for connection %s" , rowsUpdated , cw . ConnectionId ))
2020-09-18 18:12:41 -04:00
}
states , err := fetchConnectionStates ( ctx , reader , cw . ConnectionId , db . WithOrder ( "start_time desc" ))
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-09-18 18:12:41 -04:00
}
resp = append ( resp , CloseConnectionResp {
Connection : & updateConnection ,
ConnectionStates : states ,
})
}
return nil
},
)
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
2020-09-18 18:12:41 -04:00
return resp , nil
2020-09-16 12:02:02 -04:00
}
// ActivateSession will activate the session and is called by a worker after
2020-09-18 18:12:41 -04:00
// authenticating the session. The session must be in a "pending" state to be
// activated. States are ordered by start time descending. Returns an
2021-02-03 16:25:56 -08:00
// InvalidSessionState error code if a connection cannot be made because the session
2020-09-21 16:33:28 -04:00
// was canceled or terminated.
2020-09-18 18:12:41 -04:00
func ( r * Repository ) ActivateSession ( ctx context . Context , sessionId string , sessionVersion uint32 , serverId , serverType string , tofuToken [] byte ) ( * Session , [] * State , error ) {
2021-02-03 16:25:56 -08:00
const op = "session.(Repository).ActivateSession"
2020-09-16 12:02:02 -04:00
if sessionId == "" {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing session id" )
2020-09-16 12:02:02 -04:00
}
if sessionVersion == 0 {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing version" )
2020-09-18 18:12:41 -04:00
}
if serverId == "" {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing server id" )
2020-09-18 18:12:41 -04:00
}
if serverType == "" {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing server type" )
2020-09-16 12:02:02 -04:00
}
2020-09-19 00:18:17 -04:00
if len ( tofuToken ) == 0 {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing tofu token" )
2020-09-19 00:18:17 -04:00
}
2020-09-16 12:02:02 -04:00
updatedSession := AllocSession ()
updatedSession . PublicId = sessionId
var returnedStates [] * State
_ , err := r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( reader db . Reader , w db . Writer ) error {
2020-09-30 18:11:59 -04:00
rowsAffected , err := w . Exec ( ctx , activateStateCte , [] interface {}{ sessionId , sessionVersion })
2020-09-16 12:02:02 -04:00
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "unable to activate session %s" , sessionId )))
2020-09-16 12:02:02 -04:00
}
if rowsAffected == 0 {
2021-02-03 16:25:56 -08:00
return errors . New ( errors . InvalidSessionState , op , "session is not in a pending state" )
2020-09-16 12:02:02 -04:00
}
foundSession := AllocSession ()
foundSession . PublicId = sessionId
2020-09-18 18:12:41 -04:00
if err := reader . LookupById ( ctx , & foundSession ); err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , sessionId )))
2020-09-16 12:02:02 -04:00
}
databaseWrapper , err := r . kms . GetWrapper ( ctx , foundSession . ScopeId , kms . KeyPurposeDatabase )
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to get database wrapper" ))
2020-09-16 12:02:02 -04:00
}
2020-09-19 00:18:17 -04:00
if len ( foundSession . TofuToken ) > 0 && subtle . ConstantTimeCompare ( foundSession . TofuToken , tofuToken ) != 1 {
2021-02-03 16:25:56 -08:00
return errors . New ( errors . TokenMismatch , op , "tofu token mismatch" )
2020-09-19 00:18:17 -04:00
}
2020-09-16 12:02:02 -04:00
updatedSession . TofuToken = tofuToken
2020-09-18 18:12:41 -04:00
updatedSession . ServerId = serverId
updatedSession . ServerType = serverType
2020-09-16 12:02:02 -04:00
if err := updatedSession . encrypt ( ctx , databaseWrapper ); err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
rowsUpdated , err := w . Update ( ctx , & updatedSession , [] string { "CtTofuToken" }, nil )
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
if err == nil && rowsUpdated > 1 {
// return err, which will result in a rollback of the update
2021-02-03 16:25:56 -08:00
return errors . New ( errors . MultipleRecords , op , "more than 1 resource would have been updated" )
2020-09-16 12:02:02 -04:00
}
returnedStates , err = fetchStates ( ctx , reader , sessionId , db . WithOrder ( "start_time desc" ))
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
return nil
},
)
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
return & updatedSession , returnedStates , nil
}
2020-09-18 18:12:41 -04:00
// updateState will update the session's state using the session id and its
2020-09-21 11:14:30 -04:00
// version. updateState is idempotent. States are ordered by start time
// descending. No options are currently supported.
2021-02-03 16:25:56 -08:00
func ( r * Repository ) updateState ( ctx context . Context , sessionId string , sessionVersion uint32 , s Status , _ ... Option ) ( * Session , [] * State , error ) {
const op = "session.(Repository).updateState"
2020-09-16 12:02:02 -04:00
if sessionId == "" {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing session id" )
2020-09-16 12:02:02 -04:00
}
if sessionVersion == 0 {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing version" )
2020-09-16 12:02:02 -04:00
}
if s == "" {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "missing session status" )
2020-09-16 12:02:02 -04:00
}
if s == StatusActive {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . New ( errors . InvalidParameter , op , "you must call ActivateSession to update a session's state to active" )
2020-09-16 12:02:02 -04:00
}
2020-09-21 11:14:30 -04:00
var rowsAffected int
2020-09-16 12:02:02 -04:00
updatedSession := AllocSession ()
var returnedStates [] * State
2020-09-21 11:14:30 -04:00
_ , err := r . writer . DoTx (
2020-09-16 12:02:02 -04:00
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
func ( reader db . Reader , w db . Writer ) error {
2020-09-21 11:14:30 -04:00
var err error
2020-09-16 12:02:02 -04:00
// We need to update the session version as that's the aggregate
updatedSession . PublicId = sessionId
updatedSession . Version = uint32 ( sessionVersion ) + 1
rowsUpdated , err := w . Update ( ctx , & updatedSession , [] string { "Version" }, nil , db . WithVersion ( & sessionVersion ))
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
if rowsUpdated != 1 {
2021-02-03 16:25:56 -08:00
return errors . New ( errors . MultipleRecords , op , fmt . Sprintf ( "updated session and %d rows updated" , rowsUpdated ))
2020-09-16 12:02:02 -04:00
}
2020-09-21 11:14:30 -04:00
if len ( updatedSession . CtTofuToken ) > 0 {
2021-03-04 20:45:27 -05:00
databaseWrapper , err := r . kms . GetWrapper ( ctx , updatedSession . ScopeId , kms . KeyPurposeDatabase )
2020-09-21 11:14:30 -04:00
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "unable to get database wrapper" ))
2020-09-21 11:14:30 -04:00
}
if err := updatedSession . decrypt ( ctx , databaseWrapper ); err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( "cannot decrypt session value" ))
2020-09-21 11:14:30 -04:00
}
} else {
updatedSession . CtTofuToken = nil
2020-09-16 12:02:02 -04:00
}
2020-09-30 18:11:59 -04:00
rowsAffected , err = w . Exec ( ctx , updateSessionState , [] interface {}{ sessionId , s . String ()})
2020-09-21 11:14:30 -04:00
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op , errors . WithMsg ( fmt . Sprintf ( "unable to update session %s state to %s" , sessionId , s . String ())))
2020-09-21 11:14:30 -04:00
}
if rowsAffected != 0 && rowsAffected != 1 {
2021-02-03 16:25:56 -08:00
return errors . New ( errors . MultipleRecords , op , fmt . Sprintf ( "updated session %s to state %s and %d rows inserted (should be 0 or 1)" , sessionId , s . String (), rowsAffected ))
2020-09-21 11:14:30 -04:00
}
2020-09-16 12:02:02 -04:00
returnedStates , err = fetchStates ( ctx , reader , sessionId , db . WithOrder ( "start_time desc" ))
if err != nil {
2021-02-03 16:25:56 -08:00
return errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
2020-09-21 11:14:30 -04:00
if len ( returnedStates ) < 1 && returnedStates [ 0 ]. Status != s {
2021-02-03 16:25:56 -08:00
return errors . New ( errors . InvalidSessionState , op , fmt . Sprintf ( "failed to update %s to a state of %s" , sessionId , s . String ()))
2020-09-21 11:14:30 -04:00
}
2020-09-16 12:02:02 -04:00
return nil
},
)
if err != nil {
2021-02-03 16:25:56 -08:00
return nil , nil , errors . Wrap ( err , op , errors . WithMsg ( "error creating new state" ))
2020-09-16 12:02:02 -04:00
}
return & updatedSession , returnedStates , nil
}
func fetchStates ( ctx context . Context , r db . Reader , sessionId string , opt ... db . Option ) ([] * State , error ) {
2021-02-03 16:25:56 -08:00
const op = "session.fetchStates"
2020-09-16 12:02:02 -04:00
var states [] * State
if err := r . SearchWhere ( ctx , & states , "session_id = ?" , [] interface {}{ sessionId }, opt ... ); err != nil {
2021-02-03 16:25:56 -08:00
return nil , errors . Wrap ( err , op )
2020-09-16 12:02:02 -04:00
}
if len ( states ) == 0 {
return nil , nil
}
return states , nil
}