mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-04 19:45:14 -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.
153 lines
4.8 KiB
Go
153 lines
4.8 KiB
Go
package permissions
|
|
|
|
//go:generate go run github.com/golang/mock/mockgen -package permissions -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod
|
|
|
|
import (
|
|
"context"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/netbirdio/netbird/management/server/account"
|
|
"github.com/netbirdio/netbird/management/server/activity"
|
|
nbcontext "github.com/netbirdio/netbird/management/server/context"
|
|
"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/store"
|
|
"github.com/netbirdio/netbird/management/server/types"
|
|
"github.com/netbirdio/netbird/shared/management/status"
|
|
)
|
|
|
|
type Manager interface {
|
|
ValidateUserPermissions(ctx context.Context, accountID, userID string, module modules.Module, operation operations.Operation) (bool, context.Context, error)
|
|
ValidateRoleModuleAccess(ctx context.Context, accountID string, role roles.RolePermissions, module modules.Module, operation operations.Operation) bool
|
|
ValidateAccountAccess(ctx context.Context, accountID string, user *types.User, allowOwnerAndAdmin bool) (context.Context, error)
|
|
|
|
GetPermissionsByRole(ctx context.Context, role types.UserRole) (roles.Permissions, error)
|
|
SetAccountManager(accountManager account.Manager)
|
|
}
|
|
|
|
type managerImpl struct {
|
|
store store.Store
|
|
}
|
|
|
|
func NewManager(store store.Store) Manager {
|
|
return &managerImpl{
|
|
store: store,
|
|
}
|
|
}
|
|
|
|
func (m *managerImpl) ValidateUserPermissions(
|
|
ctx context.Context,
|
|
accountID string,
|
|
userID string,
|
|
module modules.Module,
|
|
operation operations.Operation,
|
|
) (bool, context.Context, error) {
|
|
if userID == activity.SystemInitiator {
|
|
return true, ctx, nil
|
|
}
|
|
|
|
user, err := m.store.GetUserByUserID(ctx, store.LockingStrengthNone, userID)
|
|
if err != nil {
|
|
return false, ctx, err
|
|
}
|
|
|
|
if user == nil {
|
|
return false, ctx, status.NewUserNotFoundError(userID)
|
|
}
|
|
|
|
if user.IsBlocked() && !user.PendingApproval {
|
|
return false, ctx, status.NewUserBlockedError()
|
|
}
|
|
|
|
if user.IsBlocked() && user.PendingApproval {
|
|
return false, ctx, status.NewUserPendingApprovalError()
|
|
}
|
|
|
|
ctxEnriched, err := m.ValidateAccountAccess(ctx, accountID, user, false)
|
|
if err != nil {
|
|
return false, ctx, err
|
|
}
|
|
|
|
if operation == operations.Read && user.IsServiceUser {
|
|
return true, ctxEnriched, nil // this should be replaced by proper granular access role
|
|
}
|
|
|
|
role, ok := roles.RolesMap[user.Role]
|
|
if !ok {
|
|
return false, ctxEnriched, status.NewUserRoleNotFoundError(string(user.Role))
|
|
}
|
|
|
|
return m.ValidateRoleModuleAccess(ctx, accountID, role, module, operation), ctxEnriched, nil
|
|
}
|
|
|
|
// ValidateRoleModuleAccess resolves an operation against the role's explicit
|
|
// grant for the module, then the grant for its parent module when the module
|
|
// is a dotted submodule, and finally the role's AutoAllowNew default.
|
|
func (m *managerImpl) ValidateRoleModuleAccess(
|
|
ctx context.Context,
|
|
accountID string,
|
|
role roles.RolePermissions,
|
|
module modules.Module,
|
|
operation operations.Operation,
|
|
) bool {
|
|
if permissions, ok := lookupModulePermissions(role, module); ok {
|
|
if allowed, exists := permissions[operation]; exists {
|
|
return allowed
|
|
}
|
|
log.WithContext(ctx).Tracef("operation %s not found on module %s for role %s", operation, module, role.Role)
|
|
return false
|
|
}
|
|
|
|
return role.AutoAllowNew[operation]
|
|
}
|
|
|
|
// lookupModulePermissions returns the role's explicit permission set for the
|
|
// module, falling back to the parent module's set for dotted submodules. The
|
|
// second return reports whether any explicit set was found.
|
|
func lookupModulePermissions(role roles.RolePermissions, module modules.Module) (map[operations.Operation]bool, bool) {
|
|
if permissions, ok := role.Permissions[module]; ok {
|
|
return permissions, true
|
|
}
|
|
if parent, hasParent := module.Parent(); hasParent {
|
|
if permissions, ok := role.Permissions[parent]; ok {
|
|
return permissions, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (m *managerImpl) ValidateAccountAccess(ctx context.Context, accountID string, user *types.User, allowOwnerAndAdmin bool) (context.Context, error) {
|
|
if user.AccountID != accountID {
|
|
return ctx, status.NewUserNotPartOfAccountError()
|
|
}
|
|
|
|
ctx = nbcontext.WithRole(ctx, string(user.Role))
|
|
|
|
return ctx, nil
|
|
}
|
|
|
|
func (m *managerImpl) GetPermissionsByRole(ctx context.Context, role types.UserRole) (roles.Permissions, error) {
|
|
roleMap, ok := roles.RolesMap[role]
|
|
if !ok {
|
|
return roles.Permissions{}, status.NewUserRoleNotFoundError(string(role))
|
|
}
|
|
|
|
permissions := roles.Permissions{}
|
|
|
|
for k := range modules.All {
|
|
if rolePermissions, ok := lookupModulePermissions(roleMap, k); ok {
|
|
permissions[k] = rolePermissions
|
|
continue
|
|
}
|
|
permissions[k] = roleMap.AutoAllowNew
|
|
}
|
|
|
|
return permissions, nil
|
|
}
|
|
|
|
func (m *managerImpl) SetAccountManager(accountManager account.Manager) {
|
|
// no-op
|
|
}
|