ソースを参照

Merge pull request #2235 from gravitl/release_v0.18.7

v0.18.7
Alex Feiszli 2 年 前
コミット
d0998ab231
57 ファイル変更628 行追加631 行削除
  1. 1 0
      .github/ISSUE_TEMPLATE/bug-report.yml
  2. 1 1
      .github/workflows/branchtest.yml
  3. 57 2
      .github/workflows/deletedroplets.yml
  4. 4 4
      .github/workflows/publish-docker.yml
  5. 6 0
      .github/workflows/release.yml
  6. 1 1
      .goreleaser.prerelease.yaml
  7. 1 1
      .goreleaser.yaml
  8. 1 1
      README.md
  9. 3 3
      auth/auth.go
  10. 258 0
      auth/host_session.go
  11. 0 162
      auth/nodesession.go
  12. 8 81
      auth/register_callback.go
  13. 21 0
      cli/cmd/host/refresh_keys.go
  14. 0 20
      cli/cmd/network/refresh_keys.go
  15. 0 72
      cli/cmd/network/update.go
  16. 9 0
      cli/functions/host.go
  17. 0 5
      cli/functions/network.go
  18. 4 3
      compose/docker-compose-emqx.yml
  19. 2 1
      compose/docker-compose.ee.yml
  20. 2 2
      compose/docker-compose.netclient.yml
  21. 2 1
      compose/docker-compose.reference.yml
  22. 2 1
      compose/docker-compose.yml
  23. 1 0
      config/config.go
  24. 1 0
      controllers/dns.go
  25. 1 1
      controllers/docs.go
  26. 2 32
      controllers/enrollmentkeys.go
  27. 1 0
      controllers/ext_client.go
  28. 81 0
      controllers/hosts.go
  29. 2 1
      controllers/migrate.go
  30. 1 137
      controllers/network.go
  31. 1 0
      controllers/node.go
  32. 3 3
      controllers/user.go
  33. 9 9
      ee/license.go
  34. 1 1
      go.mod
  35. 2 2
      go.sum
  36. 1 1
      k8s/client/netclient-daemonset.yaml
  37. 1 1
      k8s/client/netclient.yaml
  38. 1 1
      k8s/server/netmaker-server.yaml
  39. 1 1
      k8s/server/netmaker-ui.yaml
  40. 6 0
      logger/logger.go
  41. 12 1
      logic/clients.go
  42. 8 0
      logic/dns.go
  43. 2 2
      logic/gateway.go
  44. 13 0
      logic/hosts.go
  45. 8 44
      logic/networks.go
  46. 8 0
      logic/nodes.go
  47. 8 5
      logic/pro/netcache/netcache.go
  48. 8 0
      logic/users.go
  49. 1 1
      main.go
  50. 12 0
      models/host.go
  51. 0 2
      models/node.go
  52. 0 9
      models/promodels/pro.go
  53. 23 0
      mq/handlers.go
  54. 7 14
      release.md
  55. 2 1
      scripts/nm-quick.sh
  56. 16 0
      servercfg/serverconf.go
  57. 1 1
      swagger.yaml

+ 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.18.7
         - v0.18.6
         - v0.18.6
         - v0.18.5
         - v0.18.5
         - v0.18.4
         - v0.18.4

+ 1 - 1
.github/workflows/branchtest.yml

@@ -53,6 +53,6 @@ jobs:
     uses: gravitl/devops/.github/workflows/branchtest.yml@master
     uses: gravitl/devops/.github/workflows/branchtest.yml@master
     with:
     with:
       tag: ${{ github.run_id }}-${{ github.run_attempt }}
       tag: ${{ github.run_id }}-${{ github.run_attempt }}
-      network: terraform
+      network: netmaker
     secrets: inherit
     secrets: inherit
     
     

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

@@ -11,6 +11,29 @@ jobs:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     if: ${{ github.event.workflow_run.conclusion == 'success' }}
     if: ${{ github.event.workflow_run.conclusion == 'success' }}
     steps:
     steps:
+      - name: get logs
+        uses: dawidd6/action-download-artifact@v2
+        with:
+          run_id: ${{ github.event.workflow_run.id}}
+          if_no_artifact_found: warn
+      - name: discord success message
+        uses: appleboy/discord-action@master
+        with:
+          webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }}
+          webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
+          color: "#42f545"
+          username: "GitHub Bot"
+          message: " ${{ github.event.workflow_run.name }} was successful"
+          file: ./results/results.log
+      - name: discord server message
+        uses: appleboy/discord-action@master
+        with:
+          webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }}
+          webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
+          color: "#42f545"
+          username: "GitHub Bot"
+          message: "droplets from this workflow will be deleted in 15 min"
+          file: ./server/serverinfo.txt
       - name: delete droplets
       - name: delete droplets
         run: |
         run: |
           sleep 15m
           sleep 15m
@@ -20,12 +43,44 @@ jobs:
             "https://api.digitalocean.com/v2/droplets?tag_name=$TAG"
             "https://api.digitalocean.com/v2/droplets?tag_name=$TAG"
         env:
         env:
           DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
           DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
-          TAG: ${{ github.event.workflow_run.run_id }}-${{ github.event.workflow_run.run_attempt }}
+          TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}
 
 
   on-failure:
   on-failure:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
     if: ${{ github.event.workflow_run.conclusion == 'failure' }}
     if: ${{ github.event.workflow_run.conclusion == 'failure' }}
     steps:
     steps:
+      - name: get logs
+        uses: dawidd6/action-download-artifact@v2
+        with:
+          run_id: ${{ github.event.workflow_run.id}}
+          if_no_artifact_found: warn
+      - name: discord failure message
+        uses: appleboy/discord-action@master
+        with:
+          webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }}
+          webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
+          color: "#42f545"
+          username: "GitHub Bot"
+          message: " ${{ github.event.workflow_run.name }} failed"
+          file: ./results/results.log
+      - name: discord server message
+        uses: appleboy/discord-action@master
+        with:
+          webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }}
+          webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
+          color: "#42f545"
+          username: "GitHub Bot"
+          message: "droplets from this workflow will be deleted in 6 hours"
+          file: ./server/serverinfo.txt
+      - name: discord error message
+        uses: appleboy/discord-action@master
+        with:
+          webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }}
+          webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
+          color: "#990000"
+          username: "GitHub Bot"
+          message: "errors from ${{ github.event.workflow_run.name }}"
+          file: ./results/errors.log
       - name: delete droplets
       - name: delete droplets
         run: |
         run: |
           sleep 6h
           sleep 6h
@@ -35,4 +90,4 @@ jobs:
             "https://api.digitalocean.com/v2/droplets?tag_name=$TAG"
             "https://api.digitalocean.com/v2/droplets?tag_name=$TAG"
         env:
         env:
           DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
           DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
-          TAG: ${{ github.event.workflow_run.run_id }}-${{ github.event.workflow_run.run_attempt }}
+          TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}

+ 4 - 4
.github/workflows/publish-docker.yml

@@ -19,8 +19,8 @@ jobs:
       -
       -
         name: Set tag
         name: Set tag
         run: |
         run: |
-            if [[ -n "${{ github.event.inputs.tag }}" ]]; then
-              TAG=${{ github.event.inputs.tag }}
+            if [[ -n "${{ inputs.tag }}" ]]; then
+              TAG=${{ inputs.tag }}
             elif [[ "${{ github.ref_name }}" == 'master' ]]; then
             elif [[ "${{ github.ref_name }}" == 'master' ]]; then
               TAG="latest"
               TAG="latest"
             else
             else
@@ -59,8 +59,8 @@ jobs:
       -
       -
         name: Set tag
         name: Set tag
         run: |
         run: |
-            if [[ -n "${{ github.event.inputs.tag }}" ]]; then
-              TAG=${{ github.event.inputs.tag }}
+            if [[ -n "${{ inputs.tag }}" ]]; then
+              TAG=${{ inputs.tag }}
             elif [[ "${{ github.ref_name }}" == 'master' ]]; then
             elif [[ "${{ github.ref_name }}" == 'master' ]]; then
               TAG="latest"
               TAG="latest"
             else
             else

+ 6 - 0
.github/workflows/release.yml

@@ -20,6 +20,8 @@ jobs:
     uses: ./.github/workflows/release-branch.yml
     uses: ./.github/workflows/release-branch.yml
     with:
     with:
       version: ${{ github.event.inputs.version }}
       version: ${{ github.event.inputs.version }}
+    secrets: inherit
+
   
   
   release-assets:
   release-assets:
     needs: release-branch
     needs: release-branch
@@ -27,12 +29,14 @@ jobs:
     with:
     with:
       version: ${{ github.event.inputs.version }}
       version: ${{ github.event.inputs.version }}
       prerelease: ${{ github.event.inputs.prerelease == 'true' }}
       prerelease: ${{ github.event.inputs.prerelease == 'true' }}
+    secrets: inherit
 
 
   docker:
   docker:
     needs: release-branch
     needs: release-branch
     uses: ./.github/workflows/publish-docker.yml
     uses: ./.github/workflows/publish-docker.yml
     with:
     with:
       tag: ${{ github.event.inputs.version }}
       tag: ${{ github.event.inputs.version }}
+    secrets: inherit
 
 
   packages:
   packages:
     if: ${{ github.event.inputs.prerelease == 'false' }}
     if: ${{ github.event.inputs.prerelease == 'false' }}
@@ -40,6 +44,7 @@ jobs:
     uses: ./.github/workflows/packages.yml
     uses: ./.github/workflows/packages.yml
     with:
     with:
       version: ${{ github.event.inputs.version }}
       version: ${{ github.event.inputs.version }}
+    secrets: inherit
 
 
   pull-request:
   pull-request:
     if: ${{ github.event.inputs.prerelease == 'false' }}
     if: ${{ github.event.inputs.prerelease == 'false' }}
@@ -47,3 +52,4 @@ jobs:
     uses: ./.github/workflows/pull-request.yml
     uses: ./.github/workflows/pull-request.yml
     with:
     with:
       version: ${{ github.event.inputs.version }}
       version: ${{ github.event.inputs.version }}
+    secrets: inherit

+ 1 - 1
.goreleaser.prerelease.yaml

@@ -28,7 +28,7 @@ builds:
     binary: 'nmctl'
     binary: 'nmctl'
 archives:
 archives:
   - format: binary
   - format: binary
-    name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}'
+    name_template: '{{ .Binary }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}-{{ . }}{{ end }}'
 release:
 release:
   prerelease: true
   prerelease: true
     
     

+ 1 - 1
.goreleaser.yaml

@@ -28,7 +28,7 @@ builds:
     binary: 'nmctl'
     binary: 'nmctl'
 archives:
 archives:
   - format: binary
   - format: binary
-    name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}'
+    name_template: '{{ .Binary }}-{{ .Os }}-{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}-{{ . }}{{ end }}'
 release:
 release:
   prerelease: false
   prerelease: false
     
     

+ 1 - 1
README.md

@@ -17,7 +17,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.18.6-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.18.7-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" />

+ 3 - 3
auth/auth.go

@@ -114,10 +114,10 @@ func HandleAuthCallback(w http.ResponseWriter, r *http.Request) {
 	if err == nil || errors.Is(err, netcache.ErrExpired) {
 	if err == nil || errors.Is(err, netcache.ErrExpired) {
 		switch len(state) {
 		switch len(state) {
 		case node_signin_length:
 		case node_signin_length:
-			logger.Log(0, "proceeding with node SSO callback")
-			HandleNodeSSOCallback(w, r)
+			logger.Log(1, "proceeding with host SSO callback")
+			HandleHostSSOCallback(w, r)
 		case headless_signin_length:
 		case headless_signin_length:
-			logger.Log(0, "proceeding with headless SSO callback")
+			logger.Log(1, "proceeding with headless SSO callback")
 			HandleHeadlessSSOCallback(w, r)
 			HandleHeadlessSSOCallback(w, r)
 		default:
 		default:
 			logger.Log(1, "invalid state length: ", fmt.Sprintf("%d", len(state)))
 			logger.Log(1, "invalid state length: ", fmt.Sprintf("%d", len(state)))

+ 258 - 0
auth/host_session.go

@@ -0,0 +1,258 @@
+package auth
+
+import (
+	"encoding/json"
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/google/uuid"
+	"github.com/gorilla/websocket"
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/logic/hostactions"
+	"github.com/gravitl/netmaker/logic/pro/netcache"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/mq"
+	"github.com/gravitl/netmaker/servercfg"
+)
+
+// SessionHandler - called by the HTTP router when user
+// is calling netclient with join/register -s parameter in order to authenticate
+// via SSO mechanism by OAuth2 protocol flow.
+// This triggers a session start and it is managed by the flow implemented here and callback
+// When this method finishes - the auth flow has finished either OK or by timeout or any other error occured
+func SessionHandler(conn *websocket.Conn) {
+	defer conn.Close()
+	// If reached here we have a session from user to handle...
+	messageType, message, err := conn.ReadMessage()
+	if err != nil {
+		logger.Log(0, "Error during message reading:", err.Error())
+		return
+	}
+
+	var registerMessage models.RegisterMsg
+	if err = json.Unmarshal(message, &registerMessage); err != nil {
+		logger.Log(0, "Failed to unmarshall data err=", err.Error())
+		return
+	}
+	if registerMessage.RegisterHost.ID == uuid.Nil {
+		logger.Log(0, "invalid host registration attempted")
+		return
+	}
+
+	req := new(netcache.CValue)
+	req.Value = string(registerMessage.RegisterHost.ID.String())
+	req.Network = registerMessage.Network
+	req.Host = registerMessage.RegisterHost
+	req.ALL = registerMessage.JoinAll
+	req.Pass = ""
+	req.User = registerMessage.User
+	if len(req.User) > 0 && len(registerMessage.Password) == 0 {
+		logger.Log(0, "invalid host registration attempted")
+		return
+	}
+	// Add any extra parameter provided in the configuration to the Authorize Endpoint request??
+	stateStr := logic.RandomString(node_signin_length)
+	if err := netcache.Set(stateStr, req); err != nil {
+		logger.Log(0, "Failed to process sso request -", err.Error())
+		return
+	}
+	// Wait for the user to finish his auth flow...
+	timeout := make(chan bool, 1)
+	answer := make(chan netcache.CValue, 1)
+	defer close(answer)
+	defer close(timeout)
+
+	if len(registerMessage.User) > 0 { // handle basic auth
+		logger.Log(0, "user registration attempted with host:", registerMessage.RegisterHost.Name, "user:", registerMessage.User)
+
+		if !servercfg.IsBasicAuthEnabled() {
+			err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+			if err != nil {
+				logger.Log(0, "error during message writing:", err.Error())
+			}
+		}
+		_, err := logic.VerifyAuthRequest(models.UserAuthParams{
+			UserName: registerMessage.User,
+			Password: registerMessage.Password,
+		})
+		if err != nil {
+			err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+			if err != nil {
+				logger.Log(0, "error during message writing:", err.Error())
+			}
+			return
+		}
+		req.Pass = req.Host.ID.String()
+
+		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())
+			return
+		}
+	} else { // handle SSO / OAuth
+		if auth_provider == nil {
+			err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+			if err != nil {
+				logger.Log(0, "error during message writing:", err.Error())
+			}
+			return
+		}
+		logger.Log(0, "user registration attempted with host:", registerMessage.RegisterHost.Name, "via SSO")
+		redirectUrl = fmt.Sprintf("https://%s/api/oauth/register/%s", servercfg.GetAPIConnString(), stateStr)
+		err = conn.WriteMessage(messageType, []byte(redirectUrl))
+		if err != nil {
+			logger.Log(0, "error during message writing:", err.Error())
+		}
+	}
+
+	go func() {
+		for {
+			cachedReq, err := netcache.Get(stateStr)
+			if err != nil {
+				if strings.Contains(err.Error(), "expired") {
+					logger.Log(1, "timeout occurred while waiting for SSO registration")
+					timeout <- true
+					break
+				}
+				continue
+			} else if len(cachedReq.User) > 0 {
+				logger.Log(0, "host SSO process completed for user", cachedReq.User)
+				answer <- *cachedReq
+				break
+			}
+			time.Sleep(500) // try it 2 times per second to see if auth is completed
+		}
+	}()
+
+	select {
+	case result := <-answer: // a read from req.answerCh has occurred
+		// 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 servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
+				if err := mq.CreateEmqxUser(result.Host.ID.String(), result.Host.HostPass, false); err != nil {
+					logger.Log(0, "failed to create host credentials for EMQX: ", err.Error())
+					return
+				}
+				if err := mq.CreateHostACL(result.Host.ID.String(), servercfg.GetServerInfo().Server); err != nil {
+					logger.Log(0, "failed to add host ACL rules to EMQX: ", err.Error())
+					return
+				}
+			}
+			logic.CheckHostPorts(&result.Host)
+			if err := logic.CreateHost(&result.Host); err != nil {
+				handleHostRegErr(conn, err)
+				return
+			}
+		}
+		key, keyErr := logic.RetrievePublicTrafficKey()
+		if keyErr != nil {
+			handleHostRegErr(conn, err)
+			return
+		}
+		currHost, err := logic.GetHost(result.Host.ID.String())
+		if err != nil {
+			handleHostRegErr(conn, err)
+			return
+		}
+		var currentNetworks = []string{}
+		if result.ALL {
+			currentNets, err := logic.GetNetworks()
+			if err == nil && len(currentNets) > 0 {
+				for i := range currentNets {
+					currentNetworks = append(currentNetworks, currentNets[i].NetID)
+				}
+			}
+		} else if len(result.Network) > 0 {
+			currentNetworks = append(currentNetworks, result.Network)
+		}
+		var netsToAdd = []string{} // track the networks not currently owned by host
+		hostNets := logic.GetHostNetworks(currHost.ID.String())
+		for _, newNet := range currentNetworks {
+			if !logic.StringSliceContains(hostNets, newNet) {
+				if len(result.User) > 0 {
+					_, err := isUserIsAllowed(result.User, newNet, false)
+					if err != nil {
+						logger.Log(0, "unauthorized user", result.User, "attempted to register to network", newNet)
+						handleHostRegErr(conn, err)
+						return
+					}
+				}
+				netsToAdd = append(netsToAdd, newNet)
+			}
+		}
+		server := servercfg.GetServerInfo()
+		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 = ""
+		response := models.RegisterResponse{
+			ServerConf:    server,
+			RequestedHost: result.Host,
+		}
+		reponseData, err := json.Marshal(&response)
+		if err != nil {
+			handleHostRegErr(conn, err)
+			return
+		}
+		if err = conn.WriteMessage(messageType, reponseData); err != nil {
+			logger.Log(0, "error during message writing:", err.Error())
+		}
+		go CheckNetRegAndHostUpdate(netsToAdd[:], &result.Host)
+	case <-timeout: // the read from req.answerCh has timed out
+		if err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")); err != nil {
+			logger.Log(0, "error during timeout message writing:", err.Error())
+		}
+	}
+	// The entry is not needed anymore, but we will let the producer to close it to avoid panic cases
+	if err = netcache.Del(stateStr); err != nil {
+		logger.Log(0, "failed to remove node SSO cache entry", err.Error())
+	}
+	// Cleanly close the connection by sending a close message and then
+	// waiting (with timeout) for the server to close the connection.
+	if err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")); err != nil {
+		logger.Log(0, "write close:", err.Error())
+		return
+	}
+}
+
+// CheckNetRegAndHostUpdate - run through networks and send a host update
+func CheckNetRegAndHostUpdate(networks []string, h *models.Host) {
+	// publish host update through MQ
+	for i := range networks {
+		network := networks[i]
+		if ok, _ := logic.NetworkExists(network); ok {
+			newNode, err := logic.UpdateHostNetwork(h, network, true)
+			if err != nil {
+				logger.Log(0, "failed to add host to network:", h.ID.String(), h.Name, network, err.Error())
+				continue
+			}
+			logger.Log(1, "added new node", newNode.ID.String(), "to host", h.Name)
+			hostactions.AddAction(models.HostUpdate{
+				Action: models.JoinHostToNetwork,
+				Host:   *h,
+				Node:   *newNode,
+			})
+		}
+	}
+	if servercfg.IsMessageQueueBackend() {
+		mq.HostUpdate(&models.HostUpdate{
+			Action: models.RequestAck,
+			Host:   *h,
+		})
+		if err := mq.PublishPeerUpdate(); err != nil {
+			logger.Log(0, "failed to publish peer update during registration -", err.Error())
+		}
+	}
+}
+
+func handleHostRegErr(conn *websocket.Conn, err error) {
+	_ = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+	if err != nil {
+		logger.Log(0, "error during host registration via auth:", err.Error())
+	}
+}

+ 0 - 162
auth/nodesession.go

@@ -1,162 +0,0 @@
-package auth
-
-import (
-	"encoding/json"
-	"fmt"
-	"strings"
-	"time"
-
-	"github.com/gorilla/websocket"
-	"github.com/gravitl/netmaker/logger"
-	"github.com/gravitl/netmaker/logic"
-	"github.com/gravitl/netmaker/logic/pro/netcache"
-	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/models/promodels"
-	"github.com/gravitl/netmaker/servercfg"
-)
-
-// SessionHandler - called by the HTTP router when user
-// is calling netclient with --login-server parameter in order to authenticate
-// via SSO mechanism by OAuth2 protocol flow.
-// This triggers a session start and it is managed by the flow implemented here and callback
-// When this method finishes - the auth flow has finished either OK or by timeout or any other error occured
-func SessionHandler(conn *websocket.Conn) {
-	defer conn.Close()
-
-	// If reached here we have a session from user to handle...
-	messageType, message, err := conn.ReadMessage()
-	if err != nil {
-		logger.Log(0, "Error during message reading:", err.Error())
-		return
-	}
-	var loginMessage promodels.LoginMsg
-
-	err = json.Unmarshal(message, &loginMessage)
-	if err != nil {
-		logger.Log(0, "Failed to unmarshall data err=", err.Error())
-		return
-	}
-	logger.Log(1, "SSO node join attempted with info network:", loginMessage.Network, "node identifier:", loginMessage.Mac, "user:", loginMessage.User)
-
-	req := new(netcache.CValue)
-	req.Value = string(loginMessage.Mac)
-	req.Network = loginMessage.Network
-	req.Pass = ""
-	req.User = ""
-	// Add any extra parameter provided in the configuration to the Authorize Endpoint request??
-	stateStr := logic.RandomString(node_signin_length)
-	if err := netcache.Set(stateStr, req); err != nil {
-		logger.Log(0, "Failed to process sso request -", err.Error())
-		return
-	}
-	// Wait for the user to finish his auth flow...
-	// TBD: what should be the timeout here ?
-	timeout := make(chan bool, 1)
-	answer := make(chan string, 1)
-	defer close(answer)
-	defer close(timeout)
-
-	if _, err = logic.GetNetwork(loginMessage.Network); err != nil {
-		err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
-		if err != nil {
-			logger.Log(0, "error during message writing:", err.Error())
-		}
-		return
-	}
-
-	if loginMessage.User != "" { // handle basic auth
-		// verify that server supports basic auth, then authorize the request with given credentials
-		// check if user is allowed to join via node sso
-		// i.e. user is admin or user has network permissions
-		if !servercfg.IsBasicAuthEnabled() {
-			err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
-			if err != nil {
-				logger.Log(0, "error during message writing:", err.Error())
-			}
-		}
-		_, err := logic.VerifyAuthRequest(models.UserAuthParams{
-			UserName: loginMessage.User,
-			Password: loginMessage.Password,
-		})
-		if err != nil {
-			err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
-			if err != nil {
-				logger.Log(0, "error during message writing:", err.Error())
-			}
-			return
-		}
-		_, err = isUserIsAllowed(loginMessage.User, loginMessage.Network, false)
-		if err != nil {
-			err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
-			if err != nil {
-				logger.Log(0, "error during message writing:", err.Error())
-			}
-			return
-		}
-
-		// Give the user the access token via Pass in the DB
-		if err = netcache.Set(stateStr, req); err != nil {
-			logger.Log(0, "machine failed to complete join on network,", loginMessage.Network, "-", err.Error())
-			return
-		}
-	} else { // handle SSO / OAuth
-		if auth_provider == nil {
-			err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
-			if err != nil {
-				logger.Log(0, "error during message writing:", err.Error())
-			}
-			return
-		}
-		redirectUrl = fmt.Sprintf("https://%s/api/oauth/register/%s", servercfg.GetAPIConnString(), stateStr)
-		err = conn.WriteMessage(messageType, []byte(redirectUrl))
-		if err != nil {
-			logger.Log(0, "error during message writing:", err.Error())
-		}
-	}
-
-	go func() {
-		for {
-			cachedReq, err := netcache.Get(stateStr)
-			if err != nil {
-				if strings.Contains(err.Error(), "expired") {
-					logger.Log(0, "timeout occurred while waiting for SSO on network", loginMessage.Network)
-					timeout <- true
-					break
-				}
-				continue
-			} else if cachedReq.Pass != "" {
-				logger.Log(0, "node SSO process completed for user", cachedReq.User, "on network", loginMessage.Network)
-				answer <- cachedReq.Pass
-				break
-			}
-			time.Sleep(500) // try it 2 times per second to see if auth is completed
-		}
-	}()
-
-	select {
-	case result := <-answer:
-		// a read from req.answerCh has occurred
-		err = conn.WriteMessage(messageType, []byte(result))
-		if err != nil {
-			logger.Log(0, "Error during message writing:", err.Error())
-		}
-	case <-timeout:
-		logger.Log(0, "Authentication server time out for a node on network", loginMessage.Network)
-		// the read from req.answerCh has timed out
-		err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
-		if err != nil {
-			logger.Log(0, "Error during message writing:", err.Error())
-		}
-	}
-	// The entry is not needed anymore, but we will let the producer to close it to avoid panic cases
-	if err = netcache.Del(stateStr); err != nil {
-		logger.Log(0, "failed to remove node SSO cache entry", err.Error())
-	}
-	// Cleanly close the connection by sending a close message and then
-	// waiting (with timeout) for the server to close the connection.
-	err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
-	if err != nil {
-		logger.Log(0, "write close:", err.Error())
-		return
-	}
-}

+ 8 - 81
auth/nodecallback.go → auth/register_callback.go

@@ -19,13 +19,13 @@ var (
 	redirectUrl string
 	redirectUrl string
 )
 )
 
 
-// HandleNodeSSOCallback handles the callback from the sso endpoint
+// HandleHostSSOCallback handles the callback from the sso endpoint
 // It is the analogue of auth.handleNodeSSOCallback but takes care of the end point flow
 // It is the analogue of auth.handleNodeSSOCallback but takes care of the end point flow
 // Retrieves the mkey from the state cache and adds the machine to the users email namespace
 // Retrieves the mkey from the state cache and adds the machine to the users email namespace
 // TODO: A confirmation page for new machines should be added to avoid phishing vulnerabilities
 // TODO: A confirmation page for new machines should be added to avoid phishing vulnerabilities
 // TODO: Add groups information from OIDC tokens into machine HostInfo
 // TODO: Add groups information from OIDC tokens into machine HostInfo
 // Listens in /oidc/callback.
 // Listens in /oidc/callback.
-func HandleNodeSSOCallback(w http.ResponseWriter, r *http.Request) {
+func HandleHostSSOCallback(w http.ResponseWriter, r *http.Request) {
 
 
 	var functions = getCurrentAuthFunctions()
 	var functions = getCurrentAuthFunctions()
 	if functions == nil {
 	if functions == nil {
@@ -71,16 +71,7 @@ func HandleNodeSSOCallback(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
-	user, err := isUserIsAllowed(userClaims.getUserName(), reqKeyIf.Network, true)
-	if err != nil {
-		logger.Log(0, "error occurred during SSO node join for user", userClaims.getUserName(), "on network", reqKeyIf.Network, "-", err.Error())
-		response := returnErrTemplate(user.UserName, err.Error(), state, reqKeyIf)
-		w.WriteHeader(http.StatusNotAcceptable)
-		w.Write(response)
-		return
-	}
-
-	logger.Log(1, "registering new node for user:", user.UserName, "on network", reqKeyIf.Network)
+	logger.Log(1, "registering host for user:", userClaims.getUserName(), reqKeyIf.Host.Name, reqKeyIf.Host.ID.String())
 
 
 	// Send OK to user in the browser
 	// Send OK to user in the browser
 	var response bytes.Buffer
 	var response bytes.Buffer
@@ -89,32 +80,15 @@ func HandleNodeSSOCallback(w http.ResponseWriter, r *http.Request) {
 		Verb: "Authenticated",
 		Verb: "Authenticated",
 	}); err != nil {
 	}); err != nil {
 		logger.Log(0, "Could not render SSO callback template ", err.Error())
 		logger.Log(0, "Could not render SSO callback template ", err.Error())
-		response := returnErrTemplate(user.UserName, "Could not render SSO callback template", state, reqKeyIf)
+		response := returnErrTemplate(reqKeyIf.User, "Could not render SSO callback template", state, reqKeyIf)
 		w.WriteHeader(http.StatusInternalServerError)
 		w.WriteHeader(http.StatusInternalServerError)
 		w.Write(response)
 		w.Write(response)
-
 	} else {
 	} else {
 		w.WriteHeader(http.StatusOK)
 		w.WriteHeader(http.StatusOK)
 		w.Write(response.Bytes())
 		w.Write(response.Bytes())
 	}
 	}
 
 
-	// Need to send access key to the client
-	logger.Log(1, "Handling new machine addition to network",
-		reqKeyIf.Network, "with key",
-		reqKeyIf.Value, " identity:", userClaims.getUserName(), "claims:", fmt.Sprintf("%+v", userClaims))
-
-	var answer string
-	// The registation logic is starting here:
-	// we request access key with 1 use for the required network
-	// accessToken, err := requestAccessKey(reqKeyIf.Network, 1, userClaims.getUserName())
-	// if err != nil {
-	// 	answer = fmt.Sprintf("Error from the netmaker controller %s", err.Error())
-	// } else {
-	// 	answer = fmt.Sprintf("AccessToken: %s", accessToken)
-	// }
-	logger.Log(0, "Updating the token for the client request ... ")
-	// Give the user the access token via Pass in the DB
-	reqKeyIf.Pass = answer
+	reqKeyIf.User = userClaims.getUserName() // set the cached registering hosts' user
 	if err = netcache.Set(state, reqKeyIf); err != nil {
 	if err = netcache.Set(state, reqKeyIf); err != nil {
 		logger.Log(0, "machine failed to complete join on network,", reqKeyIf.Network, "-", err.Error())
 		logger.Log(0, "machine failed to complete join on network,", reqKeyIf.Network, "-", err.Error())
 		return
 		return
@@ -151,10 +125,10 @@ func returnErrTemplate(uname, message, state string, ncache *netcache.CValue) []
 	return response.Bytes()
 	return response.Bytes()
 }
 }
 
 
-// RegisterNodeSSO redirects to the IDP for authentication
+// RegisterHostSSO redirects to the IDP for authentication
 // Puts machine key in cache so the callback can retrieve it using the oidc state param
 // Puts machine key in cache so the callback can retrieve it using the oidc state param
 // Listens in /oidc/register/:regKey.
 // Listens in /oidc/register/:regKey.
-func RegisterNodeSSO(w http.ResponseWriter, r *http.Request) {
+func RegisterHostSSO(w http.ResponseWriter, r *http.Request) {
 
 
 	if auth_provider == nil {
 	if auth_provider == nil {
 		w.WriteHeader(http.StatusBadRequest)
 		w.WriteHeader(http.StatusBadRequest)
@@ -165,63 +139,16 @@ func RegisterNodeSSO(w http.ResponseWriter, r *http.Request) {
 
 
 	// machineKeyStr this is not key but state
 	// machineKeyStr this is not key but state
 	machineKeyStr := vars["regKey"]
 	machineKeyStr := vars["regKey"]
-	logger.Log(1, "requested key:", machineKeyStr)
-
 	if machineKeyStr == "" {
 	if machineKeyStr == "" {
 		w.WriteHeader(http.StatusBadRequest)
 		w.WriteHeader(http.StatusBadRequest)
 		w.Write([]byte("invalid login attempt"))
 		w.Write([]byte("invalid login attempt"))
 		return
 		return
 	}
 	}
 
 
-	// machineKeyStr this not key but state
-	authURL := auth_provider.AuthCodeURL(machineKeyStr)
-	//authURL = authURL + "&connector_id=" + "google"
-	logger.Log(0, "Redirecting to ", authURL, " for authentication")
-
-	http.Redirect(w, r, authURL, http.StatusSeeOther)
-
+	http.Redirect(w, r, auth_provider.AuthCodeURL(machineKeyStr), http.StatusSeeOther)
 }
 }
 
 
 // == private ==
 // == private ==
-// API to create an access key for a given network with a given name
-// func requestAccessKey(network string, uses int, name string) (accessKey string, err error) {
-
-// 	var sAccessKey models.AccessKey
-// 	var sNetwork models.Network
-
-// 	sNetwork, err = logic.GetParentNetwork(network)
-// 	if err != nil {
-// 		logger.Log(0, "err calling GetParentNetwork API=%s", err.Error())
-// 		return "", fmt.Errorf("internal controller error %s", err.Error())
-// 	}
-// 	// If a key already exists, we recreate it.
-// 	// @TODO Is that a preferred handling ? We could also trying to re-use.
-// 	// can happen if user started log in but did not finish
-// 	for _, currentkey := range sNetwork.AccessKeys {
-// 		if currentkey.Name == name {
-// 			logger.Log(0, "erasing existing AccessKey for: ", name)
-// 			err = logic.DeleteKey(currentkey.Name, network)
-// 			if err != nil {
-// 				logger.Log(0, "err calling CreateAccessKey API ", err.Error())
-// 				return "", fmt.Errorf("key already exists. Contact admin to resolve")
-// 			}
-// 			break
-// 		}
-// 	}
-// 	// Only one usage is needed - for the next time new access key will be required
-// 	// it will be created next time after another IdP approval
-// 	sAccessKey.Uses = 1
-// 	sAccessKey.Name = name
-
-// 	accessToken, err := logic.CreateAccessKey(sAccessKey, sNetwork)
-// 	if err != nil {
-// 		logger.Log(0, "err calling CreateAccessKey API ", err.Error())
-// 		return "", fmt.Errorf("error from the netmaker controller %s", err.Error())
-// 	} else {
-// 		logger.Log(1, "created access key", sAccessKey.Name, "on", network)
-// 	}
-// 	return accessToken.AccessString, nil
-// }
 
 
 func isUserIsAllowed(username, network string, shouldAddUser bool) (*models.User, error) {
 func isUserIsAllowed(username, network string, shouldAddUser bool) (*models.User, error) {
 
 

+ 21 - 0
cli/cmd/host/refresh_keys.go

@@ -0,0 +1,21 @@
+package host
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var hostRefreshKeysCmd = &cobra.Command{
+	Use:   "refresh_keys [HOST ID] ",
+	Args:  cobra.MaximumNArgs(1),
+	Short: "Refresh wireguard keys on host",
+	Long: `Refresh wireguard keys on specified or all hosts
+	If HOSTID is not specified, all hosts will be updated`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.RefreshKeys(args[0]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(hostRefreshKeysCmd)
+}

+ 0 - 20
cli/cmd/network/refresh_keys.go

@@ -1,20 +0,0 @@
-package network
-
-import (
-	"github.com/gravitl/netmaker/cli/functions"
-	"github.com/spf13/cobra"
-)
-
-var networkRefreshKeysCmd = &cobra.Command{
-	Use:   "refresh_keys [NETWORK NAME]",
-	Short: "Refresh public and private key pairs of a network",
-	Long:  `Refresh public and private key pairs of a network`,
-	Args:  cobra.ExactArgs(1),
-	Run: func(cmd *cobra.Command, args []string) {
-		functions.PrettyPrint(functions.RefreshKeys(args[0]))
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(networkRefreshKeysCmd)
-}

+ 0 - 72
cli/cmd/network/update.go

@@ -1,72 +0,0 @@
-package network
-
-import (
-	"encoding/json"
-	"log"
-	"os"
-
-	"github.com/gravitl/netmaker/cli/functions"
-	"github.com/gravitl/netmaker/models"
-	"github.com/spf13/cobra"
-)
-
-var networkUpdateCmd = &cobra.Command{
-	Use:   "update [NETWORK NAME]",
-	Short: "Update a Network",
-	Long:  `Update a Network`,
-	Args:  cobra.ExactArgs(1),
-	Run: func(cmd *cobra.Command, args []string) {
-		var (
-			networkName = args[0]
-			network     = &models.Network{}
-		)
-		if networkDefinitionFilePath != "" {
-			content, err := os.ReadFile(networkDefinitionFilePath)
-			if err != nil {
-				log.Fatal("Error when opening file: ", err)
-			}
-			if err := json.Unmarshal(content, network); err != nil {
-				log.Fatal(err)
-			}
-		} else {
-			network.NetID = networkName
-			network.AddressRange = address
-			if address6 != "" {
-				network.AddressRange6 = address6
-				network.IsIPv6 = "yes"
-			}
-			if udpHolePunch {
-				network.DefaultUDPHolePunch = "yes"
-			}
-			if defaultACL {
-				network.DefaultACL = "yes"
-			}
-			network.DefaultInterface = defaultInterface
-			network.DefaultListenPort = int32(defaultListenPort)
-			network.NodeLimit = int32(nodeLimit)
-			network.DefaultKeepalive = int32(defaultKeepalive)
-			if allowManualSignUp {
-				network.AllowManualSignUp = "yes"
-			}
-			network.DefaultExtClientDNS = defaultExtClientDNS
-			network.DefaultMTU = int32(defaultMTU)
-		}
-		functions.PrettyPrint(functions.UpdateNetwork(networkName, network))
-	},
-}
-
-func init() {
-	networkUpdateCmd.Flags().StringVar(&networkDefinitionFilePath, "file", "", "Path to network_definition.json")
-	networkUpdateCmd.Flags().StringVar(&address, "ipv4_addr", "", "IPv4 address of the network")
-	networkUpdateCmd.Flags().StringVar(&address6, "ipv6_addr", "", "IPv6 address of the network")
-	networkUpdateCmd.Flags().BoolVar(&udpHolePunch, "udp_hole_punch", false, "Enable UDP Hole Punching ?")
-	networkUpdateCmd.Flags().BoolVar(&defaultACL, "default_acl", false, "Enable default Access Control List ?")
-	networkUpdateCmd.Flags().StringVar(&defaultInterface, "interface", "", "Name of the network interface")
-	networkUpdateCmd.Flags().StringVar(&defaultExtClientDNS, "ext_client_dns", "", "IPv4 address of DNS server to be used by external clients")
-	networkUpdateCmd.Flags().IntVar(&defaultListenPort, "listen_port", 0, "Default wireguard port each node will attempt to use")
-	networkUpdateCmd.Flags().IntVar(&nodeLimit, "node_limit", 0, "Maximum number of nodes that can be associated with this network")
-	networkUpdateCmd.Flags().IntVar(&defaultKeepalive, "keep_alive", 0, "Keep Alive in seconds")
-	networkUpdateCmd.Flags().IntVar(&defaultMTU, "mtu", 0, "MTU size")
-	networkUpdateCmd.Flags().BoolVar(&allowManualSignUp, "manual_signup", false, "Allow manual signup ?")
-	rootCmd.AddCommand(networkUpdateCmd)
-}

+ 9 - 0
cli/functions/host.go

@@ -48,3 +48,12 @@ func CreateRelay(hostID string, relayedHosts []string) *models.ApiHost {
 func DeleteRelay(hostID string) *models.ApiHost {
 func DeleteRelay(hostID string) *models.ApiHost {
 	return request[models.ApiHost](http.MethodDelete, fmt.Sprintf("/api/hosts/%s/relay", hostID), nil)
 	return request[models.ApiHost](http.MethodDelete, fmt.Sprintf("/api/hosts/%s/relay", hostID), nil)
 }
 }
+
+// RefreshKeys - refresh wireguard keys
+func RefreshKeys(hostID string) any {
+	if hostID == "" {
+		return request[any](http.MethodPut, "/api/hosts/keys", nil)
+	}
+	return request[any](http.MethodPut, fmt.Sprintf("/api/hosts/%s/keys", hostID), nil)
+
+}

+ 0 - 5
cli/functions/network.go

@@ -38,8 +38,3 @@ func GetNetwork(name string) *models.Network {
 func DeleteNetwork(name string) *string {
 func DeleteNetwork(name string) *string {
 	return request[string](http.MethodDelete, "/api/networks/"+name, nil)
 	return request[string](http.MethodDelete, "/api/networks/"+name, nil)
 }
 }
-
-// RefreshKeys - refresh public and private key pairs for a network
-func RefreshKeys(networkName string) *models.Network {
-	return request[models.Network](http.MethodPost, fmt.Sprintf("/api/networks/%s/keyupdate", networkName), nil)
-}

+ 4 - 3
compose/docker-compose-emqx.yml

@@ -3,8 +3,8 @@ version: "3.4"
 services:
 services:
   netmaker:
   netmaker:
     container_name: netmaker
     container_name: netmaker
-    image: gravitl/netmaker:v0.18.6
-    restart: always
+    image: gravitl/netmaker:v0.18.7
+    restart: on-failure
     volumes:
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
       - sqldata:/root/data
@@ -19,6 +19,7 @@ services:
       COREDNS_ADDR: "SERVER_PUBLIC_IP"
       COREDNS_ADDR: "SERVER_PUBLIC_IP"
       DNS_MODE: "on"
       DNS_MODE: "on"
       SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN"
       SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN"
+      NETCLIENT_AUTO_UPDATE: "enabled"
       API_PORT: "8081"
       API_PORT: "8081"
       MASTER_KEY: "REPLACE_MASTER_KEY"
       MASTER_KEY: "REPLACE_MASTER_KEY"
       CORS_ALLOWED_ORIGIN: "*"
       CORS_ALLOWED_ORIGIN: "*"
@@ -35,7 +36,7 @@ services:
       - "3478:3478/udp"
       - "3478:3478/udp"
   netmaker-ui:
   netmaker-ui:
     container_name: netmaker-ui
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.18.6
+    image: gravitl/netmaker-ui:v0.18.7
     depends_on:
     depends_on:
       - netmaker
       - netmaker
     links:
     links:

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

@@ -4,7 +4,7 @@ services:
   netmaker:
   netmaker:
     container_name: netmaker
     container_name: netmaker
     image: gravitl/netmaker:REPLACE_SERVER_IMAGE_TAG
     image: gravitl/netmaker:REPLACE_SERVER_IMAGE_TAG
-    restart: always
+    restart: on-failure
     volumes:
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
       - sqldata:/root/data
@@ -17,6 +17,7 @@ services:
       COREDNS_ADDR: "SERVER_PUBLIC_IP"
       COREDNS_ADDR: "SERVER_PUBLIC_IP"
       DNS_MODE: "on"
       DNS_MODE: "on"
       SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN"
       SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN"
+      NETCLIENT_AUTO_UPDATE: "enabled"
       API_PORT: "8081"
       API_PORT: "8081"
       MASTER_KEY: "REPLACE_MASTER_KEY"
       MASTER_KEY: "REPLACE_MASTER_KEY"
       CORS_ALLOWED_ORIGIN: "*"
       CORS_ALLOWED_ORIGIN: "*"

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

@@ -3,10 +3,10 @@ version: "3.4"
 services:
 services:
   netclient:
   netclient:
     container_name: netclient
     container_name: netclient
-    image: 'gravitl/netclient:v0.18.6'
+    image: 'gravitl/netclient:v0.18.7'
     hostname: netmaker-1
     hostname: netmaker-1
     network_mode: host
     network_mode: host
-    restart: always
+    restart: on-failure
     environment:
     environment:
       TOKEN: "TOKEN_VALUE"
       TOKEN: "TOKEN_VALUE"
     volumes:
     volumes:

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

@@ -4,7 +4,7 @@ services:
   netmaker: # The Primary Server for running Netmaker
   netmaker: # The Primary Server for running Netmaker
     container_name: netmaker
     container_name: netmaker
     image: gravitl/netmaker:REPLACE_SERVER_IMAGE_TAG
     image: gravitl/netmaker:REPLACE_SERVER_IMAGE_TAG
-    restart: always
+    restart: on-failure
     volumes: # Volume mounts necessary for sql, coredns, and mqtt
     volumes: # Volume mounts necessary for sql, coredns, and mqtt
       - dnsconfig:/root/config/dnsconfig
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
       - sqldata:/root/data
@@ -14,6 +14,7 @@ services:
       SERVER_NAME: "NETMAKER_BASE_DOMAIN" # The base domain of netmaker
       SERVER_NAME: "NETMAKER_BASE_DOMAIN" # The base domain of netmaker
       SERVER_HOST: "SERVER_PUBLIC_IP" # Set to public IP of machine.
       SERVER_HOST: "SERVER_PUBLIC_IP" # Set to public IP of machine.
       SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN" # Overrides SERVER_HOST if set. Useful for making HTTP available via different interfaces/networks.
       SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN" # Overrides SERVER_HOST if set. Useful for making HTTP available via different interfaces/networks.
+      NETCLIENT_AUTO_UPDATE: "enabled" # Enable auto update of netclient ? ENUM:- enabled,disabled | default: enabled
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
       COREDNS_ADDR: "SERVER_PUBLIC_IP" # Address of the CoreDNS server. Defaults to SERVER_HOST
       COREDNS_ADDR: "SERVER_PUBLIC_IP" # Address of the CoreDNS server. Defaults to SERVER_HOST
       DNS_MODE: "on" # Enables DNS Mode, meaning all nodes will set hosts file for private dns settings.
       DNS_MODE: "on" # Enables DNS Mode, meaning all nodes will set hosts file for private dns settings.

+ 2 - 1
compose/docker-compose.yml

@@ -4,7 +4,7 @@ services:
   netmaker:
   netmaker:
     container_name: netmaker
     container_name: netmaker
     image: gravitl/netmaker:REPLACE_SERVER_IMAGE_TAG
     image: gravitl/netmaker:REPLACE_SERVER_IMAGE_TAG
-    restart: always
+    restart: on-failure
     volumes:
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
       - sqldata:/root/data
@@ -17,6 +17,7 @@ services:
       COREDNS_ADDR: "SERVER_PUBLIC_IP"
       COREDNS_ADDR: "SERVER_PUBLIC_IP"
       DNS_MODE: "on"
       DNS_MODE: "on"
       SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN"
       SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN"
+      NETCLIENT_AUTO_UPDATE: "enabled"
       API_PORT: "8081"
       API_PORT: "8081"
       MASTER_KEY: "REPLACE_MASTER_KEY"
       MASTER_KEY: "REPLACE_MASTER_KEY"
       CORS_ALLOWED_ORIGIN: "*"
       CORS_ALLOWED_ORIGIN: "*"

+ 1 - 0
config/config.go

@@ -40,6 +40,7 @@ type ServerConfig struct {
 	ServerBrokerEndpoint string    `yaml:"serverbrokerendpoint"`
 	ServerBrokerEndpoint string    `yaml:"serverbrokerendpoint"`
 	BrokerType           string    `yaml:"brokertype"`
 	BrokerType           string    `yaml:"brokertype"`
 	EmqxRestEndpoint     string    `yaml:"emqxrestendpoint"`
 	EmqxRestEndpoint     string    `yaml:"emqxrestendpoint"`
+	NetclientAutoUpdate  string    `yaml:"netclientautoupdate"`
 	MasterKey            string    `yaml:"masterkey"`
 	MasterKey            string    `yaml:"masterkey"`
 	DNSKey               string    `yaml:"dnskey"`
 	DNSKey               string    `yaml:"dnskey"`
 	AllowedOrigin        string    `yaml:"allowedorigin"`
 	AllowedOrigin        string    `yaml:"allowedorigin"`

+ 1 - 0
controllers/dns.go

@@ -70,6 +70,7 @@ func getAllDNS(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
+	logic.SortDNSEntrys(dns[:])
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(dns)
 	json.NewEncoder(w).Encode(dns)
 }
 }

+ 1 - 1
controllers/docs.go

@@ -10,7 +10,7 @@
 //
 //
 //	Schemes: https
 //	Schemes: https
 //	BasePath: /
 //	BasePath: /
-//	Version: 0.18.6
+//	Version: 0.18.7
 //	Host: netmaker.io
 //	Host: netmaker.io
 //
 //
 //	Consumes:
 //	Consumes:

+ 2 - 32
controllers/enrollmentkeys.go

@@ -7,9 +7,9 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/gorilla/mux"
 	"github.com/gorilla/mux"
+	"github.com/gravitl/netmaker/auth"
 	"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/hostactions"
 	"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"
 	"github.com/gravitl/netmaker/servercfg"
@@ -230,35 +230,5 @@ func handleHostRegister(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(&response)
 	json.NewEncoder(w).Encode(&response)
 	// notify host of changes, peer and node updates
 	// notify host of changes, peer and node updates
-	go checkNetRegAndHostUpdate(enrollmentKey.Networks, &newHost)
-}
-
-// run through networks and send a host update
-func checkNetRegAndHostUpdate(networks []string, h *models.Host) {
-	// publish host update through MQ
-	for i := range networks {
-		network := networks[i]
-		if ok, _ := logic.NetworkExists(network); ok {
-			newNode, err := logic.UpdateHostNetwork(h, network, true)
-			if err != nil {
-				logger.Log(0, "failed to add host to network:", h.ID.String(), h.Name, network, err.Error())
-				continue
-			}
-			logger.Log(1, "added new node", newNode.ID.String(), "to host", h.Name)
-			hostactions.AddAction(models.HostUpdate{
-				Action: models.JoinHostToNetwork,
-				Host:   *h,
-				Node:   *newNode,
-			})
-		}
-	}
-	if servercfg.IsMessageQueueBackend() {
-		mq.HostUpdate(&models.HostUpdate{
-			Action: models.RequestAck,
-			Host:   *h,
-		})
-		if err := mq.PublishPeerUpdate(); err != nil {
-			logger.Log(0, "failed to publish peer update during registration -", err.Error())
-		}
-	}
+	go auth.CheckNetRegAndHostUpdate(enrollmentKey.Networks, &newHost)
 }
 }

+ 1 - 0
controllers/ext_client.go

@@ -117,6 +117,7 @@ func getAllExtClients(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 
 
 	//Return all the extclients in JSON format
 	//Return all the extclients in JSON format
+	logic.SortExtClient(clients[:])
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(clients)
 	json.NewEncoder(w).Encode(clients)
 }
 }

+ 81 - 0
controllers/hosts.go

@@ -20,6 +20,8 @@ import (
 
 
 func hostHandlers(r *mux.Router) {
 func hostHandlers(r *mux.Router) {
 	r.HandleFunc("/api/hosts", logic.SecurityCheck(true, http.HandlerFunc(getHosts))).Methods(http.MethodGet)
 	r.HandleFunc("/api/hosts", logic.SecurityCheck(true, http.HandlerFunc(getHosts))).Methods(http.MethodGet)
+	r.HandleFunc("/api/hosts/keys", logic.SecurityCheck(true, http.HandlerFunc(updateAllKeys))).Methods(http.MethodPut)
+	r.HandleFunc("/api/hosts/{hostid}/keys", logic.SecurityCheck(true, http.HandlerFunc(updateKeys))).Methods(http.MethodPut)
 	r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(updateHost))).Methods(http.MethodPut)
 	r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(updateHost))).Methods(http.MethodPut)
 	r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(deleteHost))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(deleteHost))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/hosts/{hostid}/networks/{network}", logic.SecurityCheck(true, http.HandlerFunc(addHostToNetwork))).Methods(http.MethodPost)
 	r.HandleFunc("/api/hosts/{hostid}/networks/{network}", logic.SecurityCheck(true, http.HandlerFunc(addHostToNetwork))).Methods(http.MethodPost)
@@ -28,6 +30,7 @@ func hostHandlers(r *mux.Router) {
 	r.HandleFunc("/api/hosts/{hostid}/relay", logic.SecurityCheck(false, http.HandlerFunc(deleteHostRelay))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/hosts/{hostid}/relay", logic.SecurityCheck(false, http.HandlerFunc(deleteHostRelay))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/hosts/adm/authenticate", authenticateHost).Methods(http.MethodPost)
 	r.HandleFunc("/api/hosts/adm/authenticate", authenticateHost).Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/host", authorize(true, false, "host", http.HandlerFunc(pull))).Methods(http.MethodGet)
 	r.HandleFunc("/api/v1/host", authorize(true, false, "host", http.HandlerFunc(pull))).Methods(http.MethodGet)
+	r.HandleFunc("/api/v1/auth-register/host", socketHandler)
 }
 }
 
 
 // swagger:route GET /api/hosts hosts getHosts
 // swagger:route GET /api/hosts hosts getHosts
@@ -51,6 +54,7 @@ func getHosts(w http.ResponseWriter, r *http.Request) {
 	// return JSON/API formatted hosts
 	// return JSON/API formatted hosts
 	apiHosts := logic.GetAllHostsAPI(currentHosts[:])
 	apiHosts := logic.GetAllHostsAPI(currentHosts[:])
 	logger.Log(2, r.Header.Get("user"), "fetched all hosts")
 	logger.Log(2, r.Header.Get("user"), "fetched all hosts")
+	logic.SortApiHosts(apiHosts[:])
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(apiHosts)
 	json.NewEncoder(w).Encode(apiHosts)
 }
 }
@@ -445,3 +449,80 @@ func authenticateHost(response http.ResponseWriter, request *http.Request) {
 	response.Header().Set("Content-Type", "application/json")
 	response.Header().Set("Content-Type", "application/json")
 	response.Write(successJSONResponse)
 	response.Write(successJSONResponse)
 }
 }
+
+// swagger:route POST /api/hosts/keys host updateAllKeys
+//
+// Update keys for a network.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: networkBodyResponse
+func updateAllKeys(w http.ResponseWriter, r *http.Request) {
+	var errorResponse = models.ErrorResponse{}
+	w.Header().Set("Content-Type", "application/json")
+	hosts, err := logic.GetAllHosts()
+	if err != nil {
+		errorResponse.Code = http.StatusBadRequest
+		errorResponse.Message = err.Error()
+		logger.Log(0, r.Header.Get("user"),
+			"error retrieving hosts ", err.Error())
+		logic.ReturnErrorResponse(w, r, errorResponse)
+		return
+	}
+	go func() {
+		hostUpdate := models.HostUpdate{}
+		hostUpdate.Action = models.UpdateKeys
+		for _, host := range hosts {
+			hostUpdate.Host = host
+			logger.Log(2, "updating host", host.ID.String(), " for a key update")
+			if err = mq.HostUpdate(&hostUpdate); err != nil {
+				logger.Log(0, "failed to send update to node during a network wide key update", host.ID.String(), err.Error())
+			}
+		}
+	}()
+	logger.Log(2, r.Header.Get("user"), "updated keys for all hosts")
+	w.WriteHeader(http.StatusOK)
+}
+
+// swagger:route POST /api/hosts/{hostid}keys host updateKeys
+//
+// Update keys for a network.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: networkBodyResponse
+func updateKeys(w http.ResponseWriter, r *http.Request) {
+	var errorResponse = models.ErrorResponse{}
+	w.Header().Set("Content-Type", "application/json")
+	var params = mux.Vars(r)
+	hostid := params["hostid"]
+	host, err := logic.GetHost(hostid)
+	if err != nil {
+		logger.Log(0, "failed to retrieve host", hostid, err.Error())
+		errorResponse.Code = http.StatusBadRequest
+		errorResponse.Message = err.Error()
+		logger.Log(0, r.Header.Get("user"),
+			"error retrieving hosts ", err.Error())
+		logic.ReturnErrorResponse(w, r, errorResponse)
+		return
+	}
+	go func() {
+		hostUpdate := models.HostUpdate{
+			Action: models.UpdateKeys,
+			Host:   *host,
+		}
+		if err = mq.HostUpdate(&hostUpdate); err != nil {
+			logger.Log(0, "failed to send host key update", host.ID.String(), err.Error())
+		}
+	}()
+	logger.Log(2, r.Header.Get("user"), "updated key on host", host.Name)
+	w.WriteHeader(http.StatusOK)
+}

+ 2 - 1
controllers/migrate.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"net/http"
 	"net/http"
 
 
+	"github.com/gravitl/netmaker/auth"
 	"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"
@@ -83,5 +84,5 @@ func migrate(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(&response)
 	json.NewEncoder(w).Encode(&response)
 	logger.Log(0, "successfully migrated host", data.NewHost.Name, data.NewHost.ID.String())
 	logger.Log(0, "successfully migrated host", data.NewHost.Name, data.NewHost.ID.String())
 	// notify host of changes, peer and node updates
 	// notify host of changes, peer and node updates
-	go checkNetRegAndHostUpdate(networksToAdd, &data.NewHost)
+	go auth.CheckNetRegAndHostUpdate(networksToAdd, &data.NewHost)
 }
 }

+ 1 - 137
controllers/network.go

@@ -22,9 +22,7 @@ func networkHandlers(r *mux.Router) {
 	r.HandleFunc("/api/networks", logic.SecurityCheck(false, http.HandlerFunc(getNetworks))).Methods(http.MethodGet)
 	r.HandleFunc("/api/networks", logic.SecurityCheck(false, http.HandlerFunc(getNetworks))).Methods(http.MethodGet)
 	r.HandleFunc("/api/networks", logic.SecurityCheck(true, checkFreeTierLimits(networks_l, http.HandlerFunc(createNetwork)))).Methods(http.MethodPost)
 	r.HandleFunc("/api/networks", logic.SecurityCheck(true, checkFreeTierLimits(networks_l, http.HandlerFunc(createNetwork)))).Methods(http.MethodPost)
 	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(false, http.HandlerFunc(getNetwork))).Methods(http.MethodGet)
 	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(false, http.HandlerFunc(getNetwork))).Methods(http.MethodGet)
-	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(false, http.HandlerFunc(updateNetwork))).Methods(http.MethodPut)
 	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(deleteNetwork))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(deleteNetwork))).Methods(http.MethodDelete)
-	r.HandleFunc("/api/networks/{networkname}/keyupdate", logic.SecurityCheck(true, http.HandlerFunc(keyUpdate))).Methods(http.MethodPost)
 	// 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", logic.SecurityCheck(true, http.HandlerFunc(getNetworkACL))).Methods(http.MethodGet)
 	r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(getNetworkACL))).Methods(http.MethodGet)
@@ -71,6 +69,7 @@ func getNetworks(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 
 
 	logger.Log(2, r.Header.Get("user"), "fetched networks.")
 	logger.Log(2, r.Header.Get("user"), "fetched networks.")
+	logic.SortNetworks(allnetworks[:])
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(allnetworks)
 	json.NewEncoder(w).Encode(allnetworks)
 }
 }
@@ -104,141 +103,6 @@ func getNetwork(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(network)
 	json.NewEncoder(w).Encode(network)
 }
 }
 
 
-// swagger:route POST /api/networks/{networkname}/keyupdate networks keyUpdate
-//
-// Update keys for a network.
-//
-//			Schemes: https
-//
-//			Security:
-//	  		oauth
-//
-//			Responses:
-//				200: networkBodyResponse
-func keyUpdate(w http.ResponseWriter, r *http.Request) {
-	w.Header().Set("Content-Type", "application/json")
-	var params = mux.Vars(r)
-	netname := params["networkname"]
-	network, err := logic.KeyUpdate(netname)
-	if err != nil {
-		logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to update keys for network [%s]: %v",
-			netname, err))
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-		return
-	}
-	logger.Log(2, r.Header.Get("user"), "updated key on network", netname)
-	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(network)
-	nodes, err := logic.GetNetworkNodes(netname)
-	if err != nil {
-		logger.Log(0, "failed to retrieve network nodes for network", netname, err.Error())
-		return
-	}
-	for _, node := range nodes {
-		logger.Log(2, "updating node ", node.ID.String(), " for a key update")
-		if err = mq.NodeUpdate(&node); err != nil {
-			logger.Log(1, "failed to send update to node during a network wide key update", node.ID.String(), err.Error())
-		}
-	}
-}
-
-// swagger:route PUT /api/networks/{networkname} networks updateNetwork
-//
-// Update a network.
-//
-//			Schemes: https
-//
-//			Security:
-//	  		oauth
-//
-//			Responses:
-//				200: networkBodyResponse
-func updateNetwork(w http.ResponseWriter, r *http.Request) {
-	w.Header().Set("Content-Type", "application/json")
-	var params = mux.Vars(r)
-	var network models.Network
-	netname := params["networkname"]
-
-	network, err := logic.GetParentNetwork(netname)
-	if err != nil {
-		logger.Log(0, r.Header.Get("user"), "failed to get network info: ",
-			err.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-		return
-	}
-	var newNetwork models.Network
-	err = json.NewDecoder(r.Body).Decode(&newNetwork)
-	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
-	}
-	rangeupdate4, rangeupdate6, holepunchupdate, groupsDelta, userDelta, err := logic.UpdateNetwork(&network, &newNetwork)
-	if err != nil {
-		logger.Log(0, r.Header.Get("user"), "failed to update network: ",
-			err.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-		return
-	}
-
-	if len(groupsDelta) > 0 {
-		for _, g := range groupsDelta {
-			users, err := logic.GetGroupUsers(g)
-			if err == nil {
-				for _, user := range users {
-					logic.AdjustNetworkUserPermissions(&user, &newNetwork)
-				}
-			}
-		}
-	}
-	if len(userDelta) > 0 {
-		for _, uname := range userDelta {
-			user, err := logic.GetReturnUser(uname)
-			if err == nil {
-				logic.AdjustNetworkUserPermissions(&user, &newNetwork)
-			}
-		}
-	}
-	if rangeupdate4 {
-		err = logic.UpdateNetworkNodeAddresses(network.NetID)
-		if err != nil {
-			logger.Log(0, r.Header.Get("user"),
-				fmt.Sprintf("failed to update network [%s] ipv4 addresses: %v",
-					network.NetID, err.Error()))
-			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-			return
-		}
-	}
-	if rangeupdate6 {
-		err = logic.UpdateNetworkNodeAddresses6(network.NetID)
-		if err != nil {
-			logger.Log(0, r.Header.Get("user"),
-				fmt.Sprintf("failed to update network [%s] ipv6 addresses: %v",
-					network.NetID, err.Error()))
-			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-			return
-		}
-	}
-	if rangeupdate4 || rangeupdate6 || holepunchupdate {
-		nodes, err := logic.GetNetworkNodes(network.NetID)
-		if err != nil {
-			logger.Log(0, r.Header.Get("user"),
-				fmt.Sprintf("failed to get network [%s] nodes: %v",
-					network.NetID, err.Error()))
-			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-			return
-		}
-		for _, node := range nodes {
-			runUpdates(&node, true)
-		}
-	}
-
-	logger.Log(1, r.Header.Get("user"), "updated network", netname)
-	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(newNetwork)
-}
-
 // swagger:route PUT /api/networks/{networkname}/acls networks updateNetworkACL
 // swagger:route PUT /api/networks/{networkname}/acls networks updateNetworkACL
 //
 //
 // Update a network ACL (Access Control List).
 // Update a network ACL (Access Control List).

+ 1 - 0
controllers/node.go

@@ -340,6 +340,7 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) {
 	// return all the nodes in JSON/API format
 	// return all the nodes in JSON/API format
 	apiNodes := logic.GetAllNodesAPI(nodes[:])
 	apiNodes := logic.GetAllNodesAPI(nodes[:])
 	logger.Log(3, r.Header.Get("user"), "fetched all nodes they have access to")
 	logger.Log(3, r.Header.Get("user"), "fetched all nodes they have access to")
+	logic.SortApiNodes(apiNodes[:])
 	w.WriteHeader(http.StatusOK)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(apiNodes)
 	json.NewEncoder(w).Encode(apiNodes)
 }
 }

+ 3 - 3
controllers/user.go

@@ -33,9 +33,8 @@ func userHandlers(r *mux.Router) {
 	r.HandleFunc("/api/users", logic.SecurityCheck(true, http.HandlerFunc(getUsers))).Methods(http.MethodGet)
 	r.HandleFunc("/api/users", logic.SecurityCheck(true, http.HandlerFunc(getUsers))).Methods(http.MethodGet)
 	r.HandleFunc("/api/oauth/login", auth.HandleAuthLogin).Methods(http.MethodGet)
 	r.HandleFunc("/api/oauth/login", auth.HandleAuthLogin).Methods(http.MethodGet)
 	r.HandleFunc("/api/oauth/callback", auth.HandleAuthCallback).Methods(http.MethodGet)
 	r.HandleFunc("/api/oauth/callback", auth.HandleAuthCallback).Methods(http.MethodGet)
-	r.HandleFunc("/api/oauth/node-handler", socketHandler)
 	r.HandleFunc("/api/oauth/headless", auth.HandleHeadlessSSO)
 	r.HandleFunc("/api/oauth/headless", auth.HandleHeadlessSSO)
-	r.HandleFunc("/api/oauth/register/{regKey}", auth.RegisterNodeSSO).Methods(http.MethodGet)
+	r.HandleFunc("/api/oauth/register/{regKey}", auth.RegisterHostSSO).Methods(http.MethodGet)
 }
 }
 
 
 // swagger:route POST /api/users/adm/authenticate user authenticateUser
 // swagger:route POST /api/users/adm/authenticate user authenticateUser
@@ -187,6 +186,7 @@ func getUsers(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 
 
+	logic.SortUsers(users[:])
 	logger.Log(2, r.Header.Get("user"), "fetched users")
 	logger.Log(2, r.Header.Get("user"), "fetched users")
 	json.NewEncoder(w).Encode(users)
 	json.NewEncoder(w).Encode(users)
 }
 }
@@ -483,5 +483,5 @@ func socketHandler(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 	// Start handling the session
 	// Start handling the session
-	// go auth.SessionHandler(conn)
+	go auth.SessionHandler(conn)
 }
 }

+ 9 - 9
ee/license.go

@@ -44,17 +44,17 @@ func ValidateLicense() error {
 	netmakerAccountID := servercfg.GetNetmakerAccountID()
 	netmakerAccountID := servercfg.GetNetmakerAccountID()
 	logger.Log(0, "proceeding with Netmaker license validation...")
 	logger.Log(0, "proceeding with Netmaker license validation...")
 	if len(licenseKeyValue) == 0 || len(netmakerAccountID) == 0 {
 	if len(licenseKeyValue) == 0 || len(netmakerAccountID) == 0 {
-		logger.FatalLog(errValidation.Error())
+		logger.FatalLog0(errValidation.Error())
 	}
 	}
 
 
 	apiPublicKey, err := getLicensePublicKey(licenseKeyValue)
 	apiPublicKey, err := getLicensePublicKey(licenseKeyValue)
 	if err != nil {
 	if err != nil {
-		logger.FatalLog(errValidation.Error())
+		logger.FatalLog0(errValidation.Error())
 	}
 	}
 
 
 	tempPubKey, tempPrivKey, err := FetchApiServerKeys()
 	tempPubKey, tempPrivKey, err := FetchApiServerKeys()
 	if err != nil {
 	if err != nil {
-		logger.FatalLog(errValidation.Error())
+		logger.FatalLog0(errValidation.Error())
 	}
 	}
 
 
 	licenseSecret := LicenseSecret{
 	licenseSecret := LicenseSecret{
@@ -64,32 +64,32 @@ func ValidateLicense() error {
 
 
 	secretData, err := json.Marshal(&licenseSecret)
 	secretData, err := json.Marshal(&licenseSecret)
 	if err != nil {
 	if err != nil {
-		logger.FatalLog(errValidation.Error())
+		logger.FatalLog0(errValidation.Error())
 	}
 	}
 
 
 	encryptedData, err := ncutils.BoxEncrypt(secretData, apiPublicKey, tempPrivKey)
 	encryptedData, err := ncutils.BoxEncrypt(secretData, apiPublicKey, tempPrivKey)
 	if err != nil {
 	if err != nil {
-		logger.FatalLog(errValidation.Error())
+		logger.FatalLog0(errValidation.Error())
 	}
 	}
 
 
 	validationResponse, err := validateLicenseKey(encryptedData, tempPubKey)
 	validationResponse, err := validateLicenseKey(encryptedData, tempPubKey)
 	if err != nil || len(validationResponse) == 0 {
 	if err != nil || len(validationResponse) == 0 {
-		logger.FatalLog(errValidation.Error())
+		logger.FatalLog0(errValidation.Error())
 	}
 	}
 
 
 	var licenseResponse ValidatedLicense
 	var licenseResponse ValidatedLicense
 	if err = json.Unmarshal(validationResponse, &licenseResponse); err != nil {
 	if err = json.Unmarshal(validationResponse, &licenseResponse); err != nil {
-		logger.FatalLog(errValidation.Error())
+		logger.FatalLog0(errValidation.Error())
 	}
 	}
 
 
 	respData, err := ncutils.BoxDecrypt(base64decode(licenseResponse.EncryptedLicense), apiPublicKey, tempPrivKey)
 	respData, err := ncutils.BoxDecrypt(base64decode(licenseResponse.EncryptedLicense), apiPublicKey, tempPrivKey)
 	if err != nil {
 	if err != nil {
-		logger.FatalLog(errValidation.Error())
+		logger.FatalLog0(errValidation.Error())
 	}
 	}
 
 
 	license := LicenseKey{}
 	license := LicenseKey{}
 	if err = json.Unmarshal(respData, &license); err != nil {
 	if err = json.Unmarshal(respData, &license); err != nil {
-		logger.FatalLog(errValidation.Error())
+		logger.FatalLog0(errValidation.Error())
 	}
 	}
 
 
 	Limits.Networks = math.MaxInt
 	Limits.Networks = math.MaxInt

+ 1 - 1
go.mod

@@ -9,7 +9,7 @@ require (
 	github.com/google/uuid v1.3.0
 	github.com/google/uuid v1.3.0
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/handlers v1.5.1
 	github.com/gorilla/mux v1.8.0
 	github.com/gorilla/mux v1.8.0
-	github.com/lib/pq v1.10.7
+	github.com/lib/pq v1.10.8
 	github.com/mattn/go-sqlite3 v1.14.16
 	github.com/mattn/go-sqlite3 v1.14.16
 	github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
 	github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e

+ 2 - 2
go.sum

@@ -77,8 +77,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4=
 github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4=
 github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ=
 github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ=
-github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
-github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.8 h1:3fdt97i/cwSU83+E0hZTC/Xpc9mTZxc6UWSCRcSbxiE=
+github.com/lib/pq v1.10.8/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
 github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
 github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
 github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=

+ 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.18.6
+        image: gravitl/netclient:v0.18.7
         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.18.6
+        image: gravitl/netclient:v0.18.7
         env:
         env:
         - name: TOKEN
         - name: TOKEN
           value: "TOKEN_VALUE"
           value: "TOKEN_VALUE"

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

@@ -79,7 +79,7 @@ spec:
           value: "Kubernetes"
           value: "Kubernetes"
         - name: VERBOSITY
         - name: VERBOSITY
           value: "3"
           value: "3"
-        image: gravitl/netmaker:v0.18.6
+        image: gravitl/netmaker:v0.18.7
         imagePullPolicy: Always
         imagePullPolicy: Always
         name: netmaker
         name: netmaker
         ports:
         ports:

+ 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.18.6
+        image: gravitl/netmaker-ui:v0.18.7
         ports:
         ports:
         - containerPort: 443
         - containerPort: 443
         env:
         env:

+ 6 - 0
logger/logger.go

@@ -138,6 +138,12 @@ func FatalLog(message ...string) {
 	os.Exit(2)
 	os.Exit(2)
 }
 }
 
 
+// FatalLog0 - exits os after logging
+func FatalLog0(message ...string) {
+	fmt.Printf("[%s] Fatal: %s \n", program, MakeString(" ", message...))
+	os.Exit(0)
+}
+
 // == private ==
 // == private ==
 
 
 // resetLogs - reallocates logs map
 // resetLogs - reallocates logs map

+ 12 - 1
logic/clients.go

@@ -1,6 +1,10 @@
 package logic
 package logic
 
 
-import "github.com/gravitl/netmaker/models"
+import (
+	"sort"
+
+	"github.com/gravitl/netmaker/models"
+)
 
 
 // functions defined here, handle client ACLs, should be set on ee
 // functions defined here, handle client ACLs, should be set on ee
 
 
@@ -51,3 +55,10 @@ func IsClientNodeAllowedByID(clientID, networkName, clientOrNodeID string) bool
 	}
 	}
 	return IsClientNodeAllowed(&client, clientOrNodeID)
 	return IsClientNodeAllowed(&client, clientOrNodeID)
 }
 }
+
+// SortExtClient - Sorts slice of ExtClients by their ClientID alphabetically with numbers first
+func SortExtClient(unsortedExtClient []models.ExtClient) {
+	sort.Slice(unsortedExtClient, func(i, j int) bool {
+		return unsortedExtClient[i].ClientID < unsortedExtClient[j].ClientID
+	})
+}

+ 8 - 0
logic/dns.go

@@ -3,6 +3,7 @@ package logic
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 	"os"
 	"os"
+	"sort"
 
 
 	validator "github.com/go-playground/validator/v10"
 	validator "github.com/go-playground/validator/v10"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
@@ -194,6 +195,13 @@ func GetDNSEntryNum(domain string, network string) (int, error) {
 	return num, nil
 	return num, nil
 }
 }
 
 
+// SortDNSEntrys - Sorts slice of DNSEnteys by their Address alphabetically with numbers first
+func SortDNSEntrys(unsortedDNSEntrys []models.DNSEntry) {
+	sort.Slice(unsortedDNSEntrys, func(i, j int) bool {
+		return unsortedDNSEntrys[i].Address < unsortedDNSEntrys[j].Address
+	})
+}
+
 // ValidateDNSCreate - checks if an entry is valid
 // ValidateDNSCreate - checks if an entry is valid
 func ValidateDNSCreate(entry models.DNSEntry) error {
 func ValidateDNSCreate(entry models.DNSEntry) error {
 
 

+ 2 - 2
logic/gateway.go

@@ -26,8 +26,8 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 		return models.Node{}, errors.New(host.OS + " is unsupported for egress gateways")
 		return models.Node{}, errors.New(host.OS + " is unsupported for egress gateways")
 	}
 	}
 	for i := len(gateway.Ranges) - 1; i >= 0; i-- {
 	for i := len(gateway.Ranges) - 1; i >= 0; i-- {
-		if gateway.Ranges[i] == "0.0.0.0/0" || gateway.Ranges[i] == "::/0" {
-			logger.Log(0, "currently internet gateways are not supported", gateway.Ranges[i])
+		if gateway.Ranges[i] == "::/0" {
+			logger.Log(0, "currently IPv6 internet gateways are not supported", gateway.Ranges[i])
 			gateway.Ranges = append(gateway.Ranges[:i], gateway.Ranges[i+1:]...)
 			gateway.Ranges = append(gateway.Ranges[:i], gateway.Ranges[i+1:]...)
 			continue
 			continue
 		}
 		}

+ 13 - 0
logic/hosts.go

@@ -5,6 +5,7 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"log"
 	"log"
+	"sort"
 
 
 	"github.com/google/uuid"
 	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
@@ -97,6 +98,7 @@ func CreateHost(h *models.Host) error {
 		return err
 		return err
 	}
 	}
 	h.HostPass = string(hash)
 	h.HostPass = string(hash)
+	h.AutoUpdate = servercfg.AutoUpdateEnabled()
 	// if another server has already updated proxyenabled, leave it alone
 	// if another server has already updated proxyenabled, leave it alone
 	if !h.ProxyEnabledSet {
 	if !h.ProxyEnabledSet {
 		log.Println("checking default proxy", servercfg.GetServerConfig().DefaultProxyMode)
 		log.Println("checking default proxy", servercfg.GetServerConfig().DefaultProxyMode)
@@ -151,6 +153,10 @@ func UpdateHost(newHost, currentHost *models.Host) {
 // UpdateHostFromClient - used for updating host on server with update recieved from client
 // UpdateHostFromClient - used for updating host on server with update recieved from client
 func UpdateHostFromClient(newHost, currHost *models.Host) (sendPeerUpdate bool) {
 func UpdateHostFromClient(newHost, currHost *models.Host) (sendPeerUpdate bool) {
 
 
+	if newHost.PublicKey != currHost.PublicKey {
+		currHost.PublicKey = newHost.PublicKey
+		sendPeerUpdate = true
+	}
 	if newHost.ListenPort != 0 && currHost.ListenPort != newHost.ListenPort {
 	if newHost.ListenPort != 0 && currHost.ListenPort != newHost.ListenPort {
 		currHost.ListenPort = newHost.ListenPort
 		currHost.ListenPort = newHost.ListenPort
 		sendPeerUpdate = true
 		sendPeerUpdate = true
@@ -428,3 +434,10 @@ func GetHostByNodeID(id string) *models.Host {
 	}
 	}
 	return nil
 	return nil
 }
 }
+
+// SortApiHosts - Sorts slice of ApiHosts by their ID alphabetically with numbers first
+func SortApiHosts(unsortedHosts []models.ApiHost) {
+	sort.Slice(unsortedHosts, func(i, j int) bool {
+		return unsortedHosts[i].ID < unsortedHosts[j].ID
+	})
+}

+ 8 - 44
logic/networks.go

@@ -5,6 +5,7 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"net"
 	"net"
+	"sort"
 	"strings"
 	"strings"
 
 
 	"github.com/c-robinson/iplib"
 	"github.com/c-robinson/iplib"
@@ -554,15 +555,6 @@ func ParseNetwork(value string) (models.Network, error) {
 	return network, err
 	return network, err
 }
 }
 
 
-// KeyUpdate - updates keys on network
-func KeyUpdate(netname string) (models.Network, error) {
-	err := networkNodesUpdateAction(netname, models.NODE_UPDATE_KEY)
-	if err != nil {
-		return models.Network{}, err
-	}
-	return models.Network{}, nil
-}
-
 // SaveNetwork - save network struct to database
 // SaveNetwork - save network struct to database
 func SaveNetwork(network *models.Network) error {
 func SaveNetwork(network *models.Network) error {
 	data, err := json.Marshal(network)
 	data, err := json.Marshal(network)
@@ -586,39 +578,11 @@ func NetworkExists(name string) (bool, error) {
 	return len(network) > 0, nil
 	return len(network) > 0, nil
 }
 }
 
 
-// == Private ==
-
-func networkNodesUpdateAction(networkName string, action string) error {
-
-	collections, err := database.FetchRecords(database.NODES_TABLE_NAME)
-	if err != nil {
-		if database.IsEmptyRecord(err) {
-			return nil
-		}
-		return err
-	}
-
-	for k, value := range collections {
-		var node models.Node
-		err := json.Unmarshal([]byte(value), &node)
-		if err != nil {
-			if IsLegacyNode(k) { // ignore legacy nodes
-				continue
-			}
-			fmt.Println("error in node address assignment!")
-			return err
-		}
-		if action == models.NODE_UPDATE_KEY {
-			continue
-		}
-		if node.Network == networkName {
-			node.Action = action
-			data, err := json.Marshal(&node)
-			if err != nil {
-				return err
-			}
-			database.Insert(node.ID.String(), string(data), database.NODES_TABLE_NAME)
-		}
-	}
-	return nil
+// SortNetworks - Sorts slice of Networks by their NetID alphabetically with numbers first
+func SortNetworks(unsortedNetworks []models.Network) {
+	sort.Slice(unsortedNetworks, func(i, j int) bool {
+		return unsortedNetworks[i].NetID < unsortedNetworks[j].NetID
+	})
 }
 }
+
+// == Private ==

+ 8 - 0
logic/nodes.go

@@ -5,6 +5,7 @@ import (
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
 	"net"
 	"net"
+	"sort"
 	"time"
 	"time"
 
 
 	validator "github.com/go-playground/validator/v10"
 	validator "github.com/go-playground/validator/v10"
@@ -546,4 +547,11 @@ func createNode(node *models.Node) error {
 	return err
 	return err
 }
 }
 
 
+// SortApiNodes - Sorts slice of ApiNodes by their ID alphabetically with numbers first
+func SortApiNodes(unsortedNodes []models.ApiNode) {
+	sort.Slice(unsortedNodes, func(i, j int) bool {
+		return unsortedNodes[i].ID < unsortedNodes[j].ID
+	})
+}
+
 // == END PRO ==
 // == END PRO ==

+ 8 - 5
logic/pro/netcache/netcache.go

@@ -6,6 +6,7 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/models"
 )
 )
 
 
 const (
 const (
@@ -14,11 +15,13 @@ const (
 
 
 // CValue - the cache object for a network
 // CValue - the cache object for a network
 type CValue struct {
 type CValue struct {
-	Network    string    `json:"network"`
-	Value      string    `json:"value"`
-	Pass       string    `json:"pass"`
-	User       string    `json:"user"`
-	Expiration time.Time `json:"expiration"`
+	Network    string      `json:"network,omitempty"`
+	Value      string      `json:"value"`
+	Host       models.Host `json:"host"`
+	Pass       string      `json:"pass,omitempty"`
+	User       string      `json:"user,omitempty"`
+	ALL        bool        `json:"all,omitempty"`
+	Expiration time.Time   `json:"expiration"`
 }
 }
 
 
 var ErrExpired = fmt.Errorf("expired")
 var ErrExpired = fmt.Errorf("expired")

+ 8 - 0
logic/users.go

@@ -2,6 +2,7 @@ package logic
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
+	"sort"
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
@@ -77,3 +78,10 @@ func SetUserDefaults(user *models.User) {
 		user.Groups = []string{pro.DEFAULT_ALLOWED_GROUPS}
 		user.Groups = []string{pro.DEFAULT_ALLOWED_GROUPS}
 	}
 	}
 }
 }
+
+// SortUsers - Sorts slice of Users by username
+func SortUsers(unsortedUsers []models.ReturnUser) {
+	sort.Slice(unsortedUsers, func(i, j int) bool {
+		return unsortedUsers[i].UserName < unsortedUsers[j].UserName
+	})
+}

+ 1 - 1
main.go

@@ -27,7 +27,7 @@ import (
 	stunserver "github.com/gravitl/netmaker/stun-server"
 	stunserver "github.com/gravitl/netmaker/stun-server"
 )
 )
 
 
-var version = "v0.18.6"
+var version = "v0.18.7"
 
 
 // Start DB Connection and start API Request Handler
 // Start DB Connection and start API Request Handler
 func main() {
 func main() {

+ 12 - 0
models/host.go

@@ -47,6 +47,7 @@ type Host struct {
 	Version          string           `json:"version" yaml:"version"`
 	Version          string           `json:"version" yaml:"version"`
 	IPForwarding     bool             `json:"ipforwarding" yaml:"ipforwarding"`
 	IPForwarding     bool             `json:"ipforwarding" yaml:"ipforwarding"`
 	DaemonInstalled  bool             `json:"daemoninstalled" yaml:"daemoninstalled"`
 	DaemonInstalled  bool             `json:"daemoninstalled" yaml:"daemoninstalled"`
+	AutoUpdate       bool             `json:"autoupdate" yaml:"autoupdate"`
 	HostPass         string           `json:"hostpass" yaml:"hostpass"`
 	HostPass         string           `json:"hostpass" yaml:"hostpass"`
 	Name             string           `json:"name" yaml:"name"`
 	Name             string           `json:"name" yaml:"name"`
 	OS               string           `json:"os" yaml:"os"`
 	OS               string           `json:"os" yaml:"os"`
@@ -112,6 +113,8 @@ const (
 	RequestAck = "REQ_ACK"
 	RequestAck = "REQ_ACK"
 	// CheckIn - update last check in times and public address and interfaces
 	// CheckIn - update last check in times and public address and interfaces
 	CheckIn = "CHECK_IN"
 	CheckIn = "CHECK_IN"
+	// UpdateKeys - update wireguard private/public keys
+	UpdateKeys = "UPDATE_KEYS"
 )
 )
 
 
 // HostUpdate - struct for host update
 // HostUpdate - struct for host update
@@ -120,3 +123,12 @@ type HostUpdate struct {
 	Host   Host
 	Host   Host
 	Node   Node
 	Node   Node
 }
 }
+
+// RegisterMsg - login message struct for hosts to join via SSO login
+type RegisterMsg struct {
+	RegisterHost Host   `json:"host"`
+	Network      string `json:"network,omitempty"`
+	User         string `json:"user,omitempty"`
+	Password     string `json:"password,omitempty"`
+	JoinAll      bool   `json:"join_all,omitempty"`
+}

+ 0 - 2
models/node.go

@@ -19,8 +19,6 @@ const (
 	// MAX_NAME_LENGTH - max name length of node
 	// MAX_NAME_LENGTH - max name length of node
 	MAX_NAME_LENGTH = 62
 	MAX_NAME_LENGTH = 62
 	// == ACTIONS == (can only be set by server)
 	// == ACTIONS == (can only be set by server)
-	// NODE_UPDATE_KEY - action to update key
-	NODE_UPDATE_KEY = "updatekey"
 	// NODE_DELETE - delete node action
 	// NODE_DELETE - delete node action
 	NODE_DELETE = "delete"
 	NODE_DELETE = "delete"
 	// NODE_IS_PENDING - node pending status
 	// NODE_IS_PENDING - node pending status

+ 0 - 9
models/promodels/pro.go

@@ -8,12 +8,3 @@ type ProNetwork struct {
 	AllowedUsers           []string `json:"allowedusers" bson:"allowedusers" yaml:"allowedusers"`
 	AllowedUsers           []string `json:"allowedusers" bson:"allowedusers" yaml:"allowedusers"`
 	AllowedGroups          []string `json:"allowedgroups" bson:"allowedgroups" yaml:"allowedgroups"`
 	AllowedGroups          []string `json:"allowedgroups" bson:"allowedgroups" yaml:"allowedgroups"`
 }
 }
-
-// LoginMsg - login message struct for nodes to join via SSO login
-// Need to change mac to public key for tighter verification ?
-type LoginMsg struct {
-	Mac      string `json:"mac"`
-	Network  string `json:"network"`
-	User     string `json:"user,omitempty"`
-	Password string `json:"password,omitempty"`
-}

+ 23 - 0
mq/handlers.go

@@ -16,6 +16,7 @@ import (
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
+	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 )
 
 
 // DefaultHandler default message queue handler  -- NOT USED
 // DefaultHandler default message queue handler  -- NOT USED
@@ -117,6 +118,28 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 			}
 			}
 		}
 		}
 	case models.UpdateHost:
 	case models.UpdateHost:
+		if hostUpdate.Host.PublicKey != currentHost.PublicKey {
+			//remove old peer entry
+			peerUpdate := models.HostPeerUpdate{
+				ServerVersion: servercfg.GetVersion(),
+				Peers: []wgtypes.PeerConfig{
+					{
+						PublicKey: currentHost.PublicKey,
+						Remove:    true,
+					},
+				},
+			}
+			data, err := json.Marshal(&peerUpdate)
+			if err != nil {
+				logger.Log(2, "json error", err.Error())
+			}
+			hosts := logic.GetRelatedHosts(hostUpdate.Host.ID.String())
+			server := servercfg.GetServer()
+			for _, host := range hosts {
+				publish(&host, fmt.Sprintf("peers/host/%s/%s", host.ID.String(), server), data)
+			}
+
+		}
 		sendPeerUpdate = logic.UpdateHostFromClient(&hostUpdate.Host, currentHost)
 		sendPeerUpdate = logic.UpdateHostFromClient(&hostUpdate.Host, currentHost)
 		err := logic.UpsertHost(currentHost)
 		err := logic.UpsertHost(currentHost)
 		if err != nil {
 		if err != nil {

+ 7 - 14
release.md

@@ -1,20 +1,14 @@
-# Netmaker v0.18.6
+# Netmaker v0.18.7
 
 
 ## whats new
 ## whats new
-- no new features
+- internet gateways (0.0.0.0/0) for egress
+- deprecated editing of network parameters
+- allow extra ips for extclient (not enabled in UI)
     
     
 ## whats fixed
 ## whats fixed
-- a few ext client/ingress issues
-  - viewing addresses (UI)
-  - when deleting an ingress gateway, ext clients are now removed from peers immediately
-  - ext client peers should be populated immediately after creation
-  - ext clients no longer reset public key when disabled/enabled
-  - can delete an ingress without clients
-- removed unnecessary host update
-- host nat type is now collected from clients
-- fix peer update issue where caclulation was happening to frequently
-- nm-quick && nm-upgrade 
-- EMQX image change && api routes
+- nm-quick - determine lastest version from releases
+- wireguard public/private key rotation
+- ee-license checks
 
 
 ## known issues
 ## known issues
 - Caddy does not handle netmaker exporter well for EE
 - Caddy does not handle netmaker exporter well for EE
@@ -25,4 +19,3 @@
 - Nodes on same local network may not always connect
 - Nodes on same local network may not always connect
 - List populates egress ranges twice
 - List populates egress ranges twice
 - If you do NOT set STUN_LIST on server, it could lead to strange behavior on client
 - If you do NOT set STUN_LIST on server, it could lead to strange behavior on client
-- No internet gateways/default routes

+ 2 - 1
scripts/nm-quick.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 #!/bin/bash
 
 
-LATEST="v0.18.6"
+LATEST=$(curl -s https://api.github.com/repos/gravitl/netmaker/releases/latest | grep "tag_name" | cut -d : -f 2,3 | tr -d [:space:],\")
 
 
 print_logo() {(
 print_logo() {(
 cat << "EOF"
 cat << "EOF"
@@ -429,6 +429,7 @@ set_install_vars() {
 	echo "          dashboard.$NETMAKER_BASE_DOMAIN"
 	echo "          dashboard.$NETMAKER_BASE_DOMAIN"
 	echo "                api.$NETMAKER_BASE_DOMAIN"
 	echo "                api.$NETMAKER_BASE_DOMAIN"
 	echo "             broker.$NETMAKER_BASE_DOMAIN"
 	echo "             broker.$NETMAKER_BASE_DOMAIN"
+	echo "               stun.$NETMAKER_BASE_DOMAIN"
 
 
 	if [ "$INSTALL_TYPE" = "ee" ]; then
 	if [ "$INSTALL_TYPE" = "ee" ]; then
 		echo "         prometheus.$NETMAKER_BASE_DOMAIN"
 		echo "         prometheus.$NETMAKER_BASE_DOMAIN"

+ 16 - 0
servercfg/serverconf.go

@@ -46,6 +46,11 @@ func GetServerConfig() config.ServerConfig {
 	cfg.StunPort = GetStunPort()
 	cfg.StunPort = GetStunPort()
 	cfg.BrokerType = GetBrokerType()
 	cfg.BrokerType = GetBrokerType()
 	cfg.EmqxRestEndpoint = GetEmqxRestEndpoint()
 	cfg.EmqxRestEndpoint = GetEmqxRestEndpoint()
+	if AutoUpdateEnabled() {
+		cfg.NetclientAutoUpdate = "enabled"
+	} else {
+		cfg.NetclientAutoUpdate = "disabled"
+	}
 	if IsRestBackend() {
 	if IsRestBackend() {
 		cfg.RestBackend = "on"
 		cfg.RestBackend = "on"
 	}
 	}
@@ -382,6 +387,17 @@ func GetVerbosity() int32 {
 	return int32(verbosity)
 	return int32(verbosity)
 }
 }
 
 
+// AutoUpdateEnabled returns a boolean indicating whether netclient auto update is enabled or disabled
+// default is enabled
+func AutoUpdateEnabled() bool {
+	if os.Getenv("NETCLIENT_AUTO_UPDATE") == "disabled" {
+		return false
+	} else if config.Config.Server.NetclientAutoUpdate == "disabled" {
+		return false
+	}
+	return true
+}
+
 // IsDNSMode - should it run with DNS
 // IsDNSMode - should it run with DNS
 func IsDNSMode() bool {
 func IsDNSMode() bool {
 	isdns := true
 	isdns := true

+ 1 - 1
swagger.yaml

@@ -704,7 +704,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.18.6
+    version: 0.18.7
 paths:
 paths:
     /api/dns:
     /api/dns:
         get:
         get: