Files
netbird/management/server/networks/manager.go
2026-04-30 16:20:41 +02:00

323 lines
10 KiB
Go

package networks
import (
"context"
"fmt"
"github.com/rs/xid"
log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/server/account"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/networks/resources"
"github.com/netbirdio/netbird/management/server/networks/routers"
"github.com/netbirdio/netbird/management/server/networks/types"
"github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/permissions/modules"
"github.com/netbirdio/netbird/management/server/permissions/operations"
"github.com/netbirdio/netbird/management/server/store"
nbTypes "github.com/netbirdio/netbird/management/server/types"
"github.com/netbirdio/netbird/shared/management/status"
)
type Manager interface {
GetAllNetworks(ctx context.Context, accountID, userID string) ([]*types.Network, error)
CreateNetwork(ctx context.Context, userID string, network *types.Network) (*types.Network, error)
GetNetwork(ctx context.Context, accountID, userID, networkID string) (*types.Network, error)
UpdateNetwork(ctx context.Context, userID string, network *types.Network) (*types.Network, error)
DeleteNetwork(ctx context.Context, accountID, userID, networkID string) error
}
type managerImpl struct {
store store.Store
accountManager account.Manager
permissionsManager permissions.Manager
resourcesManager resources.Manager
routersManager routers.Manager
}
type mockManager struct {
}
func NewManager(store store.Store, permissionsManager permissions.Manager, resourceManager resources.Manager, routersManager routers.Manager, accountManager account.Manager) Manager {
return &managerImpl{
store: store,
permissionsManager: permissionsManager,
resourcesManager: resourceManager,
routersManager: routersManager,
accountManager: accountManager,
}
}
func (m *managerImpl) GetAllNetworks(ctx context.Context, accountID, userID string) ([]*types.Network, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Networks, operations.Read)
if err != nil {
return nil, status.NewPermissionValidationError(err)
}
if !ok {
return nil, status.NewPermissionDeniedError()
}
return m.store.GetAccountNetworks(ctx, store.LockingStrengthNone, accountID)
}
func (m *managerImpl) CreateNetwork(ctx context.Context, userID string, network *types.Network) (*types.Network, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, network.AccountID, userID, modules.Networks, operations.Create)
if err != nil {
return nil, status.NewPermissionValidationError(err)
}
if !ok {
return nil, status.NewPermissionDeniedError()
}
network.ID = xid.New().String()
err = m.store.SaveNetwork(ctx, network)
if err != nil {
return nil, fmt.Errorf("failed to save network: %w", err)
}
m.accountManager.StoreEvent(ctx, userID, network.ID, network.AccountID, activity.NetworkCreated, network.EventMeta())
return network, nil
}
func (m *managerImpl) GetNetwork(ctx context.Context, accountID, userID, networkID string) (*types.Network, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Networks, operations.Read)
if err != nil {
return nil, status.NewPermissionValidationError(err)
}
if !ok {
return nil, status.NewPermissionDeniedError()
}
return m.store.GetNetworkByID(ctx, store.LockingStrengthNone, accountID, networkID)
}
func (m *managerImpl) UpdateNetwork(ctx context.Context, userID string, network *types.Network) (*types.Network, error) {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, network.AccountID, userID, modules.Networks, operations.Update)
if err != nil {
return nil, status.NewPermissionValidationError(err)
}
if !ok {
return nil, status.NewPermissionDeniedError()
}
_, err = m.store.GetNetworkByID(ctx, store.LockingStrengthUpdate, network.AccountID, network.ID)
if err != nil {
return nil, fmt.Errorf("failed to get network: %w", err)
}
m.accountManager.StoreEvent(ctx, userID, network.ID, network.AccountID, activity.NetworkUpdated, network.EventMeta())
return network, m.store.SaveNetwork(ctx, network)
}
// networkAffectedPeersData holds data loaded inside the transaction for affected peer resolution.
type networkAffectedPeersData struct {
resourceGroupIDs []string
routerPeerGroups []string
directPeerIDs []string
policies []*nbTypes.Policy
}
func (m *managerImpl) DeleteNetwork(ctx context.Context, accountID, userID, networkID string) error {
ok, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Networks, operations.Delete)
if err != nil {
return status.NewPermissionValidationError(err)
}
if !ok {
return status.NewPermissionDeniedError()
}
network, err := m.store.GetNetworkByID(ctx, store.LockingStrengthUpdate, accountID, networkID)
if err != nil {
return fmt.Errorf("failed to get network: %w", err)
}
var eventsToStore []func()
var affectedData *networkAffectedPeersData
err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
resources, err := transaction.GetNetworkResourcesByNetID(ctx, store.LockingStrengthUpdate, accountID, networkID)
if err != nil {
return fmt.Errorf("failed to get resources in network: %w", err)
}
var resourceGroupIDs []string
for _, resource := range resources {
groups, err := transaction.GetResourceGroups(ctx, store.LockingStrengthNone, accountID, resource.ID)
if err == nil {
for _, g := range groups {
resourceGroupIDs = append(resourceGroupIDs, g.ID)
}
}
event, err := m.resourcesManager.DeleteResourceInTransaction(ctx, transaction, accountID, userID, networkID, resource.ID)
if err != nil {
return fmt.Errorf("failed to delete resource: %w", err)
}
eventsToStore = append(eventsToStore, event...)
}
netRouters, err := transaction.GetNetworkRoutersByNetID(ctx, store.LockingStrengthUpdate, accountID, networkID)
if err != nil {
return fmt.Errorf("failed to get routers in network: %w", err)
}
var routerPeerGroups []string
var directPeerIDs []string
for _, router := range netRouters {
routerPeerGroups = append(routerPeerGroups, router.PeerGroups...)
if router.Peer != "" {
directPeerIDs = append(directPeerIDs, router.Peer)
}
event, err := m.routersManager.DeleteRouterInTransaction(ctx, transaction, accountID, userID, networkID, router.ID)
if err != nil {
return fmt.Errorf("failed to delete router: %w", err)
}
eventsToStore = append(eventsToStore, event)
}
// load policies before deleting so group memberships are still present
var policies []*nbTypes.Policy
if len(resourceGroupIDs) > 0 {
policies, err = transaction.GetAccountPolicies(ctx, store.LockingStrengthNone, accountID)
if err != nil {
log.WithContext(ctx).Errorf("failed to get policies for affected peers: %v", err)
}
}
if len(resourceGroupIDs) > 0 || len(routerPeerGroups) > 0 || len(directPeerIDs) > 0 {
affectedData = &networkAffectedPeersData{
resourceGroupIDs: resourceGroupIDs,
routerPeerGroups: routerPeerGroups,
directPeerIDs: directPeerIDs,
policies: policies,
}
}
err = transaction.DeleteNetwork(ctx, accountID, networkID)
if err != nil {
return fmt.Errorf("failed to delete network: %w", err)
}
eventsToStore = append(eventsToStore, func() {
m.accountManager.StoreEvent(ctx, userID, networkID, accountID, activity.NetworkDeleted, network.EventMeta())
})
err = transaction.IncrementNetworkSerial(ctx, accountID)
if err != nil {
return fmt.Errorf("failed to increment network serial: %w", err)
}
return nil
})
if err != nil {
return fmt.Errorf("failed to delete network: %w", err)
}
for _, event := range eventsToStore {
event()
}
if affectedData != nil {
affectedPeerIDs := resolveNetworkAffectedPeers(ctx, m.store, accountID, affectedData)
if len(affectedPeerIDs) > 0 {
go m.accountManager.UpdateAffectedPeers(ctx, accountID, affectedPeerIDs)
}
}
return nil
}
// resolveNetworkAffectedPeers computes affected peer IDs from preloaded data outside the transaction.
func resolveNetworkAffectedPeers(ctx context.Context, s store.Store, accountID string, data *networkAffectedPeersData) []string {
groupSet := make(map[string]struct{})
for _, gID := range data.routerPeerGroups {
groupSet[gID] = struct{}{}
}
if len(data.resourceGroupIDs) > 0 {
destSet := make(map[string]struct{}, len(data.resourceGroupIDs))
for _, gID := range data.resourceGroupIDs {
destSet[gID] = struct{}{}
groupSet[gID] = struct{}{}
}
for _, policy := range data.policies {
if policy == nil || !policy.Enabled {
continue
}
for _, rule := range policy.Rules {
if rule == nil || !rule.Enabled {
continue
}
for _, gID := range rule.Destinations {
if _, ok := destSet[gID]; ok {
for _, srcGID := range rule.Sources {
groupSet[srcGID] = struct{}{}
}
break
}
}
}
}
}
if len(groupSet) == 0 && len(data.directPeerIDs) == 0 {
return nil
}
groupIDs := make([]string, 0, len(groupSet))
for gID := range groupSet {
groupIDs = append(groupIDs, gID)
}
peerIDs, err := s.GetPeerIDsByGroups(ctx, accountID, groupIDs)
if err != nil {
log.WithContext(ctx).Errorf("failed to resolve peer IDs: %v", err)
return nil
}
if len(data.directPeerIDs) > 0 {
seen := make(map[string]struct{}, len(peerIDs))
for _, id := range peerIDs {
seen[id] = struct{}{}
}
for _, id := range data.directPeerIDs {
if _, exists := seen[id]; !exists {
peerIDs = append(peerIDs, id)
seen[id] = struct{}{}
}
}
}
return peerIDs
}
func NewManagerMock() Manager {
return &mockManager{}
}
func (m *mockManager) GetAllNetworks(ctx context.Context, accountID, userID string) ([]*types.Network, error) {
return []*types.Network{}, nil
}
func (m *mockManager) CreateNetwork(ctx context.Context, userID string, network *types.Network) (*types.Network, error) {
return network, nil
}
func (m *mockManager) GetNetwork(ctx context.Context, accountID, userID, networkID string) (*types.Network, error) {
return &types.Network{}, nil
}
func (m *mockManager) UpdateNetwork(ctx context.Context, userID string, network *types.Network) (*types.Network, error) {
return network, nil
}
func (m *mockManager) DeleteNetwork(ctx context.Context, accountID, userID, networkID string) error {
return nil
}