Merge remote-tracking branch 'origin/main' into refactor/permissions-manager

This commit is contained in:
pascal
2026-09-14 13:33:20 +02:00
130 changed files with 4707 additions and 560 deletions
+124 -1
View File
@@ -11,10 +11,10 @@ import (
"testing"
"time"
"go.uber.org/mock/gomock"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"golang.org/x/exp/maps"
nbdns "github.com/netbirdio/netbird/dns"
@@ -131,6 +131,11 @@ func TestDefaultAccountManager_DeleteGroup(t *testing.T) {
"grp-for-agent-network-policy",
"agent network policy",
},
{
"agent network budget rule",
"grp-for-agent-network-budget-rule",
"agent network budget rule",
},
{
"reverse proxy private service access group",
"grp-for-rp-private",
@@ -151,6 +156,16 @@ func TestDefaultAccountManager_DeleteGroup(t *testing.T) {
return
}
group, getErr := am.GetGroup(context.Background(), account.Id, testCase.groupID, groupAdminUserID)
if getErr != nil {
t.Errorf("group %s should still exist after failed deletion: %s", testCase.groupID, getErr)
return
}
if group == nil {
t.Errorf("group %s was deleted despite the failed deletion", testCase.groupID)
return
}
var sErr *status.Error
if errors.As(err, &sErr) {
if sErr.Message != testCase.expectedReason {
@@ -239,6 +254,12 @@ func TestDefaultAccountManager_DeleteGroups(t *testing.T) {
groupIDs: []string{"grp-for-agent-network-policy"},
expectedReasons: []string{"agent network policy"},
},
{
name: "agent network budget rule",
groupIDs: []string{"grp-for-agent-network-budget-rule"},
expectedReasons: []string{"agent network budget rule"},
expectedNotDeleted: []string{"grp-for-agent-network-budget-rule"},
},
{
name: "reverse proxy services",
groupIDs: []string{"grp-for-rp-private", "grp-for-rp-bearer"},
@@ -500,6 +521,14 @@ func initTestGroupAccount(am *DefaultAccountManager) (*DefaultAccountManager, *t
Peers: make([]string, 0),
}
groupForAgentNetworkBudgetRule := &types.Group{
ID: "grp-for-agent-network-budget-rule",
AccountID: "account-id",
Name: "Group for agent network budget rules",
Issued: types.GroupIssuedAPI,
Peers: make([]string, 0),
}
groupForRPPrivate := &types.Group{
ID: "grp-for-rp-private",
AccountID: "account-id",
@@ -572,6 +601,7 @@ func initTestGroupAccount(am *DefaultAccountManager) (*DefaultAccountManager, *t
_ = am.CreateGroup(context.Background(), accountID, groupAdminUserID, groupForUsers)
_ = am.CreateGroup(context.Background(), accountID, groupAdminUserID, groupForIntegration)
_ = am.CreateGroup(context.Background(), accountID, groupAdminUserID, groupForAgentNetworkPolicy)
_ = am.CreateGroup(context.Background(), accountID, groupAdminUserID, groupForAgentNetworkBudgetRule)
_ = am.CreateGroup(context.Background(), accountID, groupAdminUserID, groupForRPPrivate)
_ = am.CreateGroup(context.Background(), accountID, groupAdminUserID, groupForRPBearer)
@@ -586,6 +616,20 @@ func initTestGroupAccount(am *DefaultAccountManager) (*DefaultAccountManager, *t
return nil, nil, err
}
budgetRuleDecoy := agentNetworkTypes.NewAccountBudgetRule(accountID)
budgetRuleDecoy.Name = "Unrelated agent network budget rule"
budgetRuleDecoy.TargetGroups = []string{"unrelated-group"}
if err := am.Store.SaveAgentNetworkBudgetRule(context.Background(), budgetRuleDecoy); err != nil {
return nil, nil, err
}
budgetRule := agentNetworkTypes.NewAccountBudgetRule(accountID)
budgetRule.Name = "Example agent network budget rule"
budgetRule.TargetGroups = []string{groupForAgentNetworkBudgetRule.ID}
if err := am.Store.SaveAgentNetworkBudgetRule(context.Background(), budgetRule); err != nil {
return nil, nil, err
}
// The decoy services are created first so the linkage check has to scan
// past services that do not reference the groups under test.
rpServices := []*rpservice.Service{
@@ -1234,3 +1278,82 @@ func Test_IncrementNetworkSerial(t *testing.T) {
assert.Equal(t, totalPeers, int(account.Network.Serial), "Expected %d serial increases in account %s, got %d", totalPeers, accountID, account.Network.Serial)
}
func TestDefaultAccountManager_GroupPeersMustBelongToAccount(t *testing.T) {
manager, _, account, peer1, _, _ := setupNetworkMapTest(t)
otherAccount, err := createAccount(manager, "other_account", "other_user", "")
require.NoError(t, err)
foreignPeer := &peer2.Peer{
ID: "foreign-peer",
AccountID: otherAccount.Id,
Key: "foreign-key",
DNSLabel: "foreign-peer",
IP: uint32ToIP(1),
}
require.NoError(t, manager.Store.AddPeerToAccount(context.Background(), foreignPeer))
assertRejected := func(t *testing.T, err error) {
t.Helper()
require.Error(t, err)
s, ok := status.FromError(err)
require.True(t, ok, "expected status error, got %v", err)
assert.Equal(t, status.InvalidArgument, s.Type(), "peer outside the account should be rejected as invalid argument")
}
t.Run("create rejects foreign peer", func(t *testing.T) {
err := manager.CreateGroup(context.Background(), account.Id, userID, &types.Group{
Name: "foreign",
Issued: types.GroupIssuedAPI,
Peers: []string{peer1.ID, foreignPeer.ID},
})
assertRejected(t, err)
_, err = manager.Store.GetGroupByName(context.Background(), store.LockingStrengthNone, account.Id, "foreign")
assert.Error(t, err, "rejected create must not persist the group")
})
t.Run("update rejects foreign and unknown peers", func(t *testing.T) {
group := &types.Group{ID: "own", Name: "own", Issued: types.GroupIssuedAPI, Peers: []string{peer1.ID}}
require.NoError(t, manager.CreateGroup(context.Background(), account.Id, userID, group))
group.Peers = []string{peer1.ID, foreignPeer.ID}
assertRejected(t, manager.UpdateGroup(context.Background(), account.Id, userID, group))
group.Peers = []string{peer1.ID, "does-not-exist"}
assertRejected(t, manager.UpdateGroup(context.Background(), account.Id, userID, group))
stored, err := manager.Store.GetGroupByID(context.Background(), store.LockingStrengthNone, account.Id, group.ID)
require.NoError(t, err)
assert.Equal(t, []string{peer1.ID}, stored.Peers, "rejected updates must not change membership")
})
t.Run("update tolerates and drops pre-existing dangling members", func(t *testing.T) {
group := &types.Group{ID: "polluted", Name: "polluted", Issued: types.GroupIssuedAPI, Peers: []string{peer1.ID}}
require.NoError(t, manager.CreateGroup(context.Background(), account.Id, userID, group))
require.NoError(t, manager.Store.AddPeerToGroup(context.Background(), account.Id, foreignPeer.ID, group.ID))
group.Peers = []string{peer1.ID, foreignPeer.ID}
assert.NoError(t, manager.UpdateGroup(context.Background(), account.Id, userID, group), "keeping an existing member must not be rejected")
group.Peers = []string{peer1.ID}
require.NoError(t, manager.UpdateGroup(context.Background(), account.Id, userID, group))
stored, err := manager.Store.GetGroupByID(context.Background(), store.LockingStrengthNone, account.Id, group.ID)
require.NoError(t, err)
assert.Equal(t, []string{peer1.ID}, stored.Peers, "dangling member should be removed once omitted")
})
t.Run("direct add rejects foreign and unknown peers", func(t *testing.T) {
group := &types.Group{ID: "direct", Name: "direct", Issued: types.GroupIssuedAPI, Peers: []string{peer1.ID}}
require.NoError(t, manager.CreateGroup(context.Background(), account.Id, userID, group))
assertRejected(t, manager.GroupAddPeer(context.Background(), account.Id, group.ID, foreignPeer.ID))
assertRejected(t, manager.GroupAddPeer(context.Background(), account.Id, group.ID, "does-not-exist"))
stored, err := manager.Store.GetGroupByID(context.Background(), store.LockingStrengthNone, account.Id, group.ID)
require.NoError(t, err)
assert.Equal(t, []string{peer1.ID}, stored.Peers, "rejected direct adds must not change membership")
})
}