Browse Source

Merge pull request #2834 from gravitl/release-v0.23.0

v0.23.0
Abhishek K 1 year ago
parent
commit
089df3d4cf
54 changed files with 1873 additions and 679 deletions
  1. 1 0
      .github/ISSUE_TEMPLATE/bug-report.yml
  2. 1 1
      Dockerfile
  3. 1 1
      Dockerfile-quick
  4. 1 1
      README.md
  5. 35 13
      auth/host_session.go
  6. 1 1
      compose/docker-compose.netclient.yml
  7. 1 1
      controllers/config/dnsconfig/Corefile
  8. 1 1
      controllers/docs.go
  9. 2 8
      controllers/enrollmentkeys.go
  10. 54 9
      controllers/ext_client.go
  11. 14 8
      controllers/hosts.go
  12. 0 3
      controllers/migrate.go
  13. 217 19
      controllers/network.go
  14. 25 30
      controllers/node.go
  15. 1 5
      controllers/node_test.go
  16. 2 2
      database/rqlite.go
  17. 11 11
      go.mod
  18. 22 27
      go.sum
  19. 1 1
      k8s/client/netclient-daemonset.yaml
  20. 1 1
      k8s/client/netclient.yaml
  21. 1 1
      k8s/server/netmaker-ui.yaml
  22. 17 17
      logic/acls/common.go
  23. 7 1
      logic/acls/nodeacls/modify.go
  24. 13 2
      logic/acls/nodeacls/retrieve.go
  25. 19 0
      logic/clients.go
  26. 4 0
      logic/extpeers.go
  27. 21 25
      logic/gateway.go
  28. 30 0
      logic/nodes.go
  29. 48 19
      logic/peers.go
  30. 2 1
      logic/relay.go
  31. 1 1
      main.go
  32. 125 0
      migrate/migrate.go
  33. 17 12
      models/api_node.go
  34. 4 0
      models/extclient.go
  35. 3 0
      models/mqtt.go
  36. 9 6
      models/node.go
  37. 13 0
      models/structs.go
  38. 29 373
      mq/emqx.go
  39. 264 0
      mq/emqx_cloud.go
  40. 428 0
      mq/emqx_on_prem.go
  41. 2 2
      mq/handlers.go
  42. 23 16
      mq/mq.go
  43. 1 20
      pro/controllers/failover.go
  44. 164 0
      pro/controllers/inet_gws.go
  45. 4 0
      pro/controllers/users.go
  46. 7 2
      pro/initialize.go
  47. 27 0
      pro/logic/ext_acls.go
  48. 24 0
      pro/logic/failover.go
  49. 121 7
      pro/logic/nodes.go
  50. 5 6
      pro/logic/relays.go
  51. 15 15
      release.md
  52. 1 7
      scripts/nm-upgrade.sh
  53. 31 2
      servercfg/serverconf.go
  54. 1 1
      swagger.yml

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

@@ -31,6 +31,7 @@ body:
       label: Version
       label: Version
       description: What version are you running?
       description: What version are you running?
       options:
       options:
+        - v0.23.0
         - v0.22.0
         - v0.22.0
         - v0.21.2
         - v0.21.2
         - v0.21.1
         - v0.21.1

+ 1 - 1
Dockerfile

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

+ 1 - 1
Dockerfile-quick

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

+ 1 - 1
README.md

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

+ 35 - 13
auth/host_session.go

@@ -86,6 +86,24 @@ func SessionHandler(conn *websocket.Conn) {
 			return
 			return
 		}
 		}
 		req.Pass = req.Host.ID.String()
 		req.Pass = req.Host.ID.String()
+		user, err := logic.GetUser(req.User)
+		if err != nil {
+			logger.Log(0, "failed to get user", req.User, "from database")
+			err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+			if err != nil {
+				logger.Log(0, "error during message writing:", err.Error())
+			}
+			return
+		}
+		if !user.IsAdmin && !user.IsSuperAdmin {
+			logger.Log(0, "user", req.User, "is neither an admin or superadmin. denying registeration")
+			conn.WriteMessage(messageType, []byte("cannot register with a non-admin or non-superadmin"))
+			err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+			if err != nil {
+				logger.Log(0, "error during message writing:", err.Error())
+			}
+			return
+		}
 
 
 		if err = netcache.Set(stateStr, req); err != nil { // give the user's host access in the DB
 		if err = netcache.Set(stateStr, req); err != nil { // give the user's host access in the DB
 			logger.Log(0, "machine failed to complete join on network,", registerMessage.Network, "-", err.Error())
 			logger.Log(0, "machine failed to complete join on network,", registerMessage.Network, "-", err.Error())
@@ -129,14 +147,13 @@ func SessionHandler(conn *websocket.Conn) {
 	select {
 	select {
 	case result := <-answer: // a read from req.answerCh has occurred
 	case result := <-answer: // a read from req.answerCh has occurred
 		// add the host, if not exists, handle like enrollment registration
 		// add the host, if not exists, handle like enrollment registration
-		hostPass := result.Host.HostPass
 		if !logic.HostExists(&result.Host) { // check if host already exists, add if not
 		if !logic.HostExists(&result.Host) { // check if host already exists, add if not
 			if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 			if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
-				if err := mq.CreateEmqxUser(result.Host.ID.String(), result.Host.HostPass, false); err != nil {
+				if err := mq.GetEmqxHandler().CreateEmqxUser(result.Host.ID.String(), result.Host.HostPass); err != nil {
 					logger.Log(0, "failed to create host credentials for EMQX: ", err.Error())
 					logger.Log(0, "failed to create host credentials for EMQX: ", err.Error())
 					return
 					return
 				}
 				}
-				if err := mq.CreateHostACL(result.Host.ID.String(), servercfg.GetServerInfo().Server); err != nil {
+				if err := mq.GetEmqxHandler().CreateHostACL(result.Host.ID.String(), servercfg.GetServerInfo().Server); err != nil {
 					logger.Log(0, "failed to add host ACL rules to EMQX: ", err.Error())
 					logger.Log(0, "failed to add host ACL rules to EMQX: ", err.Error())
 					return
 					return
 				}
 				}
@@ -185,11 +202,6 @@ func SessionHandler(conn *websocket.Conn) {
 		}
 		}
 		server := servercfg.GetServerInfo()
 		server := servercfg.GetServerInfo()
 		server.TrafficKey = key
 		server.TrafficKey = key
-		if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
-			// set MQ username and password for EMQX clients
-			server.MQUserName = result.Host.ID.String()
-			server.MQPassword = hostPass
-		}
 		result.Host.HostPass = ""
 		result.Host.HostPass = ""
 		response := models.RegisterResponse{
 		response := models.RegisterResponse{
 			ServerConf:    server,
 			ServerConf:    server,
@@ -233,11 +245,15 @@ func CheckNetRegAndHostUpdate(networks []string, h *models.Host, relayNodeId uui
 				continue
 				continue
 			}
 			}
 			if relayNodeId != uuid.Nil && !newNode.IsRelayed {
 			if relayNodeId != uuid.Nil && !newNode.IsRelayed {
-				newNode.IsRelayed = true
-				newNode.RelayedBy = relayNodeId.String()
-				slog.Info(fmt.Sprintf("adding relayed node %s to relay %s on network %s", newNode.ID.String(), relayNodeId.String(), network))
-				if err := logic.UpsertNode(newNode); err != nil {
-					slog.Error("failed to update node", "nodeid", relayNodeId.String())
+				// check if relay node exists and acting as relay
+				relaynode, err := logic.GetNodeByID(relayNodeId.String())
+				if err == nil && relaynode.IsRelay {
+					newNode.IsRelayed = true
+					newNode.RelayedBy = relayNodeId.String()
+					slog.Info(fmt.Sprintf("adding relayed node %s to relay %s on network %s", newNode.ID.String(), relayNodeId.String(), network))
+					if err := logic.UpsertNode(newNode); err != nil {
+						slog.Error("failed to update node", "nodeid", relayNodeId.String())
+					}
 				}
 				}
 			}
 			}
 			logger.Log(1, "added new node", newNode.ID.String(), "to host", h.Name)
 			logger.Log(1, "added new node", newNode.ID.String(), "to host", h.Name)
@@ -246,6 +262,12 @@ func CheckNetRegAndHostUpdate(networks []string, h *models.Host, relayNodeId uui
 				Host:   *h,
 				Host:   *h,
 				Node:   *newNode,
 				Node:   *newNode,
 			})
 			})
+			if h.IsDefault {
+				// make  host failover
+				logic.CreateFailOver(*newNode)
+				// make host remote access gateway
+				logic.CreateIngressGateway(network, newNode.ID.String(), models.IngressRequest{})
+			}
 		}
 		}
 	}
 	}
 	if servercfg.IsMessageQueueBackend() {
 	if servercfg.IsMessageQueueBackend() {

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

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

+ 1 - 1
controllers/config/dnsconfig/Corefile

@@ -1,4 +1,4 @@
-skynet  {
+. {
     reload 15s
     reload 15s
     hosts /root/dnsconfig/netmaker.hosts {
     hosts /root/dnsconfig/netmaker.hosts {
 	fallthrough	
 	fallthrough	

+ 1 - 1
controllers/docs.go

@@ -10,7 +10,7 @@
 //
 //
 //	Schemes: https
 //	Schemes: https
 //	BasePath: /
 //	BasePath: /
-//	Version: 0.22.0
+//	Version: 0.23.0
 //	Host: api.demo.netmaker.io
 //	Host: api.demo.netmaker.io
 //
 //
 //	Consumes:
 //	Consumes:

+ 2 - 8
controllers/enrollmentkeys.go

@@ -305,18 +305,17 @@ func handleHostRegister(w http.ResponseWriter, r *http.Request) {
 		)
 		)
 		return
 		return
 	}
 	}
-	hostPass := newHost.HostPass
 	if !hostExists {
 	if !hostExists {
 		newHost.PersistentKeepalive = models.DefaultPersistentKeepAlive
 		newHost.PersistentKeepalive = models.DefaultPersistentKeepAlive
 		// register host
 		// register host
 		logic.CheckHostPorts(&newHost)
 		logic.CheckHostPorts(&newHost)
 		// create EMQX credentials and ACLs for host
 		// create EMQX credentials and ACLs for host
 		if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 		if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
-			if err := mq.CreateEmqxUser(newHost.ID.String(), newHost.HostPass, false); err != nil {
+			if err := mq.GetEmqxHandler().CreateEmqxUser(newHost.ID.String(), newHost.HostPass); err != nil {
 				logger.Log(0, "failed to create host credentials for EMQX: ", err.Error())
 				logger.Log(0, "failed to create host credentials for EMQX: ", err.Error())
 				return
 				return
 			}
 			}
-			if err := mq.CreateHostACL(newHost.ID.String(), servercfg.GetServerInfo().Server); err != nil {
+			if err := mq.GetEmqxHandler().CreateHostACL(newHost.ID.String(), servercfg.GetServerInfo().Server); err != nil {
 				logger.Log(0, "failed to add host ACL rules to EMQX: ", err.Error())
 				logger.Log(0, "failed to add host ACL rules to EMQX: ", err.Error())
 				return
 				return
 			}
 			}
@@ -361,11 +360,6 @@ func handleHostRegister(w http.ResponseWriter, r *http.Request) {
 	// ready the response
 	// ready the response
 	server := servercfg.GetServerInfo()
 	server := servercfg.GetServerInfo()
 	server.TrafficKey = key
 	server.TrafficKey = key
-	if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
-		// set MQ username and password for EMQX clients
-		server.MQUserName = newHost.ID.String()
-		server.MQPassword = hostPass
-	}
 	response := models.RegisterResponse{
 	response := models.RegisterResponse{
 		ServerConf:    server,
 		ServerConf:    server,
 		RequestedHost: newHost,
 		RequestedHost: newHost,

+ 54 - 9
controllers/ext_client.go

@@ -6,12 +6,16 @@ import (
 	"fmt"
 	"fmt"
 	"net"
 	"net"
 	"net/http"
 	"net/http"
+	"reflect"
 	"strconv"
 	"strconv"
+	"strings"
 
 
+	"github.com/go-playground/validator/v10"
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/logic/acls"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 
 
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
@@ -218,7 +222,7 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
 		gwendpoint = fmt.Sprintf("%s:%d", host.EndpointIP.String(), host.ListenPort)
 		gwendpoint = fmt.Sprintf("%s:%d", host.EndpointIP.String(), host.ListenPort)
 	}
 	}
 	var newAllowedIPs string
 	var newAllowedIPs string
-	if logic.IsInternetGw(gwnode) {
+	if logic.IsInternetGw(gwnode) || gwnode.InternetGwID != "" {
 		egressrange := "0.0.0.0/0"
 		egressrange := "0.0.0.0/0"
 		if gwnode.Address6.IP != nil && client.Address6 != "" {
 		if gwnode.Address6.IP != nil && client.Address6 != "" {
 			egressrange += "," + "::/0"
 			egressrange += "," + "::/0"
@@ -250,11 +254,28 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
 	if host.MTU != 0 {
 	if host.MTU != 0 {
 		defaultMTU = host.MTU
 		defaultMTU = host.MTU
 	}
 	}
+
+	postUp := strings.Builder{}
+	if client.PostUp != "" && params["type"] != "qr" {
+		for _, loc := range strings.Split(client.PostUp, "\n") {
+			postUp.WriteString(fmt.Sprintf("PostUp = %s\n", loc))
+		}
+	}
+
+	postDown := strings.Builder{}
+	if client.PostDown != "" && params["type"] != "qr" {
+		for _, loc := range strings.Split(client.PostDown, "\n") {
+			postDown.WriteString(fmt.Sprintf("PostDown = %s\n", loc))
+		}
+	}
+
 	config := fmt.Sprintf(`[Interface]
 	config := fmt.Sprintf(`[Interface]
 Address = %s
 Address = %s
 PrivateKey = %s
 PrivateKey = %s
 MTU = %d
 MTU = %d
 %s
 %s
+%s
+%s
 
 
 [Peer]
 [Peer]
 PublicKey = %s
 PublicKey = %s
@@ -266,10 +287,13 @@ Endpoint = %s
 		client.PrivateKey,
 		client.PrivateKey,
 		defaultMTU,
 		defaultMTU,
 		defaultDNS,
 		defaultDNS,
+		postUp.String(),
+		postDown.String(),
 		host.PublicKey,
 		host.PublicKey,
 		newAllowedIPs,
 		newAllowedIPs,
 		gwendpoint,
 		gwendpoint,
-		keepalive)
+		keepalive,
+	)
 
 
 	if params["type"] == "qr" {
 	if params["type"] == "qr" {
 		bytes, err := qrcode.Encode(config, qrcode.Medium, 220)
 		bytes, err := qrcode.Encode(config, qrcode.Medium, 220)
@@ -330,7 +354,6 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 	var customExtClient models.CustomExtClient
 	var customExtClient models.CustomExtClient
-
 	if err := json.NewDecoder(r.Body).Decode(&customExtClient); err != nil {
 	if err := json.NewDecoder(r.Body).Decode(&customExtClient); err != nil {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
@@ -407,14 +430,14 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 		extclient.Enabled = parentNetwork.DefaultACL == "yes"
 		extclient.Enabled = parentNetwork.DefaultACL == "yes"
 	}
 	}
 
 
-	if err := logic.SetClientDefaultACLs(&extclient); err != nil {
-		slog.Error("failed to set default acls for extclient", "user", r.Header.Get("user"), "network", node.Network, "error", err)
+	if err = logic.CreateExtClient(&extclient); err != nil {
+		slog.Error("failed to create extclient", "user", r.Header.Get("user"), "network", node.Network, "error", err)
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
 
 
-	if err = logic.CreateExtClient(&extclient); err != nil {
-		slog.Error("failed to create extclient", "user", r.Header.Get("user"), "network", node.Network, "error", err)
+	if err := logic.SetClientDefaultACLs(&extclient); err != nil {
+		slog.Error("failed to set default acls for extclient", "user", r.Header.Get("user"), "network", node.Network, "error", err)
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
@@ -486,7 +509,7 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	var changedID = update.ClientID != oldExtClient.ClientID
 	var changedID = update.ClientID != oldExtClient.ClientID
 
 
-	if len(update.DeniedACLs) != len(oldExtClient.DeniedACLs) {
+	if !reflect.DeepEqual(update.DeniedACLs, oldExtClient.DeniedACLs) {
 		sendPeerUpdate = true
 		sendPeerUpdate = true
 		logic.SetClientACLs(&oldExtClient, update.DeniedACLs)
 		logic.SetClientACLs(&oldExtClient, update.DeniedACLs)
 	}
 	}
@@ -499,7 +522,6 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	newclient := logic.UpdateExtClient(&oldExtClient, &update)
 	newclient := logic.UpdateExtClient(&oldExtClient, &update)
 	if err := logic.DeleteExtClient(oldExtClient.Network, oldExtClient.ClientID); err != nil {
 	if err := logic.DeleteExtClient(oldExtClient.Network, oldExtClient.ClientID); err != nil {
-
 		slog.Error("failed to delete ext client", "user", r.Header.Get("user"), "id", oldExtClient.ClientID, "network", oldExtClient.Network, "error", err)
 		slog.Error("failed to delete ext client", "user", r.Header.Get("user"), "id", oldExtClient.ClientID, "network", oldExtClient.Network, "error", err)
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
@@ -593,6 +615,24 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
+	// delete client acls
+	var networkAcls acls.ACLContainer
+	networkAcls, err = networkAcls.Get(acls.ContainerID(network))
+	if err != nil {
+		slog.Error("failed to get network acls", "err", err)
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	for objId := range networkAcls {
+		delete(networkAcls[objId], acls.AclID(clientid))
+	}
+	delete(networkAcls, acls.AclID(clientid))
+	if _, err = networkAcls.Save(acls.ContainerID(network)); err != nil {
+		slog.Error("failed to update network acls", "err", err)
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+
 	go func() {
 	go func() {
 		if err := mq.PublishDeletedClientPeerUpdate(&extclient); err != nil {
 		if err := mq.PublishDeletedClientPeerUpdate(&extclient); err != nil {
 			logger.Log(1, "error setting ext peers on "+ingressnode.ID.String()+": "+err.Error())
 			logger.Log(1, "error setting ext peers on "+ingressnode.ID.String()+": "+err.Error())
@@ -609,6 +649,11 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
 
 
 // validateCustomExtClient	Validates the extclient object
 // validateCustomExtClient	Validates the extclient object
 func validateCustomExtClient(customExtClient *models.CustomExtClient, checkID bool) error {
 func validateCustomExtClient(customExtClient *models.CustomExtClient, checkID bool) error {
+	v := validator.New()
+	err := v.Struct(customExtClient)
+	if err != nil {
+		return err
+	}
 	//validate clientid
 	//validate clientid
 	if customExtClient.ClientID != "" {
 	if customExtClient.ClientID != "" {
 		if err := isValid(customExtClient.ClientID, checkID); err != nil {
 		if err := isValid(customExtClient.ClientID, checkID); err != nil {

+ 14 - 8
controllers/hosts.go

@@ -124,9 +124,6 @@ func pull(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 	serverConf := servercfg.GetServerInfo()
 	serverConf := servercfg.GetServerInfo()
-	if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
-		serverConf.MQUserName = hostID
-	}
 	key, keyErr := logic.RetrievePublicTrafficKey()
 	key, keyErr := logic.RetrievePublicTrafficKey()
 	if keyErr != nil {
 	if keyErr != nil {
 		logger.Log(0, "error retrieving key:", keyErr.Error())
 		logger.Log(0, "error retrieving key:", keyErr.Error())
@@ -144,6 +141,9 @@ func pull(w http.ResponseWriter, r *http.Request) {
 		HostNetworkInfo: hPU.HostNetworkInfo,
 		HostNetworkInfo: hPU.HostNetworkInfo,
 		EgressRoutes:    hPU.EgressRoutes,
 		EgressRoutes:    hPU.EgressRoutes,
 		FwUpdate:        hPU.FwUpdate,
 		FwUpdate:        hPU.FwUpdate,
+		ChangeDefaultGw: hPU.ChangeDefaultGw,
+		DefaultGwIp:     hPU.DefaultGwIp,
+		IsInternetGw:    hPU.IsInternetGw,
 	}
 	}
 
 
 	logger.Log(1, hostID, "completed a pull")
 	logger.Log(1, hostID, "completed a pull")
@@ -254,7 +254,7 @@ func hostUpdateFallback(w http.ResponseWriter, r *http.Request) {
 			return
 			return
 		}
 		}
 	case models.UpdateMetrics:
 	case models.UpdateMetrics:
-		// mq.UpdateMetricsFallBack(hostUpdate.Node.ID.String(), hostUpdate.NewMetrics)
+		mq.UpdateMetricsFallBack(hostUpdate.Node.ID.String(), hostUpdate.NewMetrics)
 	}
 	}
 	logic.ReturnSuccessResponse(w, r, "updated host data")
 	logic.ReturnSuccessResponse(w, r, "updated host data")
 
 
@@ -298,7 +298,7 @@ func deleteHost(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 	if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 		// delete EMQX credentials for host
 		// delete EMQX credentials for host
-		if err := mq.DeleteEmqxUser(currHost.ID.String()); err != nil {
+		if err := mq.GetEmqxHandler().DeleteEmqxUser(currHost.ID.String()); err != nil {
 			slog.Error("failed to remove host credentials from EMQX", "id", currHost.ID, "error", err)
 			slog.Error("failed to remove host credentials from EMQX", "id", currHost.ID, "error", err)
 		}
 		}
 	}
 	}
@@ -354,6 +354,12 @@ func addHostToNetwork(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 	logger.Log(1, "added new node", newNode.ID.String(), "to host", currHost.Name)
 	logger.Log(1, "added new node", newNode.ID.String(), "to host", currHost.Name)
+	if currHost.IsDefault {
+		// make  host failover
+		logic.CreateFailOver(*newNode)
+		// make host remote access gateway
+		logic.CreateIngressGateway(network, newNode.ID.String(), models.IngressRequest{})
+	}
 	go func() {
 	go func() {
 		mq.HostUpdate(&models.HostUpdate{
 		mq.HostUpdate(&models.HostUpdate{
 			Action: models.JoinHostToNetwork,
 			Action: models.JoinHostToNetwork,
@@ -549,15 +555,15 @@ func authenticateHost(response http.ResponseWriter, request *http.Request) {
 
 
 	// Create EMQX creds and ACLs if not found
 	// Create EMQX creds and ACLs if not found
 	if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 	if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
-		if err := mq.CreateEmqxUser(host.ID.String(), authRequest.Password, false); err != nil {
+		if err := mq.GetEmqxHandler().CreateEmqxUser(host.ID.String(), authRequest.Password); err != nil {
 			slog.Error("failed to create host credentials for EMQX: ", err.Error())
 			slog.Error("failed to create host credentials for EMQX: ", err.Error())
 		} else {
 		} else {
-			if err := mq.CreateHostACL(host.ID.String(), servercfg.GetServerInfo().Server); err != nil {
+			if err := mq.GetEmqxHandler().CreateHostACL(host.ID.String(), servercfg.GetServerInfo().Server); err != nil {
 				slog.Error("failed to add host ACL rules to EMQX: ", err.Error())
 				slog.Error("failed to add host ACL rules to EMQX: ", err.Error())
 			}
 			}
 			for _, nodeID := range host.Nodes {
 			for _, nodeID := range host.Nodes {
 				if node, err := logic.GetNodeByID(nodeID); err == nil {
 				if node, err := logic.GetNodeByID(nodeID); err == nil {
-					if err = mq.AppendNodeUpdateACL(host.ID.String(), node.Network, node.ID.String(), servercfg.GetServer()); err != nil {
+					if err = mq.GetEmqxHandler().AppendNodeUpdateACL(host.ID.String(), node.Network, node.ID.String(), servercfg.GetServer()); err != nil {
 						slog.Error("failed to add ACLs for EMQX node", "error", err)
 						slog.Error("failed to add ACLs for EMQX node", "error", err)
 					}
 					}
 				} else {
 				} else {

+ 0 - 3
controllers/migrate.go

@@ -71,9 +71,6 @@ func migrate(w http.ResponseWriter, r *http.Request) {
 				return
 				return
 			}
 			}
 			server = servercfg.GetServerInfo()
 			server = servercfg.GetServerInfo()
-			if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
-				server.MQUserName = host.ID.String()
-			}
 			key, keyErr := logic.RetrievePublicTrafficKey()
 			key, keyErr := logic.RetrievePublicTrafficKey()
 			if keyErr != nil {
 			if keyErr != nil {
 				slog.Error("retrieving traffickey", "error", err)
 				slog.Error("retrieving traffickey", "error", err)

+ 217 - 19
controllers/network.go

@@ -8,6 +8,7 @@ import (
 	"net/http"
 	"net/http"
 	"strings"
 	"strings"
 
 
+	"github.com/google/uuid"
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
 	"golang.org/x/exp/slog"
 	"golang.org/x/exp/slog"
 
 
@@ -17,6 +18,7 @@ import (
 	"github.com/gravitl/netmaker/logic/acls"
 	"github.com/gravitl/netmaker/logic/acls"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/mq"
+	"github.com/gravitl/netmaker/servercfg"
 )
 )
 
 
 func networkHandlers(r *mux.Router) {
 func networkHandlers(r *mux.Router) {
@@ -27,6 +29,7 @@ func networkHandlers(r *mux.Router) {
 	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(updateNetwork))).Methods(http.MethodPut)
 	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(updateNetwork))).Methods(http.MethodPut)
 	// ACLs
 	// ACLs
 	r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(updateNetworkACL))).Methods(http.MethodPut)
 	r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(updateNetworkACL))).Methods(http.MethodPut)
+	r.HandleFunc("/api/networks/{networkname}/acls/v2", logic.SecurityCheck(true, http.HandlerFunc(updateNetworkACLv2))).Methods(http.MethodPut)
 	r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(getNetworkACL))).Methods(http.MethodGet)
 	r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(getNetworkACL))).Methods(http.MethodGet)
 }
 }
 
 
@@ -129,7 +132,7 @@ func updateNetworkACL(w http.ResponseWriter, r *http.Request) {
 	// send peer updates
 	// send peer updates
 	go func() {
 	go func() {
 		if err = mq.PublishPeerUpdate(false); err != nil {
 		if err = mq.PublishPeerUpdate(false); err != nil {
-			logger.Log(0, "failed to publish peer update after ACL update on", netname)
+			logger.Log(0, "failed to publish peer update after ACL update on network:", netname)
 		}
 		}
 	}()
 	}()
 
 
@@ -137,6 +140,196 @@ func updateNetworkACL(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(newNetACL)
 	json.NewEncoder(w).Encode(newNetACL)
 }
 }
 
 
+// swagger:route PUT /api/networks/{networkname}/acls/v2 networks updateNetworkACL
+//
+// Update a network ACL (Access Control List).
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: aclContainerResponse
+func updateNetworkACLv2(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+	var params = mux.Vars(r)
+	netname := params["networkname"]
+	var networkACLChange acls.ACLContainer
+	networkACLChange, err := networkACLChange.Get(acls.ContainerID(netname))
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"),
+			fmt.Sprintf("failed to fetch ACLs for network [%s]: %v", netname, err))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	err = json.NewDecoder(r.Body).Decode(&networkACLChange)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "error decoding request body: ",
+			err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+
+	// clone req body to use as return data successful update
+	retData := make(acls.ACLContainer)
+	data, err := json.Marshal(networkACLChange)
+	if err != nil {
+		slog.Error("failed to marshal networkACLChange whiles cloning", "error", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	err = json.Unmarshal(data, &retData)
+	if err != nil {
+		slog.Error("failed to unmarshal networkACLChange whiles cloning", "error", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+
+	allNodes, err := logic.GetAllNodes()
+	if err != nil {
+		slog.Error("failed to fetch all nodes", "error", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	networkNodes := make([]models.Node, 0)
+	for _, node := range allNodes {
+		if node.Network == netname {
+			networkNodes = append(networkNodes, node)
+		}
+	}
+	networkNodesIdMap := make(map[string]models.Node)
+	for _, node := range networkNodes {
+		networkNodesIdMap[node.ID.String()] = node
+	}
+	networkClients, err := logic.GetNetworkExtClients(netname)
+	if err != nil {
+		slog.Error("failed to fetch network clients", "error", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	networkClientsMap := make(map[string]models.ExtClient)
+	for _, client := range networkClients {
+		networkClientsMap[client.ClientID] = client
+	}
+
+	// keep track of ingress gateways to disconnect from their clients
+	// this is required because PublishPeerUpdate only somehow does not stop communication
+	// between blocked clients and their ingress
+	assocClientsToDisconnectPerHost := make(map[uuid.UUID][]models.ExtClient)
+
+	// update client acls and then, remove client acls from req data to pass to existing functions
+	for id, acl := range networkACLChange {
+		// for node acls
+		if _, ok := networkNodesIdMap[string(id)]; ok {
+			nodeId := string(id)
+			// check acl update, then remove client entries
+			for id2 := range acl {
+				if _, ok := networkNodesIdMap[string(id2)]; !ok {
+					// update client acl
+					clientId := string(id2)
+					if client, ok := networkClientsMap[clientId]; ok {
+						if client.DeniedACLs == nil {
+							client.DeniedACLs = make(map[string]struct{})
+						}
+						if acl[acls.AclID(clientId)] == acls.NotAllowed {
+							client.DeniedACLs[nodeId] = struct{}{}
+						} else {
+							delete(client.DeniedACLs, string(nodeId))
+						}
+						networkClientsMap[clientId] = client
+					}
+				}
+			}
+		} else {
+			// for client acls
+			clientId := string(id)
+			for id2 := range acl {
+				if _, ok := networkNodesIdMap[string(id2)]; !ok {
+					// update client acl
+					clientId2 := string(id2)
+					if client, ok := networkClientsMap[clientId]; ok {
+						if client.DeniedACLs == nil {
+							client.DeniedACLs = make(map[string]struct{})
+						}
+						{
+							// TODO: review this when client-to-client acls are supported
+							// if acl[acls.AclID(clientId2)] == acls.NotAllowed {
+							// 	client.DeniedACLs[clientId2] = struct{}{}
+							// } else {
+							// 	delete(client.DeniedACLs, clientId2)
+							// }
+							delete(client.DeniedACLs, clientId2)
+						}
+						networkClientsMap[clientId] = client
+					}
+				} else {
+					nodeId2 := string(id2)
+					if networkClientsMap[clientId].IngressGatewayID == nodeId2 && acl[acls.AclID(nodeId2)] == acls.NotAllowed {
+						assocClientsToDisconnectPerHost[networkNodesIdMap[nodeId2].HostID] = append(assocClientsToDisconnectPerHost[networkNodesIdMap[nodeId2].HostID], networkClientsMap[clientId])
+					}
+				}
+			}
+		}
+	}
+
+	// update each client in db for pro servers
+	if servercfg.IsPro {
+		for _, client := range networkClientsMap {
+			client := client
+			err := logic.DeleteExtClient(client.Network, client.ClientID)
+			if err != nil {
+				slog.Error("failed to delete client during update", "client", client.ClientID, "error", err.Error())
+				logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+				return
+			}
+			err = logic.SaveExtClient(&client)
+			if err != nil {
+				slog.Error("failed to save client during update", "client", client.ClientID, "error", err.Error())
+				logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+				return
+			}
+		}
+	}
+
+	_, err = networkACLChange.Save(acls.ContainerID(netname))
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"),
+			fmt.Sprintf("failed to update ACLs for network [%s]: %v", netname, err))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	logger.Log(1, r.Header.Get("user"), "updated ACLs for network", netname)
+
+	// send peer updates
+	go func() {
+		if err = mq.PublishPeerUpdate(false); err != nil {
+			logger.Log(0, "failed to publish peer update after ACL update on network:", netname)
+		}
+
+		// update ingress gateways of associated clients
+		hosts, err := logic.GetAllHosts()
+		if err != nil {
+			slog.Error("failed to fetch hosts after network ACL update. skipping publish extclients ACL", "network", netname)
+			return
+		}
+		hostsMap := make(map[uuid.UUID]models.Host)
+		for _, host := range hosts {
+			hostsMap[host.ID] = host
+		}
+		for hostId, clients := range assocClientsToDisconnectPerHost {
+			if host, ok := hostsMap[hostId]; ok {
+				if err = mq.PublishSingleHostPeerUpdate(&host, allNodes, nil, clients, false); err != nil {
+					slog.Error("failed to publish peer update to ingress after ACL update on network", "network", netname, "host", hostId)
+				}
+			}
+		}
+	}()
+
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(networkACLChange)
+}
+
 // swagger:route GET /api/networks/{networkname}/acls networks getNetworkACL
 // swagger:route GET /api/networks/{networkname}/acls networks getNetworkACL
 //
 //
 // Get a network ACL (Access Control List).
 // Get a network ACL (Access Control List).
@@ -288,25 +481,30 @@ func createNetwork(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
-
-	defaultHosts := logic.GetDefaultHosts()
-	for i := range defaultHosts {
-		currHost := &defaultHosts[i]
-		newNode, err := logic.UpdateHostNetwork(currHost, network.NetID, true)
-		if err != nil {
-			logger.Log(0, r.Header.Get("user"), "failed to add host to network:", currHost.ID.String(), network.NetID, err.Error())
-			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-			return
-		}
-		logger.Log(1, "added new node", newNode.ID.String(), "to host", currHost.Name)
-		if err = mq.HostUpdate(&models.HostUpdate{
-			Action: models.JoinHostToNetwork,
-			Host:   *currHost,
-			Node:   *newNode,
-		}); err != nil {
-			logger.Log(0, r.Header.Get("user"), "failed to add host to network:", currHost.ID.String(), network.NetID, err.Error())
+	go func() {
+		defaultHosts := logic.GetDefaultHosts()
+		for i := range defaultHosts {
+			currHost := &defaultHosts[i]
+			newNode, err := logic.UpdateHostNetwork(currHost, network.NetID, true)
+			if err != nil {
+				logger.Log(0, r.Header.Get("user"), "failed to add host to network:", currHost.ID.String(), network.NetID, err.Error())
+				logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+				return
+			}
+			logger.Log(1, "added new node", newNode.ID.String(), "to host", currHost.Name)
+			if err = mq.HostUpdate(&models.HostUpdate{
+				Action: models.JoinHostToNetwork,
+				Host:   *currHost,
+				Node:   *newNode,
+			}); err != nil {
+				logger.Log(0, r.Header.Get("user"), "failed to add host to network:", currHost.ID.String(), network.NetID, err.Error())
+			}
+			// make  host failover
+			logic.CreateFailOver(*newNode)
+			// make host remote access gateway
+			logic.CreateIngressGateway(network.NetID, newNode.ID.String(), models.IngressRequest{})
 		}
 		}
-	}
+	}()
 
 
 	logger.Log(1, r.Header.Get("user"), "created network", network.NetID)
 	logger.Log(1, r.Header.Get("user"), "created network", network.NetID)
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)

+ 25 - 30
controllers/node.go

@@ -345,7 +345,7 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 	nodeid := params["nodeid"]
 	nodeid := params["nodeid"]
 
 
-	node, err := validateParams(nodeid, params["network"])
+	node, err := logic.ValidateParams(nodeid, params["network"])
 	if err != nil {
 	if err != nil {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
@@ -372,10 +372,6 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 	server := servercfg.GetServerInfo()
 	server := servercfg.GetServerInfo()
-	if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
-		// set MQ username for EMQX clients
-		server.MQUserName = host.ID.String()
-	}
 	response := models.NodeGet{
 	response := models.NodeGet{
 		Node:         node,
 		Node:         node,
 		Host:         *host,
 		Host:         *host,
@@ -406,9 +402,9 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 func createEgressGateway(w http.ResponseWriter, r *http.Request) {
 func createEgressGateway(w http.ResponseWriter, r *http.Request) {
 	var gateway models.EgressGatewayRequest
 	var gateway models.EgressGatewayRequest
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
-	node, err := validateParams(params["nodeid"], params["network"])
+	node, err := logic.ValidateParams(params["nodeid"], params["network"])
 	if err != nil {
 	if err != nil {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "bad request"))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
@@ -457,9 +453,9 @@ func deleteEgressGateway(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 	nodeid := params["nodeid"]
 	nodeid := params["nodeid"]
 	netid := params["network"]
 	netid := params["network"]
-	node, err := validateParams(nodeid, netid)
+	node, err := logic.ValidateParams(nodeid, netid)
 	if err != nil {
 	if err != nil {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "bad request"))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
 	node, err = logic.DeleteEgressGateway(netid, nodeid)
 	node, err = logic.DeleteEgressGateway(netid, nodeid)
@@ -501,9 +497,9 @@ func createIngressGateway(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
 	nodeid := params["nodeid"]
 	nodeid := params["nodeid"]
 	netid := params["network"]
 	netid := params["network"]
-	node, err := validateParams(nodeid, netid)
+	node, err := logic.ValidateParams(nodeid, netid)
 	if err != nil {
 	if err != nil {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "bad request"))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
 	var request models.IngressRequest
 	var request models.IngressRequest
@@ -544,9 +540,9 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 	nodeid := params["nodeid"]
 	nodeid := params["nodeid"]
 	netid := params["network"]
 	netid := params["network"]
-	node, err := validateParams(nodeid, netid)
+	node, err := logic.ValidateParams(nodeid, netid)
 	if err != nil {
 	if err != nil {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "bad request"))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
 	node, removedClients, err := logic.DeleteIngressGateway(nodeid)
 	node, removedClients, err := logic.DeleteIngressGateway(nodeid)
@@ -622,9 +618,9 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 
 
 	//start here
 	//start here
 	nodeid := params["nodeid"]
 	nodeid := params["nodeid"]
-	currentNode, err := validateParams(nodeid, params["network"])
+	currentNode, err := logic.ValidateParams(nodeid, params["network"])
 	if err != nil {
 	if err != nil {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "bad request"))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
 	var newData models.ApiNode
 	var newData models.ApiNode
@@ -635,7 +631,19 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
+	if len(newData.Metadata) > 255 {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("metadata cannot be longer than 255 characters"), "badrequest"))
+		return
+	}
 	newNode := newData.ConvertToServerNode(&currentNode)
 	newNode := newData.ConvertToServerNode(&currentNode)
+	if newNode.IsInternetGateway != currentNode.IsInternetGateway {
+		if newNode.IsInternetGateway {
+			logic.SetInternetGw(newNode, models.InetNodeReq{})
+		} else {
+			logic.UnsetInternetGw(newNode)
+		}
+
+	}
 	relayUpdate := logic.RelayUpdates(&currentNode, newNode)
 	relayUpdate := logic.RelayUpdates(&currentNode, newNode)
 	_, err = logic.GetHost(newNode.HostID.String())
 	_, err = logic.GetHost(newNode.HostID.String())
 	if err != nil {
 	if err != nil {
@@ -695,9 +703,9 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 	// get params
 	// get params
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
 	var nodeid = params["nodeid"]
 	var nodeid = params["nodeid"]
-	node, err := validateParams(nodeid, params["network"])
+	node, err := logic.ValidateParams(nodeid, params["network"])
 	if err != nil {
 	if err != nil {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "bad request"))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
 	forceDelete := r.URL.Query().Get("force") == "true"
 	forceDelete := r.URL.Query().Get("force") == "true"
@@ -716,16 +724,3 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 	logger.Log(1, r.Header.Get("user"), "Deleted node", nodeid, "from network", params["network"])
 	logger.Log(1, r.Header.Get("user"), "Deleted node", nodeid, "from network", params["network"])
 	go mq.PublishMqUpdatesForDeletedNode(node, !fromNode, gwClients)
 	go mq.PublishMqUpdatesForDeletedNode(node, !fromNode, gwClients)
 }
 }
-
-func validateParams(nodeid, netid string) (models.Node, error) {
-	node, err := logic.GetNodeByID(nodeid)
-	if err != nil {
-		slog.Error("error fetching node", "node", nodeid, "error", err.Error())
-		return node, fmt.Errorf("error fetching node during parameter validation: %v", err)
-	}
-	if node.Network != netid {
-		slog.Error("network url param does not match node id", "url nodeid", netid, "node", node.Network)
-		return node, fmt.Errorf("network url param does not match node network")
-	}
-	return node, nil
-}

+ 1 - 5
controllers/node_test.go

@@ -132,11 +132,7 @@ func TestGetNetworkNodes(t *testing.T) {
 
 
 func TestValidateEgressGateway(t *testing.T) {
 func TestValidateEgressGateway(t *testing.T) {
 	var gateway models.EgressGatewayRequest
 	var gateway models.EgressGatewayRequest
-	t.Run("EmptyRange", func(t *testing.T) {
-		gateway.Ranges = []string{}
-		err := logic.ValidateEgressGateway(gateway)
-		assert.EqualError(t, err, "IP Ranges Cannot Be Empty")
-	})
+
 	t.Run("Success", func(t *testing.T) {
 	t.Run("Success", func(t *testing.T) {
 		gateway.Ranges = []string{"10.100.100.0/24"}
 		gateway.Ranges = []string{"10.100.100.0/24"}
 		err := logic.ValidateEgressGateway(gateway)
 		err := logic.ValidateEgressGateway(gateway)

+ 2 - 2
database/rqlite.go

@@ -29,8 +29,8 @@ func initRqliteDatabase() error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
-	RQliteDatabase = conn
-	RQliteDatabase.SetConsistencyLevel("strong")
+	RQliteDatabase = *conn
+	RQliteDatabase.SetConsistencyLevel(gorqlite.ConsistencyLevelStrong)
 	return nil
 	return nil
 }
 }
 
 

+ 11 - 11
go.mod

@@ -4,21 +4,21 @@ go 1.19
 
 
 require (
 require (
 	github.com/eclipse/paho.mqtt.golang v1.4.3
 	github.com/eclipse/paho.mqtt.golang v1.4.3
-	github.com/go-playground/validator/v10 v10.16.0
+	github.com/go-playground/validator/v10 v10.18.0
 	github.com/golang-jwt/jwt/v4 v4.5.0
 	github.com/golang-jwt/jwt/v4 v4.5.0
-	github.com/google/uuid v1.5.0
+	github.com/google/uuid v1.6.0
 	github.com/gorilla/handlers v1.5.2
 	github.com/gorilla/handlers v1.5.2
 	github.com/gorilla/mux v1.8.1
 	github.com/gorilla/mux v1.8.1
 	github.com/lib/pq v1.10.9
 	github.com/lib/pq v1.10.9
-	github.com/mattn/go-sqlite3 v1.14.19
-	github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
+	github.com/mattn/go-sqlite3 v1.14.22
+	github.com/rqlite/gorqlite v0.0.0-20240122221808-a8a425b1a6aa
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/stretchr/testify v1.8.4
 	github.com/stretchr/testify v1.8.4
 	github.com/txn2/txeh v1.5.5
 	github.com/txn2/txeh v1.5.5
-	golang.org/x/crypto v0.17.0
-	golang.org/x/net v0.19.0 // indirect
-	golang.org/x/oauth2 v0.15.0
-	golang.org/x/sys v0.15.0 // indirect
+	golang.org/x/crypto v0.19.0
+	golang.org/x/net v0.21.0 // indirect
+	golang.org/x/oauth2 v0.17.0
+	golang.org/x/sys v0.17.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb
 	google.golang.org/protobuf v1.31.0 // indirect
 	google.golang.org/protobuf v1.31.0 // indirect
@@ -27,7 +27,7 @@ require (
 
 
 require (
 require (
 	filippo.io/edwards25519 v1.1.0
 	filippo.io/edwards25519 v1.1.0
-	github.com/c-robinson/iplib v1.0.7
+	github.com/c-robinson/iplib v1.0.8
 	github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0
 	github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0
 )
 )
 
 
@@ -47,7 +47,7 @@ require (
 
 
 require (
 require (
 	cloud.google.com/go/compute/metadata v0.2.3 // indirect
 	cloud.google.com/go/compute/metadata v0.2.3 // indirect
-	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
@@ -61,7 +61,7 @@ require (
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/hashicorp/go-version v1.6.0
 	github.com/hashicorp/go-version v1.6.0
-	github.com/leodido/go-urn v1.2.4 // indirect
+	github.com/leodido/go-urn v1.4.0 // indirect
 	github.com/mattn/go-runewidth v0.0.13 // indirect
 	github.com/mattn/go-runewidth v0.0.13 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
 	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect

+ 22 - 27
go.sum

@@ -5,8 +5,8 @@ cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2Aawl
 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/c-robinson/iplib v1.0.7 h1:Dh9AINAlkc+NsNzZuFiVs+pi3AjN+0B7mu01KHdJKHU=
-github.com/c-robinson/iplib v1.0.7/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo=
+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 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo=
 github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4=
 github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@@ -18,8 +18,8 @@ github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQ
 github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
 github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
 github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
 github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
 github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
-github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
+github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
+github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
 github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
 github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
 github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
 github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -27,8 +27,8 @@ 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/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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE=
-github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
+github.com/go-playground/validator/v10 v10.18.0/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 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
 github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@@ -38,8 +38,8 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
-github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
 github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
 github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
 github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
 github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
 github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
@@ -52,8 +52,8 @@ github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mO
 github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
-github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+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=
 github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
 github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
@@ -62,8 +62,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
 github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
-github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
+github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -73,8 +73,8 @@ github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sA
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 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 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f h1:BSnJgAfHzEp7o8PYJ7YfwAVHhqu7BYUTggcn/LGlUWY=
-github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f/go.mod h1:UW/gxgQwSePTvL1KA8QEHsXeYHP4xkoXgbDdN781p34=
+github.com/rqlite/gorqlite v0.0.0-20240122221808-a8a425b1a6aa h1:hxMLFbj+F444JAS5nUQxTDZwUxwCRqg3WkNqhiDzXrM=
+github.com/rqlite/gorqlite v0.0.0-20240122221808-a8a425b1a6aa/go.mod h1:xF/KoXmrRyahPfo5L7Szb5cAAUl53dMWBh9cMruGEZg=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
@@ -85,12 +85,7 @@ github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyh
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 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/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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/txn2/txeh v1.5.5 h1:UN4e/lCK5HGw/gGAi2GCVrNKg0GTCUWs7gs5riaZlz4=
 github.com/txn2/txeh v1.5.5 h1:UN4e/lCK5HGw/gGAi2GCVrNKg0GTCUWs7gs5riaZlz4=
@@ -102,8 +97,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
-golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
 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/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=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -111,10 +106,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
-golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
-golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
-golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
+golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
+golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
@@ -125,8 +120,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
+golang.org/x/sys v0.17.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

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

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

+ 1 - 1
k8s/client/netclient.yaml

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

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

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

+ 17 - 17
logic/acls/common.go

@@ -12,7 +12,7 @@ import (
 var (
 var (
 	aclCacheMutex = &sync.RWMutex{}
 	aclCacheMutex = &sync.RWMutex{}
 	aclCacheMap   = make(map[ContainerID]ACLContainer)
 	aclCacheMap   = make(map[ContainerID]ACLContainer)
-	aclMutex      = &sync.RWMutex{}
+	AclMutex      = &sync.RWMutex{}
 )
 )
 
 
 func fetchAclContainerFromCache(containerID ContainerID) (aclCont ACLContainer, ok bool) {
 func fetchAclContainerFromCache(containerID ContainerID) (aclCont ACLContainer, ok bool) {
@@ -38,22 +38,22 @@ func DeleteAclFromCache(containerID ContainerID) {
 
 
 // ACL.Allow - allows access by ID in memory
 // ACL.Allow - allows access by ID in memory
 func (acl ACL) Allow(ID AclID) {
 func (acl ACL) Allow(ID AclID) {
-	aclMutex.Lock()
-	defer aclMutex.Unlock()
+	AclMutex.Lock()
+	defer AclMutex.Unlock()
 	acl[ID] = Allowed
 	acl[ID] = Allowed
 }
 }
 
 
 // ACL.DisallowNode - disallows access by ID in memory
 // ACL.DisallowNode - disallows access by ID in memory
 func (acl ACL) Disallow(ID AclID) {
 func (acl ACL) Disallow(ID AclID) {
-	aclMutex.Lock()
-	defer aclMutex.Unlock()
+	AclMutex.Lock()
+	defer AclMutex.Unlock()
 	acl[ID] = NotAllowed
 	acl[ID] = NotAllowed
 }
 }
 
 
 // ACL.Remove - removes a node from a ACL in memory
 // ACL.Remove - removes a node from a ACL in memory
 func (acl ACL) Remove(ID AclID) {
 func (acl ACL) Remove(ID AclID) {
-	aclMutex.Lock()
-	defer aclMutex.Unlock()
+	AclMutex.Lock()
+	defer AclMutex.Unlock()
 	delete(acl, ID)
 	delete(acl, ID)
 }
 }
 
 
@@ -64,24 +64,24 @@ func (acl ACL) Save(containerID ContainerID, ID AclID) (ACL, error) {
 
 
 // ACL.IsAllowed - sees if ID is allowed in referring ACL
 // ACL.IsAllowed - sees if ID is allowed in referring ACL
 func (acl ACL) IsAllowed(ID AclID) (allowed bool) {
 func (acl ACL) IsAllowed(ID AclID) (allowed bool) {
-	aclMutex.RLock()
+	AclMutex.RLock()
 	allowed = acl[ID] == Allowed
 	allowed = acl[ID] == Allowed
-	aclMutex.RUnlock()
+	AclMutex.RUnlock()
 	return
 	return
 }
 }
 
 
 // ACLContainer.UpdateACL - saves the state of a ACL in the ACLContainer in memory
 // ACLContainer.UpdateACL - saves the state of a ACL in the ACLContainer in memory
 func (aclContainer ACLContainer) UpdateACL(ID AclID, acl ACL) ACLContainer {
 func (aclContainer ACLContainer) UpdateACL(ID AclID, acl ACL) ACLContainer {
-	aclMutex.Lock()
-	defer aclMutex.Unlock()
+	AclMutex.Lock()
+	defer AclMutex.Unlock()
 	aclContainer[ID] = acl
 	aclContainer[ID] = acl
 	return aclContainer
 	return aclContainer
 }
 }
 
 
 // ACLContainer.RemoveACL - removes the state of a ACL in the ACLContainer in memory
 // ACLContainer.RemoveACL - removes the state of a ACL in the ACLContainer in memory
 func (aclContainer ACLContainer) RemoveACL(ID AclID) ACLContainer {
 func (aclContainer ACLContainer) RemoveACL(ID AclID) ACLContainer {
-	aclMutex.Lock()
-	defer aclMutex.Unlock()
+	AclMutex.Lock()
+	defer AclMutex.Unlock()
 	delete(aclContainer, ID)
 	delete(aclContainer, ID)
 	return aclContainer
 	return aclContainer
 }
 }
@@ -127,8 +127,8 @@ func (aclContainer ACLContainer) Get(containerID ContainerID) (ACLContainer, err
 
 
 // fetchACLContainer - fetches all current rules in given ACL container
 // fetchACLContainer - fetches all current rules in given ACL container
 func fetchACLContainer(containerID ContainerID) (ACLContainer, error) {
 func fetchACLContainer(containerID ContainerID) (ACLContainer, error) {
-	aclMutex.RLock()
-	defer aclMutex.RUnlock()
+	AclMutex.RLock()
+	defer AclMutex.RUnlock()
 	if servercfg.CacheEnabled() {
 	if servercfg.CacheEnabled() {
 		if aclContainer, ok := fetchAclContainerFromCache(containerID); ok {
 		if aclContainer, ok := fetchAclContainerFromCache(containerID); ok {
 			return aclContainer, nil
 			return aclContainer, nil
@@ -171,8 +171,8 @@ func upsertACL(containerID ContainerID, ID AclID, acl ACL) (ACL, error) {
 // upsertACLContainer - Inserts or updates a network ACL given the json string of the ACL and the container ID
 // upsertACLContainer - Inserts or updates a network ACL given the json string of the ACL and the container ID
 // if nil, create it
 // if nil, create it
 func upsertACLContainer(containerID ContainerID, aclContainer ACLContainer) (ACLContainer, error) {
 func upsertACLContainer(containerID ContainerID, aclContainer ACLContainer) (ACLContainer, error) {
-	aclMutex.Lock()
-	defer aclMutex.Unlock()
+	AclMutex.Lock()
+	defer AclMutex.Unlock()
 	if aclContainer == nil {
 	if aclContainer == nil {
 		aclContainer = make(ACLContainer)
 		aclContainer = make(ACLContainer)
 	}
 	}

+ 7 - 1
logic/acls/nodeacls/modify.go

@@ -22,12 +22,14 @@ func CreateNodeACL(networkID NetworkID, nodeID NodeID, defaultVal byte) (acls.AC
 			return nil, err
 			return nil, err
 		}
 		}
 	}
 	}
+	acls.AclMutex.Lock()
 	var newNodeACL = make(acls.ACL)
 	var newNodeACL = make(acls.ACL)
 	for existingNodeID := range currentNetworkACL {
 	for existingNodeID := range currentNetworkACL {
 		currentNetworkACL[existingNodeID][acls.AclID(nodeID)] = defaultVal // set the old nodes to default value for new node
 		currentNetworkACL[existingNodeID][acls.AclID(nodeID)] = defaultVal // set the old nodes to default value for new node
 		newNodeACL[existingNodeID] = defaultVal                            // set the old nodes in new node ACL to default value
 		newNodeACL[existingNodeID] = defaultVal                            // set the old nodes in new node ACL to default value
 	}
 	}
-	currentNetworkACL[acls.AclID(nodeID)] = newNodeACL                        // append the new node's ACL
+	currentNetworkACL[acls.AclID(nodeID)] = newNodeACL // append the new node's ACL
+	acls.AclMutex.Unlock()
 	retNetworkACL, err := currentNetworkACL.Save(acls.ContainerID(networkID)) // insert into db
 	retNetworkACL, err := currentNetworkACL.Save(acls.ContainerID(networkID)) // insert into db
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -63,7 +65,9 @@ func UpdateNodeACL(networkID NetworkID, nodeID NodeID, acl acls.ACL) (acls.ACL,
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	acls.AclMutex.Lock()
 	currentNetworkACL[acls.AclID(nodeID)] = acl
 	currentNetworkACL[acls.AclID(nodeID)] = acl
+	acls.AclMutex.Unlock()
 	return currentNetworkACL[acls.AclID(nodeID)].Save(acls.ContainerID(networkID), acls.AclID(nodeID))
 	return currentNetworkACL[acls.AclID(nodeID)].Save(acls.ContainerID(networkID), acls.AclID(nodeID))
 }
 }
 
 
@@ -73,12 +77,14 @@ func RemoveNodeACL(networkID NetworkID, nodeID NodeID) (acls.ACLContainer, error
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	acls.AclMutex.Lock()
 	for currentNodeID := range currentNetworkACL {
 	for currentNodeID := range currentNetworkACL {
 		if NodeID(currentNodeID) != nodeID {
 		if NodeID(currentNodeID) != nodeID {
 			currentNetworkACL[currentNodeID].Remove(acls.AclID(nodeID))
 			currentNetworkACL[currentNodeID].Remove(acls.AclID(nodeID))
 		}
 		}
 	}
 	}
 	delete(currentNetworkACL, acls.AclID(nodeID))
 	delete(currentNetworkACL, acls.AclID(nodeID))
+	acls.AclMutex.Unlock()
 	return currentNetworkACL.Save(acls.ContainerID(networkID))
 	return currentNetworkACL.Save(acls.ContainerID(networkID))
 }
 }
 
 

+ 13 - 2
logic/acls/nodeacls/retrieve.go

@@ -13,7 +13,11 @@ func AreNodesAllowed(networkID NetworkID, node1, node2 NodeID) bool {
 	if err != nil {
 	if err != nil {
 		return false
 		return false
 	}
 	}
-	return currentNetworkACL[acls.AclID(node1)].IsAllowed(acls.AclID(node2)) && currentNetworkACL[acls.AclID(node2)].IsAllowed(acls.AclID(node1))
+	var allowed bool
+	acls.AclMutex.RLock()
+	allowed = currentNetworkACL[acls.AclID(node1)].IsAllowed(acls.AclID(node2)) && currentNetworkACL[acls.AclID(node2)].IsAllowed(acls.AclID(node1))
+	acls.AclMutex.RUnlock()
+	return allowed
 }
 }
 
 
 // FetchNodeACL - fetches a specific node's ACL in a given network
 // FetchNodeACL - fetches a specific node's ACL in a given network
@@ -22,10 +26,15 @@ func FetchNodeACL(networkID NetworkID, nodeID NodeID) (acls.ACL, error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	var acl acls.ACL
+	acls.AclMutex.RLock()
 	if currentNetworkACL[acls.AclID(nodeID)] == nil {
 	if currentNetworkACL[acls.AclID(nodeID)] == nil {
+		acls.AclMutex.RUnlock()
 		return nil, fmt.Errorf("no node ACL present for node %s", nodeID)
 		return nil, fmt.Errorf("no node ACL present for node %s", nodeID)
 	}
 	}
-	return currentNetworkACL[acls.AclID(nodeID)], nil
+	acl = currentNetworkACL[acls.AclID(nodeID)]
+	acls.AclMutex.RUnlock()
+	return acl, nil
 }
 }
 
 
 // FetchNodeACLJson - fetches a node's acl in given network except returns the json string
 // FetchNodeACLJson - fetches a node's acl in given network except returns the json string
@@ -34,6 +43,8 @@ func FetchNodeACLJson(networkID NetworkID, nodeID NodeID) (acls.ACLJson, error)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
+	acls.AclMutex.RLock()
+	defer acls.AclMutex.RUnlock()
 	jsonData, err := json.Marshal(&currentNodeACL)
 	jsonData, err := json.Marshal(&currentNodeACL)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err

+ 19 - 0
logic/clients.go

@@ -4,7 +4,9 @@ import (
 	"errors"
 	"errors"
 	"sort"
 	"sort"
 
 
+	"github.com/gravitl/netmaker/logic/acls"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
+	"golang.org/x/exp/slog"
 )
 )
 
 
 // functions defined here, handle client ACLs, should be set on ee
 // functions defined here, handle client ACLs, should be set on ee
@@ -23,6 +25,23 @@ var (
 		return true
 		return true
 	}
 	}
 	SetClientDefaultACLs = func(ec *models.ExtClient) error {
 	SetClientDefaultACLs = func(ec *models.ExtClient) error {
+		// allow all on CE
+		networkAcls := acls.ACLContainer{}
+		networkAcls, err := networkAcls.Get(acls.ContainerID(ec.Network))
+		if err != nil {
+			slog.Error("failed to get network acls", "error", err)
+			return err
+		}
+		networkAcls[acls.AclID(ec.ClientID)] = acls.ACL{}
+		for objId := range networkAcls {
+			networkAcls[objId][acls.AclID(ec.ClientID)] = acls.Allowed
+			networkAcls[acls.AclID(ec.ClientID)][objId] = acls.Allowed
+		}
+		delete(networkAcls[acls.AclID(ec.ClientID)], acls.AclID(ec.ClientID))
+		if _, err = networkAcls.Save(acls.ContainerID(ec.Network)); err != nil {
+			slog.Error("failed to update network acls", "error", err)
+			return err
+		}
 		return nil
 		return nil
 	}
 	}
 	SetClientACLs = func(ec *models.ExtClient, newACLs map[string]struct{}) {
 	SetClientACLs = func(ec *models.ExtClient, newACLs map[string]struct{}) {

+ 4 - 0
logic/extpeers.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"fmt"
 	"net"
 	"net"
 	"reflect"
 	"reflect"
+	"strings"
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
@@ -276,6 +277,9 @@ func UpdateExtClient(old *models.ExtClient, update *models.CustomExtClient) mode
 	if update.DeniedACLs != nil && !reflect.DeepEqual(old.DeniedACLs, update.DeniedACLs) {
 	if update.DeniedACLs != nil && !reflect.DeepEqual(old.DeniedACLs, update.DeniedACLs) {
 		new.DeniedACLs = update.DeniedACLs
 		new.DeniedACLs = update.DeniedACLs
 	}
 	}
+	// replace any \r\n with \n in postup and postdown from HTTP request
+	new.PostUp = strings.Replace(update.PostUp, "\r\n", "\n", -1)
+	new.PostDown = strings.Replace(update.PostDown, "\r\n", "\n", -1)
 	return new
 	return new
 }
 }
 
 

+ 21 - 25
logic/gateway.go

@@ -7,17 +7,13 @@ import (
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/servercfg"
 )
 )
 
 
-var (
-	// SetInternetGw - sets the node as internet gw based on flag bool
-	SetInternetGw = func(node *models.Node, flag bool) {
-	}
-	// IsInternetGw - checks if node is acting as internet gw
-	IsInternetGw = func(node models.Node) bool {
-		return false
-	}
-)
+// IsInternetGw - checks if node is acting as internet gw
+func IsInternetGw(node models.Node) bool {
+	return node.IsInternetGateway
+}
 
 
 // GetInternetGateways - gets all the nodes that are internet gateways
 // GetInternetGateways - gets all the nodes that are internet gateways
 func GetInternetGateways() ([]models.Node, error) {
 func GetInternetGateways() ([]models.Node, error) {
@@ -27,13 +23,8 @@ func GetInternetGateways() ([]models.Node, error) {
 	}
 	}
 	igs := make([]models.Node, 0)
 	igs := make([]models.Node, 0)
 	for _, node := range nodes {
 	for _, node := range nodes {
-		if !node.IsEgressGateway {
-			continue
-		}
-		for _, ran := range node.EgressGatewayRanges {
-			if ran == "0.0.0.0/0" {
-				igs = append(igs, node)
-			}
+		if node.IsInternetGateway {
+			igs = append(igs, node)
 		}
 		}
 	}
 	}
 	return igs, nil
 	return igs, nil
@@ -106,6 +97,9 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 	if err != nil {
 	if err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
 	}
 	}
+	if gateway.Ranges == nil {
+		gateway.Ranges = make([]string, 0)
+	}
 	node.IsEgressGateway = true
 	node.IsEgressGateway = true
 	node.EgressGatewayRanges = gateway.Ranges
 	node.EgressGatewayRanges = gateway.Ranges
 	node.EgressGatewayNatEnabled = models.ParseBool(gateway.NatEnabled)
 	node.EgressGatewayNatEnabled = models.ParseBool(gateway.NatEnabled)
@@ -119,13 +113,7 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 
 
 // ValidateEgressGateway - validates the egress gateway model
 // ValidateEgressGateway - validates the egress gateway model
 func ValidateEgressGateway(gateway models.EgressGatewayRequest) error {
 func ValidateEgressGateway(gateway models.EgressGatewayRequest) error {
-	var err error
-
-	empty := len(gateway.Ranges) == 0
-	if empty {
-		err = errors.New("IP Ranges Cannot Be Empty")
-	}
-	return err
+	return nil
 }
 }
 
 
 // DeleteEgressGateway - deletes egress from node
 // DeleteEgressGateway - deletes egress from node
@@ -170,11 +158,16 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
 		return models.Node{}, err
 		return models.Node{}, err
 	}
 	}
 	node.IsIngressGateway = true
 	node.IsIngressGateway = true
-	SetInternetGw(&node, ingress.IsInternetGateway)
+	if !servercfg.IsPro {
+		node.IsInternetGateway = ingress.IsInternetGateway
+	}
 	node.IngressGatewayRange = network.AddressRange
 	node.IngressGatewayRange = network.AddressRange
 	node.IngressGatewayRange6 = network.AddressRange6
 	node.IngressGatewayRange6 = network.AddressRange6
 	node.IngressDNS = ingress.ExtclientDNS
 	node.IngressDNS = ingress.ExtclientDNS
 	node.SetLastModified()
 	node.SetLastModified()
+	if node.Metadata == "" {
+		node.Metadata = "This host can be used for remote access"
+	}
 	err = UpsertNode(&node)
 	err = UpsertNode(&node)
 	if err != nil {
 	if err != nil {
 		return models.Node{}, err
 		return models.Node{}, err
@@ -223,8 +216,11 @@ func DeleteIngressGateway(nodeid string) (models.Node, []models.ExtClient, error
 	logger.Log(3, "deleting ingress gateway")
 	logger.Log(3, "deleting ingress gateway")
 	node.LastModified = time.Now()
 	node.LastModified = time.Now()
 	node.IsIngressGateway = false
 	node.IsIngressGateway = false
-	node.IsInternetGateway = false
+	if !servercfg.IsPro {
+		node.IsInternetGateway = false
+	}
 	node.IngressGatewayRange = ""
 	node.IngressGatewayRange = ""
+	node.Metadata = ""
 	err = UpsertNode(&node)
 	err = UpsertNode(&node)
 	if err != nil {
 	if err != nil {
 		return models.Node{}, removedClients, err
 		return models.Node{}, removedClients, err

+ 30 - 0
logic/nodes.go

@@ -218,6 +218,23 @@ func DeleteNode(node *models.Node, purge bool) error {
 		// unset all the relayed nodes
 		// unset all the relayed nodes
 		SetRelayedNodes(false, node.ID.String(), node.RelayedNodes)
 		SetRelayedNodes(false, node.ID.String(), node.RelayedNodes)
 	}
 	}
+	if node.InternetGwID != "" {
+		inetNode, err := GetNodeByID(node.InternetGwID)
+		if err == nil {
+			clientNodeIDs := []string{}
+			for _, inetNodeClientID := range inetNode.InetNodeReq.InetNodeClientIDs {
+				if inetNodeClientID == node.ID.String() {
+					continue
+				}
+				clientNodeIDs = append(clientNodeIDs, inetNodeClientID)
+			}
+			inetNode.InetNodeReq.InetNodeClientIDs = clientNodeIDs
+			UpsertNode(&inetNode)
+		}
+	}
+	if node.IsInternetGateway {
+		UnsetInternetGw(node)
+	}
 
 
 	if !purge && !alreadyDeleted {
 	if !purge && !alreadyDeleted {
 		newnode := *node
 		newnode := *node
@@ -598,3 +615,16 @@ func SortApiNodes(unsortedNodes []models.ApiNode) {
 		return unsortedNodes[i].ID < unsortedNodes[j].ID
 		return unsortedNodes[i].ID < unsortedNodes[j].ID
 	})
 	})
 }
 }
+
+func ValidateParams(nodeid, netid string) (models.Node, error) {
+	node, err := GetNodeByID(nodeid)
+	if err != nil {
+		slog.Error("error fetching node", "node", nodeid, "error", err.Error())
+		return node, fmt.Errorf("error fetching node during parameter validation: %v", err)
+	}
+	if node.Network != netid {
+		slog.Error("network url param does not match node id", "url nodeid", netid, "node", node.Network)
+		return node, fmt.Errorf("network url param does not match node network")
+	}
+	return node, nil
+}

+ 48 - 19
logic/peers.go

@@ -2,6 +2,7 @@ package logic
 
 
 import (
 import (
 	"errors"
 	"errors"
+	"fmt"
 	"net"
 	"net"
 	"net/netip"
 	"net/netip"
 
 
@@ -28,6 +29,30 @@ var (
 	GetFailOverPeerIps = func(peer, node *models.Node) []net.IPNet {
 	GetFailOverPeerIps = func(peer, node *models.Node) []net.IPNet {
 		return []net.IPNet{}
 		return []net.IPNet{}
 	}
 	}
+	// CreateFailOver - creates failover in a network
+	CreateFailOver = func(node models.Node) error {
+		return nil
+	}
+
+	// SetDefaulGw
+	SetDefaultGw = func(node models.Node, peerUpdate models.HostPeerUpdate) models.HostPeerUpdate {
+		return peerUpdate
+	}
+	SetDefaultGwForRelayedUpdate = func(relayed, relay models.Node, peerUpdate models.HostPeerUpdate) models.HostPeerUpdate {
+		return peerUpdate
+	}
+	// UnsetInternetGw
+	UnsetInternetGw = func(node *models.Node) {
+		node.IsInternetGateway = false
+	}
+	// SetInternetGw
+	SetInternetGw = func(node *models.Node, req models.InetNodeReq) {
+		node.IsInternetGateway = true
+	}
+	// GetAllowedIpForInetNodeClient
+	GetAllowedIpForInetNodeClient = func(node, peer *models.Node) []net.IPNet {
+		return []net.IPNet{}
+	}
 )
 )
 
 
 // GetPeerUpdateForHost - gets the consolidated peer update for the host from all networks
 // GetPeerUpdateForHost - gets the consolidated peer update for the host from all networks
@@ -118,7 +143,10 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 			}
 			}
 			continue
 			continue
 		}
 		}
-
+		hostPeerUpdate = SetDefaultGw(node, hostPeerUpdate)
+		if !hostPeerUpdate.IsInternetGw {
+			hostPeerUpdate.IsInternetGw = IsInternetGw(node)
+		}
 		currentPeers := GetNetworkNodesMemory(allNodes, node.Network)
 		currentPeers := GetNetworkNodesMemory(allNodes, node.Network)
 		for _, peer := range currentPeers {
 		for _, peer := range currentPeers {
 			peer := peer
 			peer := peer
@@ -160,6 +188,9 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 					peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
 					peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
 					continue
 					continue
 				}
 				}
+				if node.IsRelayed && node.RelayedBy == peer.ID.String() {
+					hostPeerUpdate = SetDefaultGwForRelayedUpdate(node, peer, hostPeerUpdate)
+				}
 			}
 			}
 
 
 			uselocal := false
 			uselocal := false
@@ -247,18 +278,8 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 				logger.Log(1, "error retrieving external clients:", err.Error())
 				logger.Log(1, "error retrieving external clients:", err.Error())
 			}
 			}
 		}
 		}
-		addedInetGwRanges := false
 		if node.IsEgressGateway && node.EgressGatewayRequest.NatEnabled == "yes" && len(node.EgressGatewayRequest.Ranges) > 0 {
 		if node.IsEgressGateway && node.EgressGatewayRequest.NatEnabled == "yes" && len(node.EgressGatewayRequest.Ranges) > 0 {
 			hostPeerUpdate.FwUpdate.IsEgressGw = true
 			hostPeerUpdate.FwUpdate.IsEgressGw = true
-			if IsInternetGw(node) {
-				hostPeerUpdate.FwUpdate.IsEgressGw = true
-				egressrange := []string{"0.0.0.0/0"}
-				if node.Address6.IP != nil {
-					egressrange = append(egressrange, "::/0")
-				}
-				node.EgressGatewayRequest.Ranges = append(node.EgressGatewayRequest.Ranges, egressrange...)
-				addedInetGwRanges = true
-			}
 			hostPeerUpdate.FwUpdate.EgressInfo[node.ID.String()] = models.EgressInfo{
 			hostPeerUpdate.FwUpdate.EgressInfo[node.ID.String()] = models.EgressInfo{
 				EgressID: node.ID.String(),
 				EgressID: node.ID.String(),
 				Network:  node.PrimaryNetworkRange(),
 				Network:  node.PrimaryNetworkRange(),
@@ -270,21 +291,21 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
 			}
 			}
 
 
 		}
 		}
-		if IsInternetGw(node) && !addedInetGwRanges {
+		if IsInternetGw(node) {
 			hostPeerUpdate.FwUpdate.IsEgressGw = true
 			hostPeerUpdate.FwUpdate.IsEgressGw = true
 			egressrange := []string{"0.0.0.0/0"}
 			egressrange := []string{"0.0.0.0/0"}
 			if node.Address6.IP != nil {
 			if node.Address6.IP != nil {
 				egressrange = append(egressrange, "::/0")
 				egressrange = append(egressrange, "::/0")
 			}
 			}
-			hostPeerUpdate.FwUpdate.EgressInfo[node.ID.String()] = models.EgressInfo{
-				EgressID: node.ID.String(),
+			hostPeerUpdate.FwUpdate.EgressInfo[fmt.Sprintf("%s-%s", node.ID.String(), "inet")] = models.EgressInfo{
+				EgressID: fmt.Sprintf("%s-%s", node.ID.String(), "inet"),
 				Network:  node.PrimaryAddressIPNet(),
 				Network:  node.PrimaryAddressIPNet(),
 				EgressGwAddr: net.IPNet{
 				EgressGwAddr: net.IPNet{
 					IP:   net.ParseIP(node.PrimaryAddress()),
 					IP:   net.ParseIP(node.PrimaryAddress()),
 					Mask: getCIDRMaskFromAddr(node.PrimaryAddress()),
 					Mask: getCIDRMaskFromAddr(node.PrimaryAddress()),
 				},
 				},
 				EgressGWCfg: models.EgressGatewayRequest{
 				EgressGWCfg: models.EgressGatewayRequest{
-					NodeID:     node.ID.String(),
+					NodeID:     fmt.Sprintf("%s-%s", node.ID.String(), "inet"),
 					NetID:      node.Network,
 					NetID:      node.Network,
 					NatEnabled: "yes",
 					NatEnabled: "yes",
 					Ranges:     egressrange,
 					Ranges:     egressrange,
@@ -350,7 +371,17 @@ func GetPeerListenPort(host *models.Host) int {
 // GetAllowedIPs - calculates the wireguard allowedip field for a peer of a node based on the peer and node settings
 // GetAllowedIPs - calculates the wireguard allowedip field for a peer of a node based on the peer and node settings
 func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics) []net.IPNet {
 func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics) []net.IPNet {
 	var allowedips []net.IPNet
 	var allowedips []net.IPNet
-	allowedips = getNodeAllowedIPs(peer, node)
+	if peer.IsInternetGateway && node.InternetGwID == peer.ID.String() {
+		allowedips = append(allowedips, GetAllowedIpForInetNodeClient(node, peer)...)
+		return allowedips
+	}
+	if node.IsRelayed && node.RelayedBy == peer.ID.String() {
+		allowedips = append(allowedips, GetAllowedIpsForRelayed(node, peer)...)
+		if peer.InternetGwID != "" {
+			return allowedips
+		}
+	}
+	allowedips = append(allowedips, getNodeAllowedIPs(peer, node)...)
 
 
 	// handle ingress gateway peers
 	// handle ingress gateway peers
 	if peer.IsIngressGateway {
 	if peer.IsIngressGateway {
@@ -362,9 +393,7 @@ func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics) []net.IPNet
 			allowedips = append(allowedips, extPeer.AllowedIPs...)
 			allowedips = append(allowedips, extPeer.AllowedIPs...)
 		}
 		}
 	}
 	}
-	if node.IsRelayed && node.RelayedBy == peer.ID.String() {
-		allowedips = append(allowedips, GetAllowedIpsForRelayed(node, peer)...)
-	}
+
 	return allowedips
 	return allowedips
 }
 }
 
 

+ 2 - 1
logic/relay.go

@@ -1,8 +1,9 @@
 package logic
 package logic
 
 
 import (
 import (
-	"github.com/gravitl/netmaker/models"
 	"net"
 	"net"
+
+	"github.com/gravitl/netmaker/models"
 )
 )
 
 
 var GetRelays = func() ([]models.Node, error) {
 var GetRelays = func() ([]models.Node, error) {

+ 1 - 1
main.go

@@ -28,7 +28,7 @@ import (
 	"golang.org/x/exp/slog"
 	"golang.org/x/exp/slog"
 )
 )
 
 
-var version = "v0.22.0"
+var version = "v0.23.0"
 
 
 // Start DB Connection and start API Request Handler
 // Start DB Connection and start API Request Handler
 func main() {
 func main() {

+ 125 - 0
migrate/migrate.go

@@ -2,6 +2,7 @@ package migrate
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
+	"fmt"
 	"log"
 	"log"
 
 
 	"golang.org/x/exp/slog"
 	"golang.org/x/exp/slog"
@@ -9,6 +10,7 @@ import (
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/logic/acls"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 )
 )
@@ -19,6 +21,7 @@ func Run() {
 	assignSuperAdmin()
 	assignSuperAdmin()
 	updateHosts()
 	updateHosts()
 	updateNodes()
 	updateNodes()
+	updateAcls()
 }
 }
 
 
 func assignSuperAdmin() {
 func assignSuperAdmin() {
@@ -167,3 +170,125 @@ func removeInterGw(egressRanges []string) ([]string, bool) {
 	}
 	}
 	return egressRanges, update
 	return egressRanges, update
 }
 }
+
+func updateAcls() {
+	// get all networks
+	networks, err := logic.GetNetworks()
+	if err != nil {
+		slog.Error("acls migration failed. error getting networks", "error", err)
+		return
+	}
+
+	// get current acls per network
+	for _, network := range networks {
+		var networkAcl acls.ACLContainer
+		networkAcl, err := networkAcl.Get(acls.ContainerID(network.NetID))
+		if err != nil {
+			if database.IsEmptyRecord(err) {
+				continue
+			}
+			slog.Error(fmt.Sprintf("error during acls migration. error getting acls for network: %s", network.NetID), "error", err)
+			continue
+		}
+		// convert old acls to new acls with clients
+		// TODO: optimise O(n^2) operation
+		clients, err := logic.GetNetworkExtClients(network.NetID)
+		if err != nil {
+			slog.Error(fmt.Sprintf("error during acls migration. error getting clients for network: %s", network.NetID), "error", err)
+			continue
+		}
+		clientsIdMap := make(map[string]struct{})
+		for _, client := range clients {
+			clientsIdMap[client.ClientID] = struct{}{}
+		}
+		nodeIdsMap := make(map[string]struct{})
+		for nodeId := range networkAcl {
+			nodeIdsMap[string(nodeId)] = struct{}{}
+		}
+		/*
+			initially, networkACL has only node acls so we add client acls to it
+			final shape:
+			{
+				"node1": {
+					"node2": 2,
+					"client1": 2,
+					"client2": 1,
+				},
+				"node2": {
+					"node1": 2,
+					"client1": 2,
+					"client2": 1,
+				},
+				"client1": {
+					"node1": 2,
+					"node2": 2,
+					"client2": 1,
+				},
+				"client2": {
+					"node1": 1,
+					"node2": 1,
+					"client1": 1,
+				},
+			}
+		*/
+		for _, client := range clients {
+			networkAcl[acls.AclID(client.ClientID)] = acls.ACL{}
+			// add client values to node acls and create client acls with node values
+			for id, nodeAcl := range networkAcl {
+				// skip if not a node
+				if _, ok := nodeIdsMap[string(id)]; !ok {
+					continue
+				}
+				if nodeAcl == nil {
+					slog.Warn("acls migration bad data: nil node acl", "node", id, "network", network.NetID)
+					continue
+				}
+				nodeAcl[acls.AclID(client.ClientID)] = acls.Allowed
+				networkAcl[acls.AclID(client.ClientID)][id] = acls.Allowed
+				if client.DeniedACLs == nil {
+					continue
+				} else if _, ok := client.DeniedACLs[string(id)]; ok {
+					nodeAcl[acls.AclID(client.ClientID)] = acls.NotAllowed
+					networkAcl[acls.AclID(client.ClientID)][id] = acls.NotAllowed
+				}
+			}
+			// add clients to client acls response
+			for _, c := range clients {
+				if c.ClientID == client.ClientID {
+					continue
+				}
+				networkAcl[acls.AclID(client.ClientID)][acls.AclID(c.ClientID)] = acls.Allowed
+				if client.DeniedACLs == nil {
+					continue
+				} else if _, ok := client.DeniedACLs[c.ClientID]; ok {
+					networkAcl[acls.AclID(client.ClientID)][acls.AclID(c.ClientID)] = acls.NotAllowed
+				}
+			}
+			// delete oneself from its own acl
+			delete(networkAcl[acls.AclID(client.ClientID)], acls.AclID(client.ClientID))
+		}
+
+		// remove non-existent client and node acls
+		for objId := range networkAcl {
+			if _, ok := nodeIdsMap[string(objId)]; ok {
+				continue
+			}
+			if _, ok := clientsIdMap[string(objId)]; ok {
+				continue
+			}
+			// remove all occurances of objId from all acls
+			for objId2 := range networkAcl {
+				delete(networkAcl[objId2], objId)
+			}
+			delete(networkAcl, objId)
+		}
+
+		// save new acls
+		slog.Info(fmt.Sprintf("(migration) saving new acls for network: %s", network.NetID), "networkAcl", networkAcl)
+		if _, err := networkAcl.Save(acls.ContainerID(network.NetID)); err != nil {
+			slog.Error(fmt.Sprintf("error during acls migration. error saving new acls for network: %s", network.NetID), "error", err)
+			continue
+		}
+		slog.Info(fmt.Sprintf("(migration) successfully saved new acls for network: %s", network.NetID))
+	}
+}

+ 17 - 12
models/api_node.go

@@ -11,9 +11,9 @@ import (
 type ApiNode struct {
 type ApiNode struct {
 	ID                      string   `json:"id,omitempty" validate:"required,min=5,id_unique"`
 	ID                      string   `json:"id,omitempty" validate:"required,min=5,id_unique"`
 	HostID                  string   `json:"hostid,omitempty" validate:"required,min=5,id_unique"`
 	HostID                  string   `json:"hostid,omitempty" validate:"required,min=5,id_unique"`
-	Address                 string   `json:"address" validate:"omitempty,ipv4"`
-	Address6                string   `json:"address6" validate:"omitempty,ipv6"`
-	LocalAddress            string   `json:"localaddress" validate:"omitempty,ipv4"`
+	Address                 string   `json:"address" validate:"omitempty,cidrv4"`
+	Address6                string   `json:"address6" validate:"omitempty,cidrv6"`
+	LocalAddress            string   `json:"localaddress" validate:"omitempty,cidr"`
 	AllowedIPs              []string `json:"allowedips"`
 	AllowedIPs              []string `json:"allowedips"`
 	LastModified            int64    `json:"lastmodified"`
 	LastModified            int64    `json:"lastmodified"`
 	ExpirationDateTime      int64    `json:"expdatetime"`
 	ExpirationDateTime      int64    `json:"expdatetime"`
@@ -28,20 +28,22 @@ type ApiNode struct {
 	RelayedNodes            []string `json:"relaynodes" yaml:"relayedNodes"`
 	RelayedNodes            []string `json:"relaynodes" yaml:"relayedNodes"`
 	IsEgressGateway         bool     `json:"isegressgateway"`
 	IsEgressGateway         bool     `json:"isegressgateway"`
 	IsIngressGateway        bool     `json:"isingressgateway"`
 	IsIngressGateway        bool     `json:"isingressgateway"`
-	IsInternetGateway       bool     `json:"isinternetgateway" yaml:"isinternetgateway"`
 	EgressGatewayRanges     []string `json:"egressgatewayranges"`
 	EgressGatewayRanges     []string `json:"egressgatewayranges"`
 	EgressGatewayNatEnabled bool     `json:"egressgatewaynatenabled"`
 	EgressGatewayNatEnabled bool     `json:"egressgatewaynatenabled"`
 	DNSOn                   bool     `json:"dnson"`
 	DNSOn                   bool     `json:"dnson"`
 	IngressDns              string   `json:"ingressdns"`
 	IngressDns              string   `json:"ingressdns"`
 	Server                  string   `json:"server"`
 	Server                  string   `json:"server"`
-	InternetGateway         string   `json:"internetgateway"`
 	Connected               bool     `json:"connected"`
 	Connected               bool     `json:"connected"`
 	PendingDelete           bool     `json:"pendingdelete"`
 	PendingDelete           bool     `json:"pendingdelete"`
+	Metadata                string   `json:"metadata" validate:"max=256"`
 	// == PRO ==
 	// == PRO ==
-	DefaultACL    string              `json:"defaultacl,omitempty" validate:"checkyesornoorunset"`
-	IsFailOver    bool                `json:"is_fail_over"`
-	FailOverPeers map[string]struct{} `json:"fail_over_peers" yaml:"fail_over_peers"`
-	FailedOverBy  uuid.UUID           `json:"failed_over_by" yaml:"failed_over_by"`
+	DefaultACL        string              `json:"defaultacl,omitempty" validate:"checkyesornoorunset"`
+	IsFailOver        bool                `json:"is_fail_over"`
+	FailOverPeers     map[string]struct{} `json:"fail_over_peers" yaml:"fail_over_peers"`
+	FailedOverBy      uuid.UUID           `json:"failed_over_by" yaml:"failed_over_by"`
+	IsInternetGateway bool                `json:"isinternetgateway" yaml:"isinternetgateway"`
+	InetNodeReq       InetNodeReq         `json:"inet_node_req" yaml:"inet_node_req"`
+	InternetGwID      string              `json:"internetgw_node_id" yaml:"internetgw_node_id"`
 }
 }
 
 
 // ApiNode.ConvertToServerNode - converts an api node to a server node
 // ApiNode.ConvertToServerNode - converts an api node to a server node
@@ -71,6 +73,8 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node {
 	convertedNode.IsInternetGateway = a.IsInternetGateway
 	convertedNode.IsInternetGateway = a.IsInternetGateway
 	convertedNode.EgressGatewayRequest = currentNode.EgressGatewayRequest
 	convertedNode.EgressGatewayRequest = currentNode.EgressGatewayRequest
 	convertedNode.EgressGatewayNatEnabled = currentNode.EgressGatewayNatEnabled
 	convertedNode.EgressGatewayNatEnabled = currentNode.EgressGatewayNatEnabled
+	convertedNode.InternetGwID = currentNode.InternetGwID
+	convertedNode.InetNodeReq = currentNode.InetNodeReq
 	convertedNode.RelayedNodes = a.RelayedNodes
 	convertedNode.RelayedNodes = a.RelayedNodes
 	convertedNode.DefaultACL = a.DefaultACL
 	convertedNode.DefaultACL = a.DefaultACL
 	convertedNode.OwnerID = currentNode.OwnerID
 	convertedNode.OwnerID = currentNode.OwnerID
@@ -104,6 +108,7 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node {
 	convertedNode.LastCheckIn = time.Unix(a.LastCheckIn, 0)
 	convertedNode.LastCheckIn = time.Unix(a.LastCheckIn, 0)
 	convertedNode.LastPeerUpdate = time.Unix(a.LastPeerUpdate, 0)
 	convertedNode.LastPeerUpdate = time.Unix(a.LastPeerUpdate, 0)
 	convertedNode.ExpirationDateTime = time.Unix(a.ExpirationDateTime, 0)
 	convertedNode.ExpirationDateTime = time.Unix(a.ExpirationDateTime, 0)
+	convertedNode.Metadata = a.Metadata
 	return &convertedNode
 	return &convertedNode
 }
 }
 
 
@@ -148,16 +153,16 @@ func (nm *Node) ConvertToAPINode() *ApiNode {
 	apiNode.DNSOn = nm.DNSOn
 	apiNode.DNSOn = nm.DNSOn
 	apiNode.IngressDns = nm.IngressDNS
 	apiNode.IngressDns = nm.IngressDNS
 	apiNode.Server = nm.Server
 	apiNode.Server = nm.Server
-	if isEmptyAddr(apiNode.InternetGateway) {
-		apiNode.InternetGateway = ""
-	}
 	apiNode.Connected = nm.Connected
 	apiNode.Connected = nm.Connected
 	apiNode.PendingDelete = nm.PendingDelete
 	apiNode.PendingDelete = nm.PendingDelete
 	apiNode.DefaultACL = nm.DefaultACL
 	apiNode.DefaultACL = nm.DefaultACL
 	apiNode.IsInternetGateway = nm.IsInternetGateway
 	apiNode.IsInternetGateway = nm.IsInternetGateway
+	apiNode.InternetGwID = nm.InternetGwID
+	apiNode.InetNodeReq = nm.InetNodeReq
 	apiNode.IsFailOver = nm.IsFailOver
 	apiNode.IsFailOver = nm.IsFailOver
 	apiNode.FailOverPeers = nm.FailOverPeers
 	apiNode.FailOverPeers = nm.FailOverPeers
 	apiNode.FailedOverBy = nm.FailedOverBy
 	apiNode.FailedOverBy = nm.FailedOverBy
+	apiNode.Metadata = nm.Metadata
 	return &apiNode
 	return &apiNode
 }
 }
 
 

+ 4 - 0
models/extclient.go

@@ -18,6 +18,8 @@ type ExtClient struct {
 	OwnerID                string              `json:"ownerid" bson:"ownerid"`
 	OwnerID                string              `json:"ownerid" bson:"ownerid"`
 	DeniedACLs             map[string]struct{} `json:"deniednodeacls" bson:"acls,omitempty"`
 	DeniedACLs             map[string]struct{} `json:"deniednodeacls" bson:"acls,omitempty"`
 	RemoteAccessClientID   string              `json:"remote_access_client_id"` // unique ID (MAC address) of RAC machine
 	RemoteAccessClientID   string              `json:"remote_access_client_id"` // unique ID (MAC address) of RAC machine
+	PostUp                 string              `json:"postup" bson:"postup"`
+	PostDown               string              `json:"postdown" bson:"postdown"`
 }
 }
 
 
 // CustomExtClient - struct for CustomExtClient params
 // CustomExtClient - struct for CustomExtClient params
@@ -29,4 +31,6 @@ type CustomExtClient struct {
 	Enabled              bool                `json:"enabled,omitempty"`
 	Enabled              bool                `json:"enabled,omitempty"`
 	DeniedACLs           map[string]struct{} `json:"deniednodeacls" bson:"acls,omitempty"`
 	DeniedACLs           map[string]struct{} `json:"deniednodeacls" bson:"acls,omitempty"`
 	RemoteAccessClientID string              `json:"remote_access_client_id"` // unique ID (MAC address) of RAC machine
 	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"`
 }
 }

+ 3 - 0
models/mqtt.go

@@ -9,6 +9,9 @@ import (
 // HostPeerUpdate - struct for host peer updates
 // HostPeerUpdate - struct for host peer updates
 type HostPeerUpdate struct {
 type HostPeerUpdate struct {
 	Host            Host                 `json:"host" bson:"host" yaml:"host"`
 	Host            Host                 `json:"host" bson:"host" yaml:"host"`
+	ChangeDefaultGw bool                 `json:"change_default_gw"`
+	DefaultGwIp     net.IP               `json:"default_gw_ip"`
+	IsInternetGw    bool                 `json:"is_inet_gw"`
 	NodeAddrs       []net.IPNet          `json:"nodes_addrs" yaml:"nodes_addrs"`
 	NodeAddrs       []net.IPNet          `json:"nodes_addrs" yaml:"nodes_addrs"`
 	Server          string               `json:"server" bson:"server" yaml:"server"`
 	Server          string               `json:"server" bson:"server" yaml:"server"`
 	ServerVersion   string               `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
 	ServerVersion   string               `json:"serverversion" bson:"serverversion" yaml:"serverversion"`

+ 9 - 6
models/node.go

@@ -66,7 +66,6 @@ type CommonNode struct {
 	IsEgressGateway     bool      `json:"isegressgateway" yaml:"isegressgateway"`
 	IsEgressGateway     bool      `json:"isegressgateway" yaml:"isegressgateway"`
 	EgressGatewayRanges []string  `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
 	EgressGatewayRanges []string  `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
 	IsIngressGateway    bool      `json:"isingressgateway" yaml:"isingressgateway"`
 	IsIngressGateway    bool      `json:"isingressgateway" yaml:"isingressgateway"`
-	IsInternetGateway   bool      `json:"isinternetgateway" yaml:"isinternetgateway"`
 	IsRelayed           bool      `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
 	IsRelayed           bool      `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
 	RelayedBy           string    `json:"relayedby" bson:"relayedby" yaml:"relayedby"`
 	RelayedBy           string    `json:"relayedby" bson:"relayedby" yaml:"relayedby"`
 	IsRelay             bool      `json:"isrelay" bson:"isrelay" yaml:"isrelay"`
 	IsRelay             bool      `json:"isrelay" bson:"isrelay" yaml:"isrelay"`
@@ -87,12 +86,16 @@ type Node struct {
 	EgressGatewayRequest    EgressGatewayRequest `json:"egressgatewayrequest" bson:"egressgatewayrequest" yaml:"egressgatewayrequest"`
 	EgressGatewayRequest    EgressGatewayRequest `json:"egressgatewayrequest" bson:"egressgatewayrequest" yaml:"egressgatewayrequest"`
 	IngressGatewayRange     string               `json:"ingressgatewayrange" bson:"ingressgatewayrange" yaml:"ingressgatewayrange"`
 	IngressGatewayRange     string               `json:"ingressgatewayrange" bson:"ingressgatewayrange" yaml:"ingressgatewayrange"`
 	IngressGatewayRange6    string               `json:"ingressgatewayrange6" bson:"ingressgatewayrange6" yaml:"ingressgatewayrange6"`
 	IngressGatewayRange6    string               `json:"ingressgatewayrange6" bson:"ingressgatewayrange6" yaml:"ingressgatewayrange6"`
+	Metadata                string               `json:"metadata"`
 	// == PRO ==
 	// == PRO ==
-	DefaultACL    string              `json:"defaultacl,omitempty" bson:"defaultacl,omitempty" yaml:"defaultacl,omitempty" validate:"checkyesornoorunset"`
-	OwnerID       string              `json:"ownerid,omitempty" bson:"ownerid,omitempty" yaml:"ownerid,omitempty"`
-	IsFailOver    bool                `json:"is_fail_over" yaml:"is_fail_over"`
-	FailOverPeers map[string]struct{} `json:"fail_over_peers" yaml:"fail_over_peers"`
-	FailedOverBy  uuid.UUID           `json:"failed_over_by" yaml:"failed_over_by"`
+	DefaultACL        string              `json:"defaultacl,omitempty" bson:"defaultacl,omitempty" yaml:"defaultacl,omitempty" validate:"checkyesornoorunset"`
+	OwnerID           string              `json:"ownerid,omitempty" bson:"ownerid,omitempty" yaml:"ownerid,omitempty"`
+	IsFailOver        bool                `json:"is_fail_over" yaml:"is_fail_over"`
+	FailOverPeers     map[string]struct{} `json:"fail_over_peers" yaml:"fail_over_peers"`
+	FailedOverBy      uuid.UUID           `json:"failed_over_by" yaml:"failed_over_by"`
+	IsInternetGateway bool                `json:"isinternetgateway" yaml:"isinternetgateway"`
+	InetNodeReq       InetNodeReq         `json:"inet_node_req" yaml:"inet_node_req"`
+	InternetGwID      string              `json:"internetgw_node_id" yaml:"internetgw_node_id"`
 }
 }
 
 
 // LegacyNode - legacy struct for node model
 // LegacyNode - legacy struct for node model

+ 13 - 0
models/structs.go

@@ -1,6 +1,7 @@
 package models
 package models
 
 
 import (
 import (
+	"net"
 	"strings"
 	"strings"
 	"time"
 	"time"
 
 
@@ -71,6 +72,7 @@ type UserRemoteGws struct {
 	IsInternetGateway bool      `json:"is_internet_gateway"`
 	IsInternetGateway bool      `json:"is_internet_gateway"`
 	GwClient          ExtClient `json:"gw_client"`
 	GwClient          ExtClient `json:"gw_client"`
 	GwPeerPublicKey   string    `json:"gw_peer_public_key"`
 	GwPeerPublicKey   string    `json:"gw_peer_public_key"`
+	Metadata          string    `json:"metadata"`
 }
 }
 
 
 // UserRemoteGwsReq - struct to hold user remote acccess gws req
 // UserRemoteGwsReq - struct to hold user remote acccess gws req
@@ -195,6 +197,11 @@ type IngressRequest struct {
 	IsInternetGateway bool   `json:"is_internet_gw"`
 	IsInternetGateway bool   `json:"is_internet_gw"`
 }
 }
 
 
+// InetNodeReq - exit node request struct
+type InetNodeReq struct {
+	InetNodeClientIDs []string `json:"inet_node_client_ids"`
+}
+
 // ServerUpdateData - contains data to configure server
 // ServerUpdateData - contains data to configure server
 // and if it should set peers
 // and if it should set peers
 type ServerUpdateData struct {
 type ServerUpdateData struct {
@@ -233,6 +240,12 @@ type HostPull struct {
 	HostNetworkInfo HostInfoMap           `json:"host_network_info,omitempty"  yaml:"host_network_info,omitempty"`
 	HostNetworkInfo HostInfoMap           `json:"host_network_info,omitempty"  yaml:"host_network_info,omitempty"`
 	EgressRoutes    []EgressNetworkRoutes `json:"egress_network_routes"`
 	EgressRoutes    []EgressNetworkRoutes `json:"egress_network_routes"`
 	FwUpdate        FwUpdate              `json:"fw_update"`
 	FwUpdate        FwUpdate              `json:"fw_update"`
+	ChangeDefaultGw bool                  `json:"change_default_gw"`
+	DefaultGwIp     net.IP                `json:"default_gw_ip"`
+	IsInternetGw    bool                  `json:"is_inet_gw"`
+}
+
+type DefaultGwInfo struct {
 }
 }
 
 
 // NodeGet - struct for a single node get response
 // NodeGet - struct for a single node get response

+ 29 - 373
mq/emqx.go

@@ -1,386 +1,42 @@
 package mq
 package mq
 
 
-import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"io"
-	"net/http"
-	"strings"
-	"sync"
+import "github.com/gravitl/netmaker/servercfg"
 
 
-	"github.com/gravitl/netmaker/servercfg"
-)
+var emqx Emqx
 
 
-const already_exists = "ALREADY_EXISTS"
-
-type (
-	emqxUser struct {
-		UserID   string `json:"user_id"`
-		Password string `json:"password"`
-		Admin    bool   `json:"is_superuser"`
-	}
-
-	emqxLogin struct {
-		Username string `json:"username"`
-		Password string `json:"password"`
-	}
-
-	emqxLoginResponse struct {
-		License struct {
-			Edition string `json:"edition"`
-		} `json:"license"`
-		Token   string `json:"token"`
-		Version string `json:"version"`
-	}
-
-	aclRule struct {
-		Topic      string `json:"topic"`
-		Permission string `json:"permission"`
-		Action     string `json:"action"`
-	}
-
-	aclObject struct {
-		Rules    []aclRule `json:"rules"`
-		Username string    `json:"username,omitempty"`
-	}
-)
-
-func getEmqxAuthToken() (string, error) {
-	payload, err := json.Marshal(&emqxLogin{
-		Username: servercfg.GetMqUserName(),
-		Password: servercfg.GetMqPassword(),
-	})
-	if err != nil {
-		return "", err
-	}
-	resp, err := http.Post(servercfg.GetEmqxRestEndpoint()+"/api/v5/login", "application/json", bytes.NewReader(payload))
-	if err != nil {
-		return "", err
-	}
-	msg, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return "", err
-	}
-	if resp.StatusCode != http.StatusOK {
-		return "", fmt.Errorf("error during EMQX login %v", string(msg))
-	}
-	var loginResp emqxLoginResponse
-	if err := json.Unmarshal(msg, &loginResp); err != nil {
-		return "", err
-	}
-	return loginResp.Token, nil
-}
-
-// CreateEmqxUser - creates an EMQX user
-func CreateEmqxUser(username, password string, admin bool) error {
-	token, err := getEmqxAuthToken()
-	if err != nil {
-		return err
-	}
-	payload, err := json.Marshal(&emqxUser{
-		UserID:   username,
-		Password: password,
-		Admin:    admin,
-	})
-	if err != nil {
-		return err
-	}
-	req, err := http.NewRequest(http.MethodPost, servercfg.GetEmqxRestEndpoint()+"/api/v5/authentication/password_based:built_in_database/users", bytes.NewReader(payload))
-	if err != nil {
-		return err
-	}
-	req.Header.Add("content-type", "application/json")
-	req.Header.Add("authorization", "Bearer "+token)
-	resp, err := (&http.Client{}).Do(req)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-	if resp.StatusCode >= 300 {
-		msg, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return err
-		}
-		if !strings.Contains(string(msg), already_exists) {
-			return fmt.Errorf("error creating EMQX user %v", string(msg))
-		}
-	}
-	return nil
-}
-
-// DeleteEmqxUser - deletes an EMQX user
-func DeleteEmqxUser(username string) error {
-	token, err := getEmqxAuthToken()
-	if err != nil {
-		return err
-	}
-	req, err := http.NewRequest(http.MethodDelete, servercfg.GetEmqxRestEndpoint()+"/api/v5/authentication/password_based:built_in_database/users/"+username, nil)
-	if err != nil {
-		return err
-	}
-	req.Header.Add("authorization", "Bearer "+token)
-	resp, err := (&http.Client{}).Do(req)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-	if resp.StatusCode >= 300 {
-		msg, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return err
-		}
-		return fmt.Errorf("error deleting EMQX user %v", string(msg))
-	}
-	return nil
-}
-
-// CreateEmqxDefaultAuthenticator - creates a default authenticator based on password and using EMQX's built in database as storage
-func CreateEmqxDefaultAuthenticator() error {
-	token, err := getEmqxAuthToken()
-	if err != nil {
-		return err
-	}
-	payload, err := json.Marshal(&struct {
-		Mechanism  string `json:"mechanism"`
-		Backend    string `json:"backend"`
-		UserIDType string `json:"user_id_type"`
-	}{Mechanism: "password_based", Backend: "built_in_database", UserIDType: "username"})
-	if err != nil {
-		return err
-	}
-	req, err := http.NewRequest(http.MethodPost, servercfg.GetEmqxRestEndpoint()+"/api/v5/authentication", bytes.NewReader(payload))
-	if err != nil {
-		return err
-	}
-	req.Header.Add("content-type", "application/json")
-	req.Header.Add("authorization", "Bearer "+token)
-	resp, err := (&http.Client{}).Do(req)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-	if resp.StatusCode != http.StatusOK {
-		msg, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return err
-		}
-		return fmt.Errorf("error creating default EMQX authenticator %v", string(msg))
-	}
-	return nil
+type Emqx interface {
+	GetType() servercfg.Emqxdeploy
+	CreateEmqxUser(username, password string) error
+	CreateEmqxUserforServer() error
+	CreateEmqxDefaultAuthenticator() error
+	CreateEmqxDefaultAuthorizer() error
+	CreateDefaultDenyRule() error
+	CreateHostACL(hostID, serverName string) error
+	AppendNodeUpdateACL(hostID, nodeNetwork, nodeID, serverName string) error
+	GetUserACL(username string) (*aclObject, error)
+	DeleteEmqxUser(username string) error
 }
 }
 
 
-// CreateEmqxDefaultAuthorizer - creates a default ACL authorization mechanism based on the built in database
-func CreateEmqxDefaultAuthorizer() error {
-	token, err := getEmqxAuthToken()
-	if err != nil {
-		return err
-	}
-	payload, err := json.Marshal(&struct {
-		Enable bool   `json:"enable"`
-		Type   string `json:"type"`
-	}{Enable: true, Type: "built_in_database"})
-	if err != nil {
-		return err
-	}
-	req, err := http.NewRequest(http.MethodPost, servercfg.GetEmqxRestEndpoint()+"/api/v5/authorization/sources", bytes.NewReader(payload))
-	if err != nil {
-		return err
+func init() {
+	if servercfg.GetBrokerType() != servercfg.EmqxBrokerType {
+		return
 	}
 	}
-	req.Header.Add("content-type", "application/json")
-	req.Header.Add("authorization", "Bearer "+token)
-	resp, err := (&http.Client{}).Do(req)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-	if resp.StatusCode != http.StatusNoContent {
-		msg, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return err
+	if servercfg.GetEmqxDeployType() == servercfg.EmqxCloudDeploy {
+		emqx = &EmqxCloud{
+			URL:       servercfg.GetEmqxRestEndpoint(),
+			AppID:     servercfg.GetEmqxAppID(),
+			AppSecret: servercfg.GetEmqxAppSecret(),
 		}
 		}
-		return fmt.Errorf("error creating default EMQX ACL authorization mechanism %v", string(msg))
-	}
-	return nil
-}
-
-// GetUserACL - returns ACL rules by username
-func GetUserACL(username string) (*aclObject, error) {
-	token, err := getEmqxAuthToken()
-	if err != nil {
-		return nil, err
-	}
-	req, err := http.NewRequest(http.MethodGet, servercfg.GetEmqxRestEndpoint()+"/api/v5/authorization/sources/built_in_database/username/"+username, nil)
-	if err != nil {
-		return nil, err
-	}
-	req.Header.Add("content-type", "application/json")
-	req.Header.Add("authorization", "Bearer "+token)
-	resp, err := (&http.Client{}).Do(req)
-	if err != nil {
-		return nil, err
-	}
-	defer resp.Body.Close()
-	response, err := io.ReadAll(resp.Body)
-	if err != nil {
-		return nil, err
-	}
-	if resp.StatusCode != http.StatusOK {
-		return nil, fmt.Errorf("error fetching ACL rules %v", string(response))
-	}
-	body := new(aclObject)
-	if err := json.Unmarshal(response, body); err != nil {
-		return nil, err
-	}
-	return body, nil
-}
-
-// CreateDefaultDenyRule - creates a rule to deny access to all topics for all users by default
-// to allow user access to topics use the `mq.CreateUserAccessRule` function
-func CreateDefaultDenyRule() error {
-	token, err := getEmqxAuthToken()
-	if err != nil {
-		return err
-	}
-	payload, err := json.Marshal(&aclObject{Rules: []aclRule{{Topic: "#", Permission: "deny", Action: "all"}}})
-	if err != nil {
-		return err
-	}
-	req, err := http.NewRequest(http.MethodPost, servercfg.GetEmqxRestEndpoint()+"/api/v5/authorization/sources/built_in_database/all", bytes.NewReader(payload))
-	if err != nil {
-		return err
-	}
-	req.Header.Add("content-type", "application/json")
-	req.Header.Add("authorization", "Bearer "+token)
-	resp, err := (&http.Client{}).Do(req)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-	if resp.StatusCode != http.StatusNoContent {
-		msg, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return err
-		}
-		return fmt.Errorf("error creating default ACL rules %v", string(msg))
-	}
-	return nil
-}
-
-// CreateHostACL - create host ACL rules
-func CreateHostACL(hostID, serverName string) error {
-	token, err := getEmqxAuthToken()
-	if err != nil {
-		return err
-	}
-	payload, err := json.Marshal(&aclObject{
-		Username: hostID,
-		Rules: []aclRule{
-			{
-				Topic:      fmt.Sprintf("peers/host/%s/%s", hostID, serverName),
-				Permission: "allow",
-				Action:     "all",
-			},
-			{
-				Topic:      fmt.Sprintf("host/update/%s/%s", hostID, serverName),
-				Permission: "allow",
-				Action:     "all",
-			},
-			{
-				Topic:      fmt.Sprintf("host/serverupdate/%s/%s", serverName, hostID),
-				Permission: "allow",
-				Action:     "all",
-			},
-		},
-	})
-	if err != nil {
-		return err
-	}
-	req, err := http.NewRequest(http.MethodPut, servercfg.GetEmqxRestEndpoint()+"/api/v5/authorization/sources/built_in_database/username/"+hostID, bytes.NewReader(payload))
-	if err != nil {
-		return err
-	}
-	req.Header.Add("content-type", "application/json")
-	req.Header.Add("authorization", "Bearer "+token)
-	resp, err := (&http.Client{}).Do(req)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-	if resp.StatusCode != http.StatusNoContent {
-		msg, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return err
+	} else {
+		emqx = &EmqxOnPrem{
+			URL:      servercfg.GetEmqxRestEndpoint(),
+			UserName: servercfg.GetMqUserName(),
+			Password: servercfg.GetMqPassword(),
 		}
 		}
-		return fmt.Errorf("error adding ACL Rules for user %s Error: %v", hostID, string(msg))
 	}
 	}
-	return nil
 }
 }
 
 
-// a lock required for preventing simultaneous updates to the same ACL object leading to overwriting each other
-// might occur when multiple nodes belonging to the same host are created at the same time
-var nodeAclMux sync.Mutex
-
-// AppendNodeUpdateACL - adds ACL rule for subscribing to node updates for a node ID
-func AppendNodeUpdateACL(hostID, nodeNetwork, nodeID, serverName string) error {
-	nodeAclMux.Lock()
-	defer nodeAclMux.Unlock()
-	token, err := getEmqxAuthToken()
-	if err != nil {
-		return err
-	}
-	aclObject, err := GetUserACL(hostID)
-	if err != nil {
-		return err
-	}
-	aclObject.Rules = append(aclObject.Rules, []aclRule{
-		{
-			Topic:      fmt.Sprintf("node/update/%s/%s", nodeNetwork, nodeID),
-			Permission: "allow",
-			Action:     "subscribe",
-		},
-		{
-			Topic:      fmt.Sprintf("ping/%s/%s", serverName, nodeID),
-			Permission: "allow",
-			Action:     "all",
-		},
-		{
-			Topic:      fmt.Sprintf("update/%s/%s", serverName, nodeID),
-			Permission: "allow",
-			Action:     "all",
-		},
-		{
-			Topic:      fmt.Sprintf("signal/%s/%s", serverName, nodeID),
-			Permission: "allow",
-			Action:     "all",
-		},
-		{
-			Topic:      fmt.Sprintf("metrics/%s/%s", serverName, nodeID),
-			Permission: "allow",
-			Action:     "all",
-		},
-	}...)
-	payload, err := json.Marshal(aclObject)
-	if err != nil {
-		return err
-	}
-	req, err := http.NewRequest(http.MethodPut, servercfg.GetEmqxRestEndpoint()+"/api/v5/authorization/sources/built_in_database/username/"+hostID, bytes.NewReader(payload))
-	if err != nil {
-		return err
-	}
-	req.Header.Add("content-type", "application/json")
-	req.Header.Add("authorization", "Bearer "+token)
-	resp, err := (&http.Client{}).Do(req)
-	if err != nil {
-		return err
-	}
-	defer resp.Body.Close()
-	if resp.StatusCode != http.StatusNoContent {
-		msg, err := io.ReadAll(resp.Body)
-		if err != nil {
-			return err
-		}
-		return fmt.Errorf("error adding ACL Rules for user %s Error: %v", hostID, string(msg))
-	}
-	return nil
+// GetEmqxHandler - gets emqx handler
+func GetEmqxHandler() Emqx {
+	return emqx
 }
 }

+ 264 - 0
mq/emqx_cloud.go

@@ -0,0 +1,264 @@
+package mq
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+
+	"github.com/gravitl/netmaker/servercfg"
+)
+
+type EmqxCloud struct {
+	URL       string
+	AppID     string
+	AppSecret string
+}
+
+type userCreateReq struct {
+	UserName string `json:"username"`
+	Password string `json:"password"`
+}
+
+type cloudAcl struct {
+	UserName string `json:"username"`
+	Topic    string `json:"topic"`
+	Action   string `json:"action"`
+	Access   string `json:"access"`
+}
+
+func (e *EmqxCloud) GetType() servercfg.Emqxdeploy { return servercfg.EmqxCloudDeploy }
+
+func (e *EmqxCloud) CreateEmqxUser(username, pass string) error {
+
+	payload := userCreateReq{
+		UserName: username,
+		Password: pass,
+	}
+	data, _ := json.Marshal(payload)
+	client := &http.Client{}
+	req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/auth_username", e.URL), strings.NewReader(string(data)))
+	if err != nil {
+		return err
+	}
+	req.SetBasicAuth(e.AppID, e.AppSecret)
+	req.Header.Add("Content-Type", "application/json")
+
+	res, err := client.Do(req)
+	if err != nil {
+		return err
+	}
+	defer res.Body.Close()
+
+	body, err := io.ReadAll(res.Body)
+	if err != nil {
+		return err
+	}
+	if res.StatusCode != http.StatusOK {
+		return errors.New("request failed " + string(body))
+	}
+	return nil
+}
+
+func (e *EmqxCloud) CreateEmqxUserforServer() error {
+	payload := userCreateReq{
+		UserName: servercfg.GetMqUserName(),
+		Password: servercfg.GetMqPassword(),
+	}
+	data, _ := json.Marshal(payload)
+	client := &http.Client{}
+	req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/auth_username", e.URL), strings.NewReader(string(data)))
+	if err != nil {
+		return err
+	}
+	req.SetBasicAuth(e.AppID, e.AppSecret)
+	req.Header.Add("Content-Type", "application/json")
+
+	res, err := client.Do(req)
+	if err != nil {
+		return err
+	}
+	defer res.Body.Close()
+
+	body, err := io.ReadAll(res.Body)
+	if err != nil {
+		return err
+	}
+	if res.StatusCode != http.StatusOK {
+		return errors.New("request failed " + string(body))
+	}
+	// add acls
+	acls := []cloudAcl{
+		{
+			UserName: servercfg.GetMqUserName(),
+			Topic:    fmt.Sprintf("update/%s/#", servercfg.GetServer()),
+			Access:   "allow",
+			Action:   "sub",
+		},
+		{
+			UserName: servercfg.GetMqUserName(),
+			Topic:    fmt.Sprintf("host/serverupdate/%s/#", servercfg.GetServer()),
+			Access:   "allow",
+			Action:   "sub",
+		},
+		{
+			UserName: servercfg.GetMqUserName(),
+			Topic:    fmt.Sprintf("signal/%s/#", servercfg.GetServer()),
+			Access:   "allow",
+			Action:   "sub",
+		},
+		{
+			UserName: servercfg.GetMqUserName(),
+			Topic:    fmt.Sprintf("metrics/%s/#", servercfg.GetServer()),
+			Access:   "allow",
+			Action:   "sub",
+		},
+		{
+			UserName: servercfg.GetMqUserName(),
+			Topic:    "peers/host/#",
+			Access:   "allow",
+			Action:   "pub",
+		},
+		{
+			UserName: servercfg.GetMqUserName(),
+			Topic:    "node/update/#",
+			Access:   "allow",
+			Action:   "pub",
+		},
+		{
+
+			UserName: servercfg.GetMqUserName(),
+			Topic:    "host/update/#",
+			Access:   "allow",
+			Action:   "pub",
+		},
+	}
+
+	return e.createacls(acls)
+}
+
+func (e *EmqxCloud) CreateEmqxDefaultAuthenticator() error { return nil } // ignore
+
+func (e *EmqxCloud) CreateEmqxDefaultAuthorizer() error { return nil } // ignore
+
+func (e *EmqxCloud) CreateDefaultDenyRule() error {
+	return nil
+}
+
+func (e *EmqxCloud) createacls(acls []cloudAcl) error {
+	payload, err := json.Marshal(acls)
+	if err != nil {
+		return err
+	}
+	client := &http.Client{}
+	req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/acl", e.URL), strings.NewReader(string(payload)))
+	if err != nil {
+		return err
+	}
+	req.Header.Add("Content-Type", "application/json")
+	req.SetBasicAuth(e.AppID, e.AppSecret)
+	res, err := client.Do(req)
+	if err != nil {
+		return err
+	}
+	defer res.Body.Close()
+
+	body, err := io.ReadAll(res.Body)
+	if err != nil {
+		return err
+	}
+	if res.StatusCode != http.StatusOK {
+		return errors.New("request failed " + string(body))
+	}
+	return nil
+}
+
+func (e *EmqxCloud) CreateHostACL(hostID, serverName string) error {
+	acls := []cloudAcl{
+		{
+			UserName: hostID,
+			Topic:    fmt.Sprintf("peers/host/%s/%s", hostID, serverName),
+			Access:   "allow",
+			Action:   "sub",
+		},
+		{
+			UserName: hostID,
+			Topic:    fmt.Sprintf("host/update/%s/%s", hostID, serverName),
+			Access:   "allow",
+			Action:   "sub",
+		},
+		{
+			UserName: hostID,
+			Topic:    fmt.Sprintf("host/serverupdate/%s/%s", serverName, hostID),
+			Access:   "allow",
+			Action:   "pub",
+		},
+	}
+
+	return e.createacls(acls)
+}
+
+func (e *EmqxCloud) AppendNodeUpdateACL(hostID, nodeNetwork, nodeID, serverName string) error {
+	acls := []cloudAcl{
+		{
+			UserName: hostID,
+			Topic:    fmt.Sprintf("node/update/%s/%s", nodeNetwork, nodeID),
+			Access:   "allow",
+			Action:   "sub",
+		},
+		{
+			UserName: hostID,
+			Topic:    fmt.Sprintf("ping/%s/%s", serverName, nodeID),
+			Access:   "allow",
+			Action:   "pubsub",
+		},
+		{
+			UserName: hostID,
+			Topic:    fmt.Sprintf("update/%s/%s", serverName, nodeID),
+			Access:   "allow",
+			Action:   "pubsub",
+		},
+		{
+			UserName: hostID,
+			Topic:    fmt.Sprintf("signal/%s/%s", serverName, nodeID),
+			Access:   "allow",
+			Action:   "pubsub",
+		},
+		{
+			UserName: hostID,
+			Topic:    fmt.Sprintf("metrics/%s/%s", serverName, nodeID),
+			Access:   "allow",
+			Action:   "pubsub",
+		},
+	}
+
+	return e.createacls(acls)
+}
+
+func (e *EmqxCloud) GetUserACL(username string) (*aclObject, error) { return nil, nil } // ununsed on cloud since it doesn't overwrite acls list
+
+func (e *EmqxCloud) DeleteEmqxUser(username string) error {
+
+	client := &http.Client{}
+	req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/api/auth_username/%s", e.URL, username), nil)
+	if err != nil {
+		return err
+	}
+	req.SetBasicAuth(e.AppID, e.AppSecret)
+	res, err := client.Do(req)
+	if err != nil {
+		return err
+	}
+	defer res.Body.Close()
+
+	body, err := io.ReadAll(res.Body)
+	if err != nil {
+		return err
+	}
+	if res.StatusCode != http.StatusOK {
+		return errors.New("request failed " + string(body))
+	}
+	return nil
+}

+ 428 - 0
mq/emqx_on_prem.go

@@ -0,0 +1,428 @@
+package mq
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+	"sync"
+
+	"github.com/gravitl/netmaker/servercfg"
+)
+
+type EmqxOnPrem struct {
+	URL      string
+	UserName string
+	Password string
+}
+
+const already_exists = "ALREADY_EXISTS"
+
+type (
+	emqxUser struct {
+		UserID   string `json:"user_id"`
+		Password string `json:"password"`
+		Admin    bool   `json:"is_superuser"`
+	}
+
+	emqxLogin struct {
+		Username string `json:"username"`
+		Password string `json:"password"`
+	}
+
+	emqxLoginResponse struct {
+		License struct {
+			Edition string `json:"edition"`
+		} `json:"license"`
+		Token   string `json:"token"`
+		Version string `json:"version"`
+	}
+
+	aclRule struct {
+		Topic      string `json:"topic"`
+		Permission string `json:"permission"`
+		Action     string `json:"action"`
+	}
+
+	aclObject struct {
+		Rules    []aclRule `json:"rules"`
+		Username string    `json:"username,omitempty"`
+	}
+)
+
+func getEmqxAuthToken() (string, error) {
+	payload, err := json.Marshal(&emqxLogin{
+		Username: servercfg.GetMqUserName(),
+		Password: servercfg.GetMqPassword(),
+	})
+	if err != nil {
+		return "", err
+	}
+	resp, err := http.Post(servercfg.GetEmqxRestEndpoint()+"/api/v5/login", "application/json", bytes.NewReader(payload))
+	if err != nil {
+		return "", err
+	}
+	msg, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return "", err
+	}
+	if resp.StatusCode != http.StatusOK {
+		return "", fmt.Errorf("error during EMQX login %v", string(msg))
+	}
+	var loginResp emqxLoginResponse
+	if err := json.Unmarshal(msg, &loginResp); err != nil {
+		return "", err
+	}
+	return loginResp.Token, nil
+}
+
+func (e *EmqxOnPrem) GetType() servercfg.Emqxdeploy { return servercfg.EmqxOnPremDeploy }
+
+// CreateEmqxUser - creates an EMQX user
+func (e *EmqxOnPrem) CreateEmqxUser(username, password string) error {
+	token, err := getEmqxAuthToken()
+	if err != nil {
+		return err
+	}
+	payload, err := json.Marshal(&emqxUser{
+		UserID:   username,
+		Password: password,
+	})
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest(http.MethodPost, servercfg.GetEmqxRestEndpoint()+"/api/v5/authentication/password_based:built_in_database/users", bytes.NewReader(payload))
+	if err != nil {
+		return err
+	}
+	req.Header.Add("content-type", "application/json")
+	req.Header.Add("authorization", "Bearer "+token)
+	resp, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode >= 300 {
+		msg, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		if !strings.Contains(string(msg), already_exists) {
+			return fmt.Errorf("error creating EMQX user %v", string(msg))
+		}
+	}
+	return nil
+}
+func (e *EmqxOnPrem) CreateEmqxUserforServer() error {
+	token, err := getEmqxAuthToken()
+	if err != nil {
+		return err
+	}
+	payload, err := json.Marshal(&emqxUser{
+		UserID:   servercfg.GetMqUserName(),
+		Password: servercfg.GetMqPassword(),
+		Admin:    true,
+	})
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest(http.MethodPost, servercfg.GetEmqxRestEndpoint()+"/api/v5/authentication/password_based:built_in_database/users", bytes.NewReader(payload))
+	if err != nil {
+		return err
+	}
+	req.Header.Add("content-type", "application/json")
+	req.Header.Add("authorization", "Bearer "+token)
+	resp, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode >= 300 {
+		msg, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		if !strings.Contains(string(msg), already_exists) {
+			return fmt.Errorf("error creating EMQX user %v", string(msg))
+		}
+	}
+	return nil
+}
+
+// DeleteEmqxUser - deletes an EMQX user
+func (e *EmqxOnPrem) DeleteEmqxUser(username string) error {
+	token, err := getEmqxAuthToken()
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest(http.MethodDelete, servercfg.GetEmqxRestEndpoint()+"/api/v5/authentication/password_based:built_in_database/users/"+username, nil)
+	if err != nil {
+		return err
+	}
+	req.Header.Add("authorization", "Bearer "+token)
+	resp, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode >= 300 {
+		msg, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		return fmt.Errorf("error deleting EMQX user %v", string(msg))
+	}
+	return nil
+}
+
+// CreateEmqxDefaultAuthenticator - creates a default authenticator based on password and using EMQX's built in database as storage
+func (e *EmqxOnPrem) CreateEmqxDefaultAuthenticator() error {
+	token, err := getEmqxAuthToken()
+	if err != nil {
+		return err
+	}
+	payload, err := json.Marshal(&struct {
+		Mechanism  string `json:"mechanism"`
+		Backend    string `json:"backend"`
+		UserIDType string `json:"user_id_type"`
+	}{Mechanism: "password_based", Backend: "built_in_database", UserIDType: "username"})
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest(http.MethodPost, servercfg.GetEmqxRestEndpoint()+"/api/v5/authentication", bytes.NewReader(payload))
+	if err != nil {
+		return err
+	}
+	req.Header.Add("content-type", "application/json")
+	req.Header.Add("authorization", "Bearer "+token)
+	resp, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != http.StatusOK {
+		msg, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		return fmt.Errorf("error creating default EMQX authenticator %v", string(msg))
+	}
+	return nil
+}
+
+// CreateEmqxDefaultAuthorizer - creates a default ACL authorization mechanism based on the built in database
+func (e *EmqxOnPrem) CreateEmqxDefaultAuthorizer() error {
+	token, err := getEmqxAuthToken()
+	if err != nil {
+		return err
+	}
+	payload, err := json.Marshal(&struct {
+		Enable bool   `json:"enable"`
+		Type   string `json:"type"`
+	}{Enable: true, Type: "built_in_database"})
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest(http.MethodPost, servercfg.GetEmqxRestEndpoint()+"/api/v5/authorization/sources", bytes.NewReader(payload))
+	if err != nil {
+		return err
+	}
+	req.Header.Add("content-type", "application/json")
+	req.Header.Add("authorization", "Bearer "+token)
+	resp, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != http.StatusNoContent {
+		msg, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		return fmt.Errorf("error creating default EMQX ACL authorization mechanism %v", string(msg))
+	}
+	return nil
+}
+
+// GetUserACL - returns ACL rules by username
+func (e *EmqxOnPrem) GetUserACL(username string) (*aclObject, error) {
+	token, err := getEmqxAuthToken()
+	if err != nil {
+		return nil, err
+	}
+	req, err := http.NewRequest(http.MethodGet, servercfg.GetEmqxRestEndpoint()+"/api/v5/authorization/sources/built_in_database/username/"+username, nil)
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Add("content-type", "application/json")
+	req.Header.Add("authorization", "Bearer "+token)
+	resp, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	response, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+	if resp.StatusCode != http.StatusOK {
+		return nil, fmt.Errorf("error fetching ACL rules %v", string(response))
+	}
+	body := new(aclObject)
+	if err := json.Unmarshal(response, body); err != nil {
+		return nil, err
+	}
+	return body, nil
+}
+
+// CreateDefaultDenyRule - creates a rule to deny access to all topics for all users by default
+// to allow user access to topics use the `mq.CreateUserAccessRule` function
+func (e *EmqxOnPrem) CreateDefaultDenyRule() error {
+	token, err := getEmqxAuthToken()
+	if err != nil {
+		return err
+	}
+	payload, err := json.Marshal(&aclObject{Rules: []aclRule{{Topic: "#", Permission: "deny", Action: "all"}}})
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest(http.MethodPost, servercfg.GetEmqxRestEndpoint()+"/api/v5/authorization/sources/built_in_database/all", bytes.NewReader(payload))
+	if err != nil {
+		return err
+	}
+	req.Header.Add("content-type", "application/json")
+	req.Header.Add("authorization", "Bearer "+token)
+	resp, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != http.StatusNoContent {
+		msg, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		return fmt.Errorf("error creating default ACL rules %v", string(msg))
+	}
+	return nil
+}
+
+// CreateHostACL - create host ACL rules
+func (e *EmqxOnPrem) CreateHostACL(hostID, serverName string) error {
+	token, err := getEmqxAuthToken()
+	if err != nil {
+		return err
+	}
+	payload, err := json.Marshal(&aclObject{
+		Username: hostID,
+		Rules: []aclRule{
+			{
+				Topic:      fmt.Sprintf("peers/host/%s/%s", hostID, serverName),
+				Permission: "allow",
+				Action:     "all",
+			},
+			{
+				Topic:      fmt.Sprintf("host/update/%s/%s", hostID, serverName),
+				Permission: "allow",
+				Action:     "all",
+			},
+			{
+				Topic:      fmt.Sprintf("host/serverupdate/%s/%s", serverName, hostID),
+				Permission: "allow",
+				Action:     "all",
+			},
+		},
+	})
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest(http.MethodPut, servercfg.GetEmqxRestEndpoint()+"/api/v5/authorization/sources/built_in_database/username/"+hostID, bytes.NewReader(payload))
+	if err != nil {
+		return err
+	}
+	req.Header.Add("content-type", "application/json")
+	req.Header.Add("authorization", "Bearer "+token)
+	resp, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != http.StatusNoContent {
+		msg, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		return fmt.Errorf("error adding ACL Rules for user %s Error: %v", hostID, string(msg))
+	}
+	return nil
+}
+
+// a lock required for preventing simultaneous updates to the same ACL object leading to overwriting each other
+// might occur when multiple nodes belonging to the same host are created at the same time
+var nodeAclMux sync.Mutex
+
+// AppendNodeUpdateACL - adds ACL rule for subscribing to node updates for a node ID
+func (e *EmqxOnPrem) AppendNodeUpdateACL(hostID, nodeNetwork, nodeID, serverName string) error {
+	nodeAclMux.Lock()
+	defer nodeAclMux.Unlock()
+	token, err := getEmqxAuthToken()
+	if err != nil {
+		return err
+	}
+	aclObject, err := emqx.GetUserACL(hostID)
+	if err != nil {
+		return err
+	}
+	aclObject.Rules = append(aclObject.Rules, []aclRule{
+		{
+			Topic:      fmt.Sprintf("node/update/%s/%s", nodeNetwork, nodeID),
+			Permission: "allow",
+			Action:     "subscribe",
+		},
+		{
+			Topic:      fmt.Sprintf("ping/%s/%s", serverName, nodeID),
+			Permission: "allow",
+			Action:     "all",
+		},
+		{
+			Topic:      fmt.Sprintf("update/%s/%s", serverName, nodeID),
+			Permission: "allow",
+			Action:     "all",
+		},
+		{
+			Topic:      fmt.Sprintf("signal/%s/%s", serverName, nodeID),
+			Permission: "allow",
+			Action:     "all",
+		},
+		{
+			Topic:      fmt.Sprintf("metrics/%s/%s", serverName, nodeID),
+			Permission: "allow",
+			Action:     "all",
+		},
+	}...)
+	payload, err := json.Marshal(aclObject)
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest(http.MethodPut, servercfg.GetEmqxRestEndpoint()+"/api/v5/authorization/sources/built_in_database/username/"+hostID, bytes.NewReader(payload))
+	if err != nil {
+		return err
+	}
+	req.Header.Add("content-type", "application/json")
+	req.Header.Add("authorization", "Bearer "+token)
+	resp, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != http.StatusNoContent {
+		msg, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		return fmt.Errorf("error adding ACL Rules for user %s Error: %v", hostID, string(msg))
+	}
+	return nil
+}

+ 2 - 2
mq/handlers.go

@@ -114,7 +114,7 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 				return
 				return
 			} else {
 			} else {
 				if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 				if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
-					if err = AppendNodeUpdateACL(hu.Host.ID.String(), hu.Node.Network, hu.Node.ID.String(), servercfg.GetServer()); err != nil {
+					if err = emqx.AppendNodeUpdateACL(hu.Host.ID.String(), hu.Node.Network, hu.Node.ID.String(), servercfg.GetServer()); err != nil {
 						slog.Error("failed to add ACLs for EMQX node", "error", err)
 						slog.Error("failed to add ACLs for EMQX node", "error", err)
 						return
 						return
 					}
 					}
@@ -143,7 +143,7 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 	case models.DeleteHost:
 	case models.DeleteHost:
 		if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 		if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 			// delete EMQX credentials for host
 			// delete EMQX credentials for host
-			if err := DeleteEmqxUser(currentHost.ID.String()); err != nil {
+			if err := emqx.DeleteEmqxUser(currentHost.ID.String()); err != nil {
 				slog.Error("failed to remove host credentials from EMQX", "id", currentHost.ID, "error", err)
 				slog.Error("failed to remove host credentials from EMQX", "id", currentHost.ID, "error", err)
 			}
 			}
 		}
 		}

+ 23 - 16
mq/mq.go

@@ -41,24 +41,31 @@ func setMqOptions(user, password string, opts *mqtt.ClientOptions) {
 // SetupMQTT creates a connection to broker and return client
 // SetupMQTT creates a connection to broker and return client
 func SetupMQTT() {
 func SetupMQTT() {
 	if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
 	if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
-		time.Sleep(10 * time.Second) // wait for the REST endpoint to be ready
-		// setup authenticator and create admin user
-		if err := CreateEmqxDefaultAuthenticator(); err != nil {
-			logger.Log(0, err.Error())
-		}
-		DeleteEmqxUser(servercfg.GetMqUserName())
-		if err := CreateEmqxUser(servercfg.GetMqUserName(), servercfg.GetMqPassword(), true); err != nil {
-			log.Fatal(err)
-		}
-		// create an ACL authorization source for the built in EMQX MNESIA database
-		if err := CreateEmqxDefaultAuthorizer(); err != nil {
-			logger.Log(0, err.Error())
-		}
-		// create a default deny ACL to all topics for all users
-		if err := CreateDefaultDenyRule(); err != nil {
-			log.Fatal(err)
+		if emqx.GetType() == servercfg.EmqxOnPremDeploy {
+			time.Sleep(10 * time.Second) // wait for the REST endpoint to be ready
+			// setup authenticator and create admin user
+			if err := emqx.CreateEmqxDefaultAuthenticator(); err != nil {
+				logger.Log(0, err.Error())
+			}
+			emqx.DeleteEmqxUser(servercfg.GetMqUserName())
+			if err := emqx.CreateEmqxUserforServer(); err != nil {
+				log.Fatal(err)
+			}
+			// create an ACL authorization source for the built in EMQX MNESIA database
+			if err := emqx.CreateEmqxDefaultAuthorizer(); err != nil {
+				logger.Log(0, err.Error())
+			}
+			// create a default deny ACL to all topics for all users
+			if err := emqx.CreateDefaultDenyRule(); err != nil {
+				log.Fatal(err)
+			}
+		} else {
+			if err := emqx.CreateEmqxUserforServer(); err != nil {
+				log.Fatal(err)
+			}
 		}
 		}
 	}
 	}
+
 	opts := mqtt.NewClientOptions()
 	opts := mqtt.NewClientOptions()
 	setMqOptions(servercfg.GetMqUserName(), servercfg.GetMqPassword(), opts)
 	setMqOptions(servercfg.GetMqUserName(), servercfg.GetMqPassword(), opts)
 	opts.SetOnConnectHandler(func(client mqtt.Client) {
 	opts.SetOnConnectHandler(func(client mqtt.Client) {

+ 1 - 20
pro/controllers/failover.go

@@ -46,27 +46,8 @@ func createfailOver(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}
-	if _, exists := proLogic.FailOverExists(node.Network); exists {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failover exists already in the network"), "badrequest"))
-		return
-	}
-	host, err := logic.GetHost(node.HostID.String())
+	err = proLogic.CreateFailOver(node)
 	if err != nil {
 	if err != nil {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("error getting host"+err.Error()), "badrequest"))
-		return
-	}
-	if host.OS != models.OS_Types.Linux {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("only linux nodes can act as failovers"), "badrequest"))
-		return
-	}
-	if node.IsRelayed {
-		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("cannot set relayed node as failover"), "badrequest"))
-		return
-	}
-	node.IsFailOver = true
-	err = logic.UpsertNode(&node)
-	if err != nil {
-		slog.Error("failed to upsert node", "node", node.ID.String(), "error", err)
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}

+ 164 - 0
pro/controllers/inet_gws.go

@@ -0,0 +1,164 @@
+package controllers
+
+import (
+	"encoding/json"
+	"errors"
+	"net/http"
+
+	"github.com/gorilla/mux"
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/mq"
+	proLogic "github.com/gravitl/netmaker/pro/logic"
+)
+
+// InetHandlers - handlers for internet gw
+func InetHandlers(r *mux.Router) {
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/inet_gw", logic.SecurityCheck(true, http.HandlerFunc(createInternetGw))).Methods(http.MethodPost)
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/inet_gw", logic.SecurityCheck(true, http.HandlerFunc(updateInternetGw))).Methods(http.MethodPut)
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/inet_gw", logic.SecurityCheck(true, http.HandlerFunc(deleteInternetGw))).Methods(http.MethodDelete)
+}
+
+// swagger:route POST /api/nodes/{network}/{nodeid}/inet_gw nodes createInternetGw
+//
+// Create an inet node.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: nodeResponse
+func createInternetGw(w http.ResponseWriter, r *http.Request) {
+	var params = mux.Vars(r)
+	w.Header().Set("Content-Type", "application/json")
+	nodeid := params["nodeid"]
+	netid := params["network"]
+	node, err := logic.ValidateParams(nodeid, netid)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	if node.IsInternetGateway {
+		logic.ReturnSuccessResponse(w, r, "node is already acting as internet gateway")
+		return
+	}
+	var request models.InetNodeReq
+	err = json.NewDecoder(r.Body).Decode(&request)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	host, err := logic.GetHost(node.HostID.String())
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	if host.OS != models.OS_Types.Linux {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("only linux nodes can be made internet gws"), "badrequest"))
+		return
+	}
+	err = proLogic.ValidateInetGwReq(node, request, false)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	proLogic.SetInternetGw(&node, request)
+	err = logic.UpsertNode(&node)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	apiNode := node.ConvertToAPINode()
+	logger.Log(1, r.Header.Get("user"), "created ingress gateway on node", nodeid, "on network", netid)
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(apiNode)
+	go mq.PublishPeerUpdate(false)
+}
+
+// swagger:route PUT /api/nodes/{network}/{nodeid}/inet_gw nodes updateInternetGw
+//
+// update an inet node.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: nodeResponse
+func updateInternetGw(w http.ResponseWriter, r *http.Request) {
+	var params = mux.Vars(r)
+	w.Header().Set("Content-Type", "application/json")
+	nodeid := params["nodeid"]
+	netid := params["network"]
+	node, err := logic.ValidateParams(nodeid, netid)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	var request models.InetNodeReq
+	err = json.NewDecoder(r.Body).Decode(&request)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	if !node.IsInternetGateway {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("node is not a internet gw"), "badrequest"))
+		return
+	}
+	err = proLogic.ValidateInetGwReq(node, request, true)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	proLogic.UnsetInternetGw(&node)
+	proLogic.SetInternetGw(&node, request)
+	err = logic.UpsertNode(&node)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	apiNode := node.ConvertToAPINode()
+	logger.Log(1, r.Header.Get("user"), "created ingress gateway on node", nodeid, "on network", netid)
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(apiNode)
+	go mq.PublishPeerUpdate(false)
+}
+
+// swagger:route DELETE /api/nodes/{network}/{nodeid}/inet_gw nodes deleteInternetGw
+//
+// Delete an internet gw.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: nodeResponse
+func deleteInternetGw(w http.ResponseWriter, r *http.Request) {
+	var params = mux.Vars(r)
+	w.Header().Set("Content-Type", "application/json")
+	nodeid := params["nodeid"]
+	netid := params["network"]
+	node, err := logic.ValidateParams(nodeid, netid)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+
+	proLogic.UnsetInternetGw(&node)
+	err = logic.UpsertNode(&node)
+	if err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	apiNode := node.ConvertToAPINode()
+	logger.Log(1, r.Header.Get("user"), "created ingress gateway on node", nodeid, "on network", netid)
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(apiNode)
+	go mq.PublishPeerUpdate(false)
+}

+ 4 - 0
pro/controllers/users.go

@@ -212,6 +212,7 @@ func getUserRemoteAccessGws(w http.ResponseWriter, r *http.Request) {
 					Connected:         true,
 					Connected:         true,
 					IsInternetGateway: node.IsInternetGateway,
 					IsInternetGateway: node.IsInternetGateway,
 					GwPeerPublicKey:   host.PublicKey.String(),
 					GwPeerPublicKey:   host.PublicKey.String(),
+					Metadata:          node.Metadata,
 				})
 				})
 				userGws[node.Network] = gws
 				userGws[node.Network] = gws
 				delete(user.RemoteGwIDs, node.ID.String())
 				delete(user.RemoteGwIDs, node.ID.String())
@@ -226,6 +227,7 @@ func getUserRemoteAccessGws(w http.ResponseWriter, r *http.Request) {
 					Connected:         true,
 					Connected:         true,
 					IsInternetGateway: node.IsInternetGateway,
 					IsInternetGateway: node.IsInternetGateway,
 					GwPeerPublicKey:   host.PublicKey.String(),
 					GwPeerPublicKey:   host.PublicKey.String(),
+					Metadata:          node.Metadata,
 				})
 				})
 				userGws[node.Network] = gws
 				userGws[node.Network] = gws
 				processedAdminNodeIds[node.ID.String()] = struct{}{}
 				processedAdminNodeIds[node.ID.String()] = struct{}{}
@@ -258,6 +260,7 @@ func getUserRemoteAccessGws(w http.ResponseWriter, r *http.Request) {
 				Network:           node.Network,
 				Network:           node.Network,
 				IsInternetGateway: node.IsInternetGateway,
 				IsInternetGateway: node.IsInternetGateway,
 				GwPeerPublicKey:   host.PublicKey.String(),
 				GwPeerPublicKey:   host.PublicKey.String(),
+				Metadata:          node.Metadata,
 			})
 			})
 			userGws[node.Network] = gws
 			userGws[node.Network] = gws
 		}
 		}
@@ -284,6 +287,7 @@ func getUserRemoteAccessGws(w http.ResponseWriter, r *http.Request) {
 					Network:           node.Network,
 					Network:           node.Network,
 					IsInternetGateway: node.IsInternetGateway,
 					IsInternetGateway: node.IsInternetGateway,
 					GwPeerPublicKey:   host.PublicKey.String(),
 					GwPeerPublicKey:   host.PublicKey.String(),
+					Metadata:          node.Metadata,
 				})
 				})
 				userGws[node.Network] = gws
 				userGws[node.Network] = gws
 			}
 			}

+ 7 - 2
pro/initialize.go

@@ -31,6 +31,7 @@ func InitPro() {
 		proControllers.RelayHandlers,
 		proControllers.RelayHandlers,
 		proControllers.UserHandlers,
 		proControllers.UserHandlers,
 		proControllers.FailOverHandlers,
 		proControllers.FailOverHandlers,
+		proControllers.InetHandlers,
 	)
 	)
 	logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() {
 	logic.EnterpriseCheckFuncs = append(logic.EnterpriseCheckFuncs, func() {
 		// == License Handling ==
 		// == License Handling ==
@@ -83,6 +84,7 @@ func InitPro() {
 	})
 	})
 	logic.ResetFailOver = proLogic.ResetFailOver
 	logic.ResetFailOver = proLogic.ResetFailOver
 	logic.ResetFailedOverPeer = proLogic.ResetFailedOverPeer
 	logic.ResetFailedOverPeer = proLogic.ResetFailedOverPeer
+	logic.CreateFailOver = proLogic.CreateFailOver
 	logic.GetFailOverPeerIps = proLogic.GetFailOverPeerIps
 	logic.GetFailOverPeerIps = proLogic.GetFailOverPeerIps
 	logic.DenyClientNodeAccess = proLogic.DenyClientNode
 	logic.DenyClientNodeAccess = proLogic.DenyClientNode
 	logic.IsClientNodeAllowed = proLogic.IsClientNodeAllowed
 	logic.IsClientNodeAllowed = proLogic.IsClientNodeAllowed
@@ -99,9 +101,12 @@ func InitPro() {
 	logic.UpdateRelayed = proLogic.UpdateRelayed
 	logic.UpdateRelayed = proLogic.UpdateRelayed
 	logic.SetRelayedNodes = proLogic.SetRelayedNodes
 	logic.SetRelayedNodes = proLogic.SetRelayedNodes
 	logic.RelayUpdates = proLogic.RelayUpdates
 	logic.RelayUpdates = proLogic.RelayUpdates
-	logic.IsInternetGw = proLogic.IsInternetGw
-	logic.SetInternetGw = proLogic.SetInternetGw
 	logic.GetTrialEndDate = getTrialEndDate
 	logic.GetTrialEndDate = getTrialEndDate
+	logic.SetDefaultGw = proLogic.SetDefaultGw
+	logic.SetDefaultGwForRelayedUpdate = proLogic.SetDefaultGwForRelayedUpdate
+	logic.UnsetInternetGw = proLogic.UnsetInternetGw
+	logic.SetInternetGw = proLogic.SetInternetGw
+	logic.GetAllowedIpForInetNodeClient = proLogic.GetAllowedIpForInetNodeClient
 	mq.UpdateMetrics = proLogic.MQUpdateMetrics
 	mq.UpdateMetrics = proLogic.MQUpdateMetrics
 	mq.UpdateMetricsFallBack = proLogic.MQUpdateMetricsFallBack
 	mq.UpdateMetricsFallBack = proLogic.MQUpdateMetricsFallBack
 }
 }

+ 27 - 0
pro/logic/ext_acls.go

@@ -5,6 +5,7 @@ import (
 	"github.com/gravitl/netmaker/logic/acls"
 	"github.com/gravitl/netmaker/logic/acls"
 	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
+	"golang.org/x/exp/slog"
 )
 )
 
 
 // DenyClientNode - add a denied node to an ext client's list
 // DenyClientNode - add a denied node to an ext client's list
@@ -55,14 +56,40 @@ func SetClientDefaultACLs(ec *models.ExtClient) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
+	var networkAcls acls.ACLContainer
+	networkAcls, err = networkAcls.Get(acls.ContainerID(ec.Network))
+	if err != nil {
+		slog.Error("failed to get network acls", "error", err)
+		return err
+	}
+	networkAcls[acls.AclID(ec.ClientID)] = acls.ACL{}
 	for i := range networkNodes {
 	for i := range networkNodes {
 		currNode := networkNodes[i]
 		currNode := networkNodes[i]
 		if network.DefaultACL == "no" || currNode.DefaultACL == "no" {
 		if network.DefaultACL == "no" || currNode.DefaultACL == "no" {
 			DenyClientNode(ec, currNode.ID.String())
 			DenyClientNode(ec, currNode.ID.String())
+			networkAcls[acls.AclID(ec.ClientID)][acls.AclID(currNode.ID.String())] = acls.NotAllowed
+			networkAcls[acls.AclID(currNode.ID.String())][acls.AclID(ec.ClientID)] = acls.NotAllowed
 		} else {
 		} else {
 			RemoveDeniedNodeFromClient(ec, currNode.ID.String())
 			RemoveDeniedNodeFromClient(ec, currNode.ID.String())
+			networkAcls[acls.AclID(ec.ClientID)][acls.AclID(currNode.ID.String())] = acls.Allowed
+			networkAcls[acls.AclID(currNode.ID.String())][acls.AclID(ec.ClientID)] = acls.Allowed
 		}
 		}
 	}
 	}
+	networkClients, err := logic.GetNetworkExtClients(ec.Network)
+	if err != nil {
+		slog.Error("failed to get network clients", "error", err)
+		return err
+	}
+	for _, client := range networkClients {
+		// TODO: revisit when client-client acls are supported
+		networkAcls[acls.AclID(ec.ClientID)][acls.AclID(client.ClientID)] = acls.Allowed
+		networkAcls[acls.AclID(client.ClientID)][acls.AclID(ec.ClientID)] = acls.Allowed
+	}
+	delete(networkAcls[acls.AclID(ec.ClientID)], acls.AclID(ec.ClientID)) // remove oneself
+	if _, err = networkAcls.Save(acls.ContainerID(ec.Network)); err != nil {
+		slog.Error("failed to update network acls", "error", err)
+		return err
+	}
 	return nil
 	return nil
 }
 }
 
 

+ 24 - 0
pro/logic/failover.go

@@ -7,6 +7,7 @@ import (
 	"github.com/google/uuid"
 	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
+	"golang.org/x/exp/slog"
 )
 )
 
 
 func SetFailOverCtx(failOverNode, victimNode, peerNode models.Node) error {
 func SetFailOverCtx(failOverNode, victimNode, peerNode models.Node) error {
@@ -123,3 +124,26 @@ func GetFailOverPeerIps(peer, node *models.Node) []net.IPNet {
 	}
 	}
 	return allowedips
 	return allowedips
 }
 }
+
+func CreateFailOver(node models.Node) error {
+	if _, exists := FailOverExists(node.Network); exists {
+		return errors.New("failover already exists in the network")
+	}
+	host, err := logic.GetHost(node.HostID.String())
+	if err != nil {
+		return err
+	}
+	if host.OS != models.OS_Types.Linux {
+		return err
+	}
+	if node.IsRelayed {
+		return err
+	}
+	node.IsFailOver = true
+	err = logic.UpsertNode(&node)
+	if err != nil {
+		slog.Error("failed to upsert node", "node", node.ID.String(), "error", err)
+		return err
+	}
+	return nil
+}

+ 121 - 7
pro/logic/nodes.go

@@ -1,24 +1,132 @@
 package logic
 package logic
 
 
 import (
 import (
-	celogic "github.com/gravitl/netmaker/logic"
+	"errors"
+	"fmt"
+	"net"
+
+	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
+	"golang.org/x/exp/slog"
 )
 )
 
 
-// IsInternetGw - checks if node is acting as internet gw
-func IsInternetGw(node models.Node) bool {
-	return node.IsInternetGateway
+func ValidateInetGwReq(inetNode models.Node, req models.InetNodeReq, update bool) error {
+	inetHost, err := logic.GetHost(inetNode.HostID.String())
+	if err != nil {
+		return err
+	}
+	if inetHost.FirewallInUse == models.FIREWALL_NONE {
+		return errors.New("iptables or nftables needs to be installed")
+	}
+	if inetNode.InternetGwID != "" {
+		return fmt.Errorf("node %s is using a internet gateway already", inetHost.Name)
+	}
+	if inetNode.IsRelayed {
+		return fmt.Errorf("node %s is being relayed", inetHost.Name)
+	}
+	for _, clientNodeID := range req.InetNodeClientIDs {
+		clientNode, err := logic.GetNodeByID(clientNodeID)
+		if err != nil {
+			return err
+		}
+		clientHost, err := logic.GetHost(clientNode.HostID.String())
+		if err != nil {
+			return err
+		}
+		if clientHost.OS != models.OS_Types.Linux && clientHost.OS != models.OS_Types.Windows {
+			return errors.New("can only attach linux or windows machine to a internet gateway")
+		}
+		if clientNode.IsInternetGateway {
+			return fmt.Errorf("node %s acting as internet gateway cannot use another internet gateway", clientHost.Name)
+		}
+		if update {
+			if clientNode.InternetGwID != "" && clientNode.InternetGwID != inetNode.ID.String() {
+				return fmt.Errorf("node %s is already using a internet gateway", clientHost.Name)
+			}
+		} else {
+			if clientNode.InternetGwID != "" {
+				return fmt.Errorf("node %s is already using a internet gateway", clientHost.Name)
+			}
+		}
+
+		if clientNode.IsRelayed {
+			return fmt.Errorf("node %s is being relayed", clientHost.Name)
+		}
+
+		for _, nodeID := range clientHost.Nodes {
+			node, err := logic.GetNodeByID(nodeID)
+			if err != nil {
+				continue
+			}
+			if node.InternetGwID != "" && node.InternetGwID != inetNode.ID.String() {
+				return errors.New("nodes on same host cannot use different internet gateway")
+			}
+
+		}
+	}
+	return nil
 }
 }
 
 
 // SetInternetGw - sets the node as internet gw based on flag bool
 // SetInternetGw - sets the node as internet gw based on flag bool
-func SetInternetGw(node *models.Node, flag bool) {
-	node.IsInternetGateway = flag
+func SetInternetGw(node *models.Node, req models.InetNodeReq) {
+	node.IsInternetGateway = true
+	node.InetNodeReq = req
+	for _, clientNodeID := range req.InetNodeClientIDs {
+		clientNode, err := logic.GetNodeByID(clientNodeID)
+		if err != nil {
+			continue
+		}
+		clientNode.InternetGwID = node.ID.String()
+		logic.UpsertNode(&clientNode)
+	}
+
+}
+
+func UnsetInternetGw(node *models.Node) {
+	nodes, err := logic.GetNetworkNodes(node.Network)
+	if err != nil {
+		slog.Error("failed to get network nodes", "network", node.Network, "error", err)
+		return
+	}
+	for _, clientNode := range nodes {
+		if node.ID.String() == clientNode.InternetGwID {
+			clientNode.InternetGwID = ""
+			logic.UpsertNode(&clientNode)
+		}
+
+	}
+	node.IsInternetGateway = false
+	node.InetNodeReq = models.InetNodeReq{}
+
+}
+
+func SetDefaultGwForRelayedUpdate(relayed, relay models.Node, peerUpdate models.HostPeerUpdate) models.HostPeerUpdate {
+	if relay.InternetGwID != "" {
+		peerUpdate.ChangeDefaultGw = true
+		peerUpdate.DefaultGwIp = relay.Address.IP
+
+	}
+	return peerUpdate
+}
+
+func SetDefaultGw(node models.Node, peerUpdate models.HostPeerUpdate) models.HostPeerUpdate {
+	if node.InternetGwID != "" {
+
+		inetNode, err := logic.GetNodeByID(node.InternetGwID)
+		if err != nil {
+			return peerUpdate
+		}
+		peerUpdate.ChangeDefaultGw = true
+		peerUpdate.DefaultGwIp = inetNode.Address.IP
+
+	}
+	return peerUpdate
 }
 }
 
 
 // GetNetworkIngresses - gets the gateways of a network
 // GetNetworkIngresses - gets the gateways of a network
 func GetNetworkIngresses(network string) ([]models.Node, error) {
 func GetNetworkIngresses(network string) ([]models.Node, error) {
 	var ingresses []models.Node
 	var ingresses []models.Node
-	netNodes, err := celogic.GetNetworkNodes(network)
+	netNodes, err := logic.GetNetworkNodes(network)
 	if err != nil {
 	if err != nil {
 		return []models.Node{}, err
 		return []models.Node{}, err
 	}
 	}
@@ -29,3 +137,9 @@ func GetNetworkIngresses(network string) ([]models.Node, error) {
 	}
 	}
 	return ingresses, nil
 	return ingresses, nil
 }
 }
+
+// GetAllowedIpsForInet - get inet cidr for node using a inet gw
+func GetAllowedIpForInetNodeClient(node, peer *models.Node) []net.IPNet {
+	_, ipnet, _ := net.ParseCIDR("0.0.0.0/0")
+	return []net.IPNet{*ipnet}
+}

+ 5 - 6
pro/logic/relays.go

@@ -103,11 +103,7 @@ func SetRelayedNodes(setRelayed bool, relay string, relayed []string) []models.N
 // ValidateRelay - checks if relay is valid
 // ValidateRelay - checks if relay is valid
 func ValidateRelay(relay models.RelayRequest) error {
 func ValidateRelay(relay models.RelayRequest) error {
 	var err error
 	var err error
-	// isIp := functions.IsIpCIDR(gateway.RangeString)
-	empty := len(relay.RelayedNodes) == 0
-	if empty {
-		return errors.New("IP Ranges Cannot Be Empty")
-	}
+
 	node, err := logic.GetNodeByID(relay.NodeID)
 	node, err := logic.GetNodeByID(relay.NodeID)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -135,7 +131,7 @@ func updateRelayNodes(relay string, oldNodes []string, newNodes []string) []mode
 
 
 func RelayUpdates(currentNode, newNode *models.Node) bool {
 func RelayUpdates(currentNode, newNode *models.Node) bool {
 	relayUpdates := false
 	relayUpdates := false
-	if servercfg.IsPro && newNode.IsRelay && len(newNode.RelayedNodes) > 0 {
+	if servercfg.IsPro && newNode.IsRelay {
 		if len(newNode.RelayedNodes) != len(currentNode.RelayedNodes) {
 		if len(newNode.RelayedNodes) != len(currentNode.RelayedNodes) {
 			relayUpdates = true
 			relayUpdates = true
 		} else {
 		} else {
@@ -208,6 +204,9 @@ func GetAllowedIpsForRelayed(relayed, relay *models.Node) (allowedIPs []net.IPNe
 		logger.Log(0, "RelayedByRelay called with invalid parameters")
 		logger.Log(0, "RelayedByRelay called with invalid parameters")
 		return
 		return
 	}
 	}
+	if relay.InternetGwID != "" {
+		return GetAllowedIpForInetNodeClient(relayed, relay)
+	}
 	peers, err := logic.GetNetworkNodes(relay.Network)
 	peers, err := logic.GetNetworkNodes(relay.Network)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, "error getting network clients", err.Error())
 		logger.Log(0, "error getting network clients", err.Error())

+ 15 - 15
release.md

@@ -1,19 +1,19 @@
+# Netmaker v0.23.0
 
 
-# Netmaker v0.22.0
+## Whats New ✨
 
 
-## Whats New
-- Revamped Internet Gateways
-- MQ fallback
-- Deprecating TURN in favour of failover hosts on Pro
-- Switch to CoreDNS for DNS resolution
-- DNS is no longer managed with OS hosts file (/etc/hosts file)
-- Add support for RAC on mobile
+- Revamped Internet Gateways: hosts and clients can now use internet gateways! More info [here](https://docs.netmaker.io/pro/internet-gateways.html)
+  On community edition, internet gateways for clients can be accessed via the Remote Access tab.
+- PostUp and PostDown commands for clients
+- EMQX cloud support
+- Metadata for Remote Access Gateways
 
 
-## What's Fixed
-- Expired nodes not getting deleted
-- NMCTL acl subcommand leading to dirty state
-- Enforce private network ranges
-- Minor bugs and enhacements with user management
-- Scalability issues
+## What's Fixed/Improved 🛠
 
 
-## Known issues
+- Allow creation of gateways, relays and egress without clients, relayed hosts and external ranges respectively
+- Make default host a remote access gateway and a failover host on joining a new network
+- Stability fixes with ACLs
+- Fixed issues with install/upgrade scripts
+- Fixed issues with CoreDNS
+
+## Known Issues 🐞

+ 1 - 7
scripts/nm-upgrade.sh

@@ -5,8 +5,7 @@ CONFIG_FILE=netmaker.env
 SCRIPT_DIR=$(dirname "$(realpath "$0")")
 SCRIPT_DIR=$(dirname "$(realpath "$0")")
 CONFIG_PATH="$SCRIPT_DIR/$CONFIG_FILE"
 CONFIG_PATH="$SCRIPT_DIR/$CONFIG_FILE"
 NM_QUICK_VERSION="0.1.0"
 NM_QUICK_VERSION="0.1.0"
-LATEST=$(curl -s https://api.github.com/repos/gravitl/netmaker/releases/latest | grep "tag_name" | cut -d : -f 2,3 | tr -d [:space:],\")
-
+LATEST="v0.21.2"
 if [ "$(id -u)" -ne 0 ]; then
 if [ "$(id -u)" -ne 0 ]; then
 	echo "This script must be run as root"
 	echo "This script must be run as root"
 	exit 1
 	exit 1
@@ -440,7 +439,6 @@ install_netmaker() {
 		wget -qO "$SCRIPT_DIR"/Caddyfile "$CADDY_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"/netmaker.default.env "$BASE_URL/scripts/netmaker.default.env"
 		wget -qO "$SCRIPT_DIR"/mosquitto.conf "$BASE_URL/docker/mosquitto.conf"
 		wget -qO "$SCRIPT_DIR"/mosquitto.conf "$BASE_URL/docker/mosquitto.conf"
-		wget -qO "$SCRIPT_DIR"/nm-certs.sh "$BASE_URL/scripts/nm-certs.sh"
 		wget -qO "$SCRIPT_DIR"/wait.sh "$BASE_URL/docker/wait.sh"
 		wget -qO "$SCRIPT_DIR"/wait.sh "$BASE_URL/docker/wait.sh"
 
 
 	chmod +x "$SCRIPT_DIR"/wait.sh
 	chmod +x "$SCRIPT_DIR"/wait.sh
@@ -450,10 +448,6 @@ install_netmaker() {
 	ln -fs "$SCRIPT_DIR/netmaker.env" "$SCRIPT_DIR/.env"
 	ln -fs "$SCRIPT_DIR/netmaker.env" "$SCRIPT_DIR/.env"
 	save_config
 	save_config
 
 
-	# Fetch / update certs using certbot
-	chmod +x "$SCRIPT_DIR"/nm-certs.sh
-	"$SCRIPT_DIR"/nm-certs.sh
-
 	echo "Starting containers..."
 	echo "Starting containers..."
 
 
 	# increase the timeouts
 	# increase the timeouts

+ 31 - 2
servercfg/serverconf.go

@@ -17,10 +17,15 @@ import (
 // EmqxBrokerType denotes the broker type for EMQX MQTT
 // EmqxBrokerType denotes the broker type for EMQX MQTT
 const EmqxBrokerType = "emqx"
 const EmqxBrokerType = "emqx"
 
 
+// Emqxdeploy - emqx deploy type
+type Emqxdeploy string
+
 var (
 var (
 	Version              = "dev"
 	Version              = "dev"
 	IsPro                = false
 	IsPro                = false
 	ErrLicenseValidation error
 	ErrLicenseValidation error
+	EmqxCloudDeploy      Emqxdeploy = "cloud"
+	EmqxOnPremDeploy     Emqxdeploy = "on-prem"
 )
 )
 
 
 // SetHost - sets the host ip
 // SetHost - sets the host ip
@@ -112,8 +117,13 @@ func GetRacAutoDisable() bool {
 func GetServerInfo() models.ServerConfig {
 func GetServerInfo() models.ServerConfig {
 	var cfg models.ServerConfig
 	var cfg models.ServerConfig
 	cfg.Server = GetServer()
 	cfg.Server = GetServer()
-	cfg.MQUserName = GetMqUserName()
-	cfg.MQPassword = GetMqPassword()
+	if GetBrokerType() == EmqxBrokerType {
+		cfg.MQUserName = "HOST_ID"
+		cfg.MQPassword = "HOST_PASS"
+	} else {
+		cfg.MQUserName = GetMqUserName()
+		cfg.MQPassword = GetMqPassword()
+	}
 	cfg.API = GetAPIConnString()
 	cfg.API = GetAPIConnString()
 	cfg.CoreDNSAddr = GetCoreDNSAddr()
 	cfg.CoreDNSAddr = GetCoreDNSAddr()
 	cfg.APIPort = GetAPIPort()
 	cfg.APIPort = GetAPIPort()
@@ -674,3 +684,22 @@ func GetEnvironment() string {
 	}
 	}
 	return ""
 	return ""
 }
 }
+
+// GetEmqxDeployType - fetches emqx deploy type this server uses
+func GetEmqxDeployType() (deployType Emqxdeploy) {
+	deployType = EmqxOnPremDeploy
+	if os.Getenv("EMQX_DEPLOY_TYPE") == string(EmqxCloudDeploy) {
+		deployType = EmqxCloudDeploy
+	}
+	return
+}
+
+// GetEmqxAppID - gets the emqx cloud app id
+func GetEmqxAppID() string {
+	return os.Getenv("EMQX_APP_ID")
+}
+
+// GetEmqxAppSecret - gets the emqx cloud app secret
+func GetEmqxAppSecret() string {
+	return os.Getenv("EMQX_APP_SECRET")
+}

+ 1 - 1
swagger.yml

@@ -1149,7 +1149,7 @@ info:
 
 
         API calls must be authenticated via a header of the format -H “Authorization: Bearer <YOUR_SECRET_KEY>” There are two methods to obtain YOUR_SECRET_KEY: 1. Using the masterkey. By default, this value is “secret key,” but you should change this on your instance and keep it secure. This value can be set via env var at startup or in a config file (config/environments/< env >.yaml). See the [Netmaker](https://docs.netmaker.org/index.html) documentation for more details. 2. Using a JWT received for a node. This can be retrieved by calling the /api/nodes/<network>/authenticate endpoint, as documented below.
         API calls must be authenticated via a header of the format -H “Authorization: Bearer <YOUR_SECRET_KEY>” There are two methods to obtain YOUR_SECRET_KEY: 1. Using the masterkey. By default, this value is “secret key,” but you should change this on your instance and keep it secure. This value can be set via env var at startup or in a config file (config/environments/< env >.yaml). See the [Netmaker](https://docs.netmaker.org/index.html) documentation for more details. 2. Using a JWT received for a node. This can be retrieved by calling the /api/nodes/<network>/authenticate endpoint, as documented below.
     title: Netmaker
     title: Netmaker
-    version: 0.22.0
+    version: 0.23.0
 paths:
 paths:
     /api/dns:
     /api/dns:
         get:
         get: