Просмотр исходного кода

NET-1910: Acl controls for Egress Traffic (#3377)

* add support for egress ranges on acl policy

* add egress ranges to acl rules

* add egress ranges to acl policies

* Add egress ranges to acl rules

* add egress ranges to fw update

* fetch acl rules for egress networks

* apply egress policies for devices

* configure user policies for egresss routes

* fix gw tag name migration

* fix egress acl rules for static nodes

* add egress ranges for static nodes on ingress gw

* fileter acl IPs to be unique

* cleanup IOT logic from peer update

* make acl Rule Dst List

* cleanup egress ranges from acl policies

* create user group default acl policy for gateways

* remove remote access name ids

* rm egress ranges removal from acl policies

* simplify user permissions on nodes

* add additional nameservers to extclient dns

* remove debug logs

* fix static checks
Abhishek K 5 месяцев назад
Родитель
Сommit
3d765f9cf1

+ 1 - 1
Dockerfile

@@ -4,7 +4,7 @@ ARG tags
 WORKDIR /app
 COPY . .
 
-RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -w " -tags ${tags} .
+RUN GOOS=linux CGO_ENABLED=1 go build -race -ldflags="-s -w " -tags ${tags} .
 # RUN go build -tags=ee . -o netmaker main.go
 FROM alpine:3.21.2
 

+ 1 - 0
controllers/acls.go

@@ -51,6 +51,7 @@ func aclPolicyTypes(w http.ResponseWriter, r *http.Request) {
 		DstGroupTypes: []models.AclGroupType{
 			models.NodeTagID,
 			models.NodeID,
+			models.EgressRange,
 			// models.NetmakerIPAclID,
 			// models.NetmakerSubNetRangeAClID,
 		},

+ 12 - 1
controllers/ext_client.go

@@ -475,7 +475,18 @@ func getExtClientHAConf(w http.ResponseWriter, r *http.Request) {
 	// 	models.RemoteAccessTagName))] = struct{}{}
 	// set extclient dns to ingressdns if extclient dns is not explicitly set
 	if (extclient.DNS == "") && (gwnode.IngressDNS != "") {
-		extclient.DNS = gwnode.IngressDNS
+		network, _ := logic.GetNetwork(gwnode.Network)
+		dns := gwnode.IngressDNS
+		if len(network.NameServers) > 0 {
+			if dns == "" {
+				dns = strings.Join(network.NameServers, ",")
+			} else {
+				dns += "," + strings.Join(network.NameServers, ",")
+			}
+
+		}
+		extclient.DNS = dns
+
 	}
 
 	listenPort := logic.GetPeerListenPort(host)

+ 5 - 53
controllers/hosts.go

@@ -140,60 +140,12 @@ func upgradeHost(w http.ResponseWriter, r *http.Request) {
 // @Failure     500 {object} models.ErrorResponse
 func getHosts(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
-	currentHosts := []models.Host{}
-	var err error
-	if r.Header.Get("ismaster") == "yes" {
-		currentHosts, err = logic.GetAllHosts()
-		if err != nil {
-			logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error())
-			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-			return
-		}
-	} else {
-		username := r.Header.Get("user")
-		user, err := logic.GetUser(username)
-		if err != nil {
-			return
-		}
-		userPlatformRole, err := logic.GetRole(user.PlatformRoleID)
-		if err != nil {
-			return
-		}
-		respHostsMap := make(map[string]struct{})
-		if !userPlatformRole.FullAccess {
-			nodes, err := logic.GetAllNodes()
-			if err != nil {
-				logger.Log(0, "error fetching all nodes info: ", err.Error())
-				logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-				return
-			}
-			filteredNodes := logic.GetFilteredNodesByUserAccess(*user, nodes)
-			if len(filteredNodes) > 0 {
-				currentHostsMap, err := logic.GetHostsMap()
-				if err != nil {
-					logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error())
-					logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-					return
-				}
-				for _, node := range filteredNodes {
-					if _, ok := respHostsMap[node.HostID.String()]; ok {
-						continue
-					}
-					if host, ok := currentHostsMap[node.HostID.String()]; ok {
-						currentHosts = append(currentHosts, host)
-						respHostsMap[host.ID.String()] = struct{}{}
-					}
-				}
 
-			}
-		} else {
-			currentHosts, err = logic.GetAllHosts()
-			if err != nil {
-				logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error())
-				logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-				return
-			}
-		}
+	currentHosts, err := logic.GetAllHosts()
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
 	}
 
 	apiHosts := logic.GetAllHostsAPI(currentHosts[:])

+ 28 - 0
controllers/network.go

@@ -41,6 +41,7 @@ func networkHandlers(r *mux.Router) {
 		Methods(http.MethodPut)
 	r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(getNetworkACL))).
 		Methods(http.MethodGet)
+	r.HandleFunc("/api/networks/{networkname}/egress_routes", logic.SecurityCheck(true, http.HandlerFunc(getNetworkEgressRoutes)))
 }
 
 // @Summary     Lists all networks
@@ -429,6 +430,33 @@ func getNetworkACL(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(networkACL)
 }
 
+// @Summary     Get a network Egress routes
+// @Router      /api/networks/{networkname}/egress_routes [get]
+// @Tags        Networks
+// @Security    oauth
+// @Param       networkname path string true "Network name"
+// @Produce     json
+// @Success     200 {object} acls.SuccessResponse
+// @Failure     500 {object} models.ErrorResponse
+func getNetworkEgressRoutes(w http.ResponseWriter, r *http.Request) {
+	var params = mux.Vars(r)
+	netname := params["networkname"]
+	// check if network exists
+	_, err := logic.GetNetwork(netname)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"),
+			fmt.Sprintf("failed to fetch ACLs for network [%s]: %v", netname, err))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	nodeEgressRoutes, _, err := logic.GetEgressRanges(models.NetworkID(netname))
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	logic.ReturnSuccessResponseWithJson(w, r, nodeEgressRoutes, "fetched network egress routes")
+}
+
 // @Summary     Delete a network
 // @Router      /api/networks/{networkname} [delete]
 // @Tags        Networks

+ 480 - 108
logic/acls.go

@@ -5,6 +5,7 @@ import (
 	"errors"
 	"fmt"
 	"maps"
+	"net"
 	"sort"
 	"sync"
 	"time"
@@ -222,6 +223,104 @@ func IsAclExists(aclID string) bool {
 	_, err := GetAcl(aclID)
 	return err == nil
 }
+func GetEgressRanges(netID models.NetworkID) (map[string][]string, map[string]struct{}, error) {
+
+	resultMap := make(map[string]struct{})
+	nodeEgressMap := make(map[string][]string)
+	networkNodes, err := GetNetworkNodes(netID.String())
+	if err != nil {
+		return nil, nil, err
+	}
+	for _, currentNode := range networkNodes {
+		if currentNode.Network != netID.String() {
+			continue
+		}
+		if currentNode.IsEgressGateway { // add the egress gateway range(s) to the result
+			if len(currentNode.EgressGatewayRanges) > 0 {
+				nodeEgressMap[currentNode.ID.String()] = currentNode.EgressGatewayRanges
+				for _, egressRangeI := range currentNode.EgressGatewayRanges {
+					resultMap[egressRangeI] = struct{}{}
+				}
+			}
+		}
+	}
+	extclients, _ := GetNetworkExtClients(netID.String())
+	for _, extclient := range extclients {
+		if len(extclient.ExtraAllowedIPs) > 0 {
+			nodeEgressMap[extclient.ClientID] = extclient.ExtraAllowedIPs
+			for _, extraAllowedIP := range extclient.ExtraAllowedIPs {
+				resultMap[extraAllowedIP] = struct{}{}
+			}
+		}
+	}
+	return nodeEgressMap, resultMap, nil
+}
+
+func checkIfAclTagisValid(t models.AclPolicyTag, netID models.NetworkID, policyType models.AclPolicyType, isSrc bool) bool {
+	switch t.ID {
+	case models.NodeTagID:
+		if policyType == models.UserPolicy && isSrc {
+			return false
+		}
+		// check if tag is valid
+		_, err := GetTag(models.TagID(t.Value))
+		if err != nil {
+			return false
+		}
+	case models.NodeID:
+		if policyType == models.UserPolicy && isSrc {
+			return false
+		}
+		_, nodeErr := GetNodeByID(t.Value)
+		if nodeErr != nil {
+			_, staticNodeErr := GetExtClient(t.Value, netID.String())
+			if staticNodeErr != nil {
+				return false
+			}
+		}
+	case models.EgressRange:
+		if isSrc {
+			return false
+		}
+		// _, rangesMap, err := GetEgressRanges(netID)
+		// if err != nil {
+		// 	return false
+		// }
+		// if _, ok := rangesMap[t.Value]; !ok {
+		// 	return false
+		// }
+	case models.UserAclID:
+		if policyType == models.DevicePolicy {
+			return false
+		}
+		if !isSrc {
+			return false
+		}
+		_, err := GetUser(t.Value)
+		if err != nil {
+			return false
+		}
+	case models.UserGroupAclID:
+		if policyType == models.DevicePolicy {
+			return false
+		}
+		if !isSrc {
+			return false
+		}
+		err := IsGroupValid(models.UserGroupID(t.Value))
+		if err != nil {
+			return false
+		}
+		// check if group belongs to this network
+		netGrps := GetUserGroupsInNetwork(netID)
+		if _, ok := netGrps[models.UserGroupID(t.Value)]; !ok {
+			return false
+		}
+	default:
+		return false
+	}
+	return true
+}
 
 // IsAclPolicyValid - validates if acl policy is valid
 func IsAclPolicyValid(acl models.Acl) bool {
@@ -235,115 +334,43 @@ func IsAclPolicyValid(acl models.Acl) bool {
 		// src list should only contain users
 		for _, srcI := range acl.Src {
 
-			if srcI.ID == "" || srcI.Value == "" {
-				return false
-			}
 			if srcI.Value == "*" {
 				continue
 			}
-			if srcI.ID != models.UserAclID && srcI.ID != models.UserGroupAclID {
-				return false
-			}
 			// check if user group is valid
-			if srcI.ID == models.UserAclID {
-				_, err := GetUser(srcI.Value)
-				if err != nil {
-					return false
-				}
-
-			} else if srcI.ID == models.UserGroupAclID {
-				err := IsGroupValid(models.UserGroupID(srcI.Value))
-				if err != nil {
-					return false
-				}
-				// check if group belongs to this network
-				netGrps := GetUserGroupsInNetwork(acl.NetworkID)
-				if _, ok := netGrps[models.UserGroupID(srcI.Value)]; !ok {
-					return false
-				}
+			if !checkIfAclTagisValid(srcI, acl.NetworkID, acl.RuleType, true) {
+				return false
 			}
-
 		}
 		for _, dstI := range acl.Dst {
 
-			if dstI.ID == "" || dstI.Value == "" {
-				return false
-			}
-			if dstI.ID != models.NodeTagID && dstI.ID != models.NodeID {
-				return false
-			}
 			if dstI.Value == "*" {
 				continue
 			}
-			if dstI.ID == models.NodeTagID {
-				// check if tag is valid
-				_, err := GetTag(models.TagID(dstI.Value))
-				if err != nil {
-					return false
-				}
-			} else {
-				_, nodeErr := GetNodeByID(dstI.Value)
-				if nodeErr != nil {
-					_, staticNodeErr := GetExtClient(dstI.Value, acl.NetworkID.String())
-					if staticNodeErr != nil {
-						return false
-					}
-				}
+
+			// check if user group is valid
+			if !checkIfAclTagisValid(dstI, acl.NetworkID, acl.RuleType, false) {
+				return false
 			}
 		}
 	case models.DevicePolicy:
 		for _, srcI := range acl.Src {
-			if srcI.ID == "" || srcI.Value == "" {
-				return false
-			}
-			if srcI.ID != models.NodeTagID && srcI.ID != models.NodeID {
-				return false
-			}
 			if srcI.Value == "*" {
 				continue
 			}
-			if srcI.ID == models.NodeTagID {
-				// check if tag is valid
-				_, err := GetTag(models.TagID(srcI.Value))
-				if err != nil {
-					return false
-				}
-			} else {
-				_, nodeErr := GetNodeByID(srcI.Value)
-				if nodeErr != nil {
-					_, staticNodeErr := GetExtClient(srcI.Value, acl.NetworkID.String())
-					if staticNodeErr != nil {
-						return false
-					}
-				}
+			// check if user group is valid
+			if !checkIfAclTagisValid(srcI, acl.NetworkID, acl.RuleType, true) {
+				return false
 			}
-
 		}
 		for _, dstI := range acl.Dst {
 
-			if dstI.ID == "" || dstI.Value == "" {
-				return false
-			}
-			if dstI.ID != models.NodeTagID && dstI.ID != models.NodeID {
-				return false
-			}
 			if dstI.Value == "*" {
 				continue
 			}
-			if dstI.ID == models.NodeTagID {
-				// check if tag is valid
-				_, err := GetTag(models.TagID(dstI.Value))
-				if err != nil {
-					return false
-				}
-			} else {
-				_, nodeErr := GetNodeByID(dstI.Value)
-				if nodeErr != nil {
-					_, staticNodeErr := GetExtClient(dstI.Value, acl.NetworkID.String())
-					if staticNodeErr != nil {
-						return false
-					}
-				}
+			// check if user group is valid
+			if !checkIfAclTagisValid(dstI, acl.NetworkID, acl.RuleType, false) {
+				return false
 			}
 		}
 	}
@@ -688,8 +715,8 @@ func RemoveUserFromAclPolicy(userName string) {
 		delete := false
 		update := false
 		if acl.RuleType == models.UserPolicy {
-			for i, srcI := range acl.Src {
-				if srcI.ID == models.UserAclID && srcI.Value == userName {
+			for i := len(acl.Src) - 1; i >= 0; i-- {
+				if acl.Src[i].ID == models.UserAclID && acl.Src[i].Value == userName {
 					if len(acl.Src) == 1 {
 						// delete policy
 						delete = true
@@ -723,8 +750,8 @@ func RemoveNodeFromAclPolicy(node models.Node) {
 		delete := false
 		update := false
 		if acl.RuleType == models.DevicePolicy {
-			for i, srcI := range acl.Src {
-				if srcI.ID == models.NodeID && srcI.Value == nodeID {
+			for i := len(acl.Src) - 1; i >= 0; i-- {
+				if acl.Src[i].ID == models.NodeID && acl.Src[i].Value == nodeID {
 					if len(acl.Src) == 1 {
 						// delete policy
 						delete = true
@@ -739,8 +766,8 @@ func RemoveNodeFromAclPolicy(node models.Node) {
 				DeleteAcl(acl)
 				continue
 			}
-			for i, dstI := range acl.Dst {
-				if dstI.ID == models.NodeID && dstI.Value == nodeID {
+			for i := len(acl.Dst) - 1; i >= 0; i-- {
+				if acl.Dst[i].ID == models.NodeID && acl.Dst[i].Value == nodeID {
 					if len(acl.Dst) == 1 {
 						// delete policy
 						delete = true
@@ -761,8 +788,8 @@ func RemoveNodeFromAclPolicy(node models.Node) {
 
 		}
 		if acl.RuleType == models.UserPolicy {
-			for i, dstI := range acl.Dst {
-				if dstI.ID == models.NodeID && dstI.Value == nodeID {
+			for i := len(acl.Dst) - 1; i >= 0; i-- {
+				if acl.Dst[i].ID == models.NodeID && acl.Dst[i].Value == nodeID {
 					if len(acl.Dst) == 1 {
 						// delete policy
 						delete = true
@@ -1239,17 +1266,17 @@ func RemoveDeviceTagFromAclPolicies(tagID models.TagID, netID models.NetworkID)
 	acls := listDevicePolicies(netID)
 	update := false
 	for _, acl := range acls {
-		for i, srcTagI := range acl.Src {
-			if srcTagI.ID == models.NodeTagID {
-				if tagID.String() == srcTagI.Value {
+		for i := len(acl.Src) - 1; i >= 0; i-- {
+			if acl.Src[i].ID == models.NodeTagID {
+				if tagID.String() == acl.Src[i].Value {
 					acl.Src = append(acl.Src[:i], acl.Src[i+1:]...)
 					update = true
 				}
 			}
 		}
-		for i, dstTagI := range acl.Dst {
-			if dstTagI.ID == models.NodeTagID {
-				if tagID.String() == dstTagI.Value {
+		for i := len(acl.Dst) - 1; i >= 0; i-- {
+			if acl.Dst[i].ID == models.NodeTagID {
+				if tagID.String() == acl.Dst[i].Value {
 					acl.Dst = append(acl.Dst[:i], acl.Dst[i+1:]...)
 					update = true
 				}
@@ -1262,6 +1289,106 @@ func RemoveDeviceTagFromAclPolicies(tagID models.TagID, netID models.NetworkID)
 	return nil
 }
 
+func getEgressUserRulesForNode(targetnode *models.Node,
+	rules map[string]models.AclRule) map[string]models.AclRule {
+	userNodes := GetStaticUserNodesByNetwork(models.NetworkID(targetnode.Network))
+	userGrpMap := GetUserGrpMap()
+	allowedUsers := make(map[string][]models.Acl)
+	acls := listUserPolicies(models.NetworkID(targetnode.Network))
+	var targetNodeTags = make(map[models.TagID]struct{})
+	targetNodeTags["*"] = struct{}{}
+	for _, rangeI := range targetnode.EgressGatewayRanges {
+		targetNodeTags[models.TagID(rangeI)] = struct{}{}
+	}
+	for _, acl := range acls {
+		if !acl.Enabled {
+			continue
+		}
+		dstTags := convAclTagToValueMap(acl.Dst)
+		_, all := dstTags["*"]
+		addUsers := false
+		if !all {
+			for nodeTag := range targetNodeTags {
+				if _, ok := dstTags[nodeTag.String()]; ok {
+					addUsers = true
+					break
+				}
+			}
+		} else {
+			addUsers = true
+		}
+
+		if addUsers {
+			// get all src tags
+			for _, srcAcl := range acl.Src {
+				if srcAcl.ID == models.UserAclID {
+					allowedUsers[srcAcl.Value] = append(allowedUsers[srcAcl.Value], acl)
+				} else if srcAcl.ID == models.UserGroupAclID {
+					// fetch all users in the group
+					if usersMap, ok := userGrpMap[models.UserGroupID(srcAcl.Value)]; ok {
+						for userName := range usersMap {
+							allowedUsers[userName] = append(allowedUsers[userName], acl)
+						}
+					}
+				}
+			}
+		}
+
+	}
+
+	for _, userNode := range userNodes {
+		if !userNode.StaticNode.Enabled {
+			continue
+		}
+		acls, ok := allowedUsers[userNode.StaticNode.OwnerID]
+		if !ok {
+			continue
+		}
+		for _, acl := range acls {
+
+			if !acl.Enabled {
+				continue
+			}
+			r := models.AclRule{
+				ID:              acl.ID,
+				AllowedProtocol: acl.Proto,
+				AllowedPorts:    acl.Port,
+				Direction:       acl.AllowedDirection,
+				Allowed:         true,
+			}
+			// Get peers in the tags and add allowed rules
+			if userNode.StaticNode.Address != "" {
+				r.IPList = append(r.IPList, userNode.StaticNode.AddressIPNet4())
+			}
+			if userNode.StaticNode.Address6 != "" {
+				r.IP6List = append(r.IP6List, userNode.StaticNode.AddressIPNet6())
+			}
+			for _, dstI := range acl.Dst {
+				if dstI.ID == models.EgressRange {
+					ip, cidr, err := net.ParseCIDR(dstI.Value)
+					if err == nil {
+						if ip.To4() != nil {
+							r.Dst = append(r.Dst, *cidr)
+						} else {
+							r.Dst6 = append(r.Dst6, *cidr)
+						}
+
+					}
+				}
+
+			}
+			if aclRule, ok := rules[acl.ID]; ok {
+				aclRule.IPList = append(aclRule.IPList, r.IPList...)
+				aclRule.IP6List = append(aclRule.IP6List, r.IP6List...)
+				rules[acl.ID] = aclRule
+			} else {
+				rules[acl.ID] = r
+			}
+		}
+	}
+	return rules
+}
+
 func getUserAclRulesForNode(targetnode *models.Node,
 	rules map[string]models.AclRule) map[string]models.AclRule {
 	userNodes := GetStaticUserNodesByNetwork(models.NetworkID(targetnode.Network))
@@ -1276,6 +1403,7 @@ func getUserAclRulesForNode(targetnode *models.Node,
 	} else {
 		targetNodeTags = maps.Clone(targetnode.Tags)
 	}
+	targetNodeTags[models.TagID(targetnode.ID.String())] = struct{}{}
 	for _, acl := range acls {
 		if !acl.Enabled {
 			continue
@@ -1285,10 +1413,9 @@ func getUserAclRulesForNode(targetnode *models.Node,
 		addUsers := false
 		if !all {
 			for nodeTag := range targetNodeTags {
-				if _, ok := dstTags[nodeTag.String()]; !ok {
-					if _, ok = dstTags[targetnode.ID.String()]; !ok {
-						break
-					}
+				if _, ok := dstTags[nodeTag.String()]; ok {
+					addUsers = true
+					break
 				}
 			}
 		} else {
@@ -1326,7 +1453,6 @@ func getUserAclRulesForNode(targetnode *models.Node,
 			if !acl.Enabled {
 				continue
 			}
-
 			r := models.AclRule{
 				ID:              acl.ID,
 				AllowedProtocol: acl.Proto,
@@ -1354,7 +1480,16 @@ func getUserAclRulesForNode(targetnode *models.Node,
 }
 
 func checkIfAnyPolicyisUniDirectional(targetNode models.Node) bool {
-	targetNode.Tags[models.TagID(targetNode.ID.String())] = struct{}{}
+	var targetNodeTags = make(map[models.TagID]struct{})
+	if targetNode.Mutex != nil {
+		targetNode.Mutex.Lock()
+		targetNodeTags = maps.Clone(targetNode.Tags)
+		targetNode.Mutex.Unlock()
+	} else {
+		targetNodeTags = maps.Clone(targetNode.Tags)
+	}
+	targetNodeTags[models.TagID(targetNode.ID.String())] = struct{}{}
+	targetNodeTags["*"] = struct{}{}
 	acls := listDevicePolicies(models.NetworkID(targetNode.Network))
 	for _, acl := range acls {
 		if !acl.Enabled {
@@ -1363,9 +1498,12 @@ func checkIfAnyPolicyisUniDirectional(targetNode models.Node) bool {
 		if acl.AllowedDirection == models.TrafficDirectionBi {
 			continue
 		}
+		if acl.Proto != models.ALL || acl.ServiceType != models.Any {
+			return true
+		}
 		srcTags := convAclTagToValueMap(acl.Src)
 		dstTags := convAclTagToValueMap(acl.Dst)
-		for nodeTag := range targetNode.Tags {
+		for nodeTag := range targetNodeTags {
 			if _, ok := srcTags[nodeTag.String()]; ok {
 				return true
 			}
@@ -1385,12 +1523,10 @@ func checkIfAnyPolicyisUniDirectional(targetNode models.Node) bool {
 
 func GetAclRulesForNode(targetnodeI *models.Node) (rules map[string]models.AclRule) {
 	targetnode := *targetnodeI
-	targetnode.Tags[models.TagID(targetnode.ID.String())] = struct{}{}
 	defer func() {
 		if !targetnode.IsIngressGateway {
 			rules = getUserAclRulesForNode(&targetnode, rules)
 		}
-
 	}()
 	rules = make(map[string]models.AclRule)
 	var taggedNodes map[models.TagID][]models.Node
@@ -1409,8 +1545,8 @@ func GetAclRulesForNode(targetnodeI *models.Node) (rules map[string]models.AclRu
 	} else {
 		targetNodeTags = maps.Clone(targetnode.Tags)
 	}
+	targetNodeTags[models.TagID(targetnode.ID.String())] = struct{}{}
 	targetNodeTags["*"] = struct{}{}
-
 	for _, acl := range acls {
 		if !acl.Enabled {
 			continue
@@ -1593,9 +1729,245 @@ func GetAclRulesForNode(targetnodeI *models.Node) (rules map[string]models.AclRu
 			}
 
 		}
+
 		if len(aclRule.IPList) > 0 || len(aclRule.IP6List) > 0 {
+			aclRule.IPList = UniqueIPNetList(aclRule.IPList)
+			aclRule.IP6List = UniqueIPNetList(aclRule.IP6List)
 			rules[acl.ID] = aclRule
 		}
 	}
 	return rules
 }
+func UniqueIPNetList(ipnets []net.IPNet) []net.IPNet {
+	uniqueMap := make(map[string]net.IPNet)
+
+	for _, ipnet := range ipnets {
+		key := ipnet.String() // Uses CIDR notation as a unique key
+		if _, exists := uniqueMap[key]; !exists {
+			uniqueMap[key] = ipnet
+		}
+	}
+
+	// Convert map back to slice
+	uniqueList := make([]net.IPNet, 0, len(uniqueMap))
+	for _, ipnet := range uniqueMap {
+		uniqueList = append(uniqueList, ipnet)
+	}
+
+	return uniqueList
+}
+
+func GetEgressRulesForNode(targetnode models.Node) (rules map[string]models.AclRule) {
+	rules = make(map[string]models.AclRule)
+	defer func() {
+		rules = getEgressUserRulesForNode(&targetnode, rules)
+	}()
+	taggedNodes := GetTagMapWithNodesByNetwork(models.NetworkID(targetnode.Network), true)
+
+	acls := listDevicePolicies(models.NetworkID(targetnode.Network))
+	var targetNodeTags = make(map[models.TagID]struct{})
+	targetNodeTags["*"] = struct{}{}
+
+	/*
+		 if target node is egress gateway
+			if acl policy has egress route and it is present in target node egress ranges
+			fetches all the nodes in that policy and add rules
+	*/
+
+	for _, rangeI := range targetnode.EgressGatewayRanges {
+		targetNodeTags[models.TagID(rangeI)] = struct{}{}
+	}
+	for _, acl := range acls {
+		if !acl.Enabled {
+			continue
+		}
+		srcTags := convAclTagToValueMap(acl.Src)
+		dstTags := convAclTagToValueMap(acl.Dst)
+		_, srcAll := srcTags["*"]
+		_, dstAll := dstTags["*"]
+		for nodeTag := range targetNodeTags {
+			aclRule := models.AclRule{
+				ID:              acl.ID,
+				AllowedProtocol: acl.Proto,
+				AllowedPorts:    acl.Port,
+				Direction:       acl.AllowedDirection,
+				Allowed:         true,
+			}
+			if nodeTag != "*" {
+				ip, cidr, err := net.ParseCIDR(nodeTag.String())
+				if err != nil {
+					continue
+				}
+				if ip.To4() != nil {
+					aclRule.Dst = append(aclRule.Dst, *cidr)
+				} else {
+					aclRule.Dst6 = append(aclRule.Dst6, *cidr)
+				}
+
+			} else {
+				aclRule.Dst = append(aclRule.Dst, net.IPNet{
+					IP:   net.IPv4zero,        // 0.0.0.0
+					Mask: net.CIDRMask(0, 32), // /0 means match all IPv4
+				})
+				aclRule.Dst6 = append(aclRule.Dst6, net.IPNet{
+					IP:   net.IPv6zero,         // ::
+					Mask: net.CIDRMask(0, 128), // /0 means match all IPv6
+				})
+			}
+			if acl.AllowedDirection == models.TrafficDirectionBi {
+				var existsInSrcTag bool
+				var existsInDstTag bool
+
+				if _, ok := srcTags[nodeTag.String()]; ok || srcAll {
+					existsInSrcTag = true
+				}
+				if _, ok := dstTags[nodeTag.String()]; ok || dstAll {
+					existsInDstTag = true
+				}
+
+				if existsInSrcTag && !existsInDstTag {
+					// get all dst tags
+					for dst := range dstTags {
+						if dst == nodeTag.String() {
+							continue
+						}
+						// Get peers in the tags and add allowed rules
+						nodes := taggedNodes[models.TagID(dst)]
+						if dst != targetnode.ID.String() {
+							node, err := GetNodeByID(dst)
+							if err == nil {
+								nodes = append(nodes, node)
+							}
+						}
+
+						for _, node := range nodes {
+							if node.ID == targetnode.ID {
+								continue
+							}
+							if node.Address.IP != nil {
+								aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4())
+							}
+							if node.Address6.IP != nil {
+								aclRule.IP6List = append(aclRule.IP6List, node.AddressIPNet6())
+							}
+							if node.IsStatic && node.StaticNode.Address != "" {
+								aclRule.IPList = append(aclRule.IPList, node.StaticNode.AddressIPNet4())
+							}
+							if node.IsStatic && node.StaticNode.Address6 != "" {
+								aclRule.IP6List = append(aclRule.IP6List, node.StaticNode.AddressIPNet6())
+							}
+						}
+					}
+				}
+				if existsInDstTag && !existsInSrcTag {
+					// get all src tags
+					for src := range srcTags {
+						if src == nodeTag.String() {
+							continue
+						}
+						// Get peers in the tags and add allowed rules
+						nodes := taggedNodes[models.TagID(src)]
+						if src != targetnode.ID.String() {
+							node, err := GetNodeByID(src)
+							if err == nil {
+								nodes = append(nodes, node)
+							}
+						}
+						for _, node := range nodes {
+							if node.ID == targetnode.ID {
+								continue
+							}
+							if node.Address.IP != nil {
+								aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4())
+							}
+							if node.Address6.IP != nil {
+								aclRule.IP6List = append(aclRule.IP6List, node.AddressIPNet6())
+							}
+							if node.IsStatic && node.StaticNode.Address != "" {
+								aclRule.IPList = append(aclRule.IPList, node.StaticNode.AddressIPNet4())
+							}
+							if node.IsStatic && node.StaticNode.Address6 != "" {
+								aclRule.IP6List = append(aclRule.IP6List, node.StaticNode.AddressIPNet6())
+							}
+						}
+					}
+				}
+				if existsInDstTag && existsInSrcTag {
+					nodes := taggedNodes[nodeTag]
+					for srcID := range srcTags {
+						if srcID == targetnode.ID.String() {
+							continue
+						}
+						node, err := GetNodeByID(srcID)
+						if err == nil {
+							nodes = append(nodes, node)
+						}
+					}
+					for dstID := range dstTags {
+						if dstID == targetnode.ID.String() {
+							continue
+						}
+						node, err := GetNodeByID(dstID)
+						if err == nil {
+							nodes = append(nodes, node)
+						}
+					}
+					for _, node := range nodes {
+						if node.ID == targetnode.ID {
+							continue
+						}
+						if node.Address.IP != nil {
+							aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4())
+						}
+						if node.Address6.IP != nil {
+							aclRule.IP6List = append(aclRule.IP6List, node.AddressIPNet6())
+						}
+						if node.IsStatic && node.StaticNode.Address != "" {
+							aclRule.IPList = append(aclRule.IPList, node.StaticNode.AddressIPNet4())
+						}
+						if node.IsStatic && node.StaticNode.Address6 != "" {
+							aclRule.IP6List = append(aclRule.IP6List, node.StaticNode.AddressIPNet6())
+						}
+					}
+				}
+			} else {
+				_, all := dstTags["*"]
+				if _, ok := dstTags[nodeTag.String()]; ok || all {
+					// get all src tags
+					for src := range srcTags {
+						if src == nodeTag.String() {
+							continue
+						}
+						// Get peers in the tags and add allowed rules
+						nodes := taggedNodes[models.TagID(src)]
+						for _, node := range nodes {
+							if node.ID == targetnode.ID {
+								continue
+							}
+							if node.Address.IP != nil {
+								aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4())
+							}
+							if node.Address6.IP != nil {
+								aclRule.IP6List = append(aclRule.IP6List, node.AddressIPNet6())
+							}
+							if node.IsStatic && node.StaticNode.Address != "" {
+								aclRule.IPList = append(aclRule.IPList, node.StaticNode.AddressIPNet4())
+							}
+							if node.IsStatic && node.StaticNode.Address6 != "" {
+								aclRule.IP6List = append(aclRule.IP6List, node.StaticNode.AddressIPNet6())
+							}
+						}
+					}
+				}
+			}
+			if len(aclRule.IPList) > 0 || len(aclRule.IP6List) > 0 {
+				aclRule.IPList = UniqueIPNetList(aclRule.IPList)
+				aclRule.IP6List = UniqueIPNetList(aclRule.IP6List)
+				rules[acl.ID] = aclRule
+			}
+
+		}
+
+	}
+	return
+}

+ 120 - 112
logic/extpeers.go

@@ -591,132 +591,130 @@ func getFwRulesForNodeAndPeerOnGw(node, peer models.Node, allowedPolicies []mode
 			}
 
 		}
+
+		// add egress range rules
+		for _, dstI := range policy.Dst {
+			if dstI.ID == models.EgressRange {
+				ip, cidr, err := net.ParseCIDR(dstI.Value)
+				if err == nil {
+					if ip.To4() != nil {
+						if node.Address.IP != nil {
+							rules = append(rules, models.FwRule{
+								SrcIP: net.IPNet{
+									IP:   node.Address.IP,
+									Mask: net.CIDRMask(32, 32),
+								},
+								DstIP:           *cidr,
+								AllowedProtocol: policy.Proto,
+								AllowedPorts:    policy.Port,
+								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:           *cidr,
+								AllowedProtocol: policy.Proto,
+								AllowedPorts:    policy.Port,
+								Allow:           true,
+							})
+						}
+					}
+
+				}
+			}
+		}
 	}
 
 	return
 }
 
-func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
-	// fetch user access to static clients via policies
+func getFwRulesForUserNodesOnGw(node models.Node, nodes []models.Node) (rules []models.FwRule) {
 	defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
-	defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
-	nodes, _ := GetNetworkNodes(node.Network)
-	nodes = append(nodes, GetStaticNodesByNetwork(models.NetworkID(node.Network), true)...)
 	userNodes := GetStaticUserNodesByNetwork(models.NetworkID(node.Network))
 	for _, userNodeI := range userNodes {
 		for _, peer := range nodes {
 			if peer.IsUserNode {
 				continue
 			}
+
 			if ok, allowedPolicies := IsUserAllowedToCommunicate(userNodeI.StaticNode.OwnerID, peer); ok {
 				if peer.IsStatic {
-					if userNodeI.StaticNode.Address != "" {
-						if !defaultUserPolicy.Enabled {
-							for _, policy := range allowedPolicies {
-								rules = append(rules, models.FwRule{
-									SrcIP:           userNodeI.StaticNode.AddressIPNet4(),
-									DstIP:           peer.StaticNode.AddressIPNet4(),
-									AllowedProtocol: policy.Proto,
-									AllowedPorts:    policy.Port,
-									Allow:           true,
-								})
-								rules = append(rules, models.FwRule{
-									SrcIP:           peer.StaticNode.AddressIPNet4(),
-									DstIP:           userNodeI.StaticNode.AddressIPNet4(),
-									AllowedProtocol: policy.Proto,
-									AllowedPorts:    policy.Port,
-									Allow:           true,
-								})
-							}
+					peer = peer.StaticNode.ConvertToStaticNode()
+				}
+				if !defaultUserPolicy.Enabled {
+					for _, policy := range allowedPolicies {
+						if userNodeI.StaticNode.Address != "" {
+							rules = append(rules, models.FwRule{
+								SrcIP: userNodeI.StaticNode.AddressIPNet4(),
+								DstIP: net.IPNet{
+									IP:   peer.Address.IP,
+									Mask: net.CIDRMask(32, 32),
+								},
+								AllowedProtocol: policy.Proto,
+								AllowedPorts:    policy.Port,
+								Allow:           true,
+							})
 						}
-
-					}
-					if userNodeI.StaticNode.Address6 != "" {
-						if !defaultUserPolicy.Enabled {
-							for _, policy := range allowedPolicies {
-								rules = append(rules, models.FwRule{
-									SrcIP:           userNodeI.StaticNode.AddressIPNet6(),
-									DstIP:           peer.StaticNode.AddressIPNet6(),
-									Allow:           true,
-									AllowedProtocol: policy.Proto,
-									AllowedPorts:    policy.Port,
-								})
-								rules = append(rules, models.FwRule{
-									SrcIP:           peer.StaticNode.AddressIPNet6(),
-									DstIP:           userNodeI.StaticNode.AddressIPNet6(),
-									AllowedProtocol: policy.Proto,
-									AllowedPorts:    policy.Port,
-									Allow:           true,
-								})
-
-							}
+						if userNodeI.StaticNode.Address6 != "" {
+							rules = append(rules, models.FwRule{
+								SrcIP: userNodeI.StaticNode.AddressIPNet6(),
+								DstIP: net.IPNet{
+									IP:   peer.Address6.IP,
+									Mask: net.CIDRMask(128, 128),
+								},
+								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: userNodeI.StaticNode.AddressIPNet4(),
-									DstIP: *ipNet,
-									Allow: true,
-								})
-							} else {
-								rules = append(rules, models.FwRule{
-									SrcIP: userNodeI.StaticNode.AddressIPNet6(),
-									DstIP: *ipNet,
-									Allow: true,
-								})
+						// add egress ranges
+						for _, dstI := range policy.Dst {
+							if dstI.ID == models.EgressRange {
+								ip, cidr, err := net.ParseCIDR(dstI.Value)
+								if err == nil {
+									if ip.To4() != nil && userNodeI.StaticNode.Address != "" {
+										rules = append(rules, models.FwRule{
+											SrcIP:           userNodeI.StaticNode.AddressIPNet4(),
+											DstIP:           *cidr,
+											AllowedProtocol: policy.Proto,
+											AllowedPorts:    policy.Port,
+											Allow:           true,
+										})
+									} else if ip.To16() != nil && userNodeI.StaticNode.Address6 != "" {
+										rules = append(rules, models.FwRule{
+											SrcIP:           userNodeI.StaticNode.AddressIPNet6(),
+											DstIP:           *cidr,
+											AllowedProtocol: policy.Proto,
+											AllowedPorts:    policy.Port,
+											Allow:           true,
+										})
+									}
+								}
 							}
-
 						}
 
 					}
-				} else {
-
-					if userNodeI.StaticNode.Address != "" {
-						if !defaultUserPolicy.Enabled {
-							for _, policy := range allowedPolicies {
-								rules = append(rules, models.FwRule{
-									SrcIP: userNodeI.StaticNode.AddressIPNet4(),
-									DstIP: net.IPNet{
-										IP:   peer.Address.IP,
-										Mask: net.CIDRMask(32, 32),
-									},
-									AllowedProtocol: policy.Proto,
-									AllowedPorts:    policy.Port,
-									Allow:           true,
-								})
-							}
-
-						}
-					}
-
-					if userNodeI.StaticNode.Address6 != "" {
-						if !defaultUserPolicy.Enabled {
-							for _, policy := range allowedPolicies {
-								rules = append(rules, models.FwRule{
-									SrcIP: userNodeI.StaticNode.AddressIPNet6(),
-									DstIP: net.IPNet{
-										IP:   peer.Address6.IP,
-										Mask: net.CIDRMask(128, 128),
-									},
-									AllowedProtocol: policy.Proto,
-									AllowedPorts:    policy.Port,
-									Allow:           true,
-								})
-							}
-						}
-					}
 				}
+
 			}
 		}
 	}
+	return
+}
+
+func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
+	// fetch user access to static clients via policies
 
+	defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
+	nodes, _ := GetNetworkNodes(node.Network)
+	nodes = append(nodes, GetStaticNodesByNetwork(models.NetworkID(node.Network), true)...)
+	rules = getFwRulesForUserNodesOnGw(node, nodes)
 	if defaultDevicePolicy.Enabled {
 		return
 	}
@@ -739,17 +737,34 @@ func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
 			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)...)
+			var allowedPolicies1 []models.Acl
+			var ok bool
+			if ok, allowedPolicies1 = IsNodeAllowedToCommunicateV1(nodeI.StaticNode.ConvertToStaticNode(), peer, true); ok {
+				rules = append(rules, getFwRulesForNodeAndPeerOnGw(nodeI.StaticNode.ConvertToStaticNode(), peer, allowedPolicies1)...)
 			}
-			if ok, allowedPolicies := IsNodeAllowedToCommunicateV1(peer, nodeI.StaticNode.ConvertToStaticNode(), true); ok {
-				rules = append(rules, getFwRulesForNodeAndPeerOnGw(peer, nodeI.StaticNode.ConvertToStaticNode(), allowedPolicies)...)
+			if ok, allowedPolicies2 := IsNodeAllowedToCommunicateV1(peer, nodeI.StaticNode.ConvertToStaticNode(), true); ok {
+				rules = append(rules,
+					getFwRulesForNodeAndPeerOnGw(peer, nodeI.StaticNode.ConvertToStaticNode(),
+						GetUniquePolicies(allowedPolicies1, allowedPolicies2))...)
 			}
 		}
 	}
 	return
 }
 
+func GetUniquePolicies(policies1, policies2 []models.Acl) []models.Acl {
+	policies1Map := make(map[string]struct{})
+	for _, policy1I := range policies1 {
+		policies1Map[policy1I.ID] = struct{}{}
+	}
+	for i := len(policies2) - 1; i >= 0; i-- {
+		if _, ok := policies1Map[policies2[i].ID]; ok {
+			policies2 = append(policies2[:i], policies2[i+1:]...)
+		}
+	}
+	return policies2
+}
+
 func GetExtPeers(node, peer *models.Node) ([]wgtypes.PeerConfig, []models.IDandAddr, []models.EgressNetworkRoutes, error) {
 	var peers []wgtypes.PeerConfig
 	var idsAndAddr []models.IDandAddr
@@ -764,13 +779,11 @@ 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 {
@@ -934,16 +947,11 @@ func GetStaticUserNodesByNetwork(network models.NetworkID) (staticNode []models.
 	for _, extI := range extClients {
 		if extI.Network == network.String() {
 			if extI.RemoteAccessClientID != "" {
-				n := models.Node{
-					IsStatic:   true,
-					StaticNode: extI,
-					IsUserNode: extI.RemoteAccessClientID != "",
-				}
+				n := extI.ConvertToStaticNode()
 				staticNode = append(staticNode, n)
 			}
 		}
 	}
-
 	return
 }
 

+ 6 - 0
logic/nodes.go

@@ -866,6 +866,9 @@ func GetTagMapWithNodesByNetwork(netID models.NetworkID, withStaticNodes bool) (
 			nodeI.Mutex.Lock()
 		}
 		for nodeTagID := range nodeI.Tags {
+			if nodeTagID == models.TagID(nodeI.ID.String()) {
+				continue
+			}
 			tagNodesMap[nodeTagID] = append(tagNodesMap[nodeTagID], nodeI)
 		}
 		if nodeI.Mutex != nil {
@@ -903,6 +906,9 @@ func AddTagMapWithStaticNodes(netID models.NetworkID,
 			extclient.Mutex.Lock()
 		}
 		for tagID := range extclient.Tags {
+			if tagID == models.TagID(extclient.ClientID) {
+				continue
+			}
 			tagNodesMap[tagID] = append(tagNodesMap[tagID], extclient.ConvertToStaticNode())
 			tagNodesMap["*"] = append(tagNodesMap["*"], extclient.ConvertToStaticNode())
 		}

+ 16 - 57
logic/peers.go

@@ -181,6 +181,7 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 	slog.Debug("peer update for host", "hostId", host.ID.String())
 	peerIndexMap := make(map[string]int)
 	for _, nodeID := range host.Nodes {
+		networkAllowAll := true
 		nodeID := nodeID
 		node, err := GetNodeByID(nodeID)
 		if err != nil {
@@ -190,60 +191,6 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 		if !node.Connected || node.PendingDelete || node.Action == models.NODE_DELETE {
 			continue
 		}
-		if host.OS == models.OS_Types.IoT {
-			hostPeerUpdate.NodeAddrs = append(hostPeerUpdate.NodeAddrs, node.PrimaryAddressIPNet())
-			if node.IsRelayed {
-				relayNode, err := GetNodeByID(node.RelayedBy)
-				if err != nil {
-					continue
-				}
-				relayHost, err := GetHost(relayNode.HostID.String())
-				if err != nil {
-					continue
-				}
-				relayPeer := wgtypes.PeerConfig{
-					PublicKey:                   relayHost.PublicKey,
-					PersistentKeepaliveInterval: &relayHost.PersistentKeepalive,
-					ReplaceAllowedIPs:           true,
-					AllowedIPs:                  GetAllowedIPs(&node, &relayNode, nil),
-				}
-				uselocal := false
-				if host.EndpointIP.String() == relayHost.EndpointIP.String() {
-					// peer is on same network
-					// set to localaddress
-					uselocal = true
-					if node.LocalAddress.IP == nil {
-						// use public endpint
-						uselocal = false
-					}
-					if node.LocalAddress.String() == relayNode.LocalAddress.String() {
-						uselocal = false
-					}
-				}
-				relayPeer.Endpoint = &net.UDPAddr{
-					IP:   relayHost.EndpointIP,
-					Port: GetPeerListenPort(relayHost),
-				}
-
-				if uselocal {
-					relayPeer.Endpoint.IP = relayNode.LocalAddress.IP
-					relayPeer.Endpoint.Port = relayHost.ListenPort
-				}
-
-				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, relayPeer)
-			} else if deletedNode != nil && deletedNode.IsRelay {
-				relayHost, err := GetHost(deletedNode.HostID.String())
-				if err != nil {
-					continue
-				}
-				relayPeer := wgtypes.PeerConfig{
-					PublicKey: relayHost.PublicKey,
-					Remove:    true,
-				}
-				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, relayPeer)
-			}
-			continue
-		}
 		hostPeerUpdate = SetDefaultGw(node, hostPeerUpdate)
 		if !hostPeerUpdate.IsInternetGw {
 			hostPeerUpdate.IsInternetGw = IsInternetGw(node)
@@ -259,6 +206,7 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 				hostPeerUpdate.FwUpdate.AllowedNetworks = append(hostPeerUpdate.FwUpdate.AllowedNetworks, node.NetworkRange6)
 			}
 		} else {
+			networkAllowAll = false
 			hostPeerUpdate.FwUpdate.AllowAll = false
 			rules := GetAclRulesForNode(&node)
 			if len(hostPeerUpdate.FwUpdate.AclRules) == 0 {
@@ -370,7 +318,6 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 			}
 
 			if uselocal {
-				peerConfig.Endpoint.IP = peer.LocalAddress.IP
 				peerConfig.Endpoint.Port = peerHost.ListenPort
 			}
 			var allowedToComm bool
@@ -438,7 +385,6 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 						IngressID:     node.ID.String(),
 						Network:       node.NetworkRange,
 						Network6:      node.NetworkRange6,
-						AllowAll:      defaultDevicePolicy.Enabled && defaultUserPolicy.Default,
 						StaticNodeIps: GetStaticNodeIps(node),
 						Rules:         GetFwRulesOnIngressGateway(node),
 					}
@@ -472,10 +418,23 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 					IP:   node.Address6.IP,
 					Mask: getCIDRMaskFromAddr(node.Address6.IP.String()),
 				},
-				EgressGWCfg: node.EgressGatewayRequest,
+				EgressGWCfg:   node.EgressGatewayRequest,
+				EgressFwRules: make(map[string]models.AclRule),
+			}
+
+		}
+		if node.IsEgressGateway {
+			if !networkAllowAll {
+				egressInfo := hostPeerUpdate.FwUpdate.EgressInfo[node.ID.String()]
+				if egressInfo.EgressFwRules == nil {
+					egressInfo.EgressFwRules = make(map[string]models.AclRule)
+				}
+				egressInfo.EgressFwRules = GetEgressRulesForNode(node)
+				hostPeerUpdate.FwUpdate.EgressInfo[node.ID.String()] = egressInfo
 			}
 
 		}
+
 		if IsInternetGw(node) {
 			hostPeerUpdate.FwUpdate.IsEgressGw = true
 			egressrange := []string{"0.0.0.0/0"}

+ 2 - 2
logic/tags.go

@@ -278,7 +278,7 @@ func CheckIDSyntax(id string) error {
 }
 
 func CreateDefaultTags(netID models.NetworkID) {
-	// create tag for remote access gws in the network
+	// create tag for gws in the network
 	tag := models.Tag{
 		ID:        models.TagID(fmt.Sprintf("%s.%s", netID.String(), models.GwTagName)),
 		TagName:   models.GwTagName,
@@ -292,7 +292,7 @@ func CreateDefaultTags(netID models.NetworkID) {
 	}
 	err = InsertTag(tag)
 	if err != nil {
-		slog.Error("failed to create remote access gw tag", "error", err.Error())
+		slog.Error("failed to create gw tag", "error", err.Error())
 		return
 	}
 }

+ 2 - 2
migrate/migrate.go

@@ -468,14 +468,14 @@ func migrateToGws() {
 		upsert := false
 		for i, srcI := range acl.Src {
 			if srcI.ID == models.NodeTagID && srcI.Value == fmt.Sprintf("%s.%s", acl.NetworkID.String(), models.OldRemoteAccessTagName) {
-				srcI.Value = models.GwTagName
+				srcI.Value = fmt.Sprintf("%s.%s", acl.NetworkID.String(), models.GwTagName)
 				acl.Src[i] = srcI
 				upsert = true
 			}
 		}
 		for i, dstI := range acl.Dst {
 			if dstI.ID == models.NodeTagID && dstI.Value == fmt.Sprintf("%s.%s", acl.NetworkID.String(), models.OldRemoteAccessTagName) {
-				dstI.Value = models.GwTagName
+				dstI.Value = fmt.Sprintf("%s.%s", acl.NetworkID.String(), models.GwTagName)
 				acl.Dst[i] = dstI
 				upsert = true
 			}

+ 2 - 0
models/acl.go

@@ -117,5 +117,7 @@ type AclRule struct {
 	AllowedProtocol Protocol                `json:"allowed_protocols"` // tcp, udp, etc.
 	AllowedPorts    []string                `json:"allowed_ports"`
 	Direction       AllowedTrafficDirection `json:"direction"` // single or two-way
+	Dst             []net.IPNet             `json:"dst"`
+	Dst6            []net.IPNet             `json:"dst6"`
 	Allowed         bool
 }

+ 1 - 0
models/extclient.go

@@ -60,6 +60,7 @@ func (ext *ExtClient) ConvertToStaticNode() Node {
 		Tags:       ext.Tags,
 		IsStatic:   true,
 		StaticNode: *ext,
+		IsUserNode: ext.RemoteAccessClientID != "",
 		Mutex:      ext.Mutex,
 	}
 }

+ 1 - 1
models/mqtt.go

@@ -53,7 +53,6 @@ type IngressInfo struct {
 	Network6      net.IPNet   `json:"network6"`
 	StaticNodeIps []net.IP    `json:"static_node_ips"`
 	Rules         []FwRule    `json:"rules"`
-	AllowAll      bool        `json:"allow_all"`
 	EgressRanges  []net.IPNet `json:"egress_ranges"`
 	EgressRanges6 []net.IPNet `json:"egress_ranges6"`
 }
@@ -66,6 +65,7 @@ type EgressInfo struct {
 	Network6      net.IPNet            `json:"network6" yaml:"network6"`
 	EgressGwAddr6 net.IPNet            `json:"egress_gw_addr6" yaml:"egress_gw_addr6"`
 	EgressGWCfg   EgressGatewayRequest `json:"egress_gateway_cfg" yaml:"egress_gateway_cfg"`
+	EgressFwRules map[string]AclRule   `json:"egress_fw_rules"`
 }
 
 // EgressNetworkRoutes - struct for egress network routes for adding routes to peer's interface

+ 1 - 1
pro/auth/error.go

@@ -98,7 +98,7 @@ var oauthNotConfigured = fmt.Sprintf(htmlBaseTemplate, `<h2>Your Netmaker server
 var oauthStateInvalid = fmt.Sprintf(htmlBaseTemplate, `<h2>Invalid OAuth Session. Please re-try again.</h2>`)
 
 var userNotAllowed = fmt.Sprintf(htmlBaseTemplate, `<h2>Your account does not have access to the dashboard. Please contact your administrator for more information about your account.</h2>
-<p>Non-Admins can access the netmaker networks using <a href="https://docs.netmaker.io/docs/remote-access-client-rac#downloadinstallation" target="_blank" rel="noopener">our Remote Access Client.</a></p>`)
+<p>Non-Admins can access the netmaker networks using <a href="https://docs.netmaker.io/docs/remote-access-client-rac#downloadinstallation" target="_blank" rel="noopener">our Netmaker Desktop App.</a></p>`)
 
 var userFirstTimeSignUp = fmt.Sprintf(htmlBaseTemplate, `<h2>Thank you for signing up. Please contact your administrator for access.</h2>`)
 

+ 40 - 0
pro/controllers/users.go

@@ -8,7 +8,9 @@ import (
 	"net/http"
 	"net/url"
 	"strings"
+	"time"
 
+	"github.com/google/uuid"
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
@@ -411,6 +413,44 @@ func createUserGroup(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
+	networks, err := logic.GetNetworks()
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	for _, network := range networks {
+		acl := models.Acl{
+			ID:          uuid.New().String(),
+			Name:        fmt.Sprintf("%s group", userGroupReq.Group.Name),
+			MetaData:    "This Policy allows user group to communicate with all gateways",
+			Default:     true,
+			ServiceType: models.Any,
+			NetworkID:   models.NetworkID(network.NetID),
+			Proto:       models.ALL,
+			RuleType:    models.UserPolicy,
+			Src: []models.AclPolicyTag{
+				{
+					ID:    models.UserGroupAclID,
+					Value: userGroupReq.Group.ID.String(),
+				},
+			},
+			Dst: []models.AclPolicyTag{
+				{
+					ID:    models.NodeTagID,
+					Value: fmt.Sprintf("%s.%s", models.NetworkID(network.NetID), models.GwTagName),
+				}},
+			AllowedDirection: models.TrafficDirectionUni,
+			Enabled:          true,
+			CreatedBy:        "auto",
+			CreatedAt:        time.Now().UTC(),
+		}
+		err = logic.InsertAcl(acl)
+		if err != nil {
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+			return
+		}
+	}
+
 	for _, userID := range userGroupReq.Members {
 		user, err := logic.GetUser(userID)
 		if err != nil {

+ 3 - 2
pro/email/invite.go

@@ -2,6 +2,7 @@ package email
 
 import (
 	"fmt"
+
 	"github.com/gravitl/netmaker/models"
 	proLogic "github.com/gravitl/netmaker/pro/logic"
 	"github.com/gravitl/netmaker/servercfg"
@@ -31,11 +32,11 @@ func (invite UserInvitedMail) GetBody(info Notification) string {
 
 	content := invite.BodyBuilder.
 		WithParagraph("Hi,").
-		WithParagraph("You've been invited to access a secure network via Netmaker's Remote Access Client (RAC). Follow these simple steps to get connected:").
+		WithParagraph("You've been invited to access a secure network via Netmaker Desktop App. Follow these simple steps to get connected:").
 		WithHtml("<ol>").
 		WithHtml(fmt.Sprintf("<li>Click <a href=\"%s\">here</a> to accept your invitation and setup your account.</li>", invite.InviteURL)).
 		WithHtml("<br>").
-		WithHtml(fmt.Sprintf("<li><a href=\"%s\">Download the Remote Access Client (RAC)</a>.</li>", downloadLink))
+		WithHtml(fmt.Sprintf("<li><a href=\"%s\">Download the Netmaker Desktop App</a>.</li>", downloadLink))
 
 	if invite.PlatformRoleID == models.AdminRole.String() || invite.PlatformRoleID == models.PlatformUser.String() {
 		content = content.

+ 7 - 87
pro/logic/user_mgmt.go

@@ -40,7 +40,7 @@ var NetworkAdminAllPermissionTemplate = models.UserRolePermissionTemplate{
 var NetworkUserAllPermissionTemplate = models.UserRolePermissionTemplate{
 	ID:         models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkUser)),
 	Name:       "Network Users",
-	MetaData:   "Can connect to nodes in your networks via Remote Access Client.",
+	MetaData:   "Can connect to nodes in your networks via Netmaker Desktop App.",
 	Default:    true,
 	FullAccess: false,
 	NetworkID:  models.AllNetworks,
@@ -131,7 +131,7 @@ func UserGroupsInit() {
 				models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkUser)): {},
 			},
 		},
-		MetaData: "Provides read-only dashboard access to platform users and allows connection to network nodes via the Remote Access Client.",
+		MetaData: "Provides read-only dashboard access to platform users and allows connection to network nodes via the Netmaker Desktop App.",
 	}
 	d, _ := json.Marshal(NetworkGlobalAdminGroup)
 	database.Insert(NetworkGlobalAdminGroup.ID.String(), string(d), database.USER_GROUPS_TABLE_NAME)
@@ -156,7 +156,7 @@ func CreateDefaultNetworkRolesAndGroups(netID models.NetworkID) {
 	var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{
 		ID:                  models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkUser)),
 		Name:                fmt.Sprintf("%s User", netID),
-		MetaData:            fmt.Sprintf("Can connect to nodes in your network `%s` via Remote Access Client.", netID),
+		MetaData:            fmt.Sprintf("Can connect to nodes in your network `%s` via Netmaker Desktop App.", netID),
 		Default:             true,
 		FullAccess:          false,
 		NetworkID:           netID,
@@ -235,7 +235,7 @@ func CreateDefaultNetworkRolesAndGroups(netID models.NetworkID) {
 				models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkUser)): {},
 			},
 		},
-		MetaData: fmt.Sprintf("Can connect to nodes in your network `%s` via Remote Access Client. Platform users will have read-only access to the the dashboard.", netID),
+		MetaData: fmt.Sprintf("Can connect to nodes in your network `%s` via Netmaker Desktop App. Platform users will have read-only access to the the dashboard.", netID),
 	}
 	d, _ = json.Marshal(NetworkAdminGroup)
 	database.Insert(NetworkAdminGroup.ID.String(), string(d), database.USER_GROUPS_TABLE_NAME)
@@ -810,87 +810,7 @@ func GetUserNetworkRolesWithRemoteVPNAccess(user models.User) (gwAccess map[mode
 }
 
 func GetFilteredNodesByUserAccess(user models.User, nodes []models.Node) (filteredNodes []models.Node) {
-
-	nodesMap := make(map[string]struct{})
-	allNetworkRoles := make(map[models.UserRoleID]struct{})
-	defer func() {
-		filteredNodes = logic.AddStaticNodestoList(filteredNodes)
-	}()
-	if len(user.NetworkRoles) > 0 {
-		for _, netRoles := range user.NetworkRoles {
-			for netRoleI := range netRoles {
-				allNetworkRoles[netRoleI] = struct{}{}
-			}
-		}
-	}
-	if _, ok := user.NetworkRoles[models.AllNetworks]; ok {
-		filteredNodes = nodes
-		return
-	}
-	if len(user.UserGroups) > 0 {
-		for userGID := range user.UserGroups {
-			userG, err := GetUserGroup(userGID)
-			if err == nil {
-				if len(userG.NetworkRoles) > 0 {
-					if _, ok := userG.NetworkRoles[models.AllNetworks]; ok {
-						filteredNodes = nodes
-						return
-					}
-					for _, netRoles := range userG.NetworkRoles {
-						for netRoleI := range netRoles {
-							allNetworkRoles[netRoleI] = struct{}{}
-						}
-					}
-				}
-			}
-		}
-	}
-	for networkRoleID := range allNetworkRoles {
-		userPermTemplate, err := logic.GetRole(networkRoleID)
-		if err != nil {
-			continue
-		}
-		networkNodes := logic.GetNetworkNodesMemory(nodes, userPermTemplate.NetworkID.String())
-		if userPermTemplate.FullAccess {
-			for _, node := range networkNodes {
-				if _, ok := nodesMap[node.ID.String()]; ok {
-					continue
-				}
-				nodesMap[node.ID.String()] = struct{}{}
-				filteredNodes = append(filteredNodes, node)
-			}
-
-			continue
-		}
-		if rsrcPerms, ok := userPermTemplate.NetworkLevelAccess[models.RemoteAccessGwRsrc]; ok {
-			if _, ok := rsrcPerms[models.AllRemoteAccessGwRsrcID]; ok {
-				for _, node := range networkNodes {
-					if _, ok := nodesMap[node.ID.String()]; ok {
-						continue
-					}
-					if node.IsIngressGateway {
-						nodesMap[node.ID.String()] = struct{}{}
-						filteredNodes = append(filteredNodes, node)
-					}
-				}
-			} else {
-				for gwID, scope := range rsrcPerms {
-					if _, ok := nodesMap[gwID.String()]; ok {
-						continue
-					}
-					if scope.Read {
-						gwNode, err := logic.GetNodeByID(gwID.String())
-						if err == nil && gwNode.IsIngressGateway {
-							nodesMap[gwNode.ID.String()] = struct{}{}
-							filteredNodes = append(filteredNodes, gwNode)
-						}
-					}
-				}
-			}
-		}
-
-	}
-	return
+	return filteredNodes
 }
 
 func FilterNetworksByRole(allnetworks []models.Network, user models.User) []models.Network {
@@ -1211,7 +1131,7 @@ func CreateDefaultUserPolicies(netID models.NetworkID) {
 		defaultUserAcl := models.Acl{
 			ID:          fmt.Sprintf("%s.%s-grp", netID, models.NetworkAdmin),
 			Name:        "Network Admin",
-			MetaData:    "This Policy allows all network admins to communicate with all remote access gateways",
+			MetaData:    "This Policy allows all network admins to communicate with all gateways",
 			Default:     true,
 			ServiceType: models.Any,
 			NetworkID:   netID,
@@ -1244,7 +1164,7 @@ func CreateDefaultUserPolicies(netID models.NetworkID) {
 		defaultUserAcl := models.Acl{
 			ID:          fmt.Sprintf("%s.%s-grp", netID, models.NetworkUser),
 			Name:        "Network User",
-			MetaData:    "This Policy allows all network users to communicate with all remote access gateways",
+			MetaData:    "This Policy allows all network users to communicate with all gateways",
 			Default:     true,
 			ServiceType: models.Any,
 			NetworkID:   netID,