mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-04 19:55:09 -04:00
## Describe your changes Agent Network gates providers, policies, guardrails, budgets, usage, access logs, and settings behind the single `agent_network` permission module, so access is all-or-nothing: a future delegated role cannot be scoped to a subset of the area (for example usage-only visibility). This introduces dotted submodules (`agent_network.providers`, `.policies`, `.guardrails`, `.budgets`, `.usage`, `.logs`, `.settings`) and resolves grants with a cascade: exact module first, then its parent, then the role's `AutoAllowNew` default. The agent network manager now validates each operation against its matching submodule. `usage` (aggregated counters, overview) is deliberately separate from `logs` (request-level entries, which can contain captured prompts). No role definitions change. No built-in role carries an explicit `agent_network` entry, so every role resolves the submodules exactly as it resolved the parent module before — pinned by a test that compares each built-in role's answer on every submodule against its answer on `agent_network`. Role additions that use these submodules come separately.
140 lines
5.8 KiB
Go
140 lines
5.8 KiB
Go
package permissions
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/netbirdio/netbird/management/server/permissions/modules"
|
|
"github.com/netbirdio/netbird/management/server/permissions/operations"
|
|
"github.com/netbirdio/netbird/management/server/permissions/roles"
|
|
"github.com/netbirdio/netbird/management/server/types"
|
|
)
|
|
|
|
func TestValidateRoleModuleAccessSubmoduleCascade(t *testing.T) {
|
|
manager := NewManager(nil)
|
|
ctx := context.Background()
|
|
|
|
fullAccess := map[operations.Operation]bool{
|
|
operations.Read: true,
|
|
operations.Create: true,
|
|
operations.Update: true,
|
|
operations.Delete: true,
|
|
}
|
|
readOnly := map[operations.Operation]bool{
|
|
operations.Read: true,
|
|
operations.Create: false,
|
|
operations.Update: false,
|
|
operations.Delete: false,
|
|
}
|
|
denyAll := map[operations.Operation]bool{
|
|
operations.Read: false,
|
|
operations.Create: false,
|
|
operations.Update: false,
|
|
operations.Delete: false,
|
|
}
|
|
|
|
t.Run("parent grant covers submodules", func(t *testing.T) {
|
|
role := roles.RolePermissions{
|
|
AutoAllowNew: denyAll,
|
|
Permissions: roles.Permissions{modules.AgentNetwork: fullAccess},
|
|
}
|
|
assert.True(t, manager.ValidateRoleModuleAccess(ctx, "account", role, modules.AgentNetworkProviders, operations.Create),
|
|
"parent full grant should allow create on a submodule")
|
|
assert.True(t, manager.ValidateRoleModuleAccess(ctx, "account", role, modules.AgentNetworkLogs, operations.Read),
|
|
"parent full grant should allow read on a submodule")
|
|
})
|
|
|
|
t.Run("submodule grant does not leak to parent or siblings", func(t *testing.T) {
|
|
role := roles.RolePermissions{
|
|
AutoAllowNew: denyAll,
|
|
Permissions: roles.Permissions{modules.AgentNetworkUsage: readOnly},
|
|
}
|
|
assert.True(t, manager.ValidateRoleModuleAccess(ctx, "account", role, modules.AgentNetworkUsage, operations.Read),
|
|
"explicit submodule read should be allowed")
|
|
assert.False(t, manager.ValidateRoleModuleAccess(ctx, "account", role, modules.AgentNetworkUsage, operations.Create),
|
|
"read-only submodule grant should not allow create")
|
|
assert.False(t, manager.ValidateRoleModuleAccess(ctx, "account", role, modules.AgentNetwork, operations.Read),
|
|
"submodule grant should not grant the parent module")
|
|
assert.False(t, manager.ValidateRoleModuleAccess(ctx, "account", role, modules.AgentNetworkProviders, operations.Read),
|
|
"submodule grant should not grant a sibling submodule")
|
|
})
|
|
|
|
t.Run("explicit submodule entry wins over parent grant", func(t *testing.T) {
|
|
role := roles.RolePermissions{
|
|
AutoAllowNew: denyAll,
|
|
Permissions: roles.Permissions{
|
|
modules.AgentNetwork: fullAccess,
|
|
modules.AgentNetworkLogs: denyAll,
|
|
},
|
|
}
|
|
assert.False(t, manager.ValidateRoleModuleAccess(ctx, "account", role, modules.AgentNetworkLogs, operations.Read),
|
|
"explicit submodule deny should override the parent grant")
|
|
assert.True(t, manager.ValidateRoleModuleAccess(ctx, "account", role, modules.AgentNetworkUsage, operations.Read),
|
|
"sibling submodules should still resolve through the parent grant")
|
|
})
|
|
|
|
t.Run("auto allow applies when neither submodule nor parent is granted", func(t *testing.T) {
|
|
role := roles.RolePermissions{
|
|
AutoAllowNew: readOnly,
|
|
}
|
|
assert.True(t, manager.ValidateRoleModuleAccess(ctx, "account", role, modules.AgentNetworkProviders, operations.Read),
|
|
"auto-allow read should apply to submodules")
|
|
assert.False(t, manager.ValidateRoleModuleAccess(ctx, "account", role, modules.AgentNetworkProviders, operations.Delete),
|
|
"auto-allow should not grant unlisted operations")
|
|
})
|
|
}
|
|
|
|
// TestExistingRolesKeepAgentNetworkBehaviorOnSubmodules pins the behavior the
|
|
// submodule split must not change: every built-in role resolves the new
|
|
// submodules exactly as it resolved the agent_network module before.
|
|
func TestExistingRolesKeepAgentNetworkBehaviorOnSubmodules(t *testing.T) {
|
|
manager := NewManager(nil)
|
|
ctx := context.Background()
|
|
|
|
submodules := []modules.Module{
|
|
modules.AgentNetworkProviders,
|
|
modules.AgentNetworkPolicies,
|
|
modules.AgentNetworkGuardrails,
|
|
modules.AgentNetworkBudgets,
|
|
modules.AgentNetworkUsage,
|
|
modules.AgentNetworkLogs,
|
|
modules.AgentNetworkSettings,
|
|
}
|
|
allOperations := []operations.Operation{operations.Read, operations.Create, operations.Update, operations.Delete}
|
|
|
|
for _, role := range []types.UserRole{types.UserRoleOwner, types.UserRoleAdmin, types.UserRoleAuditor, types.UserRoleNetworkAdmin, types.UserRoleUser} {
|
|
rolePermissions, ok := roles.RolesMap[role]
|
|
require.True(t, ok, "role %s must exist in RolesMap", role)
|
|
|
|
for _, sub := range submodules {
|
|
for _, op := range allOperations {
|
|
expected := manager.ValidateRoleModuleAccess(ctx, "account", rolePermissions, modules.AgentNetwork, op)
|
|
actual := manager.ValidateRoleModuleAccess(ctx, "account", rolePermissions, sub, op)
|
|
assert.Equal(t, expected, actual, "role %s: %s on %s should match the agent_network module", role, op, sub)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetPermissionsByRoleIncludesSubmodules(t *testing.T) {
|
|
manager := NewManager(nil)
|
|
ctx := context.Background()
|
|
|
|
permissions, err := manager.GetPermissionsByRole(ctx, types.UserRoleAuditor)
|
|
require.NoError(t, err, "auditor role must resolve")
|
|
|
|
usage, ok := permissions[modules.AgentNetworkUsage]
|
|
require.True(t, ok, "permissions map should contain the usage submodule")
|
|
assert.True(t, usage[operations.Read], "auditor should read the usage submodule")
|
|
assert.False(t, usage[operations.Update], "auditor should not update the usage submodule")
|
|
|
|
adminPermissions, err := manager.GetPermissionsByRole(ctx, types.UserRoleAdmin)
|
|
require.NoError(t, err, "admin role must resolve")
|
|
providers, ok := adminPermissions[modules.AgentNetworkProviders]
|
|
require.True(t, ok, "permissions map should contain the providers submodule")
|
|
assert.True(t, providers[operations.Delete], "admin should delete on the providers submodule")
|
|
}
|