mirror of https://github.com/netbirdio/netbird.git
127 lines
3.6 KiB
Go
127 lines
3.6 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"crypto/hmac"
|
|
"crypto/sha1"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/netbirdio/netbird/management/proto"
|
|
)
|
|
|
|
// TURNCredentialsManager used to manage TURN credentials
|
|
type TURNCredentialsManager interface {
|
|
GenerateCredentials() TURNCredentials
|
|
SetupRefresh(ctx context.Context, 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 {
|
|
mac := hmac.New(sha1.New, []byte(m.config.Secret))
|
|
|
|
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(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.
|
|
func (m *TimeBasedAuthSecretsManager) SetupRefresh(ctx context.Context, peerID string) {
|
|
m.mux.Lock()
|
|
defer m.mux.Unlock()
|
|
m.cancel(peerID)
|
|
cancel := make(chan struct{}, 1)
|
|
m.cancelMap[peerID] = cancel
|
|
log.WithContext(ctx).Debugf("starting turn refresh for %s", peerID)
|
|
|
|
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.config.CredentialsTTL.Duration / 4 * 3)
|
|
|
|
for {
|
|
select {
|
|
case <-cancel:
|
|
log.WithContext(ctx).Debugf("stopping turn refresh for %s", peerID)
|
|
return
|
|
case <-ticker.C:
|
|
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,
|
|
},
|
|
}
|
|
log.WithContext(ctx).Debugf("sending new TURN credentials to peer %s", peerID)
|
|
m.updateManager.SendUpdate(ctx, peerID, &UpdateMessage{Update: update})
|
|
}
|
|
}
|
|
}()
|
|
}
|