netbird/management/server/peer.go
Mikhail Bragin afb302d5e7
Change Management Sync protocol to support incremental (serial) network changes (#191)
* feature: introduce NetworkMap to the management protocol with a Serial ID

* test: add Management Sync method protocol test

* test: add Management Sync method NetworkMap field check [FAILING]

* test: add Management Sync method NetworkMap field check [FAILING]

* feature: fill NetworkMap property to when Deleting peer

* feature: fill NetworkMap in the Sync protocol

* test: code review mentions - GeneratePrivateKey() in the test

* fix: wiretrustee client use wireguard GeneratePrivateKey() instead of GenerateKey()

* test: add NetworkMap test

* fix: management_proto test remove store.json on test finish
2022-01-16 17:10:36 +01:00

296 lines
7.1 KiB
Go

package server
import (
"github.com/wiretrustee/wiretrustee/management/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"net"
"strings"
"time"
)
// PeerSystemMeta is a metadata of a Peer machine system
type PeerSystemMeta struct {
Hostname string
GoOS string
Kernel string
Core string
Platform string
OS string
WtVersion string
}
type PeerStatus struct {
//LastSeen is the last time peer was connected to the management service
LastSeen time.Time
//Connected indicates whether peer is connected to the management service or not
Connected bool
}
//Peer represents a machine connected to the network.
//The Peer is a Wireguard peer identified by a public key
type Peer struct {
//Wireguard public key
Key string
//A setup key this peer was registered with
SetupKey string
//IP address of the Peer
IP net.IP
//Meta is a Peer system meta data
Meta PeerSystemMeta
//Name is peer's name (machine name)
Name string
Status *PeerStatus
}
//Copy copies Peer object
func (p *Peer) Copy() *Peer {
return &Peer{
Key: p.Key,
SetupKey: p.SetupKey,
IP: p.IP,
Meta: p.Meta,
Name: p.Name,
Status: p.Status,
}
}
//GetPeer returns a peer from a Store
func (am *AccountManager) GetPeer(peerKey string) (*Peer, error) {
am.mux.Lock()
defer am.mux.Unlock()
peer, err := am.Store.GetPeer(peerKey)
if err != nil {
return nil, err
}
return peer, nil
}
//MarkPeerConnected marks peer as connected (true) or disconnected (false)
func (am *AccountManager) MarkPeerConnected(peerKey string, connected bool) error {
am.mux.Lock()
defer am.mux.Unlock()
peer, err := am.Store.GetPeer(peerKey)
if err != nil {
return err
}
account, err := am.Store.GetPeerAccount(peerKey)
if err != nil {
return err
}
peerCopy := peer.Copy()
peerCopy.Status.LastSeen = time.Now()
peerCopy.Status.Connected = connected
err = am.Store.SavePeer(account.Id, peerCopy)
if err != nil {
return err
}
return nil
}
//RenamePeer changes peer's name
func (am *AccountManager) RenamePeer(accountId string, peerKey string, newName string) (*Peer, error) {
am.mux.Lock()
defer am.mux.Unlock()
peer, err := am.Store.GetPeer(peerKey)
if err != nil {
return nil, err
}
peerCopy := peer.Copy()
peerCopy.Name = newName
err = am.Store.SavePeer(accountId, peerCopy)
if err != nil {
return nil, err
}
return peerCopy, nil
}
//DeletePeer removes peer from the account by it's IP
func (am *AccountManager) DeletePeer(accountId string, peerKey string) (*Peer, error) {
am.mux.Lock()
defer am.mux.Unlock()
account, err := am.Store.GetAccount(accountId)
if err != nil {
return nil, status.Errorf(codes.NotFound, "account not found")
}
peer, err := am.Store.DeletePeer(accountId, peerKey)
if err != nil {
return nil, err
}
account.Network.IncSerial()
err = am.Store.SaveAccount(account)
if err != nil {
return nil, err
}
err = am.peersUpdateManager.SendUpdate(peerKey,
&UpdateMessage{
Update: &proto.SyncResponse{
// fill those field for backward compatibility
RemotePeers: []*proto.RemotePeerConfig{},
RemotePeersIsEmpty: true,
// new field
NetworkMap: &proto.NetworkMap{
Serial: account.Network.Serial(),
RemotePeers: []*proto.RemotePeerConfig{},
RemotePeersIsEmpty: true,
},
}})
if err != nil {
return nil, err
}
//notify other peers of the change
peers, err := am.Store.GetAccountPeers(accountId)
if err != nil {
return nil, err
}
for _, p := range peers {
peersToSend := []*Peer{}
for _, remote := range peers {
if p.Key != remote.Key {
peersToSend = append(peersToSend, remote)
}
}
update := toRemotePeerConfig(peersToSend)
err = am.peersUpdateManager.SendUpdate(p.Key,
&UpdateMessage{
Update: &proto.SyncResponse{
// fill those field for backward compatibility
RemotePeers: update,
RemotePeersIsEmpty: len(update) == 0,
// new field
NetworkMap: &proto.NetworkMap{
Serial: account.Network.Serial(),
RemotePeers: update,
RemotePeersIsEmpty: len(update) == 0,
},
}})
if err != nil {
return nil, err
}
}
am.peersUpdateManager.CloseChannel(peerKey)
return peer, nil
}
//GetPeerByIP returns peer by it's IP
func (am *AccountManager) GetPeerByIP(accountId string, peerIP string) (*Peer, error) {
am.mux.Lock()
defer am.mux.Unlock()
account, err := am.Store.GetAccount(accountId)
if err != nil {
return nil, status.Errorf(codes.NotFound, "account not found")
}
for _, peer := range account.Peers {
if peerIP == peer.IP.String() {
return peer, nil
}
}
return nil, status.Errorf(codes.NotFound, "peer with IP %s not found", peerIP)
}
// GetNetworkMap returns Network map for a given peer (omits original peer from the Peers result)
func (am *AccountManager) GetNetworkMap(peerKey string) (*NetworkMap, error) {
am.mux.Lock()
defer am.mux.Unlock()
account, err := am.Store.GetPeerAccount(peerKey)
if err != nil {
return nil, status.Errorf(codes.Internal, "Invalid peer key %s", peerKey)
}
var res []*Peer
for _, peer := range account.Peers {
// exclude original peer
if peer.Key != peerKey {
res = append(res, peer.Copy())
}
}
return &NetworkMap{
Peers: res,
Network: account.Network.Copy(),
}, err
}
// AddPeer adds a new peer to the Store.
// Each Account has a list of pre-authorised SetupKey and if no Account has a given key err wit ha code codes.Unauthenticated
// will be returned, meaning the key is invalid
// Each new Peer will be assigned a new next net.IP from the Account.Network and Account.Network.LastIP will be updated (IP's are not reused).
// The peer property is just a placeholder for the Peer properties to pass further
func (am *AccountManager) AddPeer(setupKey string, peer *Peer) (*Peer, error) {
am.mux.Lock()
defer am.mux.Unlock()
upperKey := strings.ToUpper(setupKey)
var account *Account
var err error
var sk *SetupKey
if len(upperKey) == 0 {
// Empty setup key, fail
return nil, status.Errorf(codes.InvalidArgument, "empty setupKey %s", setupKey)
} else {
account, err = am.Store.GetAccountBySetupKey(upperKey)
if err != nil {
return nil, status.Errorf(codes.NotFound, "unknown setupKey %s", upperKey)
}
sk = getAccountSetupKeyByKey(account, upperKey)
if sk == nil {
// shouldn't happen actually
return nil, status.Errorf(codes.NotFound, "unknown setupKey %s", upperKey)
}
}
if !sk.IsValid() {
return nil, status.Errorf(codes.FailedPrecondition, "setup key was expired or overused %s", upperKey)
}
var takenIps []net.IP
for _, peer := range account.Peers {
takenIps = append(takenIps, peer.IP)
}
network := account.Network
nextIp, _ := AllocatePeerIP(network.Net, takenIps)
newPeer := &Peer{
Key: peer.Key,
SetupKey: sk.Key,
IP: nextIp,
Meta: peer.Meta,
Name: peer.Name,
Status: &PeerStatus{Connected: false, LastSeen: time.Now()},
}
account.Peers[newPeer.Key] = newPeer
account.SetupKeys[sk.Key] = sk.IncrementUsage()
account.Network.IncSerial()
err = am.Store.SaveAccount(account)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed adding peer")
}
return newPeer, nil
}