mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-22 14:49:10 +02:00
Peer configuration management (#69)
* feature: add config properties to the SyncResponse of the management gRpc service * fix: lint errors * chore: modify management protocol according to the review notes * fix: management proto fields sequence * feature: add proper peer configuration to be synced * chore: minor changes * feature: finalize peer config management * fix: lint errors * feature: add management server config file * refactor: extract hosts-config to a separate file * refactor: review notes applied to correct file_store usage * refactor: extract management service configuration to a file * refactor: simplify management config
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type AccountManager struct {
|
||||
Store Store
|
||||
// mutex to synchronise account operations (e.g. generating Peer IP address inside the Network)
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
// Account represents a unique account of the system
|
||||
type Account struct {
|
||||
Id string
|
||||
SetupKeys map[string]*SetupKey
|
||||
Network *Network
|
||||
Peers []*Peer
|
||||
}
|
||||
|
||||
// SetupKey represents a pre-authorized key used to register machines (peers)
|
||||
// One key might have multiple machines
|
||||
type SetupKey struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
// 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 *SetupKey
|
||||
// IP address of the Peer
|
||||
IP net.IP
|
||||
}
|
||||
|
||||
// NewManager creates a new AccountManager with a provided Store
|
||||
func NewManager(store Store) *AccountManager {
|
||||
return &AccountManager{
|
||||
Store: store,
|
||||
mux: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// GetPeer returns a peer from a Store
|
||||
func (manager *AccountManager) GetPeer(peerKey string) (*Peer, error) {
|
||||
manager.mux.Lock()
|
||||
defer manager.mux.Unlock()
|
||||
|
||||
peer, err := manager.Store.GetPeer(peerKey)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.NotFound, "provided peer key doesn't exists %s", peerKey)
|
||||
}
|
||||
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
// GetPeersForAPeer returns a list of peers available for a given peer (key)
|
||||
// Effectively all the peers of the original peer's account except for the peer itself
|
||||
func (manager *AccountManager) GetPeersForAPeer(peerKey string) ([]*Peer, error) {
|
||||
manager.mux.Lock()
|
||||
defer manager.mux.Unlock()
|
||||
|
||||
account, err := manager.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 {
|
||||
if peer.Key != peerKey {
|
||||
res = append(res, peer)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// 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).
|
||||
func (manager *AccountManager) AddPeer(setupKey string, peerKey string) (*Peer, error) {
|
||||
manager.mux.Lock()
|
||||
defer manager.mux.Unlock()
|
||||
|
||||
account, err := manager.Store.GetAccountBySetupKey(setupKey)
|
||||
if err != nil {
|
||||
//todo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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: peerKey,
|
||||
SetupKey: &SetupKey{Key: setupKey},
|
||||
IP: nextIp,
|
||||
}
|
||||
|
||||
account.Peers = append(account.Peers, newPeer)
|
||||
err = manager.Store.SaveAccount(account)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, "failed adding peer")
|
||||
}
|
||||
|
||||
return newPeer, nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package server
|
||||
|
||||
type Protocol string
|
||||
|
||||
const (
|
||||
UDP Protocol = "udp"
|
||||
DTLS Protocol = "dtls"
|
||||
TCP Protocol = "tcp"
|
||||
HTTP Protocol = "http"
|
||||
HTTPS Protocol = "https"
|
||||
)
|
||||
|
||||
// Config of the Management service
|
||||
type Config struct {
|
||||
Stuns []*Host
|
||||
Turns []*Host
|
||||
Signal *Host
|
||||
|
||||
Datadir string
|
||||
LetsEncryptDomain string
|
||||
}
|
||||
|
||||
// Host represents a Wiretrustee host (e.g. STUN, TURN, Signal)
|
||||
type Host struct {
|
||||
Proto Protocol
|
||||
// URI e.g. turns://stun.wiretrustee.com:4430 or signal.wiretrustee.com:10000
|
||||
URI string
|
||||
Username string
|
||||
Password []byte
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/wiretrustee/wiretrustee/util"
|
||||
)
|
||||
|
||||
// storeFileName Store file name. Stored in the datadir
|
||||
const storeFileName = "store.json"
|
||||
|
||||
// FileStore represents an account storage backed by a file persisted to disk
|
||||
type FileStore struct {
|
||||
Accounts map[string]*Account
|
||||
SetupKeyId2AccountId map[string]string `json:"-"`
|
||||
PeerKeyId2AccountId map[string]string `json:"-"`
|
||||
|
||||
// mutex to synchronise Store read/write operations
|
||||
mux sync.Mutex `json:"-"`
|
||||
storeFile string `json:"-"`
|
||||
}
|
||||
|
||||
type StoredAccount struct {
|
||||
}
|
||||
|
||||
// NewStore restores a store from the file located in the datadir
|
||||
func NewStore(dataDir string) (*FileStore, error) {
|
||||
return restore(filepath.Join(dataDir, storeFileName))
|
||||
}
|
||||
|
||||
// restore restores the state of the store from the file.
|
||||
// Creates a new empty store file if doesn't exist
|
||||
func restore(file string) (*FileStore, error) {
|
||||
|
||||
if _, err := os.Stat(file); os.IsNotExist(err) {
|
||||
// create a new FileStore if previously didn't exist (e.g. first run)
|
||||
s := &FileStore{
|
||||
Accounts: make(map[string]*Account),
|
||||
mux: sync.Mutex{},
|
||||
storeFile: file,
|
||||
}
|
||||
|
||||
err = s.persist(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
read, err := util.ReadJson(file, &FileStore{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store := read.(*FileStore)
|
||||
store.storeFile = file
|
||||
store.SetupKeyId2AccountId = make(map[string]string)
|
||||
store.PeerKeyId2AccountId = make(map[string]string)
|
||||
for accountId, account := range store.Accounts {
|
||||
for setupKeyId := range account.SetupKeys {
|
||||
store.SetupKeyId2AccountId[strings.ToLower(setupKeyId)] = accountId
|
||||
}
|
||||
for _, peer := range account.Peers {
|
||||
store.PeerKeyId2AccountId[strings.ToLower(peer.Key)] = accountId
|
||||
}
|
||||
}
|
||||
|
||||
return store, nil
|
||||
}
|
||||
|
||||
// persist persists account data to a file
|
||||
// It is recommended to call it with locking FileStore.mux
|
||||
func (s *FileStore) persist(file string) error {
|
||||
return util.WriteJson(file, s)
|
||||
}
|
||||
|
||||
// GetPeer returns a peer from a Store
|
||||
func (s *FileStore) GetPeer(peerKey string) (*Peer, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
accountId, accountIdFound := s.PeerKeyId2AccountId[peerKey]
|
||||
if !accountIdFound {
|
||||
return nil, status.Errorf(codes.Internal, "account not found")
|
||||
}
|
||||
|
||||
account, err := s.GetAccount(accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, peer := range account.Peers {
|
||||
if peer.Key == peerKey {
|
||||
return peer, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, status.Errorf(codes.NotFound, "peer not found")
|
||||
}
|
||||
|
||||
// SaveAccount updates an existing account or adds a new one
|
||||
func (s *FileStore) SaveAccount(account *Account) error {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
// todo will override, handle existing keys
|
||||
s.Accounts[account.Id] = account
|
||||
|
||||
// todo check that account.Id and keyId are not exist already
|
||||
// because if keyId exists for other accounts this can be bad
|
||||
for keyId := range account.SetupKeys {
|
||||
s.SetupKeyId2AccountId[strings.ToLower(keyId)] = account.Id
|
||||
}
|
||||
|
||||
for _, peer := range account.Peers {
|
||||
s.PeerKeyId2AccountId[peer.Key] = account.Id
|
||||
}
|
||||
|
||||
err := s.persist(s.storeFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FileStore) GetAccountBySetupKey(setupKey string) (*Account, error) {
|
||||
|
||||
accountId, accountIdFound := s.SetupKeyId2AccountId[strings.ToLower(setupKey)]
|
||||
if !accountIdFound {
|
||||
return nil, status.Errorf(codes.NotFound, "provided setup key doesn't exists")
|
||||
}
|
||||
|
||||
account, err := s.GetAccount(accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (s *FileStore) GetAccount(accountId string) (*Account, error) {
|
||||
|
||||
account, accountFound := s.Accounts[accountId]
|
||||
if !accountFound {
|
||||
return nil, status.Errorf(codes.Internal, "account not found")
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (s *FileStore) GetPeerAccount(peerKey string) (*Account, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
accountId, accountIdFound := s.PeerKeyId2AccountId[peerKey]
|
||||
if !accountIdFound {
|
||||
return nil, status.Errorf(codes.NotFound, "Provided peer key doesn't exists %s", peerKey)
|
||||
}
|
||||
|
||||
return s.GetAccount(accountId)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package server_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
server2 "github.com/wiretrustee/wiretrustee/management/server"
|
||||
server "github.com/wiretrustee/wiretrustee/management/server"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
@@ -33,7 +33,7 @@ var _ = Describe("Management service", func() {
|
||||
|
||||
var (
|
||||
addr string
|
||||
server *grpc.Server
|
||||
s *grpc.Server
|
||||
dataDir string
|
||||
client mgmtProto.ManagementServiceClient
|
||||
serverPubKey wgtypes.Key
|
||||
@@ -50,11 +50,17 @@ var _ = Describe("Management service", func() {
|
||||
err = util.CopyFileContents("testdata/store.json", filepath.Join(dataDir, "store.json"))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var listener net.Listener
|
||||
server, listener = startServer(dataDir)
|
||||
|
||||
config := &server.Config{}
|
||||
_, err = util.ReadJson("testdata/management.json", config)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
config.Datadir = dataDir
|
||||
|
||||
s, listener = startServer(config)
|
||||
addr = listener.Addr().String()
|
||||
client, conn = createRawClient(addr)
|
||||
|
||||
// server public key
|
||||
// s public key
|
||||
resp, err := client.GetServerKey(context.TODO(), &mgmtProto.Empty{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
serverPubKey, err = wgtypes.ParseKey(resp.Key)
|
||||
@@ -63,7 +69,7 @@ var _ = Describe("Management service", func() {
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
server.Stop()
|
||||
s.Stop()
|
||||
err := conn.Close()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = os.RemoveAll(dataDir)
|
||||
@@ -82,6 +88,54 @@ var _ = Describe("Management service", func() {
|
||||
|
||||
Context("when calling Sync endpoint", func() {
|
||||
|
||||
Context("when there is a new peer registered", func() {
|
||||
Specify("a proper configuration is returned", func() {
|
||||
key, _ := wgtypes.GenerateKey()
|
||||
registerPeerWithValidSetupKey(key, client)
|
||||
|
||||
encryptedBytes, err := encryption.EncryptMessage(serverPubKey, key, &mgmtProto.SyncRequest{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
sync, err := client.Sync(context.TODO(), &mgmtProto.EncryptedMessage{
|
||||
WgPubKey: key.PublicKey().String(),
|
||||
Body: encryptedBytes,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
encryptedResponse := &mgmtProto.EncryptedMessage{}
|
||||
err = sync.RecvMsg(encryptedResponse)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
resp := &mgmtProto.SyncResponse{}
|
||||
err = encryption.DecryptMessage(serverPubKey, key, encryptedResponse.Body, resp)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(resp.PeerConfig.Address).To(BeEquivalentTo("100.64.0.1"))
|
||||
|
||||
expectedSignalConfig := &mgmtProto.HostConfig{
|
||||
Uri: "signal.wiretrustee.com:10000",
|
||||
Protocol: mgmtProto.HostConfig_HTTP,
|
||||
}
|
||||
expectedStunsConfig := &mgmtProto.HostConfig{
|
||||
Uri: "stun:stun.wiretrustee.com:3468",
|
||||
Protocol: mgmtProto.HostConfig_UDP,
|
||||
}
|
||||
expectedTurnsConfig := &mgmtProto.ProtectedHostConfig{
|
||||
HostConfig: &mgmtProto.HostConfig{
|
||||
Uri: "turn:stun.wiretrustee.com:3468",
|
||||
Protocol: mgmtProto.HostConfig_UDP,
|
||||
},
|
||||
User: "some_user",
|
||||
Password: "some_password",
|
||||
}
|
||||
|
||||
Expect(resp.WiretrusteeConfig.Signal).To(BeEquivalentTo(expectedSignalConfig))
|
||||
Expect(resp.WiretrusteeConfig.Stuns).To(ConsistOf(expectedStunsConfig))
|
||||
Expect(resp.WiretrusteeConfig.Turns).To(ConsistOf(expectedTurnsConfig))
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
Context("when there are 3 peers registered under one account", func() {
|
||||
Specify("a list containing other 2 peers is returned", func() {
|
||||
key, _ := wgtypes.GenerateKey()
|
||||
@@ -112,8 +166,9 @@ var _ = Describe("Management service", func() {
|
||||
err = pb.Unmarshal(decryptedBytes, resp)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(resp.GetPeers()).To(HaveLen(2))
|
||||
Expect(resp.GetPeers()).To(ContainElements(key1.PublicKey().String(), key2.PublicKey().String()))
|
||||
Expect(resp.GetRemotePeers()).To(HaveLen(2))
|
||||
peers := []string{resp.GetRemotePeers()[0].WgPubKey, resp.GetRemotePeers()[1].WgPubKey}
|
||||
Expect(peers).To(ContainElements(key1.PublicKey().String(), key2.PublicKey().String()))
|
||||
|
||||
})
|
||||
})
|
||||
@@ -143,7 +198,7 @@ var _ = Describe("Management service", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
resp := &mgmtProto.SyncResponse{}
|
||||
err = pb.Unmarshal(decryptedBytes, resp)
|
||||
Expect(resp.GetPeers()).To(HaveLen(0))
|
||||
Expect(resp.GetRemotePeers()).To(HaveLen(0))
|
||||
|
||||
wg := sync2.WaitGroup{}
|
||||
wg.Add(1)
|
||||
@@ -167,8 +222,8 @@ var _ = Describe("Management service", func() {
|
||||
wg.Wait()
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(resp.GetPeers()).To(ContainElements(key1.PublicKey().String()))
|
||||
Expect(resp.GetPeers()).To(HaveLen(1))
|
||||
Expect(resp.GetRemotePeers()).To(HaveLen(1))
|
||||
Expect(resp.GetRemotePeers()[0].WgPubKey).To(BeEquivalentTo(key1.PublicKey().String()))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -291,6 +346,52 @@ var _ = Describe("Management service", func() {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Context("when there are peers registered under one account concurrently", func() {
|
||||
Specify("then there are no duplicate IPs", func() {
|
||||
|
||||
initialPeers := 30
|
||||
|
||||
ipChannel := make(chan string, 20)
|
||||
for i := 0; i < initialPeers; i++ {
|
||||
go func() {
|
||||
key, _ := wgtypes.GenerateKey()
|
||||
registerPeerWithValidSetupKey(key, client)
|
||||
encryptedBytes, err := encryption.EncryptMessage(serverPubKey, key, &mgmtProto.SyncRequest{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// open stream
|
||||
sync, err := client.Sync(context.TODO(), &mgmtProto.EncryptedMessage{
|
||||
WgPubKey: key.PublicKey().String(),
|
||||
Body: encryptedBytes,
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
encryptedResponse := &mgmtProto.EncryptedMessage{}
|
||||
err = sync.RecvMsg(encryptedResponse)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
resp := &mgmtProto.SyncResponse{}
|
||||
err = encryption.DecryptMessage(serverPubKey, key, encryptedResponse.Body, resp)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
ipChannel <- resp.GetPeerConfig().Address
|
||||
|
||||
}()
|
||||
}
|
||||
|
||||
ips := make(map[string]struct{})
|
||||
for ip := range ipChannel {
|
||||
if _, ok := ips[ip]; ok {
|
||||
Fail("found duplicate IP: " + ip)
|
||||
}
|
||||
ips[ip] = struct{}{}
|
||||
if len(ips) == initialPeers {
|
||||
break
|
||||
}
|
||||
}
|
||||
close(ipChannel)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
func registerPeerWithValidSetupKey(key wgtypes.Key, client mgmtProto.ManagementServiceClient) *mgmtProto.RegisterPeerResponse {
|
||||
@@ -320,13 +421,13 @@ func createRawClient(addr string) (mgmtProto.ManagementServiceClient, *grpc.Clie
|
||||
return mgmtProto.NewManagementServiceClient(conn), conn
|
||||
}
|
||||
|
||||
func startServer(dataDir string) (*grpc.Server, net.Listener) {
|
||||
func startServer(config *server.Config) (*grpc.Server, net.Listener) {
|
||||
lis, err := net.Listen("tcp", ":0")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
s := grpc.NewServer()
|
||||
server, err := server2.NewServer(dataDir)
|
||||
mgmtServer, err := server.NewServer(config)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
mgmtProto.RegisterManagementServiceServer(s, server)
|
||||
mgmtProto.RegisterManagementServiceServer(s, mgmtServer)
|
||||
go func() {
|
||||
if err := s.Serve(lis); err != nil {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
var (
|
||||
upperIPv4 = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 255, 255, 255, 255}
|
||||
upperIPv6 = []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||
)
|
||||
|
||||
type Network struct {
|
||||
Id string
|
||||
Net net.IPNet
|
||||
Dns string
|
||||
}
|
||||
|
||||
// AllocatePeerIP pics an available IP from an net.IPNet.
|
||||
// This method considers already taken IPs and reuses IPs if there are gaps in takenIps
|
||||
// E.g. if ipNet=100.30.0.0/16 and takenIps=[100.30.0.1, 100.30.0.5] then the result would be 100.30.0.2
|
||||
func AllocatePeerIP(ipNet net.IPNet, takenIps []net.IP) (net.IP, error) {
|
||||
takenIpMap := make(map[string]net.IP)
|
||||
takenIpMap[ipNet.IP.String()] = ipNet.IP
|
||||
for _, ip := range takenIps {
|
||||
takenIpMap[ip.String()] = ip
|
||||
}
|
||||
for ip := ipNet.IP.Mask(ipNet.Mask); ipNet.Contains(ip); ip = GetNextIP(ip) {
|
||||
if _, ok := takenIpMap[ip.String()]; !ok {
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed allocating new IP for the ipNet %s and takenIps %s", ipNet.String(), takenIps)
|
||||
}
|
||||
|
||||
// GetNextIP returns the next IP from the given IP address. If the given IP is
|
||||
// the last IP of a v4 or v6 range, the same IP is returned.
|
||||
// Credits to Cilium team.
|
||||
// Copyright 2017-2020 Authors of Cilium
|
||||
func GetNextIP(ip net.IP) net.IP {
|
||||
if ip.Equal(upperIPv4) || ip.Equal(upperIPv6) {
|
||||
return ip
|
||||
}
|
||||
|
||||
nextIP := make(net.IP, len(ip))
|
||||
switch len(ip) {
|
||||
case net.IPv4len:
|
||||
ipU32 := binary.BigEndian.Uint32(ip)
|
||||
ipU32++
|
||||
binary.BigEndian.PutUint32(nextIP, ipU32)
|
||||
return nextIP
|
||||
case net.IPv6len:
|
||||
ipU64 := binary.BigEndian.Uint64(ip[net.IPv6len/2:])
|
||||
ipU64++
|
||||
binary.BigEndian.PutUint64(nextIP[net.IPv6len/2:], ipU64)
|
||||
if ipU64 == 0 {
|
||||
ipU64 = binary.BigEndian.Uint64(ip[:net.IPv6len/2])
|
||||
ipU64++
|
||||
binary.BigEndian.PutUint64(nextIP[:net.IPv6len/2], ipU64)
|
||||
} else {
|
||||
copy(nextIP[:net.IPv6len/2], ip[:net.IPv6len/2])
|
||||
}
|
||||
return nextIP
|
||||
default:
|
||||
return ip
|
||||
}
|
||||
}
|
||||
+94
-24
@@ -2,7 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/wiretrustee/wiretrustee/management/store"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -17,33 +17,38 @@ import (
|
||||
|
||||
// Server an instance of a Management server
|
||||
type Server struct {
|
||||
Store *store.FileStore
|
||||
wgKey wgtypes.Key
|
||||
accountManager *AccountManager
|
||||
wgKey wgtypes.Key
|
||||
proto.UnimplementedManagementServiceServer
|
||||
peerChannels map[string]chan *UpdateChannelMessage
|
||||
channelsMux *sync.Mutex
|
||||
config *Config
|
||||
}
|
||||
|
||||
// AllowedIPsFormat generates Wireguard AllowedIPs format (e.g. 100.30.30.1/32)
|
||||
const AllowedIPsFormat = "%s/32"
|
||||
|
||||
type UpdateChannelMessage struct {
|
||||
Update *proto.SyncResponse
|
||||
}
|
||||
|
||||
// NewServer creates a new Management server
|
||||
func NewServer(dataDir string) (*Server, error) {
|
||||
func NewServer(config *Config) (*Server, error) {
|
||||
key, err := wgtypes.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
store, err := store.NewStore(dataDir)
|
||||
store, err := NewStore(config.Datadir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Server{
|
||||
Store: store,
|
||||
wgKey: key,
|
||||
// peerKey -> event channel
|
||||
peerChannels: make(map[string]chan *UpdateChannelMessage),
|
||||
channelsMux: &sync.Mutex{},
|
||||
peerChannels: make(map[string]chan *UpdateChannelMessage),
|
||||
channelsMux: &sync.Mutex{},
|
||||
accountManager: NewManager(store),
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -73,8 +78,8 @@ func (s *Server) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_S
|
||||
return status.Errorf(codes.InvalidArgument, "provided wgPubKey %s is invalid", peerKey.String())
|
||||
}
|
||||
|
||||
exists := s.Store.PeerExists(peerKey.String())
|
||||
if !exists {
|
||||
peer, err := s.accountManager.GetPeer(peerKey.String())
|
||||
if err != nil {
|
||||
return status.Errorf(codes.Unauthenticated, "provided peer with the key wgPubKey %s is not registered", peerKey.String())
|
||||
}
|
||||
|
||||
@@ -84,7 +89,7 @@ func (s *Server) Sync(req *proto.EncryptedMessage, srv proto.ManagementService_S
|
||||
return status.Errorf(codes.InvalidArgument, "invalid request message")
|
||||
}
|
||||
|
||||
err = s.sendInitialSync(peerKey, srv)
|
||||
err = s.sendInitialSync(peerKey, peer, srv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -132,28 +137,28 @@ func (s *Server) RegisterPeer(ctx context.Context, req *proto.RegisterPeerReques
|
||||
s.channelsMux.Lock()
|
||||
defer s.channelsMux.Unlock()
|
||||
|
||||
err := s.Store.AddPeer(req.SetupKey, req.Key)
|
||||
peer, err := s.accountManager.AddPeer(req.SetupKey, req.Key)
|
||||
if err != nil {
|
||||
return &proto.RegisterPeerResponse{}, status.Errorf(codes.NotFound, "provided setup key doesn't exists")
|
||||
}
|
||||
|
||||
peers, err := s.Store.GetPeersForAPeer(req.Key)
|
||||
peers, err := s.accountManager.GetPeersForAPeer(peer.Key)
|
||||
if err != nil {
|
||||
//todo return a proper error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// notify other peers of our registration
|
||||
for _, peer := range peers {
|
||||
if channel, ok := s.peerChannels[peer]; ok {
|
||||
for _, remotePeer := range peers {
|
||||
if channel, ok := s.peerChannels[remotePeer.Key]; ok {
|
||||
// exclude notified peer and add ourselves
|
||||
peersToSend := []string{req.Key}
|
||||
peersToSend := []*Peer{peer}
|
||||
for _, p := range peers {
|
||||
if peer != p {
|
||||
if remotePeer.Key != p.Key {
|
||||
peersToSend = append(peersToSend, p)
|
||||
}
|
||||
}
|
||||
update := &proto.SyncResponse{Peers: peersToSend}
|
||||
update := toSyncResponse(s.config, peer, peersToSend)
|
||||
channel <- &UpdateChannelMessage{Update: update}
|
||||
}
|
||||
}
|
||||
@@ -161,6 +166,73 @@ func (s *Server) RegisterPeer(ctx context.Context, req *proto.RegisterPeerReques
|
||||
return &proto.RegisterPeerResponse{}, nil
|
||||
}
|
||||
|
||||
func toResponseProto(configProto Protocol) proto.HostConfig_Protocol {
|
||||
switch configProto {
|
||||
case UDP:
|
||||
return proto.HostConfig_UDP
|
||||
case DTLS:
|
||||
return proto.HostConfig_DTLS
|
||||
case HTTP:
|
||||
return proto.HostConfig_HTTP
|
||||
case HTTPS:
|
||||
return proto.HostConfig_HTTPS
|
||||
case TCP:
|
||||
return proto.HostConfig_TCP
|
||||
default:
|
||||
//mbragin: todo something better?
|
||||
panic(fmt.Errorf("unexpected config protocol type %v", configProto))
|
||||
}
|
||||
}
|
||||
|
||||
func toSyncResponse(config *Config, peer *Peer, peers []*Peer) *proto.SyncResponse {
|
||||
|
||||
var stuns []*proto.HostConfig
|
||||
for _, stun := range config.Stuns {
|
||||
stuns = append(stuns, &proto.HostConfig{
|
||||
Uri: stun.URI,
|
||||
Protocol: toResponseProto(stun.Proto),
|
||||
})
|
||||
}
|
||||
var turns []*proto.ProtectedHostConfig
|
||||
for _, turn := range config.Turns {
|
||||
turns = append(turns, &proto.ProtectedHostConfig{
|
||||
HostConfig: &proto.HostConfig{
|
||||
Uri: turn.URI,
|
||||
Protocol: toResponseProto(turn.Proto),
|
||||
},
|
||||
User: turn.Username,
|
||||
Password: string(turn.Password),
|
||||
})
|
||||
}
|
||||
|
||||
wtConfig := &proto.WiretrusteeConfig{
|
||||
Stuns: stuns,
|
||||
Turns: turns,
|
||||
Signal: &proto.HostConfig{
|
||||
Uri: config.Signal.URI,
|
||||
Protocol: toResponseProto(config.Signal.Proto),
|
||||
},
|
||||
}
|
||||
|
||||
pConfig := &proto.PeerConfig{
|
||||
Address: peer.IP.String(),
|
||||
}
|
||||
|
||||
remotePeers := make([]*proto.RemotePeerConfig, 0, len(peers))
|
||||
for _, rPeer := range peers {
|
||||
remotePeers = append(remotePeers, &proto.RemotePeerConfig{
|
||||
WgPubKey: rPeer.Key,
|
||||
AllowedIps: []string{fmt.Sprintf(AllowedIPsFormat, rPeer.IP)}, //todo /32
|
||||
})
|
||||
}
|
||||
|
||||
return &proto.SyncResponse{
|
||||
WiretrusteeConfig: wtConfig,
|
||||
PeerConfig: pConfig,
|
||||
RemotePeers: remotePeers,
|
||||
}
|
||||
}
|
||||
|
||||
// IsHealthy indicates whether the service is healthy
|
||||
func (s *Server) IsHealthy(ctx context.Context, req *proto.Empty) (*proto.Empty, error) {
|
||||
return &proto.Empty{}, nil
|
||||
@@ -195,16 +267,14 @@ func (s *Server) closeUpdatesChannel(peerKey string) {
|
||||
}
|
||||
|
||||
// sendInitialSync sends initial proto.SyncResponse to the peer requesting synchronization
|
||||
func (s *Server) sendInitialSync(peerKey wgtypes.Key, srv proto.ManagementService_SyncServer) error {
|
||||
func (s *Server) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.ManagementService_SyncServer) error {
|
||||
|
||||
peers, err := s.Store.GetPeersForAPeer(peerKey.String())
|
||||
peers, err := s.accountManager.GetPeersForAPeer(peer.Key)
|
||||
if err != nil {
|
||||
log.Warnf("error getting a list of peers for a peer %s", peerKey.String())
|
||||
log.Warnf("error getting a list of peers for a peer %s", peer.Key)
|
||||
return err
|
||||
}
|
||||
plainResp := &proto.SyncResponse{
|
||||
Peers: peers,
|
||||
}
|
||||
plainResp := toSyncResponse(s.config, peer, peers)
|
||||
|
||||
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, plainResp)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package server
|
||||
|
||||
type Store interface {
|
||||
GetPeer(peerId string) (*Peer, error)
|
||||
GetAccount(accountId string) (*Account, error)
|
||||
GetPeerAccount(peerId string) (*Account, error)
|
||||
GetAccountBySetupKey(setupKey string) (*Account, error)
|
||||
SaveAccount(account *Account) error
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"Stuns": [
|
||||
{
|
||||
"Proto": "udp",
|
||||
"URI": "stun:stun.wiretrustee.com:3468",
|
||||
"Username": "",
|
||||
"Password": null
|
||||
}
|
||||
],
|
||||
"Turns": [
|
||||
{
|
||||
"Proto": "udp",
|
||||
"URI": "turn:stun.wiretrustee.com:3468",
|
||||
"Username": "some_user",
|
||||
"Password": "c29tZV9wYXNzd29yZA=="
|
||||
}
|
||||
],
|
||||
"Signal": {
|
||||
"Proto": "http",
|
||||
"URI": "signal.wiretrustee.com:10000",
|
||||
"Username": "",
|
||||
"Password": null
|
||||
},
|
||||
"DataDir": ""
|
||||
}
|
||||
+8
-1
@@ -7,7 +7,14 @@
|
||||
"Key": "a2c8e62b-38f5-4553-b31e-dd66c696cebb"
|
||||
}
|
||||
},
|
||||
"Peers": {}
|
||||
"Network": {
|
||||
"Id": "af1c8024-ha40-4ce2-9418-34653101fc3c",
|
||||
"Net": {
|
||||
"IP": "100.64.0.0",
|
||||
"Mask": "/8AAAA=="
|
||||
},
|
||||
"Dns": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user