Browse Source

Merge remote-tracking branch 'origin/develop' into GRA-1529-key-update

Matthew R Kasun 2 years ago
parent
commit
754ab44a48

+ 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
     
     

+ 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
     
     

+ 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)
-}

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

@@ -4,7 +4,7 @@ services:
   netmaker:
   netmaker:
     container_name: netmaker
     container_name: netmaker
     image: gravitl/netmaker:v0.18.6
     image: gravitl/netmaker:v0.18.6
-    restart: always
+    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: "*"

+ 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: "*"

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

@@ -6,7 +6,7 @@ services:
     image: 'gravitl/netclient:v0.18.6'
     image: 'gravitl/netclient:v0.18.6'
     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"`

+ 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/hosts.go

@@ -30,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

+ 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)
 }
 }

+ 15 - 75
controllers/network.go

@@ -22,7 +22,6 @@ 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)
 	// 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)
@@ -103,9 +102,9 @@ func getNetwork(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(network)
 	json.NewEncoder(w).Encode(network)
 }
 }
 
 
-// swagger:route PUT /api/networks/{networkname} networks updateNetwork
+// swagger:route POST /api/networks/{networkname}/keyupdate networks keyUpdate
 //
 //
-// Update a network.
+// Update keys for a network.
 //
 //
 //			Schemes: https
 //			Schemes: https
 //
 //
@@ -114,90 +113,31 @@ func getNetwork(w http.ResponseWriter, r *http.Request) {
 //
 //
 //			Responses:
 //			Responses:
 //				200: networkBodyResponse
 //				200: networkBodyResponse
-func updateNetwork(w http.ResponseWriter, r *http.Request) {
+func keyUpdate(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Type", "application/json")
 	var params = mux.Vars(r)
 	var params = mux.Vars(r)
-	var network models.Network
 	netname := params["networkname"]
 	netname := params["networkname"]
-
-	network, err := logic.GetParentNetwork(netname)
+	network, err := logic.KeyUpdate(netname)
 	if err != nil {
 	if err != nil {
-		logger.Log(0, r.Header.Get("user"), "failed to get network info: ",
-			err.Error())
+		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"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		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)
+	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 {
 	if err != nil {
-		logger.Log(0, r.Header.Get("user"), "failed to update network: ",
-			err.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		logger.Log(0, "failed to retrieve network nodes for network", netname, err.Error())
 		return
 		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)
-			}
+	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())
 		}
 		}
 	}
 	}
-	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

+ 2 - 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
@@ -483,5 +482,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

+ 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

+ 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
 		}
 		}

+ 1 - 0
logic/hosts.go

@@ -97,6 +97,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)

+ 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")

+ 10 - 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"`
@@ -122,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 - 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"`
-}

+ 4 - 14
release.md

@@ -1,27 +1,17 @@
-# Netmaker v0.18.6
-
-## **Wait till out of pre-release to fully upgrade**
+# Netmaker v0.18.7
 
 
 ## whats new
 ## whats new
-- Logic for ext client ACLs (not really usable until new UI is finished)
-- Default proxy mode, enables users to determine if all Hosts should have proxy enabled/disabled/auto by default
-  - specify with DEFAULT_PROXY_MODE="on/off/auto" 
+- deprecated editing of network parameters
     
     
 ## whats fixed
 ## whats fixed
-- Proxy Peer calculation improvements
-- DNS is populated correctly after registration by enrollment key
-- Migrate is functional for Windows/Mac **note** Ports may be set to 0 after an upgrade, can be adjusted via UI to fix
-- Interface data is sent on netclient register
-- Upgrade script
-- Latency issue with Node <-> Node Metrics
-- Ports set from server for Hosts on register/join are actually used
 
 
 ## known issues
 ## known issues
 - Caddy does not handle netmaker exporter well for EE
 - Caddy does not handle netmaker exporter well for EE
-- Migration causes a listen port of 0 for upgraded hosts
+- Migration causes a listen port of 0 for some upgraded hosts
 - Docker clients can not re-join after deletion
 - Docker clients can not re-join after deletion
 - Innacurate Ext Client Metrics 
 - Innacurate Ext Client Metrics 
 - Issue with Mac + IPv6 addressing
 - Issue with Mac + IPv6 addressing
 - 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

+ 3 - 2
scripts/nm-quick.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 #!/bin/bash
 
 
-LATEST="v0.18.5"
+LATEST="v0.18.6"
 
 
 print_logo() {(
 print_logo() {(
 cat << "EOF"
 cat << "EOF"
@@ -182,7 +182,7 @@ setup_netclient() {
 	netclient uninstall
 	netclient uninstall
 	set -e
 	set -e
 
 
-	wget -O netclient https://github.com/gravitl/netclient/releases/download/$LATEST/netclient_linux_amd64
+	wget -O netclient https://github.com/gravitl/netclient/releases/download/$LATEST/netclient-linux-amd64
 	chmod +x netclient
 	chmod +x netclient
 	./netclient install
 	./netclient install
 	netclient register -t $TOKEN
 	netclient register -t $TOKEN
@@ -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"

+ 1 - 1
scripts/nm-upgrade.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 #!/bin/bash
 
 
-LATEST="v0.18.5"
+LATEST="v0.18.6"
 INSTALL_PATH="/root"
 INSTALL_PATH="/root"
 
 
 trap restore_old_netmaker_instructions
 trap restore_old_netmaker_instructions

+ 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