mirror of
https://github.com/netbirdio/netbird.git
synced 2025-08-19 11:20:18 +02:00
added login filter to filter different peers with the same pub key
This commit is contained in:
@@ -101,6 +101,8 @@ type DefaultAccountManager struct {
|
|||||||
|
|
||||||
accountUpdateLocks sync.Map
|
accountUpdateLocks sync.Map
|
||||||
updateAccountPeersBufferInterval atomic.Int64
|
updateAccountPeersBufferInterval atomic.Int64
|
||||||
|
|
||||||
|
loginFilter *loginFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
// getJWTGroupsChanges calculates the changes needed to sync a user's JWT groups.
|
// getJWTGroupsChanges calculates the changes needed to sync a user's JWT groups.
|
||||||
@@ -194,6 +196,7 @@ func BuildManager(
|
|||||||
proxyController: proxyController,
|
proxyController: proxyController,
|
||||||
settingsManager: settingsManager,
|
settingsManager: settingsManager,
|
||||||
permissionsManager: permissionsManager,
|
permissionsManager: permissionsManager,
|
||||||
|
loginFilter: newLoginFilter(),
|
||||||
}
|
}
|
||||||
|
|
||||||
am.startWarmup(ctx)
|
am.startWarmup(ctx)
|
||||||
@@ -1561,7 +1564,7 @@ func (am *DefaultAccountManager) OnPeerDisconnected(ctx context.Context, account
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithContext(ctx).Warnf("failed marking peer as disconnected %s %v", peerPubKey, err)
|
log.WithContext(ctx).Warnf("failed marking peer as disconnected %s %v", peerPubKey, err)
|
||||||
}
|
}
|
||||||
|
am.loginFilter.removeLogin(peerPubKey)
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@@ -338,6 +339,9 @@ func mapError(ctx context.Context, err error) error {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if errors.Is(err, internalStatus.ErrPeerAlreadyLoggedIn) {
|
||||||
|
return status.Error(codes.AlreadyExists, internalStatus.ErrPeerAlreadyLoggedIn.Error())
|
||||||
|
}
|
||||||
log.WithContext(ctx).Errorf("got an unhandled error: %s", err)
|
log.WithContext(ctx).Errorf("got an unhandled error: %s", err)
|
||||||
return status.Errorf(codes.Internal, "failed handling request")
|
return status.Errorf(codes.Internal, "failed handling request")
|
||||||
}
|
}
|
||||||
|
58
management/server/loginfilter.go
Normal file
58
management/server/loginfilter.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
loginFilterSize = 100_000 // Size of the login filter map, making it large enough for a future
|
||||||
|
)
|
||||||
|
|
||||||
|
type loginFilter struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
logged map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLoginFilter() *loginFilter {
|
||||||
|
return &loginFilter{
|
||||||
|
logged: make(map[string]string, loginFilterSize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loginFilter) addLogin(wgPubKey, metaHash string) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.logged[wgPubKey] = metaHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loginFilter) allowLogin(wgPubKey, metaHash string) bool {
|
||||||
|
l.mu.RLock()
|
||||||
|
defer l.mu.RUnlock()
|
||||||
|
if loggedMetaHash, ok := l.logged[wgPubKey]; ok {
|
||||||
|
return loggedMetaHash == metaHash
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loginFilter) removeLogin(wgPubKey string) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
delete(l.logged, wgPubKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaHash(meta nbpeer.PeerSystemMeta) string {
|
||||||
|
estimatedSize := len(meta.WtVersion) + len(meta.OSVersion) + len(meta.KernelVersion) + len(meta.Hostname)
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
b.Grow(estimatedSize)
|
||||||
|
|
||||||
|
b.WriteString(meta.WtVersion)
|
||||||
|
b.WriteString(meta.OSVersion)
|
||||||
|
b.WriteString(meta.KernelVersion)
|
||||||
|
b.WriteString(meta.Hostname)
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
47
management/server/loginfilter_test.go
Normal file
47
management/server/loginfilter_test.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/peer"
|
||||||
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkMetaHash(b *testing.B) {
|
||||||
|
meta := peer.PeerSystemMeta{
|
||||||
|
WtVersion: "1.0.0",
|
||||||
|
OSVersion: "Linux 5.4.0",
|
||||||
|
KernelVersion: "5.4.0-42-generic",
|
||||||
|
Hostname: "test-host",
|
||||||
|
}
|
||||||
|
b.Run("fnv", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
metaHashFnv(meta)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("builder", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
metaHash(meta)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
b.Run("strings", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
metaHashStrings(meta)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaHashStrings(meta nbpeer.PeerSystemMeta) string {
|
||||||
|
return meta.WtVersion + meta.OSVersion + meta.KernelVersion + meta.Hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaHashFnv(meta nbpeer.PeerSystemMeta) string {
|
||||||
|
h := fnv.New64a()
|
||||||
|
h.Write([]byte(meta.WtVersion))
|
||||||
|
h.Write([]byte(meta.OSVersion))
|
||||||
|
h.Write([]byte(meta.KernelVersion))
|
||||||
|
h.Write([]byte(meta.Hostname))
|
||||||
|
return fmt.Sprintf("%x", h.Sum64())
|
||||||
|
}
|
@@ -789,6 +789,11 @@ func (am *DefaultAccountManager) handlePeerLoginNotFound(ctx context.Context, lo
|
|||||||
// LoginPeer logs in or registers a peer.
|
// 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.
|
// 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(ctx context.Context, login types.PeerLogin) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) {
|
func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login types.PeerLogin) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) {
|
||||||
|
metahash := metaHash(login.Meta)
|
||||||
|
if !am.loginFilter.allowLogin(login.WireGuardPubKey, metahash) {
|
||||||
|
return nil, nil, nil, status.ErrPeerAlreadyLoggedIn
|
||||||
|
}
|
||||||
|
|
||||||
accountID, err := am.Store.GetAccountIDByPeerPubKey(ctx, login.WireGuardPubKey)
|
accountID, err := am.Store.GetAccountIDByPeerPubKey(ctx, login.WireGuardPubKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return am.handlePeerLoginNotFound(ctx, login, err)
|
return am.handlePeerLoginNotFound(ctx, login, err)
|
||||||
@@ -900,6 +905,8 @@ func (am *DefaultAccountManager) LoginPeer(ctx context.Context, login types.Peer
|
|||||||
am.BufferUpdateAccountPeers(ctx, accountID)
|
am.BufferUpdateAccountPeers(ctx, accountID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
am.loginFilter.addLogin(login.WireGuardPubKey, metahash)
|
||||||
|
|
||||||
return am.getValidatedPeerWithMap(ctx, isRequiresApproval, accountID, peer)
|
return am.getValidatedPeerWithMap(ctx, isRequiresApproval, accountID, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -1579,7 +1580,6 @@ func Test_LoginPeer(t *testing.T) {
|
|||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
setupKey string
|
setupKey string
|
||||||
wireGuardPubKey string
|
|
||||||
expectExtraDNSLabelsMismatch bool
|
expectExtraDNSLabelsMismatch bool
|
||||||
extraDNSLabels []string
|
extraDNSLabels []string
|
||||||
expectLoginError bool
|
expectLoginError bool
|
||||||
@@ -1679,6 +1679,88 @@ func Test_LoginPeer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_LoginPeerMultipleAccess(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("The SQLite store is not properly supported by Windows yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
s, cleanup, err := store.NewTestStoreFromSQL(context.Background(), "testdata/extended-store.sql", t.TempDir())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
eventStore := &activity.InMemoryEventStore{}
|
||||||
|
|
||||||
|
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
t.Cleanup(ctrl.Finish)
|
||||||
|
settingsMockManager := settings.NewMockManager(ctrl)
|
||||||
|
permissionsManager := permissions.NewManager(s)
|
||||||
|
|
||||||
|
am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
|
||||||
|
_, err = s.GetAccount(context.Background(), existingAccountID)
|
||||||
|
require.NoError(t, err, "Failed to get existing account, check testdata/extended-store.sql. Account ID: %s", existingAccountID)
|
||||||
|
|
||||||
|
setupKey := "A2C8E62B-38F5-4553-B31E-DD66C696CEBB"
|
||||||
|
|
||||||
|
peer := &nbpeer.Peer{
|
||||||
|
ID: xid.New().String(),
|
||||||
|
AccountID: existingAccountID,
|
||||||
|
UserID: "",
|
||||||
|
IP: net.IP{123, 123, 123, 123},
|
||||||
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
|
Hostname: "Peer",
|
||||||
|
GoOS: "linux",
|
||||||
|
},
|
||||||
|
Name: "PeerName",
|
||||||
|
DNSLabel: "peer.test",
|
||||||
|
Status: &nbpeer.PeerStatus{Connected: false, LastSeen: time.Now()},
|
||||||
|
SSHEnabled: false,
|
||||||
|
}
|
||||||
|
_, _, _, err = am.AddPeer(context.Background(), setupKey, "", peer)
|
||||||
|
require.NoError(t, err, "Expected no error when adding peer with setup key: %s", setupKey)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
n int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "10 logins",
|
||||||
|
n: 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
actual := 1 // First login is always successful
|
||||||
|
for i := range tc.n {
|
||||||
|
loginInput := types.PeerLogin{
|
||||||
|
WireGuardPubKey: peer.ID,
|
||||||
|
SSHKey: "test-ssh-key",
|
||||||
|
Meta: nbpeer.PeerSystemMeta{
|
||||||
|
Hostname: "peer" + strconv.Itoa(i),
|
||||||
|
},
|
||||||
|
UserID: "",
|
||||||
|
SetupKey: setupKey,
|
||||||
|
ConnectionIP: net.ParseIP("192.0.2.100"),
|
||||||
|
}
|
||||||
|
_, _, _, loginErr := am.LoginPeer(context.Background(), loginInput)
|
||||||
|
if loginErr != nil {
|
||||||
|
actual++
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
}
|
||||||
|
require.Equal(t, tc.n-1, actual, "Expected %d insuccessful logins, got %d", tc.n, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPeerAccountPeersUpdate(t *testing.T) {
|
func TestPeerAccountPeersUpdate(t *testing.T) {
|
||||||
manager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
|
manager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
|
||||||
|
|
||||||
|
@@ -42,7 +42,10 @@ const (
|
|||||||
// Type is a type of the Error
|
// Type is a type of the Error
|
||||||
type Type int32
|
type Type int32
|
||||||
|
|
||||||
var ErrExtraSettingsNotFound = fmt.Errorf("extra settings not found")
|
var (
|
||||||
|
ErrExtraSettingsNotFound = fmt.Errorf("extra settings not found")
|
||||||
|
ErrPeerAlreadyLoggedIn = errors.New("peer with the same public key is already logged in")
|
||||||
|
)
|
||||||
|
|
||||||
// Error is an internal error
|
// Error is an internal error
|
||||||
type Error struct {
|
type Error struct {
|
||||||
|
Reference in New Issue
Block a user