2020-09-19 00:18:17 -04:00
package worker
import (
"context"
"crypto/ed25519"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"strings"
2020-09-20 12:51:08 -04:00
"sync"
2020-09-19 00:18:17 -04:00
"time"
pbs "github.com/hashicorp/boundary/internal/gen/controller/servers/services"
2020-09-21 00:09:50 -04:00
"github.com/hashicorp/boundary/internal/session"
2020-09-19 00:18:17 -04:00
)
const (
validateSessionTimeout = 90 * time . Second
)
2020-09-20 12:51:08 -04:00
type connInfo struct {
id string
connCtx context . Context
connCancel context . CancelFunc
status pbs . CONNECTIONSTATUS
2020-09-21 02:22:59 -04:00
closeTime time . Time
2020-09-20 12:51:08 -04:00
}
type sessionInfo struct {
sync . RWMutex
2020-09-21 02:22:59 -04:00
id string
2020-09-20 12:51:08 -04:00
sessionTls * tls . Config
status pbs . SESSIONSTATUS
lookupSessionResponse * pbs . LookupSessionResponse
connInfoMap map [ string ] * connInfo
}
2020-09-19 00:18:17 -04:00
func ( w * Worker ) getSessionTls ( hello * tls . ClientHelloInfo ) ( * tls . Config , error ) {
var sessionId string
switch {
case strings . HasPrefix ( hello . ServerName , "s_" ):
w . logger . Trace ( "got valid session in SNI" , "session_id" , hello . ServerName )
sessionId = hello . ServerName
default :
w . logger . Trace ( "invalid session in SNI" , "session_id" , hello . ServerName )
return nil , fmt . Errorf ( "could not find session ID in SNI" )
}
rawConn := w . controllerSessionConn . Load ()
if rawConn == nil {
w . logger . Trace ( "could not get a controller client" , "session_id" , sessionId )
return nil , errors . New ( "could not get a controller client" )
}
conn , ok := rawConn .( pbs . SessionServiceClient )
if ! ok {
w . logger . Trace ( "could not cast controller client to the real thing" , "session_id" , sessionId )
return nil , errors . New ( "could not cast atomic controller client to the real thing" )
}
if conn == nil {
w . logger . Trace ( "controller client is nil" , "session_id" , sessionId )
return nil , errors . New ( "controller client is nil" )
}
timeoutContext , cancel := context . WithTimeout ( w . baseContext , validateSessionTimeout )
defer cancel ()
w . logger . Trace ( "looking up session" , "session_id" , sessionId )
resp , err := conn . LookupSession ( timeoutContext , & pbs . LookupSessionRequest {
2021-01-21 16:27:20 -05:00
ServerId : w . conf . RawConfig . Worker . Name ,
2020-09-19 00:18:17 -04:00
SessionId : sessionId ,
})
if err != nil {
return nil , fmt . Errorf ( "error validating session: %w" , err )
}
2020-09-19 13:51:50 -04:00
if resp . GetExpiration (). AsTime (). Before ( time . Now ()) {
return nil , fmt . Errorf ( "session is expired" )
}
2020-09-19 00:18:17 -04:00
parsedCert , err := x509 . ParseCertificate ( resp . GetAuthorization (). Certificate )
if err != nil {
return nil , fmt . Errorf ( "error parsing session certificate: %w" , err )
}
if len ( parsedCert . DNSNames ) != 1 {
return nil , fmt . Errorf ( "invalid length of DNS names (%d) in parsed certificate" , len ( parsedCert . DNSNames ))
}
certPool := x509 . NewCertPool ()
certPool . AddCert ( parsedCert )
tlsConf := & tls . Config {
Certificates : [] tls . Certificate {
{
Certificate : [][] byte { resp . GetAuthorization (). Certificate },
PrivateKey : ed25519 . PrivateKey ( resp . GetAuthorization (). PrivateKey ),
Leaf : parsedCert ,
},
},
ServerName : parsedCert . DNSNames [ 0 ],
ClientAuth : tls . RequireAndVerifyClientCert ,
ClientCAs : certPool ,
MinVersion : tls . VersionTLS13 ,
}
2020-09-20 12:51:08 -04:00
si := & sessionInfo {
2020-09-21 02:22:59 -04:00
id : resp . GetAuthorization (). GetSessionId (),
2020-09-20 12:51:08 -04:00
sessionTls : tlsConf ,
lookupSessionResponse : resp ,
status : resp . GetStatus (),
connInfoMap : make ( map [ string ] * connInfo ),
}
2020-09-19 00:18:17 -04:00
// TODO: Periodicially clean this up. We can't rely on things in here but
// not in cancellation because they could be on the way to being
// established. However, since cert lifetimes are short, we can simply range
// through and remove values that are expired.
2020-09-20 12:51:08 -04:00
actualSiRaw , loaded := w . sessionInfoMap . LoadOrStore ( sessionId , si )
if loaded {
// Update the response to the latest
actualSi := actualSiRaw .( * sessionInfo )
actualSi . Lock ()
actualSi . lookupSessionResponse = resp
actualSi . Unlock ()
}
2020-09-19 00:18:17 -04:00
w . logger . Trace ( "returning TLS configuration" , "session_id" , sessionId )
return tlsConf , nil
}
2020-09-20 12:51:08 -04:00
func ( w * Worker ) activateSession ( ctx context . Context , sessionId , tofuToken string , version uint32 ) ( pbs . SESSIONSTATUS , error ) {
2020-09-19 00:18:17 -04:00
rawConn := w . controllerSessionConn . Load ()
if rawConn == nil {
2020-09-20 12:51:08 -04:00
return pbs . SESSIONSTATUS_SESSIONSTATUS_UNSPECIFIED , errors . New ( "could not get a controller client" )
2020-09-19 00:18:17 -04:00
}
conn , ok := rawConn .( pbs . SessionServiceClient )
if ! ok {
2020-09-20 12:51:08 -04:00
return pbs . SESSIONSTATUS_SESSIONSTATUS_UNSPECIFIED , errors . New ( "could not cast atomic controller client to the real thing" )
2020-09-19 00:18:17 -04:00
}
if conn == nil {
2020-09-20 12:51:08 -04:00
return pbs . SESSIONSTATUS_SESSIONSTATUS_UNSPECIFIED , errors . New ( "controller client is nil" )
2020-09-19 00:18:17 -04:00
}
2020-09-20 12:51:08 -04:00
resp , err := conn . ActivateSession ( ctx , & pbs . ActivateSessionRequest {
SessionId : sessionId ,
2020-09-19 00:18:17 -04:00
TofuToken : tofuToken ,
2020-09-20 12:51:08 -04:00
Version : version ,
2020-09-19 00:18:17 -04:00
WorkerId : w . conf . RawConfig . Worker . Name ,
})
if err != nil {
2020-09-20 12:51:08 -04:00
return pbs . SESSIONSTATUS_SESSIONSTATUS_UNSPECIFIED , fmt . Errorf ( "error activating session: %w" , err )
}
return resp . GetStatus (), nil
}
2020-12-16 17:14:47 -05:00
func ( w * Worker ) cancelSession ( ctx context . Context , sessionId string ) ( pbs . SESSIONSTATUS , error ) {
rawConn := w . controllerSessionConn . Load ()
if rawConn == nil {
return pbs . SESSIONSTATUS_SESSIONSTATUS_UNSPECIFIED , errors . New ( "could not get a controller client" )
}
conn , ok := rawConn .( pbs . SessionServiceClient )
if ! ok {
return pbs . SESSIONSTATUS_SESSIONSTATUS_UNSPECIFIED , errors . New ( "could not cast atomic controller client to the real thing" )
}
if conn == nil {
return pbs . SESSIONSTATUS_SESSIONSTATUS_UNSPECIFIED , errors . New ( "controller client is nil" )
}
resp , err := conn . CancelSession ( ctx , & pbs . CancelSessionRequest {
SessionId : sessionId ,
})
if err != nil {
return pbs . SESSIONSTATUS_SESSIONSTATUS_UNSPECIFIED , fmt . Errorf ( "error canceling session: %w" , err )
}
return resp . GetStatus (), nil
}
2020-09-20 16:28:00 -04:00
func ( w * Worker ) authorizeConnection ( ctx context . Context , sessionId string ) ( * connInfo , int32 , error ) {
2020-09-20 12:51:08 -04:00
rawConn := w . controllerSessionConn . Load ()
if rawConn == nil {
2020-09-20 16:28:00 -04:00
return nil , 0 , errors . New ( "could not get a controller client" )
2020-09-20 12:51:08 -04:00
}
conn , ok := rawConn .( pbs . SessionServiceClient )
if ! ok {
2020-09-20 16:28:00 -04:00
return nil , 0 , errors . New ( "could not cast atomic controller client to the real thing" )
2020-09-20 12:51:08 -04:00
}
if conn == nil {
2020-09-20 16:28:00 -04:00
return nil , 0 , errors . New ( "controller client is nil" )
2020-09-20 12:51:08 -04:00
}
resp , err := conn . AuthorizeConnection ( ctx , & pbs . AuthorizeConnectionRequest {
SessionId : sessionId ,
2021-05-14 12:23:12 -05:00
WorkerId : w . conf . RawConfig . Worker . Name ,
2020-09-20 12:51:08 -04:00
})
if err != nil {
2020-09-20 16:28:00 -04:00
return nil , 0 , fmt . Errorf ( "error authorizing connection: %w" , err )
2020-09-19 00:18:17 -04:00
}
2020-09-20 12:51:08 -04:00
return & connInfo {
id : resp . ConnectionId ,
status : resp . GetStatus (),
2020-09-20 16:28:00 -04:00
}, resp . GetConnectionsLeft (), nil
2020-09-19 00:18:17 -04:00
}
2020-09-20 23:09:42 -04:00
2020-09-21 00:09:50 -04:00
func ( w * Worker ) connectConnection ( ctx context . Context , req * pbs . ConnectConnectionRequest ) ( pbs . CONNECTIONSTATUS , error ) {
2020-09-20 23:09:42 -04:00
rawConn := w . controllerSessionConn . Load ()
if rawConn == nil {
return pbs . CONNECTIONSTATUS_CONNECTIONSTATUS_UNSPECIFIED , errors . New ( "could not get a controller client" )
}
conn , ok := rawConn .( pbs . SessionServiceClient )
if ! ok {
return pbs . CONNECTIONSTATUS_CONNECTIONSTATUS_UNSPECIFIED , errors . New ( "could not cast atomic controller client to the real thing" )
}
if conn == nil {
return pbs . CONNECTIONSTATUS_CONNECTIONSTATUS_UNSPECIFIED , errors . New ( "controller client is nil" )
}
2020-09-21 00:09:50 -04:00
resp , err := conn . ConnectConnection ( ctx , req )
2020-09-20 23:09:42 -04:00
if err != nil {
return pbs . CONNECTIONSTATUS_CONNECTIONSTATUS_UNSPECIFIED , err
}
if resp . GetStatus () != pbs . CONNECTIONSTATUS_CONNECTIONSTATUS_CONNECTED {
return pbs . CONNECTIONSTATUS_CONNECTIONSTATUS_UNSPECIFIED , fmt . Errorf ( "unexpected state returned: %v" , resp . GetStatus (). String ())
}
return resp . GetStatus (), nil
}
2020-09-21 00:09:50 -04:00
func ( w * Worker ) closeConnection ( ctx context . Context , req * pbs . CloseConnectionRequest ) ( * pbs . CloseConnectionResponse , error ) {
rawConn := w . controllerSessionConn . Load ()
if rawConn == nil {
return nil , errors . New ( "could not get a controller client" )
}
conn , ok := rawConn .( pbs . SessionServiceClient )
if ! ok {
return nil , errors . New ( "could not cast atomic controller client to the real thing" )
}
if conn == nil {
return nil , errors . New ( "controller client is nil" )
}
resp , err := conn . CloseConnection ( ctx , req )
if err != nil {
return nil , err
}
if len ( resp . GetCloseResponseData ()) != len ( req . GetCloseRequestData ()) {
w . logger . Warn ( "mismatched number of states returned on connection closed" , "expected" , len ( req . GetCloseRequestData ()), "got" , len ( resp . GetCloseResponseData ()))
}
return resp , nil
}
2020-09-21 02:22:59 -04:00
func ( w * Worker ) closeConnections ( ctx context . Context , closeMap map [ string ] string ) error {
2020-09-21 12:09:30 -07:00
w . logger . Trace ( "marking connections as closed" , "session_and_connection_ids" , fmt . Sprintf ( "%#v" , closeMap ))
2020-09-21 00:09:50 -04:00
2020-09-21 02:22:59 -04:00
closeData := make ([] * pbs . CloseConnectionRequestData , 0 , len ( closeMap ))
for connId := range closeMap {
2020-09-21 00:09:50 -04:00
closeData = append ( closeData , & pbs . CloseConnectionRequestData {
2020-09-21 02:22:59 -04:00
ConnectionId : connId ,
2020-09-21 00:09:50 -04:00
Reason : session . UnknownReason . String (),
})
}
closeInfo := & pbs . CloseConnectionRequest {
CloseRequestData : closeData ,
}
connStatus , err := w . closeConnection ( ctx , closeInfo )
if err != nil {
return err
}
closedIds := make ([] string , 0 , len ( connStatus . GetCloseResponseData ()))
2020-09-21 02:22:59 -04:00
// Here we build a reverse map from closeMap, that is, session ID to
// connection IDs, for more efficient locking
revMap := make ( map [ string ][] * pbs . CloseConnectionResponseData )
2020-09-21 00:09:50 -04:00
for _ , v := range connStatus . GetCloseResponseData () {
2020-09-21 02:22:59 -04:00
revMap [ closeMap [ v . GetConnectionId ()]] = append ( revMap [ closeMap [ v . GetConnectionId ()]], v )
}
for k , v := range revMap {
siRaw , ok := w . sessionInfoMap . Load ( k )
if ! ok {
w . logger . Warn ( "could not find session ID in info map after closing connections" , "session_id" , k )
2020-09-21 02:44:53 -04:00
continue
2020-09-21 02:22:59 -04:00
}
si := siRaw .( * sessionInfo )
si . Lock ()
for _ , connResult := range v {
ci := si . connInfoMap [ connResult . GetConnectionId ()]
ci . status = connResult . GetStatus ()
if ci . status == pbs . CONNECTIONSTATUS_CONNECTIONSTATUS_CLOSED {
ci . closeTime = time . Now ()
}
2020-09-21 00:09:50 -04:00
}
2020-09-21 02:22:59 -04:00
si . Unlock ()
2020-09-21 00:09:50 -04:00
}
w . logger . Trace ( "connections successfully marked closed" , "connection_ids" , closedIds )
return nil
}