Ver Fonte

Merge branch 'NET-1911' of https://github.com/gravitl/netmaker into NET-1910

abhishek9686 há 6 meses atrás
pai
commit
3cf0b5eb42

+ 55 - 0
cli/cmd/gateway/create.go

@@ -0,0 +1,55 @@
+package gateway
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/spf13/cobra"
+	"strings"
+)
+
+var externalClientDNS string
+var isInternetGateway bool
+var metadata string
+var persistentKeepAlive uint
+var mtu uint
+
+var gatewayCreateCmd = &cobra.Command{
+	Use:   "create [NETWORK NAME] [NODE ID] [RELAYED NODES ID (comma separated)]",
+	Args:  cobra.ExactArgs(3),
+	Short: "Create a new Gateway on a Netmaker network.",
+	Long: `
+Configures a node as a gateway in a specified network, allowing it to relay traffic for other nodes. The gateway can also function as an internet gateway if specified.
+
+Arguments:
+NETWORK NAME:		The name of the network where the gateway will be created.
+NODE ID:			The ID of the node to be configured as a gateway.
+RELAYED NODES ID:	A comma-separated list of node IDs that will be relayed through this gateway.
+`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(
+			functions.CreateGateway(
+				models.IngressRequest{
+					ExtclientDNS:        externalClientDNS,
+					IsInternetGateway:   isInternetGateway,
+					Metadata:            metadata,
+					PersistentKeepalive: int32(persistentKeepAlive),
+					MTU:                 int32(mtu),
+				},
+				models.RelayRequest{
+					NodeID:       args[0],
+					NetID:        args[1],
+					RelayedNodes: strings.Split(args[2], ","),
+				},
+			),
+		)
+	},
+}
+
+func init() {
+	gatewayCreateCmd.Flags().StringVarP(&externalClientDNS, "dns", "d", "", "the IP address of the DNS server to be used by external clients")
+	gatewayCreateCmd.Flags().BoolVarP(&isInternetGateway, "internet", "i", false, "if set, the gateway will route traffic to the internet")
+	gatewayCreateCmd.Flags().StringVarP(&metadata, "note", "n", "", "description or metadata to be associated with the gateway")
+	gatewayCreateCmd.Flags().UintVarP(&persistentKeepAlive, "keep-alive", "k", 20, "the keep-alive interval (in seconds) for maintaining persistent connections")
+	gatewayCreateCmd.Flags().UintVarP(&mtu, "mtu", "m", 1420, "the maximum transmission unit (MTU) size in bytes")
+	rootCmd.AddCommand(gatewayCreateCmd)
+}

+ 27 - 0
cli/cmd/gateway/delete.go

@@ -0,0 +1,27 @@
+package gateway
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var gatewayDeleteCmd = &cobra.Command{
+	Use:   "delete [NETWORK NAME] [NODE ID]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Delete a Gateway.",
+	Long: `
+Removes the gateway configuration from a node in a specified network. The node itself remains, but it will no longer function as a gateway.
+
+Arguments:
+NETWORK NAME:	The name of the network from which the gateway configuration should be removed.
+NODE ID:		The ID of the node that is currently acting as a gateway.
+`,
+	Aliases: []string{"rm"},
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.DeleteGateway(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(gatewayDeleteCmd)
+}

+ 18 - 0
cli/cmd/gateway/root.go

@@ -0,0 +1,18 @@
+package gateway
+
+import (
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands.
+var rootCmd = &cobra.Command{
+	Use:     "gateway",
+	Short:   "Manage Gateways.",
+	Long:    `Manage Gateways.`,
+	Aliases: []string{"gw"},
+}
+
+// GetRoot returns the root subcommand.
+func GetRoot() *cobra.Command {
+	return rootCmd
+}

+ 6 - 5
cli/cmd/node/create_ingress.go

@@ -6,11 +6,12 @@ import (
 )
 
 var nodeCreateIngressCmd = &cobra.Command{
-	Use:     "create_remote_access_gateway [NETWORK NAME] [NODE ID]",
-	Args:    cobra.ExactArgs(2),
-	Short:   "Turn a Node into a Remote Access Gateway (Ingress)",
-	Long:    `Turn a Node into a Remote Access Gateway (Ingress) for a Network.`,
-	Aliases: []string{"create_rag"},
+	Use:        "create_remote_access_gateway [NETWORK NAME] [NODE ID]",
+	Args:       cobra.ExactArgs(2),
+	Short:      "Turn a Node into a Remote Access Gateway (Ingress)",
+	Long:       `Turn a Node into a Remote Access Gateway (Ingress) for a Network.`,
+	Deprecated: "in favour of the `gateway` subcommand, in Netmaker v0.90.0.",
+	Aliases:    []string{"create_rag"},
 	Run: func(cmd *cobra.Command, args []string) {
 		functions.PrettyPrint(functions.CreateIngress(args[0], args[1], failover))
 	},

+ 5 - 4
cli/cmd/node/create_relay.go

@@ -8,10 +8,11 @@ import (
 )
 
 var hostCreateRelayCmd = &cobra.Command{
-	Use:   "create_relay [NETWORK][NODE ID] [RELAYED NODE IDS (comma separated)]",
-	Args:  cobra.ExactArgs(3),
-	Short: "Turn a Node into a Relay",
-	Long:  `Turn a Node into a Relay`,
+	Use:        "create_relay [NETWORK][NODE ID] [RELAYED NODE IDS (comma separated)]",
+	Args:       cobra.ExactArgs(3),
+	Short:      "Turn a Node into a Relay",
+	Long:       `Turn a Node into a Relay`,
+	Deprecated: "in favour of the `gateway` subcommand, in Netmaker v0.90.0.",
 	Run: func(cmd *cobra.Command, args []string) {
 		functions.PrettyPrint(functions.CreateRelay(args[0], args[1], strings.Split(args[2], ",")))
 	},

+ 6 - 5
cli/cmd/node/delete_ingress.go

@@ -6,11 +6,12 @@ import (
 )
 
 var nodeDeleteIngressCmd = &cobra.Command{
-	Use:     "delete_remote_access_gateway [NETWORK NAME] [NODE ID]",
-	Args:    cobra.ExactArgs(2),
-	Short:   "Delete Remote Access Gateway role from a Node",
-	Long:    `Delete Remote Access Gateway role from a Node`,
-	Aliases: []string{"delete_rag"},
+	Use:        "delete_remote_access_gateway [NETWORK NAME] [NODE ID]",
+	Args:       cobra.ExactArgs(2),
+	Short:      "Delete Remote Access Gateway role from a Node",
+	Long:       `Delete Remote Access Gateway role from a Node`,
+	Deprecated: "in favour of the `gateway` subcommand, in Netmaker v0.90.0.",
+	Aliases:    []string{"delete_rag"},
 	Run: func(cmd *cobra.Command, args []string) {
 		functions.PrettyPrint(functions.DeleteIngress(args[0], args[1]))
 	},

+ 5 - 4
cli/cmd/node/delete_relay.go

@@ -6,10 +6,11 @@ import (
 )
 
 var hostDeleteRelayCmd = &cobra.Command{
-	Use:   "delete_relay [NETWORK] [NODE ID]",
-	Args:  cobra.ExactArgs(2),
-	Short: "Delete Relay from a node",
-	Long:  `Delete Relay from a node`,
+	Use:        "delete_relay [NETWORK] [NODE ID]",
+	Args:       cobra.ExactArgs(2),
+	Short:      "Delete Relay from a node",
+	Long:       `Delete Relay from a node`,
+	Deprecated: "in favour of the `gateway` subcommand, in Netmaker v0.90.0.",
 	Run: func(cmd *cobra.Command, args []string) {
 		functions.PrettyPrint(functions.DeleteRelay(args[0], args[1]))
 	},

+ 2 - 0
cli/cmd/root.go

@@ -1,6 +1,7 @@
 package cmd
 
 import (
+	"github.com/gravitl/netmaker/cli/cmd/gateway"
 	"os"
 
 	"github.com/gravitl/netmaker/cli/cmd/acl"
@@ -55,4 +56,5 @@ func init() {
 	rootCmd.AddCommand(host.GetRoot())
 	rootCmd.AddCommand(enrollment_key.GetRoot())
 	rootCmd.AddCommand(failover.GetRoot())
+	rootCmd.AddCommand(gateway.GetRoot())
 }

+ 18 - 0
cli/functions/gateway.go

@@ -0,0 +1,18 @@
+package functions
+
+import (
+	"fmt"
+	"github.com/gravitl/netmaker/models"
+	"net/http"
+)
+
+func CreateGateway(ingressRequest models.IngressRequest, relayRequest models.RelayRequest) *models.ApiNode {
+	return request[models.ApiNode](http.MethodPost, fmt.Sprintf("/api/nodes/%s/%s/gateway", relayRequest.NetID, relayRequest.NodeID), &models.CreateGwReq{
+		IngressRequest: ingressRequest,
+		RelayRequest:   relayRequest,
+	})
+}
+
+func DeleteGateway(networkID, nodeID string) *models.ApiNode {
+	return request[models.ApiNode](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s/gateway", networkID, nodeID), nil)
+}

+ 37 - 6
controllers/acls.go

@@ -144,18 +144,49 @@ func aclPolicyTypes(w http.ResponseWriter, r *http.Request) {
 func aclDebug(w http.ResponseWriter, r *http.Request) {
 	nodeID, _ := url.QueryUnescape(r.URL.Query().Get("node"))
 	peerID, _ := url.QueryUnescape(r.URL.Query().Get("peer"))
+	peerIsStatic, _ := url.QueryUnescape(r.URL.Query().Get("peer_is_static"))
 	node, err := logic.GetNodeByID(nodeID)
 	if err != nil {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 	}
-	peer, err := logic.GetNodeByID(peerID)
-	if err != nil {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		return
+	var peer models.Node
+	if peerIsStatic == "true" {
+		extclient, err := logic.GetExtClient(peerID, node.Network)
+		if err != nil {
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+			return
+		}
+		peer = extclient.ConvertToStaticNode()
+
+	} else {
+		peer, err = logic.GetNodeByID(peerID)
+		if err != nil {
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+			return
+		}
+	}
+	type resp struct {
+		IsNodeAllowed bool
+		IsPeerAllowed bool
+		Policies      []models.Acl
+		IngressRules  []models.FwRule
+	}
+
+	allowed, ps := logic.IsNodeAllowedToCommunicateV1(node, peer, true)
+	isallowed := logic.IsPeerAllowed(node, peer, true)
+	re := resp{
+		IsNodeAllowed: allowed,
+		IsPeerAllowed: isallowed,
+		Policies:      ps,
+	}
+	if peerIsStatic == "true" {
+		ingress, err := logic.GetNodeByID(peer.StaticNode.IngressGatewayID)
+		if err == nil {
+			re.IngressRules = logic.GetFwRulesOnIngressGateway(ingress)
+		}
 	}
-	allowed, _ := logic.IsNodeAllowedToCommunicate(node, peer, true)
-	logic.ReturnSuccessResponseWithJson(w, r, allowed, "fetched all acls in the network ")
+	logic.ReturnSuccessResponseWithJson(w, r, re, "fetched all acls in the network ")
 }
 
 // @Summary     List Acls in a network

+ 1 - 0
controllers/network.go

@@ -492,6 +492,7 @@ func deleteNetwork(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, errtype))
 		return
 	}
+	go logic.UnlinkNetworkAndTagsFromEnrollmentKeys(network, true)
 	go logic.DeleteNetworkRoles(network)
 	go logic.DeleteDefaultNetworkPolicies(models.NetworkID(network))
 	//delete network from allocated ip map

+ 49 - 2
controllers/node.go

@@ -31,6 +31,7 @@ func nodeHandlers(r *mux.Router) {
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/createingress", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceIngress, http.HandlerFunc(createGateway)))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(true, http.HandlerFunc(deleteGateway))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost)
+	r.HandleFunc("/api/v1/nodes/{network}/status", logic.SecurityCheck(true, http.HandlerFunc(getNetworkNodeStatus))).Methods(http.MethodGet)
 	r.HandleFunc("/api/v1/nodes/migrate", migrate).Methods(http.MethodPost)
 }
 
@@ -328,7 +329,7 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) {
 	}
 
 	nodes = logic.AddStaticNodestoList(nodes)
-	nodes = logic.AddStatusToNodes(nodes)
+	nodes = logic.AddStatusToNodes(nodes, false)
 	// returns all the nodes in JSON/API format
 	apiNodes := logic.GetAllNodesAPI(nodes[:])
 	logger.Log(2, r.Header.Get("user"), "fetched nodes on network", networkName)
@@ -368,7 +369,7 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) {
 
 	}
 	nodes = logic.AddStaticNodestoList(nodes)
-	nodes = logic.AddStatusToNodes(nodes)
+	nodes = logic.AddStatusToNodes(nodes, false)
 	// return all the nodes in JSON/API format
 	apiNodes := logic.GetAllNodesAPI(nodes[:])
 	logger.Log(3, r.Header.Get("user"), "fetched all nodes they have access to")
@@ -377,6 +378,52 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(apiNodes)
 }
 
+// @Summary     Get all nodes status on the network
+// @Router      /api/v1/nodes/{network}/status [get]
+// @Tags        Nodes
+// @Securitydefinitions.oauth2.application OAuth2Application
+// @Success     200 {array} models.ApiNode
+// @Failure     500 {object} models.ErrorResponse
+// Not quite sure if this is necessary. Probably necessary based on front end but may want to review after iteration 1 if it's being used or not
+func getNetworkNodeStatus(w http.ResponseWriter, r *http.Request) {
+	var params = mux.Vars(r)
+	netID := params["network"]
+	// validate network
+	_, err := logic.GetNetwork(netID)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to get network %v", err), "badrequest"))
+		return
+	}
+	var nodes []models.Node
+	nodes, err = logic.GetNetworkNodes(netID)
+	if err != nil {
+		logger.Log(0, "error fetching all nodes info: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	username := r.Header.Get("user")
+	if r.Header.Get("ismaster") == "no" {
+		user, err := logic.GetUser(username)
+		if err != nil {
+			return
+		}
+		userPlatformRole, err := logic.GetRole(user.PlatformRoleID)
+		if err != nil {
+			return
+		}
+		if !userPlatformRole.FullAccess {
+			nodes = logic.GetFilteredNodesByUserAccess(*user, nodes)
+		}
+
+	}
+	nodes = logic.AddStaticNodestoList(nodes)
+	nodes = logic.AddStatusToNodes(nodes, false)
+	// return all the nodes in JSON/API format
+	apiNodesStatusMap := logic.GetNodesStatusAPI(nodes[:])
+	logger.Log(3, r.Header.Get("user"), "fetched all nodes they have access to")
+	logic.ReturnSuccessResponseWithJson(w, r, apiNodesStatusMap, "fetched nodes with metric status")
+}
+
 // @Summary     Get an individual node
 // @Router      /api/nodes/{network}/{nodeid} [get]
 // @Tags        Nodes

+ 9 - 0
controllers/user.go

@@ -258,6 +258,15 @@ func getUserV1(w http.ResponseWriter, r *http.Request) {
 	resp := models.ReturnUserWithRolesAndGroups{
 		ReturnUser:   user,
 		PlatformRole: userRoleTemplate,
+		UserGroups:   map[models.UserGroupID]models.UserGroup{},
+	}
+	for gId := range user.UserGroups {
+		grp, err := logic.GetUserGroup(gId)
+		if err != nil {
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+			return
+		}
+		resp.UserGroups[gId] = grp
 	}
 	logger.Log(2, r.Header.Get("user"), "fetched user", usernameFetched)
 	logic.ReturnSuccessResponseWithJson(w, r, resp, "fetched user with role info")

+ 180 - 9
logic/acls.go

@@ -849,6 +849,163 @@ func checkTagGroupPolicy(srcMap, dstMap map[string]struct{}, node, peer models.N
 	}
 	return false
 }
+func uniquePolicies(items []models.Acl) []models.Acl {
+	if len(items) == 0 {
+		return items
+	}
+	seen := make(map[string]bool)
+	var result []models.Acl
+	for _, item := range items {
+		if !seen[item.ID] {
+			seen[item.ID] = true
+			result = append(result, item)
+		}
+	}
+
+	return result
+}
+
+// IsNodeAllowedToCommunicate - check node is allowed to communicate with the peer // ADD ALLOWED DIRECTION - 0 => node -> peer, 1 => peer-> node,
+func IsNodeAllowedToCommunicateV1(node, peer models.Node, checkDefaultPolicy bool) (bool, []models.Acl) {
+	var nodeId, peerId string
+	if node.IsStatic {
+		nodeId = node.StaticNode.ClientID
+		node = node.StaticNode.ConvertToStaticNode()
+	} else {
+		nodeId = node.ID.String()
+	}
+	if peer.IsStatic {
+		peerId = peer.StaticNode.ClientID
+		peer = peer.StaticNode.ConvertToStaticNode()
+	} else {
+		peerId = peer.ID.String()
+	}
+
+	aclTagsMutex.RLock()
+	peerTags := maps.Clone(peer.Tags)
+	nodeTags := maps.Clone(node.Tags)
+	aclTagsMutex.RUnlock()
+	nodeTags[models.TagID(nodeId)] = struct{}{}
+	peerTags[models.TagID(peerId)] = struct{}{}
+	if checkDefaultPolicy {
+		// check default policy if all allowed return true
+		defaultPolicy, err := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
+		if err == nil {
+			if defaultPolicy.Enabled {
+				return true, []models.Acl{defaultPolicy}
+			}
+		}
+	}
+	allowedPolicies := []models.Acl{}
+	defer func() {
+		allowedPolicies = uniquePolicies(allowedPolicies)
+	}()
+	// list device policies
+	policies := listDevicePolicies(models.NetworkID(peer.Network))
+	srcMap := make(map[string]struct{})
+	dstMap := make(map[string]struct{})
+	defer func() {
+		srcMap = nil
+		dstMap = nil
+	}()
+	for _, policy := range policies {
+		if !policy.Enabled {
+			continue
+		}
+		allowed := false
+		srcMap = convAclTagToValueMap(policy.Src)
+		dstMap = convAclTagToValueMap(policy.Dst)
+		_, srcAll := srcMap["*"]
+		_, dstAll := dstMap["*"]
+		if policy.AllowedDirection == models.TrafficDirectionBi {
+			if _, ok := srcMap[nodeId]; ok || srcAll {
+				if _, ok := dstMap[peerId]; ok || dstAll {
+					allowedPolicies = append(allowedPolicies, policy)
+					continue
+				}
+
+			}
+			if _, ok := dstMap[nodeId]; ok || dstAll {
+				if _, ok := srcMap[peerId]; ok || srcAll {
+					allowedPolicies = append(allowedPolicies, policy)
+					continue
+				}
+			}
+		}
+		if _, ok := dstMap[peerId]; ok || dstAll {
+			if _, ok := srcMap[nodeId]; ok || srcAll {
+				allowedPolicies = append(allowedPolicies, policy)
+				continue
+			}
+		}
+		if policy.AllowedDirection == models.TrafficDirectionBi {
+
+			for tagID := range nodeTags {
+
+				if _, ok := dstMap[tagID.String()]; ok || dstAll {
+					if srcAll {
+						allowed = true
+						break
+					}
+					for tagID := range peerTags {
+						if _, ok := srcMap[tagID.String()]; ok {
+							allowed = true
+							break
+						}
+					}
+				}
+				if allowed {
+					allowedPolicies = append(allowedPolicies, policy)
+					break
+				}
+				if _, ok := srcMap[tagID.String()]; ok || srcAll {
+					if dstAll {
+						allowed = true
+						break
+					}
+					for tagID := range peerTags {
+						if _, ok := dstMap[tagID.String()]; ok {
+							allowed = true
+							break
+						}
+					}
+				}
+				if allowed {
+					break
+				}
+			}
+			if allowed {
+				allowedPolicies = append(allowedPolicies, policy)
+				continue
+			}
+		}
+		for tagID := range peerTags {
+			if _, ok := dstMap[tagID.String()]; ok || dstAll {
+				if srcAll {
+					allowed = true
+					break
+				}
+				for tagID := range nodeTags {
+					if _, ok := srcMap[tagID.String()]; ok {
+						allowed = true
+						break
+					}
+				}
+			}
+			if allowed {
+				break
+			}
+		}
+		if allowed {
+			allowedPolicies = append(allowedPolicies, policy)
+		}
+	}
+
+	if len(allowedPolicies) > 0 {
+		return true, allowedPolicies
+	}
+	return false, allowedPolicies
+}
 
 // IsNodeAllowedToCommunicate - check node is allowed to communicate with the peer
 func IsNodeAllowedToCommunicate(node, peer models.Node, checkDefaultPolicy bool) (bool, []models.Acl) {
@@ -921,7 +1078,7 @@ func IsNodeAllowedToCommunicate(node, peer models.Node, checkDefaultPolicy bool)
 		}
 		for tagID := range nodeTags {
 			allowed := false
-			if _, ok := dstMap[tagID.String()]; policy.AllowedDirection == models.TrafficDirectionBi && ok {
+			if _, ok := dstMap[tagID.String()]; policy.AllowedDirection == models.TrafficDirectionBi && ok || dstAll {
 				if srcAll {
 					allowed = true
 					allowedPolicies = append(allowedPolicies, policy)
@@ -938,7 +1095,7 @@ func IsNodeAllowedToCommunicate(node, peer models.Node, checkDefaultPolicy bool)
 				allowedPolicies = append(allowedPolicies, policy)
 				break
 			}
-			if _, ok := srcMap[tagID.String()]; ok {
+			if _, ok := srcMap[tagID.String()]; ok || srcAll {
 				if dstAll {
 					allowed = true
 					allowedPolicies = append(allowedPolicies, policy)
@@ -958,7 +1115,7 @@ func IsNodeAllowedToCommunicate(node, peer models.Node, checkDefaultPolicy bool)
 		}
 		for tagID := range peerTags {
 			allowed := false
-			if _, ok := dstMap[tagID.String()]; ok {
+			if _, ok := dstMap[tagID.String()]; ok || dstAll {
 				if srcAll {
 					allowed = true
 					allowedPolicies = append(allowedPolicies, policy)
@@ -966,7 +1123,7 @@ func IsNodeAllowedToCommunicate(node, peer models.Node, checkDefaultPolicy bool)
 				}
 				for tagID := range nodeTags {
 
-					if _, ok := srcMap[tagID.String()]; ok {
+					if _, ok := srcMap[tagID.String()]; ok || srcAll {
 						allowed = true
 						break
 					}
@@ -977,7 +1134,7 @@ func IsNodeAllowedToCommunicate(node, peer models.Node, checkDefaultPolicy bool)
 				break
 			}
 
-			if _, ok := srcMap[tagID.String()]; policy.AllowedDirection == models.TrafficDirectionBi && ok {
+			if _, ok := srcMap[tagID.String()]; policy.AllowedDirection == models.TrafficDirectionBi && ok || srcAll {
 				if dstAll {
 					allowed = true
 					allowedPolicies = append(allowedPolicies, policy)
@@ -1238,6 +1395,8 @@ func GetAclRulesForNode(targetnodeI *models.Node) (rules map[string]models.AclRu
 		}
 		srcTags := convAclTagToValueMap(acl.Src)
 		dstTags := convAclTagToValueMap(acl.Dst)
+		_, srcAll := srcTags["*"]
+		_, dstAll := dstTags["*"]
 		aclRule := models.AclRule{
 			ID:              acl.ID,
 			AllowedProtocol: acl.Proto,
@@ -1250,16 +1409,16 @@ func GetAclRulesForNode(targetnodeI *models.Node) (rules map[string]models.AclRu
 				var existsInSrcTag bool
 				var existsInDstTag bool
 
-				if _, ok := srcTags[nodeTag.String()]; ok {
+				if _, ok := srcTags[nodeTag.String()]; ok || srcAll {
 					existsInSrcTag = true
 				}
-				if _, ok := srcTags[targetnode.ID.String()]; ok {
+				if _, ok := srcTags[targetnode.ID.String()]; ok || srcAll {
 					existsInSrcTag = true
 				}
-				if _, ok := dstTags[nodeTag.String()]; ok {
+				if _, ok := dstTags[nodeTag.String()]; ok || dstAll {
 					existsInDstTag = true
 				}
-				if _, ok := dstTags[targetnode.ID.String()]; ok {
+				if _, ok := dstTags[targetnode.ID.String()]; ok || dstAll {
 					existsInDstTag = true
 				}
 
@@ -1282,6 +1441,9 @@ func GetAclRulesForNode(targetnodeI *models.Node) (rules map[string]models.AclRu
 							if node.ID == targetnode.ID {
 								continue
 							}
+							if node.IsStatic && node.StaticNode.IngressGatewayID == targetnode.ID.String() {
+								continue
+							}
 							if node.Address.IP != nil {
 								aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4())
 							}
@@ -1315,6 +1477,9 @@ func GetAclRulesForNode(targetnodeI *models.Node) (rules map[string]models.AclRu
 							if node.ID == targetnode.ID {
 								continue
 							}
+							if node.IsStatic && node.StaticNode.IngressGatewayID == targetnode.ID.String() {
+								continue
+							}
 							if node.Address.IP != nil {
 								aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4())
 							}
@@ -1354,6 +1519,9 @@ func GetAclRulesForNode(targetnodeI *models.Node) (rules map[string]models.AclRu
 						if node.ID == targetnode.ID {
 							continue
 						}
+						if node.IsStatic && node.StaticNode.IngressGatewayID == targetnode.ID.String() {
+							continue
+						}
 						if node.Address.IP != nil {
 							aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4())
 						}
@@ -1382,6 +1550,9 @@ func GetAclRulesForNode(targetnodeI *models.Node) (rules map[string]models.AclRu
 							if node.ID == targetnode.ID {
 								continue
 							}
+							if node.IsStatic && node.StaticNode.IngressGatewayID == targetnode.ID.String() {
+								continue
+							}
 							if node.Address.IP != nil {
 								aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4())
 							}

+ 57 - 1
logic/enrollmentkey.go

@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"strings"
 	"sync"
 	"time"
 
@@ -120,7 +121,6 @@ func UpdateEnrollmentKey(keyId string, relayId uuid.UUID, groups []models.TagID)
 }
 
 // GetAllEnrollmentKeys - fetches all enrollment keys from DB
-// TODO drop double pointer
 func GetAllEnrollmentKeys() ([]models.EnrollmentKey, error) {
 	currentKeys, err := getEnrollmentKeysMap()
 	if err != nil {
@@ -335,3 +335,59 @@ func RemoveTagFromEnrollmentKeys(deletedTagID models.TagID) {
 
 	}
 }
+
+func UnlinkNetworkAndTagsFromEnrollmentKeys(network string, delete bool) error {
+	keys, err := GetAllEnrollmentKeys()
+	if err != nil {
+		return fmt.Errorf("failed to retrieve keys: %w", err)
+	}
+
+	var errs []error
+	for _, key := range keys {
+		newNetworks := []string{}
+		newTags := []models.TagID{}
+		update := false
+
+		// Check and update networks
+		for _, net := range key.Networks {
+			if net == network {
+				update = true
+				continue
+			}
+			newNetworks = append(newNetworks, net)
+		}
+
+		// Check and update tags
+		for _, tag := range key.Groups {
+			tagParts := strings.Split(tag.String(), ".")
+			if len(tagParts) == 0 {
+				continue
+			}
+			tagNetwork := tagParts[0]
+			if tagNetwork == network {
+				update = true
+				continue
+			}
+			newTags = append(newTags, tag)
+		}
+
+		if update && len(newNetworks) == 0 && delete {
+			if err := DeleteEnrollmentKey(key.Value, true); err != nil {
+				errs = append(errs, fmt.Errorf("failed to delete key %s: %w", key.Value, err))
+			}
+			continue
+		}
+		if update {
+			key.Networks = newNetworks
+			key.Groups = newTags
+			if err := upsertEnrollmentKey(&key); err != nil {
+				errs = append(errs, fmt.Errorf("failed to update key %s: %w", key.Value, err))
+			}
+		}
+	}
+
+	if len(errs) > 0 {
+		return fmt.Errorf("errors unlinking network/tags from keys: %v", errs)
+	}
+	return nil
+}

+ 151 - 124
logic/extpeers.go

@@ -455,9 +455,139 @@ func GetStaticNodeIps(node models.Node) (ips []net.IP) {
 	return
 }
 
+func getFwRulesForNodeAndPeerOnGw(node, peer models.Node, allowedPolicies []models.Acl) (rules []models.FwRule) {
+
+	for _, policy := range allowedPolicies {
+		// if static peer dst rule not for ingress node -> skip
+		if node.Address.IP != nil {
+			rules = append(rules, models.FwRule{
+				SrcIP: net.IPNet{
+					IP:   node.Address.IP,
+					Mask: net.CIDRMask(32, 32),
+				},
+				DstIP: net.IPNet{
+					IP:   peer.Address.IP,
+					Mask: net.CIDRMask(32, 32),
+				},
+				AllowedProtocol: policy.Proto,
+				AllowedPorts:    policy.Port,
+				Allow:           true,
+			})
+		}
+
+		if node.Address6.IP != nil {
+			rules = append(rules, models.FwRule{
+				SrcIP: net.IPNet{
+					IP:   node.Address6.IP,
+					Mask: net.CIDRMask(128, 128),
+				},
+				DstIP: net.IPNet{
+					IP:   peer.Address6.IP,
+					Mask: net.CIDRMask(128, 128),
+				},
+				AllowedProtocol: policy.Proto,
+				AllowedPorts:    policy.Port,
+				Allow:           true,
+			})
+		}
+		if policy.AllowedDirection == models.TrafficDirectionBi {
+			if node.Address.IP != nil {
+				rules = append(rules, models.FwRule{
+					SrcIP: net.IPNet{
+						IP:   peer.Address.IP,
+						Mask: net.CIDRMask(32, 32),
+					},
+					DstIP: net.IPNet{
+						IP:   node.Address.IP,
+						Mask: net.CIDRMask(32, 32),
+					},
+					AllowedProtocol: policy.Proto,
+					AllowedPorts:    policy.Port,
+					Allow:           true,
+				})
+			}
+
+			if node.Address6.IP != nil {
+				rules = append(rules, models.FwRule{
+					SrcIP: net.IPNet{
+						IP:   peer.Address6.IP,
+						Mask: net.CIDRMask(128, 128),
+					},
+					DstIP: net.IPNet{
+						IP:   node.Address6.IP,
+						Mask: net.CIDRMask(128, 128),
+					},
+					AllowedProtocol: policy.Proto,
+					AllowedPorts:    policy.Port,
+					Allow:           true,
+				})
+			}
+		}
+		if len(node.StaticNode.ExtraAllowedIPs) > 0 {
+			for _, additionalAllowedIPNet := range node.StaticNode.ExtraAllowedIPs {
+				_, ipNet, err := net.ParseCIDR(additionalAllowedIPNet)
+				if err != nil {
+					continue
+				}
+				if ipNet.IP.To4() != nil && peer.Address.IP != nil {
+					rules = append(rules, models.FwRule{
+						SrcIP: net.IPNet{
+							IP:   peer.Address.IP,
+							Mask: net.CIDRMask(32, 32),
+						},
+						DstIP: *ipNet,
+						Allow: true,
+					})
+				} else if peer.Address6.IP != nil {
+					rules = append(rules, models.FwRule{
+						SrcIP: net.IPNet{
+							IP:   peer.Address6.IP,
+							Mask: net.CIDRMask(128, 128),
+						},
+						DstIP: *ipNet,
+						Allow: true,
+					})
+				}
+
+			}
+
+		}
+		if len(peer.StaticNode.ExtraAllowedIPs) > 0 {
+			for _, additionalAllowedIPNet := range peer.StaticNode.ExtraAllowedIPs {
+				_, ipNet, err := net.ParseCIDR(additionalAllowedIPNet)
+				if err != nil {
+					continue
+				}
+				if ipNet.IP.To4() != nil && node.Address.IP != nil {
+					rules = append(rules, models.FwRule{
+						SrcIP: net.IPNet{
+							IP:   node.Address.IP,
+							Mask: net.CIDRMask(32, 32),
+						},
+						DstIP: *ipNet,
+						Allow: true,
+					})
+				} else if node.Address6.IP != nil {
+					rules = append(rules, models.FwRule{
+						SrcIP: net.IPNet{
+							IP:   node.Address6.IP,
+							Mask: net.CIDRMask(128, 128),
+						},
+						DstIP: *ipNet,
+						Allow: true,
+					})
+				}
+
+			}
+
+		}
+	}
+
+	return
+}
+
 func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
 	// fetch user access to static clients via policies
-
 	defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
 	defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
 	nodes, _ := GetNetworkNodes(node.Network)
@@ -584,131 +714,26 @@ func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
 		if !nodeI.IsStatic || nodeI.IsUserNode {
 			continue
 		}
+		// if nodeI.StaticNode.IngressGatewayID != node.ID.String() {
+		// 	continue
+		// }
 		for _, peer := range nodes {
 			if peer.StaticNode.ClientID == nodeI.StaticNode.ClientID || peer.IsUserNode {
 				continue
 			}
-			if ok, allowedPolicies := IsNodeAllowedToCommunicate(nodeI, peer, true); ok {
-				if peer.IsStatic {
-					if nodeI.StaticNode.Address != "" {
-						for _, policy := range allowedPolicies {
-							rules = append(rules, models.FwRule{
-								SrcIP:           nodeI.StaticNode.AddressIPNet4(),
-								DstIP:           peer.StaticNode.AddressIPNet4(),
-								AllowedProtocol: policy.Proto,
-								AllowedPorts:    policy.Port,
-								Allow:           true,
-							})
-							if policy.AllowedDirection == models.TrafficDirectionBi {
-								rules = append(rules, models.FwRule{
-									SrcIP:           peer.StaticNode.AddressIPNet4(),
-									DstIP:           nodeI.StaticNode.AddressIPNet4(),
-									AllowedProtocol: policy.Proto,
-									AllowedPorts:    policy.Port,
-									Allow:           true,
-								})
-							}
-						}
-
-					}
-					if nodeI.StaticNode.Address6 != "" {
-						for _, policy := range allowedPolicies {
-							rules = append(rules, models.FwRule{
-								SrcIP:           nodeI.StaticNode.AddressIPNet6(),
-								DstIP:           peer.StaticNode.AddressIPNet6(),
-								AllowedProtocol: policy.Proto,
-								AllowedPorts:    policy.Port,
-								Allow:           true,
-							})
-							if policy.AllowedDirection == models.TrafficDirectionBi {
-								rules = append(rules, models.FwRule{
-									SrcIP:           peer.StaticNode.AddressIPNet6(),
-									DstIP:           nodeI.StaticNode.AddressIPNet6(),
-									AllowedProtocol: policy.Proto,
-									AllowedPorts:    policy.Port,
-									Allow:           true,
-								})
-							}
-						}
-					}
-					if len(peer.StaticNode.ExtraAllowedIPs) > 0 {
-						for _, additionalAllowedIPNet := range peer.StaticNode.ExtraAllowedIPs {
-							_, ipNet, err := net.ParseCIDR(additionalAllowedIPNet)
-							if err != nil {
-								continue
-							}
-							if ipNet.IP.To4() != nil {
-								rules = append(rules, models.FwRule{
-									SrcIP: nodeI.StaticNode.AddressIPNet4(),
-									DstIP: *ipNet,
-									Allow: true,
-								})
-							} else {
-								rules = append(rules, models.FwRule{
-									SrcIP: nodeI.StaticNode.AddressIPNet6(),
-									DstIP: *ipNet,
-									Allow: true,
-								})
-							}
-
-						}
-
-					}
-				} else {
-					if nodeI.StaticNode.Address != "" {
-						for _, policy := range allowedPolicies {
-							rules = append(rules, models.FwRule{
-								SrcIP: nodeI.StaticNode.AddressIPNet4(),
-								DstIP: net.IPNet{
-									IP:   peer.Address.IP,
-									Mask: net.CIDRMask(32, 32),
-								},
-								AllowedProtocol: policy.Proto,
-								AllowedPorts:    policy.Port,
-								Allow:           true,
-							})
-							if policy.AllowedDirection == models.TrafficDirectionBi {
-								rules = append(rules, models.FwRule{
-									SrcIP: net.IPNet{
-										IP:   peer.Address.IP,
-										Mask: net.CIDRMask(32, 32),
-									},
-									DstIP:           nodeI.StaticNode.AddressIPNet4(),
-									AllowedProtocol: policy.Proto,
-									AllowedPorts:    policy.Port,
-									Allow:           true,
-								})
-							}
-						}
-					}
-					if nodeI.StaticNode.Address6 != "" {
-						for _, policy := range allowedPolicies {
-							rules = append(rules, models.FwRule{
-								SrcIP: nodeI.StaticNode.AddressIPNet6(),
-								DstIP: net.IPNet{
-									IP:   peer.Address6.IP,
-									Mask: net.CIDRMask(128, 128),
-								},
-								AllowedProtocol: policy.Proto,
-								AllowedPorts:    policy.Port,
-								Allow:           true,
-							})
-							if policy.AllowedDirection == models.TrafficDirectionBi {
-								rules = append(rules, models.FwRule{
-									SrcIP: net.IPNet{
-										IP:   peer.Address6.IP,
-										Mask: net.CIDRMask(128, 128),
-									},
-									DstIP:           nodeI.StaticNode.AddressIPNet6(),
-									AllowedProtocol: policy.Proto,
-									AllowedPorts:    policy.Port,
-									Allow:           true,
-								})
-							}
-						}
-					}
-				}
-
+			if nodeI.StaticNode.IngressGatewayID != node.ID.String() &&
+				((!peer.IsStatic && peer.ID.String() != node.ID.String()) ||
+					(peer.IsStatic && peer.StaticNode.IngressGatewayID != node.ID.String())) {
+				continue
+			}
+			if peer.IsStatic {
+				peer = peer.StaticNode.ConvertToStaticNode()
+			}
+			if ok, allowedPolicies := IsNodeAllowedToCommunicateV1(nodeI.StaticNode.ConvertToStaticNode(), peer, true); ok {
+				rules = append(rules, getFwRulesForNodeAndPeerOnGw(nodeI.StaticNode.ConvertToStaticNode(), peer, allowedPolicies)...)
+			}
+			if ok, allowedPolicies := IsNodeAllowedToCommunicateV1(peer, nodeI.StaticNode.ConvertToStaticNode(), true); ok {
+				rules = append(rules, getFwRulesForNodeAndPeerOnGw(peer, nodeI.StaticNode.ConvertToStaticNode(), allowedPolicies)...)
 			}
 		}
 	}
@@ -729,11 +754,13 @@ func GetExtPeers(node, peer *models.Node) ([]wgtypes.PeerConfig, []models.IDandA
 	}
 	for _, extPeer := range extPeers {
 		extPeer := extPeer
+		fmt.Println("=====> checking EXT peer: ", extPeer.ClientID)
 		if !IsClientNodeAllowed(&extPeer, peer.ID.String()) {
 			continue
 		}
 		if extPeer.RemoteAccessClientID == "" {
 			if ok := IsPeerAllowed(extPeer.ConvertToStaticNode(), *peer, true); !ok {
+				fmt.Println("=====>1 checking EXT peer: ", extPeer.ClientID)
 				continue
 			}
 		} else {
@@ -822,7 +849,7 @@ func getExtpeerEgressRanges(node models.Node) (ranges, ranges6 []net.IPNet) {
 		if len(extPeer.ExtraAllowedIPs) == 0 {
 			continue
 		}
-		if ok, _ := IsNodeAllowedToCommunicate(extPeer.ConvertToStaticNode(), node, true); !ok {
+		if ok, _ := IsNodeAllowedToCommunicateV1(extPeer.ConvertToStaticNode(), node, true); !ok {
 			continue
 		}
 		for _, allowedRange := range extPeer.ExtraAllowedIPs {
@@ -849,7 +876,7 @@ func getExtpeersExtraRoutes(node models.Node) (egressRoutes []models.EgressNetwo
 		if len(extPeer.ExtraAllowedIPs) == 0 {
 			continue
 		}
-		if ok, _ := IsNodeAllowedToCommunicate(extPeer.ConvertToStaticNode(), node, true); !ok {
+		if ok, _ := IsNodeAllowedToCommunicateV1(extPeer.ConvertToStaticNode(), node, true); !ok {
 			continue
 		}
 		egressRoutes = append(egressRoutes, getExtPeerEgressRoute(node, extPeer)...)

+ 37 - 3
logic/nodes.go

@@ -444,7 +444,7 @@ func AddStaticNodestoList(nodes []models.Node) []models.Node {
 	return nodes
 }
 
-func AddStatusToNodes(nodes []models.Node) (nodesWithStatus []models.Node) {
+func AddStatusToNodes(nodes []models.Node, statusCall bool) (nodesWithStatus []models.Node) {
 	aclDefaultPolicyStatusMap := make(map[string]bool)
 	for _, node := range nodes {
 		if _, ok := aclDefaultPolicyStatusMap[node.Network]; !ok {
@@ -452,7 +452,12 @@ func AddStatusToNodes(nodes []models.Node) (nodesWithStatus []models.Node) {
 			defaultPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
 			aclDefaultPolicyStatusMap[node.Network] = defaultPolicy.Enabled
 		}
-		GetNodeStatus(&node, aclDefaultPolicyStatusMap[node.Network])
+		if statusCall {
+			GetNodeStatus(&node, aclDefaultPolicyStatusMap[node.Network])
+		} else {
+			GetNodeCheckInStatus(&node, true)
+		}
+
 		nodesWithStatus = append(nodesWithStatus, node)
 	}
 	return
@@ -573,6 +578,16 @@ func GetAllNodesAPI(nodes []models.Node) []models.ApiNode {
 	return apiNodes[:]
 }
 
+// GetNodesStatusAPI - gets nodes status
+func GetNodesStatusAPI(nodes []models.Node) map[string]models.ApiNodeStatus {
+	apiStatusNodesMap := make(map[string]models.ApiNodeStatus)
+	for i := range nodes {
+		newApiNode := nodes[i].ConvertToStatusNode()
+		apiStatusNodesMap[newApiNode.ID] = *newApiNode
+	}
+	return apiStatusNodesMap
+}
+
 // DeleteExpiredNodes - goroutine which deletes nodes which are expired
 func DeleteExpiredNodes(ctx context.Context, peerUpdate chan *models.Node) {
 	// Delete Expired Nodes Every Hour
@@ -821,6 +836,9 @@ func GetTagMapWithNodesByNetwork(netID models.NetworkID, withStaticNodes bool) (
 	tagNodesMap = make(map[models.TagID][]models.Node)
 	nodes, _ := GetNetworkNodes(netID.String())
 	for _, nodeI := range nodes {
+		tagNodesMap[models.TagID(nodeI.ID.String())] = []models.Node{
+			nodeI,
+		}
 		if nodeI.Tags == nil {
 			continue
 		}
@@ -842,9 +860,19 @@ func AddTagMapWithStaticNodes(netID models.NetworkID,
 		return tagNodesMap
 	}
 	for _, extclient := range extclients {
-		if extclient.Tags == nil || extclient.RemoteAccessClientID != "" {
+		if extclient.RemoteAccessClientID != "" {
 			continue
 		}
+		tagNodesMap[models.TagID(extclient.ClientID)] = []models.Node{
+			{
+				IsStatic:   true,
+				StaticNode: extclient,
+			},
+		}
+		if extclient.Tags == nil {
+			continue
+		}
+
 		for tagID := range extclient.Tags {
 			tagNodesMap[tagID] = append(tagNodesMap[tagID], models.Node{
 				IsStatic:   true,
@@ -867,6 +895,12 @@ func AddTagMapWithStaticNodesWithUsers(netID models.NetworkID,
 		return tagNodesMap
 	}
 	for _, extclient := range extclients {
+		tagNodesMap[models.TagID(extclient.ClientID)] = []models.Node{
+			{
+				IsStatic:   true,
+				StaticNode: extclient,
+			},
+		}
 		if extclient.Tags == nil {
 			continue
 		}

+ 2 - 2
logic/status.go

@@ -6,9 +6,9 @@ import (
 	"github.com/gravitl/netmaker/models"
 )
 
-var GetNodeStatus = getNodeStatus
+var GetNodeStatus = GetNodeCheckInStatus
 
-func getNodeStatus(node *models.Node, t bool) {
+func GetNodeCheckInStatus(node *models.Node, t bool) {
 	// On CE check only last check-in time
 	if node.IsStatic {
 		if !node.StaticNode.Enabled {

+ 1 - 0
logic/user_mgmt.go

@@ -60,6 +60,7 @@ var DeleteNetworkRoles = func(netID string) {}
 var CreateDefaultNetworkRolesAndGroups = func(netID models.NetworkID) {}
 var CreateDefaultUserPolicies = func(netID models.NetworkID) {}
 var GetUserGroupsInNetwork = func(netID models.NetworkID) (networkGrps map[models.UserGroupID]models.UserGroup) { return }
+var GetUserGroup = func(groupId models.UserGroupID) (userGrps models.UserGroup, err error) { return }
 var AddGlobalNetRolesToAdmins = func(u models.User) {}
 
 // GetRole - fetches role template by id

+ 20 - 0
models/api_node.go

@@ -8,6 +8,13 @@ import (
 	"golang.org/x/exp/slog"
 )
 
+type ApiNodeStatus struct {
+	ID         string     `json:"id"`
+	IsStatic   bool       `json:"is_static"`
+	IsUserNode bool       `json:"is_user_node"`
+	Status     NodeStatus `json:"status"`
+}
+
 // ApiNode is a stripped down Node DTO that exposes only required fields to external systems
 type ApiNode struct {
 	ID                         string   `json:"id,omitempty" validate:"required,min=5,id_unique"`
@@ -132,6 +139,19 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node {
 	return &convertedNode
 }
 
+func (nm *Node) ConvertToStatusNode() *ApiNodeStatus {
+	apiNode := ApiNodeStatus{}
+	if nm.IsStatic {
+		apiNode.ID = nm.StaticNode.ClientID
+	} else {
+		apiNode.ID = nm.ID.String()
+	}
+	apiNode.IsStatic = nm.IsStatic
+	apiNode.IsUserNode = nm.IsUserNode
+	apiNode.Status = nm.Status
+	return &apiNode
+}
+
 // Node.ConvertToAPINode - converts a node to an API node
 func (nm *Node) ConvertToAPINode() *ApiNode {
 	apiNode := ApiNode{}

+ 3 - 1
models/extclient.go

@@ -50,7 +50,9 @@ func (ext *ExtClient) ConvertToStaticNode() Node {
 
 	return Node{
 		CommonNode: CommonNode{
-			Network: ext.Network,
+			Network:  ext.Network,
+			Address:  ext.AddressIPNet4(),
+			Address6: ext.AddressIPNet6(),
 		},
 		Tags:       ext.Tags,
 		IsStatic:   true,

+ 12 - 11
models/structs.go

@@ -32,17 +32,18 @@ type IngressGwUsers struct {
 
 // UserRemoteGws - struct to hold user's remote gws
 type UserRemoteGws struct {
-	GwID              string    `json:"remote_access_gw_id"`
-	GWName            string    `json:"gw_name"`
-	Network           string    `json:"network"`
-	Connected         bool      `json:"connected"`
-	IsInternetGateway bool      `json:"is_internet_gateway"`
-	GwClient          ExtClient `json:"gw_client"`
-	GwPeerPublicKey   string    `json:"gw_peer_public_key"`
-	GwListenPort      int       `json:"gw_listen_port"`
-	Metadata          string    `json:"metadata"`
-	AllowedEndpoints  []string  `json:"allowed_endpoints"`
-	NetworkAddresses  []string  `json:"network_addresses"`
+	GwID              string     `json:"remote_access_gw_id"`
+	GWName            string     `json:"gw_name"`
+	Network           string     `json:"network"`
+	Connected         bool       `json:"connected"`
+	IsInternetGateway bool       `json:"is_internet_gateway"`
+	GwClient          ExtClient  `json:"gw_client"`
+	GwPeerPublicKey   string     `json:"gw_peer_public_key"`
+	GwListenPort      int        `json:"gw_listen_port"`
+	Metadata          string     `json:"metadata"`
+	AllowedEndpoints  []string   `json:"allowed_endpoints"`
+	NetworkAddresses  []string   `json:"network_addresses"`
+	Status            NodeStatus `json:"status"`
 }
 
 // UserRAGs - struct for user access gws

+ 1 - 0
models/user_mgmt.go

@@ -159,6 +159,7 @@ type User struct {
 type ReturnUserWithRolesAndGroups struct {
 	ReturnUser
 	PlatformRole UserRolePermissionTemplate `json:"platform_role"`
+	UserGroups   map[UserGroupID]UserGroup  `json:"user_group_ids"`
 }
 
 // ReturnUser - return user struct

+ 10 - 0
pro/controllers/users.go

@@ -1102,6 +1102,10 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 				slog.Error("failed to get node network", "error", err)
 				continue
 			}
+			nodesWithStatus := logic.AddStatusToNodes([]models.Node{node}, false)
+			if len(nodesWithStatus) > 0 {
+				node = nodesWithStatus[0]
+			}
 
 			gws := userGws[node.Network]
 			extClient.AllowedIPs = logic.GetExtclientAllowedIPs(extClient)
@@ -1117,6 +1121,7 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 				Metadata:          node.Metadata,
 				AllowedEndpoints:  getAllowedRagEndpoints(&node, host),
 				NetworkAddresses:  []string{network.AddressRange, network.AddressRange6},
+				Status:            node.Status,
 			})
 			userGws[node.Network] = gws
 			delete(userGwNodes, node.ID.String())
@@ -1138,6 +1143,10 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 		if err != nil {
 			continue
 		}
+		nodesWithStatus := logic.AddStatusToNodes([]models.Node{node}, false)
+		if len(nodesWithStatus) > 0 {
+			node = nodesWithStatus[0]
+		}
 		network, err := logic.GetNetwork(node.Network)
 		if err != nil {
 			slog.Error("failed to get node network", "error", err)
@@ -1154,6 +1163,7 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 			Metadata:          node.Metadata,
 			AllowedEndpoints:  getAllowedRagEndpoints(&node, host),
 			NetworkAddresses:  []string{network.AddressRange, network.AddressRange6},
+			Status:            node.Status,
 		})
 		userGws[node.Network] = gws
 	}

+ 1 - 0
pro/initialize.go

@@ -133,6 +133,7 @@ func InitPro() {
 	logic.IntialiseGroups = proLogic.UserGroupsInit
 	logic.AddGlobalNetRolesToAdmins = proLogic.AddGlobalNetRolesToAdmins
 	logic.GetUserGroupsInNetwork = proLogic.GetUserGroupsInNetwork
+	logic.GetUserGroup = proLogic.GetUserGroup
 	logic.GetNodeStatus = proLogic.GetNodeStatus
 }
 

+ 3 - 3
pro/logic/status.go

@@ -41,7 +41,7 @@ func GetNodeStatus(node *models.Node, defaultEnabledPolicy bool) {
 			return
 		}
 		if !defaultEnabledPolicy {
-			allowed, _ := logic.IsNodeAllowedToCommunicate(*node, ingNode, false)
+			allowed, _ := logic.IsNodeAllowedToCommunicateV1(*node, ingNode, false)
 			if !allowed {
 				node.Status = models.OnlineSt
 				return
@@ -161,7 +161,7 @@ func checkPeerStatus(node *models.Node, defaultAclPolicy bool) {
 		}
 
 		if !defaultAclPolicy {
-			allowed, _ := logic.IsNodeAllowedToCommunicate(*node, peer, false)
+			allowed, _ := logic.IsNodeAllowedToCommunicateV1(*node, peer, false)
 			if !allowed {
 				continue
 			}
@@ -199,7 +199,7 @@ func checkPeerConnectivity(node *models.Node, metrics *models.Metrics, defaultAc
 		}
 
 		if !defaultAclPolicy {
-			allowed, _ := logic.IsNodeAllowedToCommunicate(*node, peer, false)
+			allowed, _ := logic.IsNodeAllowedToCommunicateV1(*node, peer, false)
 			if !allowed {
 				continue
 			}

+ 6 - 4
pro/logic/user_mgmt.go

@@ -216,8 +216,9 @@ func CreateDefaultNetworkRolesAndGroups(netID models.NetworkID) {
 
 	// create default network groups
 	var NetworkAdminGroup = models.UserGroup{
-		ID:   models.UserGroupID(fmt.Sprintf("%s-%s-grp", netID, models.NetworkAdmin)),
-		Name: fmt.Sprintf("%s Admin Group", netID),
+		ID:      models.UserGroupID(fmt.Sprintf("%s-%s-grp", netID, models.NetworkAdmin)),
+		Name:    fmt.Sprintf("%s Admin Group", netID),
+		Default: true,
 		NetworkRoles: map[models.NetworkID]map[models.UserRoleID]struct{}{
 			netID: {
 				models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkAdmin)): {},
@@ -226,8 +227,9 @@ func CreateDefaultNetworkRolesAndGroups(netID models.NetworkID) {
 		MetaData: fmt.Sprintf("can manage your network `%s` configuration including adding and removing devices.", netID),
 	}
 	var NetworkUserGroup = models.UserGroup{
-		ID:   models.UserGroupID(fmt.Sprintf("%s-%s-grp", netID, models.NetworkUser)),
-		Name: fmt.Sprintf("%s User Group", netID),
+		ID:      models.UserGroupID(fmt.Sprintf("%s-%s-grp", netID, models.NetworkUser)),
+		Name:    fmt.Sprintf("%s User Group", netID),
+		Default: true,
 		NetworkRoles: map[models.NetworkID]map[models.UserRoleID]struct{}{
 			netID: {
 				models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkUser)): {},

+ 1 - 1
scripts/nm-quick.sh

@@ -253,7 +253,7 @@ save_config() { (
 	fi
 	# copy entries from the previous config
 	local toCopy=("SERVER_HOST" "SERVER_HOST6" "MASTER_KEY" "MQ_USERNAME" "MQ_PASSWORD" "LICENSE_KEY" "NETMAKER_TENANT_ID"
-		"INSTALL_TYPE" "NODE_ID" "DNS_MODE" "NETCLIENT_AUTO_UPDATE" "API_PORT"
+		"INSTALL_TYPE" "NODE_ID" "DNS_MODE" "NETCLIENT_AUTO_UPDATE" "API_PORT" "MANAGE_DNS" "DEFAULT_DOMAIN"
 		"CORS_ALLOWED_ORIGIN" "DISPLAY_KEYS" "DATABASE" "SERVER_BROKER_ENDPOINT" "VERBOSITY"
 		"DEBUG_MODE"  "REST_BACKEND" "DISABLE_REMOTE_IP_CHECK" "TELEMETRY" "ALLOWED_EMAIL_DOMAINS" "AUTH_PROVIDER" "CLIENT_ID" "CLIENT_SECRET"
 		"FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" "EXPORTER_API_PORT" "JWT_VALIDITY_DURATION" "RAC_AUTO_DISABLE" "CACHING_ENABLED" "ENDPOINT_DETECTION"

+ 1 - 1
servercfg/serverconf.go

@@ -513,7 +513,7 @@ func GetPublicIP() (string, error) {
 	endpoint := ""
 	var err error
 
-	iplist := []string{"https://ifconfig.me", "https://api.ipify.org", "https://ipinfo.io/ip"}
+	iplist := []string{"https://ifconfig.me/ip", "https://api.ipify.org", "https://ipinfo.io/ip"}
 	publicIpService := os.Getenv("PUBLIC_IP_SERVICE")
 	if publicIpService != "" {
 		// prepend the user-specified service so it's checked first