mirror of
https://github.com/netbirdio/netbird.git
synced 2024-12-14 19:00:50 +01:00
9adadfade4
Replace Peer.Key as internal identifier with a randomly generated Peer.ID in the Management service. Every group now references peers by ID instead of a public key. Every route now references peers by ID instead of a public key. FileStore does store.json file migration on startup by generating Peer.ID and replacing all Peer.Key identifier references .
424 lines
12 KiB
Go
424 lines
12 KiB
Go
package server
|
|
|
|
import (
|
|
"github.com/netbirdio/netbird/management/proto"
|
|
"github.com/netbirdio/netbird/management/server/activity"
|
|
"github.com/netbirdio/netbird/management/server/status"
|
|
"github.com/netbirdio/netbird/route"
|
|
"github.com/rs/xid"
|
|
log "github.com/sirupsen/logrus"
|
|
"net/netip"
|
|
"strconv"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
const (
|
|
// UpdateRouteDescription indicates a route description update operation
|
|
UpdateRouteDescription RouteUpdateOperationType = iota
|
|
// UpdateRouteNetwork indicates a route IP update operation
|
|
UpdateRouteNetwork
|
|
// UpdateRoutePeer indicates a route peer update operation
|
|
UpdateRoutePeer
|
|
// UpdateRouteMetric indicates a route metric update operation
|
|
UpdateRouteMetric
|
|
// UpdateRouteMasquerade indicates a route masquerade update operation
|
|
UpdateRouteMasquerade
|
|
// UpdateRouteEnabled indicates a route enabled update operation
|
|
UpdateRouteEnabled
|
|
// UpdateRouteNetworkIdentifier indicates a route net ID update operation
|
|
UpdateRouteNetworkIdentifier
|
|
// UpdateRouteGroups indicates a group list update operation
|
|
UpdateRouteGroups
|
|
)
|
|
|
|
// RouteUpdateOperationType operation type
|
|
type RouteUpdateOperationType int
|
|
|
|
func (t RouteUpdateOperationType) String() string {
|
|
switch t {
|
|
case UpdateRouteDescription:
|
|
return "UpdateRouteDescription"
|
|
case UpdateRouteNetwork:
|
|
return "UpdateRouteNetwork"
|
|
case UpdateRoutePeer:
|
|
return "UpdateRoutePeer"
|
|
case UpdateRouteMetric:
|
|
return "UpdateRouteMetric"
|
|
case UpdateRouteMasquerade:
|
|
return "UpdateRouteMasquerade"
|
|
case UpdateRouteEnabled:
|
|
return "UpdateRouteEnabled"
|
|
case UpdateRouteNetworkIdentifier:
|
|
return "UpdateRouteNetworkIdentifier"
|
|
case UpdateRouteGroups:
|
|
return "UpdateRouteGroups"
|
|
default:
|
|
return "InvalidOperation"
|
|
}
|
|
}
|
|
|
|
// RouteUpdateOperation operation object with type and values to be applied
|
|
type RouteUpdateOperation struct {
|
|
Type RouteUpdateOperationType
|
|
Values []string
|
|
}
|
|
|
|
// GetRoute gets a route object from account and route IDs
|
|
func (am *DefaultAccountManager) GetRoute(accountID, routeID, userID string) (*route.Route, error) {
|
|
unlock := am.Store.AcquireAccountLock(accountID)
|
|
defer unlock()
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
user, err := account.FindUser(userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !user.IsAdmin() {
|
|
return nil, status.Errorf(status.PermissionDenied, "Only administrators can view Network Routes")
|
|
}
|
|
|
|
wantedRoute, found := account.Routes[routeID]
|
|
if found {
|
|
return wantedRoute, nil
|
|
}
|
|
|
|
return nil, status.Errorf(status.NotFound, "route with ID %s not found", routeID)
|
|
}
|
|
|
|
// checkPrefixPeerExists checks the combination of prefix and peer id, if it exists returns an error, otherwise returns nil
|
|
func (am *DefaultAccountManager) checkPrefixPeerExists(accountID, peerID string, prefix netip.Prefix) error {
|
|
|
|
if peerID == "" {
|
|
return nil
|
|
}
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
routesWithPrefix := account.GetRoutesByPrefix(prefix)
|
|
|
|
if err != nil {
|
|
if s, ok := status.FromError(err); ok && s.Type() == status.NotFound {
|
|
return nil
|
|
}
|
|
return status.Errorf(status.InvalidArgument, "failed to parse prefix %s", prefix.String())
|
|
}
|
|
for _, prefixRoute := range routesWithPrefix {
|
|
if prefixRoute.Peer == peerID {
|
|
return status.Errorf(status.AlreadyExists, "failed to add route with prefix %s - peer already has this route", prefix.String())
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CreateRoute creates and saves a new route
|
|
func (am *DefaultAccountManager) CreateRoute(accountID string, network, peerID, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error) {
|
|
unlock := am.Store.AcquireAccountLock(accountID)
|
|
defer unlock()
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if peerID != "" {
|
|
peer := account.GetPeer(peerID)
|
|
if peer == nil {
|
|
return nil, status.Errorf(status.InvalidArgument, "peer with ID %s not found", peerID)
|
|
}
|
|
}
|
|
|
|
var newRoute route.Route
|
|
prefixType, newPrefix, err := route.ParseNetwork(network)
|
|
if err != nil {
|
|
return nil, status.Errorf(status.InvalidArgument, "failed to parse IP %s", network)
|
|
}
|
|
err = am.checkPrefixPeerExists(accountID, peerID, newPrefix)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if metric < route.MinMetric || metric > route.MaxMetric {
|
|
return nil, status.Errorf(status.InvalidArgument, "metric should be between %d and %d", route.MinMetric, route.MaxMetric)
|
|
}
|
|
|
|
if utf8.RuneCountInString(netID) > route.MaxNetIDChar || netID == "" {
|
|
return nil, status.Errorf(status.InvalidArgument, "identifier should be between 1 and %d", route.MaxNetIDChar)
|
|
}
|
|
|
|
err = validateGroups(groups, account.Groups)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
newRoute.Peer = peerID
|
|
newRoute.ID = xid.New().String()
|
|
newRoute.Network = newPrefix
|
|
newRoute.NetworkType = prefixType
|
|
newRoute.Description = description
|
|
newRoute.NetID = netID
|
|
newRoute.Masquerade = masquerade
|
|
newRoute.Metric = metric
|
|
newRoute.Enabled = enabled
|
|
newRoute.Groups = groups
|
|
|
|
if account.Routes == nil {
|
|
account.Routes = make(map[string]*route.Route)
|
|
}
|
|
|
|
account.Routes[newRoute.ID] = &newRoute
|
|
|
|
account.Network.IncSerial()
|
|
if err = am.Store.SaveAccount(account); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = am.updateAccountPeers(account)
|
|
if err != nil {
|
|
log.Error(err)
|
|
return &newRoute, status.Errorf(status.Internal, "failed to update peers after create route %s", newPrefix)
|
|
}
|
|
|
|
am.storeEvent(userID, newRoute.ID, accountID, activity.RouteCreated, newRoute.EventMeta())
|
|
|
|
return &newRoute, nil
|
|
}
|
|
|
|
// SaveRoute saves route
|
|
func (am *DefaultAccountManager) SaveRoute(accountID, userID string, routeToSave *route.Route) error {
|
|
unlock := am.Store.AcquireAccountLock(accountID)
|
|
defer unlock()
|
|
|
|
if routeToSave == nil {
|
|
return status.Errorf(status.InvalidArgument, "route provided is nil")
|
|
}
|
|
|
|
if !routeToSave.Network.IsValid() {
|
|
return status.Errorf(status.InvalidArgument, "invalid Prefix %s", routeToSave.Network.String())
|
|
}
|
|
|
|
if routeToSave.Metric < route.MinMetric || routeToSave.Metric > route.MaxMetric {
|
|
return status.Errorf(status.InvalidArgument, "metric should be between %d and %d", route.MinMetric, route.MaxMetric)
|
|
}
|
|
|
|
if utf8.RuneCountInString(routeToSave.NetID) > route.MaxNetIDChar || routeToSave.NetID == "" {
|
|
return status.Errorf(status.InvalidArgument, "identifier should be between 1 and %d", route.MaxNetIDChar)
|
|
}
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if routeToSave.Peer != "" {
|
|
peer := account.GetPeer(routeToSave.Peer)
|
|
if peer == nil {
|
|
return status.Errorf(status.InvalidArgument, "peer with ID %s not found", routeToSave.Peer)
|
|
}
|
|
}
|
|
|
|
err = validateGroups(routeToSave.Groups, account.Groups)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
account.Routes[routeToSave.ID] = routeToSave
|
|
|
|
account.Network.IncSerial()
|
|
if err = am.Store.SaveAccount(account); err != nil {
|
|
return err
|
|
}
|
|
|
|
err = am.updateAccountPeers(account)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
am.storeEvent(userID, routeToSave.ID, accountID, activity.RouteUpdated, routeToSave.EventMeta())
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateRoute updates existing route with set of operations
|
|
func (am *DefaultAccountManager) UpdateRoute(accountID, routeID string, operations []RouteUpdateOperation) (*route.Route, error) {
|
|
unlock := am.Store.AcquireAccountLock(accountID)
|
|
defer unlock()
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
routeToUpdate, ok := account.Routes[routeID]
|
|
if !ok {
|
|
return nil, status.Errorf(status.NotFound, "route %s no longer exists", routeID)
|
|
}
|
|
|
|
newRoute := routeToUpdate.Copy()
|
|
|
|
for _, operation := range operations {
|
|
|
|
if len(operation.Values) != 1 {
|
|
return nil, status.Errorf(status.InvalidArgument, "operation %s contains invalid number of values, it should be 1", operation.Type.String())
|
|
}
|
|
|
|
switch operation.Type {
|
|
case UpdateRouteDescription:
|
|
newRoute.Description = operation.Values[0]
|
|
case UpdateRouteNetworkIdentifier:
|
|
if utf8.RuneCountInString(operation.Values[0]) > route.MaxNetIDChar || operation.Values[0] == "" {
|
|
return nil, status.Errorf(status.InvalidArgument, "identifier should be between 1 and %d", route.MaxNetIDChar)
|
|
}
|
|
newRoute.NetID = operation.Values[0]
|
|
case UpdateRouteNetwork:
|
|
prefixType, prefix, err := route.ParseNetwork(operation.Values[0])
|
|
if err != nil {
|
|
return nil, status.Errorf(status.InvalidArgument, "failed to parse IP %s", operation.Values[0])
|
|
}
|
|
err = am.checkPrefixPeerExists(accountID, routeToUpdate.Peer, prefix)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newRoute.Network = prefix
|
|
newRoute.NetworkType = prefixType
|
|
case UpdateRoutePeer:
|
|
if operation.Values[0] != "" {
|
|
peer := account.GetPeer(operation.Values[0])
|
|
if peer == nil {
|
|
return nil, status.Errorf(status.InvalidArgument, "peer with ID %s not found", operation.Values[0])
|
|
}
|
|
}
|
|
|
|
err = am.checkPrefixPeerExists(accountID, operation.Values[0], routeToUpdate.Network)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newRoute.Peer = operation.Values[0]
|
|
case UpdateRouteMetric:
|
|
metric, err := strconv.Atoi(operation.Values[0])
|
|
if err != nil {
|
|
return nil, status.Errorf(status.InvalidArgument, "failed to parse metric %s, not int", operation.Values[0])
|
|
}
|
|
if metric < route.MinMetric || metric > route.MaxMetric {
|
|
return nil, status.Errorf(status.InvalidArgument, "failed to parse metric %s, value should be %d > N < %d",
|
|
operation.Values[0],
|
|
route.MinMetric,
|
|
route.MaxMetric,
|
|
)
|
|
}
|
|
newRoute.Metric = metric
|
|
case UpdateRouteMasquerade:
|
|
masquerade, err := strconv.ParseBool(operation.Values[0])
|
|
if err != nil {
|
|
return nil, status.Errorf(status.InvalidArgument, "failed to parse masquerade %s, not boolean", operation.Values[0])
|
|
}
|
|
newRoute.Masquerade = masquerade
|
|
case UpdateRouteEnabled:
|
|
enabled, err := strconv.ParseBool(operation.Values[0])
|
|
if err != nil {
|
|
return nil, status.Errorf(status.InvalidArgument, "failed to parse enabled %s, not boolean", operation.Values[0])
|
|
}
|
|
newRoute.Enabled = enabled
|
|
case UpdateRouteGroups:
|
|
err = validateGroups(operation.Values, account.Groups)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
newRoute.Groups = operation.Values
|
|
}
|
|
}
|
|
|
|
account.Routes[routeID] = newRoute
|
|
|
|
account.Network.IncSerial()
|
|
if err = am.Store.SaveAccount(account); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = am.updateAccountPeers(account)
|
|
if err != nil {
|
|
return nil, status.Errorf(status.Internal, "failed to update account peers")
|
|
}
|
|
return newRoute, nil
|
|
}
|
|
|
|
// DeleteRoute deletes route with routeID
|
|
func (am *DefaultAccountManager) DeleteRoute(accountID, routeID, userID string) error {
|
|
unlock := am.Store.AcquireAccountLock(accountID)
|
|
defer unlock()
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
routy := account.Routes[routeID]
|
|
if routy == nil {
|
|
return status.Errorf(status.NotFound, "route with ID %s doesn't exist", routeID)
|
|
}
|
|
delete(account.Routes, routeID)
|
|
|
|
account.Network.IncSerial()
|
|
if err = am.Store.SaveAccount(account); err != nil {
|
|
return err
|
|
}
|
|
|
|
am.storeEvent(userID, routy.ID, accountID, activity.RouteRemoved, routy.EventMeta())
|
|
|
|
return am.updateAccountPeers(account)
|
|
}
|
|
|
|
// ListRoutes returns a list of routes from account
|
|
func (am *DefaultAccountManager) ListRoutes(accountID, userID string) ([]*route.Route, error) {
|
|
unlock := am.Store.AcquireAccountLock(accountID)
|
|
defer unlock()
|
|
|
|
account, err := am.Store.GetAccount(accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
user, err := account.FindUser(userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !user.IsAdmin() {
|
|
return nil, status.Errorf(status.PermissionDenied, "Only administrators can view Network Routes")
|
|
}
|
|
|
|
routes := make([]*route.Route, 0, len(account.Routes))
|
|
for _, item := range account.Routes {
|
|
routes = append(routes, item)
|
|
}
|
|
|
|
return routes, nil
|
|
}
|
|
|
|
func toProtocolRoute(route *route.Route) *proto.Route {
|
|
return &proto.Route{
|
|
ID: route.ID,
|
|
NetID: route.NetID,
|
|
Network: route.Network.String(),
|
|
NetworkType: int64(route.NetworkType),
|
|
Peer: route.Peer,
|
|
Metric: int64(route.Metric),
|
|
Masquerade: route.Masquerade,
|
|
}
|
|
}
|
|
|
|
func toProtocolRoutes(routes []*route.Route) []*proto.Route {
|
|
protoRoutes := make([]*proto.Route, 0)
|
|
for _, r := range routes {
|
|
protoRoutes = append(protoRoutes, toProtocolRoute(r))
|
|
}
|
|
return protoRoutes
|
|
}
|