From e914adb5cdb8a114258d0d7089bcaabaeac50182 Mon Sep 17 00:00:00 2001 From: Misha Bragin Date: Fri, 3 Mar 2023 18:35:38 +0100 Subject: [PATCH] Move Login business logic from gRPC API to Accountmanager (#713) The Management gRPC API has too much business logic happening while it has to be in the Account manager. This also needs to make more requests to the store through the account manager. --- management/server/account.go | 51 ++- management/server/account_test.go | 106 +----- management/server/dns_test.go | 4 +- management/server/grpcserver.go | 267 +++++---------- management/server/management_test.go | 5 +- management/server/mock_server/account_mock.go | 51 ++- management/server/nameserver_test.go | 4 +- management/server/peer.go | 305 ++++++++++++------ management/server/peer_test.go | 40 +-- management/server/status/error.go | 3 + 10 files changed, 385 insertions(+), 451 deletions(-) diff --git a/management/server/account.go b/management/server/account.go index ac00462fa..2edcd8823 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -49,21 +49,18 @@ type AccountManager interface { SaveUser(accountID, userID string, update *User) (*UserInfo, error) GetSetupKey(accountID, userID, keyID string) (*SetupKey, error) GetAccountByUserOrAccountID(userID, accountID, domain string) (*Account, error) - GetAccountByPeerID(peerID string) (*Account, error) GetAccountFromToken(claims jwtclaims.AuthorizationClaims) (*Account, *User, error) IsUserAdmin(claims jwtclaims.AuthorizationClaims) (bool, error) AccountExists(accountId string) (*bool, error) GetPeerByKey(peerKey string) (*Peer, error) GetPeers(accountID, userID string) ([]*Peer, error) MarkPeerConnected(peerKey string, connected bool) error - MarkPeerLoginExpired(peerPubKey string, loginExpired bool) error DeletePeer(accountID, peerID, userID string) (*Peer, error) GetPeerByIP(accountId string, peerIP string) (*Peer, error) UpdatePeer(accountID, userID string, peer *Peer) (*Peer, error) GetNetworkMap(peerID string) (*NetworkMap, error) GetPeerNetwork(peerID string) (*Network, error) - AddPeer(setupKey, userID string, peer *Peer) (*Peer, error) - UpdatePeerMeta(peerID string, meta PeerSystemMeta) error + AddPeer(setupKey, userID string, peer *Peer) (*Peer, *NetworkMap, error) UpdatePeerSSHKey(peerID string, sshKey string) error GetUsersFromAccount(accountID, userID string) ([]*UserInfo, error) GetGroup(accountId, groupID string) (*Group, error) @@ -96,8 +93,9 @@ type AccountManager interface { GetDNSSettings(accountID string, userID string) (*DNSSettings, error) SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error GetPeer(accountID, peerID, userID string) (*Peer, error) - UpdatePeerLastLogin(peerID string) error UpdateAccountSettings(accountID, userID string, newSettings *Settings) (*Account, error) + LoginPeer(login PeerLogin) (*Peer, *NetworkMap, error) //used by peer gRPC API + SyncPeer(sync PeerSync) (*Peer, *NetworkMap, error) //used by peer gRPC API } type DefaultAccountManager struct { @@ -308,6 +306,44 @@ func (a *Account) GetGroup(groupID string) *Group { return a.Groups[groupID] } +// GetPeerNetworkMap returns a group by ID if exists, nil otherwise +func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { + aclPeers := a.getPeersByACL(peerID) + // exclude expired peers + var peersToConnect []*Peer + for _, p := range aclPeers { + expired, _ := p.LoginExpired(a.Settings.PeerLoginExpiration) + if expired { + continue + } + peersToConnect = append(peersToConnect, p) + } + // Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID. + routesUpdate := a.getRoutesToSync(peerID, peersToConnect) + + dnsManagementStatus := a.getPeerDNSManagementStatus(peerID) + dnsUpdate := nbdns.Config{ + ServiceEnable: dnsManagementStatus, + } + + if dnsManagementStatus { + var zones []nbdns.CustomZone + peersCustomZone := getPeersCustomZone(a, dnsDomain) + if peersCustomZone.Domain != "" { + zones = append(zones, peersCustomZone) + } + dnsUpdate.CustomZones = zones + dnsUpdate.NameServerGroups = getPeerNSGroups(a, peerID) + } + + return &NetworkMap{ + Peers: peersToConnect, + Network: a.Network.Copy(), + Routes: routesUpdate, + DNSConfig: dnsUpdate, + } +} + // GetExpiredPeers returns peers that have been expired func (a *Account) GetExpiredPeers() []*Peer { var peers []*Peer @@ -803,11 +839,6 @@ func (am *DefaultAccountManager) warmupIDPCache() error { return nil } -// GetAccountByPeerID returns account from the store by a provided peer ID -func (am *DefaultAccountManager) GetAccountByPeerID(peerID string) (*Account, error) { - return am.Store.GetAccountByPeerID(peerID) -} - // GetAccountByUserOrAccountID looks for an account by user or accountID, if no account is provided and // userID doesn't have an account associated with it, one account is created func (am *DefaultAccountManager) GetAccountByUserOrAccountID(userID, accountID, domain string) (*Account, error) { diff --git a/management/server/account_test.go b/management/server/account_test.go index 23aadcec0..c4f83839d 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -38,7 +38,7 @@ func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Ac setupKey = key.Key } - _, err := manager.AddPeer(setupKey, userID, peer) + _, _, err := manager.AddPeer(setupKey, userID, peer) if err != nil { t.Error("expected to add new peer successfully after creating new account, but failed", err) } @@ -542,10 +542,9 @@ func TestAccountManager_AddPeer(t *testing.T) { expectedPeerKey := key.PublicKey().String() expectedSetupKey := setupKey.Key - peer, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ Key: expectedPeerKey, - Meta: PeerSystemMeta{}, - Name: expectedPeerKey, + Meta: PeerSystemMeta{Hostname: expectedPeerKey}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -611,10 +610,9 @@ func TestAccountManager_AddPeerWithUserID(t *testing.T) { expectedPeerKey := key.PublicKey().String() expectedUserID := userID - peer, err := manager.AddPeer("", userID, &Peer{ + peer, _, err := manager.AddPeer("", userID, &Peer{ Key: expectedPeerKey, - Meta: PeerSystemMeta{}, - Name: expectedPeerKey, + Meta: PeerSystemMeta{Hostname: expectedPeerKey}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v, account users: %v", err, account.CreatedBy) @@ -694,10 +692,9 @@ func TestAccountManager_NetworkUpdates(t *testing.T) { } expectedPeerKey := key.PublicKey().String() - peer, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ Key: expectedPeerKey, - Meta: PeerSystemMeta{}, - Name: expectedPeerKey, + Meta: PeerSystemMeta{Hostname: expectedPeerKey}, }) if err != nil { t.Fatalf("expecting peer1 to be added, got failure %v", err) @@ -864,10 +861,9 @@ func TestAccountManager_DeletePeer(t *testing.T) { peerKey := key.PublicKey().String() - peer, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ Key: peerKey, - Meta: PeerSystemMeta{}, - Name: peerKey, + Meta: PeerSystemMeta{Hostname: peerKey}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -951,75 +947,6 @@ func TestGetUsersFromAccount(t *testing.T) { } } -func TestAccountManager_UpdatePeerMeta(t *testing.T) { - manager, err := createManager(t) - if err != nil { - t.Fatal(err) - return - } - - account, err := createAccount(manager, "test_account", "account_creator", "") - if err != nil { - t.Fatal(err) - } - - var setupKey *SetupKey - for _, key := range account.SetupKeys { - setupKey = key - } - - key, err := wgtypes.GeneratePrivateKey() - if err != nil { - t.Fatal(err) - return - } - - peer, err := manager.AddPeer(setupKey.Key, "", &Peer{ - Key: key.PublicKey().String(), - Meta: PeerSystemMeta{ - Hostname: "Hostname", - GoOS: "GoOS", - Kernel: "Kernel", - Core: "Core", - Platform: "Platform", - OS: "OS", - WtVersion: "WtVersion", - }, - Name: key.PublicKey().String(), - }) - if err != nil { - t.Errorf("expecting peer to be added, got failure %v", err) - return - } - - newMeta := PeerSystemMeta{ - Hostname: "new-Hostname", - GoOS: "new-GoOS", - Kernel: "new-Kernel", - Core: "new-Core", - Platform: "new-Platform", - OS: "new-OS", - WtVersion: "new-WtVersion", - } - err = manager.UpdatePeerMeta(peer.ID, newMeta) - if err != nil { - t.Error(err) - return - } - - p, err := manager.GetPeerByKey(peer.Key) - if err != nil { - return - } - - if err != nil { - t.Fatal(err) - return - } - - assert.Equal(t, newMeta, p.Meta) -} - func TestAccount_GetPeerRules(t *testing.T) { groups := map[string]*Group{ @@ -1302,10 +1229,9 @@ func TestDefaultAccountManager_UpdatePeer_PeerLoginExpiration(t *testing.T) { key, err := wgtypes.GenerateKey() require.NoError(t, err, "unable to generate WireGuard key") - peer, err := manager.AddPeer("", userID, &Peer{ + peer, _, err := manager.AddPeer("", userID, &Peer{ Key: key.PublicKey().String(), - Meta: PeerSystemMeta{}, - Name: "test-peer", + Meta: PeerSystemMeta{Hostname: "test-peer"}, LoginExpirationEnabled: true, }) require.NoError(t, err, "unable to add peer") @@ -1351,10 +1277,9 @@ func TestDefaultAccountManager_MarkPeerConnected_PeerLoginExpiration(t *testing. key, err := wgtypes.GenerateKey() require.NoError(t, err, "unable to generate WireGuard key") - _, err = manager.AddPeer("", userID, &Peer{ + _, _, err = manager.AddPeer("", userID, &Peer{ Key: key.PublicKey().String(), - Meta: PeerSystemMeta{}, - Name: "test-peer", + Meta: PeerSystemMeta{Hostname: "test-peer"}, LoginExpirationEnabled: true, }) require.NoError(t, err, "unable to add peer") @@ -1393,10 +1318,9 @@ func TestDefaultAccountManager_UpdateAccountSettings_PeerLoginExpiration(t *test key, err := wgtypes.GenerateKey() require.NoError(t, err, "unable to generate WireGuard key") - _, err = manager.AddPeer("", userID, &Peer{ + _, _, err = manager.AddPeer("", userID, &Peer{ Key: key.PublicKey().String(), - Meta: PeerSystemMeta{}, - Name: "test-peer", + Meta: PeerSystemMeta{Hostname: "test-peer"}, LoginExpirationEnabled: true, }) require.NoError(t, err, "unable to add peer") diff --git a/management/server/dns_test.go b/management/server/dns_test.go index 155520acb..69a3bf2d9 100644 --- a/management/server/dns_test.go +++ b/management/server/dns_test.go @@ -244,11 +244,11 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro return nil, err } - _, err = am.AddPeer("", dnsAdminUserID, peer1) + _, _, err = am.AddPeer("", dnsAdminUserID, peer1) if err != nil { return nil, err } - _, err = am.AddPeer("", dnsAdminUserID, peer2) + _, _, err = am.AddPeer("", dnsAdminUserID, peer2) if err != nil { return nil, err } diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index c1e2c5cfd..a75daa4f3 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -3,6 +3,7 @@ package server import ( "context" "fmt" + pb "github.com/golang/protobuf/proto" //nolint "strings" "time" @@ -118,44 +119,18 @@ func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementServi log.Debugf("Sync request from peer [%s] [%s]", req.WgPubKey, p.Addr.String()) } - peerKey, err := wgtypes.ParseKey(req.GetWgPubKey()) - if err != nil { - log.Warnf("error while parsing peer's Wireguard public key %s on Sync request.", peerKey.String()) - return status.Errorf(codes.InvalidArgument, "provided wgPubKey %s is invalid", peerKey.String()) - } - - peer, err := s.accountManager.GetPeerByKey(peerKey.String()) - if err != nil { - p, _ := gRPCPeer.FromContext(srv.Context()) - msg := status.Errorf(codes.PermissionDenied, "provided peer with the key wgPubKey %s is not registered, remote addr is %s", peerKey.String(), p.Addr.String()) - log.Debug(msg) - return msg - } - - account, err := s.accountManager.GetAccountByPeerID(peer.ID) - if err != nil { - return status.Error(codes.Internal, "internal server error") - } - expired, left := peer.LoginExpired(account.Settings.PeerLoginExpiration) - expired = account.Settings.PeerLoginExpirationEnabled && expired - if peer.UserID != "" && (expired || peer.Status.LoginExpired) { - err = s.accountManager.MarkPeerLoginExpired(peerKey.String(), true) - if err != nil { - log.Warnf("failed marking peer login expired %s %v", peerKey, err) - } - return status.Errorf(codes.PermissionDenied, "peer login has expired %v ago. Please log in once more", left) - } - syncReq := &proto.SyncRequest{} - err = encryption.DecryptMessage(peerKey, s.wgKey, req.Body, syncReq) + peerKey, err := s.parseRequest(req, syncReq) if err != nil { - p, _ := gRPCPeer.FromContext(srv.Context()) - msg := status.Errorf(codes.InvalidArgument, "invalid request message from %s,remote addr is %s", peerKey.String(), p.Addr.String()) - log.Debug(msg) - return msg + return err } - err = s.sendInitialSync(peerKey, peer, srv) + peer, netMap, err := s.accountManager.SyncPeer(PeerSync{WireGuardPubKey: peerKey.String()}) + if err != nil { + return mapError(err) + } + + err = s.sendInitialSync(peerKey, peer, netMap, srv) if err != nil { log.Debugf("error while sending initial sync for %s: %v", peerKey.String(), err) return err @@ -218,7 +193,7 @@ func (s *GRPCServer) validateToken(jwtToken string) (string, error) { token, err := s.jwtMiddleware.ValidateAndParse(jwtToken) if err != nil { - return "", status.Errorf(codes.Internal, "invalid jwt token, err: %v", err) + return "", status.Errorf(codes.InvalidArgument, "invalid jwt token, err: %v", err) } claims := s.jwtClaimsExtractor.FromToken(token) // we need to call this method because if user is new, we will automatically add it to existing or create a new account @@ -230,84 +205,52 @@ func (s *GRPCServer) validateToken(jwtToken string) (string, error) { return claims.UserId, nil } -func (s *GRPCServer) registerPeer(peerKey wgtypes.Key, req *proto.LoginRequest) (*Peer, error) { - var ( - reqSetupKey string - userID string - err error - ) - - if req.GetJwtToken() != "" { - log.Debugln("using jwt token to register peer") - userID, err = s.validateToken(req.JwtToken) - if err != nil { - return nil, err +// maps internal internalStatus.Error to gRPC status.Error +func mapError(err error) error { + if e, ok := internalStatus.FromError(err); ok { + switch e.Type() { + case internalStatus.PermissionDenied: + return status.Errorf(codes.PermissionDenied, e.Message) + case internalStatus.Unauthorized: + return status.Errorf(codes.PermissionDenied, e.Message) + case internalStatus.Unauthenticated: + return status.Errorf(codes.PermissionDenied, e.Message) + case internalStatus.PreconditionFailed: + return status.Errorf(codes.FailedPrecondition, e.Message) + case internalStatus.NotFound: + return status.Errorf(codes.NotFound, e.Message) + default: } - } else { - log.Debugln("using setup key to register peer") - reqSetupKey = req.GetSetupKey() - userID = "" } + return status.Errorf(codes.Internal, "failed handling request") +} - meta := req.GetMeta() - if meta == nil { - return nil, status.Errorf(codes.InvalidArgument, "peer meta data was not provided") +func extractPeerMeta(loginReq *proto.LoginRequest) PeerSystemMeta { + return PeerSystemMeta{ + Hostname: loginReq.GetMeta().GetHostname(), + GoOS: loginReq.GetMeta().GetGoOS(), + Kernel: loginReq.GetMeta().GetKernel(), + Core: loginReq.GetMeta().GetCore(), + Platform: loginReq.GetMeta().GetPlatform(), + OS: loginReq.GetMeta().GetOS(), + WtVersion: loginReq.GetMeta().GetWiretrusteeVersion(), + UIVersion: loginReq.GetMeta().GetUiVersion(), } +} - var sshKey []byte - if req.GetPeerKeys() != nil { - sshKey = req.GetPeerKeys().GetSshPubKey() - } - - peer, err := s.accountManager.AddPeer(reqSetupKey, userID, &Peer{ - Key: peerKey.String(), - Name: meta.GetHostname(), - SSHKey: string(sshKey), - Meta: PeerSystemMeta{ - Hostname: meta.GetHostname(), - GoOS: meta.GetGoOS(), - Kernel: meta.GetKernel(), - Core: meta.GetCore(), - Platform: meta.GetPlatform(), - OS: meta.GetOS(), - WtVersion: meta.GetWiretrusteeVersion(), - UIVersion: meta.GetUiVersion(), - }, - }) +func (s *GRPCServer) parseRequest(req *proto.EncryptedMessage, parsed pb.Message) (wgtypes.Key, error) { + peerKey, err := wgtypes.ParseKey(req.GetWgPubKey()) if err != nil { - if e, ok := internalStatus.FromError(err); ok { - switch e.Type() { - case internalStatus.PreconditionFailed: - return nil, status.Errorf(codes.FailedPrecondition, e.Message) - case internalStatus.NotFound: - return nil, status.Errorf(codes.NotFound, e.Message) - default: - } - } - return nil, status.Errorf(codes.Internal, "failed registering new peer") + log.Warnf("error while parsing peer's WireGuard public key %s.", req.WgPubKey) + return wgtypes.Key{}, status.Errorf(codes.InvalidArgument, "provided wgPubKey %s is invalid", req.WgPubKey) } - // todo move to DefaultAccountManager the code below - networkMap, err := s.accountManager.GetNetworkMap(peer.ID) + err = encryption.DecryptMessage(peerKey, s.wgKey, req.Body, parsed) if err != nil { - return nil, status.Errorf(codes.Internal, "unable to fetch network map after registering peer, error: %v", err) - } - // notify other peers of our registration - for _, remotePeer := range networkMap.Peers { - remotePeerNetworkMap, err := s.accountManager.GetNetworkMap(remotePeer.ID) - if err != nil { - return nil, status.Errorf(codes.Internal, "unable to fetch network map after registering peer, error: %v", err) - } - - update := toSyncResponse(s.config, remotePeer, nil, remotePeerNetworkMap, s.accountManager.GetDNSDomain()) - err = s.peersUpdateManager.SendUpdate(remotePeer.ID, &UpdateMessage{Update: update}) - if err != nil { - // todo rethink if we should keep this return - return nil, status.Errorf(codes.Internal, "unable to send update after registering peer, error: %v", err) - } + return wgtypes.Key{}, status.Errorf(codes.InvalidArgument, "invalid request message") } - return peer, nil + return peerKey, nil } // Login endpoint first checks whether peer is registered under any account @@ -323,113 +266,55 @@ func (s *GRPCServer) Login(ctx context.Context, req *proto.EncryptedMessage) (*p log.Debugf("Login request from peer [%s] [%s]", req.WgPubKey, p.Addr.String()) } - peerKey, err := wgtypes.ParseKey(req.GetWgPubKey()) - if err != nil { - log.Warnf("error while parsing peer's Wireguard public key %s on Sync request.", req.WgPubKey) - return nil, status.Errorf(codes.InvalidArgument, "provided wgPubKey %s is invalid", req.WgPubKey) - } - loginReq := &proto.LoginRequest{} - err = encryption.DecryptMessage(peerKey, s.wgKey, req.Body, loginReq) + peerKey, err := s.parseRequest(req, loginReq) if err != nil { - return nil, status.Errorf(codes.InvalidArgument, "invalid request message") + return nil, err } - peer, err := s.accountManager.GetPeerByKey(peerKey.String()) - if err != nil { - if errStatus, ok := internalStatus.FromError(err); ok && errStatus.Type() == internalStatus.NotFound { - // peer doesn't exist -> check if setup key was provided - if loginReq.GetJwtToken() == "" && loginReq.GetSetupKey() == "" { - // absent setup key or jwt -> permission denied - p, _ := gRPCPeer.FromContext(ctx) - msg := status.Errorf(codes.PermissionDenied, - "provided peer with the key wgPubKey %s is not registered and no setup key or jwt was provided,"+ - " remote addr is %s", peerKey.String(), p.Addr.String()) - log.Debug(msg) - return nil, msg - } + if loginReq.GetMeta() == nil { + msg := status.Errorf(codes.FailedPrecondition, + "peer system meta has to be provided to log in. Peer %s, remote addr %s", peerKey.String(), + p.Addr.String()) + log.Warn(msg) + return nil, msg + } - // setup key or jwt is present -> try normal registration flow - peer, err = s.registerPeer(peerKey, loginReq) - if err != nil { - return nil, err - } - - } else { - return nil, status.Error(codes.Internal, "internal server error") - } - } else if loginReq.GetMeta() != nil { - // update peer's system meta data on Login - err = s.accountManager.UpdatePeerMeta(peer.ID, PeerSystemMeta{ - Hostname: loginReq.GetMeta().GetHostname(), - GoOS: loginReq.GetMeta().GetGoOS(), - Kernel: loginReq.GetMeta().GetKernel(), - Core: loginReq.GetMeta().GetCore(), - Platform: loginReq.GetMeta().GetPlatform(), - OS: loginReq.GetMeta().GetOS(), - WtVersion: loginReq.GetMeta().GetWiretrusteeVersion(), - UIVersion: loginReq.GetMeta().GetUiVersion(), - }, - ) + userID := "" + // JWT token is not always provided, it is fine for userID to be empty cuz it might be that peer is already registered, + // or it uses a setup key to register. + if loginReq.GetJwtToken() != "" { + userID, err = s.validateToken(loginReq.GetJwtToken()) if err != nil { - log.Errorf("failed updating peer system meta data %s", peerKey.String()) - return nil, status.Error(codes.Internal, "internal server error") + log.Warnf("failed validating JWT token sent from peer %s", peerKey) + return nil, mapError(err) } } - - // check if peer login has expired - account, err := s.accountManager.GetAccountByPeerID(peer.ID) - if err != nil { - return nil, status.Error(codes.Internal, "internal server error") - } - - expired, left := peer.LoginExpired(account.Settings.PeerLoginExpiration) - expired = account.Settings.PeerLoginExpirationEnabled && expired - if peer.UserID != "" && (expired || peer.Status.LoginExpired) { - // it might be that peer expired but user has logged in already, check token then - if loginReq.GetJwtToken() == "" { - err = s.accountManager.MarkPeerLoginExpired(peerKey.String(), true) - if err != nil { - log.Warnf("failed marking peer login expired %s %v", peerKey, err) - } - return nil, status.Errorf(codes.PermissionDenied, - "peer login has expired %v ago. Please log in once more", left) - } - _, err = s.validateToken(loginReq.GetJwtToken()) - if err != nil { - return nil, err - } - - err = s.accountManager.UpdatePeerLastLogin(peer.ID) - if err != nil { - return nil, err - } - } - var sshKey []byte if loginReq.GetPeerKeys() != nil { sshKey = loginReq.GetPeerKeys().GetSshPubKey() } - if len(sshKey) > 0 { - err = s.accountManager.UpdatePeerSSHKey(peer.ID, string(sshKey)) - if err != nil { - return nil, err - } - } - - network, err := s.accountManager.GetPeerNetwork(peer.ID) + peer, netMap, err := s.accountManager.LoginPeer(PeerLogin{ + WireGuardPubKey: peerKey.String(), + SSHKey: string(sshKey), + Meta: extractPeerMeta(loginReq), + UserID: userID, + SetupKey: loginReq.GetSetupKey(), + }) if err != nil { - return nil, status.Errorf(codes.Internal, "failed getting peer network on login") + log.Warnf("failed logging in peer %s", peerKey) + return nil, mapError(err) } // if peer has reached this point then it has logged in loginResp := &proto.LoginResponse{ WiretrusteeConfig: toWiretrusteeConfig(s.config, nil), - PeerConfig: toPeerConfig(peer, network, s.accountManager.GetDNSDomain()), + PeerConfig: toPeerConfig(peer, netMap.Network, s.accountManager.GetDNSDomain()), } encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, loginResp) if err != nil { + log.Warnf("failed encrypting peer %s message", peer.ID) return nil, status.Errorf(codes.Internal, "failed logging in peer") } @@ -555,13 +440,7 @@ func (s *GRPCServer) IsHealthy(ctx context.Context, req *proto.Empty) (*proto.Em } // sendInitialSync sends initial proto.SyncResponse to the peer requesting synchronization -func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *Peer, srv proto.ManagementService_SyncServer) error { - networkMap, err := s.accountManager.GetNetworkMap(peer.ID) - if err != nil { - log.Warnf("error getting a list of peers for a peer %s", peer.ID) - return err - } - +func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *Peer, networkMap *NetworkMap, srv proto.ManagementService_SyncServer) error { // make secret time based TURN credentials optional var turnCredentials *TURNCredentials if s.config.TURNConfig.TimeBasedCredentials { diff --git a/management/server/management_test.go b/management/server/management_test.go index 565451548..978d33f3c 100644 --- a/management/server/management_test.go +++ b/management/server/management_test.go @@ -240,7 +240,8 @@ var _ = Describe("Management service", func() { Context("with an invalid setup key", func() { Specify("an error is returned", func() { key, _ := wgtypes.GenerateKey() - message, err := encryption.EncryptMessage(serverPubKey, key, &mgmtProto.LoginRequest{SetupKey: "invalid setup key"}) + message, err := encryption.EncryptMessage(serverPubKey, key, &mgmtProto.LoginRequest{SetupKey: "invalid setup key", + Meta: &mgmtProto.PeerSystemMeta{}}) Expect(err).NotTo(HaveOccurred()) resp, err := client.Login(context.TODO(), &mgmtProto.EncryptedMessage{ @@ -269,7 +270,7 @@ var _ = Describe("Management service", func() { Expect(regResp).NotTo(BeNil()) // just login without registration - message, err := encryption.EncryptMessage(serverPubKey, key, &mgmtProto.LoginRequest{}) + message, err := encryption.EncryptMessage(serverPubKey, key, &mgmtProto.LoginRequest{Meta: &mgmtProto.PeerSystemMeta{}}) Expect(err).NotTo(HaveOccurred()) loginResp, err := client.Login(context.TODO(), &mgmtProto.EncryptedMessage{ WgPubKey: key.PublicKey().String(), diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index 1776c62aa..713ccee18 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -25,12 +25,11 @@ type MockAccountManager struct { GetPeerByKeyFunc func(peerKey string) (*server.Peer, error) GetPeersFunc func(accountID, userID string) ([]*server.Peer, error) MarkPeerConnectedFunc func(peerKey string, connected bool) error - MarkPeerLoginExpiredFunc func(peerPubKey string, loginExpired bool) error DeletePeerFunc func(accountID, peerKey, userID string) (*server.Peer, error) GetPeerByIPFunc func(accountId string, peerIP string) (*server.Peer, error) GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error) GetPeerNetworkFunc func(peerKey string) (*server.Network, error) - AddPeerFunc func(setupKey string, userId string, peer *server.Peer) (*server.Peer, error) + AddPeerFunc func(setupKey string, userId string, peer *server.Peer) (*server.Peer, *server.NetworkMap, error) GetGroupFunc func(accountID, groupID string) (*server.Group, error) SaveGroupFunc func(accountID, userID string, group *server.Group) error UpdateGroupFunc func(accountID string, groupID string, operations []server.GroupUpdateOperation) (*server.Group, error) @@ -70,9 +69,9 @@ type MockAccountManager struct { GetDNSSettingsFunc func(accountID, userID string) (*server.DNSSettings, error) SaveDNSSettingsFunc func(accountID, userID string, dnsSettingsToSave *server.DNSSettings) error GetPeerFunc func(accountID, peerID, userID string) (*server.Peer, error) - GetAccountByPeerIDFunc func(peerID string) (*server.Account, error) - UpdatePeerLastLoginFunc func(peerID string) error UpdateAccountSettingsFunc func(accountID, userID string, newSettings *server.Settings) (*server.Account, error) + LoginPeerFunc func(login server.PeerLogin) (*server.Peer, *server.NetworkMap, error) + SyncPeerFunc func(sync server.PeerSync) (*server.Peer, *server.NetworkMap, error) } // GetUsersFromAccount mock implementation of GetUsersFromAccount from server.AccountManager interface @@ -165,14 +164,6 @@ func (am *MockAccountManager) MarkPeerConnected(peerKey string, connected bool) return status.Errorf(codes.Unimplemented, "method MarkPeerConnected is not implemented") } -// MarkPeerLoginExpired mock implementation of MarkPeerLoginExpired from server.AccountManager interface -func (am *MockAccountManager) MarkPeerLoginExpired(peerPubKey string, loginExpired bool) error { - if am.MarkPeerLoginExpiredFunc != nil { - return am.MarkPeerLoginExpiredFunc(peerPubKey, loginExpired) - } - return status.Errorf(codes.Unimplemented, "method MarkPeerLoginExpired is not implemented") -} - // GetPeerByIP mock implementation of GetPeerByIP from server.AccountManager interface func (am *MockAccountManager) GetPeerByIP(accountId string, peerIP string) (*server.Peer, error) { if am.GetPeerByIPFunc != nil { @@ -202,11 +193,11 @@ func (am *MockAccountManager) AddPeer( setupKey string, userId string, peer *server.Peer, -) (*server.Peer, error) { +) (*server.Peer, *server.NetworkMap, error) { if am.AddPeerFunc != nil { return am.AddPeerFunc(setupKey, userId, peer) } - return nil, status.Errorf(codes.Unimplemented, "method AddPeer is not implemented") + return nil, nil, status.Errorf(codes.Unimplemented, "method AddPeer is not implemented") } // GetGroup mock implementation of GetGroup from server.AccountManager interface @@ -541,22 +532,6 @@ func (am *MockAccountManager) GetPeer(accountID, peerID, userID string) (*server return nil, status.Errorf(codes.Unimplemented, "method GetPeer is not implemented") } -// GetAccountByPeerID mocks GetAccountByPeerID of the AccountManager interface -func (am *MockAccountManager) GetAccountByPeerID(peerID string) (*server.Account, error) { - if am.GetAccountByPeerIDFunc != nil { - return am.GetAccountByPeerIDFunc(peerID) - } - return nil, status.Errorf(codes.Unimplemented, "method GetAccountByPeerID is not implemented") -} - -// UpdatePeerLastLogin mocks UpdatePeerLastLogin of the AccountManager interface -func (am *MockAccountManager) UpdatePeerLastLogin(peerID string) error { - if am.UpdatePeerLastLoginFunc != nil { - return am.UpdatePeerLastLoginFunc(peerID) - } - return status.Errorf(codes.Unimplemented, "method UpdatePeerLastLogin is not implemented") -} - // UpdateAccountSettings mocks UpdateAccountSettings of the AccountManager interface func (am *MockAccountManager) UpdateAccountSettings(accountID, userID string, newSettings *server.Settings) (*server.Account, error) { if am.UpdateAccountSettingsFunc != nil { @@ -564,3 +539,19 @@ func (am *MockAccountManager) UpdateAccountSettings(accountID, userID string, ne } return nil, status.Errorf(codes.Unimplemented, "method UpdateAccountSettings is not implemented") } + +// LoginPeer mocks LoginPeer of the AccountManager interface +func (am *MockAccountManager) LoginPeer(login server.PeerLogin) (*server.Peer, *server.NetworkMap, error) { + if am.LoginPeerFunc != nil { + return am.LoginPeerFunc(login) + } + return nil, nil, status.Errorf(codes.Unimplemented, "method LoginPeer is not implemented") +} + +// SyncPeer mocks SyncPeer of the AccountManager interface +func (am *MockAccountManager) SyncPeer(sync server.PeerSync) (*server.Peer, *server.NetworkMap, error) { + if am.SyncPeerFunc != nil { + return am.SyncPeerFunc(sync) + } + return nil, nil, status.Errorf(codes.Unimplemented, "method SyncPeer is not implemented") +} diff --git a/management/server/nameserver_test.go b/management/server/nameserver_test.go index 71fa5e814..fde144b26 100644 --- a/management/server/nameserver_test.go +++ b/management/server/nameserver_test.go @@ -1147,11 +1147,11 @@ func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error return nil, err } - _, err = am.AddPeer("", userID, peer1) + _, _, err = am.AddPeer("", userID, peer1) if err != nil { return nil, err } - _, err = am.AddPeer("", userID, peer2) + _, _, err = am.AddPeer("", userID, peer2) if err != nil { return nil, err } diff --git a/management/server/peer.go b/management/server/peer.go index dc22a7a51..e30b3ef46 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -2,7 +2,6 @@ package server import ( "fmt" - nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/status" "github.com/rs/xid" @@ -36,6 +35,26 @@ type PeerStatus struct { LoginExpired bool } +// PeerSync used as a data object between the gRPC API and AccountManager on Sync request. +type PeerSync struct { + // WireGuardPubKey is a peers WireGuard public key + WireGuardPubKey string +} + +// PeerLogin used as a data object between the gRPC API and AccountManager on Login request. +type PeerLogin struct { + // WireGuardPubKey is a peers WireGuard public key + WireGuardPubKey string + // SSHKey is a peer's ssh key. Can be empty (e.g., old version do not provide it, or this feature is disabled) + SSHKey string + // Meta is the system information passed by peer, must be always present. + Meta PeerSystemMeta + // UserID indicates that JWT was used to log in, and it was valid. Can be empty when SetupKey is used or auth is not required. + UserID string + // SetupKey references to a server.SetupKey to log in. Can be empty when UserID is used or auth is not required. + SetupKey string +} + // Peer represents a machine connected to the network. // The Peer is a WireGuard peer identified by a public key type Peer struct { @@ -93,6 +112,15 @@ func (p *Peer) Copy() *Peer { } } +// UpdateMeta updates peer's system meta data +func (p *Peer) UpdateMeta(meta PeerSystemMeta) { + // Avoid overwriting UIVersion if the update was triggered sole by the CLI client + if meta.UIVersion == "" { + meta.UIVersion = p.Meta.UIVersion + } + p.Meta = meta +} + // MarkLoginExpired marks peer's status expired or not func (p *Peer) MarkLoginExpired(expired bool) { newStatus := p.Status.Copy() @@ -194,6 +222,18 @@ func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, er return peers, nil } +func (am *DefaultAccountManager) markPeerLoginExpired(peer *Peer, account *Account, expired bool) (*Peer, error) { + peer.MarkLoginExpired(expired) + account.UpdatePeer(peer) + + err := am.Store.SavePeerStatus(account.Id, peer.ID, *peer.Status) + if err != nil { + return nil, err + } + + return peer, nil +} + // MarkPeerLoginExpired when peer login has expired func (am *DefaultAccountManager) MarkPeerLoginExpired(peerPubKey string, loginExpired bool) error { account, err := am.Store.GetAccountByPeerPubKey(peerPubKey) @@ -423,6 +463,10 @@ func (am *DefaultAccountManager) GetPeerByIP(accountID string, peerIP string) (* return nil, status.Errorf(status.NotFound, "peer with IP %s not found", peerIP) } +func (am *DefaultAccountManager) getNetworkMap(peer *Peer, account *Account) *NetworkMap { + return account.GetPeerNetworkMap(peer.ID, am.dnsDomain) +} + // GetNetworkMap returns Network map for a given peer (omits original peer from the Peers result) func (am *DefaultAccountManager) GetNetworkMap(peerID string) (*NetworkMap, error) { @@ -436,40 +480,7 @@ func (am *DefaultAccountManager) GetNetworkMap(peerID string) (*NetworkMap, erro return nil, status.Errorf(status.NotFound, "peer with ID %s not found", peerID) } - aclPeers := account.getPeersByACL(peerID) - // exclude expired peers - var peersToConnect []*Peer - for _, p := range aclPeers { - expired, _ := peer.LoginExpired(account.Settings.PeerLoginExpiration) - if expired { - continue - } - peersToConnect = append(peersToConnect, p) - } - // Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID. - routesUpdate := account.getRoutesToSync(peerID, peersToConnect) - - dnsManagementStatus := account.getPeerDNSManagementStatus(peerID) - dnsUpdate := nbdns.Config{ - ServiceEnable: dnsManagementStatus, - } - - if dnsManagementStatus { - var zones []nbdns.CustomZone - peersCustomZone := getPeersCustomZone(account, am.dnsDomain) - if peersCustomZone.Domain != "" { - zones = append(zones, peersCustomZone) - } - dnsUpdate.CustomZones = zones - dnsUpdate.NameServerGroups = getPeerNSGroups(account, peerID) - } - - return &NetworkMap{ - Peers: peersToConnect, - Network: account.Network.Copy(), - Routes: routesUpdate, - DNSConfig: dnsUpdate, - }, err + return am.getNetworkMap(peer, account), nil } // GetPeerNetwork returns the Network for a given peer @@ -484,13 +495,17 @@ func (am *DefaultAccountManager) GetPeerNetwork(peerID string) (*Network, error) } // 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 with a code codes.Unauthenticated -// will be returned, meaning the key is invalid +// Each Account has a list of pre-authorized SetupKey and if no Account has a given key err with a code status.PermissionDenied +// will be returned, meaning the setup key is invalid or not found. // If a User ID is provided, it means that we passed the authentication using JWT, then we look for account by User ID and register the peer -// to it. We also add the User ID to the peer metadata to identify registrant. +// to it. We also add the User ID to the peer metadata to identify registrant. If no userID provided, then fail with status.PermissionDenied // 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 *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (*Peer, error) { +func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (*Peer, *NetworkMap, error) { + if setupKey == "" && userID == "" { + // no auth method provided => reject access + return nil, nil, status.Errorf(status.Unauthenticated, "no peer auth method provided, please use a setup key or interactive SSO login") + } upperKey := strings.ToUpper(setupKey) var account *Account @@ -503,7 +518,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* account, err = am.Store.GetAccountBySetupKey(setupKey) } if err != nil { - return nil, status.Errorf(status.NotFound, "failed adding new peer: account not found") + return nil, nil, status.Errorf(status.NotFound, "failed adding new peer: account not found") } unlock := am.Store.AcquireAccountLock(account.Id) @@ -512,7 +527,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* // ensure that we consider modification happened meanwhile (because we were outside the account lock when we fetched the account) account, err = am.Store.GetAccount(account.Id) if err != nil { - return nil, err + return nil, nil, err } opEvent := &activity.Event{ @@ -524,11 +539,11 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* // validate the setup key if adding with a key sk, err := account.FindSetupKey(upperKey) if err != nil { - return nil, err + return nil, nil, err } if !sk.IsValid() { - return nil, status.Errorf(status.PreconditionFailed, "couldn't add peer: setup key is invalid") + return nil, nil, status.Errorf(status.PreconditionFailed, "couldn't add peer: setup key is invalid") } account.SetupKeys[sk.Key] = sk.IncrementUsage() @@ -542,16 +557,16 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* takenIps := account.getTakenIPs() existingLabels := account.getPeerDNSLabels() - newLabel, err := getPeerHostLabel(peer.Name, existingLabels) + newLabel, err := getPeerHostLabel(peer.Meta.Hostname, existingLabels) if err != nil { - return nil, err + return nil, nil, err } peer.DNSLabel = newLabel network := account.Network nextIp, err := AllocatePeerIP(network.Net, takenIps) if err != nil { - return nil, err + return nil, nil, err } newPeer := &Peer{ @@ -560,7 +575,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* SetupKey: upperKey, IP: nextIp, Meta: peer.Meta, - Name: peer.Name, + Name: peer.Meta.Hostname, DNSLabel: newLabel, UserID: userID, Status: &PeerStatus{Connected: false, LastSeen: time.Now()}, @@ -573,7 +588,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* // add peer to 'All' group group, err := account.GetGroupAll() if err != nil { - return nil, err + return nil, nil, err } group.Peers = append(group.Peers, newPeer.ID) @@ -581,12 +596,12 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* if addedByUser { groupsToAdd, err = account.getUserGroups(userID) if err != nil { - return nil, err + return nil, nil, err } } else { groupsToAdd, err = account.getSetupKeyGroups(upperKey) if err != nil { - return nil, err + return nil, nil, err } } @@ -602,50 +617,173 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* account.Network.IncSerial() err = am.Store.SaveAccount(account) if err != nil { - return nil, err + return nil, nil, err } opEvent.TargetID = newPeer.ID opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain()) am.storeEvent(opEvent.InitiatorID, opEvent.TargetID, opEvent.AccountID, opEvent.Activity, opEvent.Meta) - return newPeer, nil -} - -// UpdatePeerLastLogin sets Peer.LastLogin to the current timestamp. -func (am *DefaultAccountManager) UpdatePeerLastLogin(peerID string) error { - account, err := am.Store.GetAccountByPeerID(peerID) + err = am.updateAccountPeers(account) if err != nil { - return err + return nil, nil, err } + networkMap := account.GetPeerNetworkMap(peer.ID, am.dnsDomain) + return newPeer, networkMap, nil +} + +func (am *DefaultAccountManager) checkPeerLoginExpiration(loginUserID string, peer *Peer, account *Account) error { + if peer.AddedWithSSOLogin() { + expired, expiresIn := peer.LoginExpired(account.Settings.PeerLoginExpiration) + expired = account.Settings.PeerLoginExpirationEnabled && expired + if expired || peer.Status.LoginExpired { + log.Debugf("peer %s login expired", peer.ID) + if loginUserID == "" { + // absence of a user ID indicates that JWT wasn't provided. + _, err := am.markPeerLoginExpired(peer, account, true) + if err != nil { + return err + } + return status.Errorf(status.PermissionDenied, + "peer login has expired %v ago. Please log in once more", expiresIn) + } + // user ID is there meaning that JWT validation passed successfully in the API layer. + if peer.UserID != loginUserID { + log.Warnf("user mismatch when loggin in peer %s: peer user %s, login user %s ", peer.ID, peer.UserID, loginUserID) + return status.Errorf(status.Unauthenticated, "can't login") + } + _ = am.updatePeerLastLogin(peer, account) + } + } + + return nil +} + +// SyncPeer checks whether peer is eligible for receiving NetworkMap (authenticated) and returns its NetworkMap if eligible +func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*Peer, *NetworkMap, error) { + account, err := am.Store.GetAccountByPeerPubKey(sync.WireGuardPubKey) + if err != nil { + if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound { + return nil, nil, status.Errorf(status.Unauthenticated, "peer is not registered") + } + return nil, nil, err + } + + // we found the peer, and we follow a normal login flow unlock := am.Store.AcquireAccountLock(account.Id) defer unlock() - // ensure that we consider modification happened meanwhile (because we were outside the account lock when we fetched the account) + // fetch the account from the store once more after acquiring lock to avoid concurrent updates inconsistencies account, err = am.Store.GetAccount(account.Id) if err != nil { - return err + return nil, nil, err } - peer := account.GetPeer(peerID) - if peer == nil { - return status.Errorf(status.NotFound, "peer with ID %s not found", peerID) + peer, err := account.FindPeerByPubKey(sync.WireGuardPubKey) + if err != nil { + return nil, nil, status.Errorf(status.Unauthenticated, "peer is not registered") } + err = am.checkPeerLoginExpiration("", peer, account) + if err != nil { + return nil, nil, err + } + + return peer, am.getNetworkMap(peer, account), nil + +} + +// LoginPeer logs in or registers a peer. +// If peer doesn't exist the function checks whether a setup key or a user is present and registers a new peer if so. +func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*Peer, *NetworkMap, error) { + + account, err := am.Store.GetAccountByPeerPubKey(login.WireGuardPubKey) + if err != nil { + if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound { + // we couldn't find this peer by its public key which can mean that peer hasn't been registered yet. + // Try registering it. + return am.AddPeer(login.SetupKey, login.UserID, &Peer{ + Key: login.WireGuardPubKey, + Meta: login.Meta, + SSHKey: login.SSHKey, + }) + } + log.Errorf("failed while logging in peer %s: %v", login.WireGuardPubKey, err) + return nil, nil, status.Errorf(status.Internal, "failed while logging in peer") + } + + // we found the peer, and we follow a normal login flow + unlock := am.Store.AcquireAccountLock(account.Id) + defer unlock() + + // fetch the account from the store once more after acquiring lock to avoid concurrent updates inconsistencies + account, err = am.Store.GetAccount(account.Id) + if err != nil { + return nil, nil, err + } + + peer, err := account.FindPeerByPubKey(login.WireGuardPubKey) + if err != nil { + return nil, nil, status.Errorf(status.Unauthenticated, "peer is not registered") + } + + err = am.checkPeerLoginExpiration(login.UserID, peer, account) + if err != nil { + return nil, nil, err + } + + peer = updatePeerMeta(peer, login.Meta, account) + + peer, err = am.checkAndUpdatePeerSSHKey(peer, account, login.SSHKey) + if err != nil { + return nil, nil, err + } + + err = am.Store.SaveAccount(account) + if err != nil { + return nil, nil, err + } + networkMap := account.GetPeerNetworkMap(peer.ID, am.dnsDomain) + return peer, networkMap, nil + +} + +func (am *DefaultAccountManager) updatePeerLastLogin(peer *Peer, account *Account) *Peer { peer.LastLogin = time.Now() newStatus := peer.Status.Copy() newStatus.LoginExpired = false peer.Status = newStatus - account.UpdatePeer(peer) + return peer +} - err = am.Store.SaveAccount(account) - if err != nil { - return err +func (am *DefaultAccountManager) checkAndUpdatePeerSSHKey(peer *Peer, account *Account, newSSHKey string) (*Peer, error) { + if len(newSSHKey) == 0 { + log.Debugf("no new SSH key provided for peer %s, skipping update", peer.ID) + return peer, nil } - return nil + if peer.SSHKey == newSSHKey { + log.Debugf("same SSH key provided for peer %s, skipping update", peer.ID) + return peer, nil + } + + peer.SSHKey = newSSHKey + account.UpdatePeer(peer) + + err := am.Store.SaveAccount(account) + if err != nil { + return nil, err + } + + // trigger network map update + err = am.updateAccountPeers(account) + if err != nil { + return nil, err + } + + return peer, nil } // UpdatePeerSSHKey updates peer's public SSH key @@ -737,35 +875,10 @@ func (am *DefaultAccountManager) GetPeer(accountID, peerID, userID string) (*Pee return nil, status.Errorf(status.Internal, "user %s has no access to peer %s under account %s", userID, peerID, accountID) } -// UpdatePeerMeta updates peer's system metadata -func (am *DefaultAccountManager) UpdatePeerMeta(peerID string, meta PeerSystemMeta) error { - - account, err := am.Store.GetAccountByPeerID(peerID) - if err != nil { - return err - } - - unlock := am.Store.AcquireAccountLock(account.Id) - defer unlock() - - peer := account.GetPeer(peerID) - if peer == nil { - return status.Errorf(status.NotFound, "peer with ID %s not found", peerID) - } - - // Avoid overwriting UIVersion if the update was triggered sole by the CLI client - if meta.UIVersion == "" { - meta.UIVersion = peer.Meta.UIVersion - } - - peer.Meta = meta +func updatePeerMeta(peer *Peer, meta PeerSystemMeta, account *Account) *Peer { + peer.UpdateMeta(meta) account.UpdatePeer(peer) - - err = am.Store.SaveAccount(account) - if err != nil { - return err - } - return nil + return peer } // getPeersByACL returns all peers that given peer has access to. diff --git a/management/server/peer_test.go b/management/server/peer_test.go index 6e635349d..c9168efa5 100644 --- a/management/server/peer_test.go +++ b/management/server/peer_test.go @@ -91,10 +91,9 @@ func TestAccountManager_GetNetworkMap(t *testing.T) { return } - peer1, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer1, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ Key: peerKey1.PublicKey().String(), - Meta: PeerSystemMeta{}, - Name: "test-peer-2", + Meta: PeerSystemMeta{Hostname: "test-peer-1"}, }) if err != nil { @@ -107,10 +106,9 @@ func TestAccountManager_GetNetworkMap(t *testing.T) { t.Fatal(err) return } - _, err = manager.AddPeer(setupKey.Key, "", &Peer{ + _, _, err = manager.AddPeer(setupKey.Key, "", &Peer{ Key: peerKey2.PublicKey().String(), - Meta: PeerSystemMeta{}, - Name: "test-peer-2", + Meta: PeerSystemMeta{Hostname: "test-peer-2"}, }) if err != nil { @@ -164,10 +162,9 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) { return } - peer1, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer1, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ Key: peerKey1.PublicKey().String(), - Meta: PeerSystemMeta{}, - Name: "test-peer-2", + Meta: PeerSystemMeta{Hostname: "test-peer-1"}, }) if err != nil { @@ -180,10 +177,9 @@ func TestAccountManager_GetNetworkMapWithRule(t *testing.T) { t.Fatal(err) return } - peer2, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer2, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ Key: peerKey2.PublicKey().String(), - Meta: PeerSystemMeta{}, - Name: "test-peer-2", + Meta: PeerSystemMeta{Hostname: "test-peer-2"}, }) if err != nil { @@ -333,10 +329,9 @@ func TestAccountManager_GetPeerNetwork(t *testing.T) { return } - peer1, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer1, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ Key: peerKey1.PublicKey().String(), - Meta: PeerSystemMeta{}, - Name: "test-peer-2", + Meta: PeerSystemMeta{Hostname: "test-peer-1"}, }) if err != nil { @@ -349,10 +344,9 @@ func TestAccountManager_GetPeerNetwork(t *testing.T) { t.Fatal(err) return } - _, err = manager.AddPeer(setupKey.Key, "", &Peer{ + _, _, err = manager.AddPeer(setupKey.Key, "", &Peer{ Key: peerKey2.PublicKey().String(), - Meta: PeerSystemMeta{}, - Name: "test-peer-2", + Meta: PeerSystemMeta{Hostname: "test-peer-2"}, }) if err != nil { @@ -402,10 +396,9 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) { return } - peer1, err := manager.AddPeer("", someUser, &Peer{ + peer1, _, err := manager.AddPeer("", someUser, &Peer{ Key: peerKey1.PublicKey().String(), - Meta: PeerSystemMeta{}, - Name: "test-peer-2", + Meta: PeerSystemMeta{Hostname: "test-peer-2"}, }) if err != nil { @@ -420,10 +413,9 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) { } // the second peer added with a setup key - peer2, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer2, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ Key: peerKey2.PublicKey().String(), - Meta: PeerSystemMeta{}, - Name: "test-peer-2", + Meta: PeerSystemMeta{Hostname: "test-peer-2"}, }) if err != nil { diff --git a/management/server/status/error.go b/management/server/status/error.go index 6d6299449..1ced95e09 100644 --- a/management/server/status/error.go +++ b/management/server/status/error.go @@ -31,6 +31,9 @@ const ( // BadRequest indicates that user is not authorized BadRequest Type = 9 + + // Unauthenticated indicates that user is not authenticated due to absence of valid credentials + Unauthenticated Type = 10 ) // Type is a type of the Error