Browse Source

NET-1932: add failover inet gw check (#3478)

* move relevant acl and tag code to CE and Pro pkgs

* intialise pro acl funcs

* list gateways by user access

* check user gw access by policies

* filter out user policies on CE

* filter out tagged policies on CE

* fix ce acl comms

* allow gateways tag

* allow gateway tag  on CE, remove failover and gw check on acl policy

* add gw rules func to pro

* add inet gw support on CE

* add egress acl API

* add egress acl API

* fix(go): set is_gw when converting api node to server node;

* fix(go): set is_gw when converting api node to server node;

* fix policy validity checker for inet gws

* move dns option to host model

* fix node removal from egress policy on delete

* add migration logic for ManageDNS

* fix dns json field

* fix nil error on node tags

* add egress info to relayed nodes

* fix default network user policy

* fix egress migration

* fix egress migration

* add failover inet gw check

* optiomise egress calls

* auto create gw on inet egress node

* optimise egress calls

* add global user role check

---------

Co-authored-by: Vishal Dalwadi <[email protected]>
Abhishek K 3 months ago
parent
commit
3bae08797f

+ 2 - 0
controllers/acls.go

@@ -176,6 +176,7 @@ func aclDebug(w http.ResponseWriter, r *http.Request) {
 		Policies      []models.Acl
 		IngressRules  []models.FwRule
 		NodeAllPolicy bool
+		EgressNets    map[string]models.Node
 	}
 
 	allowed, ps := logic.IsNodeAllowedToCommunicate(node, peer, true)
@@ -184,6 +185,7 @@ func aclDebug(w http.ResponseWriter, r *http.Request) {
 		IsNodeAllowed: allowed,
 		IsPeerAllowed: isallowed,
 		Policies:      ps,
+		EgressNets:    logic.GetNetworkEgressInfo(models.NetworkID(node.Network)),
 	}
 	if peerIsStatic == "true" {
 		ingress, err := logic.GetNodeByID(peer.StaticNode.IngressGatewayID)

+ 11 - 0
controllers/egress.go

@@ -85,6 +85,17 @@ func createEgress(w http.ResponseWriter, r *http.Request) {
 		)
 		return
 	}
+	if e.IsInetGw {
+		for nodeID := range req.Nodes {
+			node, err := logic.GetNodeByID(nodeID)
+			if err == nil && !node.IsGw {
+				node.IsGw = true
+				node.IsIngressGateway = true
+				node.IsRelay = true
+				logic.UpsertNode(&node)
+			}
+		}
+	}
 	logic.LogEvent(&models.Event{
 		Action: models.Create,
 		Source: models.Subject{

+ 5 - 1
controllers/ext_client.go

@@ -1,6 +1,7 @@
 package controller
 
 import (
+	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -13,8 +14,10 @@ import (
 	"github.com/go-playground/validator/v10"
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/db"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/schema"
 	"github.com/gravitl/netmaker/servercfg"
 
 	"github.com/gravitl/netmaker/models"
@@ -174,7 +177,8 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
-	logic.GetNodeEgressInfo(&gwnode)
+	eli, _ := (&schema.Egress{Network: gwnode.Network}).ListByNetwork(db.WithContext(context.TODO()))
+	logic.GetNodeEgressInfo(&gwnode, eli)
 	host, err := logic.GetHost(gwnode.HostID.String())
 	if err != nil {
 		logger.Log(

+ 94 - 13
logic/egress.go

@@ -37,14 +37,15 @@ func ValidateEgressReq(e *schema.Egress) error {
 			return errors.New("can only set one internet routing node")
 		}
 		req := models.InetNodeReq{}
-
+		eli, _ := (&schema.Egress{Network: e.Network}).ListByNetwork(db.WithContext(context.TODO()))
 		for k := range e.Nodes {
 			inetNode, err := GetNodeByID(k)
 			if err != nil {
 				return errors.New("invalid routing node " + err.Error())
 			}
 			// check if node is acting as egress gw already
-			GetNodeEgressInfo(&inetNode)
+
+			GetNodeEgressInfo(&inetNode, eli)
 			if err := ValidateInetGwReq(inetNode, req, false); err != nil {
 				return err
 			}
@@ -69,13 +70,14 @@ func DoesNodeHaveAccessToEgress(node *models.Node, e *schema.Egress) bool {
 	if !e.IsInetGw {
 		nodeTags[models.TagID("*")] = struct{}{}
 	}
-	acls, _ := ListAclsByNetwork(models.NetworkID(node.Network))
+
 	if !e.IsInetGw {
 		defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
 		if defaultDevicePolicy.Enabled {
 			return true
 		}
 	}
+	acls, _ := ListAclsByNetwork(models.NetworkID(node.Network))
 	for _, acl := range acls {
 		if !acl.Enabled {
 			continue
@@ -119,8 +121,8 @@ func DoesNodeHaveAccessToEgress(node *models.Node, e *schema.Egress) bool {
 	return false
 }
 
-func AddEgressInfoToPeerByAccess(node, targetNode *models.Node) {
-	eli, _ := (&schema.Egress{Network: targetNode.Network}).ListByNetwork(db.WithContext(context.TODO()))
+func AddEgressInfoToPeerByAccess(node, targetNode *models.Node, eli []schema.Egress, isDefaultPolicyActive bool) {
+
 	req := models.EgressGatewayRequest{
 		NodeID: targetNode.ID.String(),
 		NetID:  targetNode.Network,
@@ -138,16 +140,19 @@ func AddEgressInfoToPeerByAccess(node, targetNode *models.Node) {
 		if !e.Status || e.Network != targetNode.Network {
 			continue
 		}
-		if !DoesNodeHaveAccessToEgress(node, &e) {
-			if node.IsRelayed && node.RelayedBy == targetNode.ID.String() {
-				if !DoesNodeHaveAccessToEgress(targetNode, &e) {
+		if !isDefaultPolicyActive {
+			if !DoesNodeHaveAccessToEgress(node, &e) {
+				if node.IsRelayed && node.RelayedBy == targetNode.ID.String() {
+					if !DoesNodeHaveAccessToEgress(targetNode, &e) {
+						continue
+					}
+				} else {
 					continue
 				}
-			} else {
-				continue
-			}
 
+			}
 		}
+
 		if metric, ok := e.Nodes[targetNode.ID.String()]; ok {
 			if e.IsInetGw {
 				targetNode.EgressDetails.IsInternetGateway = true
@@ -199,8 +204,84 @@ func AddEgressInfoToPeerByAccess(node, targetNode *models.Node) {
 	}
 }
 
-func GetNodeEgressInfo(targetNode *models.Node) {
-	eli, _ := (&schema.Egress{Network: targetNode.Network}).ListByNetwork(db.WithContext(context.TODO()))
+// TODO
+func GetNetworkEgressInfo(network models.NetworkID) (egressNodes map[string]models.Node) {
+	eli, _ := (&schema.Egress{Network: network.String()}).ListByNetwork(db.WithContext(context.TODO()))
+	egressNodes = make(map[string]models.Node)
+	var err error
+	for _, e := range eli {
+		if !e.Status || e.Nodes == nil {
+			continue
+		}
+
+		for nodeID, metric := range e.Nodes {
+
+			targetNode, ok := egressNodes[nodeID]
+			if !ok {
+				targetNode, err = GetNodeByID(nodeID)
+				if err != nil {
+					continue
+				}
+			}
+			req := models.EgressGatewayRequest{
+				NodeID: targetNode.ID.String(),
+				NetID:  targetNode.Network,
+			}
+			IsNodeUsingInternetGw(&targetNode)
+			if e.IsInetGw {
+				targetNode.EgressDetails.IsInternetGateway = true
+				targetNode.EgressDetails.InetNodeReq = models.InetNodeReq{
+					InetNodeClientIDs: GetInetClientsFromAclPolicies(e.ID),
+				}
+				req.Ranges = append(req.Ranges, "0.0.0.0/0")
+				req.RangesWithMetric = append(req.RangesWithMetric, models.EgressRangeMetric{
+					Network:     "0.0.0.0/0",
+					Nat:         true,
+					RouteMetric: 256,
+				})
+				req.Ranges = append(req.Ranges, "::/0")
+				req.RangesWithMetric = append(req.RangesWithMetric, models.EgressRangeMetric{
+					Network:     "::/0",
+					Nat:         true,
+					RouteMetric: 256,
+				})
+			} else {
+				m64, err := metric.(json.Number).Int64()
+				if err != nil {
+					m64 = 256
+				}
+				m := uint32(m64)
+				req.Ranges = append(req.Ranges, e.Range)
+				req.RangesWithMetric = append(req.RangesWithMetric, models.EgressRangeMetric{
+					Network:     e.Range,
+					Nat:         e.Nat,
+					RouteMetric: m,
+				})
+			}
+			if targetNode.Mutex != nil {
+				targetNode.Mutex.Lock()
+			}
+			if len(req.Ranges) > 0 {
+				targetNode.EgressDetails.IsEgressGateway = true
+				targetNode.EgressDetails.EgressGatewayRanges = append(targetNode.EgressDetails.EgressGatewayRanges, req.Ranges...)
+				targetNode.EgressDetails.EgressGatewayRequest.Ranges = append(targetNode.EgressDetails.EgressGatewayRequest.Ranges, req.Ranges...)
+				targetNode.EgressDetails.EgressGatewayRequest.RangesWithMetric = append(targetNode.EgressDetails.EgressGatewayRequest.RangesWithMetric,
+					req.RangesWithMetric...)
+				targetNode.EgressDetails.EgressGatewayRequest = req
+				egressNodes[targetNode.ID.String()] = targetNode
+			}
+			if targetNode.Mutex != nil {
+				targetNode.Mutex.Unlock()
+			}
+
+		}
+
+	}
+	return
+}
+
+func GetNodeEgressInfo(targetNode *models.Node, eli []schema.Egress) {
+
 	req := models.EgressGatewayRequest{
 		NodeID: targetNode.ID.String(),
 		NetID:  targetNode.Network,

+ 5 - 1
logic/extpeers.go

@@ -1,6 +1,7 @@
 package logic
 
 import (
+	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -13,9 +14,11 @@ import (
 
 	"github.com/goombaio/namegenerator"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/db"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic/acls"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/schema"
 	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/exp/slog"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@@ -71,12 +74,13 @@ func GetEgressRangesOnNetwork(client *models.ExtClient) ([]string, error) {
 	if err != nil {
 		return []string{}, err
 	}
+	eli, _ := (&schema.Egress{Network: client.Network}).ListByNetwork(db.WithContext(context.TODO()))
 	// clientNode := client.ConvertToStaticNode()
 	for _, currentNode := range networkNodes {
 		if currentNode.Network != client.Network {
 			continue
 		}
-		GetNodeEgressInfo(&currentNode)
+		GetNodeEgressInfo(&currentNode, eli)
 		if currentNode.EgressDetails.IsInternetGateway && client.IngressGatewayID != currentNode.ID.String() {
 			continue
 		}

+ 9 - 3
logic/peers.go

@@ -1,6 +1,7 @@
 package logic
 
 import (
+	"context"
 	"errors"
 	"fmt"
 	"net"
@@ -9,9 +10,11 @@ import (
 
 	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/db"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/schema"
 	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/exp/slices"
 	"golang.org/x/exp/slog"
@@ -175,8 +178,8 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 		if !node.Connected || node.PendingDelete || node.Action == models.NODE_DELETE || time.Since(node.LastCheckIn) > time.Hour {
 			continue
 		}
-
-		GetNodeEgressInfo(&node)
+		eli, _ := (&schema.Egress{Network: node.Network}).ListByNetwork(db.WithContext(context.TODO()))
+		GetNodeEgressInfo(&node, eli)
 		hostPeerUpdate = SetDefaultGw(node, hostPeerUpdate)
 		if !hostPeerUpdate.IsInternetGw {
 			hostPeerUpdate.IsInternetGw = IsInternetGw(node)
@@ -238,7 +241,10 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 				PersistentKeepaliveInterval: &peerHost.PersistentKeepalive,
 				ReplaceAllowedIPs:           true,
 			}
-			AddEgressInfoToPeerByAccess(&node, &peer)
+			GetNodeEgressInfo(&peer, eli)
+			if peer.EgressDetails.IsEgressGateway {
+				AddEgressInfoToPeerByAccess(&node, &peer, eli, defaultDevicePolicy.Enabled)
+			}
 			_, isFailOverPeer := node.FailOverPeers[peer.ID.String()]
 			if peer.EgressDetails.IsEgressGateway {
 				peerKey := peerHost.PublicKey.String()

+ 9 - 3
logic/relay.go

@@ -1,14 +1,17 @@
 package logic
 
 import (
+	"context"
 	"errors"
 	"fmt"
 	"net"
 
 	"github.com/google/uuid"
+	"github.com/gravitl/netmaker/db"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/schema"
 )
 
 // GetRelays - gets all the nodes that are relays
@@ -109,12 +112,13 @@ func ValidateRelay(relay models.RelayRequest, update bool) error {
 	if !update && node.IsRelay {
 		return errors.New("node is already acting as a relay")
 	}
+	eli, _ := (&schema.Egress{Network: node.Network}).ListByNetwork(db.WithContext(context.TODO()))
 	for _, relayedNodeID := range relay.RelayedNodes {
 		relayedNode, err := GetNodeByID(relayedNodeID)
 		if err != nil {
 			return err
 		}
-		GetNodeEgressInfo(&relayedNode)
+		GetNodeEgressInfo(&relayedNode, eli)
 		if relayedNode.IsIngressGateway {
 			return errors.New("cannot relay an ingress gateway (" + relayedNodeID + ")")
 		}
@@ -186,6 +190,7 @@ func DeleteRelay(network, nodeid string) ([]models.Node, models.Node, error) {
 
 func RelayedAllowedIPs(peer, node *models.Node) []net.IPNet {
 	var allowedIPs = []net.IPNet{}
+	eli, _ := (&schema.Egress{Network: node.Network}).ListByNetwork(db.WithContext(context.TODO()))
 	for _, relayedNodeID := range peer.RelayedNodes {
 		if node.ID.String() == relayedNodeID {
 			continue
@@ -194,7 +199,7 @@ func RelayedAllowedIPs(peer, node *models.Node) []net.IPNet {
 		if err != nil {
 			continue
 		}
-		GetNodeEgressInfo(&relayedNode)
+		GetNodeEgressInfo(&relayedNode, eli)
 		allowed := getRelayedAddresses(relayedNodeID)
 		if relayedNode.EgressDetails.IsEgressGateway {
 			allowed = append(allowed, GetEgressIPs(&relayedNode)...)
@@ -218,6 +223,7 @@ func GetAllowedIpsForRelayed(relayed, relay *models.Node) (allowedIPs []net.IPNe
 		logger.Log(0, "error getting network clients", err.Error())
 		return
 	}
+	eli, _ := (&schema.Egress{Network: relay.Network}).ListByNetwork(db.WithContext(context.TODO()))
 	for _, peer := range peers {
 		if peer.ID == relayed.ID || peer.ID == relay.ID {
 			continue
@@ -225,7 +231,7 @@ func GetAllowedIpsForRelayed(relayed, relay *models.Node) (allowedIPs []net.IPNe
 		if !IsPeerAllowed(*relayed, peer, true) {
 			continue
 		}
-		GetNodeEgressInfo(&peer)
+		GetNodeEgressInfo(&peer, eli)
 		if nodeacls.AreNodesAllowed(nodeacls.NetworkID(relayed.Network), nodeacls.NodeID(relayed.ID.String()), nodeacls.NodeID(peer.ID.String())) {
 			allowedIPs = append(allowedIPs, GetAllowedIPs(relayed, &peer, nil)...)
 		}

+ 25 - 5
pro/controllers/failover.go

@@ -1,6 +1,7 @@
 package controllers
 
 import (
+	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -9,11 +10,13 @@ import (
 	"github.com/google/uuid"
 	"github.com/gorilla/mux"
 	controller "github.com/gravitl/netmaker/controllers"
+	"github.com/gravitl/netmaker/db"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/mq"
 	proLogic "github.com/gravitl/netmaker/pro/logic"
+	"github.com/gravitl/netmaker/schema"
 	"golang.org/x/exp/slog"
 )
 
@@ -205,8 +208,10 @@ func failOverME(w http.ResponseWriter, r *http.Request) {
 		)
 		return
 	}
-	logic.GetNodeEgressInfo(&node)
-	logic.GetNodeEgressInfo(&peerNode)
+	eli, _ := (&schema.Egress{Network: node.Network}).ListByNetwork(db.WithContext(context.TODO()))
+	logic.GetNodeEgressInfo(&node, eli)
+	logic.GetNodeEgressInfo(&peerNode, eli)
+	logic.GetNodeEgressInfo(&failOverNode, eli)
 	if peerNode.IsFailOver {
 		logic.ReturnErrorResponse(
 			w,
@@ -247,6 +252,18 @@ func failOverME(w http.ResponseWriter, r *http.Request) {
 		)
 		return
 	}
+	if (node.EgressDetails.InternetGwID != "" && failOverNode.EgressDetails.IsInternetGateway && node.EgressDetails.InternetGwID != failOverNode.ID.String()) ||
+		(peerNode.EgressDetails.InternetGwID != "" && failOverNode.EgressDetails.IsInternetGateway && peerNode.EgressDetails.InternetGwID != failOverNode.ID.String()) {
+		logic.ReturnErrorResponse(
+			w,
+			r,
+			logic.FormatError(
+				errors.New("node using a internet gw by the peer node"),
+				"badrequest",
+			),
+		)
+		return
+	}
 	if node.EgressDetails.IsInternetGateway && peerNode.EgressDetails.InternetGwID == node.ID.String() {
 		logic.ReturnErrorResponse(
 			w,
@@ -351,8 +368,10 @@ func checkfailOverCtx(w http.ResponseWriter, r *http.Request) {
 		)
 		return
 	}
-	logic.GetNodeEgressInfo(&node)
-	logic.GetNodeEgressInfo(&peerNode)
+	eli, _ := (&schema.Egress{Network: node.Network}).ListByNetwork(db.WithContext(context.TODO()))
+	logic.GetNodeEgressInfo(&node, eli)
+	logic.GetNodeEgressInfo(&peerNode, eli)
+	logic.GetNodeEgressInfo(&failOverNode, eli)
 	if peerNode.IsFailOver {
 		logic.ReturnErrorResponse(
 			w,
@@ -393,7 +412,8 @@ func checkfailOverCtx(w http.ResponseWriter, r *http.Request) {
 		)
 		return
 	}
-	if node.EgressDetails.InternetGwID != "" || peerNode.EgressDetails.InternetGwID != "" {
+	if (node.EgressDetails.InternetGwID != "" && failOverNode.EgressDetails.IsInternetGateway && node.EgressDetails.InternetGwID != failOverNode.ID.String()) ||
+		(peerNode.EgressDetails.InternetGwID != "" && failOverNode.EgressDetails.IsInternetGateway && peerNode.EgressDetails.InternetGwID != failOverNode.ID.String()) {
 		logic.ReturnErrorResponse(
 			w,
 			r,

+ 5 - 1
pro/logic/failover.go

@@ -1,14 +1,17 @@
 package logic
 
 import (
+	"context"
 	"errors"
 	"net"
 	"sync"
 
 	"github.com/google/uuid"
+	"github.com/gravitl/netmaker/db"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/schema"
 	"golang.org/x/exp/slog"
 )
 
@@ -162,10 +165,11 @@ func ResetFailOver(failOverNode *models.Node) error {
 // GetFailOverPeerIps - adds the failedOvered peerIps by the peer
 func GetFailOverPeerIps(peer, node *models.Node) []net.IPNet {
 	allowedips := []net.IPNet{}
+	eli, _ := (&schema.Egress{Network: node.Network}).ListByNetwork(db.WithContext(context.TODO()))
 	for failOverpeerID := range node.FailOverPeers {
 		failOverpeer, err := logic.GetNodeByID(failOverpeerID)
 		if err == nil && failOverpeer.FailedOverBy == peer.ID {
-			logic.GetNodeEgressInfo(&failOverpeer)
+			logic.GetNodeEgressInfo(&failOverpeer, eli)
 			if failOverpeer.Address.IP != nil {
 				allowed := net.IPNet{
 					IP:   failOverpeer.Address.IP,

+ 12 - 0
pro/logic/user_mgmt.go

@@ -738,6 +738,12 @@ func GetUserRAGNodes(user models.User) (gws map[string]models.Node) {
 					continue
 				}
 			}
+			if roles, ok := user.NetworkRoles[models.AllNetworks]; ok && len(roles) > 0 {
+				if ok, _ := IsUserAllowedToCommunicate(user.UserName, node); ok {
+					gws[node.ID.String()] = node
+					continue
+				}
+			}
 			for groupID := range user.UserGroups {
 				userGrp, err := logic.GetUserGroup(groupID)
 				if err == nil {
@@ -747,6 +753,12 @@ func GetUserRAGNodes(user models.User) (gws map[string]models.Node) {
 							break
 						}
 					}
+					if roles, ok := userGrp.NetworkRoles[models.AllNetworks]; ok && len(roles) > 0 {
+						if ok, _ := IsUserAllowedToCommunicate(user.UserName, node); ok {
+							gws[node.ID.String()] = node
+							break
+						}
+					}
 				}
 			}