2021-09-02 14:41:54 +02:00
package server
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"sync"
"time"
2023-04-03 15:09:35 +02:00
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/proto"
2021-09-02 14:41:54 +02:00
)
2023-02-03 10:33:28 +01:00
// TURNCredentialsManager used to manage TURN credentials
2021-09-02 14:41:54 +02:00
type TURNCredentialsManager interface {
GenerateCredentials ( ) TURNCredentials
SetupRefresh ( peerKey string )
CancelRefresh ( peerKey string )
}
2023-02-03 10:33:28 +01:00
// TimeBasedAuthSecretsManager generates credentials with TTL and using pre-shared secret known to TURN server
2021-09-02 14:41:54 +02:00
type TimeBasedAuthSecretsManager struct {
mux sync . Mutex
config * TURNConfig
updateManager * PeersUpdateManager
cancelMap map [ string ] chan struct { }
}
type TURNCredentials struct {
Username string
Password string
}
func NewTimeBasedAuthSecretsManager ( updateManager * PeersUpdateManager , config * TURNConfig ) * TimeBasedAuthSecretsManager {
return & TimeBasedAuthSecretsManager {
mux : sync . Mutex { } ,
config : config ,
updateManager : updateManager ,
cancelMap : make ( map [ string ] chan struct { } ) ,
}
}
2023-02-03 10:33:28 +01:00
// GenerateCredentials generates new time-based secret credentials - basically username is a unix timestamp and password is a HMAC hash of a timestamp with a preshared TURN secret
2021-09-02 14:41:54 +02:00
func ( m * TimeBasedAuthSecretsManager ) GenerateCredentials ( ) TURNCredentials {
2021-09-03 17:47:40 +02:00
mac := hmac . New ( sha1 . New , [ ] byte ( m . config . Secret ) )
2021-09-02 14:41:54 +02:00
2023-04-10 10:54:23 +02:00
timeAuth := time . Now ( ) . Add ( m . config . CredentialsTTL . Duration ) . Unix ( )
2021-09-02 14:41:54 +02:00
username := fmt . Sprint ( timeAuth )
_ , err := mac . Write ( [ ] byte ( username ) )
if err != nil {
log . Errorln ( "Generating turn password failed with error: " , err )
}
bytePassword := mac . Sum ( nil )
password := base64 . StdEncoding . EncodeToString ( bytePassword )
return TURNCredentials {
Username : username ,
Password : password ,
}
}
2023-02-28 20:02:30 +01:00
func ( m * TimeBasedAuthSecretsManager ) cancel ( peerID string ) {
if channel , ok := m . cancelMap [ peerID ] ; ok {
2021-09-02 14:41:54 +02:00
close ( channel )
2023-02-28 20:02:30 +01:00
delete ( m . cancelMap , peerID )
2021-09-02 14:41:54 +02:00
}
}
2023-02-03 10:33:28 +01:00
// CancelRefresh cancels scheduled peer credentials refresh
2023-02-28 20:02:30 +01:00
func ( m * TimeBasedAuthSecretsManager ) CancelRefresh ( peerID string ) {
2021-09-02 14:41:54 +02:00
m . mux . Lock ( )
defer m . mux . Unlock ( )
2023-02-28 20:02:30 +01:00
m . cancel ( peerID )
2021-09-02 14:41:54 +02:00
}
2023-02-03 10:33:28 +01:00
// SetupRefresh starts peer credentials refresh. Since credentials are expiring (TTL) it is necessary to always generate them and send to the peer.
// A goroutine is created and put into TimeBasedAuthSecretsManager.cancelMap. This routine should be cancelled if peer is gone.
func ( m * TimeBasedAuthSecretsManager ) SetupRefresh ( peerID string ) {
2021-09-02 14:41:54 +02:00
m . mux . Lock ( )
defer m . mux . Unlock ( )
2023-02-03 10:33:28 +01:00
m . cancel ( peerID )
2021-09-02 14:41:54 +02:00
cancel := make ( chan struct { } , 1 )
2023-02-03 10:33:28 +01:00
m . cancelMap [ peerID ] = cancel
log . Debugf ( "starting turn refresh for %s" , peerID )
2022-08-27 12:57:03 +02:00
2021-09-02 14:41:54 +02:00
go func ( ) {
2023-04-03 15:09:35 +02:00
// we don't want to regenerate credentials right on expiration, so we do it slightly before (at 3/4 of TTL)
2022-08-27 12:57:03 +02:00
ticker := time . NewTicker ( m . config . CredentialsTTL . Duration / 4 * 3 )
2021-09-02 14:41:54 +02:00
for {
select {
case <- cancel :
2023-02-03 10:33:28 +01:00
log . Debugf ( "stopping turn refresh for %s" , peerID )
2021-09-02 14:41:54 +02:00
return
2022-08-27 12:57:03 +02:00
case <- ticker . C :
2021-09-02 14:41:54 +02:00
c := m . GenerateCredentials ( )
var turns [ ] * proto . ProtectedHostConfig
for _ , host := range m . config . Turns {
turns = append ( turns , & proto . ProtectedHostConfig {
HostConfig : & proto . HostConfig {
Uri : host . URI ,
Protocol : ToResponseProto ( host . Proto ) ,
} ,
User : c . Username ,
Password : c . Password ,
} )
}
update := & proto . SyncResponse {
WiretrusteeConfig : & proto . WiretrusteeConfig {
Turns : turns ,
} ,
}
2023-02-27 16:44:26 +01:00
log . Debugf ( "sending new TURN credentials to peer %s" , peerID )
2023-02-03 10:33:28 +01:00
err := m . updateManager . SendUpdate ( peerID , & UpdateMessage { Update : update } )
2021-09-02 14:41:54 +02:00
if err != nil {
2023-02-03 10:33:28 +01:00
log . Errorf ( "error while sending TURN update to peer %s %v" , peerID , err )
2021-09-02 14:41:54 +02:00
// todo maybe continue trying?
}
}
}
} ( )
}