mirror of
https://github.com/netbirdio/netbird.git
synced 2025-02-02 19:39:17 +01:00
bb40325977
This PR showcases the implementation of additional linter rules. I've updated the golangci-lint GitHub Actions to the latest available version. This update makes sure that the tool works the same way locally - assuming being updated regularly - and with the GitHub Actions. I've also taken care of keeping all the GitHub Actions up to date, which helps our code stay current. But there's one part, goreleaser that's a bit tricky to test on our computers. So, it's important to take a close look at that. To make it easier to understand what I've done, I've made separate changes for each thing that the new linters found. This should help the people reviewing the changes see what's going on more clearly. Some of the changes might not be obvious at first glance. Things to consider for the future CI runs on Ubuntu so the static analysis only happens for Linux. Consider running it for the rest: Darwin, Windows
419 lines
12 KiB
Go
419 lines
12 KiB
Go
package server
|
|
|
|
import (
|
|
"net/netip"
|
|
"strconv"
|
|
"unicode/utf8"
|
|
|
|
"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"
|
|
)
|
|
|
|
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)
|
|
|
|
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
|
|
}
|