Przeglądaj źródła

Merge pull request #3261 from gravitl/release-v0.30.0

v0.30.0
Abhishek K 8 miesięcy temu
rodzic
commit
ac3afaa98a
65 zmienionych plików z 2339 dodań i 432 usunięć
  1. 1 0
      .github/ISSUE_TEMPLATE/bug-report.yml
  2. 2 2
      .github/workflows/deletedroplets.yml
  3. 2 0
      .swaggo
  4. 1 1
      Dockerfile
  5. 1 1
      Dockerfile-quick
  6. 1 1
      README.md
  7. 2 2
      auth/host_session.go
  8. 2 1
      compose/docker-compose-emqx.yml
  9. 1 1
      compose/docker-compose.netclient.yml
  10. 3 3
      compose/docker-compose.yml
  11. 4 1
      config/config.go
  12. 83 9
      controllers/acls.go
  13. 5 1
      controllers/dns.go
  14. 3 3
      controllers/dns_test.go
  15. 7 0
      controllers/ext_client.go
  16. 8 1
      controllers/node.go
  17. 6 0
      controllers/server.go
  18. 1 1
      controllers/user.go
  19. 21 0
      docker/emqx.conf
  20. 11 8
      go.mod
  21. 29 17
      go.sum
  22. 1 1
      k8s/client/netclient-daemonset.yaml
  23. 1 1
      k8s/client/netclient.yaml
  24. 1 1
      k8s/server/netmaker-ui.yaml
  25. 432 35
      logic/acls.go
  26. 4 0
      logic/acls/nodeacls/retrieve.go
  27. 13 3
      logic/dns.go
  28. 156 69
      logic/extpeers.go
  29. 125 3
      logic/nodes.go
  30. 40 3
      logic/peers.go
  31. 13 0
      logic/proc.go
  32. 26 0
      logic/status.go
  33. 1 1
      logic/tags.go
  34. 19 0
      logic/user_mgmt.go
  35. 28 1
      logic/util.go
  36. 12 2
      main.go
  37. 4 1
      main_ee.go
  38. 2 2
      migrate/migrate.go
  39. 48 0
      models/acl.go
  40. 6 4
      models/api_node.go
  41. 1 1
      models/enrollment_key.go
  42. 20 11
      models/extclient.go
  43. 1 1
      models/host.go
  44. 8 8
      models/metrics.go
  45. 14 7
      models/mqtt.go
  46. 2 2
      models/network.go
  47. 31 4
      models/node.go
  48. 3 1
      models/structs.go
  49. 1 1
      mq/emqx_on_prem.go
  50. 24 2
      mq/migrate.go
  51. 1 1
      mq/mq.go
  52. 13 29
      mq/publishers.go
  53. 61 3
      mq/util.go
  54. 7 7
      pro/controllers/users.go
  55. 1 0
      pro/initialize.go
  56. 73 46
      pro/license.go
  57. 199 0
      pro/logic/status.go
  58. 4 4
      pro/logic/user_mgmt.go
  59. 1 1
      pro/types.go
  60. 7 7
      release.md
  61. 4 4
      scripts/netmaker.default.env
  62. 6 9
      scripts/nm-quick.sh
  63. 31 22
      servercfg/serverconf.go
  64. 659 81
      swagger.yaml
  65. 41 0
      utils/utils.go

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

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

+ 2 - 2
.github/workflows/deletedroplets.yml

@@ -12,7 +12,7 @@ jobs:
     if: ${{ github.event.workflow_run.conclusion == 'success' }}
     steps:
       - name: get logs
-        uses: dawidd6/action-download-artifact@v6
+        uses: dawidd6/action-download-artifact@v7
         with:
           run_id: ${{ github.event.workflow_run.id}}
           if_no_artifact_found: warn
@@ -75,7 +75,7 @@ jobs:
     if: ${{ github.event.workflow_run.conclusion == 'failure' }}
     steps:
       - name: get logs
-        uses: dawidd6/action-download-artifact@v6
+        uses: dawidd6/action-download-artifact@v7
         with:
           run_id: ${{ github.event.workflow_run.id}}
           if_no_artifact_found: warn

+ 2 - 0
.swaggo

@@ -0,0 +1,2 @@
+// Replace all time.Duration with int64
+replace time.Duration int64

+ 1 - 1
Dockerfile

@@ -6,7 +6,7 @@ COPY . .
 
 RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -w " -tags ${tags} .
 # RUN go build -tags=ee . -o netmaker main.go
-FROM alpine:3.20.3
+FROM alpine:3.21.0
 
 # add a c lib
 # set the working directory

+ 1 - 1
Dockerfile-quick

@@ -1,5 +1,5 @@
 #first stage - builder
-FROM alpine:3.20.3
+FROM alpine:3.21.0
 ARG version 
 WORKDIR /app
 COPY ./netmaker /root/netmaker

+ 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.26.0-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.30.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" />

+ 2 - 2
auth/host_session.go

@@ -257,7 +257,7 @@ func CheckNetRegAndHostUpdate(networks []string, h *models.Host, relayNodeId uui
 			if relayNodeId != uuid.Nil && !newNode.IsRelayed {
 				// check if relay node exists and acting as relay
 				relaynode, err := logic.GetNodeByID(relayNodeId.String())
-				if err == nil && relaynode.IsRelay {
+				if err == nil && relaynode.IsRelay && relaynode.Network == newNode.Network {
 					slog.Info(fmt.Sprintf("adding relayed node %s to relay %s on network %s", newNode.ID.String(), relayNodeId.String(), network))
 					newNode.IsRelayed = true
 					newNode.RelayedBy = relayNodeId.String()
@@ -271,7 +271,7 @@ func CheckNetRegAndHostUpdate(networks []string, h *models.Host, relayNodeId uui
 						slog.Error("failed to update node", "nodeid", relayNodeId.String())
 					}
 				} else {
-					slog.Error("failed to relay node. maybe specified relay node is actually not a relay?", "err", err)
+					slog.Error("failed to relay node. maybe specified relay node is actually not a relay? Or the relayed node is not in the same network with relay?", "err", err)
 				}
 			}
 			logger.Log(1, "added new node", newNode.ID.String(), "to host", h.Name)

+ 2 - 1
compose/docker-compose-emqx.yml

@@ -3,7 +3,7 @@ version: "3.4"
 services:
   mq:
     container_name: mq
-    image: emqx/emqx:5.0.9
+    image: emqx/emqx:5.8.2
     env_file: ./netmaker.env
     restart: unless-stopped
     environment:
@@ -20,6 +20,7 @@ services:
       - emqx_data:/opt/emqx/data
       - emqx_etc:/opt/emqx/etc
       - emqx_logs:/opt/emqx/log
+      - ./emqx.conf:/opt/emqx/data/configs/cluster.hocon
 volumes:
   emqx_data: { } # storage for emqx data
   emqx_etc: { }  # storage for emqx etc

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

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

+ 3 - 3
compose/docker-compose.yml

@@ -12,7 +12,7 @@ services:
       - sqldata:/root/data
     environment:
       # config-dependant vars
-      - STUN_LIST=stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302
+      - STUN_SERVERS=stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302
       # The domain/host IP indicating the mq broker address
       - BROKER_ENDPOINT=wss://broker.${NM_DOMAIN} # For EMQX broker use `BROKER_ENDPOINT=wss://broker.${NM_DOMAIN}/mqtt`
       # For EMQX broker (uncomment the two lines below)
@@ -52,8 +52,8 @@ services:
       - caddy_data:/data
       - caddy_conf:/config
     ports:
-      - "80:80"
-      - "443:443"
+      - "$SERVER_HOST:80:80"
+      - "$SERVER_HOST:443:443"
 
   coredns:
     #network_mode: host

+ 4 - 1
config/config.go

@@ -89,7 +89,7 @@ type ServerConfig struct {
 	EgressesLimit              int           `yaml:"egresses_limit"`
 	DeployedByOperator         bool          `yaml:"deployed_by_operator"`
 	Environment                string        `yaml:"environment"`
-	JwtValidityDuration        time.Duration `yaml:"jwt_validity_duration"`
+	JwtValidityDuration        time.Duration `yaml:"jwt_validity_duration" swaggertype:"primitive,integer" format:"int64"`
 	RacAutoDisable             bool          `yaml:"rac_auto_disable"`
 	CacheEnabled               string        `yaml:"caching_enabled"`
 	EndpointDetection          bool          `json:"endpoint_detection"`
@@ -101,7 +101,10 @@ type ServerConfig struct {
 	SmtpPort                   int           `json:"smtp_port"`
 	MetricInterval             string        `yaml:"metric_interval"`
 	ManageDNS                  bool          `yaml:"manage_dns"`
+	Stun                       bool          `yaml:"stun"`
+	StunServers                string        `yaml:"stun_servers"`
 	DefaultDomain              string        `yaml:"default_domain"`
+	PublicIp                   string        `yaml:"public_ip"`
 }
 
 // SQLConfig - Generic SQL Config

+ 83 - 9
controllers/acls.go

@@ -52,6 +52,81 @@ func aclPolicyTypes(w http.ResponseWriter, r *http.Request) {
 			// models.NetmakerIPAclID,
 			// models.NetmakerSubNetRangeAClID,
 		},
+		ProtocolTypes: []models.ProtocolType{
+			{
+				Name: models.Any,
+				AllowedProtocols: []models.Protocol{
+					models.ALL,
+				},
+				PortRange:        "All ports",
+				AllowPortSetting: false,
+			},
+			{
+				Name: models.Http,
+				AllowedProtocols: []models.Protocol{
+					models.TCP,
+				},
+				PortRange: "80",
+			},
+			{
+				Name: models.Https,
+				AllowedProtocols: []models.Protocol{
+					models.TCP,
+				},
+				PortRange: "443",
+			},
+			// {
+			// 	Name: "MySQL",
+			// 	AllowedProtocols: []models.Protocol{
+			// 		models.TCP,
+			// 	},
+			// 	PortRange: "3306",
+			// },
+			// {
+			// 	Name: "DNS TCP",
+			// 	AllowedProtocols: []models.Protocol{
+			// 		models.TCP,
+			// 	},
+			// 	PortRange: "53",
+			// },
+			// {
+			// 	Name: "DNS UDP",
+			// 	AllowedProtocols: []models.Protocol{
+			// 		models.UDP,
+			// 	},
+			// 	PortRange: "53",
+			// },
+			{
+				Name: models.AllTCP,
+				AllowedProtocols: []models.Protocol{
+					models.TCP,
+				},
+				PortRange: "All ports",
+			},
+			{
+				Name: models.AllUDP,
+				AllowedProtocols: []models.Protocol{
+					models.UDP,
+				},
+				PortRange: "All ports",
+			},
+			{
+				Name: models.ICMPService,
+				AllowedProtocols: []models.Protocol{
+					models.ICMP,
+				},
+				PortRange: "",
+			},
+			{
+				Name: models.Custom,
+				AllowedProtocols: []models.Protocol{
+					models.UDP,
+					models.TCP,
+				},
+				PortRange:        "All ports",
+				AllowPortSetting: true,
+			},
+		},
 	}
 	logic.ReturnSuccessResponseWithJson(w, r, resp, "fetched acls types")
 }
@@ -69,7 +144,7 @@ func aclDebug(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 	}
-	allowed := logic.IsNodeAllowedToCommunicate(node, peer)
+	allowed, _ := logic.IsNodeAllowedToCommunicate(node, peer, true)
 	logic.ReturnSuccessResponseWithJson(w, r, allowed, "fetched all acls in the network ")
 }
 
@@ -91,7 +166,7 @@ func getAcls(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 	}
-	acls, err := logic.ListAcls(models.NetworkID(netID))
+	acls, err := logic.ListAclsByNetwork(models.NetworkID(netID))
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"), "failed to get all network acl entries: ", err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
@@ -132,10 +207,9 @@ func createAcl(w http.ResponseWriter, r *http.Request) {
 	acl.CreatedBy = user.UserName
 	acl.CreatedAt = time.Now().UTC()
 	acl.Default = false
-	if acl.RuleType == models.DevicePolicy {
-		acl.AllowedDirection = models.TrafficDirectionBi
-	} else {
-		acl.AllowedDirection = models.TrafficDirectionUni
+	if acl.ServiceType == models.Any {
+		acl.Port = []string{}
+		acl.Proto = models.ALL
 	}
 	// validate create acl policy
 	if !logic.IsAclPolicyValid(acl) {
@@ -152,7 +226,7 @@ func createAcl(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
-	go mq.PublishPeerUpdate(false)
+	go mq.PublishPeerUpdate(true)
 	logic.ReturnSuccessResponseWithJson(w, r, acl, "created acl successfully")
 }
 
@@ -194,7 +268,7 @@ func updateAcl(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 	}
-	go mq.PublishPeerUpdate(false)
+	go mq.PublishPeerUpdate(true)
 	logic.ReturnSuccessResponse(w, r, "updated acl "+acl.Name)
 }
 
@@ -225,6 +299,6 @@ func deleteAcl(w http.ResponseWriter, r *http.Request) {
 			logic.FormatError(errors.New("cannot delete default policy"), "internal"))
 		return
 	}
-	go mq.PublishPeerUpdate(false)
+	go mq.PublishPeerUpdate(true)
 	logic.ReturnSuccessResponse(w, r, "deleted acl "+acl.Name)
 }

+ 5 - 1
controllers/dns.go

@@ -5,6 +5,7 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
+	"strings"
 
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/database"
@@ -162,7 +163,10 @@ func createDNS(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 	}
-
+	// check if default domain is appended if not append
+	if !strings.HasSuffix(entry.Name, servercfg.GetDefaultDomain()) {
+		entry.Name += "." + servercfg.GetDefaultDomain()
+	}
 	entry, err = logic.CreateDNS(entry)
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"),

+ 3 - 3
controllers/dns_test.go

@@ -391,7 +391,7 @@ func TestValidateDNSCreate(t *testing.T) {
 		entry := models.DNSEntry{Address: "10.0.0.2", Network: "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
-		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'required' tag")
+		assert.Contains(t, err.Error(), "invalid input")
 	})
 	t.Run("NameTooLong", func(t *testing.T) {
 		name := ""
@@ -414,13 +414,13 @@ func TestValidateDNSCreate(t *testing.T) {
 		entry := models.DNSEntry{Address: "10.10.10.5", Name: "white space", Network: "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
-		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'whitespace' tag")
+		assert.Contains(t, err.Error(), "invalid input")
 	})
 	t.Run("AllSpaces", func(t *testing.T) {
 		entry := models.DNSEntry{Address: "10.10.10.5", Name: "     ", Network: "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
-		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'whitespace' tag")
+		assert.Contains(t, err.Error(), "invalid input")
 	})
 
 }

+ 7 - 0
controllers/ext_client.go

@@ -490,6 +490,13 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 	if err == nil { // check if parent network default ACL is enabled (yes) or not (no)
 		extclient.Enabled = parentNetwork.DefaultACL == "yes"
 	}
+	extclient.Os = customExtClient.Os
+	extclient.DeviceName = customExtClient.DeviceName
+	if customExtClient.IsAlreadyConnectedToInetGw {
+		slog.Warn("RAC/Client is already connected to internet gateway. this may mask their real IP address", "client IP", customExtClient.PublicEndpoint)
+	}
+	extclient.PublicEndpoint = customExtClient.PublicEndpoint
+	extclient.Country = customExtClient.Country
 
 	if err = logic.CreateExtClient(&extclient); err != nil {
 		slog.Error(

+ 8 - 1
controllers/node.go

@@ -326,8 +326,9 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) {
 	if len(filteredNodes) > 0 {
 		nodes = filteredNodes
 	}
-	nodes = logic.AddStaticNodestoList(nodes)
 
+	nodes = logic.AddStaticNodestoList(nodes)
+	nodes = logic.AddStatusToNodes(nodes)
 	// returns all the nodes in JSON/API format
 	apiNodes := logic.GetAllNodesAPI(nodes[:])
 	logger.Log(2, r.Header.Get("user"), "fetched nodes on network", networkName)
@@ -367,6 +368,7 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) {
 
 	}
 	nodes = logic.AddStaticNodestoList(nodes)
+	nodes = logic.AddStatusToNodes(nodes)
 	// return all the nodes in JSON/API format
 	apiNodes := logic.GetAllNodesAPI(nodes[:])
 	logger.Log(3, r.Header.Get("user"), "fetched all nodes they have access to")
@@ -679,6 +681,11 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 	}
+	err = logic.ValidateNodeIp(&currentNode, &newData)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
 	if !servercfg.IsPro {
 		newData.AdditionalRagIps = []string{}
 	}

+ 6 - 0
controllers/server.go

@@ -48,6 +48,8 @@ func serverHandlers(r *mux.Router) {
 		Methods(http.MethodGet)
 	r.HandleFunc("/api/server/cpu_profile", logic.SecurityCheck(false, http.HandlerFunc(cpuProfile))).
 		Methods(http.MethodPost)
+	r.HandleFunc("/api/server/mem_profile", logic.SecurityCheck(false, http.HandlerFunc(memProfile))).
+		Methods(http.MethodPost)
 }
 
 func cpuProfile(w http.ResponseWriter, r *http.Request) {
@@ -62,6 +64,10 @@ func cpuProfile(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 }
+func memProfile(w http.ResponseWriter, r *http.Request) {
+	os.Remove("/root/data/mem.prof")
+	logic.StartMemProfiling()
+}
 
 func getUsage(w http.ResponseWriter, _ *http.Request) {
 	type usage struct {

+ 1 - 1
controllers/user.go

@@ -722,7 +722,7 @@ func socketHandler(w http.ResponseWriter, r *http.Request) {
 // @Summary     lists all user roles.
 // @Router      /api/v1/user/roles [get]
 // @Tags        Users
-// @Param       role_id param string true "roleid required to get the role details"
+// @Param       role_id query string true "roleid required to get the role details"
 // @Success     200 {object}  []models.UserRolePermissionTemplate
 // @Failure     500 {object} models.ErrorResponse
 func listRoles(w http.ResponseWriter, r *http.Request) {

+ 21 - 0
docker/emqx.conf

@@ -0,0 +1,21 @@
+authentication = [
+  {
+    backend = "built_in_database"
+    mechanism = "password_based"
+    password_hash_algorithm {
+      name = "sha256",
+      salt_position = "suffix"
+    }
+    user_id_type = "username"
+  }
+]
+authorization {
+  deny_action = ignore
+  no_match = allow
+  sources = [
+    {
+      type = built_in_database
+      enable = true
+    }
+  ]
+}

+ 11 - 8
go.mod

@@ -3,9 +3,10 @@ module github.com/gravitl/netmaker
 go 1.23
 
 require (
+	github.com/blang/semver v3.5.1+incompatible
 	github.com/eclipse/paho.mqtt.golang v1.4.3
-	github.com/go-playground/validator/v10 v10.22.1
-	github.com/golang-jwt/jwt/v4 v4.5.0
+	github.com/go-playground/validator/v10 v10.23.0
+	github.com/golang-jwt/jwt/v4 v4.5.1
 	github.com/google/uuid v1.6.0
 	github.com/gorilla/handlers v1.5.2
 	github.com/gorilla/mux v1.8.1
@@ -14,13 +15,14 @@ require (
 	github.com/rqlite/gorqlite v0.0.0-20240122221808-a8a425b1a6aa
 	github.com/seancfoley/ipaddress-go v1.7.0
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
-	github.com/stretchr/testify v1.9.0
+	github.com/stretchr/testify v1.10.0
 	github.com/txn2/txeh v1.5.5
-	golang.org/x/crypto v0.28.0
+	go.uber.org/automaxprocs v1.6.0
+	golang.org/x/crypto v0.30.0
 	golang.org/x/net v0.27.0 // indirect
-	golang.org/x/oauth2 v0.23.0
-	golang.org/x/sys v0.26.0 // indirect
-	golang.org/x/text v0.19.0 // indirect
+	golang.org/x/oauth2 v0.24.0
+	golang.org/x/sys v0.28.0 // indirect
+	golang.org/x/text v0.21.0 // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb
 	gopkg.in/yaml.v3 v3.0.1
 )
@@ -51,6 +53,7 @@ require (
 	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
 	github.com/go-jose/go-jose/v3 v3.0.3 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
+	github.com/kr/text v0.2.0 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/seancfoley/bintree v1.3.1 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
@@ -66,5 +69,5 @@ require (
 	github.com/leodido/go-urn v1.4.0 // indirect
 	github.com/mattn/go-runewidth v0.0.13 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	golang.org/x/sync v0.8.0 // indirect
+	golang.org/x/sync v0.10.0 // indirect
 )

+ 29 - 17
go.sum

@@ -2,11 +2,14 @@ cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2Qx
 cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
+github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
+github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
 github.com/c-robinson/iplib v1.0.8 h1:exDRViDyL9UBLcfmlxxkY5odWX5092nPsQIykHXhIn4=
 github.com/c-robinson/iplib v1.0.8/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo=
 github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo=
 github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4=
 github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -24,10 +27,10 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
-github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
-github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
-github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
+github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
+github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -46,6 +49,10 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe
 github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
 github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
 github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
@@ -64,6 +71,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posthog/posthog-go v1.2.24 h1:A+iG4saBJemo++VDlcWovbYf8KFFNUfrCoJtsc40RPA=
 github.com/posthog/posthog-go v1.2.24/go.mod h1:uYC2l1Yktc8E+9FAHJ9QZG4vQf/NHJPD800Hsm7DzoM=
+github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
+github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -82,16 +91,18 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
-github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/txn2/txeh v1.5.5 h1:UN4e/lCK5HGw/gGAi2GCVrNKg0GTCUWs7gs5riaZlz4=
 github.com/txn2/txeh v1.5.5/go.mod h1:qYzGG9kCzeVEI12geK4IlanHWY8X4uy/I3NcW7mk8g4=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
+go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
-golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
-golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
+golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
+golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -103,13 +114,13 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
 golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
-golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
-golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
+golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
-golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -118,8 +129,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
-golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -131,8 +142,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
-golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -142,8 +153,9 @@ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb h1:9aqVcYED
 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb/go.mod h1:mQqgjkW8GQQcJQsbBvK890TKqUK1DfKWkuBGbOkuMHQ=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
 gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

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

@@ -16,7 +16,7 @@ spec:
       hostNetwork: true
       containers:
       - name: netclient
-        image: gravitl/netclient:v0.26.0
+        image: gravitl/netclient:v0.30.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.26.0
+        image: gravitl/netclient:v0.30.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.26.0
+        image: gravitl/netmaker-ui:v0.30.0
         ports:
         - containerPort: 443
         env:

+ 432 - 35
logic/acls.go

@@ -18,12 +18,25 @@ var (
 	aclCacheMap   = make(map[string]models.Acl)
 )
 
+func MigrateAclPolicies() {
+	acls := ListAcls()
+	for _, acl := range acls {
+		if acl.Proto.String() == "" {
+			acl.Proto = models.ALL
+			acl.ServiceType = models.Any
+			acl.Port = []string{}
+			UpsertAcl(acl)
+		}
+	}
+
+}
+
 // CreateDefaultAclNetworkPolicies - create default acl network policies
 func CreateDefaultAclNetworkPolicies(netID models.NetworkID) {
 	if netID.String() == "" {
 		return
 	}
-	_, _ = ListAcls(netID)
+	_, _ = ListAclsByNetwork(netID)
 	if !IsAclExists(fmt.Sprintf("%s.%s", netID, "all-nodes")) {
 		defaultDeviceAcl := models.Acl{
 			ID:        fmt.Sprintf("%s.%s", netID, "all-nodes"),
@@ -31,6 +44,8 @@ func CreateDefaultAclNetworkPolicies(netID models.NetworkID) {
 			MetaData:  "This Policy allows all nodes in the network to communicate with each other",
 			Default:   true,
 			NetworkID: netID,
+			Proto:     models.ALL,
+			Port:      []string{},
 			RuleType:  models.DevicePolicy,
 			Src: []models.AclPolicyTag{
 				{
@@ -56,6 +71,8 @@ func CreateDefaultAclNetworkPolicies(netID models.NetworkID) {
 			Name:      "All Users",
 			MetaData:  "This policy gives access to everything in the network for an user",
 			NetworkID: netID,
+			Proto:     models.ALL,
+			Port:      []string{},
 			RuleType:  models.UserPolicy,
 			Src: []models.AclPolicyTag{
 				{
@@ -81,6 +98,8 @@ func CreateDefaultAclNetworkPolicies(netID models.NetworkID) {
 			Default:   true,
 			Name:      "All Remote Access Gateways",
 			NetworkID: netID,
+			Proto:     models.ALL,
+			Port:      []string{},
 			RuleType:  models.DevicePolicy,
 			Src: []models.AclPolicyTag{
 				{
@@ -106,7 +125,7 @@ func CreateDefaultAclNetworkPolicies(netID models.NetworkID) {
 
 // DeleteDefaultNetworkPolicies - deletes all default network acl policies
 func DeleteDefaultNetworkPolicies(netId models.NetworkID) {
-	acls, _ := ListAcls(netId)
+	acls, _ := ListAclsByNetwork(netId)
 	for _, acl := range acls {
 		if acl.NetworkID == netId && acl.Default {
 			DeleteAcl(acl)
@@ -202,7 +221,10 @@ func IsAclExists(aclID string) bool {
 // IsAclPolicyValid - validates if acl policy is valid
 func IsAclPolicyValid(acl models.Acl) bool {
 	//check if src and dst are valid
-
+	if acl.AllowedDirection != models.TrafficDirectionBi &&
+		acl.AllowedDirection != models.TrafficDirectionUni {
+		return false
+	}
 	switch acl.RuleType {
 	case models.UserPolicy:
 		// src list should only contain users
@@ -298,6 +320,14 @@ func UpdateAcl(newAcl, acl models.Acl) error {
 		acl.Name = newAcl.Name
 		acl.Src = newAcl.Src
 		acl.Dst = newAcl.Dst
+		acl.AllowedDirection = newAcl.AllowedDirection
+		acl.Port = newAcl.Port
+		acl.Proto = newAcl.Proto
+		acl.ServiceType = newAcl.ServiceType
+	}
+	if newAcl.ServiceType == models.Any {
+		acl.Port = []string{}
+		acl.Proto = models.ALL
 	}
 	acl.Enabled = newAcl.Enabled
 	d, err := json.Marshal(acl)
@@ -347,14 +377,20 @@ func GetDefaultPolicy(netID models.NetworkID, ruleType models.AclPolicyType) (mo
 		return acl, nil
 	}
 	// check if there are any custom all policies
-	policies, _ := ListAcls(netID)
+	srcMap := make(map[string]struct{})
+	dstMap := make(map[string]struct{})
+	defer func() {
+		srcMap = nil
+		dstMap = nil
+	}()
+	policies, _ := ListAclsByNetwork(netID)
 	for _, policy := range policies {
 		if !policy.Enabled {
 			continue
 		}
 		if policy.RuleType == ruleType {
-			dstMap := convAclTagToValueMap(policy.Dst)
-			srcMap := convAclTagToValueMap(policy.Src)
+			dstMap = convAclTagToValueMap(policy.Dst)
+			srcMap = convAclTagToValueMap(policy.Src)
 			if _, ok := srcMap["*"]; ok {
 				if _, ok := dstMap["*"]; ok {
 					return policy, nil
@@ -367,7 +403,7 @@ func GetDefaultPolicy(netID models.NetworkID, ruleType models.AclPolicyType) (mo
 	return acl, nil
 }
 
-func listAcls() (acls []models.Acl) {
+func ListAcls() (acls []models.Acl) {
 	if servercfg.CacheEnabled() && len(aclCacheMap) > 0 {
 		return listAclFromCache()
 	}
@@ -376,7 +412,6 @@ func listAcls() (acls []models.Acl) {
 	if err != nil && !database.IsEmptyRecord(err) {
 		return []models.Acl{}
 	}
-
 	for _, dataI := range data {
 		acl := models.Acl{}
 		err := json.Unmarshal([]byte(dataI), &acl)
@@ -393,7 +428,7 @@ func listAcls() (acls []models.Acl) {
 
 // ListUserPolicies - lists all acl policies enforced on an user
 func ListUserPolicies(u models.User) []models.Acl {
-	allAcls := listAcls()
+	allAcls := ListAcls()
 	userAcls := []models.Acl{}
 	for _, acl := range allAcls {
 
@@ -418,7 +453,7 @@ func ListUserPolicies(u models.User) []models.Acl {
 
 // listPoliciesOfUser - lists all user acl policies applied to user in an network
 func listPoliciesOfUser(user models.User, netID models.NetworkID) []models.Acl {
-	allAcls := listAcls()
+	allAcls := ListAcls()
 	userAcls := []models.Acl{}
 	for _, acl := range allAcls {
 		if acl.NetworkID == netID && acl.RuleType == models.UserPolicy {
@@ -447,7 +482,7 @@ func listPoliciesOfUser(user models.User, netID models.NetworkID) []models.Acl {
 
 // listDevicePolicies - lists all device policies in a network
 func listDevicePolicies(netID models.NetworkID) []models.Acl {
-	allAcls := listAcls()
+	allAcls := ListAcls()
 	deviceAcls := []models.Acl{}
 	for _, acl := range allAcls {
 		if acl.NetworkID == netID && acl.RuleType == models.DevicePolicy {
@@ -457,10 +492,22 @@ func listDevicePolicies(netID models.NetworkID) []models.Acl {
 	return deviceAcls
 }
 
+// listUserPolicies - lists all user policies in a network
+func listUserPolicies(netID models.NetworkID) []models.Acl {
+	allAcls := ListAcls()
+	deviceAcls := []models.Acl{}
+	for _, acl := range allAcls {
+		if acl.NetworkID == netID && acl.RuleType == models.UserPolicy {
+			deviceAcls = append(deviceAcls, acl)
+		}
+	}
+	return deviceAcls
+}
+
 // ListAcls - lists all acl policies
-func ListAcls(netID models.NetworkID) ([]models.Acl, error) {
+func ListAclsByNetwork(netID models.NetworkID) ([]models.Acl, error) {
 
-	allAcls := listAcls()
+	allAcls := ListAcls()
 	netAcls := []models.Acl{}
 	for _, acl := range allAcls {
 		if acl.NetworkID == netID {
@@ -479,19 +526,19 @@ func convAclTagToValueMap(acltags []models.AclPolicyTag) map[string]struct{} {
 }
 
 // IsUserAllowedToCommunicate - check if user is allowed to communicate with peer
-func IsUserAllowedToCommunicate(userName string, peer models.Node) bool {
+func IsUserAllowedToCommunicate(userName string, peer models.Node) (bool, []models.Acl) {
 	if peer.IsStatic {
 		peer = peer.StaticNode.ConvertToStaticNode()
 	}
 	acl, _ := GetDefaultPolicy(models.NetworkID(peer.Network), models.UserPolicy)
 	if acl.Enabled {
-		return true
+		return true, []models.Acl{acl}
 	}
 	user, err := GetUser(userName)
 	if err != nil {
-		return false
+		return false, []models.Acl{}
 	}
-
+	allowedPolicies := []models.Acl{}
 	policies := listPoliciesOfUser(*user, models.NetworkID(peer.Network))
 	for _, policy := range policies {
 		if !policy.Enabled {
@@ -499,46 +546,54 @@ func IsUserAllowedToCommunicate(userName string, peer models.Node) bool {
 		}
 		dstMap := convAclTagToValueMap(policy.Dst)
 		if _, ok := dstMap["*"]; ok {
-			return true
+			allowedPolicies = append(allowedPolicies, policy)
+			continue
 		}
 		for tagID := range peer.Tags {
 			if _, ok := dstMap[tagID.String()]; ok {
-				return true
+				allowedPolicies = append(allowedPolicies, policy)
+				break
 			}
 		}
 
 	}
-	return false
+	if len(allowedPolicies) > 0 {
+		return true, allowedPolicies
+	}
+	return false, []models.Acl{}
 }
 
-// IsNodeAllowedToCommunicate - check node is allowed to communicate with the peer
-func IsNodeAllowedToCommunicate(node, peer models.Node) bool {
+// IsPeerAllowed - checks if peer needs to be added to the interface
+func IsPeerAllowed(node, peer models.Node, checkDefaultPolicy bool) bool {
 	if node.IsStatic {
 		node = node.StaticNode.ConvertToStaticNode()
 	}
 	if peer.IsStatic {
 		peer = peer.StaticNode.ConvertToStaticNode()
 	}
-	// check default policy if all allowed return true
-	defaultPolicy, err := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
-	if err == nil {
-		if defaultPolicy.Enabled {
-			return true
+	if checkDefaultPolicy {
+		// check default policy if all allowed return true
+		defaultPolicy, err := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
+		if err == nil {
+			if defaultPolicy.Enabled {
+				return true
+			}
 		}
 	}
-
 	// list device policies
 	policies := listDevicePolicies(models.NetworkID(peer.Network))
+	srcMap := make(map[string]struct{})
+	dstMap := make(map[string]struct{})
+	defer func() {
+		srcMap = nil
+		dstMap = nil
+	}()
 	for _, policy := range policies {
 		if !policy.Enabled {
 			continue
 		}
-		srcMap := convAclTagToValueMap(policy.Src)
-		dstMap := convAclTagToValueMap(policy.Dst)
-		// fmt.Printf("\n======> SRCMAP: %+v\n", srcMap)
-		// fmt.Printf("\n======> DSTMAP: %+v\n", dstMap)
-		// fmt.Printf("\n======> node Tags: %+v\n", node.Tags)
-		// fmt.Printf("\n======> peer Tags: %+v\n", peer.Tags)
+		srcMap = convAclTagToValueMap(policy.Src)
+		dstMap = convAclTagToValueMap(policy.Dst)
 		for tagID := range node.Tags {
 			if _, ok := dstMap[tagID.String()]; ok {
 				if _, ok := srcMap["*"]; ok {
@@ -588,6 +643,122 @@ func IsNodeAllowedToCommunicate(node, peer models.Node) bool {
 	return false
 }
 
+// IsNodeAllowedToCommunicate - check node is allowed to communicate with the peer
+func IsNodeAllowedToCommunicate(node, peer models.Node, checkDefaultPolicy bool) (bool, []models.Acl) {
+	if node.IsStatic {
+		node = node.StaticNode.ConvertToStaticNode()
+	}
+	if peer.IsStatic {
+		peer = peer.StaticNode.ConvertToStaticNode()
+	}
+	if checkDefaultPolicy {
+		// check default policy if all allowed return true
+		defaultPolicy, err := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
+		if err == nil {
+			if defaultPolicy.Enabled {
+				return true, []models.Acl{defaultPolicy}
+			}
+		}
+	}
+	allowedPolicies := []models.Acl{}
+	// list device policies
+	policies := listDevicePolicies(models.NetworkID(peer.Network))
+	srcMap := make(map[string]struct{})
+	dstMap := make(map[string]struct{})
+	defer func() {
+		srcMap = nil
+		dstMap = nil
+	}()
+	for _, policy := range policies {
+		if !policy.Enabled {
+			continue
+		}
+		srcMap = convAclTagToValueMap(policy.Src)
+		dstMap = convAclTagToValueMap(policy.Dst)
+		for tagID := range node.Tags {
+			allowed := false
+			if _, ok := dstMap[tagID.String()]; policy.AllowedDirection == models.TrafficDirectionBi && ok {
+				if _, ok := srcMap["*"]; ok {
+					allowed = true
+					allowedPolicies = append(allowedPolicies, policy)
+					break
+				}
+				for tagID := range peer.Tags {
+					if _, ok := srcMap[tagID.String()]; ok {
+						allowed = true
+						break
+					}
+				}
+			}
+			if allowed {
+				allowedPolicies = append(allowedPolicies, policy)
+				break
+			}
+			if _, ok := srcMap[tagID.String()]; ok {
+				if _, ok := dstMap["*"]; ok {
+					allowed = true
+					allowedPolicies = append(allowedPolicies, policy)
+					break
+				}
+				for tagID := range peer.Tags {
+					if _, ok := dstMap[tagID.String()]; ok {
+						allowed = true
+						break
+					}
+				}
+			}
+			if allowed {
+				allowedPolicies = append(allowedPolicies, policy)
+				break
+			}
+		}
+		for tagID := range peer.Tags {
+			allowed := false
+			if _, ok := dstMap[tagID.String()]; ok {
+				if _, ok := srcMap["*"]; ok {
+					allowed = true
+					allowedPolicies = append(allowedPolicies, policy)
+					break
+				}
+				for tagID := range node.Tags {
+
+					if _, ok := srcMap[tagID.String()]; ok {
+						allowed = true
+						break
+					}
+				}
+			}
+			if allowed {
+				allowedPolicies = append(allowedPolicies, policy)
+				break
+			}
+
+			if _, ok := srcMap[tagID.String()]; policy.AllowedDirection == models.TrafficDirectionBi && ok {
+				if _, ok := dstMap["*"]; ok {
+					allowed = true
+					allowedPolicies = append(allowedPolicies, policy)
+					break
+				}
+				for tagID := range node.Tags {
+					if _, ok := dstMap[tagID.String()]; ok {
+						allowed = true
+						break
+					}
+				}
+			}
+			if allowed {
+				allowedPolicies = append(allowedPolicies, policy)
+				break
+			}
+		}
+	}
+
+	if len(allowedPolicies) > 0 {
+		return true, allowedPolicies
+	}
+	return false, allowedPolicies
+}
+
 // SortTagEntrys - Sorts slice of Tag entries by their id
 func SortAclEntrys(acls []models.Acl) {
 	sort.Slice(acls, func(i, j int) bool {
@@ -634,7 +805,9 @@ func CheckIfTagAsActivePolicy(tagID models.TagID, netID models.NetworkID) bool {
 		}
 		for _, dstTagI := range acl.Dst {
 			if dstTagI.ID == models.DeviceAclID {
-				return true
+				if tagID.String() == dstTagI.Value {
+					return true
+				}
 			}
 		}
 	}
@@ -668,3 +841,227 @@ func RemoveDeviceTagFromAclPolicies(tagID models.TagID, netID models.NetworkID)
 	}
 	return nil
 }
+
+func getUserAclRulesForNode(targetnode *models.Node,
+	rules map[string]models.AclRule) map[string]models.AclRule {
+	userNodes := GetStaticUserNodesByNetwork(models.NetworkID(targetnode.Network))
+	userGrpMap := GetUserGrpMap()
+	allowedUsers := make(map[string][]models.Acl)
+	acls := listUserPolicies(models.NetworkID(targetnode.Network))
+	for nodeTag := range targetnode.Tags {
+		for _, acl := range acls {
+			if !acl.Enabled {
+				continue
+			}
+			dstTags := convAclTagToValueMap(acl.Dst)
+			if _, ok := dstTags[nodeTag.String()]; ok {
+				// get all src tags
+				for _, srcAcl := range acl.Src {
+					if srcAcl.ID == models.UserAclID {
+						allowedUsers[srcAcl.Value] = append(allowedUsers[srcAcl.Value], acl)
+					} else if srcAcl.ID == models.UserGroupAclID {
+						// fetch all users in the group
+						if usersMap, ok := userGrpMap[models.UserGroupID(srcAcl.Value)]; ok {
+							for userName := range usersMap {
+								allowedUsers[userName] = append(allowedUsers[userName], acl)
+							}
+						}
+					}
+				}
+
+			}
+		}
+	}
+	for _, userNode := range userNodes {
+		if !userNode.StaticNode.Enabled {
+			continue
+		}
+		acls, ok := allowedUsers[userNode.StaticNode.OwnerID]
+		if !ok {
+			continue
+		}
+		for _, acl := range acls {
+
+			if !acl.Enabled {
+				continue
+			}
+
+			r := models.AclRule{
+				ID:              acl.ID,
+				AllowedProtocol: acl.Proto,
+				AllowedPorts:    acl.Port,
+				Direction:       acl.AllowedDirection,
+				Allowed:         true,
+			}
+			// Get peers in the tags and add allowed rules
+			if userNode.StaticNode.Address != "" {
+				r.IPList = append(r.IPList, userNode.StaticNode.AddressIPNet4())
+			}
+			if userNode.StaticNode.Address6 != "" {
+				r.IP6List = append(r.IP6List, userNode.StaticNode.AddressIPNet6())
+			}
+			if aclRule, ok := rules[acl.ID]; ok {
+				aclRule.IPList = append(aclRule.IPList, r.IPList...)
+				aclRule.IP6List = append(aclRule.IP6List, r.IP6List...)
+				rules[acl.ID] = aclRule
+			} else {
+				rules[acl.ID] = r
+			}
+		}
+	}
+	return rules
+}
+
+func GetAclRulesForNode(targetnode *models.Node) (rules map[string]models.AclRule) {
+	defer func() {
+		if !targetnode.IsIngressGateway {
+			rules = getUserAclRulesForNode(targetnode, rules)
+		}
+
+	}()
+	rules = make(map[string]models.AclRule)
+	var taggedNodes map[models.TagID][]models.Node
+	if targetnode.IsIngressGateway {
+		taggedNodes = GetTagMapWithNodesByNetwork(models.NetworkID(targetnode.Network), false)
+	} else {
+		taggedNodes = GetTagMapWithNodesByNetwork(models.NetworkID(targetnode.Network), true)
+	}
+
+	acls := listDevicePolicies(models.NetworkID(targetnode.Network))
+	targetnode.Tags["*"] = struct{}{}
+	for nodeTag := range targetnode.Tags {
+		for _, acl := range acls {
+			if !acl.Enabled {
+				continue
+			}
+			srcTags := convAclTagToValueMap(acl.Src)
+			dstTags := convAclTagToValueMap(acl.Dst)
+			aclRule := models.AclRule{
+				ID:              acl.ID,
+				AllowedProtocol: acl.Proto,
+				AllowedPorts:    acl.Port,
+				Direction:       acl.AllowedDirection,
+				Allowed:         true,
+			}
+			if acl.AllowedDirection == models.TrafficDirectionBi {
+				var existsInSrcTag bool
+				var existsInDstTag bool
+
+				if _, ok := srcTags[nodeTag.String()]; ok {
+					existsInSrcTag = true
+				}
+				if _, ok := dstTags[nodeTag.String()]; ok {
+					existsInDstTag = true
+				}
+
+				if existsInSrcTag && !existsInDstTag {
+					// get all dst tags
+					for dst := range dstTags {
+						if dst == nodeTag.String() {
+							continue
+						}
+						// Get peers in the tags and add allowed rules
+						nodes := taggedNodes[models.TagID(dst)]
+						for _, node := range nodes {
+							if node.ID == targetnode.ID {
+								continue
+							}
+							if node.Address.IP != nil {
+								aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4())
+							}
+							if node.Address6.IP != nil {
+								aclRule.IP6List = append(aclRule.IP6List, node.AddressIPNet6())
+							}
+							if node.IsStatic && node.StaticNode.Address != "" {
+								aclRule.IPList = append(aclRule.IPList, node.StaticNode.AddressIPNet4())
+							}
+							if node.IsStatic && node.StaticNode.Address6 != "" {
+								aclRule.IP6List = append(aclRule.IP6List, node.StaticNode.AddressIPNet6())
+							}
+						}
+					}
+				}
+				if existsInDstTag && !existsInSrcTag {
+					// get all src tags
+					for src := range srcTags {
+						if src == nodeTag.String() {
+							continue
+						}
+						// Get peers in the tags and add allowed rules
+						nodes := taggedNodes[models.TagID(src)]
+						for _, node := range nodes {
+							if node.ID == targetnode.ID {
+								continue
+							}
+							if node.Address.IP != nil {
+								aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4())
+							}
+							if node.Address6.IP != nil {
+								aclRule.IP6List = append(aclRule.IP6List, node.AddressIPNet6())
+							}
+							if node.IsStatic && node.StaticNode.Address != "" {
+								aclRule.IPList = append(aclRule.IPList, node.StaticNode.AddressIPNet4())
+							}
+							if node.IsStatic && node.StaticNode.Address6 != "" {
+								aclRule.IP6List = append(aclRule.IP6List, node.StaticNode.AddressIPNet6())
+							}
+						}
+					}
+				}
+				if existsInDstTag && existsInSrcTag {
+					nodes := taggedNodes[nodeTag]
+					for _, node := range nodes {
+						if node.ID == targetnode.ID {
+							continue
+						}
+						if node.Address.IP != nil {
+							aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4())
+						}
+						if node.Address6.IP != nil {
+							aclRule.IP6List = append(aclRule.IP6List, node.AddressIPNet6())
+						}
+						if node.IsStatic && node.StaticNode.Address != "" {
+							aclRule.IPList = append(aclRule.IPList, node.StaticNode.AddressIPNet4())
+						}
+						if node.IsStatic && node.StaticNode.Address6 != "" {
+							aclRule.IP6List = append(aclRule.IP6List, node.StaticNode.AddressIPNet6())
+						}
+					}
+				}
+			} else {
+				_, all := dstTags["*"]
+				if _, ok := dstTags[nodeTag.String()]; ok || all {
+					// get all src tags
+					for src := range srcTags {
+						if src == nodeTag.String() {
+							continue
+						}
+						// Get peers in the tags and add allowed rules
+						nodes := taggedNodes[models.TagID(src)]
+						for _, node := range nodes {
+							if node.ID == targetnode.ID {
+								continue
+							}
+							if node.Address.IP != nil {
+								aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4())
+							}
+							if node.Address6.IP != nil {
+								aclRule.IP6List = append(aclRule.IP6List, node.AddressIPNet6())
+							}
+							if node.IsStatic && node.StaticNode.Address != "" {
+								aclRule.IPList = append(aclRule.IPList, node.StaticNode.AddressIPNet4())
+							}
+							if node.IsStatic && node.StaticNode.Address6 != "" {
+								aclRule.IP6List = append(aclRule.IP6List, node.StaticNode.AddressIPNet6())
+							}
+						}
+					}
+				}
+			}
+			if len(aclRule.IPList) > 0 || len(aclRule.IP6List) > 0 {
+				rules[acl.ID] = aclRule
+			}
+		}
+	}
+	return rules
+}

+ 4 - 0
logic/acls/nodeacls/retrieve.go

@@ -7,12 +7,16 @@ import (
 	"sync"
 
 	"github.com/gravitl/netmaker/logic/acls"
+	"github.com/gravitl/netmaker/servercfg"
 )
 
 var NodesAllowedACLMutex = &sync.Mutex{}
 
 // AreNodesAllowed - checks if nodes are allowed to communicate in their network ACL
 func AreNodesAllowed(networkID NetworkID, node1, node2 NodeID) bool {
+	if !servercfg.IsOldAclEnabled() {
+		return true
+	}
 	NodesAllowedACLMutex.Lock()
 	defer NodesAllowedACLMutex.Unlock()
 	var currentNetworkACL, err = FetchAllACLs(networkID)

+ 13 - 3
logic/dns.go

@@ -2,6 +2,7 @@ package logic
 
 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 	"os"
 	"regexp"
@@ -11,6 +12,7 @@ import (
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/servercfg"
 	"github.com/txn2/txeh"
 )
 
@@ -104,7 +106,7 @@ func GetNodeDNS(network string) ([]models.DNSEntry, error) {
 	if err != nil {
 		return dns, err
 	}
-
+	defaultDomain := servercfg.GetDefaultDomain()
 	for _, node := range nodes {
 		if node.Network != network {
 			continue
@@ -114,7 +116,7 @@ func GetNodeDNS(network string) ([]models.DNSEntry, error) {
 			continue
 		}
 		var entry = models.DNSEntry{}
-		entry.Name = fmt.Sprintf("%s.%s", host.Name, network)
+		entry.Name = fmt.Sprintf("%s.%s.%s", host.Name, network, defaultDomain)
 		entry.Network = network
 		if node.Address.IP != nil {
 			entry.Address = node.Address.IP.String()
@@ -224,9 +226,17 @@ func SortDNSEntrys(unsortedDNSEntrys []models.DNSEntry) {
 	})
 }
 
+// IsNetworkNameValid - checks if a netid of a network uses valid characters
+func IsDNSEntryValid(d string) bool {
+	re := regexp.MustCompile(`^[A-Za-z0-9-.]+$`)
+	return re.MatchString(d)
+}
+
 // ValidateDNSCreate - checks if an entry is valid
 func ValidateDNSCreate(entry models.DNSEntry) error {
-
+	if !IsDNSEntryValid(entry.Name) {
+		return errors.New("invalid input. Only uppercase letters (A-Z), lowercase letters (a-z), numbers (0-9), minus sign (-) and dots (.) are allowed")
+	}
 	v := validator.New()
 
 	_ = v.RegisterValidation("whitespace", func(f1 validator.FieldLevel) bool {

+ 156 - 69
logic/extpeers.go

@@ -456,6 +456,10 @@ func GetStaticNodeIps(node models.Node) (ips []net.IP) {
 
 func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
 	// fetch user access to static clients via policies
+	defer func() {
+		logger.Log(0, fmt.Sprintf("node.ID: %s, Rules: %+v\n", node.ID, rules))
+	}()
+
 	defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
 	defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
 	nodes, _ := GetNetworkNodes(node.Network)
@@ -468,36 +472,50 @@ func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
 			if peer.IsUserNode {
 				continue
 			}
-			if IsUserAllowedToCommunicate(userNodeI.StaticNode.OwnerID, peer) {
+			if ok, allowedPolicies := IsUserAllowedToCommunicate(userNodeI.StaticNode.OwnerID, peer); ok {
 				if peer.IsStatic {
 					if userNodeI.StaticNode.Address != "" {
 						if !defaultUserPolicy.Enabled {
-							rules = append(rules, models.FwRule{
-								SrcIP: userNodeI.StaticNode.AddressIPNet4(),
-								DstIP: peer.StaticNode.AddressIPNet4(),
-								Allow: true,
-							})
+							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,
+								})
+							}
 						}
-						rules = append(rules, models.FwRule{
-							SrcIP: peer.StaticNode.AddressIPNet4(),
-							DstIP: userNodeI.StaticNode.AddressIPNet4(),
-							Allow: true,
-						})
+
 					}
 					if userNodeI.StaticNode.Address6 != "" {
 						if !defaultUserPolicy.Enabled {
-							rules = append(rules, models.FwRule{
-								SrcIP: userNodeI.StaticNode.AddressIPNet6(),
-								DstIP: peer.StaticNode.AddressIPNet6(),
-								Allow: true,
-							})
+							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,
+								})
+
+							}
 						}
 
-						rules = append(rules, models.FwRule{
-							SrcIP: peer.StaticNode.AddressIPNet6(),
-							DstIP: userNodeI.StaticNode.AddressIPNet6(),
-							Allow: true,
-						})
 					}
 					if len(peer.StaticNode.ExtraAllowedIPs) > 0 {
 						for _, additionalAllowedIPNet := range peer.StaticNode.ExtraAllowedIPs {
@@ -526,29 +544,39 @@ func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
 
 					if userNodeI.StaticNode.Address != "" {
 						if !defaultUserPolicy.Enabled {
-							rules = append(rules, models.FwRule{
-								SrcIP: userNodeI.StaticNode.AddressIPNet4(),
-								DstIP: net.IPNet{
-									IP:   peer.Address.IP,
-									Mask: net.CIDRMask(32, 32),
-								},
-								Allow: true,
-							})
+							for _, policy := range allowedPolicies {
+								rules = append(rules, models.FwRule{
+									SrcIP: userNodeI.StaticNode.AddressIPNet4(),
+									DstIP: net.IPNet{
+										IP:   peer.Address.IP,
+										Mask: net.CIDRMask(32, 32),
+									},
+									AllowedProtocol: policy.Proto,
+									AllowedPorts:    policy.Port,
+									Allow:           true,
+								})
+							}
+
 						}
 					}
 
 					if userNodeI.StaticNode.Address6 != "" {
-						rules = append(rules, models.FwRule{
-							SrcIP: userNodeI.StaticNode.AddressIPNet6(),
-							DstIP: net.IPNet{
-								IP:   peer.Address6.IP,
-								Mask: net.CIDRMask(128, 128),
-							},
-							Allow: true,
-						})
+						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,
+								})
+							}
+						}
 					}
 				}
-
 			}
 		}
 	}
@@ -564,21 +592,48 @@ func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
 			if peer.StaticNode.ClientID == nodeI.StaticNode.ClientID || peer.IsUserNode {
 				continue
 			}
-			if IsNodeAllowedToCommunicate(nodeI, peer) {
+			if ok, allowedPolicies := IsNodeAllowedToCommunicate(nodeI, peer, true); ok {
 				if peer.IsStatic {
 					if nodeI.StaticNode.Address != "" {
-						rules = append(rules, models.FwRule{
-							SrcIP: nodeI.StaticNode.AddressIPNet4(),
-							DstIP: peer.StaticNode.AddressIPNet4(),
-							Allow: true,
-						})
+						for _, policy := range allowedPolicies {
+							rules = append(rules, models.FwRule{
+								SrcIP:           nodeI.StaticNode.AddressIPNet4(),
+								DstIP:           peer.StaticNode.AddressIPNet4(),
+								AllowedProtocol: policy.Proto,
+								AllowedPorts:    policy.Port,
+								Allow:           true,
+							})
+							if policy.AllowedDirection == models.TrafficDirectionBi {
+								rules = append(rules, models.FwRule{
+									SrcIP:           peer.StaticNode.AddressIPNet4(),
+									DstIP:           nodeI.StaticNode.AddressIPNet4(),
+									AllowedProtocol: policy.Proto,
+									AllowedPorts:    policy.Port,
+									Allow:           true,
+								})
+							}
+						}
+
 					}
 					if nodeI.StaticNode.Address6 != "" {
-						rules = append(rules, models.FwRule{
-							SrcIP: nodeI.StaticNode.AddressIPNet6(),
-							DstIP: peer.StaticNode.AddressIPNet6(),
-							Allow: true,
-						})
+						for _, policy := range allowedPolicies {
+							rules = append(rules, models.FwRule{
+								SrcIP:           nodeI.StaticNode.AddressIPNet6(),
+								DstIP:           peer.StaticNode.AddressIPNet6(),
+								AllowedProtocol: policy.Proto,
+								AllowedPorts:    policy.Port,
+								Allow:           true,
+							})
+							if policy.AllowedDirection == models.TrafficDirectionBi {
+								rules = append(rules, models.FwRule{
+									SrcIP:           peer.StaticNode.AddressIPNet6(),
+									DstIP:           nodeI.StaticNode.AddressIPNet6(),
+									AllowedProtocol: policy.Proto,
+									AllowedPorts:    policy.Port,
+									Allow:           true,
+								})
+							}
+						}
 					}
 					if len(peer.StaticNode.ExtraAllowedIPs) > 0 {
 						for _, additionalAllowedIPNet := range peer.StaticNode.ExtraAllowedIPs {
@@ -605,24 +660,56 @@ func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
 					}
 				} else {
 					if nodeI.StaticNode.Address != "" {
-						rules = append(rules, models.FwRule{
-							SrcIP: nodeI.StaticNode.AddressIPNet4(),
-							DstIP: net.IPNet{
-								IP:   peer.Address.IP,
-								Mask: net.CIDRMask(32, 32),
-							},
-							Allow: true,
-						})
+						for _, policy := range allowedPolicies {
+							rules = append(rules, models.FwRule{
+								SrcIP: nodeI.StaticNode.AddressIPNet4(),
+								DstIP: net.IPNet{
+									IP:   peer.Address.IP,
+									Mask: net.CIDRMask(32, 32),
+								},
+								AllowedProtocol: policy.Proto,
+								AllowedPorts:    policy.Port,
+								Allow:           true,
+							})
+							if policy.AllowedDirection == models.TrafficDirectionBi {
+								rules = append(rules, models.FwRule{
+									SrcIP: net.IPNet{
+										IP:   peer.Address.IP,
+										Mask: net.CIDRMask(32, 32),
+									},
+									DstIP:           nodeI.StaticNode.AddressIPNet4(),
+									AllowedProtocol: policy.Proto,
+									AllowedPorts:    policy.Port,
+									Allow:           true,
+								})
+							}
+						}
 					}
 					if nodeI.StaticNode.Address6 != "" {
-						rules = append(rules, models.FwRule{
-							SrcIP: nodeI.StaticNode.AddressIPNet6(),
-							DstIP: net.IPNet{
-								IP:   peer.Address6.IP,
-								Mask: net.CIDRMask(128, 128),
-							},
-							Allow: true,
-						})
+						for _, policy := range allowedPolicies {
+							rules = append(rules, models.FwRule{
+								SrcIP: nodeI.StaticNode.AddressIPNet6(),
+								DstIP: net.IPNet{
+									IP:   peer.Address6.IP,
+									Mask: net.CIDRMask(128, 128),
+								},
+								AllowedProtocol: policy.Proto,
+								AllowedPorts:    policy.Port,
+								Allow:           true,
+							})
+							if policy.AllowedDirection == models.TrafficDirectionBi {
+								rules = append(rules, models.FwRule{
+									SrcIP: net.IPNet{
+										IP:   peer.Address6.IP,
+										Mask: net.CIDRMask(128, 128),
+									},
+									DstIP:           nodeI.StaticNode.AddressIPNet6(),
+									AllowedProtocol: policy.Proto,
+									AllowedPorts:    policy.Port,
+									Allow:           true,
+								})
+							}
+						}
 					}
 				}
 
@@ -650,11 +737,11 @@ func GetExtPeers(node, peer *models.Node) ([]wgtypes.PeerConfig, []models.IDandA
 			continue
 		}
 		if extPeer.RemoteAccessClientID == "" {
-			if !IsNodeAllowedToCommunicate(extPeer.ConvertToStaticNode(), *peer) {
+			if ok := IsPeerAllowed(extPeer.ConvertToStaticNode(), *peer, true); !ok {
 				continue
 			}
 		} else {
-			if !IsUserAllowedToCommunicate(extPeer.OwnerID, *peer) {
+			if ok, _ := IsUserAllowedToCommunicate(extPeer.OwnerID, *peer); !ok {
 				continue
 			}
 		}
@@ -739,7 +826,7 @@ func getExtpeerEgressRanges(node models.Node) (ranges, ranges6 []net.IPNet) {
 		if len(extPeer.ExtraAllowedIPs) == 0 {
 			continue
 		}
-		if !IsNodeAllowedToCommunicate(extPeer.ConvertToStaticNode(), node) {
+		if ok, _ := IsNodeAllowedToCommunicate(extPeer.ConvertToStaticNode(), node, true); !ok {
 			continue
 		}
 		for _, allowedRange := range extPeer.ExtraAllowedIPs {
@@ -766,7 +853,7 @@ func getExtpeersExtraRoutes(node models.Node) (egressRoutes []models.EgressNetwo
 		if len(extPeer.ExtraAllowedIPs) == 0 {
 			continue
 		}
-		if !IsNodeAllowedToCommunicate(extPeer.ConvertToStaticNode(), node) {
+		if ok, _ := IsNodeAllowedToCommunicate(extPeer.ConvertToStaticNode(), node, true); !ok {
 			continue
 		}
 		egressRoutes = append(egressRoutes, getExtPeerEgressRoute(node, extPeer)...)

+ 125 - 3
logic/nodes.go

@@ -5,7 +5,9 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"maps"
 	"net"
+	"slices"
 	"sort"
 	"sync"
 	"time"
@@ -24,8 +26,10 @@ import (
 )
 
 var (
-	nodeCacheMutex = &sync.RWMutex{}
-	nodesCacheMap  = make(map[string]models.Node)
+	nodeCacheMutex        = &sync.RWMutex{}
+	nodeNetworkCacheMutex = &sync.RWMutex{}
+	nodesCacheMap         = make(map[string]models.Node)
+	nodesNetworkCacheMap  = make(map[string]map[string]models.Node)
 )
 
 func getNodeFromCache(nodeID string) (node models.Node, ok bool) {
@@ -48,12 +52,37 @@ func deleteNodeFromCache(nodeID string) {
 	delete(nodesCacheMap, nodeID)
 	nodeCacheMutex.Unlock()
 }
+func deleteNodeFromNetworkCache(nodeID string, network string) {
+	nodeNetworkCacheMutex.Lock()
+	delete(nodesNetworkCacheMap[network], nodeID)
+	nodeNetworkCacheMutex.Unlock()
+}
+
+func storeNodeInNetworkCache(node models.Node, network string) {
+	nodeNetworkCacheMutex.Lock()
+	if nodesNetworkCacheMap[network] == nil {
+		nodesNetworkCacheMap[network] = make(map[string]models.Node)
+	}
+	nodesNetworkCacheMap[network][node.ID.String()] = node
+	nodeNetworkCacheMutex.Unlock()
+}
 
 func storeNodeInCache(node models.Node) {
 	nodeCacheMutex.Lock()
 	nodesCacheMap[node.ID.String()] = node
 	nodeCacheMutex.Unlock()
 }
+func loadNodesIntoNetworkCache(nMap map[string]models.Node) {
+	nodeNetworkCacheMutex.Lock()
+	for _, v := range nMap {
+		network := v.Network
+		if nodesNetworkCacheMap[network] == nil {
+			nodesNetworkCacheMap[network] = make(map[string]models.Node)
+		}
+		nodesNetworkCacheMap[network][v.ID.String()] = v
+	}
+	nodeNetworkCacheMutex.Unlock()
+}
 
 func loadNodesIntoCache(nMap map[string]models.Node) {
 	nodeCacheMutex.Lock()
@@ -63,6 +92,7 @@ func loadNodesIntoCache(nMap map[string]models.Node) {
 func ClearNodeCache() {
 	nodeCacheMutex.Lock()
 	nodesCacheMap = make(map[string]models.Node)
+	nodesNetworkCacheMap = make(map[string]map[string]models.Node)
 	nodeCacheMutex.Unlock()
 }
 
@@ -77,6 +107,12 @@ const (
 
 // GetNetworkNodes - gets the nodes of a network
 func GetNetworkNodes(network string) ([]models.Node, error) {
+
+	if networkNodes, ok := nodesNetworkCacheMap[network]; ok {
+		nodeNetworkCacheMutex.Lock()
+		defer nodeNetworkCacheMutex.Unlock()
+		return slices.Collect(maps.Values(networkNodes)), nil
+	}
 	allnodes, err := GetAllNodes()
 	if err != nil {
 		return []models.Node{}, err
@@ -99,6 +135,12 @@ func GetHostNodes(host *models.Host) []models.Node {
 
 // GetNetworkNodesMemory - gets all nodes belonging to a network from list in memory
 func GetNetworkNodesMemory(allNodes []models.Node, network string) []models.Node {
+
+	if networkNodes, ok := nodesNetworkCacheMap[network]; ok {
+		nodeNetworkCacheMutex.Lock()
+		defer nodeNetworkCacheMutex.Unlock()
+		return slices.Collect(maps.Values(networkNodes))
+	}
 	var nodes = []models.Node{}
 	for i := range allNodes {
 		node := allNodes[i]
@@ -123,6 +165,7 @@ func UpdateNodeCheckin(node *models.Node) error {
 	}
 	if servercfg.CacheEnabled() {
 		storeNodeInCache(*node)
+		storeNodeInNetworkCache(*node, node.Network)
 	}
 	return nil
 }
@@ -140,6 +183,7 @@ func UpsertNode(newNode *models.Node) error {
 	}
 	if servercfg.CacheEnabled() {
 		storeNodeInCache(*newNode)
+		storeNodeInNetworkCache(*newNode, newNode.Network)
 	}
 	return nil
 }
@@ -179,6 +223,17 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error {
 			}
 			if servercfg.CacheEnabled() {
 				storeNodeInCache(*newNode)
+				storeNodeInNetworkCache(*newNode, newNode.Network)
+				if _, ok := allocatedIpMap[newNode.Network]; ok {
+					if newNode.Address.IP != nil && !newNode.Address.IP.Equal(currentNode.Address.IP) {
+						AddIpToAllocatedIpMap(newNode.Network, newNode.Address.IP)
+						RemoveIpFromAllocatedIpMap(currentNode.Network, currentNode.Address.IP.String())
+					}
+					if newNode.Address6.IP != nil && !newNode.Address6.IP.Equal(currentNode.Address6.IP) {
+						AddIpToAllocatedIpMap(newNode.Network, newNode.Address6.IP)
+						RemoveIpFromAllocatedIpMap(currentNode.Network, currentNode.Address6.IP.String())
+					}
+				}
 			}
 			return nil
 		}
@@ -288,6 +343,7 @@ func DeleteNodeByID(node *models.Node) error {
 	}
 	if servercfg.CacheEnabled() {
 		deleteNodeFromCache(node.ID.String())
+		deleteNodeFromNetworkCache(node.ID.String(), node.Network)
 	}
 	if servercfg.IsDNSMode() {
 		SetDNS()
@@ -350,6 +406,7 @@ func GetAllNodes() ([]models.Node, error) {
 	nodesMap := make(map[string]models.Node)
 	if servercfg.CacheEnabled() {
 		defer loadNodesIntoCache(nodesMap)
+		defer loadNodesIntoNetworkCache(nodesMap)
 	}
 	collection, err := database.FetchRecords(database.NODES_TABLE_NAME)
 	if err != nil {
@@ -388,6 +445,20 @@ func AddStaticNodestoList(nodes []models.Node) []models.Node {
 	return nodes
 }
 
+func AddStatusToNodes(nodes []models.Node) (nodesWithStatus []models.Node) {
+	aclDefaultPolicyStatusMap := make(map[string]bool)
+	for _, node := range nodes {
+		if _, ok := aclDefaultPolicyStatusMap[node.Network]; !ok {
+			// check default policy if all allowed return true
+			defaultPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
+			aclDefaultPolicyStatusMap[node.Network] = defaultPolicy.Enabled
+		}
+		GetNodeStatus(&node, aclDefaultPolicyStatusMap[node.Network])
+		nodesWithStatus = append(nodesWithStatus, node)
+	}
+	return
+}
+
 // GetNetworkByNode - gets the network model from a node
 func GetNetworkByNode(node *models.Node) (models.Network, error) {
 
@@ -459,6 +530,7 @@ func GetNodeByID(uuid string) (models.Node, error) {
 	}
 	if servercfg.CacheEnabled() {
 		storeNodeInCache(node)
+		storeNodeInNetworkCache(node, node.Network)
 	}
 	return node, nil
 }
@@ -612,6 +684,7 @@ func createNode(node *models.Node) error {
 	}
 	if servercfg.CacheEnabled() {
 		storeNodeInCache(*node)
+		storeNodeInNetworkCache(*node, node.Network)
 	}
 	if _, ok := allocatedIpMap[node.Network]; ok {
 		if node.Address.IP != nil {
@@ -663,6 +736,26 @@ func ValidateParams(nodeid, netid string) (models.Node, error) {
 	return node, nil
 }
 
+func ValidateNodeIp(currentNode *models.Node, newNode *models.ApiNode) error {
+
+	if currentNode.Address.IP != nil && currentNode.Address.String() != newNode.Address {
+		newIp, _, _ := net.ParseCIDR(newNode.Address)
+		ipAllocated := allocatedIpMap[currentNode.Network]
+		if _, ok := ipAllocated[newIp.String()]; ok {
+			return errors.New("ip specified is already allocated:  " + newNode.Address)
+		}
+	}
+	if currentNode.Address6.IP != nil && currentNode.Address6.String() != newNode.Address6 {
+		newIp, _, _ := net.ParseCIDR(newNode.Address6)
+		ipAllocated := allocatedIpMap[currentNode.Network]
+		if _, ok := ipAllocated[newIp.String()]; ok {
+			return errors.New("ip specified is already allocated:  " + newNode.Address6)
+		}
+	}
+
+	return nil
+}
+
 func ValidateEgressRange(gateway models.EgressGatewayRequest) error {
 	network, err := GetNetworkSettings(gateway.NetID)
 	if err != nil {
@@ -725,7 +818,7 @@ func GetTagMapWithNodes() (tagNodesMap map[models.TagID][]models.Node) {
 	return
 }
 
-func GetTagMapWithNodesByNetwork(netID models.NetworkID) (tagNodesMap map[models.TagID][]models.Node) {
+func GetTagMapWithNodesByNetwork(netID models.NetworkID, withStaticNodes bool) (tagNodesMap map[models.TagID][]models.Node) {
 	tagNodesMap = make(map[models.TagID][]models.Node)
 	nodes, _ := GetNetworkNodes(netID.String())
 	for _, nodeI := range nodes {
@@ -736,6 +829,10 @@ func GetTagMapWithNodesByNetwork(netID models.NetworkID) (tagNodesMap map[models
 			tagNodesMap[nodeTagID] = append(tagNodesMap[nodeTagID], nodeI)
 		}
 	}
+	tagNodesMap["*"] = nodes
+	if !withStaticNodes {
+		return
+	}
 	return AddTagMapWithStaticNodes(netID, tagNodesMap)
 }
 
@@ -749,6 +846,31 @@ func AddTagMapWithStaticNodes(netID models.NetworkID,
 		if extclient.Tags == nil || extclient.RemoteAccessClientID != "" {
 			continue
 		}
+		for tagID := range extclient.Tags {
+			tagNodesMap[tagID] = append(tagNodesMap[tagID], models.Node{
+				IsStatic:   true,
+				StaticNode: extclient,
+			})
+			tagNodesMap["*"] = append(tagNodesMap["*"], models.Node{
+				IsStatic:   true,
+				StaticNode: extclient,
+			})
+		}
+
+	}
+	return tagNodesMap
+}
+
+func AddTagMapWithStaticNodesWithUsers(netID models.NetworkID,
+	tagNodesMap map[models.TagID][]models.Node) map[models.TagID][]models.Node {
+	extclients, err := GetNetworkExtClients(netID.String())
+	if err != nil {
+		return tagNodesMap
+	}
+	for _, extclient := range extclients {
+		if extclient.Tags == nil {
+			continue
+		}
 		for tagID := range extclient.Tags {
 			tagNodesMap[tagID] = append(tagNodesMap[tagID], models.Node{
 				IsStatic:   true,

+ 40 - 3
logic/peers.go

@@ -74,8 +74,10 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 		ServerVersion: servercfg.GetVersion(),
 		ServerAddrs:   []models.ServerAddr{},
 		FwUpdate: models.FwUpdate{
+			AllowAll:    true,
 			EgressInfo:  make(map[string]models.EgressInfo),
 			IngressInfo: make(map[string]models.IngressInfo),
+			AclRules:    make(map[string]models.AclRule),
 		},
 		PeerIDs:           make(models.PeerMap, 0),
 		Peers:             []wgtypes.PeerConfig{},
@@ -83,6 +85,24 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 		HostNetworkInfo:   models.HostInfoMap{},
 		EndpointDetection: servercfg.IsEndpointDetectionEnabled(),
 	}
+	defer func() {
+		if !hostPeerUpdate.FwUpdate.AllowAll {
+			aclRule := models.AclRule{
+				ID:              "allowed-network-rules",
+				AllowedProtocol: models.ALL,
+				Direction:       models.TrafficDirectionBi,
+				Allowed:         true,
+			}
+			for _, allowedNet := range hostPeerUpdate.FwUpdate.AllowedNetworks {
+				if allowedNet.IP.To4() != nil {
+					aclRule.IPList = append(aclRule.IPList, allowedNet)
+				} else {
+					aclRule.IP6List = append(aclRule.IP6List, allowedNet)
+				}
+			}
+			hostPeerUpdate.FwUpdate.AclRules["allowed-network-rules"] = aclRule
+		}
+	}()
 
 	slog.Debug("peer update for host", "hostId", host.ID.String())
 	peerIndexMap := make(map[string]int)
@@ -154,6 +174,22 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 		if !hostPeerUpdate.IsInternetGw {
 			hostPeerUpdate.IsInternetGw = IsInternetGw(node)
 		}
+		defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
+		defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
+
+		if defaultDevicePolicy.Enabled && defaultUserPolicy.Enabled {
+			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 {
+			hostPeerUpdate.FwUpdate.AllowAll = false
+			hostPeerUpdate.FwUpdate.AclRules = GetAclRulesForNode(&node)
+		}
+
 		currentPeers := GetNetworkNodesMemory(allNodes, node.Network)
 		for _, peer := range currentPeers {
 			peer := peer
@@ -255,11 +291,12 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 				peerConfig.Endpoint.Port = peerHost.ListenPort
 			}
 			allowedips := GetAllowedIPs(&node, &peer, nil)
+			allowedToComm := IsPeerAllowed(node, peer, false)
 			if peer.Action != models.NODE_DELETE &&
 				!peer.PendingDelete &&
 				peer.Connected &&
 				nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) &&
-				IsNodeAllowedToCommunicate(node, peer) &&
+				(defaultDevicePolicy.Enabled || allowedToComm) &&
 				(deletedNode == nil || (deletedNode != nil && peer.ID.String() != deletedNode.ID.String())) {
 				peerConfig.AllowedIPs = allowedips // only append allowed IPs if valid connection
 			}
@@ -309,8 +346,6 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 			hostPeerUpdate.FwUpdate.IsIngressGw = true
 			extPeers, extPeerIDAndAddrs, egressRoutes, err = GetExtPeers(&node, &node)
 			if err == nil {
-				defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
-				defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
 				if !defaultDevicePolicy.Enabled || !defaultUserPolicy.Enabled {
 					ingFwUpdate := models.IngressInfo{
 						IngressID:     node.ID.String(),
@@ -426,6 +461,8 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 	}
 
 	hostPeerUpdate.ManageDNS = servercfg.GetManageDNS()
+	hostPeerUpdate.Stun = servercfg.IsStunEnabled()
+	hostPeerUpdate.StunServers = servercfg.GetStunServers()
 	return hostPeerUpdate, nil
 }
 

+ 13 - 0
logic/proc.go

@@ -2,6 +2,7 @@ package logic
 
 import (
 	"os"
+	"runtime"
 	"runtime/pprof"
 
 	"github.com/gravitl/netmaker/logger"
@@ -22,3 +23,15 @@ func StopCPUProfiling(f *os.File) {
 	pprof.StopCPUProfile()
 	f.Close()
 }
+
+func StartMemProfiling() {
+	f, err := os.OpenFile("/root/data/mem.prof", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0755)
+	if err != nil {
+		logger.Log(0, "could not create Memory profile: ", err.Error())
+	}
+	defer f.Close()
+	runtime.GC() // get up-to-date statistics
+	if err = pprof.WriteHeapProfile(f); err != nil {
+		logger.Log(0, "could not write memory profile: ", err.Error())
+	}
+}

+ 26 - 0
logic/status.go

@@ -0,0 +1,26 @@
+package logic
+
+import (
+	"time"
+
+	"github.com/gravitl/netmaker/models"
+)
+
+var GetNodeStatus = getNodeStatus
+
+func getNodeStatus(node *models.Node, t bool) {
+	// On CE check only last check-in time
+	if node.IsStatic {
+		if !node.StaticNode.Enabled {
+			node.Status = models.OfflineSt
+			return
+		}
+		node.Status = models.OnlineSt
+		return
+	}
+	if time.Since(node.LastCheckIn) > time.Minute*10 {
+		node.Status = models.OfflineSt
+		return
+	}
+	node.Status = models.OnlineSt
+}

+ 1 - 1
logic/tags.go

@@ -85,7 +85,7 @@ func ListTagsWithNodes(netID models.NetworkID) ([]models.TagListResp, error) {
 	if err != nil {
 		return []models.TagListResp{}, err
 	}
-	tagsNodeMap := GetTagMapWithNodesByNetwork(netID)
+	tagsNodeMap := GetTagMapWithNodesByNetwork(netID, true)
 	resp := []models.TagListResp{}
 	for _, tagI := range tags {
 		tagRespI := models.TagListResp{

+ 19 - 0
logic/user_mgmt.go

@@ -98,6 +98,25 @@ func ListPlatformRoles() ([]models.UserRolePermissionTemplate, error) {
 	return userRoles, nil
 }
 
+func GetUserGrpMap() map[models.UserGroupID]map[string]struct{} {
+	grpUsersMap := make(map[models.UserGroupID]map[string]struct{})
+	users, _ := GetUsersDB()
+	for _, user := range users {
+		for gID := range user.UserGroups {
+			if grpUsers, ok := grpUsersMap[gID]; ok {
+				grpUsers[user.UserName] = struct{}{}
+				grpUsersMap[gID] = grpUsers
+			} else {
+				grpUsersMap[gID] = make(map[string]struct{})
+				grpUsersMap[gID][user.UserName] = struct{}{}
+			}
+		}
+
+	}
+
+	return grpUsersMap
+}
+
 func userRolesInit() {
 	d, _ := json.Marshal(SuperAdminPermissionTemplate)
 	database.Insert(SuperAdminPermissionTemplate.ID.String(), string(d), database.USER_PERMISSIONS_TABLE_NAME)

+ 28 - 1
logic/util.go

@@ -6,11 +6,14 @@ import (
 	"encoding/base32"
 	"encoding/base64"
 	"encoding/json"
+	"fmt"
 	"net"
 	"os"
 	"strings"
 	"time"
+	"unicode"
 
+	"github.com/blang/semver"
 	"github.com/c-robinson/iplib"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
@@ -148,4 +151,28 @@ func IsSlicesEqual(a, b []string) bool {
 	return true
 }
 
-// == private ==
+// VersionLessThan checks if v1 < v2 semantically
+// dev is the latest version
+func VersionLessThan(v1, v2 string) (bool, error) {
+	if v1 == "dev" {
+		return false, nil
+	}
+	if v2 == "dev" {
+		return true, nil
+	}
+	semVer1 := strings.TrimFunc(v1, func(r rune) bool {
+		return !unicode.IsNumber(r)
+	})
+	semVer2 := strings.TrimFunc(v2, func(r rune) bool {
+		return !unicode.IsNumber(r)
+	})
+	sv1, err := semver.Parse(semVer1)
+	if err != nil {
+		return false, fmt.Errorf("failed to parse semver1 (%s): %w", semVer1, err)
+	}
+	sv2, err := semver.Parse(semVer2)
+	if err != nil {
+		return false, fmt.Errorf("failed to parse semver2 (%s): %w", semVer2, err)
+	}
+	return sv1.LT(sv2), nil
+}

+ 12 - 2
main.go

@@ -24,13 +24,14 @@ import (
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/serverctl"
+	_ "go.uber.org/automaxprocs"
 	"golang.org/x/exp/slog"
 )
 
-var version = "v0.26.0"
+var version = "v0.30.0"
 
 //	@title			NetMaker
-//	@version		0.26.0
+//	@version		0.30.0
 //	@description	NetMaker API Docs
 //	@tag.name	    APIUsage
 //	@tag.description.markdown
@@ -99,6 +100,15 @@ func initialize() { // Client Mode Prereq Check
 		logger.FatalLog("Error connecting to database: ", err.Error())
 	}
 	logger.Log(0, "database successfully connected")
+
+	//initialize cache
+	_, _ = logic.GetNetworks()
+	_, _ = logic.GetAllNodes()
+	_, _ = logic.GetAllHosts()
+	_, _ = logic.GetAllExtClients()
+	_ = logic.ListAcls()
+	_, _ = logic.GetAllEnrollmentKeys()
+
 	migrate.Run()
 
 	logic.SetJWTSecret()

+ 4 - 1
main_ee.go

@@ -3,7 +3,10 @@
 
 package main
 
-import "github.com/gravitl/netmaker/pro"
+import (
+	"github.com/gravitl/netmaker/pro"
+	_ "go.uber.org/automaxprocs"
+)
 
 func init() {
 	pro.InitPro()

+ 2 - 2
migrate/migrate.go

@@ -20,8 +20,6 @@ import (
 
 // Run - runs all migrations
 func Run() {
-	_, _ = logic.GetAllNodes()
-	_, _ = logic.GetAllHosts()
 	updateEnrollmentKeys()
 	assignSuperAdmin()
 	createDefaultTagsAndPolicies()
@@ -439,5 +437,7 @@ func createDefaultTagsAndPolicies() {
 	for _, network := range networks {
 		logic.CreateDefaultTags(models.NetworkID(network.NetID))
 		logic.CreateDefaultAclNetworkPolicies(models.NetworkID(network.NetID))
+
 	}
+	logic.MigrateAclPolicies()
 }

+ 48 - 0
models/acl.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"net"
 	"time"
 )
 
@@ -14,6 +15,32 @@ const (
 	TrafficDirectionBi
 )
 
+// Protocol - allowed protocol
+type Protocol string
+
+const (
+	ALL  Protocol = "all"
+	UDP  Protocol = "udp"
+	TCP  Protocol = "tcp"
+	ICMP Protocol = "icmp"
+)
+
+type ServiceType string
+
+const (
+	Http        = "HTTP"
+	Https       = "HTTPS"
+	AllTCP      = "All TCP"
+	AllUDP      = "All UDP"
+	ICMPService = "ICMP"
+	Custom      = "Custom"
+	Any         = "Any"
+)
+
+func (p Protocol) String() string {
+	return string(p)
+}
+
 type AclPolicyType string
 
 const (
@@ -59,6 +86,9 @@ type Acl struct {
 	RuleType         AclPolicyType           `json:"policy_type"`
 	Src              []AclPolicyTag          `json:"src_type"`
 	Dst              []AclPolicyTag          `json:"dst_type"`
+	Proto            Protocol                `json:"protocol"` // tcp, udp, etc.
+	ServiceType      string                  `json:"type"`
+	Port             []string                `json:"ports"`
 	AllowedDirection AllowedTrafficDirection `json:"allowed_traffic_direction"`
 	Enabled          bool                    `json:"enabled"`
 	CreatedBy        string                  `json:"created_by"`
@@ -66,7 +96,25 @@ type Acl struct {
 }
 
 type AclPolicyTypes struct {
+	ProtocolTypes []ProtocolType
 	RuleTypes     []AclPolicyType `json:"policy_types"`
 	SrcGroupTypes []AclGroupType  `json:"src_grp_types"`
 	DstGroupTypes []AclGroupType  `json:"dst_grp_types"`
 }
+
+type ProtocolType struct {
+	Name             string     `json:"name"`
+	AllowedProtocols []Protocol `json:"allowed_protocols"`
+	PortRange        string     `json:"port_range"`
+	AllowPortSetting bool       `json:"allow_port_setting"`
+}
+
+type AclRule struct {
+	ID              string                  `json:"id"`
+	IPList          []net.IPNet             `json:"ip_list"`
+	IP6List         []net.IPNet             `json:"ip6_list"`
+	AllowedProtocol Protocol                `json:"allowed_protocols"` // tcp, udp, etc.
+	AllowedPorts    []string                `json:"allowed_ports"`
+	Direction       AllowedTrafficDirection `json:"direction"` // single or two-way
+	Allowed         bool
+}

+ 6 - 4
models/api_node.go

@@ -16,10 +16,10 @@ type ApiNode struct {
 	Address6                   string   `json:"address6" validate:"omitempty,cidrv6"`
 	LocalAddress               string   `json:"localaddress" validate:"omitempty,cidr"`
 	AllowedIPs                 []string `json:"allowedips"`
-	LastModified               int64    `json:"lastmodified"`
-	ExpirationDateTime         int64    `json:"expdatetime"`
-	LastCheckIn                int64    `json:"lastcheckin"`
-	LastPeerUpdate             int64    `json:"lastpeerupdate"`
+	LastModified               int64    `json:"lastmodified" swaggertype:"primitive,integer" format:"int64"`
+	ExpirationDateTime         int64    `json:"expdatetime" swaggertype:"primitive,integer" format:"int64"`
+	LastCheckIn                int64    `json:"lastcheckin" swaggertype:"primitive,integer" format:"int64"`
+	LastPeerUpdate             int64    `json:"lastpeerupdate" swaggertype:"primitive,integer" format:"int64"`
 	Network                    string   `json:"network"`
 	NetworkRange               string   `json:"networkrange"`
 	NetworkRange6              string   `json:"networkrange6"`
@@ -52,6 +52,7 @@ type ApiNode struct {
 	IsStatic          bool                `json:"is_static"`
 	IsUserNode        bool                `json:"is_user_node"`
 	StaticNode        ExtClient           `json:"static_node"`
+	Status            NodeStatus          `json:"status"`
 }
 
 // ApiNode.ConvertToServerNode - converts an api node to a server node
@@ -192,6 +193,7 @@ func (nm *Node) ConvertToAPINode() *ApiNode {
 	apiNode.IsStatic = nm.IsStatic
 	apiNode.IsUserNode = nm.IsUserNode
 	apiNode.StaticNode = nm.StaticNode
+	apiNode.Status = nm.Status
 	return &apiNode
 }
 

+ 1 - 1
models/enrollment_key.go

@@ -58,7 +58,7 @@ type EnrollmentKey struct {
 
 // APIEnrollmentKey - used to create enrollment keys via API
 type APIEnrollmentKey struct {
-	Expiration    int64    `json:"expiration"`
+	Expiration    int64    `json:"expiration" swaggertype:"primitive,integer" format:"int64"`
 	UsesRemaining int      `json:"uses_remaining"`
 	Networks      []string `json:"networks"`
 	Unlimited     bool     `json:"unlimited"`

+ 20 - 11
models/extclient.go

@@ -13,7 +13,7 @@ type ExtClient struct {
 	AllowedIPs             []string            `json:"allowed_ips"`
 	IngressGatewayID       string              `json:"ingressgatewayid" bson:"ingressgatewayid"`
 	IngressGatewayEndpoint string              `json:"ingressgatewayendpoint" bson:"ingressgatewayendpoint"`
-	LastModified           int64               `json:"lastmodified" bson:"lastmodified"`
+	LastModified           int64               `json:"lastmodified" bson:"lastmodified" swaggertype:"primitive,integer" format:"int64"`
 	Enabled                bool                `json:"enabled" bson:"enabled"`
 	OwnerID                string              `json:"ownerid" bson:"ownerid"`
 	DeniedACLs             map[string]struct{} `json:"deniednodeacls" bson:"acls,omitempty"`
@@ -21,20 +21,29 @@ type ExtClient struct {
 	PostUp                 string              `json:"postup" bson:"postup"`
 	PostDown               string              `json:"postdown" bson:"postdown"`
 	Tags                   map[TagID]struct{}  `json:"tags"`
+	Os                     string              `json:"os"`
+	DeviceName             string              `json:"device_name"`
+	PublicEndpoint         string              `json:"public_endpoint"`
+	Country                string              `json:"country"`
 }
 
 // CustomExtClient - struct for CustomExtClient params
 type CustomExtClient struct {
-	ClientID             string              `json:"clientid,omitempty"`
-	PublicKey            string              `json:"publickey,omitempty"`
-	DNS                  string              `json:"dns,omitempty"`
-	ExtraAllowedIPs      []string            `json:"extraallowedips,omitempty"`
-	Enabled              bool                `json:"enabled,omitempty"`
-	DeniedACLs           map[string]struct{} `json:"deniednodeacls" bson:"acls,omitempty"`
-	RemoteAccessClientID string              `json:"remote_access_client_id"` // unique ID (MAC address) of RAC machine
-	PostUp               string              `json:"postup" bson:"postup" validate:"max=1024"`
-	PostDown             string              `json:"postdown" bson:"postdown" validate:"max=1024"`
-	Tags                 map[TagID]struct{}  `json:"tags"`
+	ClientID                   string              `json:"clientid,omitempty"`
+	PublicKey                  string              `json:"publickey,omitempty"`
+	DNS                        string              `json:"dns,omitempty"`
+	ExtraAllowedIPs            []string            `json:"extraallowedips,omitempty"`
+	Enabled                    bool                `json:"enabled,omitempty"`
+	DeniedACLs                 map[string]struct{} `json:"deniednodeacls" bson:"acls,omitempty"`
+	RemoteAccessClientID       string              `json:"remote_access_client_id"` // unique ID (MAC address) of RAC machine
+	PostUp                     string              `json:"postup" bson:"postup" validate:"max=1024"`
+	PostDown                   string              `json:"postdown" bson:"postdown" validate:"max=1024"`
+	Tags                       map[TagID]struct{}  `json:"tags"`
+	Os                         string              `json:"os"`
+	DeviceName                 string              `json:"device_name"`
+	IsAlreadyConnectedToInetGw bool                `json:"is_already_connected_to_inet_gw"`
+	PublicEndpoint             string              `json:"public_endpoint"`
+	Country                    string              `json:"country"`
 }
 
 func (ext *ExtClient) ConvertToStaticNode() Node {

+ 1 - 1
models/host.go

@@ -71,7 +71,7 @@ type Host struct {
 	IsDefault           bool             `json:"isdefault"               yaml:"isdefault"`
 	NatType             string           `json:"nat_type,omitempty"      yaml:"nat_type,omitempty"`
 	TurnEndpoint        *netip.AddrPort  `json:"turn_endpoint,omitempty" yaml:"turn_endpoint,omitempty"`
-	PersistentKeepalive time.Duration    `json:"persistentkeepalive"     yaml:"persistentkeepalive"`
+	PersistentKeepalive time.Duration    `json:"persistentkeepalive" swaggertype:"primitive,integer" format:"int64" yaml:"persistentkeepalive"`
 }
 
 // FormatBool converts a boolean to a [yes|no] string

+ 8 - 8
models/metrics.go

@@ -15,14 +15,14 @@ type Metrics struct {
 // Metric - holds a metric for data between nodes
 type Metric struct {
 	NodeName          string        `json:"node_name" bson:"node_name" yaml:"node_name"`
-	Uptime            int64         `json:"uptime" bson:"uptime" yaml:"uptime"`
-	TotalTime         int64         `json:"totaltime" bson:"totaltime" yaml:"totaltime"`
-	Latency           int64         `json:"latency" bson:"latency" yaml:"latency"`
-	TotalReceived     int64         `json:"totalreceived" bson:"totalreceived" yaml:"totalreceived"`
-	LastTotalReceived int64         `json:"lasttotalreceived" bson:"lasttotalreceived" yaml:"lasttotalreceived"`
-	TotalSent         int64         `json:"totalsent" bson:"totalsent" yaml:"totalsent"`
-	LastTotalSent     int64         `json:"lasttotalsent" bson:"lasttotalsent" yaml:"lasttotalsent"`
-	ActualUptime      time.Duration `json:"actualuptime" bson:"actualuptime" yaml:"actualuptime"`
+	Uptime            int64         `json:"uptime" bson:"uptime" yaml:"uptime" swaggertype:"primitive,integer" format:"int64"`
+	TotalTime         int64         `json:"totaltime" bson:"totaltime" yaml:"totaltime" swaggertype:"primitive,integer" format:"int64"`
+	Latency           int64         `json:"latency" bson:"latency" yaml:"latency" swaggertype:"primitive,integer" format:"int64"`
+	TotalReceived     int64         `json:"totalreceived" bson:"totalreceived" yaml:"totalreceived" swaggertype:"primitive,integer" format:"int64"`
+	LastTotalReceived int64         `json:"lasttotalreceived" bson:"lasttotalreceived" yaml:"lasttotalreceived" swaggertype:"primitive,integer" format:"int64"`
+	TotalSent         int64         `json:"totalsent" bson:"totalsent" yaml:"totalsent" swaggertype:"primitive,integer" format:"int64"`
+	LastTotalSent     int64         `json:"lasttotalsent" bson:"lasttotalsent" yaml:"lasttotalsent" swaggertype:"primitive,integer" format:"int64"`
+	ActualUptime      time.Duration `json:"actualuptime" swaggertype:"primitive,integer" format:"int64" bson:"actualuptime" yaml:"actualuptime"`
 	PercentUp         float64       `json:"percentup" bson:"percentup" yaml:"percentup"`
 	Connected         bool          `json:"connected" bson:"connected" yaml:"connected"`
 }

+ 14 - 7
models/mqtt.go

@@ -25,12 +25,16 @@ type HostPeerUpdate struct {
 	ReplacePeers      bool                  `json:"replace_peers"`
 	EndpointDetection bool                  `json:"endpoint_detection"`
 	ManageDNS         bool                  `yaml:"manage_dns"`
+	Stun              bool                  `yaml:"stun"`
+	StunServers       string                `yaml:"stun_servers"`
 }
 
 type FwRule struct {
-	SrcIP net.IPNet
-	DstIP net.IPNet
-	Allow bool
+	SrcIP           net.IPNet `json:"src_ip"`
+	DstIP           net.IPNet `json:"dst_ip"`
+	AllowedProtocol Protocol  `json:"allowed_protocols"` // tcp, udp, etc.
+	AllowedPorts    []string  `json:"allowed_ports"`
+	Allow           bool      `json:"allow"`
 }
 
 // IngressInfo - struct for ingress info
@@ -90,10 +94,13 @@ type KeyUpdate struct {
 
 // FwUpdate - struct for firewall updates
 type FwUpdate struct {
-	IsEgressGw  bool                   `json:"is_egress_gw"`
-	IsIngressGw bool                   `json:"is_ingress_gw"`
-	EgressInfo  map[string]EgressInfo  `json:"egress_info"`
-	IngressInfo map[string]IngressInfo `json:"ingress_info"`
+	AllowAll        bool                   `json:"allow_all"`
+	AllowedNetworks []net.IPNet            `json:"networks"`
+	IsEgressGw      bool                   `json:"is_egress_gw"`
+	IsIngressGw     bool                   `json:"is_ingress_gw"`
+	EgressInfo      map[string]EgressInfo  `json:"egress_info"`
+	IngressInfo     map[string]IngressInfo `json:"ingress_info"`
+	AclRules        map[string]AclRule     `json:"acl_rules"`
 }
 
 // FailOverMeReq - struct for failover req

+ 2 - 2
models/network.go

@@ -11,8 +11,8 @@ type Network struct {
 	AddressRange        string `json:"addressrange" bson:"addressrange" validate:"omitempty,cidrv4"`
 	AddressRange6       string `json:"addressrange6" bson:"addressrange6" validate:"omitempty,cidrv6"`
 	NetID               string `json:"netid" bson:"netid" validate:"required,min=1,max=32,netid_valid"`
-	NodesLastModified   int64  `json:"nodeslastmodified" bson:"nodeslastmodified"`
-	NetworkLastModified int64  `json:"networklastmodified" bson:"networklastmodified"`
+	NodesLastModified   int64  `json:"nodeslastmodified" bson:"nodeslastmodified" swaggertype:"primitive,integer" format:"int64"`
+	NetworkLastModified int64  `json:"networklastmodified" bson:"networklastmodified" swaggertype:"primitive,integer" format:"int64"`
 	DefaultInterface    string `json:"defaultinterface" bson:"defaultinterface" validate:"min=1,max=35"`
 	DefaultListenPort   int32  `json:"defaultlistenport,omitempty" bson:"defaultlistenport,omitempty" validate:"omitempty,min=1024,max=65535"`
 	NodeLimit           int32  `json:"nodelimit" bson:"nodelimit"`

+ 31 - 4
models/node.go

@@ -11,6 +11,19 @@ import (
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
+type NodeStatus string
+
+const (
+	OnlineSt  NodeStatus = "online"
+	OfflineSt NodeStatus = "offline"
+	WarningSt NodeStatus = "warning"
+	ErrorSt   NodeStatus = "error"
+	UnKnown   NodeStatus = "unknown"
+)
+
+// LastCheckInThreshold - if node's checkin more than this threshold,then node is declared as offline
+const LastCheckInThreshold = time.Minute * 10
+
 const (
 	// NODE_SERVER_NAME - the default server name
 	NODE_SERVER_NAME = "netmaker"
@@ -103,6 +116,7 @@ type Node struct {
 	IsStatic          bool                `json:"is_static"`
 	IsUserNode        bool                `json:"is_user_node"`
 	StaticNode        ExtClient           `json:"static_node"`
+	Status            NodeStatus          `json:"node_status"`
 }
 
 // LegacyNode - legacy struct for node model
@@ -123,10 +137,10 @@ type LegacyNode struct {
 	IsHub                   string               `json:"ishub"                   bson:"ishub"                   yaml:"ishub"                   validate:"checkyesorno"`
 	AccessKey               string               `json:"accesskey"               bson:"accesskey"               yaml:"accesskey"`
 	Interface               string               `json:"interface"               bson:"interface"               yaml:"interface"`
-	LastModified            int64                `json:"lastmodified"            bson:"lastmodified"            yaml:"lastmodified"`
-	ExpirationDateTime      int64                `json:"expdatetime"             bson:"expdatetime"             yaml:"expdatetime"`
-	LastPeerUpdate          int64                `json:"lastpeerupdate"          bson:"lastpeerupdate"          yaml:"lastpeerupdate"`
-	LastCheckIn             int64                `json:"lastcheckin"             bson:"lastcheckin"             yaml:"lastcheckin"`
+	LastModified            int64                `json:"lastmodified"            bson:"lastmodified"            yaml:"lastmodified" swaggertype:"primitive,integer" format:"int64"`
+	ExpirationDateTime      int64                `json:"expdatetime"             bson:"expdatetime"             yaml:"expdatetime" swaggertype:"primitive,integer" format:"int64"`
+	LastPeerUpdate          int64                `json:"lastpeerupdate"          bson:"lastpeerupdate"          yaml:"lastpeerupdate" swaggertype:"primitive,integer" format:"int64"`
+	LastCheckIn             int64                `json:"lastcheckin"             bson:"lastcheckin"             yaml:"lastcheckin" swaggertype:"primitive,integer" format:"int64"`
 	MacAddress              string               `json:"macaddress"              bson:"macaddress"              yaml:"macaddress"`
 	Password                string               `json:"password"                bson:"password"                yaml:"password"                validate:"required,min=6"`
 	Network                 string               `json:"network"                 bson:"network"                 yaml:"network"                 validate:"network_exists"`
@@ -201,6 +215,19 @@ func (node *Node) PrimaryAddress() string {
 	return node.Address6.IP.String()
 }
 
+func (node *Node) AddressIPNet4() net.IPNet {
+	return net.IPNet{
+		IP:   node.Address.IP,
+		Mask: net.CIDRMask(32, 32),
+	}
+}
+func (node *Node) AddressIPNet6() net.IPNet {
+	return net.IPNet{
+		IP:   node.Address6.IP,
+		Mask: net.CIDRMask(128, 128),
+	}
+}
+
 // ExtClient.PrimaryAddress - returns ipv4 IPNet format
 func (extPeer *ExtClient) AddressIPNet4() net.IPNet {
 	return net.IPNet{

+ 3 - 1
models/structs.go

@@ -196,7 +196,7 @@ type ServerUpdateData struct {
 // also contains assymetrical encryption pub/priv keys for any server traffic
 type Telemetry struct {
 	UUID           string `json:"uuid" bson:"uuid"`
-	LastSend       int64  `json:"lastsend" bson:"lastsend"`
+	LastSend       int64  `json:"lastsend" bson:"lastsend" swaggertype:"primitive,integer" format:"int64"`
 	TrafficKeyPriv []byte `json:"traffickeypriv" bson:"traffickeypriv"`
 	TrafficKeyPub  []byte `json:"traffickeypub" bson:"traffickeypub"`
 }
@@ -267,6 +267,8 @@ type ServerConfig struct {
 	TrafficKey     []byte `yaml:"traffickey"`
 	MetricInterval string `yaml:"metric_interval"`
 	ManageDNS      bool   `yaml:"manage_dns"`
+	Stun           bool   `yaml:"stun"`
+	StunServers    string `yaml:"stun_servers"`
 	DefaultDomain  string `yaml:"default_domain"`
 }
 

+ 1 - 1
mq/emqx_on_prem.go

@@ -261,7 +261,7 @@ func (e *EmqxOnPrem) CreateDefaultAllowRule() error {
 	if err != nil {
 		return err
 	}
-	req, err := http.NewRequest(http.MethodPost, servercfg.GetEmqxRestEndpoint()+"/api/v5/authorization/sources/built_in_database/all", bytes.NewReader(payload))
+	req, err := http.NewRequest(http.MethodPost, servercfg.GetEmqxRestEndpoint()+"/api/v5/authorization/sources/built_in_database/rules/all", bytes.NewReader(payload))
 	if err != nil {
 		return err
 	}

+ 24 - 2
mq/migrate.go

@@ -88,10 +88,32 @@ func SendPullSYN() error {
 			Host:   host,
 		}
 		msg, _ := json.Marshal(hostUpdate)
-		encrypted, encryptErr := encryptMsg(&host, msg)
-		if encryptErr != nil {
+		var encrypted []byte
+		var encryptErr error
+		vlt, err := logic.VersionLessThan(host.Version, "v0.30.0")
+		if err != nil {
+			slog.Warn("error checking version less than", "warn", err)
 			continue
 		}
+		if vlt {
+			encrypted, encryptErr = encryptMsg(&host, msg)
+			if encryptErr != nil {
+				slog.Warn("error encrypt with encryptMsg", "warn", encryptErr)
+				continue
+			}
+		} else {
+			zipped, err := compressPayload(msg)
+			if err != nil {
+				slog.Warn("error compressing message", "warn", err)
+				continue
+			}
+			encrypted, encryptErr = encryptAESGCM(host.TrafficKeyPublic[0:32], zipped)
+			if encryptErr != nil {
+				slog.Warn("error encrypt with encryptMsg", "warn", encryptErr)
+				continue
+			}
+		}
+
 		logger.Log(0, "sending pull syn to", host.Name)
 		mqclient.Publish(fmt.Sprintf("host/update/%s/%s", hostUpdate.Host.ID.String(), servercfg.GetServer()), 0, true, encrypted)
 	}

+ 1 - 1
mq/mq.go

@@ -35,7 +35,7 @@ func setMqOptions(user, password string, opts *mqtt.ClientOptions) {
 	opts.SetConnectRetry(true)
 	opts.SetCleanSession(true)
 	opts.SetConnectRetryInterval(time.Second * 1)
-	opts.SetKeepAlive(time.Second * 10)
+	opts.SetKeepAlive(time.Second * 15)
 	opts.SetOrderMatters(false)
 	opts.SetWriteTimeout(time.Minute)
 }

+ 13 - 29
mq/publishers.go

@@ -7,6 +7,7 @@ import (
 	"sync"
 	"time"
 
+	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
@@ -14,11 +15,9 @@ import (
 	"golang.org/x/exp/slog"
 )
 
-var batchSize = servercfg.GetPeerUpdateBatchSize()
-var batchUpdate = servercfg.GetBatchPeerUpdate()
-
 // PublishPeerUpdate --- determines and publishes a peer update to all the hosts
 func PublishPeerUpdate(replacePeers bool) error {
+
 	if !servercfg.IsMessageQueueBackend() {
 		return nil
 	}
@@ -37,35 +36,20 @@ func PublishPeerUpdate(replacePeers bool) error {
 		return err
 	}
 
-	//if batch peer update disabled
-	if !batchUpdate {
-		for _, host := range hosts {
-			host := host
-			go func(host models.Host) {
-				if err = PublishSingleHostPeerUpdate(&host, allNodes, nil, nil, replacePeers, nil); err != nil {
-					logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
+	for _, host := range hosts {
+		host := host
+		time.Sleep(5 * time.Millisecond)
+		go func(host models.Host) {
+			if err = PublishSingleHostPeerUpdate(&host, allNodes, nil, nil, replacePeers, nil); err != nil {
+				id := host.Name
+				if host.ID != uuid.Nil {
+					id = host.ID.String()
 				}
-			}(host)
-		}
-		return nil
+				slog.Error("failed to publish peer update to host", id, ": ", err)
+			}
+		}(host)
 	}
 
-	//if batch peer update enabled
-	batchHost := BatchItems(hosts, batchSize)
-	var wg sync.WaitGroup
-	for _, v := range batchHost {
-		hostLen := len(v)
-		wg.Add(hostLen)
-		for i := 0; i < hostLen; i++ {
-			host := hosts[i]
-			go func(host models.Host) {
-				if err = PublishSingleHostPeerUpdate(&host, allNodes, nil, nil, replacePeers, &wg); err != nil {
-					logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
-				}
-			}(host)
-		}
-		wg.Wait()
-	}
 	return nil
 }
 

+ 61 - 3
mq/util.go

@@ -1,8 +1,14 @@
 package mq
 
 import (
+	"bytes"
+	"compress/gzip"
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/rand"
 	"errors"
 	"fmt"
+	"io"
 	"math"
 	"strings"
 	"time"
@@ -66,6 +72,39 @@ func BatchItems[T any](items []T, batchSize int) [][]T {
 	return batches
 }
 
+func compressPayload(data []byte) ([]byte, error) {
+	var buf bytes.Buffer
+	zw := gzip.NewWriter(&buf)
+	if _, err := zw.Write(data); err != nil {
+		return nil, err
+	}
+	zw.Close()
+	return buf.Bytes(), nil
+}
+func encryptAESGCM(key, plaintext []byte) ([]byte, error) {
+	// Create AES block cipher
+	block, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+
+	// Create GCM (Galois/Counter Mode) cipher
+	aesGCM, err := cipher.NewGCM(block)
+	if err != nil {
+		return nil, err
+	}
+
+	// Create a random nonce
+	nonce := make([]byte, aesGCM.NonceSize())
+	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
+		return nil, err
+	}
+
+	// Encrypt the data
+	ciphertext := aesGCM.Seal(nonce, nonce, plaintext, nil)
+	return ciphertext, nil
+}
+
 func encryptMsg(host *models.Host, msg []byte) ([]byte, error) {
 	if host.OS == models.OS_Types.IoT {
 		return msg, nil
@@ -96,10 +135,29 @@ func encryptMsg(host *models.Host, msg []byte) ([]byte, error) {
 
 func publish(host *models.Host, dest string, msg []byte) error {
 
-	encrypted, encryptErr := encryptMsg(host, msg)
-	if encryptErr != nil {
-		return encryptErr
+	var encrypted []byte
+	var encryptErr error
+	vlt, err := logic.VersionLessThan(host.Version, "v0.30.0")
+	if err != nil {
+		slog.Warn("error checking version less than", "error", err)
+		return err
 	}
+	if vlt {
+		encrypted, encryptErr = encryptMsg(host, msg)
+		if encryptErr != nil {
+			return encryptErr
+		}
+	} else {
+		zipped, err := compressPayload(msg)
+		if err != nil {
+			return err
+		}
+		encrypted, encryptErr = encryptAESGCM(host.TrafficKeyPublic[0:32], zipped)
+		if encryptErr != nil {
+			return encryptErr
+		}
+	}
+
 	if mqclient == nil || !mqclient.IsConnectionOpen() {
 		return errors.New("cannot publish ... mqclient not connected")
 	}

+ 7 - 7
pro/controllers/users.go

@@ -486,7 +486,7 @@ func updateUserGroup(w http.ResponseWriter, r *http.Request) {
 // @Summary     Delete user group.
 // @Router      /api/v1/user/group [delete]
 // @Tags        Users
-// @Param       group_id param string true "group id required to delete the role"
+// @Param       group_id query string true "group id required to delete the role"
 // @Success     200 {string} string
 // @Failure     500 {object} models.ErrorResponse
 func deleteUserGroup(w http.ResponseWriter, r *http.Request) {
@@ -517,7 +517,7 @@ func deleteUserGroup(w http.ResponseWriter, r *http.Request) {
 // @Summary     lists all user roles.
 // @Router      /api/v1/user/roles [get]
 // @Tags        Users
-// @Param       role_id param string true "roleid required to get the role details"
+// @Param       role_id query string true "roleid required to get the role details"
 // @Success     200 {object}  []models.UserRolePermissionTemplate
 // @Failure     500 {object} models.ErrorResponse
 func ListRoles(w http.ResponseWriter, r *http.Request) {
@@ -543,7 +543,7 @@ func ListRoles(w http.ResponseWriter, r *http.Request) {
 // @Summary     Get user role permission template.
 // @Router      /api/v1/user/role [get]
 // @Tags        Users
-// @Param       role_id param string true "roleid required to get the role details"
+// @Param       role_id query string true "roleid required to get the role details"
 // @Success     200 {object} models.UserRolePermissionTemplate
 // @Failure     500 {object} models.ErrorResponse
 func getRole(w http.ResponseWriter, r *http.Request) {
@@ -566,7 +566,7 @@ func getRole(w http.ResponseWriter, r *http.Request) {
 // @Summary     Create user role permission template.
 // @Router      /api/v1/user/role [post]
 // @Tags        Users
-// @Param       body models.UserRolePermissionTemplate true "user role template"
+// @Param       body body models.UserRolePermissionTemplate true "user role template"
 // @Success     200 {object}  models.UserRolePermissionTemplate
 // @Failure     500 {object} models.ErrorResponse
 func createRole(w http.ResponseWriter, r *http.Request) {
@@ -596,8 +596,8 @@ func createRole(w http.ResponseWriter, r *http.Request) {
 // @Summary     Update user role permission template.
 // @Router      /api/v1/user/role [put]
 // @Tags        Users
-// @Param       body models.UserRolePermissionTemplate true "user role template"
-// @Success     200 {object} userBodyResponse
+// @Param       body body models.UserRolePermissionTemplate true "user role template"
+// @Success     200 {object} models.UserRolePermissionTemplate
 // @Failure     500 {object} models.ErrorResponse
 func updateRole(w http.ResponseWriter, r *http.Request) {
 	var userRole models.UserRolePermissionTemplate
@@ -632,7 +632,7 @@ func updateRole(w http.ResponseWriter, r *http.Request) {
 // @Summary     Delete user role permission template.
 // @Router      /api/v1/user/role [delete]
 // @Tags        Users
-// @Param       role_id param string true "roleid required to delete the role"
+// @Param       role_id query string true "roleid required to delete the role"
 // @Success     200 {string} string
 // @Failure     500 {object} models.ErrorResponse
 func deleteRole(w http.ResponseWriter, r *http.Request) {

+ 1 - 0
pro/initialize.go

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

+ 73 - 46
pro/license.go

@@ -9,6 +9,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"github.com/gravitl/netmaker/utils"
 	"io"
 	"net/http"
 	"time"
@@ -205,55 +206,81 @@ func validateLicenseKey(encryptedData []byte, publicKey *[32]byte) ([]byte, bool
 		return nil, false, err
 	}
 
-	req, err := http.NewRequest(
-		http.MethodPost,
-		proLogic.GetAccountsHost()+"/api/v1/license/validate",
-		bytes.NewReader(requestBody),
-	)
-	if err != nil {
-		return nil, false, err
-	}
-	req.Header.Add("Content-Type", "application/json")
-	req.Header.Add("Accept", "application/json")
-	client := &http.Client{}
-	validateResponse, err := client.Do(req)
-	if err != nil { // check cache
-		slog.Warn("proceeding with cached response, Netmaker API may be down")
-		cachedResp, err := getCachedResponse()
-		return cachedResp, false, err
-	}
-	defer validateResponse.Body.Close()
-	code := validateResponse.StatusCode
-
-	// if we received a 200, cache the response locally
-	if code == http.StatusOK {
-		body, err := io.ReadAll(validateResponse.Body)
-		if err != nil {
-			slog.Warn("failed to parse response", "error", err)
-			return nil, false, err
-		}
-		if err := cacheResponse(body); err != nil {
-			slog.Warn("failed to cache response", "error", err)
-		}
-		return body, false, nil
+	var validateResponse *http.Response
+	var validationResponse []byte
+	var timedOut bool
+
+	validationRetries := utils.RetryStrategy{
+		WaitTime:         time.Second * 5,
+		WaitTimeIncrease: time.Second * 2,
+		MaxTries:         15,
+		Wait: func(duration time.Duration) {
+			time.Sleep(duration)
+		},
+		Try: func() error {
+			req, err := http.NewRequest(
+				http.MethodPost,
+				proLogic.GetAccountsHost()+"/api/v1/license/validate",
+				bytes.NewReader(requestBody),
+			)
+			if err != nil {
+				return err
+			}
+			req.Header.Add("Content-Type", "application/json")
+			req.Header.Add("Accept", "application/json")
+			client := &http.Client{}
+
+			validateResponse, err = client.Do(req)
+			if err != nil {
+				slog.Warn(fmt.Sprintf("error while validating license key: %v", err))
+				return err
+			}
+
+			if validateResponse.StatusCode == http.StatusServiceUnavailable ||
+				validateResponse.StatusCode == http.StatusGatewayTimeout ||
+				validateResponse.StatusCode == http.StatusBadGateway {
+				timedOut = true
+				return errors.New("failed to reach netmaker api")
+			}
+
+			return nil
+		},
+		OnMaxTries: func() {
+			slog.Warn("proceeding with cached response, Netmaker API may be down")
+			validationResponse, err = getCachedResponse()
+			timedOut = false
+		},
+		OnSuccess: func() {
+			defer validateResponse.Body.Close()
+
+			// if we received a 200, cache the response locally
+			if validateResponse.StatusCode == http.StatusOK {
+				validationResponse, err = io.ReadAll(validateResponse.Body)
+				if err != nil {
+					slog.Warn("failed to parse response", "error", err)
+					validationResponse = nil
+					timedOut = false
+					return
+				}
+
+				if err := cacheResponse(validationResponse); err != nil {
+					slog.Warn("failed to cache response", "error", err)
+				}
+			} else {
+				// at this point the backend returned some undesired state
+
+				// inform failure via logs
+				body, _ := io.ReadAll(validateResponse.Body)
+				err = fmt.Errorf("could not validate license with validation backend (status={%d}, body={%s})",
+					validateResponse.StatusCode, string(body))
+				slog.Warn(err.Error())
+			}
+		},
 	}
 
-	// at this point the backend returned some undesired state
-
-	// inform failure via logs
-	body, _ := io.ReadAll(validateResponse.Body)
-	err = fmt.Errorf("could not validate license with validation backend (status={%d}, body={%s})",
-		validateResponse.StatusCode, string(body))
-	slog.Warn(err.Error())
-
-	// try to use cache if we had a temporary error
-	if code == http.StatusServiceUnavailable || code == http.StatusGatewayTimeout || code == http.StatusBadGateway {
-		slog.Warn("Netmaker API may be down, will retry later...", "code", code)
-		return nil, true, nil
-	}
+	validationRetries.DoStrategy()
 
-	// at this point the error is irreversible, return it
-	return nil, false, err
+	return validationResponse, timedOut, err
 }
 
 func cacheResponse(response []byte) error {

+ 199 - 0
pro/logic/status.go

@@ -0,0 +1,199 @@
+package logic
+
+import (
+	"time"
+
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/models"
+)
+
+func getNodeStatusOld(node *models.Node) {
+	// On CE check only last check-in time
+	if node.IsStatic {
+		if !node.StaticNode.Enabled {
+			node.Status = models.OfflineSt
+			return
+		}
+		node.Status = models.OnlineSt
+		return
+	}
+	if time.Since(node.LastCheckIn) > time.Minute*10 {
+		node.Status = models.OfflineSt
+		return
+	}
+	node.Status = models.OnlineSt
+}
+
+func GetNodeStatus(node *models.Node, defaultEnabledPolicy bool) {
+
+	if time.Since(node.LastCheckIn) > models.LastCheckInThreshold {
+		node.Status = models.OfflineSt
+		return
+	}
+	if node.IsStatic {
+		if !node.StaticNode.Enabled {
+			node.Status = models.OfflineSt
+			return
+		}
+		// check extclient connection from metrics
+		ingressMetrics, err := GetMetrics(node.StaticNode.IngressGatewayID)
+		if err != nil || ingressMetrics == nil || ingressMetrics.Connectivity == nil {
+			node.Status = models.UnKnown
+			return
+		}
+		if metric, ok := ingressMetrics.Connectivity[node.StaticNode.ClientID]; ok {
+			if metric.Connected {
+				node.Status = models.OnlineSt
+				return
+			} else {
+				node.Status = models.OfflineSt
+				return
+			}
+		}
+		node.Status = models.UnKnown
+		return
+	}
+	host, err := logic.GetHost(node.HostID.String())
+	if err != nil {
+		node.Status = models.UnKnown
+		return
+	}
+	vlt, err := logic.VersionLessThan(host.Version, "v0.30.0")
+	if err != nil {
+		node.Status = models.UnKnown
+		return
+	}
+	if vlt {
+		getNodeStatusOld(node)
+		return
+	}
+	metrics, err := logic.GetMetrics(node.ID.String())
+	if err != nil {
+		return
+	}
+	if metrics == nil || metrics.Connectivity == nil {
+		if time.Since(node.LastCheckIn) < models.LastCheckInThreshold {
+			node.Status = models.OnlineSt
+			return
+		}
+	}
+	// if node.IsFailOver {
+	// 	if time.Since(node.LastCheckIn) < models.LastCheckInThreshold {
+	// 		node.Status = models.OnlineSt
+	// 		return
+	// 	}
+	// }
+	// If all Peers are able to reach me and and the peer is not able to reached by any peer then return online
+	/* 1. FailOver Exists
+		a. check connectivity to failover Node - if no connection return warning
+		b. if getting failedover and still no connection to any of the peers - then show error
+		c. if getting failedOver and has connections to some peers - show warning
+	2. FailOver Doesn't Exist
+		a. check connectivity to pu
+
+	*/
+
+	// failoverNode, exists := FailOverExists(node.Network)
+	// if exists && failoverNode.FailedOverBy != uuid.Nil {
+	// 	// check connectivity to failover Node
+	// 	if metric, ok := metrics.Connectivity[failoverNode.ID.String()]; ok {
+	// 		if time.Since(failoverNode.LastCheckIn) < models.LastCheckInThreshold {
+	// 			if metric.Connected {
+	// 				node.Status = models.OnlineSt
+	// 				return
+	// 			} else {
+	// 				checkPeerConnectivity(node, metrics)
+	// 				return
+	// 			}
+	// 		}
+	// 	} else {
+	// 		node.Status = models.OnlineSt
+	// 		return
+	// 	}
+
+	// }
+	checkPeerConnectivity(node, metrics, defaultEnabledPolicy)
+
+}
+
+func checkPeerStatus(node *models.Node, defaultAclPolicy bool) {
+	peerNotConnectedCnt := 0
+	metrics, err := logic.GetMetrics(node.ID.String())
+	if err != nil {
+		return
+	}
+	if metrics == nil || metrics.Connectivity == nil {
+		if time.Since(node.LastCheckIn) < models.LastCheckInThreshold {
+			node.Status = models.OnlineSt
+			return
+		}
+	}
+	for peerID, metric := range metrics.Connectivity {
+		peer, err := logic.GetNodeByID(peerID)
+		if err != nil {
+			continue
+		}
+		allowed, _ := logic.IsNodeAllowedToCommunicate(*node, peer, false)
+		if !defaultAclPolicy && !allowed {
+			continue
+		}
+
+		if time.Since(peer.LastCheckIn) > models.LastCheckInThreshold {
+			continue
+		}
+		if metric.Connected {
+			continue
+		}
+		if peer.Status == models.ErrorSt {
+			continue
+		}
+		peerNotConnectedCnt++
+
+	}
+	if peerNotConnectedCnt == 0 {
+		node.Status = models.OnlineSt
+		return
+	}
+	if peerNotConnectedCnt == len(metrics.Connectivity) {
+		node.Status = models.ErrorSt
+		return
+	}
+	node.Status = models.WarningSt
+}
+
+func checkPeerConnectivity(node *models.Node, metrics *models.Metrics, defaultAclPolicy bool) {
+	peerNotConnectedCnt := 0
+	for peerID, metric := range metrics.Connectivity {
+		peer, err := logic.GetNodeByID(peerID)
+		if err != nil {
+			continue
+		}
+		allowed, _ := logic.IsNodeAllowedToCommunicate(*node, peer, false)
+		if !defaultAclPolicy && !allowed {
+			continue
+		}
+
+		if time.Since(peer.LastCheckIn) > models.LastCheckInThreshold {
+			continue
+		}
+		if metric.Connected {
+			continue
+		}
+		// check if peer is in error state
+		checkPeerStatus(&peer, defaultAclPolicy)
+		if peer.Status == models.ErrorSt {
+			continue
+		}
+		peerNotConnectedCnt++
+
+	}
+	if peerNotConnectedCnt == 0 {
+		node.Status = models.OnlineSt
+		return
+	}
+	if peerNotConnectedCnt == len(metrics.Connectivity) {
+		node.Status = models.ErrorSt
+		return
+	}
+	node.Status = models.WarningSt
+}

+ 4 - 4
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:   "cannot access the admin console, but can connect to nodes in your networks via Remote Access Client.",
+	MetaData:   "Can connect to nodes in your networks via Remote Access Client.",
 	Default:    true,
 	FullAccess: false,
 	NetworkID:  models.AllNetworks,
@@ -131,7 +131,7 @@ func UserGroupsInit() {
 				models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkUser)): {},
 			},
 		},
-		MetaData: "cannot access the admin console, but can connect to nodes in your networks via Remote Access Client.",
+		MetaData: "Provides read-only dashboard access to platform users and allows connection to network nodes via the Remote Access Client.",
 	}
 	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("cannot access the admin console, but 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 Remote Access Client.", netID),
 		Default:             true,
 		FullAccess:          false,
 		NetworkID:           netID,
@@ -233,7 +233,7 @@ func CreateDefaultNetworkRolesAndGroups(netID models.NetworkID) {
 				models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkUser)): {},
 			},
 		},
-		MetaData: fmt.Sprintf("cannot access the admin console, but 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 Remote Access Client. 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)

+ 1 - 1
pro/types.go

@@ -18,7 +18,7 @@ var errValidation = errors.New(license_validation_err_msg)
 // LicenseKey - the license key struct representation with associated data
 type LicenseKey struct {
 	LicenseValue   string `json:"license_value"` // actual (public) key and the unique value for the key
-	Expiration     int64  `json:"expiration"`
+	Expiration     int64  `json:"expiration" swaggertype:"primitive,integer" format:"int64"`
 	UsageServers   int    `json:"limit_servers"`
 	UsageUsers     int    `json:"limit_users"`
 	UsageClients   int    `json:"limit_clients"`

+ 7 - 7
release.md

@@ -1,21 +1,21 @@
-# Netmaker v0.26.0
+# Netmaker v0.30.0
 
 ## Whats New ✨
-- New ACLs and Tag Management System
-- Managed DNS system (Linux)
-- Simplified User Mgmt With Default Roles and Groups (Hidden away network roles)
-- New Add a Node Flow for netclient and static wireguard files
+- 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.
 
 ## What's Fixed/Improved 🛠
 - Metrics Data
+- Optimised MQ message size
 - FailOver Stability Fixes
 - Scalability Fixes
+- Duplicate Node IP check on update
 
 ## Known Issues 🐞
 
-- Adding Custom Private/Public Key For Remote Access Gw Clients Doesn't Get Propagated To Other Peers.
 - IPv6 DNS Entries Are Not Working.
 - Stale Peer On The Interface, When Forced Removed From Multiple Networks At Once.
-- Can Still Ping The Domain Name Even When The DNS Toggle Is Switched Off.
 - 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.
 

+ 4 - 4
scripts/netmaker.default.env

@@ -86,11 +86,11 @@ EMAIL_SENDER_ADDR=
 EMAIL_SENDER_USER=
 # sender smtp password
 EMAIL_SENDER_PASSWORD=
-# if batch peer update enable or not
-PEER_UPDATE_BATCH=true
-# batch peer update size when PEER_UPDATE_BATCH is enabled
-PEER_UPDATE_BATCH_SIZE=50
 # default domain for internal DNS lookup
 DEFAULT_DOMAIN=netmaker.hosted
 # managed dns setting, set to true to resolve dns entries on netmaker network
 MANAGE_DNS=false
+# 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
+STUN=true

+ 6 - 9
scripts/nm-quick.sh

@@ -127,7 +127,7 @@ setup_netclient() {
 	./netclient install
 	echo "Register token: $TOKEN"
 	sleep 2
-	netclient join -t $TOKEN
+	netclient join -t $TOKEN --static-port -p 443
 
 	echo "waiting for netclient to become available"
 	local found=false
@@ -251,10 +251,8 @@ save_config() { (
 	if [ "$INSTALL_TYPE" = "pro" ]; then
 		save_config_item NETMAKER_TENANT_ID "$NETMAKER_TENANT_ID"
 		save_config_item LICENSE_KEY "$LICENSE_KEY"
-		if [ "$UPGRADE_FLAG" = "yes" ];then
-			save_config_item METRICS_EXPORTER "on"
-			save_config_item PROMETHEUS "on"
-		fi
+		save_config_item METRICS_EXPORTER "on"
+		save_config_item PROMETHEUS "on"
 		save_config_item SERVER_IMAGE_TAG "$IMAGE_TAG-ee"
 	else
 		save_config_item METRICS_EXPORTER "off"
@@ -559,7 +557,7 @@ set_install_vars() {
 	echo "                api.$NETMAKER_BASE_DOMAIN"
 	echo "             broker.$NETMAKER_BASE_DOMAIN"
 
-	if [ "$UPGRADE_FLAG" = "yes" ]; then
+	if [ "$INSTALL_TYPE" = "pro" ]; then
 		echo "         prometheus.$NETMAKER_BASE_DOMAIN"
 		echo "  netmaker-exporter.$NETMAKER_BASE_DOMAIN"
 		echo "            grafana.$NETMAKER_BASE_DOMAIN"
@@ -632,13 +630,12 @@ install_netmaker() {
 	if [ "$INSTALL_TYPE" = "pro" ]; then
 		local COMPOSE_OVERRIDE_URL="$BASE_URL/compose/docker-compose.pro.yml"
 		local CADDY_URL="$BASE_URL/docker/Caddyfile-pro"
-	fi
-	wget -qO "$SCRIPT_DIR"/docker-compose.yml $COMPOSE_URL
-	if [ "$UPGRADE_FLAG" = "yes" ]; then
 		wget -qO "$SCRIPT_DIR"/docker-compose.override.yml $COMPOSE_OVERRIDE_URL
 	elif [ -a "$SCRIPT_DIR"/docker-compose.override.yml ]; then
 		rm -f "$SCRIPT_DIR"/docker-compose.override.yml
 	fi
+	wget -qO "$SCRIPT_DIR"/docker-compose.yml $COMPOSE_URL
+
 	wget -qO "$SCRIPT_DIR"/Caddyfile "$CADDY_URL"
 	wget -qO "$SCRIPT_DIR"/netmaker.default.env "$BASE_URL/scripts/netmaker.default.env"
 	wget -qO "$SCRIPT_DIR"/mosquitto.conf "$BASE_URL/docker/mosquitto.conf"

+ 31 - 22
servercfg/serverconf.go

@@ -76,6 +76,7 @@ func GetServerConfig() config.ServerConfig {
 	cfg.Database = GetDB()
 	cfg.Platform = GetPlatform()
 	cfg.Version = GetVersion()
+	cfg.PublicIp = GetServerHostIP()
 
 	// == auth config ==
 	var authInfo = GetAuthProviderInfo()
@@ -94,6 +95,8 @@ func GetServerConfig() config.ServerConfig {
 	cfg.RacAutoDisable = GetRacAutoDisable()
 	cfg.MetricInterval = GetMetricInterval()
 	cfg.ManageDNS = GetManageDNS()
+	cfg.Stun = IsStunEnabled()
+	cfg.StunServers = GetStunServers()
 	cfg.DefaultDomain = GetDefaultDomain()
 	return cfg
 }
@@ -140,6 +143,8 @@ func GetServerInfo() models.ServerConfig {
 	cfg.IsPro = IsPro
 	cfg.MetricInterval = GetMetricInterval()
 	cfg.ManageDNS = GetManageDNS()
+	cfg.Stun = IsStunEnabled()
+	cfg.StunServers = GetStunServers()
 	cfg.DefaultDomain = GetDefaultDomain()
 	return cfg
 }
@@ -176,6 +181,11 @@ func GetVersion() string {
 	return Version
 }
 
+// GetServerHostIP - fetches server IP
+func GetServerHostIP() string {
+	return os.Getenv("SERVER_HOST")
+}
+
 // GetDB - gets the database type
 func GetDB() string {
 	database := "sqlite"
@@ -664,6 +674,14 @@ func GetManageDNS() bool {
 	return enabled
 }
 
+func IsOldAclEnabled() bool {
+	enabled := true
+	if os.Getenv("OLD_ACL_SUPPORT") != "" {
+		enabled = os.Getenv("OLD_ACL_SUPPORT") == "true"
+	}
+	return enabled
+}
+
 // GetDefaultDomain - get the default domain
 func GetDefaultDomain() string {
 	//default netmaker.hosted
@@ -686,28 +704,6 @@ func validateDomain(domain string) bool {
 	return exp.MatchString(domain)
 }
 
-// GetBatchPeerUpdate - if batch peer update
-func GetBatchPeerUpdate() bool {
-	enabled := true
-	if os.Getenv("PEER_UPDATE_BATCH") != "" {
-		enabled = os.Getenv("PEER_UPDATE_BATCH") == "true"
-	}
-	return enabled
-}
-
-// GetPeerUpdateBatchSize - get the batch size for peer update
-func GetPeerUpdateBatchSize() int {
-	//default 50
-	batchSize := 50
-	if os.Getenv("PEER_UPDATE_BATCH_SIZE") != "" {
-		b, e := strconv.Atoi(os.Getenv("PEER_UPDATE_BATCH_SIZE"))
-		if e == nil && b > 0 && b < 1000 {
-			batchSize = b
-		}
-	}
-	return batchSize
-}
-
 // GetEmqxRestEndpoint - returns the REST API Endpoint of EMQX
 func GetEmqxRestEndpoint() string {
 	return os.Getenv("EMQX_REST_ENDPOINT")
@@ -805,6 +801,19 @@ func IsEndpointDetectionEnabled() bool {
 	return enabled
 }
 
+// IsStunEnabled - returns true if STUN set to on
+func IsStunEnabled() bool {
+	var enabled = true
+	if os.Getenv("STUN") != "" {
+		enabled = os.Getenv("STUN") == "true"
+	}
+	return enabled
+}
+
+func GetStunServers() string {
+	return os.Getenv("STUN_SERVERS")
+}
+
 // GetEnvironment returns the environment the server is running in (e.g. dev, staging, prod...)
 func GetEnvironment() string {
 	if env := os.Getenv("ENVIRONMENT"); env != "" {

+ 659 - 81
swagger.yaml

@@ -1,6 +1,7 @@
 definitions:
   acls.ACL:
     additionalProperties:
+      format: int32
       type: integer
     type: object
   acls.ACLContainer:
@@ -41,6 +42,8 @@ definitions:
         type: string
       database:
         type: string
+      defaultDomain:
+        type: string
       deployedByOperator:
         type: boolean
       disableRemoteIPCheck:
@@ -53,6 +56,12 @@ definitions:
         type: string
       egressesLimit:
         type: integer
+      email_sender_addr:
+        type: string
+      email_sender_password:
+        type: string
+      email_sender_user:
+        type: string
       emqxRestEndpoint:
         type: string
       endpoint_detection:
@@ -66,11 +75,14 @@ definitions:
       ingressesLimit:
         type: integer
       jwtValidityDuration:
-        $ref: '#/definitions/time.Duration'
+        format: int64
+        type: integer
       licenseValue:
         type: string
       machinesLimit:
         type: integer
+      manageDNS:
+        type: boolean
       masterKey:
         type: string
       messageQueueBackend:
@@ -99,6 +111,8 @@ definitions:
         type: string
       publicIPService:
         type: string
+      publicIp:
+        type: string
       racAutoDisable:
         type: boolean
       restBackend:
@@ -107,12 +121,20 @@ definitions:
         type: string
       serverBrokerEndpoint:
         type: string
+      smtp_host:
+        type: string
+      smtp_port:
+        type: integer
       sqlconn:
         type: string
+      stun:
+        type: boolean
       stunList:
         type: string
       stunPort:
         type: integer
+      stunServers:
+        type: string
       telemetry:
         type: string
       turnApiServer:
@@ -137,7 +159,12 @@ definitions:
   models.APIEnrollmentKey:
     properties:
       expiration:
+        format: int64
         type: integer
+      groups:
+        items:
+          type: string
+        type: array
       networks:
         items:
           type: string
@@ -157,6 +184,41 @@ definitions:
     required:
     - tags
     type: object
+  models.AclRule:
+    properties:
+      allowed:
+        type: boolean
+      allowed_ports:
+        items:
+          type: string
+        type: array
+      allowed_protocols:
+        allOf:
+        - $ref: '#/definitions/models.Protocol'
+        description: tcp, udp, etc.
+      direction:
+        allOf:
+        - $ref: '#/definitions/models.AllowedTrafficDirection'
+        description: single or two-way
+      id:
+        type: string
+      ip_list:
+        items:
+          $ref: '#/definitions/net.IPNet'
+        type: array
+      ip6_list:
+        items:
+          $ref: '#/definitions/net.IPNet'
+        type: array
+    type: object
+  models.AllowedTrafficDirection:
+    enum:
+    - 0
+    - 1
+    type: integer
+    x-enum-varnames:
+    - TrafficDirectionUni
+    - TrafficDirectionBi
   models.ApiHost:
     properties:
       autoupdate:
@@ -245,6 +307,7 @@ definitions:
           type: string
         type: array
       expdatetime:
+        format: int64
         type: integer
       fail_over_peers:
         additionalProperties:
@@ -262,10 +325,18 @@ definitions:
         $ref: '#/definitions/models.InetNodeReq'
       ingressdns:
         type: string
+      ingressmtu:
+        type: integer
+      ingresspersistentkeepalive:
+        type: integer
       internetgw_node_id:
         type: string
       is_fail_over:
         type: boolean
+      is_static:
+        type: boolean
+      is_user_node:
+        type: boolean
       isegressgateway:
         type: boolean
       isingressgateway:
@@ -277,10 +348,13 @@ definitions:
       isrelayed:
         type: boolean
       lastcheckin:
+        format: int64
         type: integer
       lastmodified:
+        format: int64
         type: integer
       lastpeerupdate:
+        format: int64
         type: integer
       localaddress:
         type: string
@@ -302,6 +376,14 @@ definitions:
         type: array
       server:
         type: string
+      static_node:
+        $ref: '#/definitions/models.ExtClient'
+      status:
+        $ref: '#/definitions/models.NodeStatus'
+      tags:
+        additionalProperties:
+          type: object
+        type: object
     required:
     - hostid
     - id
@@ -375,8 +457,14 @@ definitions:
     type: object
   models.EnrollmentKey:
     properties:
+      default:
+        type: boolean
       expiration:
         type: string
+      groups:
+        items:
+          type: string
+        type: array
       networks:
         items:
           type: string
@@ -418,10 +506,14 @@ definitions:
         type: array
       clientid:
         type: string
+      country:
+        type: string
       deniednodeacls:
         additionalProperties:
           type: object
         type: object
+      device_name:
+        type: string
       dns:
         type: string
       enabled:
@@ -435,9 +527,12 @@ definitions:
       ingressgatewayid:
         type: string
       lastmodified:
+        format: int64
         type: integer
       network:
         type: string
+      os:
+        type: string
       ownerid:
         type: string
       postdown:
@@ -446,25 +541,64 @@ definitions:
         type: string
       privatekey:
         type: string
+      public_endpoint:
+        type: string
       publickey:
         type: string
       remote_access_client_id:
         description: unique ID (MAC address) of RAC machine
         type: string
+      tags:
+        additionalProperties:
+          type: object
+        type: object
     type: object
   models.FailOverMeReq:
     properties:
       node_id:
         type: string
     type: object
+  models.FwRule:
+    properties:
+      allow:
+        type: boolean
+      allowed_ports:
+        items:
+          type: string
+        type: array
+      allowed_protocols:
+        allOf:
+        - $ref: '#/definitions/models.Protocol'
+        description: tcp, udp, etc.
+      dst_ip:
+        $ref: '#/definitions/net.IPNet'
+      src_ip:
+        $ref: '#/definitions/net.IPNet'
+    type: object
   models.FwUpdate:
     properties:
+      acl_rules:
+        additionalProperties:
+          $ref: '#/definitions/models.AclRule'
+        type: object
+      allow_all:
+        type: boolean
       egress_info:
         additionalProperties:
           $ref: '#/definitions/models.EgressInfo'
         type: object
+      ingress_info:
+        additionalProperties:
+          $ref: '#/definitions/models.IngressInfo'
+        type: object
       is_egress_gw:
         type: boolean
+      is_ingress_gw:
+        type: boolean
+      networks:
+        items:
+          $ref: '#/definitions/net.IPNet'
+        type: array
     type: object
   models.Host:
     properties:
@@ -527,7 +661,8 @@ definitions:
       os:
         type: string
       persistentkeepalive:
-        $ref: '#/definitions/time.Duration'
+        format: int64
+        type: integer
       publickey:
         items:
           type: integer
@@ -684,6 +819,35 @@ definitions:
           $ref: '#/definitions/models.ReturnUser'
         type: array
     type: object
+  models.IngressInfo:
+    properties:
+      allow_all:
+        type: boolean
+      egress_ranges:
+        items:
+          $ref: '#/definitions/net.IPNet'
+        type: array
+      egress_ranges6:
+        items:
+          $ref: '#/definitions/net.IPNet'
+        type: array
+      ingress_id:
+        type: string
+      network:
+        $ref: '#/definitions/net.IPNet'
+      network6:
+        $ref: '#/definitions/net.IPNet'
+      rules:
+        items:
+          $ref: '#/definitions/models.FwRule'
+        type: array
+      static_node_ips:
+        items:
+          items:
+            type: integer
+          type: array
+        type: array
+    type: object
   models.KeyType:
     enum:
     - 0
@@ -699,22 +863,34 @@ definitions:
   models.Metric:
     properties:
       actualuptime:
-        $ref: '#/definitions/time.Duration'
+        format: int64
+        type: integer
       connected:
         type: boolean
+      lasttotalreceived:
+        format: int64
+        type: integer
+      lasttotalsent:
+        format: int64
+        type: integer
       latency:
+        format: int64
         type: integer
       node_name:
         type: string
       percentup:
         type: number
       totalreceived:
+        format: int64
         type: integer
       totalsent:
+        format: int64
         type: integer
       totaltime:
+        format: int64
         type: integer
       uptime:
+        format: int64
         type: integer
     type: object
   models.Metrics:
@@ -766,14 +942,22 @@ definitions:
         minLength: 1
         type: string
       networklastmodified:
+        format: int64
         type: integer
       nodelimit:
         type: integer
       nodeslastmodified:
+        format: int64
         type: integer
     required:
     - netid
     type: object
+  models.NetworkID:
+    enum:
+    - all_networks
+    type: string
+    x-enum-varnames:
+    - AllNetworks
   models.Node:
     properties:
       action:
@@ -821,10 +1005,18 @@ definitions:
         type: string
       ingressgatewayrange6:
         type: string
+      ingressmtu:
+        type: integer
+      ingresspersistentkeepalive:
+        type: integer
       internetgw_node_id:
         type: string
       is_fail_over:
         type: boolean
+      is_static:
+        type: boolean
+      is_user_node:
+        type: boolean
       isegressgateway:
         type: boolean
       isingressgateway:
@@ -851,6 +1043,8 @@ definitions:
         type: integer
       networkrange6:
         type: number
+      node_status:
+        $ref: '#/definitions/models.NodeStatus'
       ownerid:
         type: string
       pendingdelete:
@@ -863,6 +1057,12 @@ definitions:
         type: array
       server:
         type: string
+      static_node:
+        $ref: '#/definitions/models.ExtClient'
+      tags:
+        additionalProperties:
+          type: object
+        type: object
     type: object
   models.NodeGet:
     properties:
@@ -883,10 +1083,36 @@ definitions:
       serverconfig:
         $ref: '#/definitions/models.ServerConfig'
     type: object
+  models.NodeStatus:
+    enum:
+    - online
+    - offline
+    - warning
+    - error
+    - unknown
+    type: string
+    x-enum-varnames:
+    - OnlineSt
+    - OfflineSt
+    - WarningSt
+    - ErrorSt
+    - UnKnown
   models.PeerMap:
     additionalProperties:
       $ref: '#/definitions/models.IDandAddr'
     type: object
+  models.Protocol:
+    enum:
+    - all
+    - udp
+    - tcp
+    - icmp
+    type: string
+    x-enum-varnames:
+    - ALL
+    - UDP
+    - TCP
+    - ICMP
   models.RegisterResponse:
     properties:
       requested_host:
@@ -907,19 +1133,49 @@ definitions:
     type: object
   models.ReturnUser:
     properties:
+      auth_type:
+        type: string
       isadmin:
         type: boolean
       issuperadmin:
         type: boolean
       last_login_time:
         type: string
+      network_roles:
+        additionalProperties:
+          additionalProperties:
+            type: object
+          type: object
+        type: object
+      platform_role_id:
+        $ref: '#/definitions/models.UserRoleID'
       remote_gw_ids:
+        additionalProperties:
+          type: object
+        description: deprecated
+        type: object
+      user_group_ids:
         additionalProperties:
           type: object
         type: object
       username:
         type: string
     type: object
+  models.RsrcPermissionScope:
+    properties:
+      create:
+        type: boolean
+      delete:
+        type: boolean
+      read:
+        type: boolean
+      self_only:
+        type: boolean
+      update:
+        type: boolean
+      vpn_access:
+        type: boolean
+    type: object
   models.ServerConfig:
     properties:
       Is_EE:
@@ -934,8 +1190,12 @@ definitions:
         type: string
       coreDNSAddr:
         type: string
+      defaultDomain:
+        type: string
       dnsmode:
         type: string
+      manageDNS:
+        type: boolean
       metricInterval:
         type: string
       mqpassword:
@@ -946,6 +1206,10 @@ definitions:
         type: string
       server:
         type: string
+      stun:
+        type: boolean
+      stunServers:
+        type: string
       trafficKey:
         items:
           type: integer
@@ -996,16 +1260,35 @@ definitions:
     type: object
   models.User:
     properties:
+      auth_type:
+        type: string
+      external_identity_provider_id:
+        type: string
       isadmin:
+        description: deprecated
         type: boolean
       issuperadmin:
+        description: deprecated
         type: boolean
       last_login_time:
         type: string
+      network_roles:
+        additionalProperties:
+          additionalProperties:
+            type: object
+          type: object
+        type: object
       password:
         minLength: 5
         type: string
+      platform_role_id:
+        $ref: '#/definitions/models.UserRoleID'
       remote_gw_ids:
+        additionalProperties:
+          type: object
+        description: deprecated
+        type: object
+      user_group_ids:
         additionalProperties:
           type: object
         type: object
@@ -1052,6 +1335,51 @@ definitions:
       remote_access_gw_id:
         type: string
     type: object
+  models.UserRoleID:
+    enum:
+    - super-admin
+    - admin
+    - service-user
+    - platform-user
+    - network-admin
+    - network-user
+    type: string
+    x-enum-varnames:
+    - SuperAdminRole
+    - AdminRole
+    - ServiceUser
+    - PlatformUser
+    - NetworkAdmin
+    - NetworkUser
+  models.UserRolePermissionTemplate:
+    properties:
+      default:
+        type: boolean
+      deny_dashboard_access:
+        type: boolean
+      full_access:
+        type: boolean
+      global_level_access:
+        additionalProperties:
+          additionalProperties:
+            $ref: '#/definitions/models.RsrcPermissionScope'
+          type: object
+        type: object
+      id:
+        $ref: '#/definitions/models.UserRoleID'
+      meta_data:
+        type: string
+      name:
+        type: string
+      network_id:
+        $ref: '#/definitions/models.NetworkID'
+      network_level_access:
+        additionalProperties:
+          additionalProperties:
+            $ref: '#/definitions/models.RsrcPermissionScope'
+          type: object
+        type: object
+    type: object
   net.IPNet:
     properties:
       ip:
@@ -1062,6 +1390,7 @@ definitions:
       mask:
         description: network mask
         items:
+          format: int32
           type: integer
         type: array
     type: object
@@ -1079,42 +1408,6 @@ definitions:
     type: object
   netip.AddrPort:
     type: object
-  time.Duration:
-    enum:
-    - -9223372036854775808
-    - 9223372036854775807
-    - 1
-    - 1000
-    - 1000000
-    - 1000000000
-    - 60000000000
-    - 3600000000000
-    - -9223372036854775808
-    - 9223372036854775807
-    - 1
-    - 1000
-    - 1000000
-    - 1000000000
-    - 60000000000
-    - 3600000000000
-    type: integer
-    x-enum-varnames:
-    - minDuration
-    - maxDuration
-    - Nanosecond
-    - Microsecond
-    - Millisecond
-    - Second
-    - Minute
-    - Hour
-    - minDuration
-    - maxDuration
-    - Nanosecond
-    - Microsecond
-    - Millisecond
-    - Second
-    - Minute
-    - Hour
   wgtypes.PeerConfig:
     properties:
       allowedIPs:
@@ -1129,13 +1422,13 @@ definitions:
         - $ref: '#/definitions/net.UDPAddr'
         description: Endpoint specifies the endpoint of this peer entry, if not nil.
       persistentKeepaliveInterval:
-        allOf:
-        - $ref: '#/definitions/time.Duration'
         description: |-
           PersistentKeepaliveInterval specifies the persistent keepalive interval
           for this peer, if not nil.
 
           A non-nil value of 0 will clear the persistent keepalive interval.
+        format: int64
+        type: integer
       presharedKey:
         description: |-
           PresharedKey specifies a peer's preshared key configuration, if not nil.
@@ -1173,7 +1466,7 @@ info:
   contact: {}
   description: NetMaker API Docs
   title: NetMaker
-  version: 0.24.3
+  version: 0.30.0
 paths:
   /api/dns:
     get:
@@ -1325,6 +1618,26 @@ paths:
       summary: Gets custom DNS entries associated with a network
       tags:
       - DNS
+  /api/dns/adm/{network}/sync:
+    post:
+      consumes:
+      - application/json
+      responses:
+        "200":
+          description: DNS Sync completed successfully
+          schema:
+            type: string
+        "400":
+          description: Bad Request
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: Sync DNS entries for a given network
+      tags:
+      - DNS
   /api/dns/adm/pushdns:
     post:
       consumes:
@@ -2293,7 +2606,7 @@ paths:
           description: Internal Server Error
           schema:
             $ref: '#/definitions/models.ErrorResponse'
-      summary: List users attached to an ingress gateway
+      summary: List users attached to an remote access gateway
       tags:
       - PRO
   /api/nodes/adm/{network}:
@@ -2344,22 +2657,6 @@ paths:
       summary: Get the server status
       tags:
       - Server
-  /api/users:
-    get:
-      responses:
-        "200":
-          description: OK
-          schema:
-            items:
-              $ref: '#/definitions/models.User'
-            type: array
-        "500":
-          description: Internal Server Error
-          schema:
-            $ref: '#/definitions/models.ErrorResponse'
-      summary: Get all users
-      tags:
-      - Users
   /api/users/{username}:
     delete:
       parameters:
@@ -2467,42 +2764,28 @@ paths:
       - Users
   /api/users/{username}/remote_access_gw:
     get:
-      consumes:
-      - application/json
       parameters:
-      - description: Username
+      - description: Username to fetch all the gateways with access
         in: path
         name: username
         required: true
         type: string
-      - description: Remote Access Client ID
-        in: query
-        name: remote_access_clientid
-        type: string
-      - description: Request from mobile
-        in: query
-        name: from_mobile
-        type: boolean
-      produces:
-      - application/json
       responses:
         "200":
           description: OK
           schema:
-            items:
-              $ref: '#/definitions/models.UserRemoteGws'
-            type: array
-        "400":
-          description: Bad Request
-          schema:
-            $ref: '#/definitions/models.ErrorResponse'
+            additionalProperties:
+              items:
+                $ref: '#/definitions/models.UserRemoteGws'
+              type: array
+            type: object
         "500":
           description: Internal Server Error
           schema:
             $ref: '#/definitions/models.ErrorResponse'
-      summary: Get user's remote access gateways
+      summary: Get Users Remote Access Gw.
       tags:
-      - PRO
+      - Users
   /api/users/{username}/remote_access_gw/{remote_access_gateway_id}:
     delete:
       consumes:
@@ -2730,6 +3013,93 @@ paths:
       summary: Approve a pending user
       tags:
       - Users
+  /api/v1/acls:
+    delete:
+      consumes:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/models.SuccessResponse'
+            type: array
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: Delete Acl
+      tags:
+      - ACL
+    get:
+      consumes:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/models.SuccessResponse'
+            type: array
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: List Acls in a network
+      tags:
+      - ACL
+    post:
+      consumes:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/models.SuccessResponse'
+            type: array
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: Create Acl
+      tags:
+      - ACL
+    put:
+      consumes:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/models.SuccessResponse'
+            type: array
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: Update Acl
+      tags:
+      - ACL
+  /api/v1/acls/policy_types:
+    get:
+      consumes:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/models.SuccessResponse'
+            type: array
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: List Acl Policy types
+      tags:
+      - ACL
   /api/v1/enrollment-keys:
     get:
       responses:
@@ -2947,6 +3317,24 @@ paths:
       summary: Delete all legacy nodes from DB.
       tags:
       - Nodes
+  /api/v1/networks/stats:
+    get:
+      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: Lists all networks with stats
+      tags:
+      - Networks
   /api/v1/node/{network}/failover/reset:
     post:
       parameters:
@@ -3069,6 +3457,196 @@ paths:
       summary: Failover me
       tags:
       - PRO
+  /api/v1/tags:
+    delete:
+      consumes:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/models.SuccessResponse'
+            type: array
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: Delete Tag
+      tags:
+      - TAG
+    get:
+      consumes:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/models.SuccessResponse'
+            type: array
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: List Tags in a network
+      tags:
+      - TAG
+    post:
+      consumes:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/models.SuccessResponse'
+            type: array
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: Create Tag
+      tags:
+      - TAG
+    put:
+      consumes:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/models.SuccessResponse'
+            type: array
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: Update Tag
+      tags:
+      - TAG
+  /api/v1/user/group:
+    delete:
+      parameters:
+      - description: group id required to delete the role
+        in: query
+        name: group_id
+        required: true
+        type: string
+      responses:
+        "200":
+          description: OK
+          schema:
+            type: string
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: Delete user group.
+      tags:
+      - Users
+  /api/v1/user/role:
+    delete:
+      parameters:
+      - description: roleid required to delete the role
+        in: query
+        name: role_id
+        required: true
+        type: string
+      responses:
+        "200":
+          description: OK
+          schema:
+            type: string
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: Delete user role permission template.
+      tags:
+      - Users
+    get:
+      parameters:
+      - description: roleid required to get the role details
+        in: query
+        name: role_id
+        required: true
+        type: string
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/models.UserRolePermissionTemplate'
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: Get user role permission template.
+      tags:
+      - Users
+    post:
+      parameters:
+      - description: user role template
+        in: body
+        name: body
+        required: true
+        schema:
+          $ref: '#/definitions/models.UserRolePermissionTemplate'
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/models.UserRolePermissionTemplate'
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: Create user role permission template.
+      tags:
+      - Users
+    put:
+      parameters:
+      - description: user role template
+        in: body
+        name: body
+        required: true
+        schema:
+          $ref: '#/definitions/models.UserRolePermissionTemplate'
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/models.UserRolePermissionTemplate'
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: Update user role permission template.
+      tags:
+      - Users
+  /api/v1/user/roles:
+    get:
+      parameters:
+      - description: roleid required to get the role details
+        in: query
+        name: role_id
+        required: true
+        type: string
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/models.UserRolePermissionTemplate'
+            type: array
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/models.ErrorResponse'
+      summary: lists all user roles.
+      tags:
+      - Users
   /meshclient/files/{filename}:
     get:
       responses:

+ 41 - 0
utils/utils.go

@@ -0,0 +1,41 @@
+package utils
+
+import "time"
+
+// RetryStrategy specifies a strategy to retry an operation after waiting a while,
+// with hooks for successful and unsuccessful (>=max) tries.
+type RetryStrategy struct {
+	Wait             func(time.Duration)
+	WaitTime         time.Duration
+	WaitTimeIncrease time.Duration
+	MaxTries         int
+	Try              func() error
+	OnMaxTries       func()
+	OnSuccess        func()
+}
+
+// DoStrategy does the retry strategy specified in the struct, waiting before retrying an operator,
+// up to a max number of tries, and if executes a success "finalizer" operation if a retry is successful
+func (rs RetryStrategy) DoStrategy() {
+	err := rs.Try()
+	if err == nil {
+		rs.OnSuccess()
+		return
+	}
+
+	tries := 1
+	for {
+		if tries >= rs.MaxTries {
+			rs.OnMaxTries()
+			return
+		}
+		rs.Wait(rs.WaitTime)
+		if err := rs.Try(); err != nil {
+			tries++                            // we tried, increase count
+			rs.WaitTime += rs.WaitTimeIncrease // for the next time, sleep more
+			continue                           // retry
+		}
+		rs.OnSuccess()
+		return
+	}
+}