diff --git a/README.md b/README.md index 370445412..1c5e76627 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@
- +

diff --git a/client/internal/auth/device_flow.go b/client/internal/auth/device_flow.go index 3c51fe4f5..87d00de5e 100644 --- a/client/internal/auth/device_flow.go +++ b/client/internal/auth/device_flow.go @@ -3,6 +3,7 @@ package auth import ( "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -180,7 +181,7 @@ func (d *DeviceAuthorizationFlow) WaitToken(ctx context.Context, info AuthFlowIn continue } - return TokenInfo{}, fmt.Errorf(tokenResponse.ErrorDescription) + return TokenInfo{}, errors.New(tokenResponse.ErrorDescription) } tokenInfo := TokenInfo{ diff --git a/client/internal/engine.go b/client/internal/engine.go index 9e275c007..d65322d6a 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -960,9 +960,9 @@ func (e *Engine) connWorker(conn *peer.Conn, peerKey string) { for { // randomize starting time a bit - min := 500 - max := 2000 - duration := time.Duration(rand.Intn(max-min)+min) * time.Millisecond + minValue := 500 + maxValue := 2000 + duration := time.Duration(rand.Intn(maxValue-minValue)+minValue) * time.Millisecond select { case <-e.ctx.Done(): return diff --git a/client/internal/routemanager/sysctl/sysctl_linux.go b/client/internal/routemanager/sysctl/sysctl_linux.go index 3f2937c89..43394a823 100644 --- a/client/internal/routemanager/sysctl/sysctl_linux.go +++ b/client/internal/routemanager/sysctl/sysctl_linux.go @@ -1,4 +1,5 @@ -// go:build !android +//go:build !android + package sysctl import ( diff --git a/client/ssh/server.go b/client/ssh/server.go index ae5c65c4a..a390302b7 100644 --- a/client/ssh/server.go +++ b/client/ssh/server.go @@ -118,9 +118,9 @@ func (srv *DefaultServer) publicKeyHandler(ctx ssh.Context, key ssh.PublicKey) b func prepareUserEnv(user *user.User, shell string) []string { return []string{ - fmt.Sprintf("SHELL=" + shell), - fmt.Sprintf("USER=" + user.Username), - fmt.Sprintf("HOME=" + user.HomeDir), + fmt.Sprint("SHELL=" + shell), + fmt.Sprint("USER=" + user.Username), + fmt.Sprint("HOME=" + user.HomeDir), } } diff --git a/client/ui/netbird-systemtray-disconnected.png b/client/ui/netbird-systemtray-disconnected.png index 0e1b7275f..3aae73231 100644 Binary files a/client/ui/netbird-systemtray-disconnected.png and b/client/ui/netbird-systemtray-disconnected.png differ diff --git a/client/ui/netbird-systemtray-update-disconnected.png b/client/ui/netbird-systemtray-update-disconnected.png index 44a30dc9a..3fbe88953 100644 Binary files a/client/ui/netbird-systemtray-update-disconnected.png and b/client/ui/netbird-systemtray-update-disconnected.png differ diff --git a/management/client/grpc.go b/management/client/grpc.go index eaadcd317..74e808c32 100644 --- a/management/client/grpc.go +++ b/management/client/grpc.go @@ -2,6 +2,7 @@ package client import ( "context" + "errors" "fmt" "io" "sync" @@ -267,7 +268,7 @@ func (c *GrpcClient) receiveEvents(stream proto.ManagementService_SyncClient, se // GetServerPublicKey returns server's WireGuard public key (used later for encrypting messages sent to the server) func (c *GrpcClient) GetServerPublicKey() (*wgtypes.Key, error) { if !c.ready() { - return nil, fmt.Errorf(errMsgNoMgmtConnection) + return nil, errors.New(errMsgNoMgmtConnection) } mgmCtx, cancel := context.WithTimeout(c.ctx, 5*time.Second) @@ -314,7 +315,7 @@ func (c *GrpcClient) IsHealthy() bool { func (c *GrpcClient) login(serverKey wgtypes.Key, req *proto.LoginRequest) (*proto.LoginResponse, error) { if !c.ready() { - return nil, fmt.Errorf(errMsgNoMgmtConnection) + return nil, errors.New(errMsgNoMgmtConnection) } loginReq, err := encryption.EncryptMessage(serverKey, c.key, req) @@ -452,7 +453,7 @@ func (c *GrpcClient) GetPKCEAuthorizationFlow(serverKey wgtypes.Key) (*proto.PKC // It should be used if there is changes on peer posture check after initial sync. func (c *GrpcClient) SyncMeta(sysInfo *system.Info) error { if !c.ready() { - return fmt.Errorf(errMsgNoMgmtConnection) + return errors.New(errMsgNoMgmtConnection) } serverPubKey, err := c.GetServerPublicKey() diff --git a/management/server/account.go b/management/server/account.go index 972272746..4c150fd7e 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -2072,6 +2072,28 @@ func (am *DefaultAccountManager) GetAccountIDForPeerKey(ctx context.Context, pee return am.Store.GetAccountIDByPeerPubKey(ctx, peerKey) } +func (am *DefaultAccountManager) handleUserPeer(ctx context.Context, peer *nbpeer.Peer, settings *Settings) (bool, error) { + user, err := am.Store.GetUserByUserID(ctx, peer.UserID) + if err != nil { + return false, err + } + + err = checkIfPeerOwnerIsBlocked(peer, user) + if err != nil { + return false, err + } + + if peerLoginExpired(ctx, peer, settings) { + err = am.handleExpiredPeer(ctx, user, peer) + if err != nil { + return false, err + } + return true, nil + } + + return false, nil +} + // addAllGroup to account object if it doesn't exist func addAllGroup(account *Account) error { if len(account.Groups) == 0 { diff --git a/management/server/file_store.go b/management/server/file_store.go index 6e3536bcd..1927568ef 100644 --- a/management/server/file_store.go +++ b/management/server/file_store.go @@ -469,6 +469,35 @@ func (s *FileStore) GetUserByTokenID(_ context.Context, tokenID string) (*User, return account.Users[userID].Copy(), nil } +func (s *FileStore) GetUserByUserID(_ context.Context, userID string) (*User, error) { + accountID, ok := s.UserID2AccountID[userID] + if !ok { + return nil, status.Errorf(status.NotFound, "accountID not found: provided userID doesn't exists") + } + + account, err := s.getAccount(accountID) + if err != nil { + return nil, err + } + + return account.Users[userID].Copy(), nil +} + +func (s *FileStore) GetAccountGroups(ctx context.Context, accountID string) ([]*nbgroup.Group, error) { + account, err := s.getAccount(accountID) + if err != nil { + return nil, err + } + + groupsSlice := make([]*nbgroup.Group, 0, len(account.Groups)) + + for _, group := range account.Groups { + groupsSlice = append(groupsSlice, group) + } + + return groupsSlice, nil +} + // GetAllAccounts returns all accounts func (s *FileStore) GetAllAccounts(_ context.Context) (all []*Account) { s.mux.Lock() diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index 7738abe5e..ff7a71cfd 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -257,7 +257,7 @@ func (s *GRPCServer) validateToken(ctx context.Context, jwtToken string) (string } if err := s.accountManager.CheckUserAccessByJWTGroups(ctx, claims); err != nil { - return "", status.Errorf(codes.PermissionDenied, err.Error()) + return "", status.Error(codes.PermissionDenied, err.Error()) } return claims.UserId, nil @@ -268,15 +268,15 @@ func mapError(ctx context.Context, err error) error { if e, ok := internalStatus.FromError(err); ok { switch e.Type() { case internalStatus.PermissionDenied: - return status.Errorf(codes.PermissionDenied, e.Message) + return status.Error(codes.PermissionDenied, e.Message) case internalStatus.Unauthorized: - return status.Errorf(codes.PermissionDenied, e.Message) + return status.Error(codes.PermissionDenied, e.Message) case internalStatus.Unauthenticated: - return status.Errorf(codes.PermissionDenied, e.Message) + return status.Error(codes.PermissionDenied, e.Message) case internalStatus.PreconditionFailed: - return status.Errorf(codes.FailedPrecondition, e.Message) + return status.Error(codes.FailedPrecondition, e.Message) case internalStatus.NotFound: - return status.Errorf(codes.NotFound, e.Message) + return status.Error(codes.NotFound, e.Message) default: } } diff --git a/management/server/http/posture_checks_handler_test.go b/management/server/http/posture_checks_handler_test.go index dcb6e4924..974edafde 100644 --- a/management/server/http/posture_checks_handler_test.go +++ b/management/server/http/posture_checks_handler_test.go @@ -46,7 +46,7 @@ func initPostureChecksTestData(postureChecks ...*posture.Checks) *PostureChecksH testPostureChecks[postureChecks.ID] = postureChecks if err := postureChecks.Validate(); err != nil { - return status.Errorf(status.InvalidArgument, err.Error()) + return status.Errorf(status.InvalidArgument, err.Error()) //nolint } return nil diff --git a/management/server/idp/auth0_test.go b/management/server/idp/auth0_test.go index de42ced99..f8a0e1210 100644 --- a/management/server/idp/auth0_test.go +++ b/management/server/idp/auth0_test.go @@ -3,6 +3,7 @@ package idp import ( "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -44,14 +45,14 @@ type mockJsonParser struct { func (m *mockJsonParser) Marshal(v interface{}) ([]byte, error) { if m.marshalErrorString != "" { - return nil, fmt.Errorf(m.marshalErrorString) + return nil, errors.New(m.marshalErrorString) } return m.jsonParser.Marshal(v) } func (m *mockJsonParser) Unmarshal(data []byte, v interface{}) error { if m.unmarshalErrorString != "" { - return fmt.Errorf(m.unmarshalErrorString) + return errors.New(m.unmarshalErrorString) } return m.jsonParser.Unmarshal(data, v) } diff --git a/management/server/jwtclaims/jwtValidator.go b/management/server/jwtclaims/jwtValidator.go index c3417a769..39676982e 100644 --- a/management/server/jwtclaims/jwtValidator.go +++ b/management/server/jwtclaims/jwtValidator.go @@ -150,7 +150,7 @@ func (m *JWTValidator) ValidateAndParse(ctx context.Context, token string) (*jwt // If we get here, the required token is missing errorMsg := "required authorization token not found" log.WithContext(ctx).Debugf(" Error: No credentials found (CredentialsOptional=false)") - return nil, fmt.Errorf(errorMsg) + return nil, errors.New(errorMsg) } // Now parse the token @@ -173,7 +173,7 @@ func (m *JWTValidator) ValidateAndParse(ctx context.Context, token string) (*jwt // Check if the parsed token is valid... if !parsedToken.Valid { errorMsg := "token is invalid" - log.WithContext(ctx).Debugf(errorMsg) + log.WithContext(ctx).Debug(errorMsg) return nil, errors.New(errorMsg) } diff --git a/management/server/peer.go b/management/server/peer.go index 7afe6ee0d..93234d9de 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -549,16 +549,25 @@ func (am *DefaultAccountManager) SyncPeer(ctx context.Context, sync PeerSync, ac return nil, nil, nil, status.NewPeerNotRegisteredError() } - err = checkIfPeerOwnerIsBlocked(peer, account) - if err != nil { - return nil, nil, nil, err + if peer.UserID != "" { + log.Infof("Peer has no userID") + + user, err := account.FindUser(peer.UserID) + if err != nil { + return nil, nil, nil, err + } + + err = checkIfPeerOwnerIsBlocked(peer, user) + if err != nil { + return nil, nil, nil, err + } } if peerLoginExpired(ctx, peer, account.Settings) { return nil, nil, nil, status.NewPeerLoginExpiredError() } - peer, updated := updatePeerMeta(peer, sync.Meta, account) + updated := peer.UpdateMetaIfNew(sync.Meta) if updated { err = am.Store.SavePeer(ctx, account.Id, peer) if err != nil { @@ -624,31 +633,28 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login PeerLogin) // it means that the client has already checked if it needs login and had been through the SSO flow // so, we can skip this check and directly proceed with the login if login.UserID == "" { + log.Info("Peer needs login") err = am.checkIFPeerNeedsLoginWithoutLock(ctx, accountID, login) if err != nil { return nil, nil, nil, err } } - unlock := am.Store.AcquireWriteLockByUID(ctx, accountID) + unlockAccount := am.Store.AcquireReadLockByUID(ctx, accountID) + defer unlockAccount() + unlockPeer := am.Store.AcquireWriteLockByUID(ctx, login.WireGuardPubKey) defer func() { - if unlock != nil { - unlock() + if unlockPeer != nil { + unlockPeer() } }() - // fetch the account from the store once more after acquiring lock to avoid concurrent updates inconsistencies - account, err := am.Store.GetAccount(ctx, accountID) + peer, err := am.Store.GetPeerByPeerPubKey(ctx, login.WireGuardPubKey) if err != nil { return nil, nil, nil, err } - peer, err := account.FindPeerByPubKey(login.WireGuardPubKey) - if err != nil { - return nil, nil, nil, status.NewPeerNotRegisteredError() - } - - err = checkIfPeerOwnerIsBlocked(peer, account) + settings, err := am.Store.GetAccountSettings(ctx, accountID) if err != nil { return nil, nil, nil, err } @@ -656,21 +662,39 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login PeerLogin) // this flag prevents unnecessary calls to the persistent store. shouldStorePeer := false updateRemotePeers := false - if peerLoginExpired(ctx, peer, account.Settings) { - err = am.handleExpiredPeer(ctx, login, account, peer) + + if login.UserID != "" { + changed, err := am.handleUserPeer(ctx, peer, settings) if err != nil { return nil, nil, nil, err } - updateRemotePeers = true - shouldStorePeer = true + if changed { + shouldStorePeer = true + updateRemotePeers = true + } } - isRequiresApproval, isStatusChanged, err := am.integratedPeerValidator.IsNotValidPeer(ctx, account.Id, peer, account.GetPeerGroupsList(peer.ID), account.Settings.Extra) + groups, err := am.Store.GetAccountGroups(ctx, accountID) if err != nil { return nil, nil, nil, err } - peer, updated := updatePeerMeta(peer, login.Meta, account) + var grps []string + for _, group := range groups { + for _, id := range group.Peers { + if id == peer.ID { + grps = append(grps, group.ID) + break + } + } + } + + isRequiresApproval, isStatusChanged, err := am.integratedPeerValidator.IsNotValidPeer(ctx, accountID, peer, grps, settings.Extra) + if err != nil { + return nil, nil, nil, err + } + + updated := peer.UpdateMetaIfNew(login.Meta) if updated { shouldStorePeer = true } @@ -687,8 +711,13 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login PeerLogin) } } - unlock() - unlock = nil + unlockPeer() + unlockPeer = nil + + account, err := am.Store.GetAccount(ctx, accountID) + if err != nil { + return nil, nil, nil, err + } if updateRemotePeers || isStatusChanged { am.updateAccountPeers(ctx, account) @@ -746,36 +775,30 @@ func (am *DefaultAccountManager) getValidatedPeerWithMap(ctx context.Context, is return peer, account.GetPeerNetworkMap(ctx, peer.ID, customZone, approvedPeersMap, am.metrics.AccountManagerMetrics()), postureChecks, nil } -func (am *DefaultAccountManager) handleExpiredPeer(ctx context.Context, login PeerLogin, account *Account, peer *nbpeer.Peer) error { - err := checkAuth(ctx, login.UserID, peer) +func (am *DefaultAccountManager) handleExpiredPeer(ctx context.Context, user *User, peer *nbpeer.Peer) error { + err := checkAuth(ctx, user.Id, peer) if err != nil { return err } // If peer was expired before and if it reached this point, it is re-authenticated. // UserID is present, meaning that JWT validation passed successfully in the API layer. - updatePeerLastLogin(peer, account) - - // sync user last login with peer last login - user, err := account.FindUser(login.UserID) - if err != nil { - return status.Errorf(status.Internal, "couldn't find user") - } - - err = am.Store.SaveUserLastLogin(account.Id, user.Id, peer.LastLogin) + peer = peer.UpdateLastLogin() + err = am.Store.SavePeer(ctx, peer.AccountID, peer) if err != nil { return err } - am.StoreEvent(ctx, login.UserID, peer.ID, account.Id, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain())) + err = am.Store.SaveUserLastLogin(user.AccountID, user.Id, peer.LastLogin) + if err != nil { + return err + } + + am.StoreEvent(ctx, user.Id, peer.ID, user.AccountID, activity.UserLoggedInPeer, peer.EventMeta(am.GetDNSDomain())) return nil } -func checkIfPeerOwnerIsBlocked(peer *nbpeer.Peer, account *Account) error { +func checkIfPeerOwnerIsBlocked(peer *nbpeer.Peer, user *User) error { if peer.AddedWithSSOLogin() { - user, err := account.FindUser(peer.UserID) - if err != nil { - return status.Errorf(status.PermissionDenied, "user doesn't exist") - } if user.IsBlocked() { return status.Errorf(status.PermissionDenied, "user is blocked") } @@ -805,11 +828,6 @@ func peerLoginExpired(ctx context.Context, peer *nbpeer.Peer, settings *Settings return false } -func updatePeerLastLogin(peer *nbpeer.Peer, account *Account) { - peer.UpdateLastLogin() - account.UpdatePeer(peer) -} - // UpdatePeerSSHKey updates peer's public SSH key func (am *DefaultAccountManager) UpdatePeerSSHKey(ctx context.Context, peerID string, sshKey string) error { if sshKey == "" { @@ -908,14 +926,6 @@ func (am *DefaultAccountManager) GetPeer(ctx context.Context, accountID, peerID, return nil, status.Errorf(status.Internal, "user %s has no access to peer %s under account %s", userID, peerID, accountID) } -func updatePeerMeta(peer *nbpeer.Peer, meta nbpeer.PeerSystemMeta, account *Account) (*nbpeer.Peer, bool) { - if peer.UpdateMetaIfNew(meta) { - account.UpdatePeer(peer) - return peer, true - } - return peer, false -} - // updateAccountPeers updates all peers that belong to an account. // Should be called when changes have to be synced to peers. func (am *DefaultAccountManager) updateAccountPeers(ctx context.Context, account *Account) { diff --git a/management/server/posture_checks.go b/management/server/posture_checks.go index 4a7c9755d..4180550e6 100644 --- a/management/server/posture_checks.go +++ b/management/server/posture_checks.go @@ -60,7 +60,7 @@ func (am *DefaultAccountManager) SavePostureChecks(ctx context.Context, accountI } if err := postureChecks.Validate(); err != nil { - return status.Errorf(status.InvalidArgument, err.Error()) + return status.Errorf(status.InvalidArgument, err.Error()) //nolint } exists, uniqName := am.savePostureChecks(account, postureChecks) diff --git a/management/server/sql_store.go b/management/server/sql_store.go index c44ab7f09..912e31410 100644 --- a/management/server/sql_store.go +++ b/management/server/sql_store.go @@ -468,6 +468,34 @@ func (s *SqlStore) GetUserByTokenID(ctx context.Context, tokenID string) (*User, return &user, nil } +func (s *SqlStore) GetUserByUserID(ctx context.Context, userID string) (*User, error) { + var user User + result := s.db.First(&user, idQueryCondition, userID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "user not found: index lookup failed") + } + log.WithContext(ctx).Errorf("error when getting user from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "issue getting user from store") + } + + return &user, nil +} + +func (s *SqlStore) GetAccountGroups(ctx context.Context, accountID string) ([]*nbgroup.Group, error) { + var groups []*nbgroup.Group + result := s.db.Find(&groups, idQueryCondition, accountID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, status.Errorf(status.NotFound, "accountID not found: index lookup failed") + } + log.WithContext(ctx).Errorf("error when getting groups from the store: %s", result.Error) + return nil, status.Errorf(status.Internal, "issue getting groups from store") + } + + return groups, nil +} + func (s *SqlStore) GetAllAccounts(ctx context.Context) (all []*Account) { var accounts []Account result := s.db.Find(&accounts) diff --git a/management/server/store.go b/management/server/store.go index 864871c8e..a2b489391 100644 --- a/management/server/store.go +++ b/management/server/store.go @@ -41,6 +41,8 @@ type Store interface { GetAccountByPrivateDomain(ctx context.Context, domain string) (*Account, error) GetTokenIDByHashedToken(ctx context.Context, secret string) (string, error) GetUserByTokenID(ctx context.Context, tokenID string) (*User, error) + GetUserByUserID(ctx context.Context, userID string) (*User, error) + GetAccountGroups(ctx context.Context, accountID string) ([]*nbgroup.Group, error) GetPostureCheckByChecksDefinition(accountID string, checks *posture.ChecksDefinition) (*posture.Checks, error) SaveAccount(ctx context.Context, account *Account) error SaveUsers(accountID string, users map[string]*User) error diff --git a/release_files/install.sh b/release_files/install.sh index 198d74428..d9d436ba5 100755 --- a/release_files/install.sh +++ b/release_files/install.sh @@ -151,6 +151,22 @@ add_aur_repo() { ${SUDO} pacman -Rs "$REMOVE_PKGS" --noconfirm } +prepare_tun_module() { + # Create the necessary file structure for /dev/net/tun + if [ ! -c /dev/net/tun ]; then + if [ ! -d /dev/net ]; then + mkdir -m 755 /dev/net + fi + mknod /dev/net/tun c 10 200 + chmod 0755 /dev/net/tun + fi + + # Load the tun module if not already loaded + if ! lsmod | grep -q "^tun\s"; then + insmod /lib/modules/tun.ko + fi +} + install_native_binaries() { # Checks for supported architecture case "$ARCH" in @@ -268,6 +284,10 @@ install_netbird() { ;; esac + if [ "$OS_NAME" = "synology" ]; then + prepare_tun_module + fi + # Add package manager to config ${SUDO} mkdir -p "$CONFIG_FOLDER" echo "package_manager=$PACKAGE_MANAGER" | ${SUDO} tee "$CONFIG_FILE" > /dev/null diff --git a/sharedsock/sock_nolinux.go b/sharedsock/sock_nolinux.go index 93ac6b96f..a36ef67c6 100644 --- a/sharedsock/sock_nolinux.go +++ b/sharedsock/sock_nolinux.go @@ -10,5 +10,5 @@ import ( // Listen is not supported on other platforms then Linux func Listen(port int, filter BPFFilter) (net.PacketConn, error) { - return nil, fmt.Errorf(fmt.Sprintf("Not supported OS %s. SharedSocket is only supported on Linux", runtime.GOOS)) + return nil, fmt.Errorf("not supported OS %s. SharedSocket is only supported on Linux", runtime.GOOS) }