2022-11-16 17:17:28 -06:00
// Copyright (c) HashiCorp, Inc.
2021-02-03 16:25:56 -08:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2020-09-16 12:02:02 -04:00
package session
import (
"context"
2020-09-19 00:18:17 -04:00
"crypto/subtle"
2021-09-30 08:47:34 -04:00
"database/sql"
2020-09-16 12:02:02 -04:00
"fmt"
"strings"
2022-05-20 18:16:06 +00:00
"time"
2020-09-16 12:02:02 -04:00
"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"
2023-09-12 08:15:34 -04:00
"github.com/hashicorp/boundary/internal/event"
2020-09-16 12:02:02 -04:00
"github.com/hashicorp/boundary/internal/kms"
2022-11-16 17:17:28 -06:00
"github.com/hashicorp/boundary/internal/util"
2022-03-16 17:04:07 -04:00
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
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
2022-05-02 10:32:33 -04:00
// session: WorkerId, and PublicId. No options are
2020-09-16 12:02:02 -04:00
// currently supported.
2022-11-16 17:17:28 -06:00
func ( r * Repository ) CreateSession ( ctx context . Context , sessionWrapper wrapping . Wrapper , newSession * Session , workerAddresses [] string , _ ... Option ) ( * Session , error ) {
2021-02-03 16:25:56 -08:00
const op = "session.(Repository).CreateSession"
2020-09-16 12:02:02 -04:00
if newSession == nil {
2022-11-16 17:17:28 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing session" )
2020-09-16 12:02:02 -04:00
}
if newSession . PublicId != "" {
2022-11-16 17:17:28 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "public id is not empty" )
2020-09-16 12:02:02 -04:00
}
if len ( newSession . Certificate ) != 0 {
2022-11-16 17:17:28 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "certificate is not empty" )
2020-09-16 12:02:02 -04:00
}
if newSession . TargetId == "" {
2022-11-16 17:17:28 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing target id" )
2020-09-16 12:02:02 -04:00
}
if newSession . UserId == "" {
2022-11-16 17:17:28 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing user id" )
2020-09-16 12:02:02 -04:00
}
if newSession . AuthTokenId == "" {
2022-11-16 17:17:28 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing auth token id" )
2020-09-16 12:02:02 -04:00
}
2022-07-20 16:24:46 -07:00
if newSession . ProjectId == "" {
2022-11-16 17:17:28 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing project id" )
2020-09-16 12:02:02 -04:00
}
2022-12-06 12:30:36 -08:00
if newSession . HostId == "" && newSession . HostSetId == "" && newSession . Endpoint == "" {
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing host source and endpoint" )
}
2020-09-16 12:02:02 -04:00
if newSession . CtTofuToken != nil {
2022-11-16 17:17:28 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "ct is not empty" )
2020-09-16 12:02:02 -04:00
}
if newSession . TofuToken != nil {
2022-11-16 17:17:28 -06:00
return nil , errors . New ( ctx , 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 () {
2022-11-16 17:17:28 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing expiration time" )
2020-09-19 10:31:49 -04:00
}
2022-04-01 16:32:20 -04:00
if len ( workerAddresses ) == 0 {
2022-11-16 17:17:28 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing addresses" )
2022-04-01 16:32:20 -04:00
}
2020-09-16 12:02:02 -04:00
2023-06-06 17:09:08 -04:00
id , err := newId ( ctx )
2020-09-16 12:02:02 -04:00
if err != nil {
2022-11-16 17:17:28 -06:00
return nil , errors . Wrap ( ctx , err , op )
2020-09-16 12:02:02 -04:00
}
2022-11-16 17:17:28 -06:00
privKey , certBytes , err := newCert ( ctx , id , workerAddresses , newSession . ExpirationTime . Timestamp . AsTime (), r . randomReader )
2020-09-16 12:02:02 -04:00
if err != nil {
2022-11-16 17:17:28 -06:00
return nil , errors . Wrap ( ctx , err , op )
2020-09-16 12:02:02 -04:00
}
newSession . Certificate = certBytes
2022-11-16 17:17:28 -06:00
newSession . CertificatePrivateKey = privKey
2020-09-16 12:02:02 -04:00
newSession . PublicId = id
2022-11-16 17:17:28 -06:00
if err := newSession . encrypt ( ctx , sessionWrapper ); err != nil {
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to encrypt session" ))
2022-03-16 17:04:07 -04:00
}
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
2022-06-09 15:32:48 -07:00
returnedSession . StaticCredentials = nil
2020-09-16 12:02:02 -04:00
if err = w . Create ( ctx , returnedSession ); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2020-09-16 12:02:02 -04:00
}
2021-09-16 08:53:11 -04:00
2022-12-06 12:30:36 -08:00
if newSession . HostSetId != "" && newSession . HostId != "" {
2023-06-06 17:09:08 -04:00
hs , err := NewSessionHostSetHost ( ctx , newSession . PublicId , newSession . HostSetId , newSession . HostId )
2022-12-06 12:30:36 -08:00
if err != nil {
return errors . Wrap ( ctx , err , op )
}
if err = w . Create ( ctx , hs ); err != nil {
return errors . Wrap ( ctx , err , op )
}
returnedSession . HostSetId = hs . HostSetId
returnedSession . HostId = hs . HostId
} else if newSession . Endpoint != "" {
2023-06-06 17:09:08 -04:00
ta , err := NewSessionTargetAddress ( ctx , newSession . PublicId , newSession . TargetId )
2022-12-06 12:30:36 -08:00
if err != nil {
return errors . Wrap ( ctx , err , op )
}
if err = w . Create ( ctx , ta ); err != nil {
return errors . Wrap ( ctx , err , op )
}
}
2023-05-05 09:22:54 -04:00
if newSession . ProtocolWorkerId != "" {
swp , err := NewSessionWorkerProtocol ( ctx , newSession . PublicId , newSession . ProtocolWorkerId )
if err != nil {
return errors . Wrap ( ctx , err , op )
}
if err = w . Create ( ctx , swp ); err != nil {
return errors . Wrap ( ctx , err , op )
}
returnedSession . ProtocolWorkerId = swp . WorkerId
}
2021-06-22 22:26:26 -04:00
for _ , cred := range newSession . DynamicCredentials {
cred . SessionId = newSession . PublicId
2021-09-16 08:53:11 -04:00
}
2024-08-01 11:18:53 -04:00
var staticCreds [] * StaticCredential
2022-06-09 15:32:48 -07:00
for _ , cred := range newSession . StaticCredentials {
cred . SessionId = newSession . PublicId
staticCreds = append ( staticCreds , cred )
}
if len ( staticCreds ) > 0 {
if err = w . CreateItems ( ctx , staticCreds ); err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to create static credentials" ))
}
// Get static creds back from the db for return
var c [] * StaticCredential
2022-10-25 18:29:38 -05:00
if err := read . SearchWhere ( ctx , & c , "session_id = ?" , [] any { newSession . PublicId }); err != nil {
2022-06-09 15:32:48 -07:00
return errors . Wrap ( ctx , err , op )
}
returnedSession . StaticCredentials = c
}
2021-09-16 08:53:11 -04:00
// TODO: after upgrading to gorm v2 this batch insert can be replaced, since gorm v2 supports batch inserts
2022-11-16 17:17:28 -06:00
q , batchInsertArgs , err := batchInsertSessionCredentialDynamic ( newSession . DynamicCredentials )
2021-09-16 08:53:11 -04:00
if err == nil {
rows , err := w . Query ( ctx , q , batchInsertArgs )
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
2021-09-16 08:53:11 -04:00
defer rows . Close ()
for rows . Next () {
var returnedCred DynamicCredential
2024-02-16 08:30:16 -05:00
if err := w . ScanRows ( ctx , rows , & returnedCred ); err != nil {
return errors . Wrap ( ctx , err , op )
}
2021-09-16 08:53:11 -04:00
returnedSession . DynamicCredentials = append ( returnedSession . DynamicCredentials , & returnedCred )
}
2024-01-22 14:03:40 -05:00
if err := rows . Err (); err != nil {
return errors . Wrap ( ctx , err , op )
}
2021-06-22 22:26:26 -04:00
}
2021-09-16 08:53:11 -04:00
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-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2020-09-16 12:02:02 -04:00
}
2020-09-20 15:54:39 -04:00
if len ( foundStates ) == 0 {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . SessionNotFound , op , fmt . Sprintf ( "no states found for new session %s" , returnedSession . PublicId ))
2020-09-20 15:54:39 -04:00
}
2022-01-14 14:31:08 -05:00
if len ( foundStates ) != 1 {
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "%d states found for new session %s" , len ( foundStates ), returnedSession . PublicId ))
}
2020-09-20 15:54:39 -04:00
returnedSession . States = foundStates
if returnedSession . States [ 0 ]. Status != StatusPending {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , 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 {
2022-11-16 17:17:28 -06:00
return nil , errors . Wrap ( ctx , err , op )
2020-09-16 12:02:02 -04:00
}
2022-11-16 17:17:28 -06:00
return returnedSession , 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
2022-11-16 17:17:28 -06:00
// session is not found, it will return nil, nil, nil. If the session has no user
// or project associated with it, decryption of fields will not be performed.
// Supported Options:
// - WithIgnoreDecryptionFailures
func ( r * Repository ) LookupSession ( ctx context . Context , sessionId string , opt ... Option ) ( * Session , * AuthzSummary , error ) {
2021-02-03 16:25:56 -08:00
const op = "session.(Repository).LookupSession"
2020-09-16 12:02:02 -04:00
if sessionId == "" {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "missing session id" )
2020-09-16 12:02:02 -04:00
}
2022-11-16 17:17:28 -06:00
opts := getOpts ( opt ... )
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-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , sessionId )))
2020-09-16 12:02:02 -04:00
}
2024-09-30 14:39:19 -04:00
states , err := fetchStates ( ctx , read , sessionId )
2020-09-20 15:54:39 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , 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
2022-06-09 15:32:48 -07:00
var dynamicCreds [] * DynamicCredential
2022-10-25 18:29:38 -05:00
if err := read . SearchWhere ( ctx , & dynamicCreds , "session_id = ?" , [] any { sessionId }); err != nil {
2022-06-09 15:32:48 -07:00
return errors . Wrap ( ctx , err , op )
}
if len ( dynamicCreds ) > 0 {
session . DynamicCredentials = dynamicCreds
}
var staticCreds [] * StaticCredential
2022-10-25 18:29:38 -05:00
if err := read . SearchWhere ( ctx , & staticCreds , "session_id = ?" , [] any { sessionId }); err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2021-06-22 22:26:26 -04:00
}
2022-06-09 15:32:48 -07:00
if len ( staticCreds ) > 0 {
session . StaticCredentials = staticCreds
2021-06-22 22:26:26 -04:00
}
2021-11-15 15:26:08 -06:00
2022-12-06 12:30:36 -08:00
sessionHostSetHost := AllocSessionHostSetHost ()
if err := read . LookupWhere ( ctx , sessionHostSetHost , "session_id = ?" , [] any { sessionId }); err != nil && ! errors . IsNotFoundError ( err ) {
return errors . Wrap ( ctx , err , op )
}
session . HostSetId = sessionHostSetHost . HostSetId
session . HostId = sessionHostSetHost . HostId
2023-05-05 09:22:54 -04:00
sessionWorkerProtocol := AllocSessionWorkerProtocol ()
if err := read . LookupWhere ( ctx , sessionWorkerProtocol , "session_id = ?" , [] any { sessionId }); err != nil && ! errors . IsNotFoundError ( err ) {
return errors . Wrap ( ctx , err , op )
}
session . ProtocolWorkerId = sessionWorkerProtocol . WorkerId
2021-11-15 15:26:08 -06:00
connections , err := fetchConnections ( ctx , read , sessionId , db . WithOrder ( "create_time desc" ))
if err != nil {
return errors . Wrap ( ctx , err , op )
}
session . Connections = connections
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-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op )
2020-09-16 12:02:02 -04:00
}
2020-09-20 15:54:39 -04:00
2024-05-16 08:45:45 -04:00
// Skip decryption if Project ID or UserId is missing,
// since it will just lead to errors, and the session
// is already canceled if either of those are empty.
if session . ProjectId != "" && session . UserId != "" {
if err := decrypt ( ctx , r . kms , & session ); err != nil && ! opts . withIgnoreDecryptionFailures {
return nil , nil , errors . Wrap ( ctx , err , op )
}
}
2020-09-20 21:18:07 -04:00
authzSummary , err := r . sessionAuthzSummary ( ctx , sessionId )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , 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
}
2023-12-01 16:02:43 -05:00
// listSessions lists sessions. Sessions returned will be limited by the list
// permissions of the repository.
// Supported options:
// - withTerminated
// - withLimit
// - withStartPageAfterItem
func ( r * Repository ) listSessions ( ctx context . Context , opt ... Option ) ([] * Session , time . Time , error ) {
2021-02-03 16:25:56 -08:00
const op = "session.(Repository).ListSessions"
2023-12-01 16:02:43 -05:00
where , args := r . listPermissionWhereClauses ()
if len ( where ) == 0 {
return nil , time . Time {}, nil
}
2020-09-16 12:02:02 -04:00
opts := getOpts ( opt ... )
2020-09-20 15:54:39 -04:00
2024-01-10 13:43:13 -08:00
permissionWhereClause := "(" + strings . Join ( where , " or " ) + ")"
if ! opts . withTerminated {
permissionWhereClause += " and termination_reason is null"
2023-12-01 16:02:43 -05:00
}
limit := r . defaultLimit
if opts . withLimit > 0 {
limit = opts . withLimit
}
query := fmt . Sprintf ( listSessionsTemplate , permissionWhereClause , limit )
if opts . withStartPageAfterItem != nil {
query = fmt . Sprintf ( listSessionsPageTemplate , permissionWhereClause , limit )
args = append ( args ,
sql . Named ( "last_item_create_time" , opts . withStartPageAfterItem . GetCreateTime ()),
sql . Named ( "last_item_id" , opts . withStartPageAfterItem . GetPublicId ()),
)
}
2024-01-22 14:02:50 -05:00
return r . querySessions ( ctx , query , args )
2023-12-01 16:02:43 -05:00
}
// listSessionsRefresh lists sessions limited by the list
// permissions of the repository.
// Supported options:
// - withTerminated
// - withLimit
// - withStartPageAfterItem
func ( r * Repository ) listSessionsRefresh ( ctx context . Context , updatedAfter time . Time , opt ... Option ) ([] * Session , time . Time , error ) {
const op = "session.(Repository).ListSessionsRefresh"
if updatedAfter . IsZero () {
return nil , time . Time {}, errors . New ( ctx , errors . InvalidParameter , op , "missing updated after time" )
}
2022-08-09 12:42:58 +00:00
where , args := r . listPermissionWhereClauses ()
if len ( where ) == 0 {
2023-12-01 16:02:43 -05:00
return nil , time . Time {}, nil
2020-09-20 15:54:39 -04:00
}
2022-04-29 09:52:01 -04:00
2023-12-01 16:02:43 -05:00
opts := getOpts ( opt ... )
2024-01-10 13:43:13 -08:00
permissionWhereClause := "(" + strings . Join ( where , " or " ) + ")"
if ! opts . withTerminated {
permissionWhereClause += " and termination_reason is null"
2020-09-22 10:41:42 -04:00
}
2022-04-29 09:52:01 -04:00
2023-12-01 16:02:43 -05:00
limit := r . defaultLimit
if opts . withLimit > 0 {
limit = opts . withLimit
2020-09-20 15:54:39 -04:00
}
2023-12-01 16:02:43 -05:00
query := fmt . Sprintf ( refreshSessionsTemplate , permissionWhereClause , limit )
args = append ( args ,
sql . Named ( "updated_after_time" , timestamp . New ( updatedAfter )),
)
if opts . withStartPageAfterItem != nil {
query = fmt . Sprintf ( refreshSessionsPageTemplate , permissionWhereClause , limit )
args = append ( args ,
sql . Named ( "last_item_update_time" , opts . withStartPageAfterItem . GetUpdateTime ()),
sql . Named ( "last_item_id" , opts . withStartPageAfterItem . GetPublicId ()),
)
2020-09-20 15:54:39 -04:00
}
2024-01-22 14:02:50 -05:00
return r . querySessions ( ctx , query , args )
2023-12-01 16:02:43 -05:00
}
2020-09-16 12:02:02 -04:00
2024-01-22 14:02:50 -05:00
func ( r * Repository ) querySessions ( ctx context . Context , query string , args [] any ) ([] * Session , time . Time , error ) {
2023-12-01 16:02:43 -05:00
const op = "session.(Repository).querySessions"
var sessions [] * Session
var transactionTimestamp time . Time
if _ , err := r . writer . DoTx ( ctx , db . StdRetryCnt , db . ExpBackoff {}, func ( rd db . Reader , w db . Writer ) error {
rows , err := rd . Query ( ctx , query , args )
if err != nil {
return err
}
defer rows . Close ()
var sessionsList [] * sessionListView
for rows . Next () {
var s sessionListView
if err := rd . ScanRows ( ctx , rows , & s ); err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "scan row failed" ))
}
sessionsList = append ( sessionsList , & s )
}
2024-01-22 14:03:40 -05:00
if err := rows . Err (); err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to get next row for session" ))
}
2023-12-01 16:02:43 -05:00
sessions , err = r . convertToSessions ( ctx , sessionsList )
if err != nil {
return errors . Wrap ( ctx , err , op )
}
transactionTimestamp , err = rd . Now ( ctx )
return err
}); err != nil {
return nil , time . Time {}, err
2020-09-16 12:02:02 -04:00
}
2023-12-01 16:02:43 -05:00
return sessions , transactionTimestamp , nil
}
2020-09-20 15:54:39 -04:00
2023-12-01 16:02:43 -05:00
// listDeletedIds lists the public IDs of any sessions deleted since the timestamp provided.
func ( r * Repository ) listDeletedIds ( ctx context . Context , since time . Time ) ([] string , time . Time , error ) {
const op = "session.(Repository).listDeletedIds"
var deletedSessions [] * deletedSession
var transactionTimestamp time . Time
if _ , err := r . writer . DoTx ( ctx , db . StdRetryCnt , db . ExpBackoff {}, func ( r db . Reader , _ db . Writer ) error {
if err := r . SearchWhere ( ctx , & deletedSessions , "delete_time >= ?" , [] any { since }); err != nil {
2024-01-09 11:25:59 -08:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query deleted sessions" ))
2023-12-01 16:02:43 -05:00
}
var err error
transactionTimestamp , err = r . Now ( ctx )
if err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to get transaction timestamp" ))
2020-09-20 15:54:39 -04:00
}
2023-12-01 16:02:43 -05:00
return nil
}); err != nil {
return nil , time . Time {}, err
2020-09-20 15:54:39 -04:00
}
2023-12-01 16:02:43 -05:00
var sessionIds [] string
for _ , sess := range deletedSessions {
sessionIds = append ( sessionIds , sess . PublicId )
}
return sessionIds , transactionTimestamp , nil
}
// estimatedCount returns an estimate of the total number of items in the session table.
func ( r * Repository ) estimatedCount ( ctx context . Context ) ( int , error ) {
const op = "session.(Repository).estimatedCount"
rows , err := r . reader . Query ( ctx , estimateCountSessions , nil )
2020-09-20 15:54:39 -04:00
if err != nil {
2023-12-01 16:02:43 -05:00
return 0 , errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query total sessions" ))
}
var count int
for rows . Next () {
if err := r . reader . ScanRows ( ctx , rows , & count ); err != nil {
return 0 , errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query total sessions" ))
}
2020-09-16 12:02:02 -04:00
}
2024-01-22 14:03:40 -05:00
if err := rows . Err (); err != nil {
return 0 , errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to query total sessions" ))
}
2023-12-01 16:02:43 -05:00
return count , nil
2020-09-16 12:02:02 -04:00
}
// 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-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . New ( ctx , 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-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . Wrap ( ctx , 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 {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2021-02-03 16:25:56 -08:00
}
if rowsDeleted > 1 {
2020-09-16 12:02:02 -04:00
// return err, which will result in a rollback of the delete
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , 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-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . Wrap ( ctx , 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.
2022-11-16 17:17:28 -06:00
// Supported Options:
// - WithIgnoreDecryptionFailures
func ( r * Repository ) CancelSession ( ctx context . Context , sessionId string , sessionVersion uint32 , opt ... Option ) ( * 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-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , 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-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing session version" )
2020-09-16 12:02:02 -04:00
}
2022-11-16 17:17:28 -06:00
s , ss , err := r . updateState ( ctx , sessionId , sessionVersion , StatusCanceling , opt ... )
2020-09-18 18:12:41 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , 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
// TerminateCompletedSessions will terminate sessions in the repo based on:
2022-08-25 09:12:24 -07:00
// - 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
//
2020-10-01 10:13:57 -04:00
// 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-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2020-10-01 10:13:57 -04:00
}
return nil
},
)
if err != nil {
2021-07-29 18:52:56 -06:00
return db . NoRowsAffected , errors . Wrap ( ctx , err , op )
2020-10-01 10:13:57 -04:00
}
return rowsAffected , nil
}
2022-03-09 08:54:18 -05:00
// terminateSessionIfPossible is called on connection close and will attempt to close the connection's
// session if the following conditions are met:
2022-08-25 09:12:24 -07:00
// - 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
2022-03-09 08:54:18 -05:00
func ( r * Repository ) terminateSessionIfPossible ( ctx context . Context , sessionId string ) ( int , error ) {
const op = "session.(Repository).terminateSessionIfPossible"
rowsAffected := 0
_ , 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 , terminateSessionIfPossible ,
2022-10-25 18:29:38 -05:00
[] any { sql . Named ( "public_id" , sessionId )})
2022-03-09 08:54:18 -05:00
if err != nil {
return errors . Wrap ( ctx , err , op )
}
return nil
},
)
if err != nil {
return db . NoRowsAffected , errors . Wrap ( ctx , err , op )
}
return rowsAffected , nil
}
2022-02-25 08:37:35 -05:00
type AuthzSummary struct {
2020-09-19 21:05:56 -04:00
ExpirationTime * timestamp . Timestamp
2020-09-20 15:53:49 -04:00
ConnectionLimit int32
2020-09-19 21:05:56 -04:00
CurrentConnectionCount uint32
2023-01-04 11:24:09 -05:00
EgressWorkerFilter string
2023-01-19 15:39:49 -07:00
IngressWorkerFilter string
2020-09-19 21:05:56 -04:00
}
2022-02-25 08:37:35 -05:00
func ( r * Repository ) sessionAuthzSummary ( ctx context . Context , sessionId string ) ( * AuthzSummary , error ) {
2021-02-03 16:25:56 -08:00
const op = "session.(Repository).sessionAuthzSummary"
2022-10-25 18:29:38 -05:00
rows , err := r . reader . Query ( ctx , remainingConnectionsCte , [] any { sql . Named ( "session_id" , sessionId )})
2020-09-19 21:05:56 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2020-09-19 21:05:56 -04:00
}
defer rows . Close ()
2022-02-25 08:37:35 -05:00
var info * AuthzSummary
2020-09-19 21:05:56 -04:00
for rows . Next () {
if info != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . MultipleRecords , op , "query returned more than one row" )
2020-09-19 21:05:56 -04:00
}
2022-02-25 08:37:35 -05:00
info = & AuthzSummary {}
2022-01-05 14:38:38 -05:00
if err := r . reader . ScanRows ( ctx , rows , info ); err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "scan row failed" ))
2020-09-19 21:05:56 -04:00
}
}
2024-01-22 14:03:40 -05:00
if err := rows . Err (); err != nil {
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to get next row for session" ))
}
2020-09-19 21:05:56 -04:00
return info , nil
2020-09-19 14:21:09 -04:00
}
2023-01-24 09:24:03 -05:00
// Lookup an activated session. Must run in a transaction.
func ( r * Repository ) lookupActivatedSessionTx ( ctx context . Context , reader db . Reader , writer db . Writer , sessionId string ,
tofuToken [] byte , activatedSession * Session ,
) error {
const op = "session.(Repository).lookupActivatedSessionTx"
var txErr error
if txErr = reader . LookupById ( ctx , activatedSession ); txErr != nil {
return errors . Wrap ( ctx , txErr , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , sessionId )))
}
2024-05-16 08:45:45 -04:00
if txErr = decrypt ( ctx , r . kms , activatedSession ); txErr != nil {
2023-01-24 09:24:03 -05:00
return errors . Wrap ( ctx , txErr , op )
}
if len ( activatedSession . TofuToken ) > 0 && subtle . ConstantTimeCompare ( activatedSession . TofuToken , tofuToken ) != 1 {
return errors . New ( ctx , errors . TokenMismatch , op , "tofu token mismatch" )
}
return nil
}
// Return states for an activated session. Must run in a transaction.
func ( r * Repository ) fetchActivatedSessionStatesTx ( ctx context . Context , reader db . Reader , sessionId string ) ([] * State , error ) {
const op = "session.(Repository).fetchActivatedSessionStatesTx"
var txErr error
var returnedStates [] * State
2024-09-30 14:39:19 -04:00
returnedStates , txErr = fetchStates ( ctx , reader , sessionId )
2023-01-24 09:24:03 -05:00
if txErr != nil {
return nil , errors . Wrap ( ctx , txErr , op )
}
return returnedStates , nil
}
// getActivatedSession is called if there was a duplicate attempt to activate a session
// It validates the tofu token matches and returns the session
func ( r * Repository ) getActivatedSession ( ctx context . Context , sessionId string , tofuToken [] byte ) ( * Session , [] * State , error ) {
const op = "session.(Repository).getActivatedSession"
activatedSession := AllocSession ()
activatedSession . PublicId = sessionId
var returnedStates [] * State
_ , err := r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
2024-05-16 08:45:45 -04:00
func ( reader db . Reader , _ db . Writer ) error {
err := reader . LookupById ( ctx , & activatedSession )
2023-01-24 09:24:03 -05:00
if err != nil {
2024-05-16 08:45:45 -04:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , sessionId )))
2023-01-24 09:24:03 -05:00
}
returnedStates , err = r . fetchActivatedSessionStatesTx ( ctx , reader , sessionId )
if err != nil {
return errors . Wrap ( ctx , err , op )
}
return nil
},
)
if err != nil {
return nil , nil , errors . Wrap ( ctx , err , op )
}
2024-05-16 08:45:45 -04:00
if err := decrypt ( ctx , r . kms , & activatedSession ); err != nil {
return nil , nil , errors . Wrap ( ctx , err , op )
}
if len ( activatedSession . TofuToken ) > 0 && subtle . ConstantTimeCompare ( activatedSession . TofuToken , tofuToken ) != 1 {
return nil , nil , errors . New ( ctx , errors . TokenMismatch , op , "tofu token mismatch" )
}
2023-01-24 09:24:03 -05:00
return & activatedSession , returnedStates , 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.
2023-01-24 09:24:03 -05:00
// If ActivateSession receives duplicate requests for the same session, it will return the
// already active session if the tofu token is correct
2022-05-16 15:14:12 -07:00
func ( r * Repository ) ActivateSession ( ctx context . Context , sessionId string , sessionVersion uint32 , 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-07-29 18:52:56 -06:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "missing session id" )
2020-09-16 12:02:02 -04:00
}
if sessionVersion == 0 {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "missing version" )
2020-09-18 18:12:41 -04:00
}
2020-09-19 00:18:17 -04:00
if len ( tofuToken ) == 0 {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "missing tofu token" )
2020-09-19 00:18:17 -04:00
}
2024-05-16 08:45:45 -04:00
// Lookup session first to get the project id so the correct kms wrapper can be used for encrypting the tofu.
foundSession := AllocSession ()
foundSession . PublicId = sessionId
if err := r . reader . LookupById ( ctx , & foundSession ); err != nil {
return nil , nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , sessionId )))
}
// Encrypt the tofu before we start a database transaction to avoid holding the transaction while encrypting.
2020-09-16 12:02:02 -04:00
updatedSession := AllocSession ()
updatedSession . PublicId = sessionId
2024-05-16 08:45:45 -04:00
updatedSession . TofuToken = tofuToken
sessionWrapper , err := r . kms . GetWrapper ( ctx , foundSession . ProjectId , kms . KeyPurposeSessions )
if err != nil {
return nil , nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get session wrapper" ))
}
if err := updatedSession . encrypt ( ctx , sessionWrapper ); err != nil {
return nil , nil , errors . Wrap ( ctx , err , op )
}
var tofuSeen bool
2020-09-16 12:02:02 -04:00
var returnedStates [] * State
2024-05-16 08:45:45 -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 {
2022-10-25 18:29:38 -05:00
rowsAffected , err := w . Exec ( ctx , activateStateCte , [] any {
2021-09-30 08:47:34 -04:00
sql . Named ( "session_id" , sessionId ),
sql . Named ( "version" , sessionVersion ),
})
2020-09-16 12:02:02 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "unable to activate session %s" , sessionId )))
2020-09-16 12:02:02 -04:00
}
if rowsAffected == 0 {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . InvalidSessionState , op , "session is not in a pending state" )
2020-09-16 12:02:02 -04:00
}
2024-05-16 08:45:45 -04:00
foundSession = AllocSession ()
2020-09-16 12:02:02 -04:00
foundSession . PublicId = sessionId
2024-05-16 08:45:45 -04:00
err = reader . LookupById ( ctx , & foundSession )
2023-01-24 09:24:03 -05:00
if err != nil {
2024-05-16 08:45:45 -04:00
return errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "failed for %s" , sessionId )))
2020-09-16 12:02:02 -04:00
}
2020-09-19 00:18:17 -04:00
2024-05-16 08:45:45 -04:00
// If we already have recorded a tofu, we don't need to update anything.
// Once we are out of the transaction, we can decrypt and check if the
// recorded tofu matches.
if len ( foundSession . CtTofuToken ) > 0 {
tofuSeen = true
returnedStates , err = r . fetchActivatedSessionStatesTx ( ctx , reader , sessionId )
if err != nil {
return errors . Wrap ( ctx , err , op )
}
return nil
2020-09-16 12:02:02 -04:00
}
2024-05-16 08:45:45 -04:00
2022-11-16 17:17:28 -06:00
rowsUpdated , err := w . Update ( ctx , & updatedSession , [] string { "CtTofuToken" , "KeyId" }, nil )
2020-09-16 12:02:02 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , 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-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , "more than 1 resource would have been updated" )
2020-09-16 12:02:02 -04:00
}
2023-01-24 09:24:03 -05:00
returnedStates , err = r . fetchActivatedSessionStatesTx ( ctx , reader , sessionId )
2020-09-16 12:02:02 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2020-09-16 12:02:02 -04:00
}
return nil
},
)
if err != nil {
2023-01-24 09:24:03 -05:00
// If this was a duplicate activation attempt, return existing session if the tofu token matches
if errors . IsUniqueError ( err ) {
event . WriteSysEvent ( ctx , op , fmt . Sprintf ( "ignoring duplicate session activation attempt for session %v" , sessionId ))
return r . getActivatedSession ( ctx , sessionId , tofuToken )
}
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op )
2020-09-16 12:02:02 -04:00
}
2024-05-16 08:45:45 -04:00
if tofuSeen {
if err := decrypt ( ctx , r . kms , & foundSession ); err != nil {
return nil , nil , errors . Wrap ( ctx , err , op )
}
if subtle . ConstantTimeCompare ( foundSession . TofuToken , tofuToken ) != 1 {
return nil , nil , errors . New ( ctx , errors . TokenMismatch , op , "tofu token mismatch" )
}
return & foundSession , returnedStates , nil
}
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.
2022-11-16 17:17:28 -06:00
// Supported Options:
// - WithIgnoreDecryptionFailures
func ( r * Repository ) updateState ( ctx context . Context , sessionId string , sessionVersion uint32 , s Status , opt ... Option ) ( * Session , [] * State , error ) {
2021-02-03 16:25:56 -08:00
const op = "session.(Repository).updateState"
2020-09-16 12:02:02 -04:00
if sessionId == "" {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "missing session id" )
2020-09-16 12:02:02 -04:00
}
if sessionVersion == 0 {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "missing version" )
2020-09-16 12:02:02 -04:00
}
if s == "" {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "missing session status" )
2020-09-16 12:02:02 -04:00
}
if s == StatusActive {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . New ( ctx , errors . InvalidParameter , op , "you must call ActivateSession to update a session's state to active" )
2020-09-16 12:02:02 -04:00
}
2022-11-16 17:17:28 -06:00
opts := getOpts ( opt ... )
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-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , err , op )
2020-09-16 12:02:02 -04:00
}
if rowsUpdated != 1 {
2021-07-29 18:52:56 -06:00
return errors . New ( ctx , errors . MultipleRecords , op , fmt . Sprintf ( "updated session and %d rows updated" , rowsUpdated ))
2020-09-16 12:02:02 -04:00
}
2022-10-25 18:29:38 -05:00
rowsAffected , err = w . Exec ( ctx , updateSessionState , [] any {
2021-09-30 08:47:34 -04:00
sql . Named ( "session_id" , sessionId ),
sql . Named ( "status" , s . String ()),
})
2020-09-21 11:14:30 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , 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-07-29 18:52:56 -06:00
return errors . New ( ctx , 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
}
2024-09-30 14:39:19 -04:00
returnedStates , err = fetchStates ( ctx , reader , sessionId )
2020-09-16 12:02:02 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return errors . Wrap ( ctx , 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-07-29 18:52:56 -06:00
return errors . New ( ctx , 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
}
2022-12-06 12:33:28 -08:00
hostSetHost , err := fetchHostSetHost ( ctx , reader , sessionId )
if err != nil && ! errors . IsNotFoundError ( err ) {
return errors . Wrap ( ctx , err , op )
}
updatedSession . HostId = hostSetHost . HostId
updatedSession . HostSetId = hostSetHost . HostSetId
2020-09-16 12:02:02 -04:00
return nil
},
)
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "error creating new state" ))
2020-09-16 12:02:02 -04:00
}
2024-05-16 08:45:45 -04:00
if err := decrypt ( ctx , r . kms , & updatedSession ); err != nil && ! opts . withIgnoreDecryptionFailures {
return nil , nil , errors . Wrap ( ctx , err , op )
}
2020-09-16 12:02:02 -04:00
return & updatedSession , returnedStates , nil
}
2023-04-19 17:30:01 -07:00
// CheckIfNotActive checks the given sessions to see if they are in a
2022-10-06 16:52:26 +01:00
// non-active state, i.e. "canceling" or "terminated" It returns a *StateReport
// object for each session that is not active, with its current status.
2023-04-19 17:30:01 -07:00
func ( r * Repository ) CheckIfNotActive ( ctx context . Context , reportedSessions [] string ) ([] * StateReport , error ) {
2024-09-30 14:39:19 -04:00
const op = "session.(Repository).CheckIfNotActive"
2022-03-07 15:37:24 -05:00
2022-10-06 16:52:26 +01:00
notActive := make ([] * StateReport , 0 , len ( reportedSessions ))
2022-03-07 15:37:24 -05:00
if len ( reportedSessions ) <= 0 {
return notActive , nil
}
2023-04-19 17:30:01 -07:00
unrecognizedSessions := make ( map [ string ] struct {})
for _ , sessId := range reportedSessions {
unrecognizedSessions [ sessId ] = struct {}{}
2022-03-07 15:37:24 -05:00
}
_ , err := r . writer . DoTx (
ctx ,
db . StdRetryCnt ,
db . ExpBackoff {},
2024-04-19 13:40:42 -04:00
func ( reader db . Reader , _ db . Writer ) error {
2023-04-19 17:30:01 -07:00
var states [] * State
2024-09-30 14:39:19 -04:00
err := reader . SearchWhere ( ctx , & states , "upper(active_time_range) is null and session_id in (?)" , [] any { reportedSessions })
2022-03-07 15:37:24 -05:00
if err != nil {
return errors . Wrap ( ctx , err , op )
}
2023-04-19 17:30:01 -07:00
for _ , s := range states {
delete ( unrecognizedSessions , s . SessionId )
switch s . Status {
case StatusPending , StatusActive :
continue
case StatusCanceling , StatusTerminated :
default :
return errors . New ( ctx , errors . Internal , op , fmt . Sprintf ( "unknown session state %q" , s . Status ))
2022-03-07 15:37:24 -05:00
}
2023-04-19 17:30:01 -07:00
2022-10-06 16:52:26 +01:00
notActive = append ( notActive , & StateReport {
2023-04-19 17:30:01 -07:00
SessionId : s . SessionId ,
Status : s . Status ,
2022-03-07 15:37:24 -05:00
})
}
return nil
},
)
if err != nil {
return nil , errors . Wrap ( ctx , err , op , errors . WithMsg ( "error checking if sessions are no longer active" ))
}
2023-04-19 17:30:01 -07:00
for s := range unrecognizedSessions {
notActive = append ( notActive , & StateReport {
SessionId : s ,
Unrecognized : true ,
})
}
2022-03-07 15:37:24 -05:00
return notActive , nil
}
2020-09-16 12:02:02 -04:00
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
2024-09-30 14:39:19 -04:00
rows , err := r . Query ( ctx , selectStates , [] any { sessionId }, opt ... )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2020-09-16 12:02:02 -04:00
}
2024-09-30 14:39:19 -04:00
defer rows . Close ()
for rows . Next () {
if err := r . ScanRows ( ctx , rows , & states ); err != nil {
return nil , errors . Wrap ( ctx , err , op )
}
}
2020-09-16 12:02:02 -04:00
if len ( states ) == 0 {
return nil , nil
}
return states , nil
}
2021-11-15 15:26:08 -06:00
func fetchConnections ( ctx context . Context , r db . Reader , sessionId string , opt ... db . Option ) ([] * Connection , error ) {
const op = "session.fetchConnections"
var connections [] * Connection
2022-10-25 18:29:38 -05:00
if err := r . SearchWhere ( ctx , & connections , "session_id = ?" , [] any { sessionId }, opt ... ); err != nil {
2021-11-15 15:26:08 -06:00
return nil , errors . Wrap ( ctx , err , op )
}
if len ( connections ) == 0 {
return nil , nil
}
return connections , nil
}
2022-11-16 17:17:28 -06:00
2022-12-06 12:33:28 -08:00
func fetchHostSetHost ( ctx context . Context , r db . Reader , sessionId string , opt ... db . Option ) ( * SessionHostSetHost , error ) {
const op = "session.fetchHostSetHost"
var hostSetHost * SessionHostSetHost
if err := r . SearchWhere ( ctx , & hostSetHost , "session_id = ?" , [] any { sessionId }, opt ... ); err != nil {
return nil , errors . Wrap ( ctx , err , op )
}
return hostSetHost , nil
}
2024-05-16 08:45:45 -04:00
// decrypt decrypts encrypted fields of the Session.
func decrypt ( ctx context . Context , kmsRepo kms . GetWrapperer , session * Session ) error {
const op = "session.decrypt"
2022-12-13 05:38:14 -05:00
if util . IsNil ( kmsRepo ) {
2022-11-16 17:17:28 -06:00
return errors . New ( ctx , errors . InvalidParameter , op , "missing kms repo" )
}
if session == nil {
return errors . New ( ctx , errors . InvalidParameter , op , "missing session" )
}
if session . ProjectId == "" {
return errors . New ( ctx , errors . InvalidParameter , op , "missing session project ID" )
}
if session . KeyId == "" {
return errors . New ( ctx , errors . InvalidParameter , op , "missing session key ID" )
}
if session . UserId == "" {
return errors . New ( ctx , errors . InvalidParameter , op , "missing session user ID" )
}
if session . PublicId == "" {
return errors . New ( ctx , errors . InvalidParameter , op , "missing session ID" )
}
sessionWrapper , err := kmsRepo . GetWrapper ( ctx , session . ProjectId , kms . KeyPurposeSessions , kms . WithKeyId ( session . KeyId ))
if err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to get session wrapper" ))
}
2024-05-16 08:45:45 -04:00
if err := session . decrypt ( ctx , sessionWrapper ); err != nil {
return errors . Wrap ( ctx , err , op , errors . WithMsg ( "unable to decrypt session value" ))
2022-11-16 17:17:28 -06:00
}
return nil
}