2
0
abhishek9686 4 сар өмнө
parent
commit
ffb312189c

+ 1 - 0
.github/ISSUE_TEMPLATE/bug-report.yml

@@ -31,6 +31,7 @@ body:
       label: Version
       label: Version
       description: What version are you running?
       description: What version are you running?
       options:
       options:
+        - v0.90.0
         - v0.30.0
         - v0.30.0
         - v0.26.0
         - v0.26.0
         - v0.25.0
         - v0.25.0

+ 1 - 1
README.md

@@ -16,7 +16,7 @@
 
 
 <p align="center">
 <p align="center">
   <a href="https://github.com/gravitl/netmaker/releases">
   <a href="https://github.com/gravitl/netmaker/releases">
-    <img src="https://img.shields.io/badge/Version-0.30.0-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.90.0-informational?style=flat-square" />
   </a>
   </a>
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />

+ 1 - 1
compose/docker-compose.netclient.yml

@@ -3,7 +3,7 @@ version: "3.4"
 services:
 services:
   netclient:
   netclient:
     container_name: netclient
     container_name: netclient
-    image: 'gravitl/netclient:v0.30.0'
+    image: 'gravitl/netclient:v0.90.0'
     hostname: netmaker-1
     hostname: netmaker-1
     network_mode: host
     network_mode: host
     restart: on-failure
     restart: on-failure

+ 1 - 0
config/config.go

@@ -91,6 +91,7 @@ type ServerConfig struct {
 	Environment                string        `yaml:"environment"`
 	Environment                string        `yaml:"environment"`
 	JwtValidityDuration        time.Duration `yaml:"jwt_validity_duration" swaggertype:"primitive,integer" format:"int64"`
 	JwtValidityDuration        time.Duration `yaml:"jwt_validity_duration" swaggertype:"primitive,integer" format:"int64"`
 	RacAutoDisable             bool          `yaml:"rac_auto_disable"`
 	RacAutoDisable             bool          `yaml:"rac_auto_disable"`
+	RacRestrictToSingleNetwork bool          `yaml:"rac_restrict_to_single_network"`
 	CacheEnabled               string        `yaml:"caching_enabled"`
 	CacheEnabled               string        `yaml:"caching_enabled"`
 	EndpointDetection          bool          `yaml:"endpoint_detection"`
 	EndpointDetection          bool          `yaml:"endpoint_detection"`
 	AllowedEmailDomains        string        `yaml:"allowed_email_domains"`
 	AllowedEmailDomains        string        `yaml:"allowed_email_domains"`

+ 49 - 8
controllers/acls.go

@@ -45,10 +45,13 @@ func aclPolicyTypes(w http.ResponseWriter, r *http.Request) {
 		SrcGroupTypes: []models.AclGroupType{
 		SrcGroupTypes: []models.AclGroupType{
 			models.UserAclID,
 			models.UserAclID,
 			models.UserGroupAclID,
 			models.UserGroupAclID,
-			models.DeviceAclID,
+			models.NodeTagID,
+			models.NodeID,
 		},
 		},
 		DstGroupTypes: []models.AclGroupType{
 		DstGroupTypes: []models.AclGroupType{
-			models.DeviceAclID,
+			models.NodeTagID,
+			models.NodeID,
+			models.EgressRange,
 			// models.NetmakerIPAclID,
 			// models.NetmakerIPAclID,
 			// models.NetmakerSubNetRangeAClID,
 			// models.NetmakerSubNetRangeAClID,
 		},
 		},
@@ -117,6 +120,13 @@ func aclPolicyTypes(w http.ResponseWriter, r *http.Request) {
 				},
 				},
 				PortRange: "",
 				PortRange: "",
 			},
 			},
+			{
+				Name: models.SSH,
+				AllowedProtocols: []models.Protocol{
+					models.TCP,
+				},
+				PortRange: "22",
+			},
 			{
 			{
 				Name: models.Custom,
 				Name: models.Custom,
 				AllowedProtocols: []models.Protocol{
 				AllowedProtocols: []models.Protocol{
@@ -134,18 +144,49 @@ func aclPolicyTypes(w http.ResponseWriter, r *http.Request) {
 func aclDebug(w http.ResponseWriter, r *http.Request) {
 func aclDebug(w http.ResponseWriter, r *http.Request) {
 	nodeID, _ := url.QueryUnescape(r.URL.Query().Get("node"))
 	nodeID, _ := url.QueryUnescape(r.URL.Query().Get("node"))
 	peerID, _ := url.QueryUnescape(r.URL.Query().Get("peer"))
 	peerID, _ := url.QueryUnescape(r.URL.Query().Get("peer"))
+	peerIsStatic, _ := url.QueryUnescape(r.URL.Query().Get("peer_is_static"))
 	node, err := logic.GetNodeByID(nodeID)
 	node, err := logic.GetNodeByID(nodeID)
 	if err != nil {
 	if err != nil {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		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
 // @Summary     List Acls in a network

+ 39 - 5
controllers/ext_client.go

@@ -288,6 +288,16 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
 	} else if gwnode.IngressDNS != "" {
 	} else if gwnode.IngressDNS != "" {
 		defaultDNS = "DNS = " + gwnode.IngressDNS
 		defaultDNS = "DNS = " + gwnode.IngressDNS
 	}
 	}
+	if client.DNS == "" {
+		if len(network.NameServers) > 0 {
+			if defaultDNS == "" {
+				defaultDNS = "DNS = " + strings.Join(network.NameServers, ",")
+			} else {
+				defaultDNS += "," + strings.Join(network.NameServers, ",")
+			}
+
+		}
+	}
 	// if servercfg.GetManageDNS() {
 	// if servercfg.GetManageDNS() {
 	// 	if gwnode.Address6.IP != nil {
 	// 	if gwnode.Address6.IP != nil {
 	// 		if defaultDNS == "" {
 	// 		if defaultDNS == "" {
@@ -475,7 +485,18 @@ func getExtClientHAConf(w http.ResponseWriter, r *http.Request) {
 	// 	models.RemoteAccessTagName))] = struct{}{}
 	// 	models.RemoteAccessTagName))] = struct{}{}
 	// set extclient dns to ingressdns if extclient dns is not explicitly set
 	// set extclient dns to ingressdns if extclient dns is not explicitly set
 	if (extclient.DNS == "") && (gwnode.IngressDNS != "") {
 	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)
 	listenPort := logic.GetPeerListenPort(host)
@@ -720,7 +741,17 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 	// 	models.RemoteAccessTagName))] = struct{}{}
 	// 	models.RemoteAccessTagName))] = struct{}{}
 	// set extclient dns to ingressdns if extclient dns is not explicitly set
 	// set extclient dns to ingressdns if extclient dns is not explicitly set
 	if (extclient.DNS == "") && (node.IngressDNS != "") {
 	if (extclient.DNS == "") && (node.IngressDNS != "") {
-		extclient.DNS = node.IngressDNS
+		network, _ := logic.GetNetwork(node.Network)
+		dns := node.IngressDNS
+		if len(network.NameServers) > 0 {
+			if dns == "" {
+				dns = strings.Join(network.NameServers, ",")
+			} else {
+				dns += "," + strings.Join(network.NameServers, ",")
+			}
+
+		}
+		extclient.DNS = dns
 	}
 	}
 	host, err := logic.GetHost(node.HostID.String())
 	host, err := logic.GetHost(node.HostID.String())
 	if err != nil {
 	if err != nil {
@@ -1043,10 +1074,13 @@ func validateCustomExtClient(customExtClient *models.CustomExtClient, checkID bo
 	}
 	}
 	//validate DNS
 	//validate DNS
 	if customExtClient.DNS != "" {
 	if customExtClient.DNS != "" {
-		if ip := net.ParseIP(customExtClient.DNS); ip == nil {
-			return errInvalidExtClientDNS
+		ips := strings.Split(customExtClient.DNS, ",")
+		for _, ip := range ips {
+			trimmedIp := strings.TrimSpace(ip)
+			if ip := net.ParseIP(trimmedIp); ip == nil {
+				return errInvalidExtClientDNS
+			}
 		}
 		}
-		//extclient.DNS = customExtClient.DNS
 	}
 	}
 	return nil
 	return nil
 }
 }

+ 5 - 53
controllers/hosts.go

@@ -140,60 +140,12 @@ func upgradeHost(w http.ResponseWriter, r *http.Request) {
 // @Failure     500 {object} models.ErrorResponse
 // @Failure     500 {object} models.ErrorResponse
 func getHosts(w http.ResponseWriter, r *http.Request) {
 func getHosts(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	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[:])
 	apiHosts := logic.GetAllHostsAPI(currentHosts[:])

+ 34 - 1
controllers/network.go

@@ -41,6 +41,7 @@ func networkHandlers(r *mux.Router) {
 		Methods(http.MethodPut)
 		Methods(http.MethodPut)
 	r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(getNetworkACL))).
 	r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(getNetworkACL))).
 		Methods(http.MethodGet)
 		Methods(http.MethodGet)
+	r.HandleFunc("/api/networks/{networkname}/egress_routes", logic.SecurityCheck(true, http.HandlerFunc(getNetworkEgressRoutes)))
 }
 }
 
 
 // @Summary     Lists all networks
 // @Summary     Lists all networks
@@ -429,6 +430,33 @@ func getNetworkACL(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(networkACL)
 	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} models.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
 // @Summary     Delete a network
 // @Router      /api/networks/{networkname} [delete]
 // @Router      /api/networks/{networkname} [delete]
 // @Tags        Networks
 // @Tags        Networks
@@ -466,7 +494,8 @@ func deleteNetwork(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	go logic.UnlinkNetworkAndTagsFromEnrollmentKeys(network, true)
 	go logic.UnlinkNetworkAndTagsFromEnrollmentKeys(network, true)
 	go logic.DeleteNetworkRoles(network)
 	go logic.DeleteNetworkRoles(network)
-	go logic.DeleteDefaultNetworkPolicies(models.NetworkID(network))
+	go logic.DeleteAllNetworkTags(models.NetworkID(network))
+	go logic.DeleteNetworkPolicies(models.NetworkID(network))
 	//delete network from allocated ip map
 	//delete network from allocated ip map
 	go logic.RemoveNetworkFromAllocatedIpMap(network)
 	go logic.RemoveNetworkFromAllocatedIpMap(network)
 	go func() {
 	go func() {
@@ -598,6 +627,10 @@ func createNetwork(w http.ResponseWriter, r *http.Request) {
 			logic.CreateFailOver(*newNode)
 			logic.CreateFailOver(*newNode)
 			// make host remote access gateway
 			// make host remote access gateway
 			logic.CreateIngressGateway(network.NetID, newNode.ID.String(), models.IngressRequest{})
 			logic.CreateIngressGateway(network.NetID, newNode.ID.String(), models.IngressRequest{})
+			logic.CreateRelay(models.RelayRequest{
+				NodeID: newNode.ID.String(),
+				NetID:  network.NetID,
+			})
 		}
 		}
 		// send peer updates
 		// send peer updates
 		if err = mq.PublishPeerUpdate(false); err != nil {
 		if err = mq.PublishPeerUpdate(false); err != nil {

+ 2 - 0
controllers/node.go

@@ -678,6 +678,8 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 		logic.UpdateRelayed(&currentNode, newNode)
 		logic.UpdateRelayed(&currentNode, newNode)
 	}
 	}
 
 
+	logic.GetNodeStatus(newNode, false)
+
 	apiNode := newNode.ConvertToAPINode()
 	apiNode := newNode.ConvertToAPINode()
 	logger.Log(
 	logger.Log(
 		1,
 		1,

+ 2 - 2
controllers/server.go

@@ -82,11 +82,11 @@ func getUsage(w http.ResponseWriter, _ *http.Request) {
 		FailOvers        int `json:"fail_overs"`
 		FailOvers        int `json:"fail_overs"`
 	}
 	}
 	var serverUsage usage
 	var serverUsage usage
-	hosts, err := logic.GetAllHosts()
+	hosts, err := logic.GetAllHostsWithStatus(models.OnlineSt)
 	if err == nil {
 	if err == nil {
 		serverUsage.Hosts = len(hosts)
 		serverUsage.Hosts = len(hosts)
 	}
 	}
-	clients, err := logic.GetAllExtClients()
+	clients, err := logic.GetAllExtClientsWithStatus(models.OnlineSt)
 	if err == nil {
 	if err == nil {
 		serverUsage.Clients = len(clients)
 		serverUsage.Clients = len(clients)
 	}
 	}

+ 9 - 0
controllers/tags.go

@@ -88,6 +88,7 @@ func createTag(w http.ResponseWriter, r *http.Request) {
 		TagName:   req.TagName,
 		TagName:   req.TagName,
 		Network:   req.Network,
 		Network:   req.Network,
 		CreatedBy: user.UserName,
 		CreatedBy: user.UserName,
+		ColorCode: req.ColorCode,
 		CreatedAt: time.Now(),
 		CreatedAt: time.Now(),
 	}
 	}
 	_, err = logic.GetTag(tag.ID)
 	_, err = logic.GetTag(tag.ID)
@@ -182,6 +183,14 @@ func updateTag(w http.ResponseWriter, r *http.Request) {
 		// delete old Tag entry
 		// delete old Tag entry
 		logic.DeleteTag(updateTag.ID, false)
 		logic.DeleteTag(updateTag.ID, false)
 	}
 	}
+	if updateTag.ColorCode != "" && updateTag.ColorCode != tag.ColorCode {
+		tag.ColorCode = updateTag.ColorCode
+		err = logic.UpsertTag(tag)
+		if err != nil {
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+			return
+		}
+	}
 	go func() {
 	go func() {
 		logic.UpdateTag(updateTag, newID)
 		logic.UpdateTag(updateTag, newID)
 		if updateTag.NewName != "" {
 		if updateTag.NewName != "" {

+ 1 - 1
k8s/client/netclient-daemonset.yaml

@@ -16,7 +16,7 @@ spec:
       hostNetwork: true
       hostNetwork: true
       containers:
       containers:
       - name: netclient
       - name: netclient
-        image: gravitl/netclient:v0.30.0
+        image: gravitl/netclient:v0.90.0
         env:
         env:
         - name: TOKEN
         - name: TOKEN
           value: "TOKEN_VALUE"
           value: "TOKEN_VALUE"

+ 1 - 1
k8s/client/netclient.yaml

@@ -28,7 +28,7 @@ spec:
       #           - "<node label value>"
       #           - "<node label value>"
       containers:
       containers:
       - name: netclient
       - name: netclient
-        image: gravitl/netclient:v0.30.0
+        image: gravitl/netclient:v0.90.0
         env:
         env:
         - name: TOKEN
         - name: TOKEN
           value: "TOKEN_VALUE"
           value: "TOKEN_VALUE"

+ 1 - 1
k8s/server/netmaker-ui.yaml

@@ -15,7 +15,7 @@ spec:
     spec:
     spec:
       containers:
       containers:
       - name: netmaker-ui
       - name: netmaker-ui
-        image: gravitl/netmaker-ui:v0.30.0
+        image: gravitl/netmaker-ui:v0.90.0
         ports:
         ports:
         - containerPort: 443
         - containerPort: 443
         env:
         env:

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 547 - 172
logic/acls.go


+ 1 - 0
logic/auth.go

@@ -359,6 +359,7 @@ func DeleteUser(user string) (bool, error) {
 	if err != nil {
 	if err != nil {
 		return false, err
 		return false, err
 	}
 	}
+	go RemoveUserFromAclPolicy(user)
 
 
 	return true, nil
 	return true, nil
 }
 }

+ 300 - 216
logic/extpeers.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"fmt"
 	"net"
 	"net"
 	"reflect"
 	"reflect"
+	"sort"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 	"time"
 	"time"
@@ -115,6 +116,7 @@ func DeleteExtClient(network string, clientid string) error {
 		}
 		}
 		deleteExtClientFromCache(key)
 		deleteExtClientFromCache(key)
 	}
 	}
+	go RemoveNodeFromAclPolicy(extClient.ConvertToStaticNode())
 	return nil
 	return nil
 }
 }
 
 
@@ -416,6 +418,27 @@ func GetAllExtClients() ([]models.ExtClient, error) {
 	return clients, nil
 	return clients, nil
 }
 }
 
 
+// GetAllExtClientsWithStatus - returns all external clients with
+// given status.
+func GetAllExtClientsWithStatus(status models.NodeStatus) ([]models.ExtClient, error) {
+	extClients, err := GetAllExtClients()
+	if err != nil {
+		return nil, err
+	}
+
+	var validExtClients []models.ExtClient
+	for _, extClient := range extClients {
+		node := extClient.ConvertToStaticNode()
+		GetNodeCheckInStatus(&node, false)
+
+		if node.Status == status {
+			validExtClients = append(validExtClients, extClient)
+		}
+	}
+
+	return validExtClients, nil
+}
+
 // ToggleExtClientConnectivity - enables or disables an ext client
 // ToggleExtClientConnectivity - enables or disables an ext client
 func ToggleExtClientConnectivity(client *models.ExtClient, enable bool) (models.ExtClient, error) {
 func ToggleExtClientConnectivity(client *models.ExtClient, enable bool) (models.ExtClient, error) {
 	update := models.CustomExtClient{
 	update := models.CustomExtClient{
@@ -442,7 +465,18 @@ func ToggleExtClientConnectivity(client *models.ExtClient, enable bool) (models.
 	return newClient, nil
 	return newClient, nil
 }
 }
 
 
+// Sort a slice of net.IP addresses
+func sortIPs(ips []net.IP) {
+	sort.Slice(ips, func(i, j int) bool {
+		ip1, ip2 := ips[i].To16(), ips[j].To16()
+		return string(ip1) < string(ip2) // Compare as byte slices
+	})
+}
+
 func GetStaticNodeIps(node models.Node) (ips []net.IP) {
 func GetStaticNodeIps(node models.Node) (ips []net.IP) {
+	defer func() {
+		sortIPs(ips)
+	}()
 	defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
 	defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
 	defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
 	defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
 
 
@@ -464,210 +498,192 @@ func GetStaticNodeIps(node models.Node) (ips []net.IP) {
 	return
 	return
 }
 }
 
 
-func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
-	// fetch user access to static clients via policies
+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,
+			})
+		}
 
 
-	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 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 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,
-								})
-							}
-						}
-
-					}
-					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 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,
-								})
-							}
-
-						}
 
 
-					}
-				} 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 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 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,
-								})
-							}
-						}
-					}
+		}
+		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,
+					})
 				}
 				}
+
 			}
 			}
-		}
-	}
 
 
-	if defaultDevicePolicy.Enabled {
-		return
-	}
-	for _, nodeI := range nodes {
-		if !nodeI.IsStatic || nodeI.IsUserNode {
-			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 {
+
+		// 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{
 							rules = append(rules, models.FwRule{
-								SrcIP:           nodeI.StaticNode.AddressIPNet4(),
-								DstIP:           peer.StaticNode.AddressIPNet4(),
+								SrcIP: net.IPNet{
+									IP:   node.Address.IP,
+									Mask: net.CIDRMask(32, 32),
+								},
+								DstIP:           *cidr,
 								AllowedProtocol: policy.Proto,
 								AllowedProtocol: policy.Proto,
 								AllowedPorts:    policy.Port,
 								AllowedPorts:    policy.Port,
 								Allow:           true,
 								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 {
+					} else {
+						if node.Address6.IP != nil {
 							rules = append(rules, models.FwRule{
 							rules = append(rules, models.FwRule{
-								SrcIP:           nodeI.StaticNode.AddressIPNet6(),
-								DstIP:           peer.StaticNode.AddressIPNet6(),
+								SrcIP: net.IPNet{
+									IP:   node.Address6.IP,
+									Mask: net.CIDRMask(128, 128),
+								},
+								DstIP:           *cidr,
 								AllowedProtocol: policy.Proto,
 								AllowedProtocol: policy.Proto,
 								AllowedPorts:    policy.Port,
 								AllowedPorts:    policy.Port,
 								Allow:           true,
 								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 {
+	return
+}
+
+func getFwRulesForUserNodesOnGw(node models.Node, nodes []models.Node) (rules []models.FwRule) {
+	defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
+	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 {
+					peer = peer.StaticNode.ConvertToStaticNode()
+				}
+				if !defaultUserPolicy.Enabled {
+					for _, policy := range allowedPolicies {
+						if userNodeI.StaticNode.Address != "" {
 							rules = append(rules, models.FwRule{
 							rules = append(rules, models.FwRule{
-								SrcIP: nodeI.StaticNode.AddressIPNet4(),
+								SrcIP: userNodeI.StaticNode.AddressIPNet4(),
 								DstIP: net.IPNet{
 								DstIP: net.IPNet{
 									IP:   peer.Address.IP,
 									IP:   peer.Address.IP,
 									Mask: net.CIDRMask(32, 32),
 									Mask: net.CIDRMask(32, 32),
@@ -676,24 +692,10 @@ func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
 								AllowedPorts:    policy.Port,
 								AllowedPorts:    policy.Port,
 								Allow:           true,
 								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 {
+						if userNodeI.StaticNode.Address6 != "" {
 							rules = append(rules, models.FwRule{
 							rules = append(rules, models.FwRule{
-								SrcIP: nodeI.StaticNode.AddressIPNet6(),
+								SrcIP: userNodeI.StaticNode.AddressIPNet6(),
 								DstIP: net.IPNet{
 								DstIP: net.IPNet{
 									IP:   peer.Address6.IP,
 									IP:   peer.Address6.IP,
 									Mask: net.CIDRMask(128, 128),
 									Mask: net.CIDRMask(128, 128),
@@ -702,19 +704,34 @@ func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
 								AllowedPorts:    policy.Port,
 								AllowedPorts:    policy.Port,
 								Allow:           true,
 								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,
-								})
+						}
+
+						// 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,
+										})
+									}
+								}
 							}
 							}
 						}
 						}
+
 					}
 					}
 				}
 				}
 
 
@@ -724,6 +741,70 @@ func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
 	return
 	return
 }
 }
 
 
+func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
+	// fetch user access to static clients via policies
+	defer func() {
+		sort.Slice(rules, func(i, j int) bool {
+			if !rules[i].SrcIP.IP.Equal(rules[j].SrcIP.IP) {
+				return string(rules[i].SrcIP.IP.To16()) < string(rules[j].SrcIP.IP.To16())
+			}
+			return string(rules[i].DstIP.IP.To16()) < string(rules[j].DstIP.IP.To16())
+		})
+	}()
+	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
+	}
+	for _, nodeI := range nodes {
+		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 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()
+			}
+			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, 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) {
 func GetExtPeers(node, peer *models.Node) ([]wgtypes.PeerConfig, []models.IDandAddr, []models.EgressNetworkRoutes, error) {
 	var peers []wgtypes.PeerConfig
 	var peers []wgtypes.PeerConfig
 	var idsAndAddr []models.IDandAddr
 	var idsAndAddr []models.IDandAddr
@@ -812,13 +893,21 @@ func GetExtPeers(node, peer *models.Node) ([]wgtypes.PeerConfig, []models.IDandA
 }
 }
 
 
 func getExtPeerEgressRoute(node models.Node, extPeer models.ExtClient) (egressRoutes []models.EgressNetworkRoutes) {
 func getExtPeerEgressRoute(node models.Node, extPeer models.ExtClient) (egressRoutes []models.EgressNetworkRoutes) {
-	egressRoutes = append(egressRoutes, models.EgressNetworkRoutes{
+	r := models.EgressNetworkRoutes{
+		PeerKey:       extPeer.PublicKey,
 		EgressGwAddr:  extPeer.AddressIPNet4(),
 		EgressGwAddr:  extPeer.AddressIPNet4(),
 		EgressGwAddr6: extPeer.AddressIPNet6(),
 		EgressGwAddr6: extPeer.AddressIPNet6(),
 		NodeAddr:      node.Address,
 		NodeAddr:      node.Address,
 		NodeAddr6:     node.Address6,
 		NodeAddr6:     node.Address6,
 		EgressRanges:  extPeer.ExtraAllowedIPs,
 		EgressRanges:  extPeer.ExtraAllowedIPs,
-	})
+	}
+	for _, extraAllowedIP := range extPeer.ExtraAllowedIPs {
+		r.EgressRangesWithMetric = append(r.EgressRangesWithMetric, models.EgressRangeMetric{
+			Network:     extraAllowedIP,
+			RouteMetric: 256,
+		})
+	}
+	egressRoutes = append(egressRoutes, r)
 	return
 	return
 }
 }
 
 
@@ -831,7 +920,7 @@ func getExtpeerEgressRanges(node models.Node) (ranges, ranges6 []net.IPNet) {
 		if len(extPeer.ExtraAllowedIPs) == 0 {
 		if len(extPeer.ExtraAllowedIPs) == 0 {
 			continue
 			continue
 		}
 		}
-		if ok, _ := IsNodeAllowedToCommunicate(extPeer.ConvertToStaticNode(), node, true); !ok {
+		if ok, _ := IsNodeAllowedToCommunicateV1(extPeer.ConvertToStaticNode(), node, true); !ok {
 			continue
 			continue
 		}
 		}
 		for _, allowedRange := range extPeer.ExtraAllowedIPs {
 		for _, allowedRange := range extPeer.ExtraAllowedIPs {
@@ -858,7 +947,7 @@ func getExtpeersExtraRoutes(node models.Node) (egressRoutes []models.EgressNetwo
 		if len(extPeer.ExtraAllowedIPs) == 0 {
 		if len(extPeer.ExtraAllowedIPs) == 0 {
 			continue
 			continue
 		}
 		}
-		if ok, _ := IsNodeAllowedToCommunicate(extPeer.ConvertToStaticNode(), node, true); !ok {
+		if ok, _ := IsNodeAllowedToCommunicateV1(extPeer.ConvertToStaticNode(), node, true); !ok {
 			continue
 			continue
 		}
 		}
 		egressRoutes = append(egressRoutes, getExtPeerEgressRoute(node, extPeer)...)
 		egressRoutes = append(egressRoutes, getExtPeerEgressRoute(node, extPeer)...)
@@ -906,16 +995,11 @@ func GetStaticUserNodesByNetwork(network models.NetworkID) (staticNode []models.
 	for _, extI := range extClients {
 	for _, extI := range extClients {
 		if extI.Network == network.String() {
 		if extI.Network == network.String() {
 			if extI.RemoteAccessClientID != "" {
 			if extI.RemoteAccessClientID != "" {
-				n := models.Node{
-					IsStatic:   true,
-					StaticNode: extI,
-					IsUserNode: extI.RemoteAccessClientID != "",
-				}
+				n := extI.ConvertToStaticNode()
 				staticNode = append(staticNode, n)
 				staticNode = append(staticNode, n)
 			}
 			}
 		}
 		}
 	}
 	}
-
 	return
 	return
 }
 }
 
 

+ 2 - 2
logic/gateway.go

@@ -220,7 +220,7 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
 	if node.Tags == nil {
 	if node.Tags == nil {
 		node.Tags = make(map[models.TagID]struct{})
 		node.Tags = make(map[models.TagID]struct{})
 	}
 	}
-	node.Tags[models.TagID(fmt.Sprintf("%s.%s", netid, models.RemoteAccessTagName))] = struct{}{}
+	node.Tags[models.TagID(fmt.Sprintf("%s.%s", netid, models.GwTagName))] = struct{}{}
 	err = UpsertNode(&node)
 	err = UpsertNode(&node)
 	if err != nil {
 	if err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
@@ -272,7 +272,7 @@ func DeleteIngressGateway(nodeid string) (models.Node, []models.ExtClient, error
 	if !servercfg.IsPro {
 	if !servercfg.IsPro {
 		node.IsInternetGateway = false
 		node.IsInternetGateway = false
 	}
 	}
-	delete(node.Tags, models.TagID(fmt.Sprintf("%s.%s", node.Network, models.RemoteAccessTagName)))
+	delete(node.Tags, models.TagID(fmt.Sprintf("%s.%s", node.Network, models.GwTagName)))
 	node.IngressGatewayRange = ""
 	node.IngressGatewayRange = ""
 	node.Metadata = ""
 	node.Metadata = ""
 	err = UpsertNode(&node)
 	err = UpsertNode(&node)

+ 27 - 0
logic/hosts.go

@@ -106,6 +106,33 @@ func GetAllHosts() ([]models.Host, error) {
 	return currHosts, nil
 	return currHosts, nil
 }
 }
 
 
+// GetAllHostsWithStatus - returns all hosts with at least one
+// node with given status.
+func GetAllHostsWithStatus(status models.NodeStatus) ([]models.Host, error) {
+	hosts, err := GetAllHosts()
+	if err != nil {
+		return nil, err
+	}
+
+	var validHosts []models.Host
+	for _, host := range hosts {
+		if len(host.Nodes) == 0 {
+			continue
+		}
+
+		nodes := GetHostNodes(&host)
+		for _, node := range nodes {
+			GetNodeCheckInStatus(&node, false)
+			if node.Status == status {
+				validHosts = append(validHosts, host)
+				break
+			}
+		}
+	}
+
+	return validHosts, nil
+}
+
 // GetAllHostsAPI - get's all the hosts in an API usable format
 // GetAllHostsAPI - get's all the hosts in an API usable format
 func GetAllHostsAPI(hosts []models.Host) []models.ApiHost {
 func GetAllHostsAPI(hosts []models.Host) []models.ApiHost {
 	apiHosts := []models.ApiHost{}
 	apiHosts := []models.ApiHost{}

+ 29 - 3
logic/nodes.go

@@ -320,6 +320,7 @@ func DeleteNode(node *models.Node, purge bool) error {
 	if err := DissasociateNodeFromHost(node, host); err != nil {
 	if err := DissasociateNodeFromHost(node, host); err != nil {
 		return err
 		return err
 	}
 	}
+	go RemoveNodeFromAclPolicy(*node)
 
 
 	return nil
 	return nil
 }
 }
@@ -511,7 +512,7 @@ func SetNodeDefaults(node *models.Node, resetConnected bool) {
 	}
 	}
 
 
 	node.SetLastModified()
 	node.SetLastModified()
-	node.SetLastCheckIn()
+	//node.SetLastCheckIn()
 
 
 	if resetConnected {
 	if resetConnected {
 		node.SetDefaultConnected()
 		node.SetDefaultConnected()
@@ -700,7 +701,7 @@ func createNode(node *models.Node) error {
 		return err
 		return err
 	}
 	}
 	CheckZombies(node)
 	CheckZombies(node)
-
+	node.SetLastCheckIn()
 	nodebytes, err := json.Marshal(&node)
 	nodebytes, err := json.Marshal(&node)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -855,6 +856,9 @@ func GetTagMapWithNodesByNetwork(netID models.NetworkID, withStaticNodes bool) (
 	tagNodesMap = make(map[models.TagID][]models.Node)
 	tagNodesMap = make(map[models.TagID][]models.Node)
 	nodes, _ := GetNetworkNodes(netID.String())
 	nodes, _ := GetNetworkNodes(netID.String())
 	for _, nodeI := range nodes {
 	for _, nodeI := range nodes {
+		tagNodesMap[models.TagID(nodeI.ID.String())] = []models.Node{
+			nodeI,
+		}
 		if nodeI.Tags == nil {
 		if nodeI.Tags == nil {
 			continue
 			continue
 		}
 		}
@@ -862,6 +866,9 @@ func GetTagMapWithNodesByNetwork(netID models.NetworkID, withStaticNodes bool) (
 			nodeI.Mutex.Lock()
 			nodeI.Mutex.Lock()
 		}
 		}
 		for nodeTagID := range nodeI.Tags {
 		for nodeTagID := range nodeI.Tags {
+			if nodeTagID == models.TagID(nodeI.ID.String()) {
+				continue
+			}
 			tagNodesMap[nodeTagID] = append(tagNodesMap[nodeTagID], nodeI)
 			tagNodesMap[nodeTagID] = append(tagNodesMap[nodeTagID], nodeI)
 		}
 		}
 		if nodeI.Mutex != nil {
 		if nodeI.Mutex != nil {
@@ -882,13 +889,26 @@ func AddTagMapWithStaticNodes(netID models.NetworkID,
 		return tagNodesMap
 		return tagNodesMap
 	}
 	}
 	for _, extclient := range extclients {
 	for _, extclient := range extclients {
-		if extclient.Tags == nil || extclient.RemoteAccessClientID != "" {
+		if extclient.RemoteAccessClientID != "" {
 			continue
 			continue
 		}
 		}
+		tagNodesMap[models.TagID(extclient.ClientID)] = []models.Node{
+			{
+				IsStatic:   true,
+				StaticNode: extclient,
+			},
+		}
+		if extclient.Tags == nil {
+			continue
+		}
+
 		if extclient.Mutex != nil {
 		if extclient.Mutex != nil {
 			extclient.Mutex.Lock()
 			extclient.Mutex.Lock()
 		}
 		}
 		for tagID := range extclient.Tags {
 		for tagID := range extclient.Tags {
+			if tagID == models.TagID(extclient.ClientID) {
+				continue
+			}
 			tagNodesMap[tagID] = append(tagNodesMap[tagID], extclient.ConvertToStaticNode())
 			tagNodesMap[tagID] = append(tagNodesMap[tagID], extclient.ConvertToStaticNode())
 			tagNodesMap["*"] = append(tagNodesMap["*"], extclient.ConvertToStaticNode())
 			tagNodesMap["*"] = append(tagNodesMap["*"], extclient.ConvertToStaticNode())
 		}
 		}
@@ -906,6 +926,12 @@ func AddTagMapWithStaticNodesWithUsers(netID models.NetworkID,
 		return tagNodesMap
 		return tagNodesMap
 	}
 	}
 	for _, extclient := range extclients {
 	for _, extclient := range extclients {
+		tagNodesMap[models.TagID(extclient.ClientID)] = []models.Node{
+			{
+				IsStatic:   true,
+				StaticNode: extclient,
+			},
+		}
 		if extclient.Tags == nil {
 		if extclient.Tags == nil {
 			continue
 			continue
 		}
 		}

+ 99 - 62
logic/peers.go

@@ -175,12 +175,19 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 				}
 				}
 			}
 			}
 			hostPeerUpdate.FwUpdate.AclRules["allowed-network-rules"] = aclRule
 			hostPeerUpdate.FwUpdate.AclRules["allowed-network-rules"] = aclRule
+			hostPeerUpdate.FwUpdate.EgressInfo["allowed-network-rules"] = models.EgressInfo{
+				EgressID: "allowed-network-rules",
+				EgressFwRules: map[string]models.AclRule{
+					"allowed-network-rules": aclRule,
+				},
+			}
 		}
 		}
 	}()
 	}()
 
 
 	slog.Debug("peer update for host", "hostId", host.ID.String())
 	slog.Debug("peer update for host", "hostId", host.ID.String())
 	peerIndexMap := make(map[string]int)
 	peerIndexMap := make(map[string]int)
 	for _, nodeID := range host.Nodes {
 	for _, nodeID := range host.Nodes {
+		networkAllowAll := true
 		nodeID := nodeID
 		nodeID := nodeID
 		node, err := GetNodeByID(nodeID)
 		node, err := GetNodeByID(nodeID)
 		if err != nil {
 		if err != nil {
@@ -190,60 +197,6 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 		if !node.Connected || node.PendingDelete || node.Action == models.NODE_DELETE {
 		if !node.Connected || node.PendingDelete || node.Action == models.NODE_DELETE {
 			continue
 			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)
 		hostPeerUpdate = SetDefaultGw(node, hostPeerUpdate)
 		if !hostPeerUpdate.IsInternetGw {
 		if !hostPeerUpdate.IsInternetGw {
 			hostPeerUpdate.IsInternetGw = IsInternetGw(node)
 			hostPeerUpdate.IsInternetGw = IsInternetGw(node)
@@ -251,15 +204,15 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 		defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
 		defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
 		defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
 		defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
 
 
-		if defaultDevicePolicy.Enabled && defaultUserPolicy.Enabled {
+		if (defaultDevicePolicy.Enabled && defaultUserPolicy.Enabled) || (!checkIfAnyPolicyisUniDirectional(node) && !checkIfAnyActiveEgressPolicy(node)) {
 			if node.NetworkRange.IP != nil {
 			if node.NetworkRange.IP != nil {
 				hostPeerUpdate.FwUpdate.AllowedNetworks = append(hostPeerUpdate.FwUpdate.AllowedNetworks, node.NetworkRange)
 				hostPeerUpdate.FwUpdate.AllowedNetworks = append(hostPeerUpdate.FwUpdate.AllowedNetworks, node.NetworkRange)
 			}
 			}
 			if node.NetworkRange6.IP != nil {
 			if node.NetworkRange6.IP != nil {
 				hostPeerUpdate.FwUpdate.AllowedNetworks = append(hostPeerUpdate.FwUpdate.AllowedNetworks, node.NetworkRange6)
 				hostPeerUpdate.FwUpdate.AllowedNetworks = append(hostPeerUpdate.FwUpdate.AllowedNetworks, node.NetworkRange6)
 			}
 			}
-
 		} else {
 		} else {
+			networkAllowAll = false
 			hostPeerUpdate.FwUpdate.AllowAll = false
 			hostPeerUpdate.FwUpdate.AllowAll = false
 			rules := GetAclRulesForNode(&node)
 			rules := GetAclRulesForNode(&node)
 			if len(hostPeerUpdate.FwUpdate.AclRules) == 0 {
 			if len(hostPeerUpdate.FwUpdate.AclRules) == 0 {
@@ -294,20 +247,45 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 				PersistentKeepaliveInterval: &peerHost.PersistentKeepalive,
 				PersistentKeepaliveInterval: &peerHost.PersistentKeepalive,
 				ReplaceAllowedIPs:           true,
 				ReplaceAllowedIPs:           true,
 			}
 			}
+			_, isFailOverPeer := node.FailOverPeers[peer.ID.String()]
 			if peer.IsEgressGateway {
 			if peer.IsEgressGateway {
+				peerKey := peerHost.PublicKey.String()
+				if isFailOverPeer && peer.FailedOverBy.String() != node.ID.String() {
+					// get relay host
+					failOverNode, err := GetNodeByID(peer.FailedOverBy.String())
+					if err == nil {
+						relayHost, err := GetHost(failOverNode.HostID.String())
+						if err == nil {
+							peerKey = relayHost.PublicKey.String()
+						}
+					}
+				}
+				if peer.IsRelayed && (peer.RelayedBy != node.ID.String()) {
+					// get relay host
+					relayNode, err := GetNodeByID(peer.RelayedBy)
+					if err == nil {
+						relayHost, err := GetHost(relayNode.HostID.String())
+						if err == nil {
+							peerKey = relayHost.PublicKey.String()
+						}
+					}
+				}
+
 				hostPeerUpdate.EgressRoutes = append(hostPeerUpdate.EgressRoutes, models.EgressNetworkRoutes{
 				hostPeerUpdate.EgressRoutes = append(hostPeerUpdate.EgressRoutes, models.EgressNetworkRoutes{
+					PeerKey:                peerKey,
 					EgressGwAddr:           peer.Address,
 					EgressGwAddr:           peer.Address,
 					EgressGwAddr6:          peer.Address6,
 					EgressGwAddr6:          peer.Address6,
 					NodeAddr:               node.Address,
 					NodeAddr:               node.Address,
 					NodeAddr6:              node.Address6,
 					NodeAddr6:              node.Address6,
-					EgressRanges:           peer.EgressGatewayRanges,
-					EgressRangesWithMetric: peer.EgressGatewayRequest.RangesWithMetric,
+					EgressRanges:           filterConflictingEgressRoutes(node, peer),
+					EgressRangesWithMetric: filterConflictingEgressRoutesWithMetric(node, peer),
+					Network:                peer.Network,
 				})
 				})
 			}
 			}
 			if peer.IsIngressGateway {
 			if peer.IsIngressGateway {
 				hostPeerUpdate.EgressRoutes = append(hostPeerUpdate.EgressRoutes, getExtpeersExtraRoutes(node)...)
 				hostPeerUpdate.EgressRoutes = append(hostPeerUpdate.EgressRoutes, getExtpeersExtraRoutes(node)...)
 			}
 			}
-			_, isFailOverPeer := node.FailOverPeers[peer.ID.String()]
+
 			if (node.IsRelayed && node.RelayedBy != peer.ID.String()) ||
 			if (node.IsRelayed && node.RelayedBy != peer.ID.String()) ||
 				(peer.IsRelayed && peer.RelayedBy != node.ID.String()) || isFailOverPeer {
 				(peer.IsRelayed && peer.RelayedBy != node.ID.String()) || isFailOverPeer {
 				// if node is relayed and peer is not the relay, set remove to true
 				// if node is relayed and peer is not the relay, set remove to true
@@ -371,7 +349,6 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 			}
 			}
 
 
 			if uselocal {
 			if uselocal {
-				peerConfig.Endpoint.IP = peer.LocalAddress.IP
 				peerConfig.Endpoint.Port = peerHost.ListenPort
 				peerConfig.Endpoint.Port = peerHost.ListenPort
 			}
 			}
 			var allowedToComm bool
 			var allowedToComm bool
@@ -439,7 +416,6 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 						IngressID:     node.ID.String(),
 						IngressID:     node.ID.String(),
 						Network:       node.NetworkRange,
 						Network:       node.NetworkRange,
 						Network6:      node.NetworkRange6,
 						Network6:      node.NetworkRange6,
-						AllowAll:      defaultDevicePolicy.Enabled && defaultUserPolicy.Default,
 						StaticNodeIps: GetStaticNodeIps(node),
 						StaticNodeIps: GetStaticNodeIps(node),
 						Rules:         GetFwRulesOnIngressGateway(node),
 						Rules:         GetFwRulesOnIngressGateway(node),
 					}
 					}
@@ -473,10 +449,23 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 					IP:   node.Address6.IP,
 					IP:   node.Address6.IP,
 					Mask: getCIDRMaskFromAddr(node.Address6.IP.String()),
 					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) {
 		if IsInternetGw(node) {
 			hostPeerUpdate.FwUpdate.IsEgressGw = true
 			hostPeerUpdate.FwUpdate.IsEgressGw = true
 			egressrange := []string{"0.0.0.0/0"}
 			egressrange := []string{"0.0.0.0/0"}
@@ -559,6 +548,42 @@ func GetPeerListenPort(host *models.Host) int {
 	return peerPort
 	return peerPort
 }
 }
 
 
+func filterConflictingEgressRoutes(node, peer models.Node) []string {
+	egressIPs := slices.Clone(peer.EgressGatewayRanges)
+	if node.IsEgressGateway {
+		// filter conflicting addrs
+		nodeEgressMap := make(map[string]struct{})
+		for _, rangeI := range node.EgressGatewayRanges {
+			nodeEgressMap[rangeI] = struct{}{}
+		}
+		for i := len(egressIPs) - 1; i >= 0; i-- {
+			if _, ok := nodeEgressMap[egressIPs[i]]; ok {
+				egressIPs = append(egressIPs[:i], egressIPs[i+1:]...)
+			}
+		}
+	}
+
+	return egressIPs
+}
+
+func filterConflictingEgressRoutesWithMetric(node, peer models.Node) []models.EgressRangeMetric {
+	egressIPs := slices.Clone(peer.EgressGatewayRequest.RangesWithMetric)
+	if node.IsEgressGateway {
+		// filter conflicting addrs
+		nodeEgressMap := make(map[string]struct{})
+		for _, rangeI := range node.EgressGatewayRanges {
+			nodeEgressMap[rangeI] = struct{}{}
+		}
+		for i := len(egressIPs) - 1; i >= 0; i-- {
+			if _, ok := nodeEgressMap[egressIPs[i].Network]; ok {
+				egressIPs = append(egressIPs[:i], egressIPs[i+1:]...)
+			}
+		}
+	}
+
+	return egressIPs
+}
+
 // GetAllowedIPs - calculates the wireguard allowedip field for a peer of a node based on the peer and node settings
 // GetAllowedIPs - calculates the wireguard allowedip field for a peer of a node based on the peer and node settings
 func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics) []net.IPNet {
 func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics) []net.IPNet {
 	var allowedips []net.IPNet
 	var allowedips []net.IPNet
@@ -647,6 +672,18 @@ func getNodeAllowedIPs(peer, node *models.Node) []net.IPNet {
 	if peer.IsEgressGateway {
 	if peer.IsEgressGateway {
 		// hasGateway = true
 		// hasGateway = true
 		egressIPs := GetEgressIPs(peer)
 		egressIPs := GetEgressIPs(peer)
+		if node.IsEgressGateway {
+			// filter conflicting addrs
+			nodeEgressMap := make(map[string]struct{})
+			for _, rangeI := range node.EgressGatewayRanges {
+				nodeEgressMap[rangeI] = struct{}{}
+			}
+			for i := len(egressIPs) - 1; i >= 0; i-- {
+				if _, ok := nodeEgressMap[egressIPs[i].String()]; ok {
+					egressIPs = append(egressIPs[:i], egressIPs[i+1:]...)
+				}
+			}
+		}
 		allowedips = append(allowedips, egressIPs...)
 		allowedips = append(allowedips, egressIPs...)
 	}
 	}
 	if peer.IsRelay {
 	if peer.IsRelay {

+ 18 - 4
logic/tags.go

@@ -30,6 +30,14 @@ func GetTag(tagID models.TagID) (models.Tag, error) {
 	return tag, nil
 	return tag, nil
 }
 }
 
 
+func UpsertTag(tag models.Tag) error {
+	d, err := json.Marshal(tag)
+	if err != nil {
+		return err
+	}
+	return database.Insert(tag.ID.String(), string(d), database.TAG_TABLE_NAME)
+}
+
 // InsertTag - creates new tag
 // InsertTag - creates new tag
 func InsertTag(tag models.Tag) error {
 func InsertTag(tag models.Tag) error {
 	tagMutex.Lock()
 	tagMutex.Lock()
@@ -97,6 +105,12 @@ func ListTagsWithNodes(netID models.NetworkID) ([]models.TagListResp, error) {
 	}
 	}
 	return resp, nil
 	return resp, nil
 }
 }
+func DeleteAllNetworkTags(networkID models.NetworkID) {
+	tags, _ := ListNetworkTags(networkID)
+	for _, tagI := range tags {
+		DeleteTag(tagI.ID, false)
+	}
+}
 
 
 // ListTags - lists all tags from DB
 // ListTags - lists all tags from DB
 func ListTags() ([]models.Tag, error) {
 func ListTags() ([]models.Tag, error) {
@@ -270,10 +284,10 @@ func CheckIDSyntax(id string) error {
 }
 }
 
 
 func CreateDefaultTags(netID models.NetworkID) {
 func CreateDefaultTags(netID models.NetworkID) {
-	// create tag for remote access gws in the network
+	// create tag for gws in the network
 	tag := models.Tag{
 	tag := models.Tag{
-		ID:        models.TagID(fmt.Sprintf("%s.%s", netID.String(), models.RemoteAccessTagName)),
-		TagName:   models.RemoteAccessTagName,
+		ID:        models.TagID(fmt.Sprintf("%s.%s", netID.String(), models.GwTagName)),
+		TagName:   models.GwTagName,
 		Network:   netID,
 		Network:   netID,
 		CreatedBy: "auto",
 		CreatedBy: "auto",
 		CreatedAt: time.Now(),
 		CreatedAt: time.Now(),
@@ -284,7 +298,7 @@ func CreateDefaultTags(netID models.NetworkID) {
 	}
 	}
 	err = InsertTag(tag)
 	err = InsertTag(tag)
 	if err != nil {
 	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
 		return
 	}
 	}
 }
 }

+ 10 - 1
logic/zombie.go

@@ -77,7 +77,7 @@ func checkForZombieHosts(h *models.Host) {
 func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 	logger.Log(2, "Zombie management started")
 	logger.Log(2, "Zombie management started")
 	go InitializeZombies()
 	go InitializeZombies()
-
+	go checkPendingRemovalNodes()
 	// Zombie Nodes Cleanup Four Times a Day
 	// Zombie Nodes Cleanup Four Times a Day
 	ticker := time.NewTicker(time.Hour * ZOMBIE_TIMEOUT)
 	ticker := time.NewTicker(time.Hour * ZOMBIE_TIMEOUT)
 
 
@@ -138,6 +138,15 @@ func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 		}
 		}
 	}
 	}
 }
 }
+func checkPendingRemovalNodes() {
+	nodes, _ := GetAllNodes()
+	for _, node := range nodes {
+		pendingDelete := node.PendingDelete || node.Action == models.NODE_DELETE
+		if pendingDelete {
+			DeleteNode(&node, true)
+		}
+	}
+}
 
 
 // InitializeZombies - populates the zombie quarantine list (should be called from initialization)
 // InitializeZombies - populates the zombie quarantine list (should be called from initialization)
 func InitializeZombies() {
 func InitializeZombies() {

+ 2 - 2
main.go

@@ -34,10 +34,10 @@ import (
 	"golang.org/x/exp/slog"
 	"golang.org/x/exp/slog"
 )
 )
 
 
-var version = "v0.30.0"
+var version = "v0.90.0"
 
 
 //	@title			NetMaker
 //	@title			NetMaker
-//	@version		0.30.0
+//	@version		0.90.0
 //	@description	NetMaker API Docs
 //	@description	NetMaker API Docs
 //	@tag.name	    APIUsage
 //	@tag.name	    APIUsage
 //	@tag.description.markdown
 //	@tag.description.markdown

+ 39 - 10
migrate/migrate.go

@@ -204,15 +204,6 @@ func updateNodes() {
 			logic.UpsertNode(&node)
 			logic.UpsertNode(&node)
 		}
 		}
 		if node.IsIngressGateway {
 		if node.IsIngressGateway {
-			tagID := models.TagID(fmt.Sprintf("%s.%s", node.Network,
-				models.RemoteAccessTagName))
-			if node.Tags == nil {
-				node.Tags = make(map[models.TagID]struct{})
-			}
-			if _, ok := node.Tags[tagID]; !ok {
-				node.Tags[tagID] = struct{}{}
-				logic.UpsertNode(&node)
-			}
 			host, err := logic.GetHost(node.HostID.String())
 			host, err := logic.GetHost(node.HostID.String())
 			if err == nil {
 			if err == nil {
 				go logic.DeleteRole(models.GetRAGRoleID(node.Network, host.ID.String()), true)
 				go logic.DeleteRole(models.GetRAGRoleID(node.Network, host.ID.String()), true)
@@ -237,6 +228,13 @@ func updateNodes() {
 
 
 		}
 		}
 	}
 	}
+	extclients, _ := logic.GetAllExtClients()
+	for _, extclient := range extclients {
+		if extclient.Tags == nil {
+			extclient.Tags = make(map[models.TagID]struct{})
+			logic.SaveExtClient(&extclient)
+		}
+	}
 }
 }
 
 
 func removeInterGw(egressRanges []string) ([]string, bool) {
 func removeInterGw(egressRanges []string) ([]string, bool) {
@@ -448,7 +446,8 @@ func createDefaultTagsAndPolicies() {
 	for _, network := range networks {
 	for _, network := range networks {
 		logic.CreateDefaultTags(models.NetworkID(network.NetID))
 		logic.CreateDefaultTags(models.NetworkID(network.NetID))
 		logic.CreateDefaultAclNetworkPolicies(models.NetworkID(network.NetID))
 		logic.CreateDefaultAclNetworkPolicies(models.NetworkID(network.NetID))
-
+		// delete old remote access gws policy
+		logic.DeleteAcl(models.Acl{ID: fmt.Sprintf("%s.%s", network.NetID, "all-remote-access-gws")})
 	}
 	}
 	logic.MigrateAclPolicies()
 	logic.MigrateAclPolicies()
 }
 }
@@ -463,7 +462,37 @@ func migrateToGws() {
 			node.IsGw = true
 			node.IsGw = true
 			node.IsIngressGateway = true
 			node.IsIngressGateway = true
 			node.IsRelay = true
 			node.IsRelay = true
+			if node.Tags == nil {
+				node.Tags = make(map[models.TagID]struct{})
+			}
+			node.Tags[models.TagID(fmt.Sprintf("%s.%s", node.Network, models.GwTagName))] = struct{}{}
+			delete(node.Tags, models.TagID(fmt.Sprintf("%s.%s", node.Network, models.OldRemoteAccessTagName)))
 			logic.UpsertNode(&node)
 			logic.UpsertNode(&node)
 		}
 		}
 	}
 	}
+	acls := logic.ListAcls()
+	for _, acl := range acls {
+		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 = 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 = fmt.Sprintf("%s.%s", acl.NetworkID.String(), models.GwTagName)
+				acl.Dst[i] = dstI
+				upsert = true
+			}
+		}
+		if upsert {
+			logic.UpsertAcl(acl)
+		}
+	}
+	nets, _ := logic.GetNetworks()
+	for _, netI := range nets {
+		logic.DeleteTag(models.TagID(fmt.Sprintf("%s.%s", netI.NetID, models.OldRemoteAccessTagName)), true)
+	}
 }
 }

+ 6 - 3
models/acl.go

@@ -25,14 +25,13 @@ const (
 	ICMP Protocol = "icmp"
 	ICMP Protocol = "icmp"
 )
 )
 
 
-type ServiceType string
-
 const (
 const (
 	Http        = "HTTP"
 	Http        = "HTTP"
 	Https       = "HTTPS"
 	Https       = "HTTPS"
 	AllTCP      = "All TCP"
 	AllTCP      = "All TCP"
 	AllUDP      = "All UDP"
 	AllUDP      = "All UDP"
 	ICMPService = "ICMP"
 	ICMPService = "ICMP"
+	SSH         = "SSH"
 	Custom      = "Custom"
 	Custom      = "Custom"
 	Any         = "Any"
 	Any         = "Any"
 )
 )
@@ -58,7 +57,9 @@ type AclGroupType string
 const (
 const (
 	UserAclID                AclGroupType = "user"
 	UserAclID                AclGroupType = "user"
 	UserGroupAclID           AclGroupType = "user-group"
 	UserGroupAclID           AclGroupType = "user-group"
-	DeviceAclID              AclGroupType = "tag"
+	NodeTagID                AclGroupType = "tag"
+	NodeID                   AclGroupType = "device"
+	EgressRange              AclGroupType = "egress-range"
 	NetmakerIPAclID          AclGroupType = "ip"
 	NetmakerIPAclID          AclGroupType = "ip"
 	NetmakerSubNetRangeAClID AclGroupType = "ipset"
 	NetmakerSubNetRangeAClID AclGroupType = "ipset"
 )
 )
@@ -116,5 +117,7 @@ type AclRule struct {
 	AllowedProtocol Protocol                `json:"allowed_protocols"` // tcp, udp, etc.
 	AllowedProtocol Protocol                `json:"allowed_protocols"` // tcp, udp, etc.
 	AllowedPorts    []string                `json:"allowed_ports"`
 	AllowedPorts    []string                `json:"allowed_ports"`
 	Direction       AllowedTrafficDirection `json:"direction"` // single or two-way
 	Direction       AllowedTrafficDirection `json:"direction"` // single or two-way
+	Dst             []net.IPNet             `json:"dst"`
+	Dst6            []net.IPNet             `json:"dst6"`
 	Allowed         bool
 	Allowed         bool
 }
 }

+ 7 - 2
models/extclient.go

@@ -50,14 +50,19 @@ type CustomExtClient struct {
 }
 }
 
 
 func (ext *ExtClient) ConvertToStaticNode() Node {
 func (ext *ExtClient) ConvertToStaticNode() Node {
-
+	if ext.Tags == nil {
+		ext.Tags = make(map[TagID]struct{})
+	}
 	return Node{
 	return Node{
 		CommonNode: CommonNode{
 		CommonNode: CommonNode{
-			Network: ext.Network,
+			Network:  ext.Network,
+			Address:  ext.AddressIPNet4(),
+			Address6: ext.AddressIPNet6(),
 		},
 		},
 		Tags:       ext.Tags,
 		Tags:       ext.Tags,
 		IsStatic:   true,
 		IsStatic:   true,
 		StaticNode: *ext,
 		StaticNode: *ext,
+		IsUserNode: ext.RemoteAccessClientID != "",
 		Mutex:      ext.Mutex,
 		Mutex:      ext.Mutex,
 	}
 	}
 }
 }

+ 3 - 1
models/mqtt.go

@@ -53,7 +53,6 @@ type IngressInfo struct {
 	Network6      net.IPNet   `json:"network6"`
 	Network6      net.IPNet   `json:"network6"`
 	StaticNodeIps []net.IP    `json:"static_node_ips"`
 	StaticNodeIps []net.IP    `json:"static_node_ips"`
 	Rules         []FwRule    `json:"rules"`
 	Rules         []FwRule    `json:"rules"`
-	AllowAll      bool        `json:"allow_all"`
 	EgressRanges  []net.IPNet `json:"egress_ranges"`
 	EgressRanges  []net.IPNet `json:"egress_ranges"`
 	EgressRanges6 []net.IPNet `json:"egress_ranges6"`
 	EgressRanges6 []net.IPNet `json:"egress_ranges6"`
 }
 }
@@ -66,16 +65,19 @@ type EgressInfo struct {
 	Network6      net.IPNet            `json:"network6" yaml:"network6"`
 	Network6      net.IPNet            `json:"network6" yaml:"network6"`
 	EgressGwAddr6 net.IPNet            `json:"egress_gw_addr6" yaml:"egress_gw_addr6"`
 	EgressGwAddr6 net.IPNet            `json:"egress_gw_addr6" yaml:"egress_gw_addr6"`
 	EgressGWCfg   EgressGatewayRequest `json:"egress_gateway_cfg" yaml:"egress_gateway_cfg"`
 	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
 // EgressNetworkRoutes - struct for egress network routes for adding routes to peer's interface
 type EgressNetworkRoutes struct {
 type EgressNetworkRoutes struct {
+	PeerKey                string              `json:"peer_key"`
 	EgressGwAddr           net.IPNet           `json:"egress_gw_addr" yaml:"egress_gw_addr"`
 	EgressGwAddr           net.IPNet           `json:"egress_gw_addr" yaml:"egress_gw_addr"`
 	EgressGwAddr6          net.IPNet           `json:"egress_gw_addr6" yaml:"egress_gw_addr6"`
 	EgressGwAddr6          net.IPNet           `json:"egress_gw_addr6" yaml:"egress_gw_addr6"`
 	NodeAddr               net.IPNet           `json:"node_addr"`
 	NodeAddr               net.IPNet           `json:"node_addr"`
 	NodeAddr6              net.IPNet           `json:"node_addr6"`
 	NodeAddr6              net.IPNet           `json:"node_addr6"`
 	EgressRanges           []string            `json:"egress_ranges"`
 	EgressRanges           []string            `json:"egress_ranges"`
 	EgressRangesWithMetric []EgressRangeMetric `json:"egress_ranges_metric"`
 	EgressRangesWithMetric []EgressRangeMetric `json:"egress_ranges_metric"`
+	Network                string              `json:"network"`
 }
 }
 
 
 // PeerRouteInfo - struct for peer info for an ext. client
 // PeerRouteInfo - struct for peer info for an ext. client

+ 2 - 0
models/structs.go

@@ -44,6 +44,8 @@ type UserRemoteGws struct {
 	AllowedEndpoints  []string   `json:"allowed_endpoints"`
 	AllowedEndpoints  []string   `json:"allowed_endpoints"`
 	NetworkAddresses  []string   `json:"network_addresses"`
 	NetworkAddresses  []string   `json:"network_addresses"`
 	Status            NodeStatus `json:"status"`
 	Status            NodeStatus `json:"status"`
+	DnsAddress        string     `json:"dns_address"`
+	Addresses         string     `json:"addresses"`
 }
 }
 
 
 // UserRAGs - struct for user access gws
 // UserRAGs - struct for user access gws

+ 5 - 1
models/tags.go

@@ -8,7 +8,8 @@ import (
 type TagID string
 type TagID string
 
 
 const (
 const (
-	RemoteAccessTagName = "remote-access-gws"
+	OldRemoteAccessTagName = "remote-access-gws"
+	GwTagName              = "gateways"
 )
 )
 
 
 func (id TagID) String() string {
 func (id TagID) String() string {
@@ -23,6 +24,7 @@ type Tag struct {
 	ID        TagID     `json:"id"`
 	ID        TagID     `json:"id"`
 	TagName   string    `json:"tag_name"`
 	TagName   string    `json:"tag_name"`
 	Network   NetworkID `json:"network"`
 	Network   NetworkID `json:"network"`
+	ColorCode string    `json:"color_code"`
 	CreatedBy string    `json:"created_by"`
 	CreatedBy string    `json:"created_by"`
 	CreatedAt time.Time `json:"created_at"`
 	CreatedAt time.Time `json:"created_at"`
 }
 }
@@ -30,6 +32,7 @@ type Tag struct {
 type CreateTagReq struct {
 type CreateTagReq struct {
 	TagName     string    `json:"tag_name"`
 	TagName     string    `json:"tag_name"`
 	Network     NetworkID `json:"network"`
 	Network     NetworkID `json:"network"`
+	ColorCode   string    `json:"color_code"`
 	TaggedNodes []ApiNode `json:"tagged_nodes"`
 	TaggedNodes []ApiNode `json:"tagged_nodes"`
 }
 }
 
 
@@ -48,5 +51,6 @@ type TagListRespNodes struct {
 type UpdateTagReq struct {
 type UpdateTagReq struct {
 	Tag
 	Tag
 	NewName     string    `json:"new_name"`
 	NewName     string    `json:"new_name"`
+	ColorCode   string    `json:"color_code"`
 	TaggedNodes []ApiNode `json:"tagged_nodes"`
 	TaggedNodes []ApiNode `json:"tagged_nodes"`
 }
 }

+ 1 - 1
pro/LICENSE

@@ -3,7 +3,7 @@ Copyright (c) 2022 Netmaker, Inc.
 
 
 With regard to the Netmaker Software:
 With regard to the Netmaker Software:
 
 
-This software and associated documentation files (the "Software") may only be used in production, if you (and any entity that you represent) have agreed to, and are in compliance with, the Netmaker Subscription Terms of Service, available at https://netmaker.io/terms (the “Enterprise Terms”), or other agreement governing the use of the Software, as agreed by you and Netmaker, and otherwise have a valid Netmaker Enterprise license for the correct number of users, networks, nodes, servers, and external clients. Subject to the foregoing sentence, you are free to modify this Software and publish patches to the Software. You agree that Netmaker and/or its licensors (as applicable) retain all right, title and interest in and to all such modifications and/or patches, and all such modifications and/or patches may only be used, copied, modified, displayed, distributed, or otherwise exploited with a valid Netmaker Enterprise license for the correct number of users, networks, nodes, servers, and external clients as allocated by the license. Notwithstanding the foregoing, you may copy and modify the Software for development and testing purposes, without requiring a subscription. You agree that Netmaker and/or its licensors (as applicable) retain all right, title and interest in and to all such modifications. You are not granted any other rights beyond what is expressly stated herein. Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense, and/or sell the Software.
+This software and associated documentation files (the "Software") may only be used in production, if you (and any entity that you represent) have agreed to, and are in compliance with, the Netmaker Subscription Terms of Service, available at https://www.netmaker.io/terms-and-conditions (the “Enterprise Terms”), or other agreement governing the use of the Software, as agreed by you and Netmaker, and otherwise have a valid Netmaker Enterprise license for the correct number of users, networks, nodes, servers, and external clients. Subject to the foregoing sentence, you are free to modify this Software and publish patches to the Software. You agree that Netmaker and/or its licensors (as applicable) retain all right, title and interest in and to all such modifications and/or patches, and all such modifications and/or patches may only be used, copied, modified, displayed, distributed, or otherwise exploited with a valid Netmaker Enterprise license for the correct number of users, networks, nodes, servers, and external clients as allocated by the license. Notwithstanding the foregoing, you may copy and modify the Software for development and testing purposes, without requiring a subscription. You agree that Netmaker and/or its licensors (as applicable) retain all right, title and interest in and to all such modifications. You are not granted any other rights beyond what is expressly stated herein. Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense, and/or sell the Software.
 
 
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 

+ 21 - 2
pro/auth/azure-ad.go

@@ -79,9 +79,18 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 		handleOauthUserSignUpApprovalPending(w)
 		handleOauthUserSignUpApprovalPending(w)
 		return
 		return
 	}
 	}
-	// if user exists with provider ID, convert them into email ID
+
 	user, err := logic.GetUser(content.UserPrincipalName)
 	user, err := logic.GetUser(content.UserPrincipalName)
 	if err == nil {
 	if err == nil {
+		// if user exists, then ensure user's auth type is
+		// oauth before proceeding.
+		if user.AuthType == models.BasicAuth {
+			logger.Log(0, "invalid auth type: basic_auth")
+			handleAuthTypeMismatch(w)
+			return
+		}
+
+		// if user exists with provider ID, convert them into email ID
 		_, err := logic.GetUser(content.Email)
 		_, err := logic.GetUser(content.Email)
 		if err != nil {
 		if err != nil {
 			user.UserName = content.Email
 			user.UserName = content.Email
@@ -91,7 +100,8 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 			database.Insert(user.UserName, string(d), database.USERS_TABLE_NAME)
 			database.Insert(user.UserName, string(d), database.USERS_TABLE_NAME)
 		}
 		}
 	}
 	}
-	_, err = logic.GetUser(content.Email)
+
+	user, err = logic.GetUser(content.Email)
 	if err != nil {
 	if err != nil {
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 			if inviteExists {
 			if inviteExists {
@@ -127,7 +137,16 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 			handleSomethingWentWrong(w)
 			handleSomethingWentWrong(w)
 			return
 			return
 		}
 		}
+	} else {
+		// if user exists, then ensure user's auth type is
+		// oauth before proceeding.
+		if user.AuthType == models.BasicAuth {
+			logger.Log(0, "invalid auth type: basic_auth")
+			handleAuthTypeMismatch(w)
+			return
+		}
 	}
 	}
+
 	user, err = logic.GetUser(content.Email)
 	user, err = logic.GetUser(content.Email)
 	if err != nil {
 	if err != nil {
 		handleOauthUserNotFound(w)
 		handleOauthUserNotFound(w)

+ 10 - 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 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>
 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>`)
 var userFirstTimeSignUp = fmt.Sprintf(htmlBaseTemplate, `<h2>Thank you for signing up. Please contact your administrator for access.</h2>`)
 
 
@@ -110,6 +110,9 @@ var somethingwentwrong = fmt.Sprintf(htmlBaseTemplate, `<h2>Something went wrong
 
 
 var notallowedtosignup = fmt.Sprintf(htmlBaseTemplate, `<h2>Your email is not allowed. Please contact your administrator.</h2>`)
 var notallowedtosignup = fmt.Sprintf(htmlBaseTemplate, `<h2>Your email is not allowed. Please contact your administrator.</h2>`)
 
 
+var authTypeMismatch = fmt.Sprintf(htmlBaseTemplate, `<h2>It looks like you already have an account with us using Basic Authentication.</h2>
+<p>To continue, please log in with your existing credentials or reset your password if needed.</p>`)
+
 func handleOauthUserNotFound(response http.ResponseWriter) {
 func handleOauthUserNotFound(response http.ResponseWriter) {
 	response.Header().Set("Content-Type", "text/html; charset=utf-8")
 	response.Header().Set("Content-Type", "text/html; charset=utf-8")
 	response.WriteHeader(http.StatusNotFound)
 	response.WriteHeader(http.StatusNotFound)
@@ -157,3 +160,9 @@ func handleSomethingWentWrong(response http.ResponseWriter) {
 	response.WriteHeader(http.StatusInternalServerError)
 	response.WriteHeader(http.StatusInternalServerError)
 	response.Write([]byte(somethingwentwrong))
 	response.Write([]byte(somethingwentwrong))
 }
 }
+
+func handleAuthTypeMismatch(response http.ResponseWriter) {
+	response.Header().Set("Content-Type", "text/html; charset=utf-8")
+	response.WriteHeader(http.StatusBadRequest)
+	response.Write([]byte(authTypeMismatch))
+}

+ 8 - 0
pro/auth/github.go

@@ -82,6 +82,14 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
 	// if user exists with provider ID, convert them into email ID
 	// if user exists with provider ID, convert them into email ID
 	user, err := logic.GetUser(content.Login)
 	user, err := logic.GetUser(content.Login)
 	if err == nil {
 	if err == nil {
+		// if user exists, then ensure user's auth type is
+		// oauth before proceeding.
+		if user.AuthType == models.BasicAuth {
+			logger.Log(0, "invalid auth type: basic_auth")
+			handleAuthTypeMismatch(w)
+			return
+		}
+
 		// checks if user exists with email
 		// checks if user exists with email
 		_, err := logic.GetUser(content.Email)
 		_, err := logic.GetUser(content.Email)
 		if err != nil {
 		if err != nil {

+ 13 - 2
pro/auth/google.go

@@ -80,7 +80,8 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
 		handleOauthUserSignUpApprovalPending(w)
 		handleOauthUserSignUpApprovalPending(w)
 		return
 		return
 	}
 	}
-	_, err = logic.GetUser(content.Email)
+
+	user, err := logic.GetUser(content.Email)
 	if err != nil {
 	if err != nil {
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 			if inviteExists {
 			if inviteExists {
@@ -117,13 +118,23 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
 			handleSomethingWentWrong(w)
 			handleSomethingWentWrong(w)
 			return
 			return
 		}
 		}
+	} else {
+		// if user exists, then ensure user's auth type is
+		// oauth before proceeding.
+		if user.AuthType == models.BasicAuth {
+			logger.Log(0, "invalid auth type: basic_auth")
+			handleAuthTypeMismatch(w)
+			return
+		}
 	}
 	}
-	user, err := logic.GetUser(content.Email)
+
+	user, err = logic.GetUser(content.Email)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, "error fetching user: ", err.Error())
 		logger.Log(0, "error fetching user: ", err.Error())
 		handleOauthUserNotFound(w)
 		handleOauthUserNotFound(w)
 		return
 		return
 	}
 	}
+
 	userRole, err := logic.GetRole(user.PlatformRoleID)
 	userRole, err := logic.GetRole(user.PlatformRoleID)
 	if err != nil {
 	if err != nil {
 		handleSomethingWentWrong(w)
 		handleSomethingWentWrong(w)

+ 12 - 2
pro/auth/oidc.go

@@ -91,7 +91,8 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
 		handleOauthUserSignUpApprovalPending(w)
 		handleOauthUserSignUpApprovalPending(w)
 		return
 		return
 	}
 	}
-	_, err = logic.GetUser(content.Email)
+
+	user, err := logic.GetUser(content.Email)
 	if err != nil {
 	if err != nil {
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 			if inviteExists {
 			if inviteExists {
@@ -127,8 +128,17 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
 			handleSomethingWentWrong(w)
 			handleSomethingWentWrong(w)
 			return
 			return
 		}
 		}
+	} else {
+		// if user exists, then ensure user's auth type is
+		// oauth before proceeding.
+		if user.AuthType == models.BasicAuth {
+			logger.Log(0, "invalid auth type: basic_auth")
+			handleAuthTypeMismatch(w)
+			return
+		}
 	}
 	}
-	user, err := logic.GetUser(content.Email)
+
+	user, err = logic.GetUser(content.Email)
 	if err != nil {
 	if err != nil {
 		handleOauthUserNotFound(w)
 		handleOauthUserNotFound(w)
 		return
 		return

+ 47 - 0
pro/controllers/users.go

@@ -8,7 +8,9 @@ import (
 	"net/http"
 	"net/http"
 	"net/url"
 	"net/url"
 	"strings"
 	"strings"
+	"time"
 
 
+	"github.com/google/uuid"
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
@@ -19,6 +21,7 @@ import (
 	"github.com/gravitl/netmaker/pro/email"
 	"github.com/gravitl/netmaker/pro/email"
 	proLogic "github.com/gravitl/netmaker/pro/logic"
 	proLogic "github.com/gravitl/netmaker/pro/logic"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
+	"github.com/gravitl/netmaker/utils"
 	"golang.org/x/exp/slog"
 	"golang.org/x/exp/slog"
 )
 )
 
 
@@ -411,6 +414,44 @@ func createUserGroup(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		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:     false,
+			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 {
 	for _, userID := range userGroupReq.Members {
 		user, err := logic.GetUser(userID)
 		user, err := logic.GetUser(userID)
 		if err != nil {
 		if err != nil {
@@ -1034,6 +1075,8 @@ func getRemoteAccessGatewayConf(w http.ResponseWriter, r *http.Request) {
 		Metadata:          node.Metadata,
 		Metadata:          node.Metadata,
 		AllowedEndpoints:  getAllowedRagEndpoints(&node, host),
 		AllowedEndpoints:  getAllowedRagEndpoints(&node, host),
 		NetworkAddresses:  []string{network.AddressRange, network.AddressRange6},
 		NetworkAddresses:  []string{network.AddressRange, network.AddressRange6},
+		DnsAddress:        node.IngressDNS,
+		Addresses:         utils.NoEmptyStringToCsv(node.Address.String(), node.Address6.String()),
 	}
 	}
 
 
 	slog.Debug("returned user gw config", "user", user.UserName, "gws", userGw)
 	slog.Debug("returned user gw config", "user", user.UserName, "gws", userGw)
@@ -1125,6 +1168,8 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 				AllowedEndpoints:  getAllowedRagEndpoints(&node, host),
 				AllowedEndpoints:  getAllowedRagEndpoints(&node, host),
 				NetworkAddresses:  []string{network.AddressRange, network.AddressRange6},
 				NetworkAddresses:  []string{network.AddressRange, network.AddressRange6},
 				Status:            node.Status,
 				Status:            node.Status,
+				DnsAddress:        node.IngressDNS,
+				Addresses:         utils.NoEmptyStringToCsv(node.Address.String(), node.Address6.String()),
 			})
 			})
 			userGws[node.Network] = gws
 			userGws[node.Network] = gws
 			delete(userGwNodes, node.ID.String())
 			delete(userGwNodes, node.ID.String())
@@ -1167,6 +1212,8 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 			AllowedEndpoints:  getAllowedRagEndpoints(&node, host),
 			AllowedEndpoints:  getAllowedRagEndpoints(&node, host),
 			NetworkAddresses:  []string{network.AddressRange, network.AddressRange6},
 			NetworkAddresses:  []string{network.AddressRange, network.AddressRange6},
 			Status:            node.Status,
 			Status:            node.Status,
+			DnsAddress:        node.IngressDNS,
+			Addresses:         utils.NoEmptyStringToCsv(node.Address.String(), node.Address6.String()),
 		})
 		})
 		userGws[node.Network] = gws
 		userGws[node.Network] = gws
 	}
 	}

+ 3 - 2
pro/email/invite.go

@@ -2,6 +2,7 @@ package email
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	proLogic "github.com/gravitl/netmaker/pro/logic"
 	proLogic "github.com/gravitl/netmaker/pro/logic"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
@@ -31,11 +32,11 @@ func (invite UserInvitedMail) GetBody(info Notification) string {
 
 
 	content := invite.BodyBuilder.
 	content := invite.BodyBuilder.
 		WithParagraph("Hi,").
 		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("<ol>").
 		WithHtml(fmt.Sprintf("<li>Click <a href=\"%s\">here</a> to accept your invitation and setup your account.</li>", invite.InviteURL)).
 		WithHtml(fmt.Sprintf("<li>Click <a href=\"%s\">here</a> to accept your invitation and setup your account.</li>", invite.InviteURL)).
 		WithHtml("<br>").
 		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() {
 	if invite.PlatformRoleID == models.AdminRole.String() || invite.PlatformRoleID == models.PlatformUser.String() {
 		content = content.
 		content = content.

+ 3 - 3
pro/logic/status.go

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

+ 11 - 91
pro/logic/user_mgmt.go

@@ -40,7 +40,7 @@ var NetworkAdminAllPermissionTemplate = models.UserRolePermissionTemplate{
 var NetworkUserAllPermissionTemplate = models.UserRolePermissionTemplate{
 var NetworkUserAllPermissionTemplate = models.UserRolePermissionTemplate{
 	ID:         models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkUser)),
 	ID:         models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkUser)),
 	Name:       "Network Users",
 	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,
 	Default:    true,
 	FullAccess: false,
 	FullAccess: false,
 	NetworkID:  models.AllNetworks,
 	NetworkID:  models.AllNetworks,
@@ -131,7 +131,7 @@ func UserGroupsInit() {
 				models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkUser)): {},
 				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)
 	d, _ := json.Marshal(NetworkGlobalAdminGroup)
 	database.Insert(NetworkGlobalAdminGroup.ID.String(), string(d), database.USER_GROUPS_TABLE_NAME)
 	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{
 	var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{
 		ID:                  models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkUser)),
 		ID:                  models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkUser)),
 		Name:                fmt.Sprintf("%s User", netID),
 		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,
 		Default:             true,
 		FullAccess:          false,
 		FullAccess:          false,
 		NetworkID:           netID,
 		NetworkID:           netID,
@@ -235,7 +235,7 @@ func CreateDefaultNetworkRolesAndGroups(netID models.NetworkID) {
 				models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkUser)): {},
 				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)
 	d, _ = json.Marshal(NetworkAdminGroup)
 	database.Insert(NetworkAdminGroup.ID.String(), string(d), database.USER_GROUPS_TABLE_NAME)
 	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) {
 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 {
 func FilterNetworksByRole(allnetworks []models.Network, user models.User) []models.Network {
@@ -1211,7 +1131,7 @@ func CreateDefaultUserPolicies(netID models.NetworkID) {
 		defaultUserAcl := models.Acl{
 		defaultUserAcl := models.Acl{
 			ID:          fmt.Sprintf("%s.%s-grp", netID, models.NetworkAdmin),
 			ID:          fmt.Sprintf("%s.%s-grp", netID, models.NetworkAdmin),
 			Name:        "Network Admin",
 			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,
 			Default:     true,
 			ServiceType: models.Any,
 			ServiceType: models.Any,
 			NetworkID:   netID,
 			NetworkID:   netID,
@@ -1229,8 +1149,8 @@ func CreateDefaultUserPolicies(netID models.NetworkID) {
 			},
 			},
 			Dst: []models.AclPolicyTag{
 			Dst: []models.AclPolicyTag{
 				{
 				{
-					ID:    models.DeviceAclID,
-					Value: fmt.Sprintf("%s.%s", netID, models.RemoteAccessTagName),
+					ID:    models.NodeTagID,
+					Value: fmt.Sprintf("%s.%s", netID, models.GwTagName),
 				}},
 				}},
 			AllowedDirection: models.TrafficDirectionUni,
 			AllowedDirection: models.TrafficDirectionUni,
 			Enabled:          true,
 			Enabled:          true,
@@ -1244,7 +1164,7 @@ func CreateDefaultUserPolicies(netID models.NetworkID) {
 		defaultUserAcl := models.Acl{
 		defaultUserAcl := models.Acl{
 			ID:          fmt.Sprintf("%s.%s-grp", netID, models.NetworkUser),
 			ID:          fmt.Sprintf("%s.%s-grp", netID, models.NetworkUser),
 			Name:        "Network User",
 			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,
 			Default:     true,
 			ServiceType: models.Any,
 			ServiceType: models.Any,
 			NetworkID:   netID,
 			NetworkID:   netID,
@@ -1263,8 +1183,8 @@ func CreateDefaultUserPolicies(netID models.NetworkID) {
 
 
 			Dst: []models.AclPolicyTag{
 			Dst: []models.AclPolicyTag{
 				{
 				{
-					ID:    models.DeviceAclID,
-					Value: fmt.Sprintf("%s.%s", netID, models.RemoteAccessTagName),
+					ID:    models.NodeTagID,
+					Value: fmt.Sprintf("%s.%s", netID, models.GwTagName),
 				}},
 				}},
 			AllowedDirection: models.TrafficDirectionUni,
 			AllowedDirection: models.TrafficDirectionUni,
 			Enabled:          true,
 			Enabled:          true,

+ 3 - 2
pro/util.go

@@ -5,6 +5,7 @@ package pro
 
 
 import (
 import (
 	"encoding/base64"
 	"encoding/base64"
+	"github.com/gravitl/netmaker/models"
 
 
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 )
 )
@@ -26,11 +27,11 @@ func base64decode(input string) []byte {
 
 
 func getCurrentServerUsage() (limits Usage) {
 func getCurrentServerUsage() (limits Usage) {
 	limits.SetDefaults()
 	limits.SetDefaults()
-	hosts, hErr := logic.GetAllHosts()
+	hosts, hErr := logic.GetAllHostsWithStatus(models.OnlineSt)
 	if hErr == nil {
 	if hErr == nil {
 		limits.Hosts = len(hosts)
 		limits.Hosts = len(hosts)
 	}
 	}
-	clients, cErr := logic.GetAllExtClients()
+	clients, cErr := logic.GetAllExtClientsWithStatus(models.OnlineSt)
 	if cErr == nil {
 	if cErr == nil {
 		limits.Clients = len(clients)
 		limits.Clients = len(clients)
 	}
 	}

+ 11 - 12
release.md

@@ -1,21 +1,20 @@
-# Netmaker v0.30.0
+# Netmaker v0.90.0
 
 
 ## Whats New ✨
 ## Whats New ✨
-- Advanced ACL Rules - port, protocol and traffic direction
-- Reduced Firewall Requirements To One Single Port (443 udp/tcp)
-- Option to Turn off STUN or specify custom stun servers
-- Improved Connectivity Status Indicator with real-time troubleshooting help.
+- ACL Rules for Egress Ranges
+- High Availability for Egress Routes
+- Remote Access Gateways and Relays have been merged together into  "Gateways" and are now available on CE.
+- Enchanced Graph Page
+- Ability to Define Additional Nameservers in your network
 
 
 ## What's Fixed/Improved 🛠
 ## What's Fixed/Improved 🛠
 - Metrics Data
 - Metrics Data
-- Optimised MQ message size
-- FailOver Stability Fixes
-- Scalability Fixes
-- Duplicate Node IP check on update
+- IPv6 DNS Entries Are Not Working.
+- FailOver connection improvements.
+- Optimized Failover peer signaling.
+- Improved Connectivity Status Indicator with real-time troubleshooting help.
 
 
 ## Known Issues 🐞
 ## Known Issues 🐞
 
 
-- IPv6 DNS Entries Are Not Working.
-- Stale Peer On The Interface, When Forced Removed From Multiple Networks At Once.
-- WireGuard DNS issue on most flavours of Ubuntu 24.04 and some other newer Linux distributions. The issue is affecting the Remote Access Client (RAC) and the plain WireGuard external clients. Workaround can be found here https://help.netmaker.io/en/articles/9612016-extclient-rac-dns-issue-on-ubuntu-24-04.
+- WireGuard DNS issue on Ubuntu 24.04 and some other newer Linux distributions. The issue is affecting the Remote Access Client (RAC) and the plain WireGuard external clients. Workaround can be found here https://help.netmaker.io/en/articles/9612016-extclient-rac-dns-issue-on-ubuntu-24-04.
 
 

+ 3 - 1
scripts/netmaker.default.env

@@ -73,6 +73,8 @@ OIDC_ISSUER=
 JWT_VALIDITY_DURATION=43200
 JWT_VALIDITY_DURATION=43200
 # Auto disable a user's connecteds clients bassed on JWT token expiration
 # Auto disable a user's connecteds clients bassed on JWT token expiration
 RAC_AUTO_DISABLE=false
 RAC_AUTO_DISABLE=false
+# Allow a user to connect to multiple networks simultaneously
+RAC_RESTRICT_TO_SINGLE_NETWORK=false
 # if turned on data will be cached on to improve performance significantly (IMPORTANT: If HA set to `false` )
 # if turned on data will be cached on to improve performance significantly (IMPORTANT: If HA set to `false` )
 CACHING_ENABLED=true
 CACHING_ENABLED=true
 # if turned on netclient checks if peers are reachable over private/LAN address, and choose that as peer endpoint
 # if turned on netclient checks if peers are reachable over private/LAN address, and choose that as peer endpoint
@@ -91,7 +93,7 @@ EMAIL_SENDER_PASSWORD=
 # default domain for internal DNS lookup
 # default domain for internal DNS lookup
 DEFAULT_DOMAIN=hosted.nm
 DEFAULT_DOMAIN=hosted.nm
 # managed dns setting, set to true to resolve dns entries on netmaker network
 # managed dns setting, set to true to resolve dns entries on netmaker network
-MANAGE_DNS=false
+MANAGE_DNS=true
 # set to true, old acl is supported, otherwise, old acl is disabled
 # set to true, old acl is supported, otherwise, old acl is disabled
 OLD_ACL_SUPPORT=true
 OLD_ACL_SUPPORT=true
 # if STUN is set to true, hole punch is called
 # if STUN is set to true, hole punch is called

+ 4 - 4
scripts/nm-quick.sh

@@ -253,10 +253,10 @@ save_config() { (
 	fi
 	fi
 	# copy entries from the previous config
 	# copy entries from the previous config
 	local toCopy=("SERVER_HOST" "SERVER_HOST6" "MASTER_KEY" "MQ_USERNAME" "MQ_PASSWORD" "LICENSE_KEY" "NETMAKER_TENANT_ID"
 	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"
 		"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"
 		"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"
+		"FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" "EXPORTER_API_PORT" "JWT_VALIDITY_DURATION" "RAC_AUTO_DISABLE" "RAC_RESTRICT_TO_SINGLE_NETWORK" "CACHING_ENABLED" "ENDPOINT_DETECTION"
 		"SMTP_HOST" "SMTP_PORT" "EMAIL_SENDER_ADDR" "EMAIL_SENDER_USER" "EMAIL_SENDER_PASSWORD")
 		"SMTP_HOST" "SMTP_PORT" "EMAIL_SENDER_ADDR" "EMAIL_SENDER_USER" "EMAIL_SENDER_PASSWORD")
 	for name in "${toCopy[@]}"; do
 	for name in "${toCopy[@]}"; do
 		save_config_item $name "${!name}"
 		save_config_item $name "${!name}"
@@ -501,8 +501,8 @@ set -e
 # set_install_vars - sets the variables that will be used throughout installation
 # set_install_vars - sets the variables that will be used throughout installation
 set_install_vars() {
 set_install_vars() {
 
 
-	IP_ADDR=$(curl -s -4 ifconfig.me || echo "")
-    IP6_ADDR=$(curl -s -6 ifconfig.me || echo "")
+	IP_ADDR=$(curl -s -4 api64.ipify.org || echo "")
+    IP6_ADDR=$(curl -s -6 api64.ipify.org || echo "")
 	if [ "$NETMAKER_BASE_DOMAIN" = "" ]; then
 	if [ "$NETMAKER_BASE_DOMAIN" = "" ]; then
 		NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io
 		NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io
 	fi
 	fi

+ 1 - 1
scripts/nm-upgrade.sh

@@ -179,7 +179,7 @@ save_config() { (
 		"CORS_ALLOWED_ORIGIN" "DISPLAY_KEYS" "DATABASE" "SERVER_BROKER_ENDPOINT" "STUN_PORT" "VERBOSITY"
 		"CORS_ALLOWED_ORIGIN" "DISPLAY_KEYS" "DATABASE" "SERVER_BROKER_ENDPOINT" "STUN_PORT" "VERBOSITY"
 		"TURN_PORT" "USE_TURN" "DEBUG_MODE" "TURN_API_PORT" "REST_BACKEND"
 		"TURN_PORT" "USE_TURN" "DEBUG_MODE" "TURN_API_PORT" "REST_BACKEND"
 		"DISABLE_REMOTE_IP_CHECK" "TELEMETRY" "AUTH_PROVIDER" "CLIENT_ID" "CLIENT_SECRET"
 		"DISABLE_REMOTE_IP_CHECK" "TELEMETRY" "AUTH_PROVIDER" "CLIENT_ID" "CLIENT_SECRET"
-		"FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" "EXPORTER_API_PORT" "JWT_VALIDITY_DURATION" "RAC_AUTO_DISABLE")
+		"FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" "EXPORTER_API_PORT" "JWT_VALIDITY_DURATION" "RAC_AUTO_DISABLE" "RAC_RESTRICT_TO_SINGLE_NETWORK")
 	for name in "${toCopy[@]}"; do
 	for name in "${toCopy[@]}"; do
 		save_config_item $name "${!name}"
 		save_config_item $name "${!name}"
 	done
 	done

+ 6 - 0
servercfg/serverconf.go

@@ -94,6 +94,7 @@ func GetServerConfig() config.ServerConfig {
 	}
 	}
 	cfg.JwtValidityDuration = GetJwtValidityDuration()
 	cfg.JwtValidityDuration = GetJwtValidityDuration()
 	cfg.RacAutoDisable = GetRacAutoDisable()
 	cfg.RacAutoDisable = GetRacAutoDisable()
+	cfg.RacRestrictToSingleNetwork = GetRacRestrictToSingleNetwork()
 	cfg.MetricInterval = GetMetricInterval()
 	cfg.MetricInterval = GetMetricInterval()
 	cfg.ManageDNS = GetManageDNS()
 	cfg.ManageDNS = GetManageDNS()
 	cfg.Stun = IsStunEnabled()
 	cfg.Stun = IsStunEnabled()
@@ -120,6 +121,11 @@ func GetRacAutoDisable() bool {
 	return os.Getenv("RAC_AUTO_DISABLE") == "true"
 	return os.Getenv("RAC_AUTO_DISABLE") == "true"
 }
 }
 
 
+// GetRacRestrictToSingleNetwork - returns whether the feature to allow simultaneous network connections via RAC is enabled
+func GetRacRestrictToSingleNetwork() bool {
+	return os.Getenv("RAC_RESTRICT_TO_SINGLE_NETWORK") == "true"
+}
+
 // GetServerInfo - gets the server config into memory from file or env
 // GetServerInfo - gets the server config into memory from file or env
 func GetServerInfo() models.ServerConfig {
 func GetServerInfo() models.ServerConfig {
 	var cfg models.ServerConfig
 	var cfg models.ServerConfig

+ 207 - 104
swagger.yaml

@@ -1,7 +1,6 @@
 definitions:
 definitions:
   acls.ACL:
   acls.ACL:
     additionalProperties:
     additionalProperties:
-      format: int32
       type: integer
       type: integer
     type: object
     type: object
   acls.ACLContainer:
   acls.ACLContainer:
@@ -56,15 +55,15 @@ definitions:
         type: string
         type: string
       egressesLimit:
       egressesLimit:
         type: integer
         type: integer
-      email_sender_addr:
+      emailSenderAddr:
         type: string
         type: string
-      email_sender_password:
+      emailSenderPassword:
         type: string
         type: string
-      email_sender_user:
+      emailSenderUser:
         type: string
         type: string
       emqxRestEndpoint:
       emqxRestEndpoint:
         type: string
         type: string
-      endpoint_detection:
+      endpointDetection:
         type: boolean
         type: boolean
       environment:
       environment:
         type: string
         type: string
@@ -91,6 +90,8 @@ definitions:
         type: string
         type: string
       metricsExporter:
       metricsExporter:
         type: string
         type: string
+      metricsPort:
+        type: integer
       mqpassword:
       mqpassword:
         type: string
         type: string
       mquserName:
       mquserName:
@@ -115,15 +116,17 @@ definitions:
         type: string
         type: string
       racAutoDisable:
       racAutoDisable:
         type: boolean
         type: boolean
+      racRestrictToSingleNetwork:
+        type: boolean
       restBackend:
       restBackend:
         type: string
         type: string
       server:
       server:
         type: string
         type: string
       serverBrokerEndpoint:
       serverBrokerEndpoint:
         type: string
         type: string
-      smtp_host:
+      smtpHost:
         type: string
         type: string
-      smtp_port:
+      smtpPort:
         type: integer
         type: integer
       sqlconn:
       sqlconn:
         type: string
         type: string
@@ -200,6 +203,14 @@ definitions:
         allOf:
         allOf:
         - $ref: '#/definitions/models.AllowedTrafficDirection'
         - $ref: '#/definitions/models.AllowedTrafficDirection'
         description: single or two-way
         description: single or two-way
+      dst:
+        items:
+          $ref: '#/definitions/net.IPNet'
+        type: array
+      dst6:
+        items:
+          $ref: '#/definitions/net.IPNet'
+        type: array
       id:
       id:
         type: string
         type: string
       ip_list:
       ip_list:
@@ -306,6 +317,10 @@ definitions:
         items:
         items:
           type: string
           type: string
         type: array
         type: array
+      egressgatewayranges_with_metric:
+        items:
+          $ref: '#/definitions/models.EgressRangeMetric'
+        type: array
       expdatetime:
       expdatetime:
         format: int64
         format: int64
         type: integer
         type: integer
@@ -424,9 +439,17 @@ definitions:
         items:
         items:
           type: string
           type: string
         type: array
         type: array
+      ranges_with_metric:
+        items:
+          $ref: '#/definitions/models.EgressRangeMetric'
+        type: array
     type: object
     type: object
   models.EgressInfo:
   models.EgressInfo:
     properties:
     properties:
+      egress_fw_rules:
+        additionalProperties:
+          $ref: '#/definitions/models.AclRule'
+        type: object
       egress_gateway_cfg:
       egress_gateway_cfg:
         $ref: '#/definitions/models.EgressGatewayRequest'
         $ref: '#/definitions/models.EgressGatewayRequest'
       egress_gw_addr:
       egress_gw_addr:
@@ -450,10 +473,26 @@ definitions:
         items:
         items:
           type: string
           type: string
         type: array
         type: array
+      egress_ranges_metric:
+        items:
+          $ref: '#/definitions/models.EgressRangeMetric'
+        type: array
+      network:
+        type: string
       node_addr:
       node_addr:
         $ref: '#/definitions/net.IPNet'
         $ref: '#/definitions/net.IPNet'
       node_addr6:
       node_addr6:
         $ref: '#/definitions/net.IPNet'
         $ref: '#/definitions/net.IPNet'
+      peer_key:
+        type: string
+    type: object
+  models.EgressRangeMetric:
+    properties:
+      network:
+        type: string
+      route_metric:
+        description: preffered range 1-999
+        type: integer
     type: object
     type: object
   models.EnrollmentKey:
   models.EnrollmentKey:
     properties:
     properties:
@@ -687,6 +726,7 @@ definitions:
   models.HostMqAction:
   models.HostMqAction:
     enum:
     enum:
     - UPGRADE
     - UPGRADE
+    - FORCE_UPGRADE
     - SIGNAL_HOST
     - SIGNAL_HOST
     - UPDATE_HOST
     - UPDATE_HOST
     - DELETE_HOST
     - DELETE_HOST
@@ -701,6 +741,7 @@ definitions:
     type: string
     type: string
     x-enum-varnames:
     x-enum-varnames:
     - Upgrade
     - Upgrade
+    - ForceUpgrade
     - SignalHost
     - SignalHost
     - UpdateHost
     - UpdateHost
     - DeleteHost
     - DeleteHost
@@ -724,6 +765,8 @@ definitions:
         type: boolean
         type: boolean
       listen_port:
       listen_port:
         type: integer
         type: integer
+      version:
+        type: string
     type: object
     type: object
   models.HostPull:
   models.HostPull:
     properties:
     properties:
@@ -821,8 +864,6 @@ definitions:
     type: object
     type: object
   models.IngressInfo:
   models.IngressInfo:
     properties:
     properties:
-      allow_all:
-        type: boolean
       egress_ranges:
       egress_ranges:
         items:
         items:
           $ref: '#/definitions/net.IPNet'
           $ref: '#/definitions/net.IPNet'
@@ -933,6 +974,10 @@ definitions:
         type: string
         type: string
       defaultudpholepunch:
       defaultudpholepunch:
         type: string
         type: string
+      dns_nameservers:
+        items:
+          type: string
+        type: array
       isipv4:
       isipv4:
         type: string
         type: string
       isipv6:
       isipv6:
@@ -1013,6 +1058,8 @@ definitions:
         type: string
         type: string
       is_fail_over:
       is_fail_over:
         type: boolean
         type: boolean
+      is_gw:
+        type: boolean
       is_static:
       is_static:
         type: boolean
         type: boolean
       is_user_node:
       is_user_node:
@@ -1090,6 +1137,7 @@ definitions:
     - warning
     - warning
     - error
     - error
     - unknown
     - unknown
+    - disconnected
     type: string
     type: string
     x-enum-varnames:
     x-enum-varnames:
     - OnlineSt
     - OnlineSt
@@ -1097,6 +1145,7 @@ definitions:
     - WarningSt
     - WarningSt
     - ErrorSt
     - ErrorSt
     - UnKnown
     - UnKnown
+    - Disconnected
   models.PeerMap:
   models.PeerMap:
     additionalProperties:
     additionalProperties:
       $ref: '#/definitions/models.IDandAddr'
       $ref: '#/definitions/models.IDandAddr'
@@ -1120,17 +1169,6 @@ definitions:
       server_config:
       server_config:
         $ref: '#/definitions/models.ServerConfig'
         $ref: '#/definitions/models.ServerConfig'
     type: object
     type: object
-  models.RelayRequest:
-    properties:
-      netid:
-        type: string
-      nodeid:
-        type: string
-      relayaddrs:
-        items:
-          type: string
-        type: array
-    type: object
   models.ReturnUser:
   models.ReturnUser:
     properties:
     properties:
       auth_type:
       auth_type:
@@ -1194,10 +1232,14 @@ definitions:
         type: string
         type: string
       dnsmode:
       dnsmode:
         type: string
         type: string
+      endpointDetection:
+        type: boolean
       manageDNS:
       manageDNS:
         type: boolean
         type: boolean
       metricInterval:
       metricInterval:
         type: string
         type: string
+      metricsPort:
+        type: integer
       mqpassword:
       mqpassword:
         type: string
         type: string
       mqport:
       mqport:
@@ -1293,7 +1335,6 @@ definitions:
           type: object
           type: object
         type: object
         type: object
       username:
       username:
-        maxLength: 40
         minLength: 3
         minLength: 3
         type: string
         type: string
     required:
     required:
@@ -1308,12 +1349,16 @@ definitions:
     type: object
     type: object
   models.UserRemoteGws:
   models.UserRemoteGws:
     properties:
     properties:
+      addresses:
+        type: string
       allowed_endpoints:
       allowed_endpoints:
         items:
         items:
           type: string
           type: string
         type: array
         type: array
       connected:
       connected:
         type: boolean
         type: boolean
+      dns_address:
+        type: string
       gw_client:
       gw_client:
         $ref: '#/definitions/models.ExtClient'
         $ref: '#/definitions/models.ExtClient'
       gw_listen_port:
       gw_listen_port:
@@ -1334,6 +1379,8 @@ definitions:
         type: array
         type: array
       remote_access_gw_id:
       remote_access_gw_id:
         type: string
         type: string
+      status:
+        $ref: '#/definitions/models.NodeStatus'
     type: object
     type: object
   models.UserRoleID:
   models.UserRoleID:
     enum:
     enum:
@@ -1390,7 +1437,6 @@ definitions:
       mask:
       mask:
         description: network mask
         description: network mask
         items:
         items:
-          format: int32
           type: integer
           type: integer
         type: array
         type: array
     type: object
     type: object
@@ -1427,7 +1473,6 @@ definitions:
           for this peer, if not nil.
           for this peer, if not nil.
 
 
           A non-nil value of 0 will clear the persistent keepalive interval.
           A non-nil value of 0 will clear the persistent keepalive interval.
-        format: int64
         type: integer
         type: integer
       presharedKey:
       presharedKey:
         description: |-
         description: |-
@@ -1466,7 +1511,7 @@ info:
   contact: {}
   contact: {}
   description: NetMaker API Docs
   description: NetMaker API Docs
   title: NetMaker
   title: NetMaker
-  version: 0.30.0
+  version: 0.90.0
 paths:
 paths:
   /api/dns:
   /api/dns:
     get:
     get:
@@ -1826,6 +1871,28 @@ paths:
       summary: Get the current public IP address.
       summary: Get the current public IP address.
       tags:
       tags:
       - IP Service
       - IP Service
+  /api/host/{hostid}/peer_info:
+    get:
+      parameters:
+      - description: Host ID
+        in: path
+        name: hostid
+        required: true
+        type: string
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/models.SuccessResponse'
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      security:
+      - oauth: []
+      summary: Fetches host peerinfo
+      tags:
+      - Hosts
   /api/hosts:
   /api/hosts:
     get:
     get:
       responses:
       responses:
@@ -2006,6 +2073,10 @@ paths:
         name: hostid
         name: hostid
         required: true
         required: true
         type: string
         type: string
+      - description: Force upgrade
+        in: query
+        name: force
+        type: boolean
       responses:
       responses:
         "200":
         "200":
           description: passed message to upgrade host
           description: passed message to upgrade host
@@ -2067,6 +2138,35 @@ paths:
       summary: Update keys for all hosts
       summary: Update keys for all hosts
       tags:
       tags:
       - Hosts
       - Hosts
+  /api/hosts/sync:
+    post:
+      responses:
+        "200":
+          description: sync all hosts request received
+          schema:
+            type: string
+      security:
+      - oauth: []
+      summary: Requests all the hosts to pull
+      tags:
+      - Hosts
+  /api/hosts/upgrade:
+    post:
+      parameters:
+      - description: Force upgrade
+        in: query
+        name: force
+        type: boolean
+      responses:
+        "200":
+          description: upgrade all hosts request received
+          schema:
+            type: string
+      security:
+      - oauth: []
+      summary: Requests all the hosts to upgrade their version
+      tags:
+      - Hosts
   /api/networks:
   /api/networks:
     get:
     get:
       produces:
       produces:
@@ -2117,6 +2217,10 @@ paths:
         name: networkname
         name: networkname
         required: true
         required: true
         type: string
         type: string
+      - description: Force Delete
+        in: query
+        name: force
+        type: boolean
       produces:
       produces:
       - application/json
       - application/json
       responses:
       responses:
@@ -2280,6 +2384,30 @@ paths:
       summary: Update a network ACL (Access Control List)
       summary: Update a network ACL (Access Control List)
       tags:
       tags:
       - Networks
       - Networks
+  /api/networks/{networkname}/egress_routes:
+    get:
+      parameters:
+      - description: Network name
+        in: path
+        name: networkname
+        required: true
+        type: string
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/models.SuccessResponse'
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      security:
+      - oauth: []
+      summary: Get a network Egress routes
+      tags:
+      - Networks
   /api/nodes:
   /api/nodes:
     get:
     get:
       responses:
       responses:
@@ -2358,61 +2486,6 @@ paths:
       summary: Create an egress gateway
       summary: Create an egress gateway
       tags:
       tags:
       - Nodes
       - Nodes
-  /api/nodes/{network}/{nodeid}/createingress:
-    post:
-      responses:
-        "200":
-          description: OK
-          schema:
-            $ref: '#/definitions/models.ApiNode'
-        "500":
-          description: Internal Server Error
-          schema:
-            $ref: '#/definitions/models.ErrorResponse'
-      security:
-      - oauth2: []
-      summary: Create an remote access gateway
-      tags:
-      - Nodes
-  /api/nodes/{network}/{nodeid}/createrelay:
-    post:
-      consumes:
-      - application/json
-      parameters:
-      - description: Network ID
-        in: path
-        name: network
-        required: true
-        type: string
-      - description: Node ID
-        in: path
-        name: nodeid
-        required: true
-        type: string
-      - description: Relay request parameters
-        in: body
-        name: body
-        required: true
-        schema:
-          $ref: '#/definitions/models.RelayRequest'
-      produces:
-      - application/json
-      responses:
-        "200":
-          description: OK
-          schema:
-            $ref: '#/definitions/models.ApiNode'
-        "400":
-          description: Bad Request
-          schema:
-            $ref: '#/definitions/models.ErrorResponse'
-        "500":
-          description: Internal Server Error
-          schema:
-            $ref: '#/definitions/models.ErrorResponse'
-      summary: Create a relay
-      tags:
-      - PRO
   /api/nodes/{network}/{nodeid}/deletegateway:
   /api/nodes/{network}/{nodeid}/deletegateway:
     delete:
     delete:
       responses:
       responses:
@@ -2429,7 +2502,7 @@ paths:
       summary: Delete an egress gateway
       summary: Delete an egress gateway
       tags:
       tags:
       - Nodes
       - Nodes
-  /api/nodes/{network}/{nodeid}/deleteingress:
+  /api/nodes/{network}/{nodeid}/gateway:
     delete:
     delete:
       responses:
       responses:
         "200":
         "200":
@@ -2442,42 +2515,24 @@ paths:
             $ref: '#/definitions/models.ErrorResponse'
             $ref: '#/definitions/models.ErrorResponse'
       security:
       security:
       - oauth2: []
       - oauth2: []
-      summary: Delete an remote access gateway
+      summary: Delete a gateway
       tags:
       tags:
       - Nodes
       - Nodes
-  /api/nodes/{network}/{nodeid}/deleterelay:
-    delete:
-      consumes:
-      - application/json
-      parameters:
-      - description: Network ID
-        in: path
-        name: network
-        required: true
-        type: string
-      - description: Node ID
-        in: path
-        name: nodeid
-        required: true
-        type: string
-      produces:
-      - application/json
+    post:
       responses:
       responses:
         "200":
         "200":
           description: OK
           description: OK
           schema:
           schema:
             $ref: '#/definitions/models.ApiNode'
             $ref: '#/definitions/models.ApiNode'
-        "400":
-          description: Bad Request
-          schema:
-            $ref: '#/definitions/models.ErrorResponse'
         "500":
         "500":
           description: Internal Server Error
           description: Internal Server Error
           schema:
           schema:
             $ref: '#/definitions/models.ErrorResponse'
             $ref: '#/definitions/models.ErrorResponse'
-      summary: Remove a relay
+      security:
+      - oauth2: []
+      summary: Create a gateway
       tags:
       tags:
-      - PRO
+      - Nodes
   /api/nodes/{network}/{nodeid}/inet_gw:
   /api/nodes/{network}/{nodeid}/inet_gw:
     delete:
     delete:
       parameters:
       parameters:
@@ -3425,6 +3480,38 @@ paths:
       summary: Create failover node
       summary: Create failover node
       tags:
       tags:
       - PRO
       - PRO
+  /api/v1/node/{nodeid}/failover_check:
+    get:
+      consumes:
+      - application/json
+      parameters:
+      - description: Node ID
+        in: path
+        name: nodeid
+        required: true
+        type: string
+      - description: Failover request
+        in: body
+        name: body
+        required: true
+        schema:
+          $ref: '#/definitions/models.FailOverMeReq'
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/models.SuccessResponse'
+        "400":
+          description: Bad Request
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: checkfailOverCtx
+      tags:
+      - PRO
   /api/v1/node/{nodeid}/failover_me:
   /api/v1/node/{nodeid}/failover_me:
     post:
     post:
       consumes:
       consumes:
@@ -3457,6 +3544,22 @@ paths:
       summary: Failover me
       summary: Failover me
       tags:
       tags:
       - PRO
       - PRO
+  /api/v1/nodes/{network}/status:
+    get:
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/models.ApiNode'
+            type: array
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: Get all nodes status on the network
+      tags:
+      - Nodes
   /api/v1/tags:
   /api/v1/tags:
     delete:
     delete:
       consumes:
       consumes:

+ 16 - 0
utils/utils.go

@@ -3,6 +3,7 @@ package utils
 import (
 import (
 	"log/slog"
 	"log/slog"
 	"runtime"
 	"runtime"
+	"strings"
 	"time"
 	"time"
 )
 )
 
 
@@ -59,3 +60,18 @@ func TraceCaller() {
 	slog.Debug("Called from function: %s\n", "func-name", funcName)
 	slog.Debug("Called from function: %s\n", "func-name", funcName)
 	slog.Debug("File: %s, Line: %d\n", "file", file, "line-no", line)
 	slog.Debug("File: %s, Line: %d\n", "file", file, "line-no", line)
 }
 }
+
+// NoEmptyStringToCsv takes a bunch of strings, filters out empty ones and returns a csv version of the string
+func NoEmptyStringToCsv(strs ...string) string {
+	var sb strings.Builder
+	for _, str := range strs {
+		trimmedStr := strings.TrimSpace(str)
+		if trimmedStr != "" && trimmedStr != "<nil>" {
+			if sb.Len() > 0 {
+				sb.WriteString(", ")
+			}
+			sb.WriteString(str)
+		}
+	}
+	return sb.String()
+}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно