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 (
"context"
"github.com/pion/ice/v2"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"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{}{}
}
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{
StunsTurns: stunTurns,
WgIface: config.WgIface,
WgAddr: peerConfig.Address,
IFaceBlackList: iFaceBlackList,
@ -129,30 +122,6 @@ func createEngineConfig(key wgtypes.Key, config *internal.Config, wtConfig *mgmP
}, 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
func connectToSignal(ctx context.Context, wtConfig *mgmProto.WiretrusteeConfig, ourPrivateKey wgtypes.Key) (*signal.Client, error) {
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
a, err := ice.NewAgent(&ice.AgentConfig{
// MulticastDNSMode: ice.MulticastDNSModeQueryAndGather,
NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4},
Urls: conn.Config.StunTurnURLS,
NetworkTypes: []ice.NetworkType{ice.NetworkTypeUDP4},
Urls: conn.Config.StunTurnURLS,
CandidateTypes: []ice.CandidateType{ice.CandidateTypeHost, ice.CandidateTypeServerReflexive, ice.CandidateTypeRelay},
InterfaceFilter: func(s string) bool {
if conn.Config.iFaceBlackList == nil {
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
log.Infof("opened connection to peer %s", conn.Config.RemoteWgKey.String())
case <-conn.closeCond.C:

View File

@ -18,13 +18,11 @@ import (
// 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.
const PeerConnectionTimeout = 60 * time.Second
const PeerConnectionTimeout = 40 * time.Second
// EngineConfig is a config for the Engine
type EngineConfig struct {
// StunsTurns is a list of STUN and TURN servers used by ICE
StunsTurns []*ice.URL
WgIface string
WgIface string
// WgAddr is a Wireguard local address (Wiretrustee Network IP)
WgAddr string
// 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 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
@ -187,7 +190,7 @@ func (e *Engine) openPeerConnection(wgPort int, myKey wgtypes.Key, peer Peer) (*
WgAllowedIPs: peer.WgAllowedIps,
WgKey: myKey,
RemoteWgKey: remoteKey,
StunTurnURLS: e.config.StunsTurns,
StunTurnURLS: append(e.STUNs, e.TURNs...),
iFaceBlackList: e.config.IFaceBlackList,
}
@ -261,44 +264,26 @@ func (e *Engine) receiveManagementEvents() {
log.Debugf("connecting to Management Service updates stream")
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()
defer e.syncMsgMux.Unlock()
remotePeers := update.GetRemotePeers()
if len(remotePeers) != 0 {
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 update.GetWiretrusteeConfig() != nil {
err := e.updateTURNs(update.GetWiretrusteeConfig().GetTurns())
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, ","),
})
}
err = e.updateSTUNs(update.GetWiretrusteeConfig().GetStuns())
if err != nil {
return err
}
//todo update signal
}
err := e.updatePeers(update.GetRemotePeers())
if err != nil {
return err
}
return nil
@ -307,6 +292,82 @@ func (e *Engine) receiveManagementEvents() {
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
func (e *Engine) receiveSignalEvents() {
// connect to a stream of messages coming from the signal server

View File

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

View File

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

View File

@ -29,7 +29,7 @@ type Config struct {
type TURNConfig struct {
TimeBasedCredentials bool
CredentialsTTL util.Duration
Secret []byte
Secret string
Turns []*Host
}
@ -51,5 +51,5 @@ type Host struct {
// URI e.g. turns://stun.wiretrustee.com:4430 or signal.wiretrustee.com:10000
URI 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)
}
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
for {
select {

View File

@ -13,7 +13,7 @@
"Proto": "udp",
"URI": "turn:stun.wiretrustee.com:3468",
"Username": "some_user",
"Password": "c29tZV9wYXNzd29yZA=="
"Password": "some_password"
}
],
"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
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()

View File

@ -13,12 +13,12 @@ var TurnTestHost = &Host{
Proto: UDP,
URI: "turn:turn.wiretrustee.com:77777",
Username: "username",
Password: nil,
Password: "",
}
func TestTimeBasedAuthSecretsManager_GenerateCredentials(t *testing.T) {
ttl := util.Duration{Duration: time.Hour}
secret := []byte("some_secret")
secret := "some_secret"
peersManager := NewPeersUpdateManager()
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")
}
validateMAC(credentials.Username, credentials.Password, secret, t)
validateMAC(credentials.Username, credentials.Password, []byte(secret), t)
}
func TestTimeBasedAuthSecretsManager_SetupRefresh(t *testing.T) {
ttl := util.Duration{Duration: 2 * time.Second}
secret := []byte("some_secret")
secret := "some_secret"
peersManager := NewPeersUpdateManager()
peer := "some_peer"
updateChannel := peersManager.CreateChannel(peer)
@ -91,7 +91,7 @@ loop:
func TestTimeBasedAuthSecretsManager_CancelRefresh(t *testing.T) {
ttl := util.Duration{Duration: time.Hour}
secret := []byte("some_secret")
secret := "some_secret"
peersManager := NewPeersUpdateManager()
peer := "some_peer"