Forráskód Böngészése

resolve merge conflicts

abhishek9686 4 hónapja
szülő
commit
ffb312189c

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

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

+ 1 - 1
README.md

@@ -16,7 +16,7 @@
 
 <p align="center">
   <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 href="https://hub.docker.com/r/gravitl/netmaker/tags">
     <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:
   netclient:
     container_name: netclient
-    image: 'gravitl/netclient:v0.30.0'
+    image: 'gravitl/netclient:v0.90.0'
     hostname: netmaker-1
     network_mode: host
     restart: on-failure

+ 1 - 0
config/config.go

@@ -91,6 +91,7 @@ type ServerConfig struct {
 	Environment                string        `yaml:"environment"`
 	JwtValidityDuration        time.Duration `yaml:"jwt_validity_duration" swaggertype:"primitive,integer" format:"int64"`
 	RacAutoDisable             bool          `yaml:"rac_auto_disable"`
+	RacRestrictToSingleNetwork bool          `yaml:"rac_restrict_to_single_network"`
 	CacheEnabled               string        `yaml:"caching_enabled"`
 	EndpointDetection          bool          `yaml:"endpoint_detection"`
 	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{
 			models.UserAclID,
 			models.UserGroupAclID,
-			models.DeviceAclID,
+			models.NodeTagID,
+			models.NodeID,
 		},
 		DstGroupTypes: []models.AclGroupType{
-			models.DeviceAclID,
+			models.NodeTagID,
+			models.NodeID,
+			models.EgressRange,
 			// models.NetmakerIPAclID,
 			// models.NetmakerSubNetRangeAClID,
 		},
@@ -117,6 +120,13 @@ func aclPolicyTypes(w http.ResponseWriter, r *http.Request) {
 				},
 				PortRange: "",
 			},
+			{
+				Name: models.SSH,
+				AllowedProtocols: []models.Protocol{
+					models.TCP,
+				},
+				PortRange: "22",
+			},
 			{
 				Name: models.Custom,
 				AllowedProtocols: []models.Protocol{
@@ -134,18 +144,49 @@ func aclPolicyTypes(w http.ResponseWriter, r *http.Request) {
 func aclDebug(w http.ResponseWriter, r *http.Request) {
 	nodeID, _ := url.QueryUnescape(r.URL.Query().Get("node"))
 	peerID, _ := url.QueryUnescape(r.URL.Query().Get("peer"))
+	peerIsStatic, _ := url.QueryUnescape(r.URL.Query().Get("peer_is_static"))
 	node, err := logic.GetNodeByID(nodeID)
 	if err != nil {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 	}
-	peer, err := logic.GetNodeByID(peerID)
-	if err != nil {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		return
+	var peer models.Node
+	if peerIsStatic == "true" {
+		extclient, err := logic.GetExtClient(peerID, node.Network)
+		if err != nil {
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+			return
+		}
+		peer = extclient.ConvertToStaticNode()
+
+	} else {
+		peer, err = logic.GetNodeByID(peerID)
+		if err != nil {
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+			return
+		}
+	}
+	type resp struct {
+		IsNodeAllowed bool
+		IsPeerAllowed bool
+		Policies      []models.Acl
+		IngressRules  []models.FwRule
+	}
+
+	allowed, ps := logic.IsNodeAllowedToCommunicateV1(node, peer, true)
+	isallowed := logic.IsPeerAllowed(node, peer, true)
+	re := resp{
+		IsNodeAllowed: allowed,
+		IsPeerAllowed: isallowed,
+		Policies:      ps,
+	}
+	if peerIsStatic == "true" {
+		ingress, err := logic.GetNodeByID(peer.StaticNode.IngressGatewayID)
+		if err == nil {
+			re.IngressRules = logic.GetFwRulesOnIngressGateway(ingress)
+		}
 	}
-	allowed, _ := logic.IsNodeAllowedToCommunicate(node, peer, true)
-	logic.ReturnSuccessResponseWithJson(w, r, allowed, "fetched all acls in the network ")
+	logic.ReturnSuccessResponseWithJson(w, r, re, "fetched all acls in the network ")
 }
 
 // @Summary     List Acls in a network

+ 39 - 5
controllers/ext_client.go

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

+ 5 - 53
controllers/hosts.go

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

+ 34 - 1
controllers/network.go

@@ -41,6 +41,7 @@ func networkHandlers(r *mux.Router) {
 		Methods(http.MethodPut)
 	r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(getNetworkACL))).
 		Methods(http.MethodGet)
+	r.HandleFunc("/api/networks/{networkname}/egress_routes", logic.SecurityCheck(true, http.HandlerFunc(getNetworkEgressRoutes)))
 }
 
 // @Summary     Lists all networks
@@ -429,6 +430,33 @@ func getNetworkACL(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(networkACL)
 }
 
+// @Summary     Get a network Egress routes
+// @Router      /api/networks/{networkname}/egress_routes [get]
+// @Tags        Networks
+// @Security    oauth
+// @Param       networkname path string true "Network name"
+// @Produce     json
+// @Success     200 {object} 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
 // @Router      /api/networks/{networkname} [delete]
 // @Tags        Networks
@@ -466,7 +494,8 @@ func deleteNetwork(w http.ResponseWriter, r *http.Request) {
 	}
 	go logic.UnlinkNetworkAndTagsFromEnrollmentKeys(network, true)
 	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
 	go logic.RemoveNetworkFromAllocatedIpMap(network)
 	go func() {
@@ -598,6 +627,10 @@ func createNetwork(w http.ResponseWriter, r *http.Request) {
 			logic.CreateFailOver(*newNode)
 			// make host remote access gateway
 			logic.CreateIngressGateway(network.NetID, newNode.ID.String(), models.IngressRequest{})
+			logic.CreateRelay(models.RelayRequest{
+				NodeID: newNode.ID.String(),
+				NetID:  network.NetID,
+			})
 		}
 		// send peer updates
 		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.GetNodeStatus(newNode, false)
+
 	apiNode := newNode.ConvertToAPINode()
 	logger.Log(
 		1,

+ 2 - 2
controllers/server.go

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

+ 9 - 0
controllers/tags.go

@@ -88,6 +88,7 @@ func createTag(w http.ResponseWriter, r *http.Request) {
 		TagName:   req.TagName,
 		Network:   req.Network,
 		CreatedBy: user.UserName,
+		ColorCode: req.ColorCode,
 		CreatedAt: time.Now(),
 	}
 	_, err = logic.GetTag(tag.ID)
@@ -182,6 +183,14 @@ func updateTag(w http.ResponseWriter, r *http.Request) {
 		// delete old Tag entry
 		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() {
 		logic.UpdateTag(updateTag, newID)
 		if updateTag.NewName != "" {

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

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

+ 1 - 1
k8s/client/netclient.yaml

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

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

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

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 547 - 172
logic/acls.go


+ 1 - 0
logic/auth.go

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

+ 300 - 216
logic/extpeers.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"net"
 	"reflect"
+	"sort"
 	"strings"
 	"sync"
 	"time"
@@ -115,6 +116,7 @@ func DeleteExtClient(network string, clientid string) error {
 		}
 		deleteExtClientFromCache(key)
 	}
+	go RemoveNodeFromAclPolicy(extClient.ConvertToStaticNode())
 	return nil
 }
 
@@ -416,6 +418,27 @@ func GetAllExtClients() ([]models.ExtClient, error) {
 	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
 func ToggleExtClientConnectivity(client *models.ExtClient, enable bool) (models.ExtClient, error) {
 	update := models.CustomExtClient{
@@ -442,7 +465,18 @@ func ToggleExtClientConnectivity(client *models.ExtClient, enable bool) (models.
 	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) {
+	defer func() {
+		sortIPs(ips)
+	}()
 	defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
 	defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
 
@@ -464,210 +498,192 @@ func GetStaticNodeIps(node models.Node) (ips []net.IP) {
 	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{
-								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,
 								AllowedPorts:    policy.Port,
 								Allow:           true,
 							})
-							if policy.AllowedDirection == models.TrafficDirectionBi {
-								rules = append(rules, models.FwRule{
-									SrcIP:           peer.StaticNode.AddressIPNet4(),
-									DstIP:           nodeI.StaticNode.AddressIPNet4(),
-									AllowedProtocol: policy.Proto,
-									AllowedPorts:    policy.Port,
-									Allow:           true,
-								})
-							}
 						}
-
-					}
-					if nodeI.StaticNode.Address6 != "" {
-						for _, policy := range allowedPolicies {
+					} else {
+						if node.Address6.IP != nil {
 							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,
 								AllowedPorts:    policy.Port,
 								Allow:           true,
 							})
-							if policy.AllowedDirection == models.TrafficDirectionBi {
-								rules = append(rules, models.FwRule{
-									SrcIP:           peer.StaticNode.AddressIPNet6(),
-									DstIP:           nodeI.StaticNode.AddressIPNet6(),
-									AllowedProtocol: policy.Proto,
-									AllowedPorts:    policy.Port,
-									Allow:           true,
-								})
-							}
 						}
 					}
-					if len(peer.StaticNode.ExtraAllowedIPs) > 0 {
-						for _, additionalAllowedIPNet := range peer.StaticNode.ExtraAllowedIPs {
-							_, ipNet, err := net.ParseCIDR(additionalAllowedIPNet)
-							if err != nil {
-								continue
-							}
-							if ipNet.IP.To4() != nil {
-								rules = append(rules, models.FwRule{
-									SrcIP: nodeI.StaticNode.AddressIPNet4(),
-									DstIP: *ipNet,
-									Allow: true,
-								})
-							} else {
-								rules = append(rules, models.FwRule{
-									SrcIP: nodeI.StaticNode.AddressIPNet6(),
-									DstIP: *ipNet,
-									Allow: true,
-								})
-							}
 
-						}
+				}
+			}
+		}
+	}
 
-					}
-				} else {
-					if nodeI.StaticNode.Address != "" {
-						for _, policy := range allowedPolicies {
+	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{
-								SrcIP: nodeI.StaticNode.AddressIPNet4(),
+								SrcIP: userNodeI.StaticNode.AddressIPNet4(),
 								DstIP: net.IPNet{
 									IP:   peer.Address.IP,
 									Mask: net.CIDRMask(32, 32),
@@ -676,24 +692,10 @@ func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
 								AllowedPorts:    policy.Port,
 								Allow:           true,
 							})
-							if policy.AllowedDirection == models.TrafficDirectionBi {
-								rules = append(rules, models.FwRule{
-									SrcIP: net.IPNet{
-										IP:   peer.Address.IP,
-										Mask: net.CIDRMask(32, 32),
-									},
-									DstIP:           nodeI.StaticNode.AddressIPNet4(),
-									AllowedProtocol: policy.Proto,
-									AllowedPorts:    policy.Port,
-									Allow:           true,
-								})
-							}
 						}
-					}
-					if nodeI.StaticNode.Address6 != "" {
-						for _, policy := range allowedPolicies {
+						if userNodeI.StaticNode.Address6 != "" {
 							rules = append(rules, models.FwRule{
-								SrcIP: nodeI.StaticNode.AddressIPNet6(),
+								SrcIP: userNodeI.StaticNode.AddressIPNet6(),
 								DstIP: net.IPNet{
 									IP:   peer.Address6.IP,
 									Mask: net.CIDRMask(128, 128),
@@ -702,19 +704,34 @@ func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
 								AllowedPorts:    policy.Port,
 								Allow:           true,
 							})
-							if policy.AllowedDirection == models.TrafficDirectionBi {
-								rules = append(rules, models.FwRule{
-									SrcIP: net.IPNet{
-										IP:   peer.Address6.IP,
-										Mask: net.CIDRMask(128, 128),
-									},
-									DstIP:           nodeI.StaticNode.AddressIPNet6(),
-									AllowedProtocol: policy.Proto,
-									AllowedPorts:    policy.Port,
-									Allow:           true,
-								})
+						}
+
+						// 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
 }
 
+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) {
 	var peers []wgtypes.PeerConfig
 	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) {
-	egressRoutes = append(egressRoutes, models.EgressNetworkRoutes{
+	r := models.EgressNetworkRoutes{
+		PeerKey:       extPeer.PublicKey,
 		EgressGwAddr:  extPeer.AddressIPNet4(),
 		EgressGwAddr6: extPeer.AddressIPNet6(),
 		NodeAddr:      node.Address,
 		NodeAddr6:     node.Address6,
 		EgressRanges:  extPeer.ExtraAllowedIPs,
-	})
+	}
+	for _, extraAllowedIP := range extPeer.ExtraAllowedIPs {
+		r.EgressRangesWithMetric = append(r.EgressRangesWithMetric, models.EgressRangeMetric{
+			Network:     extraAllowedIP,
+			RouteMetric: 256,
+		})
+	}
+	egressRoutes = append(egressRoutes, r)
 	return
 }
 
@@ -831,7 +920,7 @@ func getExtpeerEgressRanges(node models.Node) (ranges, ranges6 []net.IPNet) {
 		if len(extPeer.ExtraAllowedIPs) == 0 {
 			continue
 		}
-		if ok, _ := IsNodeAllowedToCommunicate(extPeer.ConvertToStaticNode(), node, true); !ok {
+		if ok, _ := IsNodeAllowedToCommunicateV1(extPeer.ConvertToStaticNode(), node, true); !ok {
 			continue
 		}
 		for _, allowedRange := range extPeer.ExtraAllowedIPs {
@@ -858,7 +947,7 @@ func getExtpeersExtraRoutes(node models.Node) (egressRoutes []models.EgressNetwo
 		if len(extPeer.ExtraAllowedIPs) == 0 {
 			continue
 		}
-		if ok, _ := IsNodeAllowedToCommunicate(extPeer.ConvertToStaticNode(), node, true); !ok {
+		if ok, _ := IsNodeAllowedToCommunicateV1(extPeer.ConvertToStaticNode(), node, true); !ok {
 			continue
 		}
 		egressRoutes = append(egressRoutes, getExtPeerEgressRoute(node, extPeer)...)
@@ -906,16 +995,11 @@ func GetStaticUserNodesByNetwork(network models.NetworkID) (staticNode []models.
 	for _, extI := range extClients {
 		if extI.Network == network.String() {
 			if extI.RemoteAccessClientID != "" {
-				n := models.Node{
-					IsStatic:   true,
-					StaticNode: extI,
-					IsUserNode: extI.RemoteAccessClientID != "",
-				}
+				n := extI.ConvertToStaticNode()
 				staticNode = append(staticNode, n)
 			}
 		}
 	}
-
 	return
 }
 

+ 2 - 2
logic/gateway.go

@@ -220,7 +220,7 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
 	if node.Tags == nil {
 		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)
 	if err != nil {
 		return models.Node{}, err
@@ -272,7 +272,7 @@ func DeleteIngressGateway(nodeid string) (models.Node, []models.ExtClient, error
 	if !servercfg.IsPro {
 		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.Metadata = ""
 	err = UpsertNode(&node)

+ 27 - 0
logic/hosts.go

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

+ 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.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())
 	peerIndexMap := make(map[string]int)
 	for _, nodeID := range host.Nodes {
+		networkAllowAll := true
 		nodeID := nodeID
 		node, err := GetNodeByID(nodeID)
 		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 {
 			continue
 		}
-		if host.OS == models.OS_Types.IoT {
-			hostPeerUpdate.NodeAddrs = append(hostPeerUpdate.NodeAddrs, node.PrimaryAddressIPNet())
-			if node.IsRelayed {
-				relayNode, err := GetNodeByID(node.RelayedBy)
-				if err != nil {
-					continue
-				}
-				relayHost, err := GetHost(relayNode.HostID.String())
-				if err != nil {
-					continue
-				}
-				relayPeer := wgtypes.PeerConfig{
-					PublicKey:                   relayHost.PublicKey,
-					PersistentKeepaliveInterval: &relayHost.PersistentKeepalive,
-					ReplaceAllowedIPs:           true,
-					AllowedIPs:                  GetAllowedIPs(&node, &relayNode, nil),
-				}
-				uselocal := false
-				if host.EndpointIP.String() == relayHost.EndpointIP.String() {
-					// peer is on same network
-					// set to localaddress
-					uselocal = true
-					if node.LocalAddress.IP == nil {
-						// use public endpint
-						uselocal = false
-					}
-					if node.LocalAddress.String() == relayNode.LocalAddress.String() {
-						uselocal = false
-					}
-				}
-				relayPeer.Endpoint = &net.UDPAddr{
-					IP:   relayHost.EndpointIP,
-					Port: GetPeerListenPort(relayHost),
-				}
-
-				if uselocal {
-					relayPeer.Endpoint.IP = relayNode.LocalAddress.IP
-					relayPeer.Endpoint.Port = relayHost.ListenPort
-				}
-
-				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, relayPeer)
-			} else if deletedNode != nil && deletedNode.IsRelay {
-				relayHost, err := GetHost(deletedNode.HostID.String())
-				if err != nil {
-					continue
-				}
-				relayPeer := wgtypes.PeerConfig{
-					PublicKey: relayHost.PublicKey,
-					Remove:    true,
-				}
-				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, relayPeer)
-			}
-			continue
-		}
 		hostPeerUpdate = SetDefaultGw(node, hostPeerUpdate)
 		if !hostPeerUpdate.IsInternetGw {
 			hostPeerUpdate.IsInternetGw = IsInternetGw(node)
@@ -251,15 +204,15 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 		defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
 		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 {
 				hostPeerUpdate.FwUpdate.AllowedNetworks = append(hostPeerUpdate.FwUpdate.AllowedNetworks, node.NetworkRange)
 			}
 			if node.NetworkRange6.IP != nil {
 				hostPeerUpdate.FwUpdate.AllowedNetworks = append(hostPeerUpdate.FwUpdate.AllowedNetworks, node.NetworkRange6)
 			}
-
 		} else {
+			networkAllowAll = false
 			hostPeerUpdate.FwUpdate.AllowAll = false
 			rules := GetAclRulesForNode(&node)
 			if len(hostPeerUpdate.FwUpdate.AclRules) == 0 {
@@ -294,20 +247,45 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 				PersistentKeepaliveInterval: &peerHost.PersistentKeepalive,
 				ReplaceAllowedIPs:           true,
 			}
+			_, isFailOverPeer := node.FailOverPeers[peer.ID.String()]
 			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{
+					PeerKey:                peerKey,
 					EgressGwAddr:           peer.Address,
 					EgressGwAddr6:          peer.Address6,
 					NodeAddr:               node.Address,
 					NodeAddr6:              node.Address6,
-					EgressRanges:           peer.EgressGatewayRanges,
-					EgressRangesWithMetric: peer.EgressGatewayRequest.RangesWithMetric,
+					EgressRanges:           filterConflictingEgressRoutes(node, peer),
+					EgressRangesWithMetric: filterConflictingEgressRoutesWithMetric(node, peer),
+					Network:                peer.Network,
 				})
 			}
 			if peer.IsIngressGateway {
 				hostPeerUpdate.EgressRoutes = append(hostPeerUpdate.EgressRoutes, getExtpeersExtraRoutes(node)...)
 			}
-			_, isFailOverPeer := node.FailOverPeers[peer.ID.String()]
+
 			if (node.IsRelayed && node.RelayedBy != peer.ID.String()) ||
 				(peer.IsRelayed && peer.RelayedBy != node.ID.String()) || isFailOverPeer {
 				// 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 {
-				peerConfig.Endpoint.IP = peer.LocalAddress.IP
 				peerConfig.Endpoint.Port = peerHost.ListenPort
 			}
 			var allowedToComm bool
@@ -439,7 +416,6 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 						IngressID:     node.ID.String(),
 						Network:       node.NetworkRange,
 						Network6:      node.NetworkRange6,
-						AllowAll:      defaultDevicePolicy.Enabled && defaultUserPolicy.Default,
 						StaticNodeIps: GetStaticNodeIps(node),
 						Rules:         GetFwRulesOnIngressGateway(node),
 					}
@@ -473,10 +449,23 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 					IP:   node.Address6.IP,
 					Mask: getCIDRMaskFromAddr(node.Address6.IP.String()),
 				},
-				EgressGWCfg: node.EgressGatewayRequest,
+				EgressGWCfg:   node.EgressGatewayRequest,
+				EgressFwRules: make(map[string]models.AclRule),
 			}
 
 		}
+		if node.IsEgressGateway {
+			if !networkAllowAll {
+				egressInfo := hostPeerUpdate.FwUpdate.EgressInfo[node.ID.String()]
+				if egressInfo.EgressFwRules == nil {
+					egressInfo.EgressFwRules = make(map[string]models.AclRule)
+				}
+				egressInfo.EgressFwRules = GetEgressRulesForNode(node)
+				hostPeerUpdate.FwUpdate.EgressInfo[node.ID.String()] = egressInfo
+			}
+
+		}
+
 		if IsInternetGw(node) {
 			hostPeerUpdate.FwUpdate.IsEgressGw = true
 			egressrange := []string{"0.0.0.0/0"}
@@ -559,6 +548,42 @@ func GetPeerListenPort(host *models.Host) int {
 	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
 func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics) []net.IPNet {
 	var allowedips []net.IPNet
@@ -647,6 +672,18 @@ func getNodeAllowedIPs(peer, node *models.Node) []net.IPNet {
 	if peer.IsEgressGateway {
 		// hasGateway = true
 		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...)
 	}
 	if peer.IsRelay {

+ 18 - 4
logic/tags.go

@@ -30,6 +30,14 @@ func GetTag(tagID models.TagID) (models.Tag, error) {
 	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
 func InsertTag(tag models.Tag) error {
 	tagMutex.Lock()
@@ -97,6 +105,12 @@ func ListTagsWithNodes(netID models.NetworkID) ([]models.TagListResp, error) {
 	}
 	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
 func ListTags() ([]models.Tag, error) {
@@ -270,10 +284,10 @@ func CheckIDSyntax(id string) error {
 }
 
 func CreateDefaultTags(netID models.NetworkID) {
-	// create tag for remote access gws in the network
+	// create tag for gws in the network
 	tag := models.Tag{
-		ID:        models.TagID(fmt.Sprintf("%s.%s", netID.String(), models.RemoteAccessTagName)),
-		TagName:   models.RemoteAccessTagName,
+		ID:        models.TagID(fmt.Sprintf("%s.%s", netID.String(), models.GwTagName)),
+		TagName:   models.GwTagName,
 		Network:   netID,
 		CreatedBy: "auto",
 		CreatedAt: time.Now(),
@@ -284,7 +298,7 @@ func CreateDefaultTags(netID models.NetworkID) {
 	}
 	err = InsertTag(tag)
 	if err != nil {
-		slog.Error("failed to create remote access gw tag", "error", err.Error())
+		slog.Error("failed to create gw tag", "error", err.Error())
 		return
 	}
 }

+ 10 - 1
logic/zombie.go

@@ -77,7 +77,7 @@ func checkForZombieHosts(h *models.Host) {
 func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 	logger.Log(2, "Zombie management started")
 	go InitializeZombies()
-
+	go checkPendingRemovalNodes()
 	// Zombie Nodes Cleanup Four Times a Day
 	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)
 func InitializeZombies() {

+ 2 - 2
main.go

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

+ 39 - 10
migrate/migrate.go

@@ -204,15 +204,6 @@ func updateNodes() {
 			logic.UpsertNode(&node)
 		}
 		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())
 			if err == nil {
 				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) {
@@ -448,7 +446,8 @@ func createDefaultTagsAndPolicies() {
 	for _, network := range networks {
 		logic.CreateDefaultTags(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()
 }
@@ -463,7 +462,37 @@ func migrateToGws() {
 			node.IsGw = true
 			node.IsIngressGateway = 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)
 		}
 	}
+	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"
 )
 
-type ServiceType string
-
 const (
 	Http        = "HTTP"
 	Https       = "HTTPS"
 	AllTCP      = "All TCP"
 	AllUDP      = "All UDP"
 	ICMPService = "ICMP"
+	SSH         = "SSH"
 	Custom      = "Custom"
 	Any         = "Any"
 )
@@ -58,7 +57,9 @@ type AclGroupType string
 const (
 	UserAclID                AclGroupType = "user"
 	UserGroupAclID           AclGroupType = "user-group"
-	DeviceAclID              AclGroupType = "tag"
+	NodeTagID                AclGroupType = "tag"
+	NodeID                   AclGroupType = "device"
+	EgressRange              AclGroupType = "egress-range"
 	NetmakerIPAclID          AclGroupType = "ip"
 	NetmakerSubNetRangeAClID AclGroupType = "ipset"
 )
@@ -116,5 +117,7 @@ type AclRule struct {
 	AllowedProtocol Protocol                `json:"allowed_protocols"` // tcp, udp, etc.
 	AllowedPorts    []string                `json:"allowed_ports"`
 	Direction       AllowedTrafficDirection `json:"direction"` // single or two-way
+	Dst             []net.IPNet             `json:"dst"`
+	Dst6            []net.IPNet             `json:"dst6"`
 	Allowed         bool
 }

+ 7 - 2
models/extclient.go

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

+ 3 - 1
models/mqtt.go

@@ -53,7 +53,6 @@ type IngressInfo struct {
 	Network6      net.IPNet   `json:"network6"`
 	StaticNodeIps []net.IP    `json:"static_node_ips"`
 	Rules         []FwRule    `json:"rules"`
-	AllowAll      bool        `json:"allow_all"`
 	EgressRanges  []net.IPNet `json:"egress_ranges"`
 	EgressRanges6 []net.IPNet `json:"egress_ranges6"`
 }
@@ -66,16 +65,19 @@ type EgressInfo struct {
 	Network6      net.IPNet            `json:"network6" yaml:"network6"`
 	EgressGwAddr6 net.IPNet            `json:"egress_gw_addr6" yaml:"egress_gw_addr6"`
 	EgressGWCfg   EgressGatewayRequest `json:"egress_gateway_cfg" yaml:"egress_gateway_cfg"`
+	EgressFwRules map[string]AclRule   `json:"egress_fw_rules"`
 }
 
 // EgressNetworkRoutes - struct for egress network routes for adding routes to peer's interface
 type EgressNetworkRoutes struct {
+	PeerKey                string              `json:"peer_key"`
 	EgressGwAddr           net.IPNet           `json:"egress_gw_addr" yaml:"egress_gw_addr"`
 	EgressGwAddr6          net.IPNet           `json:"egress_gw_addr6" yaml:"egress_gw_addr6"`
 	NodeAddr               net.IPNet           `json:"node_addr"`
 	NodeAddr6              net.IPNet           `json:"node_addr6"`
 	EgressRanges           []string            `json:"egress_ranges"`
 	EgressRangesWithMetric []EgressRangeMetric `json:"egress_ranges_metric"`
+	Network                string              `json:"network"`
 }
 
 // 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"`
 	NetworkAddresses  []string   `json:"network_addresses"`
 	Status            NodeStatus `json:"status"`
+	DnsAddress        string     `json:"dns_address"`
+	Addresses         string     `json:"addresses"`
 }
 
 // UserRAGs - struct for user access gws

+ 5 - 1
models/tags.go

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

+ 1 - 1
pro/LICENSE

@@ -3,7 +3,7 @@ Copyright (c) 2022 Netmaker, Inc.
 
 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.
 

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

@@ -79,9 +79,18 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 		handleOauthUserSignUpApprovalPending(w)
 		return
 	}
-	// if user exists with provider ID, convert them into email ID
+
 	user, err := logic.GetUser(content.UserPrincipalName)
 	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)
 		if err != nil {
 			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)
 		}
 	}
-	_, err = logic.GetUser(content.Email)
+
+	user, err = logic.GetUser(content.Email)
 	if err != nil {
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 			if inviteExists {
@@ -127,7 +137,16 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 			handleSomethingWentWrong(w)
 			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)
 	if err != nil {
 		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 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>`)
 
@@ -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 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) {
 	response.Header().Set("Content-Type", "text/html; charset=utf-8")
 	response.WriteHeader(http.StatusNotFound)
@@ -157,3 +160,9 @@ func handleSomethingWentWrong(response http.ResponseWriter) {
 	response.WriteHeader(http.StatusInternalServerError)
 	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
 	user, err := logic.GetUser(content.Login)
 	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
 		_, err := logic.GetUser(content.Email)
 		if err != nil {

+ 13 - 2
pro/auth/google.go

@@ -80,7 +80,8 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
 		handleOauthUserSignUpApprovalPending(w)
 		return
 	}
-	_, err = logic.GetUser(content.Email)
+
+	user, err := logic.GetUser(content.Email)
 	if err != nil {
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 			if inviteExists {
@@ -117,13 +118,23 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
 			handleSomethingWentWrong(w)
 			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 {
 		logger.Log(0, "error fetching user: ", err.Error())
 		handleOauthUserNotFound(w)
 		return
 	}
+
 	userRole, err := logic.GetRole(user.PlatformRoleID)
 	if err != nil {
 		handleSomethingWentWrong(w)

+ 12 - 2
pro/auth/oidc.go

@@ -91,7 +91,8 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
 		handleOauthUserSignUpApprovalPending(w)
 		return
 	}
-	_, err = logic.GetUser(content.Email)
+
+	user, err := logic.GetUser(content.Email)
 	if err != nil {
 		if database.IsEmptyRecord(err) { // user must not exist, so try to make one
 			if inviteExists {
@@ -127,8 +128,17 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
 			handleSomethingWentWrong(w)
 			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 {
 		handleOauthUserNotFound(w)
 		return

+ 47 - 0
pro/controllers/users.go

@@ -8,7 +8,9 @@ import (
 	"net/http"
 	"net/url"
 	"strings"
+	"time"
 
+	"github.com/google/uuid"
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
@@ -19,6 +21,7 @@ import (
 	"github.com/gravitl/netmaker/pro/email"
 	proLogic "github.com/gravitl/netmaker/pro/logic"
 	"github.com/gravitl/netmaker/servercfg"
+	"github.com/gravitl/netmaker/utils"
 	"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"))
 		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 {
 		user, err := logic.GetUser(userID)
 		if err != nil {
@@ -1034,6 +1075,8 @@ func getRemoteAccessGatewayConf(w http.ResponseWriter, r *http.Request) {
 		Metadata:          node.Metadata,
 		AllowedEndpoints:  getAllowedRagEndpoints(&node, host),
 		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)
@@ -1125,6 +1168,8 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 				AllowedEndpoints:  getAllowedRagEndpoints(&node, host),
 				NetworkAddresses:  []string{network.AddressRange, network.AddressRange6},
 				Status:            node.Status,
+				DnsAddress:        node.IngressDNS,
+				Addresses:         utils.NoEmptyStringToCsv(node.Address.String(), node.Address6.String()),
 			})
 			userGws[node.Network] = gws
 			delete(userGwNodes, node.ID.String())
@@ -1167,6 +1212,8 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) {
 			AllowedEndpoints:  getAllowedRagEndpoints(&node, host),
 			NetworkAddresses:  []string{network.AddressRange, network.AddressRange6},
 			Status:            node.Status,
+			DnsAddress:        node.IngressDNS,
+			Addresses:         utils.NoEmptyStringToCsv(node.Address.String(), node.Address6.String()),
 		})
 		userGws[node.Network] = gws
 	}

+ 3 - 2
pro/email/invite.go

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

+ 3 - 3
pro/logic/status.go

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

+ 11 - 91
pro/logic/user_mgmt.go

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

+ 3 - 2
pro/util.go

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

+ 11 - 12
release.md

@@ -1,21 +1,20 @@
-# Netmaker v0.30.0
+# Netmaker v0.90.0
 
 ## 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 🛠
 - 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 🐞
 
-- 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
 # Auto disable a user's connecteds clients bassed on JWT token expiration
 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` )
 CACHING_ENABLED=true
 # 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=hosted.nm
 # 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
 OLD_ACL_SUPPORT=true
 # if STUN is set to true, hole punch is called

+ 4 - 4
scripts/nm-quick.sh

@@ -253,10 +253,10 @@ save_config() { (
 	fi
 	# copy entries from the previous config
 	local toCopy=("SERVER_HOST" "SERVER_HOST6" "MASTER_KEY" "MQ_USERNAME" "MQ_PASSWORD" "LICENSE_KEY" "NETMAKER_TENANT_ID"
-		"INSTALL_TYPE" "NODE_ID" "DNS_MODE" "NETCLIENT_AUTO_UPDATE" "API_PORT"
+		"INSTALL_TYPE" "NODE_ID" "DNS_MODE" "NETCLIENT_AUTO_UPDATE" "API_PORT" "MANAGE_DNS" "DEFAULT_DOMAIN"
 		"CORS_ALLOWED_ORIGIN" "DISPLAY_KEYS" "DATABASE" "SERVER_BROKER_ENDPOINT" "VERBOSITY"
 		"DEBUG_MODE"  "REST_BACKEND" "DISABLE_REMOTE_IP_CHECK" "TELEMETRY" "ALLOWED_EMAIL_DOMAINS" "AUTH_PROVIDER" "CLIENT_ID" "CLIENT_SECRET"
-		"FRONTEND_URL" "AZURE_TENANT" "OIDC_ISSUER" "EXPORTER_API_PORT" "JWT_VALIDITY_DURATION" "RAC_AUTO_DISABLE" "CACHING_ENABLED" "ENDPOINT_DETECTION"
+		"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")
 	for name in "${toCopy[@]}"; do
 		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() {
 
-	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
 		NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io
 	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"
 		"TURN_PORT" "USE_TURN" "DEBUG_MODE" "TURN_API_PORT" "REST_BACKEND"
 		"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
 		save_config_item $name "${!name}"
 	done

+ 6 - 0
servercfg/serverconf.go

@@ -94,6 +94,7 @@ func GetServerConfig() config.ServerConfig {
 	}
 	cfg.JwtValidityDuration = GetJwtValidityDuration()
 	cfg.RacAutoDisable = GetRacAutoDisable()
+	cfg.RacRestrictToSingleNetwork = GetRacRestrictToSingleNetwork()
 	cfg.MetricInterval = GetMetricInterval()
 	cfg.ManageDNS = GetManageDNS()
 	cfg.Stun = IsStunEnabled()
@@ -120,6 +121,11 @@ func GetRacAutoDisable() bool {
 	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
 func GetServerInfo() models.ServerConfig {
 	var cfg models.ServerConfig

+ 207 - 104
swagger.yaml

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

+ 16 - 0
utils/utils.go

@@ -3,6 +3,7 @@ package utils
 import (
 	"log/slog"
 	"runtime"
+	"strings"
 	"time"
 )
 
@@ -59,3 +60,18 @@ func TraceCaller() {
 	slog.Debug("Called from function: %s\n", "func-name", funcName)
 	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()
+}

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott