2024-07-05 16:12:30 +02:00
package server
import (
2024-07-08 15:05:29 +02:00
"context"
2024-07-05 16:12:30 +02:00
"fmt"
"sync"
"time"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/proto"
auth "github.com/netbirdio/netbird/relay/auth/hmac"
)
// TURNRelayTokenManager used to manage TURN credentials
type TURNRelayTokenManager interface {
Generate ( ) ( * TURNRelayToken , error )
2024-07-08 15:05:29 +02:00
SetupRefresh ( ctx context . Context , peerKey string )
2024-07-05 16:12:30 +02:00
CancelRefresh ( peerKey string )
}
// TimeBasedAuthSecretsManager generates credentials with TTL and using pre-shared secret known to TURN server
type TimeBasedAuthSecretsManager struct {
mux sync . Mutex
turnCfg * TURNConfig
relayAddr string
hmacToken * auth . TimedHMAC
updateManager * PeersUpdateManager
cancelMap map [ string ] chan struct { }
}
type TURNRelayToken auth . Token
2024-07-10 11:30:02 +02:00
func NewTimeBasedAuthSecretsManager ( updateManager * PeersUpdateManager , turnCfg * TURNConfig , relayConfig * RelayConfig ) * TimeBasedAuthSecretsManager {
var relayAddr string
if relayConfig != nil {
relayAddr = relayConfig . Address
}
2024-07-05 16:12:30 +02:00
return & TimeBasedAuthSecretsManager {
mux : sync . Mutex { } ,
updateManager : updateManager ,
turnCfg : turnCfg ,
2024-07-10 11:30:02 +02:00
relayAddr : relayAddr ,
2024-07-05 16:12:30 +02:00
hmacToken : auth . NewTimedHMAC ( turnCfg . Secret , turnCfg . CredentialsTTL . Duration ) ,
cancelMap : make ( map [ string ] chan struct { } ) ,
}
}
// Generate 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 ) Generate ( ) ( * TURNRelayToken , error ) {
token , err := m . hmacToken . GenerateToken ( )
if err != nil {
return nil , fmt . Errorf ( "failed to generate token: %s" , err )
}
return ( * TURNRelayToken ) ( token ) , nil
}
func ( m * TimeBasedAuthSecretsManager ) cancel ( peerID string ) {
if channel , ok := m . cancelMap [ peerID ] ; ok {
close ( channel )
delete ( m . cancelMap , peerID )
}
}
// CancelRefresh cancels scheduled peer credentials refresh
func ( m * TimeBasedAuthSecretsManager ) CancelRefresh ( peerID string ) {
m . mux . Lock ( )
defer m . mux . Unlock ( )
m . cancel ( peerID )
}
// 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.
2024-07-08 15:05:29 +02:00
func ( m * TimeBasedAuthSecretsManager ) SetupRefresh ( ctx context . Context , peerID string ) {
2024-07-05 16:12:30 +02:00
m . mux . Lock ( )
defer m . mux . Unlock ( )
m . cancel ( peerID )
cancel := make ( chan struct { } , 1 )
m . cancelMap [ peerID ] = cancel
2024-07-08 15:05:29 +02:00
log . WithContext ( ctx ) . Debugf ( "starting turn refresh for %s" , peerID )
2024-07-05 16:12:30 +02:00
go func ( ) {
// 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 . turnCfg . CredentialsTTL . Duration / 4 * 3 )
defer ticker . Stop ( )
for {
select {
case <- cancel :
2024-07-08 15:05:29 +02:00
log . WithContext ( ctx ) . Debugf ( "stopping turn refresh for %s" , peerID )
2024-07-05 16:12:30 +02:00
return
case <- ticker . C :
2024-07-08 15:05:29 +02:00
m . pushNewTokens ( ctx , peerID )
2024-07-05 16:12:30 +02:00
}
}
} ( )
}
2024-07-08 15:05:29 +02:00
func ( m * TimeBasedAuthSecretsManager ) pushNewTokens ( ctx context . Context , peerID string ) {
2024-07-05 16:12:30 +02:00
token , err := m . hmacToken . GenerateToken ( )
if err != nil {
log . Errorf ( "failed to generate token for peer '%s': %s" , peerID , err )
return
}
var turns [ ] * proto . ProtectedHostConfig
for _ , host := range m . turnCfg . Turns {
turns = append ( turns , & proto . ProtectedHostConfig {
HostConfig : & proto . HostConfig {
Uri : host . URI ,
Protocol : ToResponseProto ( host . Proto ) ,
} ,
User : token . Payload ,
Password : token . Signature ,
} )
}
update := & proto . SyncResponse {
WiretrusteeConfig : & proto . WiretrusteeConfig {
Turns : turns ,
Relay : & proto . RelayConfig {
Urls : [ ] string { m . relayAddr } ,
TokenPayload : token . Payload ,
TokenSignature : token . Signature ,
} ,
} ,
}
2024-07-08 15:05:29 +02:00
log . WithContext ( ctx ) . Debugf ( "sending new TURN credentials to peer %s" , peerID )
m . updateManager . SendUpdate ( ctx , peerID , & UpdateMessage { Update : update } )
2024-07-05 16:12:30 +02:00
}