2021-09-02 14:41:54 +02:00
package server
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
2022-03-26 12:08:54 +01:00
"github.com/netbirdio/netbird/management/proto"
2021-09-02 14:41:54 +02:00
log "github.com/sirupsen/logrus"
"sync"
"time"
)
//TURNCredentialsManager used to manage TURN credentials
type TURNCredentialsManager interface {
GenerateCredentials ( ) TURNCredentials
SetupRefresh ( peerKey string )
CancelRefresh ( peerKey string )
}
//TimeBasedAuthSecretsManager generates credentials with TTL and using pre-shared secret known to TURN server
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 { } ) ,
}
}
//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
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
timeAuth := time . Now ( ) . Add ( m . config . CredentialsTTL . Duration ) . Unix ( )
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 ,
}
}
func ( m * TimeBasedAuthSecretsManager ) cancel ( peerKey string ) {
if channel , ok := m . cancelMap [ peerKey ] ; ok {
close ( channel )
delete ( m . cancelMap , peerKey )
}
}
//CancelRefresh cancels scheduled peer credentials refresh
func ( m * TimeBasedAuthSecretsManager ) CancelRefresh ( peerKey string ) {
m . mux . Lock ( )
defer m . mux . Unlock ( )
m . cancel ( peerKey )
}
//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 ( peerKey string ) {
m . mux . Lock ( )
defer m . mux . Unlock ( )
m . cancel ( peerKey )
cancel := make ( chan struct { } , 1 )
m . cancelMap [ peerKey ] = cancel
2022-08-27 12:57:03 +02:00
log . Debugf ( "starting turn refresh for %s" , peerKey )
2021-09-02 14:41:54 +02:00
go func ( ) {
2022-08-27 12:57:03 +02:00
//we don't want to regenerate credentials right on expiration, so we do it slightly before (at 3/4 of TTL)
ticker := time . NewTicker ( m . config . CredentialsTTL . Duration / 4 * 3 )
2021-09-02 14:41:54 +02:00
for {
select {
case <- cancel :
2022-08-27 12:57:03 +02:00
log . Debugf ( "stopping turn refresh for %s" , peerKey )
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 ,
} ,
}
err := m . updateManager . SendUpdate ( peerKey , & UpdateMessage { Update : update } )
if err != nil {
log . Errorf ( "error while sending TURN update to peer %s %v" , peerKey , err )
// todo maybe continue trying?
}
}
}
} ( )
}