client update of TURNs and STUNs (#106)

* feature: update STUNs and TURNs in engine

* fix: setup TURN credentials request only when refresh enabled

* feature: update TURNs and STUNs in teh client app on Management update

* chore: disable peer reflexive candidates in ICE

* chore: relocate management.json

* chore: make TURN secret and pwd plain text in config
This commit is contained in:
Mikhail Bragin 2021-09-03 17:47:40 +02:00 committed by GitHub
parent 81c5aa1341
commit a4db0b4e94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 129 additions and 87 deletions

View File

@ -2,7 +2,6 @@ package cmd
import ( import (
"context" "context"
"github.com/pion/ice/v2"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/wiretrustee/wiretrustee/client/internal" "github.com/wiretrustee/wiretrustee/client/internal"
@ -115,13 +114,7 @@ func createEngineConfig(key wgtypes.Key, config *internal.Config, wtConfig *mgmP
iFaceBlackList[config.IFaceBlackList[i]] = struct{}{} iFaceBlackList[config.IFaceBlackList[i]] = struct{}{}
} }
stunTurns, err := toStunTurnURLs(wtConfig)
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "failed parsing STUN and TURN URLs received from Management Service : %s", err)
}
return &internal.EngineConfig{ return &internal.EngineConfig{
StunsTurns: stunTurns,
WgIface: config.WgIface, WgIface: config.WgIface,
WgAddr: peerConfig.Address, WgAddr: peerConfig.Address,
IFaceBlackList: iFaceBlackList, IFaceBlackList: iFaceBlackList,
@ -129,30 +122,6 @@ func createEngineConfig(key wgtypes.Key, config *internal.Config, wtConfig *mgmP
}, nil }, nil
} }
// toStunTurnURLs converts Wiretrustee STUN and TURN configs to ice.URL array
func toStunTurnURLs(wtConfig *mgmProto.WiretrusteeConfig) ([]*ice.URL, error) {
var stunsTurns []*ice.URL
for _, stun := range wtConfig.Stuns {
url, err := ice.ParseURL(stun.Uri)
if err != nil {
return nil, err
}
stunsTurns = append(stunsTurns, url)
}
for _, turn := range wtConfig.Turns {
url, err := ice.ParseURL(turn.HostConfig.Uri)
if err != nil {
return nil, err
}
url.Username = turn.User
url.Password = turn.Password
stunsTurns = append(stunsTurns, url)
}
return stunsTurns, nil
}
// connectToSignal creates Signal Service client and established a connection // connectToSignal creates Signal Service client and established a connection
func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig, ourPrivateKey wgtypes.Key) (*signal.Client, error) { func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig, ourPrivateKey wgtypes.Key) (*signal.Client, error) {
var sigTLSEnabled bool var sigTLSEnabled bool

View File

@ -127,8 +127,9 @@ func (conn *Connection) Open(timeout time.Duration) error {
// create an ice.Agent that will be responsible for negotiating and establishing actual peer-to-peer connection // create an ice.Agent that will be responsible for negotiating and establishing actual peer-to-peer connection
a, err := ice.NewAgent(&ice.AgentConfig{ a, err := ice.NewAgent(&ice.AgentConfig{
// MulticastDNSMode: ice.MulticastDNSModeQueryAndGather, // MulticastDNSMode: ice.MulticastDNSModeQueryAndGather,
NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4}, NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4},
Urls: conn.Config.StunTurnURLS, Urls: conn.Config.StunTurnURLS,
CandidateTypes: []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive, ice.CandidateTypeRelay},
InterfaceFilter: func(s string) bool { InterfaceFilter: func(s string) bool {
if conn.Config.iFaceBlackList == nil { if conn.Config.iFaceBlackList == nil {
return true return true
@ -200,6 +201,10 @@ func (conn *Connection) Open(timeout time.Duration) error {
} }
} }
if pair.Remote.Type() == ice.CandidateTypeRelay || pair.Local.Type() == ice.CandidateTypeRelay {
log.Infof("using relay with peer %s", conn.Config.RemoteWgKey)
}
conn.Status = StatusConnected conn.Status = StatusConnected
log.Infof("opened connection to peer %s", conn.Config.RemoteWgKey.String()) log.Infof("opened connection to peer %s", conn.Config.RemoteWgKey.String())
case <-conn.closeCond.C: case <-conn.closeCond.C:

View File

@ -18,13 +18,11 @@ import (
// PeerConnectionTimeout is a timeout of an initial connection attempt to a remote peer. // PeerConnectionTimeout is a timeout of an initial connection attempt to a remote peer.
// E.g. this peer will wait PeerConnectionTimeout for the remote peer to respond, if not successful then it will retry the connection attempt. // E.g. this peer will wait PeerConnectionTimeout for the remote peer to respond, if not successful then it will retry the connection attempt.
const PeerConnectionTimeout = 60 * time.Second const PeerConnectionTimeout = 40 * time.Second
// EngineConfig is a config for the Engine // EngineConfig is a config for the Engine
type EngineConfig struct { type EngineConfig struct {
// StunsTurns is a list of STUN and TURN servers used by ICE WgIface string
StunsTurns []*ice.URL
WgIface string
// WgAddr is a Wireguard local address (Wiretrustee Network IP) // WgAddr is a Wireguard local address (Wiretrustee Network IP)
WgAddr string WgAddr string
// WgPrivateKey is a Wireguard private key of our peer (it MUST never leave the machine) // WgPrivateKey is a Wireguard private key of our peer (it MUST never leave the machine)
@ -51,6 +49,11 @@ type Engine struct {
// wgPort is a Wireguard local listen port // wgPort is a Wireguard local listen port
wgPort int wgPort int
// STUNs is a list of STUN servers used by ICE
STUNs []*ice.URL
// TURNs is a list of STUN servers used by ICE
TURNs []*ice.URL
} }
// Peer is an instance of the Connection Peer // Peer is an instance of the Connection Peer
@ -187,7 +190,7 @@ func (e *Engine) openPeerConnection(wgPort int, myKey wgtypes.Key, peer Peer) (*
WgAllowedIPs: peer.WgAllowedIps, WgAllowedIPs: peer.WgAllowedIps,
WgKey: myKey, WgKey: myKey,
RemoteWgKey: remoteKey, RemoteWgKey: remoteKey,
StunTurnURLS: e.config.StunsTurns, StunTurnURLS: append(e.STUNs, e.TURNs...),
iFaceBlackList: e.config.IFaceBlackList, iFaceBlackList: e.config.IFaceBlackList,
} }
@ -261,44 +264,26 @@ func (e *Engine) receiveManagementEvents() {
log.Debugf("connecting to Management Service updates stream") log.Debugf("connecting to Management Service updates stream")
e.mgmClient.Sync(func(update *mgmProto.SyncResponse) error { e.mgmClient.Sync(func(update *mgmProto.SyncResponse) error {
// todo handle changes of global settings (in update.GetWiretrusteeConfig())
// todo handle changes of peer settings (in update.GetPeerConfig())
e.syncMsgMux.Lock() e.syncMsgMux.Lock()
defer e.syncMsgMux.Unlock() defer e.syncMsgMux.Unlock()
remotePeers := update.GetRemotePeers() if update.GetWiretrusteeConfig() != nil {
if len(remotePeers) != 0 { err := e.updateTURNs(update.GetWiretrusteeConfig().GetTurns())
remotePeerMap := make(map[string]struct{})
for _, peer := range remotePeers {
remotePeerMap[peer.GetWgPubKey()] = struct{}{}
}
//remove peers that are no longer available for us
toRemove := []string{}
for p := range e.conns {
if _, ok := remotePeerMap[p]; !ok {
toRemove = append(toRemove, p)
}
}
err := e.removePeerConnections(toRemove)
if err != nil { if err != nil {
return err return err
} }
// add new peers err = e.updateSTUNs(update.GetWiretrusteeConfig().GetStuns())
for _, peer := range remotePeers { if err != nil {
peerKey := peer.GetWgPubKey() return err
peerIPs := peer.GetAllowedIps()
if _, ok := e.conns[peerKey]; !ok {
go e.initializePeer(Peer{
WgPubKey: peerKey,
WgAllowedIps: strings.Join(peerIPs, ","),
})
}
} }
//todo update signal
}
err := e.updatePeers(update.GetRemotePeers())
if err != nil {
return err
} }
return nil return nil
@ -307,6 +292,82 @@ func (e *Engine) receiveManagementEvents() {
log.Infof("connected to Management Service updates stream") log.Infof("connected to Management Service updates stream")
} }
func (e *Engine) updateSTUNs(stuns []*mgmProto.HostConfig) error {
if len(stuns) == 0 {
return nil
}
var newSTUNs []*ice.URL
log.Debugf("got STUNs update from Management Service, updating")
for _, stun := range stuns {
url, err := ice.ParseURL(stun.Uri)
if err != nil {
return err
}
newSTUNs = append(newSTUNs, url)
}
e.STUNs = newSTUNs
return nil
}
func (e *Engine) updateTURNs(turns []*mgmProto.ProtectedHostConfig) error {
if len(turns) == 0 {
return nil
}
var newTURNs []*ice.URL
log.Debugf("got TURNs update from Management Service, updating")
for _, turn := range turns {
url, err := ice.ParseURL(turn.HostConfig.Uri)
if err != nil {
return err
}
url.Username = turn.User
url.Password = turn.Password
newTURNs = append(newTURNs, url)
}
e.TURNs = newTURNs
return nil
}
func (e *Engine) updatePeers(remotePeers []*mgmProto.RemotePeerConfig) error {
if len(remotePeers) == 0 {
return nil
}
log.Debugf("got peers update from Management Service, updating")
remotePeerMap := make(map[string]struct{})
for _, peer := range remotePeers {
remotePeerMap[peer.GetWgPubKey()] = struct{}{}
}
//remove peers that are no longer available for us
toRemove := []string{}
for p := range e.conns {
if _, ok := remotePeerMap[p]; !ok {
toRemove = append(toRemove, p)
}
}
err := e.removePeerConnections(toRemove)
if err != nil {
return err
}
// add new peers
for _, peer := range remotePeers {
peerKey := peer.GetWgPubKey()
peerIPs := peer.GetAllowedIps()
if _, ok := e.conns[peerKey]; !ok {
go e.initializePeer(Peer{
WgPubKey: peerKey,
WgAllowedIps: strings.Join(peerIPs, ","),
})
}
}
return nil
}
// receiveSignalEvents connects to the Signal Service event stream to negotiate connection with remote peers // receiveSignalEvents connects to the Signal Service event stream to negotiate connection with remote peers
func (e *Engine) receiveSignalEvents() { func (e *Engine) receiveSignalEvents() {
// connect to a stream of messages coming from the signal server // connect to a stream of messages coming from the signal server

View File

@ -7,14 +7,19 @@
"Password": null "Password": null
} }
], ],
"Turns": [ "TURNConfig": {
{ "Turns": [
"Proto": "udp", {
"URI": "turn:stun.wiretrustee.com:3468", "Proto": "udp",
"Username": "some_user", "URI": "turn:stun.wiretrustee.com:3468",
"Password": "c29tZV9wYXNzd29yZA==" "Username": "some_user",
} "Password": "c29tZV9wYXNzd29yZA=="
], }
],
"CredentialsTTL": "1h",
"Secret": "c29tZV9wYXNzd29yZA==",
"TimeBasedCredentials": true
},
"Signal": { "Signal": {
"Proto": "http", "Proto": "http",
"URI": "signal.wiretrustee.com:10000", "URI": "signal.wiretrustee.com:10000",

View File

@ -38,7 +38,7 @@ func init() {
stopCh = make(chan int) stopCh = make(chan int)
defaultConfigPath = "/etc/wiretrustee/config.json" defaultConfigPath = "/etc/wiretrustee/management.json"
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
defaultConfigPath = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "config.json" defaultConfigPath = os.Getenv("PROGRAMDATA") + "\\Wiretrustee\\" + "config.json"
} }

View File

@ -29,7 +29,7 @@ type Config struct {
type TURNConfig struct { type TURNConfig struct {
TimeBasedCredentials bool TimeBasedCredentials bool
CredentialsTTL util.Duration CredentialsTTL util.Duration
Secret []byte Secret string
Turns []*Host Turns []*Host
} }
@ -51,5 +51,5 @@ type Host struct {
// URI e.g. turns://stun.wiretrustee.com:4430 or signal.wiretrustee.com:10000 // URI e.g. turns://stun.wiretrustee.com:4430 or signal.wiretrustee.com:10000
URI string URI string
Username string Username string
Password []byte Password string
} }

View File

@ -92,7 +92,9 @@ func (s *Server) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_S
log.Warnf("failed marking peer as connected %s %v", peerKey, err) log.Warnf("failed marking peer as connected %s %v", peerKey, err)
} }
s.turnCredentialsManager.SetupRefresh(peerKey.String()) if s.config.TURNConfig.TimeBasedCredentials {
s.turnCredentialsManager.SetupRefresh(peerKey.String())
}
// keep a connection to the peer and send updates when available // keep a connection to the peer and send updates when available
for { for {
select { select {

View File

@ -13,7 +13,7 @@
"Proto": "udp", "Proto": "udp",
"URI": "turn:stun.wiretrustee.com:3468", "URI": "turn:stun.wiretrustee.com:3468",
"Username": "some_user", "Username": "some_user",
"Password": "c29tZV9wYXNzd29yZA==" "Password": "some_password"
} }
], ],
"CredentialsTTL": "1h", "CredentialsTTL": "1h",

View File

@ -42,7 +42,7 @@ func NewTimeBasedAuthSecretsManager(updateManager *PeersUpdateManager, config *T
//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 //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 { func (m *TimeBasedAuthSecretsManager) GenerateCredentials() TURNCredentials {
mac := hmac.New(sha1.New, m.config.Secret) mac := hmac.New(sha1.New, []byte(m.config.Secret))
timeAuth := time.Now().Add(m.config.CredentialsTTL.Duration).Unix() timeAuth := time.Now().Add(m.config.CredentialsTTL.Duration).Unix()

View File

@ -13,12 +13,12 @@ var TurnTestHost = &Host{
Proto: UDP, Proto: UDP,
URI: "turn:turn.wiretrustee.com:77777", URI: "turn:turn.wiretrustee.com:77777",
Username: "username", Username: "username",
Password: nil, Password: "",
} }
func TestTimeBasedAuthSecretsManager_GenerateCredentials(t *testing.T) { func TestTimeBasedAuthSecretsManager_GenerateCredentials(t *testing.T) {
ttl := util.Duration{Duration: time.Hour} ttl := util.Duration{Duration: time.Hour}
secret := []byte("some_secret") secret := "some_secret"
peersManager := NewPeersUpdateManager() peersManager := NewPeersUpdateManager()
tested := NewTimeBasedAuthSecretsManager(peersManager, &TURNConfig{ tested := NewTimeBasedAuthSecretsManager(peersManager, &TURNConfig{
@ -36,13 +36,13 @@ func TestTimeBasedAuthSecretsManager_GenerateCredentials(t *testing.T) {
t.Errorf("expected generated TURN password not to be empty, got empty") t.Errorf("expected generated TURN password not to be empty, got empty")
} }
validateMAC(credentials.Username, credentials.Password, secret, t) validateMAC(credentials.Username, credentials.Password, []byte(secret), t)
} }
func TestTimeBasedAuthSecretsManager_SetupRefresh(t *testing.T) { func TestTimeBasedAuthSecretsManager_SetupRefresh(t *testing.T) {
ttl := util.Duration{Duration: 2 * time.Second} ttl := util.Duration{Duration: 2 * time.Second}
secret := []byte("some_secret") secret := "some_secret"
peersManager := NewPeersUpdateManager() peersManager := NewPeersUpdateManager()
peer := "some_peer" peer := "some_peer"
updateChannel := peersManager.CreateChannel(peer) updateChannel := peersManager.CreateChannel(peer)
@ -91,7 +91,7 @@ loop:
func TestTimeBasedAuthSecretsManager_CancelRefresh(t *testing.T) { func TestTimeBasedAuthSecretsManager_CancelRefresh(t *testing.T) {
ttl := util.Duration{Duration: time.Hour} ttl := util.Duration{Duration: time.Hour}
secret := []byte("some_secret") secret := "some_secret"
peersManager := NewPeersUpdateManager() peersManager := NewPeersUpdateManager()
peer := "some_peer" peer := "some_peer"