Browse Source

merge develop

0xdcarns 2 years ago
parent
commit
5fb3100c72

+ 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.4
         - v0.18.3
         - v0.18.3
         - v0.18.2
         - v0.18.2
         - v0.18.1
         - v0.18.1

+ 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.3-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.18.4-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 - 2
compose/docker-compose-emqx.yml

@@ -3,7 +3,7 @@ version: "3.4"
 services:
 services:
   netmaker:
   netmaker:
     container_name: netmaker
     container_name: netmaker
-    image: gravitl/netmaker:v0.18.2
+    image: gravitl/netmaker:v0.18.4
     restart: always
     restart: always
     volumes:
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - dnsconfig:/root/config/dnsconfig
@@ -30,11 +30,12 @@ services:
       VERBOSITY: "1"
       VERBOSITY: "1"
       MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
       MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
       MQ_USERNAME: "REPLACE_MQ_USERNAME"
       MQ_USERNAME: "REPLACE_MQ_USERNAME"
+      DEFAULT_PROXY_MODE: "auto"
     ports:
     ports:
       - "3478:3478/udp"
       - "3478:3478/udp"
   netmaker-ui:
   netmaker-ui:
     container_name: netmaker-ui
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.18.2
+    image: gravitl/netmaker-ui:v0.18.4
     depends_on:
     depends_on:
       - netmaker
       - netmaker
     links:
     links:

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

@@ -33,6 +33,7 @@ services:
       METRICS_EXPORTER: "on"
       METRICS_EXPORTER: "on"
       LICENSE_KEY: "YOUR_LICENSE_KEY"
       LICENSE_KEY: "YOUR_LICENSE_KEY"
       NETMAKER_ACCOUNT_ID: "YOUR_ACCOUNT_ID"
       NETMAKER_ACCOUNT_ID: "YOUR_ACCOUNT_ID"
+      DEFAULT_PROXY_MODE: "auto"
     ports:
     ports:
       - "3478:3478/udp"
       - "3478:3478/udp"
   netmaker-ui:
   netmaker-ui:

+ 17 - 0
compose/docker-compose.netclient.yml

@@ -0,0 +1,17 @@
+version: "3.4"
+
+services:
+  netclient:
+    container_name: netclient
+    image: 'gravitl/netclient:v0.18.4'
+    hostname: netmaker-1
+    network_mode: host
+    restart: always
+    environment:
+      TOKEN: "TOKEN_VALUE"
+    volumes:
+      - /etc/netclient:/etc/netclient
+    cap_add:
+      - NET_ADMIN
+      - NET_RAW
+      - SYS_MODULE

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

@@ -38,6 +38,7 @@ services:
       FRONTEND_URL: "" # "https://dashboard.<netmaker base domain>"
       FRONTEND_URL: "" # "https://dashboard.<netmaker base domain>"
       AZURE_TENANT: "" # "<only for azure, you may optionally specify the tenant for the OAuth>"
       AZURE_TENANT: "" # "<only for azure, you may optionally specify the tenant for the OAuth>"
       OIDC_ISSUER: "" # https://oidc.yourprovider.com - URL of oidc provider
       OIDC_ISSUER: "" # https://oidc.yourprovider.com - URL of oidc provider
+      DEFAULT_PROXY_MODE: "auto" # if ON, all new clients will enable proxy by default if OFF, all new clients will disable proxy by default, if AUTO, stick with the existing logic for NAT detection
     ports:
     ports:
       - "3478:3478/udp" # the stun port
       - "3478:3478/udp" # the stun port
   netmaker-ui:  # The Netmaker UI Component
   netmaker-ui:  # The Netmaker UI Component

+ 1 - 0
compose/docker-compose.yml

@@ -28,6 +28,7 @@ services:
       MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
       MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
       MQ_USERNAME: "REPLACE_MQ_USERNAME"
       MQ_USERNAME: "REPLACE_MQ_USERNAME"
       STUN_PORT: "3478"
       STUN_PORT: "3478"
+      DEFAULT_PROXY_MODE: "auto"
     ports:
     ports:
       - "3478:3478/udp"
       - "3478:3478/udp"
   netmaker-ui:
   netmaker-ui:

+ 49 - 42
config/config.go

@@ -32,48 +32,55 @@ type EnvironmentConfig struct {
 
 
 // ServerConfig - server conf struct
 // ServerConfig - server conf struct
 type ServerConfig struct {
 type ServerConfig struct {
-	CoreDNSAddr          string `yaml:"corednsaddr"`
-	APIConnString        string `yaml:"apiconn"`
-	APIHost              string `yaml:"apihost"`
-	APIPort              string `yaml:"apiport"`
-	Broker               string `yam:"broker"`
-	ServerBrokerEndpoint string `yaml:"serverbrokerendpoint"`
-	BrokerType           string `yaml:"brokertype"`
-	EmqxRestEndpoint     string `yaml:"emqxrestendpoint"`
-	MasterKey            string `yaml:"masterkey"`
-	DNSKey               string `yaml:"dnskey"`
-	AllowedOrigin        string `yaml:"allowedorigin"`
-	NodeID               string `yaml:"nodeid"`
-	RestBackend          string `yaml:"restbackend"`
-	MessageQueueBackend  string `yaml:"messagequeuebackend"`
-	DNSMode              string `yaml:"dnsmode"`
-	DisableRemoteIPCheck string `yaml:"disableremoteipcheck"`
-	Version              string `yaml:"version"`
-	SQLConn              string `yaml:"sqlconn"`
-	Platform             string `yaml:"platform"`
-	Database             string `yaml:"database"`
-	Verbosity            int32  `yaml:"verbosity"`
-	AuthProvider         string `yaml:"authprovider"`
-	OIDCIssuer           string `yaml:"oidcissuer"`
-	ClientID             string `yaml:"clientid"`
-	ClientSecret         string `yaml:"clientsecret"`
-	FrontendURL          string `yaml:"frontendurl"`
-	DisplayKeys          string `yaml:"displaykeys"`
-	AzureTenant          string `yaml:"azuretenant"`
-	Telemetry            string `yaml:"telemetry"`
-	HostNetwork          string `yaml:"hostnetwork"`
-	Server               string `yaml:"server"`
-	PublicIPService      string `yaml:"publicipservice"`
-	MQPassword           string `yaml:"mqpassword"`
-	MQUserName           string `yaml:"mqusername"`
-	MetricsExporter      string `yaml:"metrics_exporter"`
-	BasicAuth            string `yaml:"basic_auth"`
-	LicenseValue         string `yaml:"license_value"`
-	NetmakerAccountID    string `yaml:"netmaker_account_id"`
-	IsEE                 string `yaml:"is_ee"`
-	StunPort             int    `yaml:"stun_port"`
-	StunList             string `yaml:"stun_list"`
-	Proxy                string `yaml:"proxy"`
+	CoreDNSAddr          string    `yaml:"corednsaddr"`
+	APIConnString        string    `yaml:"apiconn"`
+	APIHost              string    `yaml:"apihost"`
+	APIPort              string    `yaml:"apiport"`
+	Broker               string    `yam:"broker"`
+	ServerBrokerEndpoint string    `yaml:"serverbrokerendpoint"`
+	BrokerType           string    `yaml:"brokertype"`
+	EmqxRestEndpoint     string    `yaml:"emqxrestendpoint"`
+	MasterKey            string    `yaml:"masterkey"`
+	DNSKey               string    `yaml:"dnskey"`
+	AllowedOrigin        string    `yaml:"allowedorigin"`
+	NodeID               string    `yaml:"nodeid"`
+	RestBackend          string    `yaml:"restbackend"`
+	MessageQueueBackend  string    `yaml:"messagequeuebackend"`
+	DNSMode              string    `yaml:"dnsmode"`
+	DisableRemoteIPCheck string    `yaml:"disableremoteipcheck"`
+	Version              string    `yaml:"version"`
+	SQLConn              string    `yaml:"sqlconn"`
+	Platform             string    `yaml:"platform"`
+	Database             string    `yaml:"database"`
+	Verbosity            int32     `yaml:"verbosity"`
+	AuthProvider         string    `yaml:"authprovider"`
+	OIDCIssuer           string    `yaml:"oidcissuer"`
+	ClientID             string    `yaml:"clientid"`
+	ClientSecret         string    `yaml:"clientsecret"`
+	FrontendURL          string    `yaml:"frontendurl"`
+	DisplayKeys          string    `yaml:"displaykeys"`
+	AzureTenant          string    `yaml:"azuretenant"`
+	Telemetry            string    `yaml:"telemetry"`
+	HostNetwork          string    `yaml:"hostnetwork"`
+	Server               string    `yaml:"server"`
+	PublicIPService      string    `yaml:"publicipservice"`
+	MQPassword           string    `yaml:"mqpassword"`
+	MQUserName           string    `yaml:"mqusername"`
+	MetricsExporter      string    `yaml:"metrics_exporter"`
+	BasicAuth            string    `yaml:"basic_auth"`
+	LicenseValue         string    `yaml:"license_value"`
+	NetmakerAccountID    string    `yaml:"netmaker_account_id"`
+	IsEE                 string    `yaml:"is_ee"`
+	StunPort             int       `yaml:"stun_port"`
+	StunList             string    `yaml:"stun_list"`
+	Proxy                string    `yaml:"proxy"`
+	DefaultProxyMode     ProxyMode `yaml:"defaultproxymode"`
+}
+
+// ProxyMode - default proxy mode for server
+type ProxyMode struct {
+	Set   bool
+	Value bool
 }
 }
 
 
 // SQLConfig - Generic SQL Config
 // SQLConfig - Generic SQL Config

+ 1 - 1
controllers/docs.go

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

+ 7 - 2
controllers/enrollmentkeys.go

@@ -160,8 +160,13 @@ func handleHostRegister(w http.ResponseWriter, r *http.Request) {
 		return
 		return
 	}
 	}
 	// version check
 	// version check
-	if !logic.IsVersionComptatible(newHost.Version) || newHost.TrafficKeyPublic == nil {
-		err := fmt.Errorf("incompatible netclient")
+	if !logic.IsVersionComptatible(newHost.Version) {
+		err := fmt.Errorf("bad client version on register: %s", newHost.Version)
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	if newHost.TrafficKeyPublic == nil && newHost.OS != models.OS_Types.IoT {
+		err := fmt.Errorf("missing traffic key")
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 		return
 	}
 	}

+ 12 - 4
controllers/ext_client.go

@@ -363,6 +363,13 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 		extclient.Enabled = parentNetwork.DefaultACL == "yes"
 		extclient.Enabled = parentNetwork.DefaultACL == "yes"
 	}
 	}
 
 
+	if err := logic.SetClientDefaultACLs(&extclient); err != nil {
+		logger.Log(0, r.Header.Get("user"),
+			fmt.Sprintf("failed to assign ACLs to new ext client on network [%s]: %v", networkName, err))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+
 	if err = logic.CreateExtClient(&extclient); err != nil {
 	if err = logic.CreateExtClient(&extclient); err != nil {
 		logger.Log(0, r.Header.Get("user"),
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("failed to create new ext client on network [%s]: %v", networkName, err))
 			fmt.Sprintf("failed to create new ext client on network [%s]: %v", networkName, err))
@@ -384,7 +391,7 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 				logger.Log(0, "failed to associate client", extclient.ClientID, "to user", userID)
 				logger.Log(0, "failed to associate client", extclient.ClientID, "to user", userID)
 			}
 			}
 			extclient.OwnerID = userID
 			extclient.OwnerID = userID
-			if _, err := logic.UpdateExtClient(extclient.ClientID, extclient.Network, extclient.Enabled, &extclient); err != nil {
+			if _, err := logic.UpdateExtClient(extclient.ClientID, extclient.Network, extclient.Enabled, &extclient, extclient.ACLs); err != nil {
 				logger.Log(0, "failed to add owner id", userID, "to client", extclient.ClientID)
 				logger.Log(0, "failed to add owner id", userID, "to client", extclient.ClientID)
 			}
 			}
 		}
 		}
@@ -477,10 +484,11 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 	// == END PRO ==
 	// == END PRO ==
 
 
-	var changedEnabled = newExtClient.Enabled != oldExtClient.Enabled // indicates there was a change in enablement
+	var changedEnabled = (newExtClient.Enabled != oldExtClient.Enabled) || // indicates there was a change in enablement
+		len(newExtClient.ACLs) != len(oldExtClient.ACLs)
 	// extra var need as logic.Update changes oldExtClient
 	// extra var need as logic.Update changes oldExtClient
 	currentClient := oldExtClient
 	currentClient := oldExtClient
-	newclient, err := logic.UpdateExtClient(newExtClient.ClientID, params["network"], newExtClient.Enabled, &oldExtClient)
+	newclient, err := logic.UpdateExtClient(newExtClient.ClientID, params["network"], newExtClient.Enabled, &oldExtClient, newExtClient.ACLs)
 	if err != nil {
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"),
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("failed to update ext client [%s], network [%s]: %v",
 			fmt.Sprintf("failed to update ext client [%s], network [%s]: %v",
@@ -570,7 +578,7 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
 	}
 	}
 
 
 	go func() {
 	go func() {
-		if err := mq.PublishPeerUpdate(); err != nil {
+		if err := mq.PublishDeletedClientPeerUpdate(&extclient); err != nil {
 			logger.Log(1, "error setting ext peers on "+ingressnode.ID.String()+": "+err.Error())
 			logger.Log(1, "error setting ext peers on "+ingressnode.ID.String()+": "+err.Error())
 		}
 		}
 		if err = mq.PublishDeleteExtClientDNS(&extclient); err != nil {
 		if err = mq.PublishDeleteExtClientDNS(&extclient); err != nil {

+ 68 - 76
controllers/migrate.go

@@ -1,10 +1,18 @@
 package controller
 package controller
 
 
 import (
 import (
+	"encoding/json"
 	"net/http"
 	"net/http"
+
+	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/servercfg"
+	"golang.org/x/crypto/bcrypt"
 )
 )
 
 
-// swagger:route PUT /api/nodes/{network}/{nodeid}/migrate nodes migrateNode
+// swagger:route PUT /api/v1/nodes/migrate nodes migrateNode
 //
 //
 // Used to migrate a legacy node.
 // Used to migrate a legacy node.
 //
 //
@@ -16,79 +24,63 @@ import (
 //			Responses:
 //			Responses:
 //				200: nodeJoinResponse
 //				200: nodeJoinResponse
 func migrate(w http.ResponseWriter, r *http.Request) {
 func migrate(w http.ResponseWriter, r *http.Request) {
-	// TODO adapt with enrollment-keys or re-think how this works
-	// we decode our body request params
-	// data := models.MigrationData{}
-	// err := json.NewDecoder(r.Body).Decode(&data)
-	// 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
-	// }
-	// params := mux.Vars(r)
-	// //check authorization
-	// record, err := database.FetchRecord(database.NODES_TABLE_NAME, data.LegacyNodeID)
-	// if err != nil {
-	// 	logger.Log(0, "no record for legacy node", data.LegacyNodeID, err.Error())
-	// 	logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-	// 	return
-	// }
-	// var legacyNode models.LegacyNode
-	// if err = json.Unmarshal([]byte(record), &legacyNode); err != nil {
-	// 	logger.Log(0, "error decoding legacy node", err.Error())
-	// 	logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-	// 	return
-	// }
-	// if err := bcrypt.CompareHashAndPassword([]byte(legacyNode.Password), []byte(data.Password)); err != nil {
-	// 	logger.Log(0, "error decoding legacy password", err.Error())
-	// 	logic.ReturnErrorResponse(w, r, logic.FormatError(err, "unauthorized"))
-	// 	return
-	// }
-	// network, err := logic.GetNetwork(params["network"])
-	// if err != nil {
-	// 	logger.Log(0, "error retrieving network:  ", err.Error())
-	// 	logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-	// 	return
-	// }
-	// key, err := logic.CreateAccessKey(models.AccessKey{}, network)
-	// if err != nil {
-	// 	logger.Log(0, "error creating key:  ", err.Error())
-	// 	logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-	// 	return
-	// }
-	// data.JoinData.Key = key.Value
-	// payload, err := json.Marshal(data.JoinData)
-	// if err != nil {
-	// 	logger.Log(0, "error encoding data:  ", err.Error())
-	// 	logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
-	// 	return
-	// }
-	// r.Body = io.NopCloser(strings.NewReader(string(payload)))
-	// r.ContentLength = int64(len(string(payload)))
-	// logger.Log(3, "deleteing legacy node", data.LegacyNodeID, legacyNode.ID, legacyNode.Name)
-	// if err := database.DeleteRecord(database.NODES_TABLE_NAME, data.LegacyNodeID); err != nil {
-	// 	logger.Log(0, "error deleting legacy node", legacyNode.Name, err.Error())
-	// }
-	// // createNode(w, r) should not have been tied to another handler func
-	// //newly created node has same node id as legacy node allowing using legacyNode.ID in gateway creation
-	// logger.Log(3, "re-creating legacy gateways")
-	// if legacyNode.IsIngressGateway == "yes" {
-	// 	if _, err := logic.CreateIngressGateway(legacyNode.Network, legacyNode.ID, false); err != nil {
-	// 		logger.Log(0, "error creating ingress gateway during migration", err.Error())
-	// 	}
-	// }
-	// if legacyNode.IsEgressGateway == "yes" {
-	// 	if _, err := logic.CreateEgressGateway(legacyNode.EgressGatewayRequest); err != nil {
-	// 		logger.Log(0, "error creating egress gateway during migration", err.Error())
-	// 	}
-	// }
-	// if legacyNode.IsRelay == "yes" {
-	// 	if _, _, err := logic.CreateRelay(models.RelayRequest{
-	// 		NodeID:     legacyNode.ID,
-	// 		NetID:      legacyNode.Network,
-	// 		RelayAddrs: legacyNode.RelayAddrs,
-	// 	}); err != nil {
-	// 		logger.Log(0, "error creating relay during migration", err.Error())
-	// 	}
-	// }
+	data := models.MigrationData{}
+	err := json.NewDecoder(r.Body).Decode(&data)
+	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
+	}
+
+	var networksToAdd = []string{}
+	for i := range data.LegacyNodes {
+		legacyNode := data.LegacyNodes[i]
+		record, err := database.FetchRecord(database.NODES_TABLE_NAME, legacyNode.ID)
+		if err != nil {
+			logger.Log(0, "no record for legacy node", legacyNode.ID, err.Error())
+			continue
+		} else {
+			var oldLegacyNode models.LegacyNode
+			if err = json.Unmarshal([]byte(record), &oldLegacyNode); err != nil {
+				logger.Log(0, "error decoding legacy node", err.Error())
+				logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+				continue
+			}
+			if err := bcrypt.CompareHashAndPassword([]byte(oldLegacyNode.Password), []byte(legacyNode.Password)); err != nil {
+				logger.Log(0, "error decoding legacy password", err.Error())
+				logic.ReturnErrorResponse(w, r, logic.FormatError(err, "unauthorized"))
+				continue
+			}
+			networksToAdd = append(networksToAdd, oldLegacyNode.Network)
+			_ = database.DeleteRecord(database.NODES_TABLE_NAME, oldLegacyNode.ID)
+		}
+	}
+	if len(networksToAdd) == 0 {
+		logger.Log(0, "no valid networks to migrate for host", data.NewHost.Name)
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "unauthorized"))
+		return
+	}
+	if !logic.HostExists(&data.NewHost) {
+		if err = logic.CreateHost(&data.NewHost); err != nil {
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+			return
+		}
+	}
+	key, keyErr := logic.RetrievePublicTrafficKey()
+	if keyErr != nil {
+		logger.Log(0, "error retrieving key:", keyErr.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	server := servercfg.GetServerInfo()
+	server.TrafficKey = key
+	response := models.RegisterResponse{
+		ServerConf:    server,
+		RequestedHost: data.NewHost,
+	}
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(&response)
+	logger.Log(0, "successfully migrated host", data.NewHost.Name, data.NewHost.ID.String())
+	// notify host of changes, peer and node updates
+	go checkNetRegAndHostUpdate(networksToAdd, &data.NewHost)
 }
 }

+ 3 - 4
controllers/network_test.go

@@ -40,6 +40,8 @@ func TestMain(m *testing.M) {
 			logger.Log(3, "received node update", update.Action)
 			logger.Log(3, "received node update", update.Action)
 		}
 		}
 	}()
 	}()
+	os.Exit(m.Run())
+
 }
 }
 
 
 func TestCreateNetwork(t *testing.T) {
 func TestCreateNetwork(t *testing.T) {
@@ -213,10 +215,7 @@ func TestIpv6Network(t *testing.T) {
 
 
 func deleteAllNetworks() {
 func deleteAllNetworks() {
 	deleteAllNodes()
 	deleteAllNodes()
-	nets, _ := logic.GetNetworks()
-	for _, net := range nets {
-		logic.DeleteNetwork(net.NetID)
-	}
+	database.DeleteAllRecords(database.NETWORKS_TABLE_NAME)
 }
 }
 
 
 func createNet() {
 func createNet() {

+ 2 - 2
controllers/node.go

@@ -25,7 +25,6 @@ func nodeHandlers(r *mux.Router) {
 	r.HandleFunc("/api/nodes/{network}", authorize(false, true, "network", http.HandlerFunc(getNetworkNodes))).Methods(http.MethodGet)
 	r.HandleFunc("/api/nodes/{network}", authorize(false, true, "network", http.HandlerFunc(getNetworkNodes))).Methods(http.MethodGet)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(getNode))).Methods(http.MethodGet)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(getNode))).Methods(http.MethodGet)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(false, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPut)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(false, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPut)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}/migrate", migrate).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", authorize(false, true, "user", http.HandlerFunc(createRelay))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", authorize(false, true, "user", http.HandlerFunc(createRelay))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", authorize(false, true, "user", http.HandlerFunc(deleteRelay))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", authorize(false, true, "user", http.HandlerFunc(deleteRelay))).Methods(http.MethodDelete)
@@ -35,6 +34,7 @@ func nodeHandlers(r *mux.Router) {
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(false, http.HandlerFunc(deleteIngressGateway))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleteingress", logic.SecurityCheck(false, http.HandlerFunc(deleteIngressGateway))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods(http.MethodPost)
+	r.HandleFunc("/api/v1/nodes/migrate", migrate).Methods(http.MethodPost)
 }
 }
 
 
 // swagger:route POST /api/nodes/adm/{network}/authenticate nodes authenticate
 // swagger:route POST /api/nodes/adm/{network}/authenticate nodes authenticate
@@ -386,7 +386,7 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 		return
 	}
 	}
-	hostPeerUpdate, err := logic.GetPeerUpdateForHost(context.Background(), node.Network, host, nil)
+	hostPeerUpdate, err := logic.GetPeerUpdateForHost(context.Background(), node.Network, host, nil, nil)
 	if err != nil && !database.IsEmptyRecord(err) {
 	if err != nil && !database.IsEmptyRecord(err) {
 		logger.Log(0, r.Header.Get("user"),
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", host.ID.String(), err))
 			fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", host.ID.String(), err))

+ 2 - 2
controllers/node_test.go

@@ -113,12 +113,12 @@ func TestGetNetworkNodes(t *testing.T) {
 	t.Run("BadNet", func(t *testing.T) {
 	t.Run("BadNet", func(t *testing.T) {
 		node, err := logic.GetNetworkNodes("badnet")
 		node, err := logic.GetNetworkNodes("badnet")
 		assert.Nil(t, err)
 		assert.Nil(t, err)
-		assert.Nil(t, node)
+		assert.Equal(t, []models.Node{}, node)
 	})
 	})
 	t.Run("NoNodes", func(t *testing.T) {
 	t.Run("NoNodes", func(t *testing.T) {
 		node, err := logic.GetNetworkNodes("skynet")
 		node, err := logic.GetNetworkNodes("skynet")
 		assert.Nil(t, err)
 		assert.Nil(t, err)
-		assert.Nil(t, node)
+		assert.Equal(t, []models.Node{}, node)
 	})
 	})
 	t.Run("Success", func(t *testing.T) {
 	t.Run("Success", func(t *testing.T) {
 		createTestNode()
 		createTestNode()

+ 4 - 0
controllers/user_test.go

@@ -186,6 +186,7 @@ func TestGetUsers(t *testing.T) {
 		assert.Equal(t, []models.ReturnUser(nil), admin)
 		assert.Equal(t, []models.ReturnUser(nil), admin)
 	})
 	})
 	t.Run("UserExisits", func(t *testing.T) {
 	t.Run("UserExisits", func(t *testing.T) {
+		user.UserName = "anotheruser"
 		if err := logic.CreateUser(&adminUser); err != nil {
 		if err := logic.CreateUser(&adminUser); err != nil {
 			t.Error(err)
 			t.Error(err)
 		}
 		}
@@ -281,6 +282,9 @@ func TestVerifyAuthRequest(t *testing.T) {
 		assert.EqualError(t, err, "error retrieving user from db: could not find any records")
 		assert.EqualError(t, err, "error retrieving user from db: could not find any records")
 	})
 	})
 	t.Run("Non-Admin", func(t *testing.T) {
 	t.Run("Non-Admin", func(t *testing.T) {
+		user.IsAdmin = false
+		user.Password = "somepass"
+		user.UserName = "nonadmin"
 		if err := logic.CreateUser(&user); err != nil {
 		if err := logic.CreateUser(&user); err != nil {
 			t.Error(err)
 			t.Error(err)
 		}
 		}

+ 3 - 0
ee/initialize.go

@@ -40,6 +40,9 @@ func InitEE() {
 	logic.EnterpriseFailoverFunc = eelogic.SetFailover
 	logic.EnterpriseFailoverFunc = eelogic.SetFailover
 	logic.EnterpriseResetFailoverFunc = eelogic.ResetFailover
 	logic.EnterpriseResetFailoverFunc = eelogic.ResetFailover
 	logic.EnterpriseResetAllPeersFailovers = eelogic.WipeAffectedFailoversOnly
 	logic.EnterpriseResetAllPeersFailovers = eelogic.WipeAffectedFailoversOnly
+	logic.DenyClientNodeAccess = eelogic.DenyClientNode
+	logic.IsClientNodeAllowed = eelogic.IsClientNodeAllowed
+	logic.AllowClientNodeAccess = eelogic.RemoveDeniedNodeFromClient
 }
 }
 
 
 func setControllerLimits() {
 func setControllerLimits() {

+ 41 - 0
ee/logic/ext_acls.go

@@ -0,0 +1,41 @@
+package logic
+
+import "github.com/gravitl/netmaker/models"
+
+// DenyClientNode - add a denied node to an ext client's list
+func DenyClientNode(ec *models.ExtClient, clientOrNodeID string) (ok bool) {
+	if ec == nil || len(clientOrNodeID) == 0 {
+		return
+	}
+	if ec.ACLs == nil {
+		ec.ACLs = map[string]struct{}{}
+	}
+	ok = true
+	ec.ACLs[clientOrNodeID] = struct{}{}
+	return
+}
+
+// IsClientNodeAllowed - checks if given ext client and node are allowed to communicate
+func IsClientNodeAllowed(ec *models.ExtClient, clientOrNodeID string) bool {
+	if ec == nil || len(clientOrNodeID) == 0 {
+		return false
+	}
+	if ec.ACLs == nil {
+		return true
+	}
+	_, ok := ec.ACLs[clientOrNodeID]
+	return ok
+}
+
+// RemoveDeniedNodeFromClient - removes a node id from set of denied nodes
+func RemoveDeniedNodeFromClient(ec *models.ExtClient, clientOrNodeID string) bool {
+	if ec.ACLs == nil {
+		return true
+	}
+	_, ok := ec.ACLs[clientOrNodeID]
+	if !ok {
+		return false
+	}
+	delete(ec.ACLs, clientOrNodeID)
+	return true
+}

+ 12 - 21
functions/helpers_test.go

@@ -3,12 +3,14 @@ package functions
 import (
 import (
 	"context"
 	"context"
 	"encoding/json"
 	"encoding/json"
+	"os"
 	"testing"
 	"testing"
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
+	"github.com/stretchr/testify/assert"
 )
 )
 
 
 var (
 var (
@@ -39,40 +41,29 @@ func TestMain(m *testing.M) {
 			logger.Log(3, "received node update", update.Action)
 			logger.Log(3, "received node update", update.Action)
 		}
 		}
 	}()
 	}()
+	os.Exit(m.Run())
+
 }
 }
 
 
 func TestNetworkExists(t *testing.T) {
 func TestNetworkExists(t *testing.T) {
 	database.DeleteRecord(database.NETWORKS_TABLE_NAME, testNetwork.NetID)
 	database.DeleteRecord(database.NETWORKS_TABLE_NAME, testNetwork.NetID)
-	defer database.CloseDB()
 	exists, err := logic.NetworkExists(testNetwork.NetID)
 	exists, err := logic.NetworkExists(testNetwork.NetID)
-	if err == nil {
-		t.Fatalf("expected error, received nil")
-	}
-	if exists {
-		t.Fatalf("expected false")
-	}
+	assert.NotNil(t, err)
+	assert.False(t, exists)
 
 
 	err = logic.SaveNetwork(testNetwork)
 	err = logic.SaveNetwork(testNetwork)
-	if err != nil {
-		t.Fatalf("failed to save test network in databse: %s", err)
-	}
+	assert.Nil(t, err)
 	exists, err = logic.NetworkExists(testNetwork.NetID)
 	exists, err = logic.NetworkExists(testNetwork.NetID)
-	if err != nil {
-		t.Fatalf("expected nil, received err: %s", err)
-	}
-	if !exists {
-		t.Fatalf("expected network to exist in database")
-	}
+	assert.Nil(t, err)
+	assert.True(t, exists)
 
 
 	err = database.DeleteRecord(database.NETWORKS_TABLE_NAME, testNetwork.NetID)
 	err = database.DeleteRecord(database.NETWORKS_TABLE_NAME, testNetwork.NetID)
-	if err != nil {
-		t.Fatalf("expected nil, failed to delete test network: %s", err)
-	}
+	assert.Nil(t, err)
 }
 }
 
 
 func TestGetAllExtClients(t *testing.T) {
 func TestGetAllExtClients(t *testing.T) {
-	defer database.CloseDB()
-	database.DeleteRecord(database.EXT_CLIENT_TABLE_NAME, testExternalClient.ClientID)
+	err := database.DeleteRecord(database.EXT_CLIENT_TABLE_NAME, testExternalClient.ClientID)
+	assert.Nil(t, err)
 
 
 	extClients, err := GetAllExtClients()
 	extClients, err := GetAllExtClients()
 	if err == nil {
 	if err == nil {

+ 7 - 9
go.mod

@@ -15,11 +15,11 @@ require (
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/stretchr/testify v1.8.2
 	github.com/stretchr/testify v1.8.2
 	github.com/txn2/txeh v1.3.0
 	github.com/txn2/txeh v1.3.0
-	golang.org/x/crypto v0.6.0
-	golang.org/x/net v0.6.0 // indirect
-	golang.org/x/oauth2 v0.5.0
-	golang.org/x/sys v0.5.0 // indirect
-	golang.org/x/text v0.7.0 // indirect
+	golang.org/x/crypto v0.7.0
+	golang.org/x/net v0.8.0 // indirect
+	golang.org/x/oauth2 v0.6.0
+	golang.org/x/sys v0.6.0 // indirect
+	golang.org/x/text v0.8.0 // indirect
 	golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect
 	golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	google.golang.org/protobuf v1.28.1 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
@@ -42,8 +42,7 @@ require (
 
 
 require (
 require (
 	github.com/guumaster/tablewriter v0.0.10
 	github.com/guumaster/tablewriter v0.0.10
-	github.com/kr/pretty v0.3.0
-	github.com/matryer/is v1.4.0
+	github.com/matryer/is v1.4.1
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/spf13/cobra v1.6.1
 	github.com/spf13/cobra v1.6.1
 )
 )
@@ -52,9 +51,8 @@ require (
 	cloud.google.com/go/compute/metadata v0.2.1 // indirect
 	cloud.google.com/go/compute/metadata v0.2.1 // indirect
 	github.com/go-jose/go-jose/v3 v3.0.0 // indirect
 	github.com/go-jose/go-jose/v3 v3.0.0 // indirect
 	github.com/inconshreveable/mousetrap v1.0.1 // indirect
 	github.com/inconshreveable/mousetrap v1.0.1 // indirect
-	github.com/kr/text v0.2.0 // indirect
+	github.com/kr/pretty v0.3.1 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
-	github.com/rogpeppe/go-internal v1.8.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 )
 )
 
 

+ 16 - 18
go.sum

@@ -69,8 +69,8 @@ github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
 github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
 github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
 github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -80,8 +80,8 @@ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ic
 github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
 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.7/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.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
-github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
+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/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
 github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
@@ -111,9 +111,8 @@ github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sA
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
-github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f h1:BSnJgAfHzEp7o8PYJ7YfwAVHhqu7BYUTggcn/LGlUWY=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f h1:BSnJgAfHzEp7o8PYJ7YfwAVHhqu7BYUTggcn/LGlUWY=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f/go.mod h1:UW/gxgQwSePTvL1KA8QEHsXeYHP4xkoXgbDdN781p34=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f/go.mod h1:UW/gxgQwSePTvL1KA8QEHsXeYHP4xkoXgbDdN781p34=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -156,8 +155,8 @@ golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
-golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
+golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
+golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -173,11 +172,11 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
-golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
 golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
-golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
-golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
+golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
+golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -199,8 +198,8 @@ golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
 golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
@@ -210,8 +209,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -235,7 +234,6 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

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

+ 53 - 0
logic/clients.go

@@ -0,0 +1,53 @@
+package logic
+
+import "github.com/gravitl/netmaker/models"
+
+// functions defined here, handle client ACLs, should be set on ee
+
+var (
+	// DenyClientNodeAccess - function to handle adding a node to an ext client's denied node set
+	DenyClientNodeAccess = func(ec *models.ExtClient, clientOrNodeID string) bool { return true }
+	// IsClientNodeAllowed - function to check if an ext client's denied node set contains a node ID
+	IsClientNodeAllowed = func(ec *models.ExtClient, clientOrNodeID string) bool { return true }
+	// AllowClientNodeAccess - function to handle removing a node ID from ext client's denied nodes, thus allowing it
+	AllowClientNodeAccess = func(ec *models.ExtClient, clientOrNodeID string) bool { return true }
+)
+
+// SetClientDefaultACLs - set's a client's default ACLs based on network and nodes in network
+func SetClientDefaultACLs(ec *models.ExtClient) error {
+	if !isEE {
+		return nil
+	}
+	networkNodes, err := GetNetworkNodes(ec.Network)
+	if err != nil {
+		return err
+	}
+	network, err := GetNetwork(ec.Network)
+	if err != nil {
+		return err
+	}
+	for i := range networkNodes {
+		currNode := networkNodes[i]
+		if network.DefaultACL == "no" || currNode.DefaultACL == "no" {
+			DenyClientNodeAccess(ec, currNode.ID.String())
+		}
+	}
+	return nil
+}
+
+// SetClientACLs - overwrites an ext client's ACL
+func SetClientACLs(ec *models.ExtClient, newACLs map[string]struct{}) {
+	if ec == nil || newACLs == nil || !isEE {
+		return
+	}
+	ec.ACLs = newACLs
+}
+
+// IsClientNodeAllowedByID - checks if a given ext client ID + nodeID are allowed
+func IsClientNodeAllowedByID(clientID, networkName, clientOrNodeID string) bool {
+	client, err := GetExtClient(clientID, networkName)
+	if err != nil {
+		return false
+	}
+	return IsClientNodeAllowed(&client, clientOrNodeID)
+}

+ 22 - 3
logic/extpeers.go

@@ -2,6 +2,7 @@ package logic
 
 
 import (
 import (
 	"encoding/json"
 	"encoding/json"
+	"fmt"
 	"time"
 	"time"
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
@@ -114,6 +115,22 @@ func GetExtClient(clientid string, network string) (models.ExtClient, error) {
 	return extclient, err
 	return extclient, err
 }
 }
 
 
+// GetExtClient - gets a single ext client on a network
+func GetExtClientByPubKey(publicKey string, network string) (*models.ExtClient, error) {
+	netClients, err := GetNetworkExtClients(network)
+	if err != nil {
+		return nil, err
+	}
+	for i := range netClients {
+		ec := netClients[i]
+		if ec.PublicKey == publicKey {
+			return &ec, nil
+		}
+	}
+
+	return nil, fmt.Errorf("no client found")
+}
+
 // CreateExtClient - creates an extclient
 // CreateExtClient - creates an extclient
 func CreateExtClient(extclient *models.ExtClient) error {
 func CreateExtClient(extclient *models.ExtClient) error {
 
 
@@ -172,15 +189,17 @@ func CreateExtClient(extclient *models.ExtClient) error {
 }
 }
 
 
 // UpdateExtClient - only supports name changes right now
 // UpdateExtClient - only supports name changes right now
-func UpdateExtClient(newclientid string, network string, enabled bool, client *models.ExtClient) (*models.ExtClient, error) {
-
+func UpdateExtClient(newclientid string, network string, enabled bool, client *models.ExtClient, newACLs map[string]struct{}) (*models.ExtClient, error) {
 	err := DeleteExtClient(network, client.ClientID)
 	err := DeleteExtClient(network, client.ClientID)
 	if err != nil {
 	if err != nil {
 		return client, err
 		return client, err
 	}
 	}
 	client.ClientID = newclientid
 	client.ClientID = newclientid
 	client.Enabled = enabled
 	client.Enabled = enabled
-	CreateExtClient(client)
+	SetClientACLs(client, newACLs)
+	if err = CreateExtClient(client); err != nil {
+		return client, err
+	}
 	return client, err
 	return client, err
 }
 }
 
 

+ 20 - 1
logic/host_test.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
 	"net"
 	"net"
+	"os"
 	"testing"
 	"testing"
 
 
 	"github.com/google/uuid"
 	"github.com/google/uuid"
@@ -12,7 +13,7 @@ import (
 	"github.com/matryer/is"
 	"github.com/matryer/is"
 )
 )
 
 
-func TestCheckPorts(t *testing.T) {
+func TestMain(m *testing.M) {
 	database.InitializeDatabase()
 	database.InitializeDatabase()
 	defer database.CloseDB()
 	defer database.CloseDB()
 	peerUpdate := make(chan *models.Node)
 	peerUpdate := make(chan *models.Node)
@@ -24,6 +25,10 @@ func TestCheckPorts(t *testing.T) {
 		}
 		}
 	}()
 	}()
 
 
+	os.Exit(m.Run())
+}
+
+func TestCheckPorts(t *testing.T) {
 	h := models.Host{
 	h := models.Host{
 		ID:              uuid.New(),
 		ID:              uuid.New(),
 		EndpointIP:      net.ParseIP("192.168.1.1"),
 		EndpointIP:      net.ParseIP("192.168.1.1"),
@@ -36,10 +41,16 @@ func TestCheckPorts(t *testing.T) {
 		ListenPort:      51830,
 		ListenPort:      51830,
 		ProxyListenPort: 51730,
 		ProxyListenPort: 51730,
 	}
 	}
+	//not sure why this initialization is required but without it
+	// RemoveHost returns database is closed
+	database.InitializeDatabase()
+	RemoveHost(&h)
 	CreateHost(&h)
 	CreateHost(&h)
 	t.Run("no change", func(t *testing.T) {
 	t.Run("no change", func(t *testing.T) {
 		is := is.New(t)
 		is := is.New(t)
 		CheckHostPorts(&testHost)
 		CheckHostPorts(&testHost)
+		t.Log(testHost.ListenPort, testHost.ProxyListenPort)
+		t.Log(h.ListenPort, h.ProxyListenPort)
 		is.Equal(testHost.ListenPort, 51830)
 		is.Equal(testHost.ListenPort, 51830)
 		is.Equal(testHost.ProxyListenPort, 51730)
 		is.Equal(testHost.ProxyListenPort, 51730)
 	})
 	})
@@ -47,6 +58,8 @@ func TestCheckPorts(t *testing.T) {
 		is := is.New(t)
 		is := is.New(t)
 		testHost.ListenPort = 51821
 		testHost.ListenPort = 51821
 		CheckHostPorts(&testHost)
 		CheckHostPorts(&testHost)
+		t.Log(testHost.ListenPort, testHost.ProxyListenPort)
+		t.Log(h.ListenPort, h.ProxyListenPort)
 		is.Equal(testHost.ListenPort, 51822)
 		is.Equal(testHost.ListenPort, 51822)
 		is.Equal(testHost.ProxyListenPort, 51730)
 		is.Equal(testHost.ProxyListenPort, 51730)
 	})
 	})
@@ -54,6 +67,8 @@ func TestCheckPorts(t *testing.T) {
 		is := is.New(t)
 		is := is.New(t)
 		testHost.ProxyListenPort = 65535
 		testHost.ProxyListenPort = 65535
 		CheckHostPorts(&testHost)
 		CheckHostPorts(&testHost)
+		t.Log(testHost.ListenPort, testHost.ProxyListenPort)
+		t.Log(h.ListenPort, h.ProxyListenPort)
 		is.Equal(testHost.ListenPort, 51822)
 		is.Equal(testHost.ListenPort, 51822)
 		is.Equal(testHost.ProxyListenPort, minPort)
 		is.Equal(testHost.ProxyListenPort, minPort)
 	})
 	})
@@ -61,6 +76,8 @@ func TestCheckPorts(t *testing.T) {
 		is := is.New(t)
 		is := is.New(t)
 		testHost.ListenPort = maxPort
 		testHost.ListenPort = maxPort
 		CheckHostPorts(&testHost)
 		CheckHostPorts(&testHost)
+		t.Log(testHost.ListenPort, testHost.ProxyListenPort)
+		t.Log(h.ListenPort, h.ProxyListenPort)
 		is.Equal(testHost.ListenPort, minPort)
 		is.Equal(testHost.ListenPort, minPort)
 		is.Equal(testHost.ProxyListenPort, minPort+1)
 		is.Equal(testHost.ProxyListenPort, minPort+1)
 	})
 	})
@@ -68,6 +85,8 @@ func TestCheckPorts(t *testing.T) {
 		is := is.New(t)
 		is := is.New(t)
 		testHost.ProxyListenPort = 51821
 		testHost.ProxyListenPort = 51821
 		CheckHostPorts(&testHost)
 		CheckHostPorts(&testHost)
+		t.Log(testHost.ListenPort, testHost.ProxyListenPort)
+		t.Log(h.ListenPort, h.ProxyListenPort)
 		is.Equal(testHost.ListenPort, minPort)
 		is.Equal(testHost.ListenPort, minPort)
 		is.Equal(testHost.ProxyListenPort, 51822)
 		is.Equal(testHost.ProxyListenPort, 51822)
 	})
 	})

+ 10 - 0
logic/hosts.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"encoding/json"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
+	"log"
 
 
 	"github.com/google/uuid"
 	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
@@ -96,6 +97,15 @@ func CreateHost(h *models.Host) error {
 		return err
 		return err
 	}
 	}
 	h.HostPass = string(hash)
 	h.HostPass = string(hash)
+	// if another server has already updated proxyenabled, leave it alone
+	if !h.ProxyEnabledSet {
+		log.Println("checking default proxy", servercfg.GetServerConfig().DefaultProxyMode)
+		if servercfg.GetServerConfig().DefaultProxyMode.Set {
+			h.ProxyEnabledSet = true
+			h.ProxyEnabled = servercfg.GetServerConfig().DefaultProxyMode.Value
+			log.Println("set proxy enabled to ", h.ProxyEnabled)
+		}
+	}
 	checkForZombieHosts(h)
 	checkForZombieHosts(h)
 	return UpsertHost(h)
 	return UpsertHost(h)
 }
 }

+ 2 - 1
logic/metrics/metrics.go

@@ -10,7 +10,7 @@ import (
 )
 )
 
 
 // Collect - collects metrics
 // Collect - collects metrics
-func Collect(iface, server, network string, peerMap models.PeerMap) (*models.Metrics, error) {
+func Collect(iface, server, network string, peerMap models.PeerMap, proxy bool) (*models.Metrics, error) {
 	var metrics models.Metrics
 	var metrics models.Metrics
 	metrics.Connectivity = make(map[string]models.Metric)
 	metrics.Connectivity = make(map[string]models.Metric)
 	var wgclient, err = wgctrl.New()
 	var wgclient, err = wgctrl.New()
@@ -45,6 +45,7 @@ func Collect(iface, server, network string, peerMap models.PeerMap) (*models.Met
 		newMetric.TotalSent = int64(proxyMetrics.TrafficSent)
 		newMetric.TotalSent = int64(proxyMetrics.TrafficSent)
 		newMetric.Latency = int64(proxyMetrics.LastRecordedLatency)
 		newMetric.Latency = int64(proxyMetrics.LastRecordedLatency)
 		newMetric.Connected = proxyMetrics.NodeConnectionStatus[id]
 		newMetric.Connected = proxyMetrics.NodeConnectionStatus[id]
+		newMetric.CollectedByProxy = proxy
 		if newMetric.Connected {
 		if newMetric.Connected {
 			newMetric.Uptime = 1
 			newMetric.Uptime = 1
 		}
 		}

+ 84 - 16
logic/peers.go

@@ -47,7 +47,7 @@ func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.Proxy
 		relayPeersMap := make(map[string]models.RelayedConf)
 		relayPeersMap := make(map[string]models.RelayedConf)
 		for _, relayedHost := range relayedHosts {
 		for _, relayedHost := range relayedHosts {
 			relayedHost := relayedHost
 			relayedHost := relayedHost
-			payload, err := GetPeerUpdateForHost(ctx, "", &relayedHost, nil)
+			payload, err := GetPeerUpdateForHost(ctx, "", &relayedHost, nil, nil)
 			if err == nil {
 			if err == nil {
 				relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, GetPeerListenPort(&relayedHost)))
 				relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, GetPeerListenPort(&relayedHost)))
 				if udpErr == nil {
 				if udpErr == nil {
@@ -71,6 +71,9 @@ func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.Proxy
 		if err != nil {
 		if err != nil {
 			continue
 			continue
 		}
 		}
+		if !node.Connected || node.PendingDelete || node.Action == models.NODE_DELETE {
+			continue
+		}
 		currentPeers, err := GetNetworkNodes(node.Network)
 		currentPeers, err := GetNetworkNodes(node.Network)
 		if err != nil {
 		if err != nil {
 			continue
 			continue
@@ -80,6 +83,9 @@ func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.Proxy
 				//skip yourself
 				//skip yourself
 				continue
 				continue
 			}
 			}
+			if !peer.Connected || peer.PendingDelete || peer.Action == models.NODE_DELETE {
+				continue
+			}
 			peerHost, err := GetHost(peer.HostID.String())
 			peerHost, err := GetHost(peer.HostID.String())
 			if err != nil {
 			if err != nil {
 				continue
 				continue
@@ -90,6 +96,7 @@ func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.Proxy
 				currPeerConf = models.PeerConf{
 				currPeerConf = models.PeerConf{
 					Proxy:            peerHost.ProxyEnabled,
 					Proxy:            peerHost.ProxyEnabled,
 					PublicListenPort: int32(GetPeerListenPort(peerHost)),
 					PublicListenPort: int32(GetPeerListenPort(peerHost)),
+					ProxyListenPort:  GetProxyListenPort(peerHost),
 				}
 				}
 			}
 			}
 
 
@@ -133,7 +140,7 @@ func ResetPeerUpdateContext() {
 }
 }
 
 
 // GetPeerUpdateForHost - gets the consolidated peer update for the host from all networks
 // GetPeerUpdateForHost - gets the consolidated peer update for the host from all networks
-func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host, deletedNode *models.Node) (models.HostPeerUpdate, error) {
+func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host, deletedNode *models.Node, deletedClient *models.ExtClient) (models.HostPeerUpdate, error) {
 	if host == nil {
 	if host == nil {
 		return models.HostPeerUpdate{}, errors.New("host is nil")
 		return models.HostPeerUpdate{}, errors.New("host is nil")
 	}
 	}
@@ -152,10 +159,11 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 		IngressInfo: models.IngressInfo{
 		IngressInfo: models.IngressInfo{
 			ExtPeers: make(map[string]models.ExtClientInfo),
 			ExtPeers: make(map[string]models.ExtClientInfo),
 		},
 		},
-		EgressInfo: make(map[string]models.EgressInfo),
-		PeerIDs:    make(models.PeerMap, 0),
-		Peers:      []wgtypes.PeerConfig{},
-		NodePeers:  []wgtypes.PeerConfig{},
+		EgressInfo:      make(map[string]models.EgressInfo),
+		PeerIDs:         make(models.PeerMap, 0),
+		Peers:           []wgtypes.PeerConfig{},
+		NodePeers:       []wgtypes.PeerConfig{},
+		HostNetworkInfo: models.HostInfoMap{},
 	}
 	}
 
 
 	logger.Log(1, "peer update for host", host.ID.String())
 	logger.Log(1, "peer update for host", host.ID.String())
@@ -250,6 +258,7 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 									},
 									},
 									PeerKey: extPeerIdAndAddr.ID,
 									PeerKey: extPeerIdAndAddr.ID,
 									Allow:   true,
 									Allow:   true,
+									ID:      extPeerIdAndAddr.ID,
 								}
 								}
 							}
 							}
 						}
 						}
@@ -265,19 +274,26 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 						},
 						},
 						PeerKey: peerHost.PublicKey.String(),
 						PeerKey: peerHost.PublicKey.String(),
 						Allow:   true,
 						Allow:   true,
+						ID:      peer.ID.String(),
 					}
 					}
 				}
 				}
 
 
+				peerProxyPort := GetProxyListenPort(peerHost)
 				var nodePeer wgtypes.PeerConfig
 				var nodePeer wgtypes.PeerConfig
 				if _, ok := hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()]; !ok {
 				if _, ok := hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()]; !ok {
 					hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()] = make(map[string]models.IDandAddr)
 					hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()] = make(map[string]models.IDandAddr)
 					hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
 					hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
 					peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
 					peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
 					hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
 					hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
-						ID:      peer.ID.String(),
-						Address: peer.PrimaryAddress(),
-						Name:    peerHost.Name,
-						Network: peer.Network,
+						ID:              peer.ID.String(),
+						Address:         peer.PrimaryAddress(),
+						Name:            peerHost.Name,
+						Network:         peer.Network,
+						ProxyListenPort: peerProxyPort,
+					}
+					hostPeerUpdate.HostNetworkInfo[peerHost.PublicKey.String()] = models.HostNetworkInfo{
+						Interfaces:      peerHost.Interfaces,
+						ProxyListenPort: peerProxyPort,
 					}
 					}
 					nodePeer = peerConfig
 					nodePeer = peerConfig
 				} else {
 				} else {
@@ -285,10 +301,15 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 					peerAllowedIPs = append(peerAllowedIPs, allowedips...)
 					peerAllowedIPs = append(peerAllowedIPs, allowedips...)
 					hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs = peerAllowedIPs
 					hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs = peerAllowedIPs
 					hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
 					hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
-						ID:      peer.ID.String(),
-						Address: peer.PrimaryAddress(),
-						Name:    peerHost.Name,
-						Network: peer.Network,
+						ID:              peer.ID.String(),
+						Address:         peer.PrimaryAddress(),
+						Name:            peerHost.Name,
+						Network:         peer.Network,
+						ProxyListenPort: GetProxyListenPort(peerHost),
+					}
+					hostPeerUpdate.HostNetworkInfo[peerHost.PublicKey.String()] = models.HostNetworkInfo{
+						Interfaces:      peerHost.Interfaces,
+						ProxyListenPort: peerProxyPort,
 					}
 					}
 					nodePeer = hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]]
 					nodePeer = hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]]
 				}
 				}
@@ -299,7 +320,6 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 						Address:         peer.PrimaryAddress(),
 						Address:         peer.PrimaryAddress(),
 						Name:            peerHost.Name,
 						Name:            peerHost.Name,
 						Network:         peer.Network,
 						Network:         peer.Network,
-						Interfaces:      peerHost.Interfaces,
 						ProxyListenPort: peerHost.ProxyListenPort,
 						ProxyListenPort: peerHost.ProxyListenPort,
 					}
 					}
 					hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, nodePeer)
 					hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, nodePeer)
@@ -319,6 +339,7 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 							},
 							},
 							PeerKey: extPeerIdAndAddr.ID,
 							PeerKey: extPeerIdAndAddr.ID,
 							Allow:   true,
 							Allow:   true,
+							ID:      extPeerIdAndAddr.ID,
 						}
 						}
 					}
 					}
 					hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, extPeers...)
 					hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, extPeers...)
@@ -331,6 +352,7 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 							Name:    extPeerIdAndAddr.Name,
 							Name:    extPeerIdAndAddr.Name,
 							Network: node.Network,
 							Network: node.Network,
 						}
 						}
+
 						hostPeerUpdate.IngressInfo.ExtPeers[extPeerIdAndAddr.ID] = models.ExtClientInfo{
 						hostPeerUpdate.IngressInfo.ExtPeers[extPeerIdAndAddr.ID] = models.ExtClientInfo{
 							Masquerade: true,
 							Masquerade: true,
 							IngGwAddr: net.IPNet{
 							IngGwAddr: net.IPNet{
@@ -343,7 +365,7 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 								Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
 								Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
 							},
 							},
 							ExtPeerKey: extPeerIdAndAddr.ID,
 							ExtPeerKey: extPeerIdAndAddr.ID,
-							Peers:      nodePeerMap,
+							Peers:      filterNodeMapForClientACLs(extPeerIdAndAddr.ID, node.Network, nodePeerMap),
 						}
 						}
 						if node.Network == network {
 						if node.Network == network {
 							hostPeerUpdate.PeerIDs[extPeerIdAndAddr.ID] = extPeerIdAndAddr
 							hostPeerUpdate.PeerIDs[extPeerIdAndAddr.ID] = extPeerIdAndAddr
@@ -386,6 +408,16 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 		hostPeerUpdate.NodePeers[i] = peer
 		hostPeerUpdate.NodePeers[i] = peer
 	}
 	}
 
 
+	if deletedClient != nil {
+		key, err := wgtypes.ParseKey(deletedClient.PublicKey)
+		if err == nil {
+			hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, wgtypes.PeerConfig{
+				PublicKey: key,
+				Remove:    true,
+			})
+		}
+	}
+
 	return hostPeerUpdate, nil
 	return hostPeerUpdate, nil
 }
 }
 
 
@@ -402,6 +434,15 @@ func GetPeerListenPort(host *models.Host) int {
 	return peerPort
 	return peerPort
 }
 }
 
 
+// GetProxyListenPort - fetches the proxy listen port
+func GetProxyListenPort(host *models.Host) int {
+	proxyPort := host.ProxyListenPort
+	if host.PublicListenPort != 0 {
+		proxyPort = host.PublicListenPort
+	}
+	return proxyPort
+}
+
 func getExtPeers(node *models.Node) ([]wgtypes.PeerConfig, []models.IDandAddr, error) {
 func getExtPeers(node *models.Node) ([]wgtypes.PeerConfig, []models.IDandAddr, error) {
 	var peers []wgtypes.PeerConfig
 	var peers []wgtypes.PeerConfig
 	var idsAndAddr []models.IDandAddr
 	var idsAndAddr []models.IDandAddr
@@ -414,6 +455,7 @@ func getExtPeers(node *models.Node) ([]wgtypes.PeerConfig, []models.IDandAddr, e
 		return peers, idsAndAddr, err
 		return peers, idsAndAddr, err
 	}
 	}
 	for _, extPeer := range extPeers {
 	for _, extPeer := range extPeers {
+		extPeer := extPeer
 		pubkey, err := wgtypes.ParseKey(extPeer.PublicKey)
 		pubkey, err := wgtypes.ParseKey(extPeer.PublicKey)
 		if err != nil {
 		if err != nil {
 			logger.Log(1, "error parsing ext pub key:", err.Error())
 			logger.Log(1, "error parsing ext pub key:", err.Error())
@@ -645,3 +687,29 @@ func getCIDRMaskFromAddr(addr string) net.IPMask {
 	}
 	}
 	return cidr
 	return cidr
 }
 }
+
+// accounts for ext client ACLs
+func filterNodeMapForClientACLs(publicKey, network string, nodePeerMap map[string]models.PeerRouteInfo) map[string]models.PeerRouteInfo {
+	if !isEE {
+		return nodePeerMap
+	}
+	if nodePeerMap == nil {
+		return map[string]models.PeerRouteInfo{}
+	}
+
+	if len(publicKey) == 0 || len(network) == 0 {
+		return nodePeerMap
+	}
+
+	client, err := GetExtClientByPubKey(publicKey, network)
+	if err != nil {
+		return nodePeerMap
+	}
+	for k := range nodePeerMap {
+		currNodePeer := nodePeerMap[k]
+		if _, ok := client.ACLs[currNodePeer.ID]; ok {
+			delete(nodePeerMap, k)
+		}
+	}
+	return nodePeerMap
+}

+ 3 - 1
logic/pro/networkuser_test.go

@@ -1,6 +1,7 @@
 package pro
 package pro
 
 
 import (
 import (
+	"os"
 	"testing"
 	"testing"
 
 
 	"github.com/google/uuid"
 	"github.com/google/uuid"
@@ -13,6 +14,7 @@ import (
 func TestMain(m *testing.M) {
 func TestMain(m *testing.M) {
 	database.InitializeDatabase()
 	database.InitializeDatabase()
 	defer database.CloseDB()
 	defer database.CloseDB()
+	os.Exit(m.Run())
 }
 }
 
 
 func TestNetworkUserLogic(t *testing.T) {
 func TestNetworkUserLogic(t *testing.T) {
@@ -33,7 +35,7 @@ func TestNetworkUserLogic(t *testing.T) {
 	}
 	}
 
 
 	clients := []models.ExtClient{
 	clients := []models.ExtClient{
-		models.ExtClient{
+		{
 			ClientID: "coolclient",
 			ClientID: "coolclient",
 		},
 		},
 	}
 	}

+ 11 - 1
logic/telemetry.go

@@ -5,6 +5,7 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/posthog/posthog-go"
 	"github.com/posthog/posthog-go"
@@ -84,7 +85,7 @@ func fetchTelemetryData() (telemetryData, error) {
 	data.Users = getDBLength(database.USERS_TABLE_NAME)
 	data.Users = getDBLength(database.USERS_TABLE_NAME)
 	data.Networks = getDBLength(database.NETWORKS_TABLE_NAME)
 	data.Networks = getDBLength(database.NETWORKS_TABLE_NAME)
 	data.Version = servercfg.GetVersion()
 	data.Version = servercfg.GetVersion()
-	//data.Servers = GetServerCount()
+	data.Servers = getServerCount()
 	nodes, err := GetAllNodes()
 	nodes, err := GetAllNodes()
 	if err == nil {
 	if err == nil {
 		data.Nodes = len(nodes)
 		data.Nodes = len(nodes)
@@ -93,6 +94,15 @@ func fetchTelemetryData() (telemetryData, error) {
 	return data, err
 	return data, err
 }
 }
 
 
+// getServerCount returns number of servers from database
+func getServerCount() int {
+	data, err := database.FetchRecords(database.SERVER_UUID_TABLE_NAME)
+	if err != nil {
+		logger.Log(0, "errror retrieving server data", err.Error())
+	}
+	return len(data)
+}
+
 // setTelemetryTimestamp - Give the entry in the DB a new timestamp
 // setTelemetryTimestamp - Give the entry in the DB a new timestamp
 func setTelemetryTimestamp(telRecord *models.Telemetry) error {
 func setTelemetryTimestamp(telRecord *models.Telemetry) error {
 	lastsend := time.Now().Unix()
 	lastsend := time.Now().Unix()

+ 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.3"
+var version = "v0.18.4"
 
 
 // Start DB Connection and start API Request Handler
 // Start DB Connection and start API Request Handler
 func main() {
 func main() {

+ 13 - 12
models/extclient.go

@@ -2,16 +2,17 @@ package models
 
 
 // ExtClient - struct for external clients
 // ExtClient - struct for external clients
 type ExtClient struct {
 type ExtClient struct {
-	ClientID               string `json:"clientid" bson:"clientid"`
-	Description            string `json:"description" bson:"description"`
-	PrivateKey             string `json:"privatekey" bson:"privatekey"`
-	PublicKey              string `json:"publickey" bson:"publickey"`
-	Network                string `json:"network" bson:"network"`
-	Address                string `json:"address" bson:"address"`
-	Address6               string `json:"address6" bson:"address6"`
-	IngressGatewayID       string `json:"ingressgatewayid" bson:"ingressgatewayid"`
-	IngressGatewayEndpoint string `json:"ingressgatewayendpoint" bson:"ingressgatewayendpoint"`
-	LastModified           int64  `json:"lastmodified" bson:"lastmodified"`
-	Enabled                bool   `json:"enabled" bson:"enabled"`
-	OwnerID                string `json:"ownerid" bson:"ownerid"`
+	ClientID               string              `json:"clientid" bson:"clientid"`
+	Description            string              `json:"description" bson:"description"`
+	PrivateKey             string              `json:"privatekey" bson:"privatekey"`
+	PublicKey              string              `json:"publickey" bson:"publickey"`
+	Network                string              `json:"network" bson:"network"`
+	Address                string              `json:"address" bson:"address"`
+	Address6               string              `json:"address6" bson:"address6"`
+	IngressGatewayID       string              `json:"ingressgatewayid" bson:"ingressgatewayid"`
+	IngressGatewayEndpoint string              `json:"ingressgatewayendpoint" bson:"ingressgatewayendpoint"`
+	LastModified           int64               `json:"lastmodified" bson:"lastmodified"`
+	Enabled                bool                `json:"enabled" bson:"enabled"`
+	OwnerID                string              `json:"ownerid" bson:"ownerid"`
+	ACLs                   map[string]struct{} `json:"acls,omitempty" bson:"acls,omitempty"`
 }
 }

+ 18 - 2
models/host.go

@@ -7,6 +7,21 @@ import (
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 )
 
 
+// OS_Types - list of OS types Netmaker cares about
+var OS_Types = struct {
+	Linux   string
+	Windows string
+	Mac     string
+	FreeBSD string
+	IoT     string
+}{
+	Linux:   "linux",
+	Windows: "windows",
+	Mac:     "darwin",
+	FreeBSD: "freebsd",
+	IoT:     "iot",
+}
+
 // WIREGUARD_INTERFACE name of wireguard interface
 // WIREGUARD_INTERFACE name of wireguard interface
 const WIREGUARD_INTERFACE = "netmaker"
 const WIREGUARD_INTERFACE = "netmaker"
 
 
@@ -29,7 +44,7 @@ type Host struct {
 	MTU              int              `json:"mtu" yaml:"mtu"`
 	MTU              int              `json:"mtu" yaml:"mtu"`
 	PublicKey        wgtypes.Key      `json:"publickey" yaml:"publickey"`
 	PublicKey        wgtypes.Key      `json:"publickey" yaml:"publickey"`
 	MacAddress       net.HardwareAddr `json:"macaddress" yaml:"macaddress"`
 	MacAddress       net.HardwareAddr `json:"macaddress" yaml:"macaddress"`
-	TrafficKeyPublic []byte           `json:"traffickeypublic" yaml:"trafficekeypublic"`
+	TrafficKeyPublic []byte           `json:"traffickeypublic" yaml:"traffickeypublic"`
 	InternetGateway  net.UDPAddr      `json:"internetgateway" yaml:"internetgateway"`
 	InternetGateway  net.UDPAddr      `json:"internetgateway" yaml:"internetgateway"`
 	Nodes            []string         `json:"nodes" yaml:"nodes"`
 	Nodes            []string         `json:"nodes" yaml:"nodes"`
 	IsRelayed        bool             `json:"isrelayed" yaml:"isrelayed"`
 	IsRelayed        bool             `json:"isrelayed" yaml:"isrelayed"`
@@ -37,9 +52,10 @@ type Host struct {
 	IsRelay          bool             `json:"isrelay" yaml:"isrelay"`
 	IsRelay          bool             `json:"isrelay" yaml:"isrelay"`
 	RelayedHosts     []string         `json:"relay_hosts" yaml:"relay_hosts"`
 	RelayedHosts     []string         `json:"relay_hosts" yaml:"relay_hosts"`
 	Interfaces       []Iface          `json:"interfaces" yaml:"interfaces"`
 	Interfaces       []Iface          `json:"interfaces" yaml:"interfaces"`
-	DefaultInterface string           `json:"defaultinterface" yaml:"defautlinterface"`
+	DefaultInterface string           `json:"defaultinterface" yaml:"defaultinterface"`
 	EndpointIP       net.IP           `json:"endpointip" yaml:"endpointip"`
 	EndpointIP       net.IP           `json:"endpointip" yaml:"endpointip"`
 	ProxyEnabled     bool             `json:"proxy_enabled" yaml:"proxy_enabled"`
 	ProxyEnabled     bool             `json:"proxy_enabled" yaml:"proxy_enabled"`
+	ProxyEnabledSet  bool             `json:"proxy_enabled_updated" yaml:"proxy_enabled_updated"`
 	IsDocker         bool             `json:"isdocker" yaml:"isdocker"`
 	IsDocker         bool             `json:"isdocker" yaml:"isdocker"`
 	IsK8S            bool             `json:"isk8s" yaml:"isk8s"`
 	IsK8S            bool             `json:"isk8s" yaml:"isk8s"`
 	IsStatic         bool             `json:"isstatic" yaml:"isstatic"`
 	IsStatic         bool             `json:"isstatic" yaml:"isstatic"`

+ 23 - 14
models/metrics.go

@@ -15,24 +15,33 @@ type Metrics struct {
 
 
 // Metric - holds a metric for data between nodes
 // Metric - holds a metric for data between nodes
 type Metric struct {
 type Metric struct {
-	NodeName      string        `json:"node_name" bson:"node_name" yaml:"node_name"`
-	Uptime        int64         `json:"uptime" bson:"uptime" yaml:"uptime"`
-	TotalTime     int64         `json:"totaltime" bson:"totaltime" yaml:"totaltime"`
-	Latency       int64         `json:"latency" bson:"latency" yaml:"latency"`
-	TotalReceived int64         `json:"totalreceived" bson:"totalreceived" yaml:"totalreceived"`
-	TotalSent     int64         `json:"totalsent" bson:"totalsent" yaml:"totalsent"`
-	ActualUptime  time.Duration `json:"actualuptime" bson:"actualuptime" yaml:"actualuptime"`
-	PercentUp     float64       `json:"percentup" bson:"percentup" yaml:"percentup"`
-	Connected     bool          `json:"connected" bson:"connected" yaml:"connected"`
+	NodeName         string        `json:"node_name" bson:"node_name" yaml:"node_name"`
+	Uptime           int64         `json:"uptime" bson:"uptime" yaml:"uptime"`
+	TotalTime        int64         `json:"totaltime" bson:"totaltime" yaml:"totaltime"`
+	Latency          int64         `json:"latency" bson:"latency" yaml:"latency"`
+	TotalReceived    int64         `json:"totalreceived" bson:"totalreceived" yaml:"totalreceived"`
+	TotalSent        int64         `json:"totalsent" bson:"totalsent" yaml:"totalsent"`
+	ActualUptime     time.Duration `json:"actualuptime" bson:"actualuptime" yaml:"actualuptime"`
+	PercentUp        float64       `json:"percentup" bson:"percentup" yaml:"percentup"`
+	Connected        bool          `json:"connected" bson:"connected" yaml:"connected"`
+	CollectedByProxy bool          `json:"collected_by_proxy" bson:"collected_by_proxy" yaml:"collected_by_proxy"`
 }
 }
 
 
 // IDandAddr - struct to hold ID and primary Address
 // IDandAddr - struct to hold ID and primary Address
 type IDandAddr struct {
 type IDandAddr struct {
-	ID              string  `json:"id" bson:"id" yaml:"id"`
-	Address         string  `json:"address" bson:"address" yaml:"address"`
-	Name            string  `json:"name" bson:"name" yaml:"name"`
-	IsServer        string  `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"`
-	Network         string  `json:"network" bson:"network" yaml:"network" validate:"network"`
+	ID              string `json:"id" bson:"id" yaml:"id"`
+	Address         string `json:"address" bson:"address" yaml:"address"`
+	Name            string `json:"name" bson:"name" yaml:"name"`
+	IsServer        string `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"`
+	Network         string `json:"network" bson:"network" yaml:"network" validate:"network"`
+	ProxyListenPort int    `json:"proxy_listen_port" yaml:"proxy_listen_port"`
+}
+
+// HostInfoMap - map of host public keys to host networking info
+type HostInfoMap map[string]HostNetworkInfo
+
+// HostNetworkInfo - holds info related to host networking (used for client side peer calculations)
+type HostNetworkInfo struct {
 	Interfaces      []Iface `json:"interfaces" yaml:"interfaces"`
 	Interfaces      []Iface `json:"interfaces" yaml:"interfaces"`
 	ProxyListenPort int     `json:"proxy_listen_port" yaml:"proxy_listen_port"`
 	ProxyListenPort int     `json:"proxy_listen_port" yaml:"proxy_listen_port"`
 }
 }

+ 2 - 3
models/migrate.go

@@ -2,7 +2,6 @@ package models
 
 
 // MigrationData struct needed to create new v0.18.0 node from v.0.17.X node
 // MigrationData struct needed to create new v0.18.0 node from v.0.17.X node
 type MigrationData struct {
 type MigrationData struct {
-	JoinData     JoinData
-	LegacyNodeID string
-	Password     string
+	NewHost     Host
+	LegacyNodes []LegacyNode
 }
 }

+ 13 - 11
models/mqtt.go

@@ -8,17 +8,18 @@ import (
 
 
 // HostPeerUpdate - struct for host peer updates
 // HostPeerUpdate - struct for host peer updates
 type HostPeerUpdate struct {
 type HostPeerUpdate struct {
-	Host          Host                 `json:"host" bson:"host" yaml:"host"`
-	Server        string               `json:"server" bson:"server" yaml:"server"`
-	ServerVersion string               `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
-	ServerAddrs   []ServerAddr         `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
-	NodePeers     []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
-	Peers         []wgtypes.PeerConfig
-	HostPeerIDs   HostPeerMap           `json:"hostpeerids" bson:"hostpeerids" yaml:"hostpeerids"`
-	ProxyUpdate   ProxyManagerPayload   `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"`
-	EgressInfo    map[string]EgressInfo `json:"egress_info" bson:"egress_info" yaml:"egress_info"` // map key is node ID
-	IngressInfo   IngressInfo           `json:"ingress_info" bson:"ext_peers" yaml:"ext_peers"`
-	PeerIDs       PeerMap               `json:"peerids" bson:"peerids" yaml:"peerids"`
+	Host            Host                 `json:"host" bson:"host" yaml:"host"`
+	Server          string               `json:"server" bson:"server" yaml:"server"`
+	ServerVersion   string               `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
+	ServerAddrs     []ServerAddr         `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
+	NodePeers       []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
+	Peers           []wgtypes.PeerConfig
+	HostPeerIDs     HostPeerMap           `json:"hostpeerids" bson:"hostpeerids" yaml:"hostpeerids"`
+	ProxyUpdate     ProxyManagerPayload   `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"`
+	EgressInfo      map[string]EgressInfo `json:"egress_info" bson:"egress_info" yaml:"egress_info"` // map key is node ID
+	IngressInfo     IngressInfo           `json:"ingress_info" bson:"ext_peers" yaml:"ext_peers"`
+	PeerIDs         PeerMap               `json:"peerids" bson:"peerids" yaml:"peerids"`
+	HostNetworkInfo HostInfoMap           `json:"host_network_info,omitempty" bson:"host_network_info,omitempty" yaml:"host_network_info,omitempty"`
 }
 }
 
 
 // IngressInfo - struct for ingress info
 // IngressInfo - struct for ingress info
@@ -41,6 +42,7 @@ type PeerRouteInfo struct {
 	PeerAddr net.IPNet `json:"peer_addr" yaml:"peer_addr"`
 	PeerAddr net.IPNet `json:"peer_addr" yaml:"peer_addr"`
 	PeerKey  string    `json:"peer_key" yaml:"peer_key"`
 	PeerKey  string    `json:"peer_key" yaml:"peer_key"`
 	Allow    bool      `json:"allow" yaml:"allow"`
 	Allow    bool      `json:"allow" yaml:"allow"`
+	ID       string    `json:"id,omitempty" yaml:"id,omitempty"`
 }
 }
 
 
 // ExtClientInfo - struct for ext. client and it's peers
 // ExtClientInfo - struct for ext. client and it's peers

+ 1 - 0
models/proxy.go

@@ -37,6 +37,7 @@ type RelayedConf struct {
 type PeerConf struct {
 type PeerConf struct {
 	Proxy            bool         `json:"proxy"`
 	Proxy            bool         `json:"proxy"`
 	PublicListenPort int32        `json:"public_listen_port"`
 	PublicListenPort int32        `json:"public_listen_port"`
+	ProxyListenPort  int          `json:"proxy_listen_port"`
 	IsExtClient      bool         `json:"is_ext_client"`
 	IsExtClient      bool         `json:"is_ext_client"`
 	Address          net.IP       `json:"address"`
 	Address          net.IP       `json:"address"`
 	ExtInternalIp    net.IP       `json:"ext_internal_ip"`
 	ExtInternalIp    net.IP       `json:"ext_internal_ip"`

+ 44 - 2
mq/handlers.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"context"
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
+	"math"
 	"time"
 	"time"
 
 
 	mqtt "github.com/eclipse/paho.mqtt.golang"
 	mqtt "github.com/eclipse/paho.mqtt.golang"
@@ -185,10 +186,14 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 				logger.Log(0, "failed to send new node to host", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
 				logger.Log(0, "failed to send new node to host", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
 				return
 				return
 			} else {
 			} else {
-				if err = PublishSingleHostPeerUpdate(context.Background(), currentHost, nil); err != nil {
+				if err = PublishSingleHostPeerUpdate(context.Background(), currentHost, nil, nil); err != nil {
 					logger.Log(0, "failed peers publish after join acknowledged", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
 					logger.Log(0, "failed peers publish after join acknowledged", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
 					return
 					return
 				}
 				}
+				if err = handleNewNodeDNS(&hu.Host, &hu.Node); err != nil {
+					logger.Log(0, "failed to send dns update after node,", hu.Node.ID.String(), ", added to host", hu.Host.Name, err.Error())
+					return
+				}
 			}
 			}
 		}
 		}
 	case models.UpdateHost:
 	case models.UpdateHost:
@@ -279,7 +284,7 @@ func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
 			logger.Log(2, "updating peers after node", currentNode.ID.String(), currentNode.Network, "detected connectivity issues")
 			logger.Log(2, "updating peers after node", currentNode.ID.String(), currentNode.Network, "detected connectivity issues")
 			host, err := logic.GetHost(currentNode.HostID.String())
 			host, err := logic.GetHost(currentNode.HostID.String())
 			if err == nil {
 			if err == nil {
-				if err = PublishSingleHostPeerUpdate(context.Background(), host, nil); err != nil {
+				if err = PublishSingleHostPeerUpdate(context.Background(), host, nil, nil); err != nil {
 					logger.Log(0, "failed to publish update after failover peer change for node", currentNode.ID.String(), currentNode.Network)
 					logger.Log(0, "failed to publish update after failover peer change for node", currentNode.ID.String(), currentNode.Network)
 				}
 				}
 			}
 			}
@@ -362,6 +367,21 @@ func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) boo
 		oldMetric := oldMetrics.Connectivity[k]
 		oldMetric := oldMetrics.Connectivity[k]
 		currMetric.TotalTime += oldMetric.TotalTime
 		currMetric.TotalTime += oldMetric.TotalTime
 		currMetric.Uptime += oldMetric.Uptime // get the total uptime for this connection
 		currMetric.Uptime += oldMetric.Uptime // get the total uptime for this connection
+		if currMetric.CollectedByProxy {
+			currMetric.TotalReceived += oldMetric.TotalReceived
+			currMetric.TotalSent += oldMetric.TotalSent
+		} else {
+			if currMetric.TotalReceived < oldMetric.TotalReceived {
+				currMetric.TotalReceived += oldMetric.TotalReceived
+			} else {
+				currMetric.TotalReceived += int64(math.Abs(float64(currMetric.TotalReceived) - float64(oldMetric.TotalReceived)))
+			}
+			if currMetric.TotalSent < oldMetric.TotalSent {
+				currMetric.TotalSent += oldMetric.TotalSent
+			} else {
+				currMetric.TotalSent += int64(math.Abs(float64(currMetric.TotalSent) - float64(oldMetric.TotalSent)))
+			}
+		}
 		if currMetric.Uptime == 0 || currMetric.TotalTime == 0 {
 		if currMetric.Uptime == 0 || currMetric.TotalTime == 0 {
 			currMetric.PercentUp = 0
 			currMetric.PercentUp = 0
 		} else {
 		} else {
@@ -405,3 +425,25 @@ func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) boo
 	}
 	}
 	return shouldUpdate
 	return shouldUpdate
 }
 }
+
+func handleNewNodeDNS(host *models.Host, node *models.Node) error {
+	dns := models.DNSUpdate{
+		Action: models.DNSInsert,
+		Name:   host.Name + "." + node.Network,
+	}
+	if node.Address.IP != nil {
+		dns.Address = node.Address.IP.String()
+		if err := PublishDNSUpdate(node.Network, dns); err != nil {
+			return err
+		}
+	} else if node.Address6.IP != nil {
+		dns.Address = node.Address6.IP.String()
+		if err := PublishDNSUpdate(node.Network, dns); err != nil {
+			return err
+		}
+	}
+	if err := PublishAllDNS(node); err != nil {
+		return err
+	}
+	return nil
+}

+ 27 - 5
mq/publishers.go

@@ -27,7 +27,7 @@ func PublishPeerUpdate() error {
 	logic.ResetPeerUpdateContext()
 	logic.ResetPeerUpdateContext()
 	for _, host := range hosts {
 	for _, host := range hosts {
 		host := host
 		host := host
-		if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil); err != nil {
+		if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil, nil); err != nil {
 			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
 			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
 		}
 		}
 	}
 	}
@@ -49,7 +49,29 @@ func PublishDeletedNodePeerUpdate(delNode *models.Node) error {
 	logic.ResetPeerUpdateContext()
 	logic.ResetPeerUpdateContext()
 	for _, host := range hosts {
 	for _, host := range hosts {
 		host := host
 		host := host
-		if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, delNode); err != nil {
+		if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, delNode, nil); err != nil {
+			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
+		}
+	}
+	return err
+}
+
+// PublishDeletedClientPeerUpdate --- determines and publishes a peer update
+// to all the hosts with a deleted ext client to account for
+func PublishDeletedClientPeerUpdate(delClient *models.ExtClient) error {
+	if !servercfg.IsMessageQueueBackend() {
+		return nil
+	}
+
+	hosts, err := logic.GetAllHosts()
+	if err != nil {
+		logger.Log(1, "err getting all hosts", err.Error())
+		return err
+	}
+	logic.ResetPeerUpdateContext()
+	for _, host := range hosts {
+		host := host
+		if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil, delClient); err != nil {
 			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
 			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
 		}
 		}
 	}
 	}
@@ -57,9 +79,9 @@ func PublishDeletedNodePeerUpdate(delNode *models.Node) error {
 }
 }
 
 
 // PublishSingleHostPeerUpdate --- determines and publishes a peer update to one host
 // PublishSingleHostPeerUpdate --- determines and publishes a peer update to one host
-func PublishSingleHostPeerUpdate(ctx context.Context, host *models.Host, deletedNode *models.Node) error {
+func PublishSingleHostPeerUpdate(ctx context.Context, host *models.Host, deletedNode *models.Node, deletedClient *models.ExtClient) error {
 
 
-	peerUpdate, err := logic.GetPeerUpdateForHost(ctx, "", host, deletedNode)
+	peerUpdate, err := logic.GetPeerUpdateForHost(ctx, "", host, deletedNode, deletedClient)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
@@ -429,7 +451,7 @@ func sendPeers() {
 		for _, host := range hosts {
 		for _, host := range hosts {
 			host := host
 			host := host
 			logger.Log(2, "sending scheduled peer update (5 min)")
 			logger.Log(2, "sending scheduled peer update (5 min)")
-			if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil); err != nil {
+			if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil, nil); err != nil {
 				logger.Log(1, "error publishing peer updates for host: ", host.ID.String(), " Err: ", err.Error())
 				logger.Log(1, "error publishing peer updates for host: ", host.ID.String(), " Err: ", err.Error())
 			}
 			}
 		}
 		}

+ 8 - 0
mq/util.go

@@ -12,6 +12,10 @@ import (
 )
 )
 
 
 func decryptMsgWithHost(host *models.Host, msg []byte) ([]byte, error) {
 func decryptMsgWithHost(host *models.Host, msg []byte) ([]byte, error) {
+	if host.OS == models.OS_Types.IoT { // just pass along IoT messages
+		return msg, nil
+	}
+
 	trafficKey, trafficErr := logic.RetrievePrivateTrafficKey() // get server private key
 	trafficKey, trafficErr := logic.RetrievePrivateTrafficKey() // get server private key
 	if trafficErr != nil {
 	if trafficErr != nil {
 		return nil, trafficErr
 		return nil, trafficErr
@@ -41,6 +45,10 @@ func decryptMsg(node *models.Node, msg []byte) ([]byte, error) {
 }
 }
 
 
 func encryptMsg(host *models.Host, msg []byte) ([]byte, error) {
 func encryptMsg(host *models.Host, msg []byte) ([]byte, error) {
+	if host.OS == models.OS_Types.IoT {
+		return msg, nil
+	}
+
 	// fetch server public key to be certain hasn't changed in transit
 	// fetch server public key to be certain hasn't changed in transit
 	trafficKey, trafficErr := logic.RetrievePrivateTrafficKey()
 	trafficKey, trafficErr := logic.RetrievePrivateTrafficKey()
 	if trafficErr != nil {
 	if trafficErr != nil {

+ 14 - 25
release.md

@@ -1,38 +1,27 @@
-# Netmaker v0.18.3
+# Netmaker v0.18.4
 
 
 ## **Wait till out of pre-release to fully upgrade**
 ## **Wait till out of pre-release to fully upgrade**
 
 
 ## whats new
 ## whats new
-- Forced node deletions, if a host doesn't not receive message to delete a node, you can forcefully remove it by deleting it twice from UI/CLI  
-  - Allows user to remove orpahned Nodes + Hosts easier
-- EMQX ACLs, if using EMQX as broker, ACLs per host will be created, enhancing security around messages
-- You can now create ext clients with your own public key, but this feature will not be represented on current UI (new UI on the horizon)
-- STUN is now represented as a list including your NM server + 2 we are hosting + 2 of googles (clients will only use 2) for better NAT detection
-  - you specify which STUN servers to use with STUN_LIST env variable
+- 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" 
     
     
 ## whats fixed
 ## whats fixed
-- More Peer calculation improvements
-- JSON output on list commands for `nmctl`
+- 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
 - Upgrade script
+- Latency issue with Node <-> Node Metrics
 - Ports set from server for Hosts on register/join are actually used
 - Ports set from server for Hosts on register/join are actually used
-- **CLients**
-  - More efficient Windows daemon handling
-  - Better peer route setting on clients
-  - Some commands involving the message queue on client have been fixed
-  - NFTables masquerading issue
-  - Some logging has been adjusted
-  - Migrations on Linux work for 0.17.x - 0.18.3
-  - EnrollmentKEys in an HA setup should function fine now
-  - Registration by enrollment key on client GUI
 
 
 ## known issues
 ## known issues
-- Network interface routes may be removed after sometime/unintended network update
 - Caddy does not handle netmaker exporter well for EE
 - Caddy does not handle netmaker exporter well for EE
-- Incorrect latency on metrics (EE)
-- Swagger docs not up to date
-- Lengthy delay when you create an ext client
-- issues connecting over IPv6 on Macs
+- Migration causes a listen port of 0 for upgraded hosts
+- Docker clients can not re-join after deletion
+- Innacurate Ext Client Metrics 
+- Issue with Mac + IPv6 addressing
 - Nodes on same local network may not always connect
 - Nodes on same local network may not always connect
-- Netclient GUI shows egress range(s) twice
-- DNS entries are not sent after registration with EnrollmentKeys
+- 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

+ 544 - 359
scripts/nm-quick.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 #!/bin/bash
 
 
-LATEST="v0.18.3"
+LATEST="v0.18.4"
 
 
 print_logo() {(
 print_logo() {(
 cat << "EOF"
 cat << "EOF"
@@ -30,100 +30,219 @@ unset INSTALL_TYPE
 unset BUILD_TYPE
 unset BUILD_TYPE
 unset BUILD_TAG
 unset BUILD_TAG
 unset IMAGE_TAG
 unset IMAGE_TAG
+unset AUTO_BUILD
 
 
-usage () {(
-    echo "usage: ./nm-quick.sh [-e] [-b buildtype] [-t tag]"
+# usage - displays usage instructions
+usage () {
+    echo "usage: ./nm-quick.sh [-e] [-b buildtype] [-t tag] [-a auto]"
     echo "  -e      if specified, will install netmaker EE"
     echo "  -e      if specified, will install netmaker EE"
     echo "  -b      type of build; options:"
     echo "  -b      type of build; options:"
 	echo "          \"version\" - will install a specific version of Netmaker using remote git and dockerhub"
 	echo "          \"version\" - will install a specific version of Netmaker using remote git and dockerhub"
 	echo "          \"local\": - will install by cloning repo and and building images from git"
 	echo "          \"local\": - will install by cloning repo and and building images from git"
-	echo "          \"branch\": - will install a specific branch using remote git and dockerhub "
+	echo "          \"branch\": - will install a specific branch using remote git and dockerhub"
     echo "  -t      tag of build; if buildtype=version, tag=version. If builtype=branch or builtype=local, tag=branch"
     echo "  -t      tag of build; if buildtype=version, tag=version. If builtype=branch or builtype=local, tag=branch"
+    echo "  -a      auto-build; skip prompts and use defaults, if none provided"
     echo "examples:"
     echo "examples:"
-	echo "          nm-quick.sh -e -b version -t v0.18.3"
+	echo "          nm-quick.sh -e -b version -t v0.18.4"
 	echo "          nm-quick.sh -e -b local -t feature_v0.17.2_newfeature"	
 	echo "          nm-quick.sh -e -b local -t feature_v0.17.2_newfeature"	
 	echo "          nm-quick.sh -e -b branch -t develop"
 	echo "          nm-quick.sh -e -b branch -t develop"
     exit 1
     exit 1
-)}
+}
 
 
-while getopts evb:t: flag
+while getopts evab:t: flag
 do
 do
-    case "${flag}" in
-        e) 
+	case "${flag}" in
+		e) 
 			INSTALL_TYPE="ee"
 			INSTALL_TYPE="ee"
 			;;
 			;;
 		v) 
 		v) 
 			usage
 			usage
 			exit 0
 			exit 0
 			;;
 			;;
-        b) 
+		a) 
+			AUTO_BUILD="on"
+			;;			
+		b) 
 			BUILD_TYPE=${OPTARG}
 			BUILD_TYPE=${OPTARG}
 			if [[ ! "$BUILD_TYPE" =~ ^(version|local|branch)$ ]]; then
 			if [[ ! "$BUILD_TYPE" =~ ^(version|local|branch)$ ]]; then
-    			echo "error: $BUILD_TYPE is invalid"
+				echo "error: $BUILD_TYPE is invalid"
 				echo "valid options: version, local, branch"
 				echo "valid options: version, local, branch"
 				usage
 				usage
 				exit 1
 				exit 1
 			fi
 			fi
 			;;
 			;;
-        t) 
+		t) 
 			BUILD_TAG=${OPTARG}
 			BUILD_TAG=${OPTARG}
 			;;
 			;;
-    esac
+	esac
 done
 done
 
 
-if [ -z "$BUILD_TYPE" ]; then
-	BUILD_TYPE="version"
-	BUILD_TAG=$LATEST
-fi
+# print_logo - prints the netmaker logo
+print_logo() {
+cat << "EOF"
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+                                                                                         
+ __   __     ______     ______   __    __     ______     __  __     ______     ______    
+/\ "-.\ \   /\  ___\   /\__  _\ /\ "-./  \   /\  __ \   /\ \/ /    /\  ___\   /\  == \   
+\ \ \-.  \  \ \  __\   \/_/\ \/ \ \ \-./\ \  \ \  __ \  \ \  _"-.  \ \  __\   \ \  __<   
+ \ \_\\"\_\  \ \_____\    \ \_\  \ \_\ \ \_\  \ \_\ \_\  \ \_\ \_\  \ \_____\  \ \_\ \_\ 
+  \/_/ \/_/   \/_____/     \/_/   \/_/  \/_/   \/_/\/_/   \/_/\/_/   \/_____/   \/_/ /_/ 
+                                                                                                                                                                                                 
 
 
-if [ -z "$BUILD_TAG" ] && [ "$BUILD_TYPE" = "version" ]; then
-	BUILD_TAG=$LATEST
-fi
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+EOF
+}
 
 
-if [ -z "$BUILD_TAG" ] && [ ! -z "$BUILD_TYPE" ]; then
-	echo "error: must specify build tag when build type \"$BUILD_TYPE\" is specified"
-	usage		
-	exit 1
-fi
+# set_buildinfo - sets the information based on script input for how the installation should be run
+set_buildinfo() {
 
 
-IMAGE_TAG=$(sed 's/\//-/g' <<< "$BUILD_TAG")
+	if [ -z "$BUILD_TYPE" ]; then
+		BUILD_TYPE="version"
+		BUILD_TAG=$LATEST
+	fi
 
 
-if [ "$1" = "ce" ]; then
-	INSTALL_TYPE="ce"
-elif [ "$1" = "ee" ]; then
-	INSTALL_TYPE="ee"
-fi
+	if [ -z "$BUILD_TAG" ] && [ "$BUILD_TYPE" = "version" ]; then
+		BUILD_TAG=$LATEST
+	fi
 
 
-if [ -z "$INSTALL_TYPE" ]; then
-	echo "-----------------------------------------------------"
-	echo "Would you like to install Netmaker Community Edition (CE), or Netmaker Enterprise Edition (EE)?"
-	echo "EE will require you to create an account at https://dashboard.license.netmaker.io"
-	echo "-----------------------------------------------------"
-	select install_option in "Community Edition" "Enterprise Edition"; do
-	case $REPLY in
-		1)
-		echo "installing Netmaker CE"
+	if [ -z "$BUILD_TAG" ] && [ ! -z "$BUILD_TYPE" ]; then
+		echo "error: must specify build tag when build type \"$BUILD_TYPE\" is specified"
+		usage		
+		exit 1
+	fi
+
+	IMAGE_TAG=$(sed 's/\//-/g' <<< "$BUILD_TAG")
+
+	if [ "$1" = "ce" ]; then
 		INSTALL_TYPE="ce"
 		INSTALL_TYPE="ce"
-		break
-		;;      
-		2)
-		echo "installing Netmaker EE"
+	elif [ "$1" = "ee" ]; then
 		INSTALL_TYPE="ee"
 		INSTALL_TYPE="ee"
-		break
-		;;
-		*) echo "invalid option $REPLY";;
-	esac
-	done
-fi
-echo "-----------Build Options-----------------------------"
-echo "    EE or CE: $INSTALL_TYPE";
-echo "  Build Type: $BUILD_TYPE";
-echo "   Build Tag: $BUILD_TAG";
-echo "   Image Tag: $IMAGE_TAG";
-echo "-----------------------------------------------------"
+	fi
 
 
-print_logo
+	if [ "$AUTO_BUILD" = "on" ] && [ -z "$INSTALL_TYPE" ]; then
+		INSTALL_TYPE="ce"
+	elif [ -z "$INSTALL_TYPE" ]; then
+		echo "-----------------------------------------------------"
+		echo "Would you like to install Netmaker Community Edition (CE), or Netmaker Enterprise Edition (EE)?"
+		echo "EE will require you to create an account at https://dashboard.license.netmaker.io"
+		echo "-----------------------------------------------------"
+		select install_option in "Community Edition" "Enterprise Edition"; do
+		case $REPLY in
+			1)
+			echo "installing Netmaker CE"
+			INSTALL_TYPE="ce"
+			break
+			;;      
+			2)
+			echo "installing Netmaker EE"
+			INSTALL_TYPE="ee"
+			break
+			;;
+			*) echo "invalid option $REPLY";;
+		esac
+		done
+	fi
+	echo "-----------Build Options-----------------------------"
+	echo "    EE or CE: $INSTALL_TYPE";
+	echo "  Build Type: $BUILD_TYPE";
+	echo "   Build Tag: $BUILD_TAG";
+	echo "   Image Tag: $IMAGE_TAG";
+	echo "-----------------------------------------------------"
 
 
+}
+
+# install_yq - install yq if not present
+install_yq() {
+	if ! command -v yq &> /dev/null; then
+		wget -O /usr/bin/yq https://github.com/mikefarah/yq/releases/download/v4.31.1/yq_linux_$(dpkg --print-architecture)
+		chmod +x /usr/bin/yq
+	fi
+	set +e
+	if ! command -v yq &> /dev/null; then
+		set -e
+		wget -O /usr/bin/yq https://github.com/mikefarah/yq/releases/download/v4.31.1/yq_linux_amd64
+		chmod +x /usr/bin/yq
+	fi
+	set -e
+	if ! command -v yq &> /dev/null; then
+		echo "failed to install yq. Please install yq and try again."
+		echo "https://github.com/mikefarah/yq/#install"
+		exit 1
+	fi	
+}
+
+# setup_netclient - adds netclient to docker-compose
+setup_netclient() {
+
+
+	# yq ".services.netclient += {\"container_name\": \"netclient\"}" -i /root/docker-compose.yml
+	# yq ".services.netclient += {\"image\": \"gravitl/netclient:$IMAGE_TAG\"}" -i /root/docker-compose.yml
+	# yq ".services.netclient += {\"hostname\": \"netmaker-1\"}" -i /root/docker-compose.yml
+	# yq ".services.netclient += {\"network_mode\": \"host\"}" -i /root/docker-compose.yml
+	# yq ".services.netclient.depends_on += [\"netmaker\"]" -i /root/docker-compose.yml
+	# yq ".services.netclient += {\"restart\": \"always\"}" -i /root/docker-compose.yml
+	# yq ".services.netclient.environment += {\"TOKEN\": \"$TOKEN\"}" -i /root/docker-compose.yml
+	# yq ".services.netclient.volumes += [\"/etc/netclient:/etc/netclient\"]" -i /root/docker-compose.yml
+	# yq ".services.netclient.cap_add += [\"NET_ADMIN\"]" -i /root/docker-compose.yml
+	# yq ".services.netclient.cap_add += [\"NET_RAW\"]" -i /root/docker-compose.yml
+	# yq ".services.netclient.cap_add += [\"SYS_MODULE\"]" -i /root/docker-compose.yml
+
+	# docker-compose up -d
+
+	set +e
+	netclient uninstall
+	set -e
+
+	wget -O netclient https://github.com/gravitl/netclient/releases/download/$LATEST/netclient_linux_amd64
+	chmod +x netclient
+	./netclient install
+	netclient join -t $TOKEN
+
+	echo "waiting for client to become available"
+	wait_seconds 10 
+}
+
+# configure_netclient - configures server's netclient as a default host and an ingress gateway
+configure_netclient() {
+
+	NODE_ID=$(sudo cat /etc/netclient/nodes.yml | yq -r .netmaker.commonnode.id)
+	echo "join complete. New node ID: $NODE_ID"
+	HOST_ID=$(sudo cat /etc/netclient/netclient.yml | yq -r .host.id)
+	echo "For first join, making host a default"
+	echo "Host ID: $HOST_ID"
+	# set as a default host
+	set +e
+	nmctl host update $HOST_ID --default
+	sleep 5
+	nmctl node create_ingress netmaker $NODE_ID
+	set -e
+}
+
+# setup_nmctl - pulls nmctl and makes it executable
+setup_nmctl() {
+
+	# DEV_TEMP - Temporary instructions for testing
+	wget -O /usr/bin/nmctl https://fileserver.netmaker.org/testing/nmctl
+
+	# RELEASE_REPLACE - Use this once release is ready
+	# wget https://github.com/gravitl/netmaker/releases/download/v0.17.1/nmctl
+    chmod +x /usr/bin/nmctl
+    echo "using server api.$NETMAKER_BASE_DOMAIN"
+    echo "using master key $MASTER_KEY"
+    nmctl context set default --endpoint="https://api.$NETMAKER_BASE_DOMAIN" --master_key="$MASTER_KEY"
+    nmctl context use default
+    RESP=$(nmctl network list)
+    if [[ $RESP == *"unauthorized"* ]]; then
+        echo "Unable to properly configure NMCTL, exiting..."
+        exit 1
+    fi
+}
+
+# wait_seconds - wait for the specified period of time
 wait_seconds() {(
 wait_seconds() {(
   for ((a=1; a <= $1; a++))
   for ((a=1; a <= $1; a++))
   do
   do
@@ -132,7 +251,11 @@ wait_seconds() {(
   done
   done
 )}
 )}
 
 
+# confirm - get user input to confirm that they want to perform the next step
 confirm() {(
 confirm() {(
+  if [ "$AUTO_BUILD" = "on" ]; then
+	return 0
+  fi
   while true; do
   while true; do
       read -p 'Does everything look right? [y/n]: ' yn
       read -p 'Does everything look right? [y/n]: ' yn
       case $yn in
       case $yn in
@@ -143,6 +266,7 @@ confirm() {(
   done
   done
 )}
 )}
 
 
+# local_install_setup - builds artifacts based on specified branch locally to use in install
 local_install_setup() {(
 local_install_setup() {(
 	rm -rf netmaker-tmp
 	rm -rf netmaker-tmp
 	mkdir netmaker-tmp
 	mkdir netmaker-tmp
@@ -165,92 +289,82 @@ local_install_setup() {(
 	rm -rf netmaker-tmp
 	rm -rf netmaker-tmp
 )}
 )}
 
 
-echo "checking dependencies..."
-
-OS=$(uname)
-
-if [ -f /etc/debian_version ]; then
-	dependencies="git wireguard wireguard-tools jq docker.io docker-compose"
-	update_cmd='apt update'
-	install_cmd='apt-get install -y'
-elif [ -f /etc/alpine-release ]; then
-	dependencies="git wireguard jq docker.io docker-compose"
-	update_cmd='apk update'
-	install_cmd='apk --update add'
-elif [ -f /etc/centos-release ]; then
-	dependencies="git wireguard jq docker.io docker-compose"
-	update_cmd='yum update'
-	install_cmd='yum install -y'
-elif [ -f /etc/fedora-release ]; then
-	dependencies="git wireguard jq docker.io docker-compose"
-	update_cmd='dnf update'
-	install_cmd='dnf install -y'
-elif [ -f /etc/redhat-release ]; then
-	dependencies="git wireguard jq docker.io docker-compose"
-	update_cmd='yum update'
-	install_cmd='yum install -y'
-elif [ -f /etc/arch-release ]; then
-    	dependecies="git wireguard-tools jq docker.io docker-compose"
-	update_cmd='pacman -Sy'
-	install_cmd='pacman -S --noconfirm'
-elif [ "${OS}" = "FreeBSD" ]; then
-	dependencies="git wireguard wget jq docker.io docker-compose"
-	update_cmd='pkg update'
-	install_cmd='pkg install -y'
-elif [ -f /etc/turris-version ]; then
-	dependencies="git wireguard-tools bash jq docker.io docker-compose"
-	OS="TurrisOS"
-	update_cmd='opkg update'	
-	install_cmd='opkg install'
-elif [ -f /etc/openwrt_release ]; then
-	dependencies="git wireguard-tools bash jq docker.io docker-compose"
-	OS="OpenWRT"
-	update_cmd='opkg update'	
-	install_cmd='opkg install'
-else
-	install_cmd=''
-fi
+# install_dependencies - install necessary packages to run netmaker 
+install_dependencies() {
+	echo "checking dependencies..."
+
+	OS=$(uname)
+	if [ -f /etc/debian_version ]; then
+		dependencies="git wireguard wireguard-tools jq docker.io docker-compose"
+		update_cmd='apt update'
+		install_cmd='apt-get install -y'
+	elif [ -f /etc/alpine-release ]; then
+		dependencies="git wireguard jq docker.io docker-compose"
+		update_cmd='apk update'
+		install_cmd='apk --update add'
+	elif [ -f /etc/centos-release ]; then
+		dependencies="git wireguard jq docker.io docker-compose"
+		update_cmd='yum update'
+		install_cmd='yum install -y'
+	elif [ -f /etc/fedora-release ]; then
+		dependencies="git wireguard jq docker.io docker-compose"
+		update_cmd='dnf update'
+		install_cmd='dnf install -y'
+	elif [ -f /etc/redhat-release ]; then
+		dependencies="git wireguard jq docker.io docker-compose"
+		update_cmd='yum update'
+		install_cmd='yum install -y'
+	elif [ -f /etc/arch-release ]; then
+			dependecies="git wireguard-tools jq docker.io docker-compose"
+		update_cmd='pacman -Sy'
+		install_cmd='pacman -S --noconfirm'
+	elif [ "${OS}" = "FreeBSD" ]; then
+		dependencies="git wireguard wget jq docker.io docker-compose"
+		update_cmd='pkg update'
+		install_cmd='pkg install -y'
+	elif [ -f /etc/turris-version ]; then
+		dependencies="git wireguard-tools bash jq docker.io docker-compose"
+		OS="TurrisOS"
+		update_cmd='opkg update'	
+		install_cmd='opkg install'
+	elif [ -f /etc/openwrt_release ]; then
+		dependencies="git wireguard-tools bash jq docker.io docker-compose"
+		OS="OpenWRT"
+		update_cmd='opkg update'	
+		install_cmd='opkg install'
+	else
+		install_cmd=''
+	fi
 
 
-if [ -z "${install_cmd}" ]; then
-        echo "OS unsupported for automatic dependency install"
-	exit 1
-fi
+	if [ -z "${install_cmd}" ]; then
+			echo "OS unsupported for automatic dependency install"
+		exit 1
+	fi
 
 
-set -- $dependencies
+	set -- $dependencies
 
 
-${update_cmd}
+	${update_cmd}
 
 
-while [ -n "$1" ]; do
-	if [ "${OS}" = "FreeBSD" ]; then
-		is_installed=$(pkg check -d $1 | grep "Checking" | grep "done")
-		if [ "$is_installed" != "" ]; then
-			echo "  " $1 is installed
-		else
-			echo "  " $1 is not installed. Attempting install.
-			${install_cmd} $1
-			sleep 5
+	while [ -n "$1" ]; do
+		if [ "${OS}" = "FreeBSD" ]; then
 			is_installed=$(pkg check -d $1 | grep "Checking" | grep "done")
 			is_installed=$(pkg check -d $1 | grep "Checking" | grep "done")
 			if [ "$is_installed" != "" ]; then
 			if [ "$is_installed" != "" ]; then
 				echo "  " $1 is installed
 				echo "  " $1 is installed
-			elif [ -x "$(command -v $1)" ]; then
-				echo "  " $1 is installed
 			else
 			else
-				echo "  " FAILED TO INSTALL $1
-				echo "  " This may break functionality.
-			fi
-		fi	
-	else
-		if [ "${OS}" = "OpenWRT" ] || [ "${OS}" = "TurrisOS" ]; then
-			is_installed=$(opkg list-installed $1 | grep $1)
+				echo "  " $1 is not installed. Attempting install.
+				${install_cmd} $1
+				sleep 5
+				is_installed=$(pkg check -d $1 | grep "Checking" | grep "done")
+				if [ "$is_installed" != "" ]; then
+					echo "  " $1 is installed
+				elif [ -x "$(command -v $1)" ]; then
+					echo "  " $1 is installed
+				else
+					echo "  " FAILED TO INSTALL $1
+					echo "  " This may break functionality.
+				fi
+			fi	
 		else
 		else
-			is_installed=$(dpkg-query -W --showformat='${Status}\n' $1 | grep "install ok installed")
-		fi
-		if [ "${is_installed}" != "" ]; then
-			echo "    " $1 is installed
-		else
-			echo "    " $1 is not installed. Attempting install.
-			${install_cmd} $1
-			sleep 5
 			if [ "${OS}" = "OpenWRT" ] || [ "${OS}" = "TurrisOS" ]; then
 			if [ "${OS}" = "OpenWRT" ] || [ "${OS}" = "TurrisOS" ]; then
 				is_installed=$(opkg list-installed $1 | grep $1)
 				is_installed=$(opkg list-installed $1 | grep $1)
 			else
 			else
@@ -258,290 +372,361 @@ while [ -n "$1" ]; do
 			fi
 			fi
 			if [ "${is_installed}" != "" ]; then
 			if [ "${is_installed}" != "" ]; then
 				echo "    " $1 is installed
 				echo "    " $1 is installed
-			elif [ -x "$(command -v $1)" ]; then
-				echo "  " $1 is installed
 			else
 			else
-				echo "  " FAILED TO INSTALL $1
-				echo "  " This may break functionality.
+				echo "    " $1 is not installed. Attempting install.
+				${install_cmd} $1
+				sleep 5
+				if [ "${OS}" = "OpenWRT" ] || [ "${OS}" = "TurrisOS" ]; then
+					is_installed=$(opkg list-installed $1 | grep $1)
+				else
+					is_installed=$(dpkg-query -W --showformat='${Status}\n' $1 | grep "install ok installed")
+				fi
+				if [ "${is_installed}" != "" ]; then
+					echo "    " $1 is installed
+				elif [ -x "$(command -v $1)" ]; then
+					echo "  " $1 is installed
+				else
+					echo "  " FAILED TO INSTALL $1
+					echo "  " This may break functionality.
+				fi
 			fi
 			fi
 		fi
 		fi
+		shift
+	done
+
+	echo "-----------------------------------------------------"
+	echo "dependency check complete"
+	echo "-----------------------------------------------------"
+} 
+set -e
+
+# set_install_vars - sets the variables that will be used throughout installation
+set_install_vars() {
+
+	IP_ADDR=$(dig -4 myip.opendns.com @resolver1.opendns.com +short)
+	if [ "$IP_ADDR" = "" ]; then
+		IP_ADDR=$(curl -s ifconfig.me)
 	fi
 	fi
-	shift
-done
 
 
-echo "-----------------------------------------------------"
-echo "dependency check complete"
-echo "-----------------------------------------------------"
+	NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io
+	COREDNS_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p')
+	SERVER_PUBLIC_IP=$IP_ADDR
+	MASTER_KEY=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
+	DOMAIN_TYPE=""
+	echo "-----------------------------------------------------"
+	echo "Would you like to use your own domain for netmaker, or an auto-generated domain?"
+	echo "To use your own domain, add a Wildcard DNS record (e.x: *.netmaker.example.com) pointing to $SERVER_PUBLIC_IP"
+	echo "-----------------------------------------------------"
 
 
-wait_seconds 3
+	if [ "$AUTO_BUILD" = "on" ]; then
+			DOMAIN_TYPE="auto"
+	else
+		select domain_option in "Auto Generated ($NETMAKER_BASE_DOMAIN)" "Custom Domain (e.x: netmaker.example.com)"; do
+		case $REPLY in
+			1)
+			echo "using $NETMAKER_BASE_DOMAIN for base domain"
+			DOMAIN_TYPE="auto"
+			break
+			;;      
+			2)
+			read -p "Enter Custom Domain (make sure  *.domain points to $SERVER_PUBLIC_IP first): " domain
+			NETMAKER_BASE_DOMAIN=$domain
+			echo "using $NETMAKER_BASE_DOMAIN"
+			DOMAIN_TYPE="custom"
+			break
+			;;
+			*) echo "invalid option $REPLY";;
+		esac
+		done
+	fi
 
 
+	wait_seconds 2
 
 
-if [ "$BUILD_TYPE" = "local" ]; then
-	local_install_setup
-fi
+	echo "-----------------------------------------------------"
+	echo "The following subdomains will be used:"
+	echo "          dashboard.$NETMAKER_BASE_DOMAIN"
+	echo "                api.$NETMAKER_BASE_DOMAIN"
+	echo "             broker.$NETMAKER_BASE_DOMAIN"
 
 
-set -e
+	if [ "$INSTALL_TYPE" = "ee" ]; then
+		echo "         prometheus.$NETMAKER_BASE_DOMAIN"
+		echo "  netmaker-exporter.$NETMAKER_BASE_DOMAIN"
+		echo "            grafana.$NETMAKER_BASE_DOMAIN"
+	fi
 
 
-IP_ADDR=$(dig -4 myip.opendns.com @resolver1.opendns.com +short)
-if [ "$IP_ADDR" = "" ]; then
-	IP_ADDR=$(curl -s ifconfig.me)
-fi
+	echo "-----------------------------------------------------"
 
 
-NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io
-COREDNS_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p')
-SERVER_PUBLIC_IP=$IP_ADDR
-MASTER_KEY=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
-DOMAIN_TYPE=""
-echo "-----------------------------------------------------"
-echo "Would you like to use your own domain for netmaker, or an auto-generated domain?"
-echo "To use your own domain, add a Wildcard DNS record (e.x: *.netmaker.example.com) pointing to $SERVER_PUBLIC_IP"
-echo "-----------------------------------------------------"
-select domain_option in "Auto Generated ($NETMAKER_BASE_DOMAIN)" "Custom Domain (e.x: netmaker.example.com)"; do
-  case $REPLY in
-    1)
-      echo "using $NETMAKER_BASE_DOMAIN for base domain"
-      DOMAIN_TYPE="auto"
-	  break
-      ;;      
-    2)
-      read -p "Enter Custom Domain (make sure  *.domain points to $SERVER_PUBLIC_IP first): " domain
-      NETMAKER_BASE_DOMAIN=$domain
-      echo "using $NETMAKER_BASE_DOMAIN"
-      DOMAIN_TYPE="custom"
-      break
-      ;;
-    *) echo "invalid option $REPLY";;
-  esac
-done
+	if [[ "$DOMAIN_TYPE" == "custom" ]]; then
+		echo "before continuing, confirm DNS is configured correctly, with records pointing to $SERVER_PUBLIC_IP"
+		confirm
+	fi
 
 
-wait_seconds 2
+	wait_seconds 1
 
 
-echo "-----------------------------------------------------"
-echo "The following subdomains will be used:"
-echo "          dashboard.$NETMAKER_BASE_DOMAIN"
-echo "                api.$NETMAKER_BASE_DOMAIN"
-echo "             broker.$NETMAKER_BASE_DOMAIN"
+	if [ "$INSTALL_TYPE" = "ee" ]; then
 
 
-if [ "$INSTALL_TYPE" = "ee" ]; then
-	echo "         prometheus.$NETMAKER_BASE_DOMAIN"
-	echo "  netmaker-exporter.$NETMAKER_BASE_DOMAIN"
-	echo "            grafana.$NETMAKER_BASE_DOMAIN"
-fi
+		echo "-----------------------------------------------------"
+		echo "Provide Details for EE installation:"
+		echo "    1. Log into https://dashboard.license.netmaker.io"
+		echo "    2. Copy License Key Value: https://dashboard.license.netmaker.io/license-keys"
+		echo "    3. Retrieve Account ID: https://dashboard.license.netmaker.io/user"
+		echo "    4. note email address"
+		echo "-----------------------------------------------------"
+		unset LICENSE_KEY
+		while [ -z "$LICENSE_KEY" ]; do
+			read -p "License Key: " LICENSE_KEY
+		done
+		unset ACCOUNT_ID
+		while [ -z ${ACCOUNT_ID} ]; do
+			read -p "Account ID: " ACCOUNT_ID
+		done
+	fi
+
+	unset GET_EMAIL
+	unset RAND_EMAIL
+	RAND_EMAIL="$(echo $RANDOM | md5sum  | head -c 16)@email.com"
+	if [ -z $AUTO_BUILD ]; then
+		read -p "Email Address for Domain Registration (click 'enter' to use $RAND_EMAIL): " GET_EMAIL
+	fi
+	if [ -z "$GET_EMAIL" ]; then
+	echo "using rand email"
+	EMAIL="$RAND_EMAIL"
+	else
+	EMAIL="$GET_EMAIL"
+	fi
+
+	wait_seconds 1
+
+	unset GET_MQ_USERNAME
+	unset GET_MQ_PASSWORD
+	unset CONFIRM_MQ_PASSWORD
+	echo "Enter Credentials For MQ..."
+	if [ -z $AUTO_BUILD ]; then
+		read -p "MQ Username (click 'enter' to use 'netmaker'): " GET_MQ_USERNAME
+	fi
+	if [ -z "$GET_MQ_USERNAME" ]; then
+	echo "using default username for mq"
+	MQ_USERNAME="netmaker"
+	else
+	MQ_USERNAME="$GET_MQ_USERNAME"
+	fi
+
+	MQ_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
+
+	if [ -z $AUTO_BUILD ]; then  
+		select domain_option in "Auto Generated Password" "Input Your Own Password"; do
+			case $REPLY in
+			1)
+			echo "using random password for mq"
+			break
+			;;      
+			2)
+			while true
+			do
+				echo "Enter your Password For MQ: " 
+				read -s GET_MQ_PASSWORD
+				echo "Enter your password again to confirm: "
+				read -s CONFIRM_MQ_PASSWORD
+				if [ ${GET_MQ_PASSWORD} != ${CONFIRM_MQ_PASSWORD} ]; then
+					echo "wrong password entered, try again..."
+					continue
+				fi
+				MQ_PASSWORD="$GET_MQ_PASSWORD"
+				echo "MQ Password Saved Successfully!!"
+				break
+			done
+			break
+			;;
+			*) echo "invalid option $REPLY";;
+		esac
+		done
+	fi
 
 
-echo "-----------------------------------------------------"
+	wait_seconds 2
+
+	echo "-----------------------------------------------------------------"
+	echo "                SETUP ARGUMENTS"
+	echo "-----------------------------------------------------------------"
+	echo "        domain: $NETMAKER_BASE_DOMAIN"
+	echo "         email: $EMAIL"
+	echo "     public ip: $SERVER_PUBLIC_IP"
+	if [ "$INSTALL_TYPE" = "ee" ]; then
+		echo "       license: $LICENSE_KEY"
+		echo "    account id: $ACCOUNT_ID"
+	fi
+	echo "-----------------------------------------------------------------"
+	echo "Confirm Settings for Installation"
+	echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
 
 
-if [[ "$DOMAIN_TYPE" == "custom" ]]; then
-	echo "before continuing, confirm DNS is configured correctly, with records pointing to $SERVER_PUBLIC_IP"
 	confirm
 	confirm
-fi
 
 
-wait_seconds 1
+}
 
 
-if [ "$INSTALL_TYPE" = "ee" ]; then
+# install_netmaker - sets the config files and starts docker-compose
+install_netmaker() {
 
 
-	echo "-----------------------------------------------------"
-	echo "Provide Details for EE installation:"
-	echo "    1. Log into https://dashboard.license.netmaker.io"
-	echo "    2. Copy License Key Value: https://dashboard.license.netmaker.io/license-keys"
-	echo "    3. Retrieve Account ID: https://dashboard.license.netmaker.io/user"
-	echo "    4. note email address"
-	echo "-----------------------------------------------------"
-	unset LICENSE_KEY
-	while [ -z "$LICENSE_KEY" ]; do
-		read -p "License Key: " LICENSE_KEY
-	done
-	unset ACCOUNT_ID
-	while [ -z ${ACCOUNT_ID} ]; do
-		read -p "Account ID: " ACCOUNT_ID
-	done
+	echo "-----------------------------------------------------------------"
+	echo "Beginning installation..."
+	echo "-----------------------------------------------------------------"
 
 
-fi
+	wait_seconds 3
 
 
-unset GET_EMAIL
-unset RAND_EMAIL
-RAND_EMAIL="$(echo $RANDOM | md5sum  | head -c 16)@email.com"
-read -p "Email Address for Domain Registration (click 'enter' to use $RAND_EMAIL): " GET_EMAIL
-if [ -z "$GET_EMAIL" ]; then
-  echo "using rand email"
-  EMAIL="$RAND_EMAIL"
-else
-  EMAIL="$GET_EMAIL"
-fi
+	echo "Pulling config files..."
 
 
-wait_seconds 1
-
-unset GET_MQ_USERNAME
-unset GET_MQ_PASSWORD
-unset CONFIRM_MQ_PASSWORD
-echo "Enter Credentials For MQ..."
-read -p "MQ Username (click 'enter' to use 'netmaker'): " GET_MQ_USERNAME
-if [ -z "$GET_MQ_USERNAME" ]; then
-  echo "using default username for mq"
-  MQ_USERNAME="netmaker"
-else
-  MQ_USERNAME="$GET_MQ_USERNAME"
-fi
 
 
-select domain_option in "Auto Generated Password" "Input Your Own Password"; do
-	case $REPLY in
-	1)
-	echo "generating random password for mq"
-	MQ_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
-	break
-	;;      
-    2)
-	while true
-    do
-        echo "Enter your Password For MQ: " 
-        read -s GET_MQ_PASSWORD
-        echo "Enter your password again to confirm: "
-        read -s CONFIRM_MQ_PASSWORD
-        if [ ${GET_MQ_PASSWORD} != ${CONFIRM_MQ_PASSWORD} ]; then
-            echo "wrong password entered, try again..."
-            continue
-        fi
-		MQ_PASSWORD="$GET_MQ_PASSWORD"
-        echo "MQ Password Saved Successfully!!"
-        break
-    done
-      break
-      ;;
-    *) echo "invalid option $REPLY";;
-  esac
-done
+	COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/compose/docker-compose.yml" 
+	CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/Caddyfile"
+	if [ "$INSTALL_TYPE" = "ee" ]; then
+		COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/compose/docker-compose.ee.yml" 
+		CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/Caddyfile-EE"
+	fi
+	if [ ! "$BUILD_TYPE" = "local" ]; then
+		wget -O /root/docker-compose.yml $COMPOSE_URL && wget -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/mosquitto.conf && wget -O /root/Caddyfile $CADDY_URL
+		wget -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/wait.sh
+	fi
 
 
+	chmod +x /root/wait.sh
+	mkdir -p /etc/netmaker
 
 
-wait_seconds 2
+	echo "Setting docker-compose and Caddyfile..."
 
 
-echo "-----------------------------------------------------------------"
-echo "                SETUP ARGUMENTS"
-echo "-----------------------------------------------------------------"
-echo "        domain: $NETMAKER_BASE_DOMAIN"
-echo "         email: $EMAIL"
-echo "     public ip: $SERVER_PUBLIC_IP"
-if [ "$INSTALL_TYPE" = "ee" ]; then
-	echo "       license: $LICENSE_KEY"
-	echo "    account id: $ACCOUNT_ID"
-fi
-echo "-----------------------------------------------------------------"
-echo "Confirm Settings for Installation"
-echo "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"
+	sed -i "s/SERVER_PUBLIC_IP/$SERVER_PUBLIC_IP/g" /root/docker-compose.yml
+	sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/Caddyfile
+	sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/docker-compose.yml
+	sed -i "s/REPLACE_MASTER_KEY/$MASTER_KEY/g" /root/docker-compose.yml
+	sed -i "s/YOUR_EMAIL/$EMAIL/g" /root/Caddyfile
+	sed -i "s/REPLACE_MQ_PASSWORD/$MQ_PASSWORD/g" /root/docker-compose.yml
+	sed -i "s/REPLACE_MQ_USERNAME/$MQ_USERNAME/g" /root/docker-compose.yml 
+	if [ "$INSTALL_TYPE" = "ee" ]; then
+		sed -i "s~YOUR_LICENSE_KEY~$LICENSE_KEY~g" /root/docker-compose.yml
+		sed -i "s/YOUR_ACCOUNT_ID/$ACCOUNT_ID/g" /root/docker-compose.yml
+	fi
 
 
-confirm
+	if [ "$BUILD_TYPE" = "version" ] && [ "$INSTALL_TYPE" = "ee" ]; then
+		sed -i "s/REPLACE_SERVER_IMAGE_TAG/$IMAGE_TAG-ee/g" /root/docker-compose.yml
+	else
+		sed -i "s/REPLACE_SERVER_IMAGE_TAG/$IMAGE_TAG/g" /root/docker-compose.yml
+	fi
 
 
+	if [ "$BUILD_TYPE" = "local" ]; then
+		sed -i "s/REPLACE_UI_IMAGE_TAG/$LATEST/g" /root/docker-compose.yml
+	else
+		sed -i "s/REPLACE_UI_IMAGE_TAG/$IMAGE_TAG/g" /root/docker-compose.yml
+	fi
 
 
-echo "-----------------------------------------------------------------"
-echo "Beginning installation..."
-echo "-----------------------------------------------------------------"
+	echo "Starting containers..."
 
 
-wait_seconds 3
+	docker-compose -f /root/docker-compose.yml up -d
 
 
-echo "Pulling config files..."
+	wait_seconds 2
 
 
+}
 
 
-COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/compose/docker-compose.yml" 
-CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/Caddyfile"
-if [ "$INSTALL_TYPE" = "ee" ]; then
-	COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/compose/docker-compose.ee.yml" 
-	CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/Caddyfile-EE"
-fi
-if [ ! "$BUILD_TYPE" = "local" ]; then
-	wget -O /root/docker-compose.yml $COMPOSE_URL && wget -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/mosquitto.conf && wget -O /root/Caddyfile $CADDY_URL
-	wget -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/wait.sh
-fi
+# test_connection - tests to make sure Caddy has proper SSL certs
+test_connection() {
 
 
-chmod +x /root/wait.sh
-mkdir -p /etc/netmaker
-
-echo "Setting docker-compose and Caddyfile..."
-
-sed -i "s/SERVER_PUBLIC_IP/$SERVER_PUBLIC_IP/g" /root/docker-compose.yml
-sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/Caddyfile
-sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/docker-compose.yml
-sed -i "s/REPLACE_MASTER_KEY/$MASTER_KEY/g" /root/docker-compose.yml
-sed -i "s/YOUR_EMAIL/$EMAIL/g" /root/Caddyfile
-sed -i "s/REPLACE_MQ_PASSWORD/$MQ_PASSWORD/g" /root/docker-compose.yml
-sed -i "s/REPLACE_MQ_USERNAME/$MQ_USERNAME/g" /root/docker-compose.yml 
-if [ "$INSTALL_TYPE" = "ee" ]; then
-	sed -i "s~YOUR_LICENSE_KEY~$LICENSE_KEY~g" /root/docker-compose.yml
-	sed -i "s/YOUR_ACCOUNT_ID/$ACCOUNT_ID/g" /root/docker-compose.yml
-fi
+	echo "Testing Caddy setup (please be patient, this may take 1-2 minutes)"
+	for i in 1 2 3 4 5 6 7 8
+	do
+	curlresponse=$(curl -vIs https://api.${NETMAKER_BASE_DOMAIN} 2>&1)
 
 
-if [ "$BUILD_TYPE" = "version" ] && [ "$INSTALL_TYPE" = "ee" ]; then
-	sed -i "s/REPLACE_SERVER_IMAGE_TAG/$IMAGE_TAG-ee/g" /root/docker-compose.yml
-else
-	sed -i "s/REPLACE_SERVER_IMAGE_TAG/$IMAGE_TAG/g" /root/docker-compose.yml
-fi
+	if [[ "$i" == 8 ]]; then
+	echo "    Caddy is having an issue setting up certificates, please investigate (docker logs caddy)"
+	echo "    Exiting..."
+	exit 1
+	elif [[ "$curlresponse" == *"failed to verify the legitimacy of the server"* ]]; then
+	echo "    Certificates not yet configured, retrying..."
 
 
-if [ "$BUILD_TYPE" = "local" ]; then
-	sed -i "s/REPLACE_UI_IMAGE_TAG/$LATEST/g" /root/docker-compose.yml
-else
-	sed -i "s/REPLACE_UI_IMAGE_TAG/$IMAGE_TAG/g" /root/docker-compose.yml
-fi
+	elif [[ "$curlresponse" == *"left intact"* ]]; then
+	echo "    Certificates ok"
+	break
+	else
+	secs=$(($i*5+10))
+	echo "    Issue establishing connection...retrying in $secs seconds..."       
+	fi
+	sleep $secs
+	done
 
 
-echo "Starting containers..."
+}
 
 
-docker-compose -f /root/docker-compose.yml up -d
+# setup_mesh - sets up a default mesh network on the server
+setup_mesh() {
 
 
-sleep 2
+	wait_seconds 5
 
 
-test_connection() {
+	echo "Creating netmaker network (10.101.0.0/16)"
+
+	nmctl network create --name netmaker --ipv4_addr 10.101.0.0/16
+
+	wait_seconds 5
+
+	echo "Creating netmaker access key"
+
+	nmctl keys create test1 99999 --name netmaker-key
+	tokenJson=$(nmctl keys create netmaker 2)
+	TOKEN=$(jq -r '.accessstring' <<< ${tokenJson})
+
+	wait_seconds 3
 
 
-echo "Testing Caddy setup (please be patient, this may take 1-2 minutes)"
-for i in 1 2 3 4 5 6 7 8
-do
-curlresponse=$(curl -vIs https://api.${NETMAKER_BASE_DOMAIN} 2>&1)
-
-if [[ "$i" == 8 ]]; then
-  echo "    Caddy is having an issue setting up certificates, please investigate (docker logs caddy)"
-  echo "    Exiting..."
-  exit 1
-elif [[ "$curlresponse" == *"failed to verify the legitimacy of the server"* ]]; then
-  echo "    Certificates not yet configured, retrying..."
-
-elif [[ "$curlresponse" == *"left intact"* ]]; then
-  echo "    Certificates ok"
-  break
-else
-  secs=$(($i*5+10))
-  echo "    Issue establishing connection...retrying in $secs seconds..."       
-fi
-sleep $secs
-done
 }
 }
 
 
+# print_success - prints a success message upon completion
+print_success() {
+	echo "-----------------------------------------------------------------"
+	echo "-----------------------------------------------------------------"
+	echo "Netmaker setup is now complete. You are ready to begin using Netmaker."
+	echo "Visit dashboard.$NETMAKER_BASE_DOMAIN to log in"
+	echo "-----------------------------------------------------------------"
+	echo "-----------------------------------------------------------------"
+}
 
 
-setup_mesh() {( set -e
+# 1. print netmaker logo
+print_logo
 
 
-wait_seconds 15
+# 2. setup the build instructions
+set_buildinfo
 
 
-echo "Creating netmaker network (10.101.0.0/16)"
+set +e
 
 
-curl -s -o /dev/null -d '{"addressrange":"10.101.0.0/16","netid":"netmaker"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks
+# 3. install necessary packages
+install_dependencies
 
 
-wait_seconds 5
+# 4. install yq if necessary
+install_yq
 
 
-echo "Creating netmaker access key"
+# 5. if running a local build, clone git and build artifacts
+if [ "$BUILD_TYPE" = "local" ]; then
+	local_install_setup
+fi
 
 
-curlresponse=$(curl -s -d '{"uses":99999,"name":"netmaker-key"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks/netmaker/keys)
-ACCESS_TOKEN=$(jq -r '.accessstring' <<< ${curlresponse})
+set -e
 
 
-wait_seconds 3
+# 6. get user input for variables
+set_install_vars
 
 
-)}
+# 7. get and set config files, startup docker-compose
+install_netmaker
 
 
 set +e
 set +e
+
+# 8. make sure Caddy certs are working
 test_connection
 test_connection
 
 
-wait_seconds 3
+# 9. install the netmaker CLI
+setup_nmctl
 
 
+# 10. create a default mesh network for netmaker
 setup_mesh
 setup_mesh
 
 
-echo "-----------------------------------------------------------------"
-echo "-----------------------------------------------------------------"
-echo "Netmaker setup is now complete. You are ready to begin using Netmaker."
-echo "Visit dashboard.$NETMAKER_BASE_DOMAIN to log in"
-echo "-----------------------------------------------------------------"
-echo "-----------------------------------------------------------------"
+set -e
+
+# 11. add netclient to docker-compose and start it up
+setup_netclient
+
+# 12. make the netclient a default host and ingress gw
+configure_netclient
+
+# 13. print success message
+print_success
 
 
 # cp -f /etc/skel/.bashrc /root/.bashrc
 # cp -f /etc/skel/.bashrc /root/.bashrc

+ 28 - 22
scripts/nm-upgrade.sh

@@ -39,13 +39,8 @@ confirm() {
 # install_dependencies - install system dependencies necessary for script to run
 # install_dependencies - install system dependencies necessary for script to run
 install_dependencies() {
 install_dependencies() {
   OS=$(uname)
   OS=$(uname)
-  is_ubuntu=$(sudo cat /etc/lsb-release | grep "Ubuntu")
-  if [ "${is_ubuntu}" != "" ]; then
-    dependencies="yq jq wireguard jq docker.io docker-compose"
-    update_cmd='apt update'
-    install_cmd='snap install'
-  elif [ -f /etc/debian_version ]; then
-    dependencies="yq jq wireguard jq docker.io docker-compose"
+  if [ -f /etc/debian_version ]; then
+    dependencies="jq wireguard jq docker.io docker-compose"
     update_cmd='apt update'
     update_cmd='apt update'
     install_cmd='apt install -y'
     install_cmd='apt install -y'
   elif [ -f /etc/centos-release ]; then
   elif [ -f /etc/centos-release ]; then
@@ -105,20 +100,24 @@ install_dependencies() {
   echo "-----------------------------------------------------"
   echo "-----------------------------------------------------"
 }
 }
 
 
-# get_email- gets upgrader's email address 
-get_email() {
-
-  unset GET_EMAIL
-  unset RAND_EMAIL
-  RAND_EMAIL="$(echo $RANDOM | md5sum  | head -c 16)@email.com"
-  read -p "Email Address for Domain Registration (click 'enter' to use $RAND_EMAIL): " GET_EMAIL
-  if [ -z "$GET_EMAIL" ]; then
-    echo "using rand email"
-    EMAIL="$RAND_EMAIL"
-  else
-    EMAIL="$GET_EMAIL"
-  fi
-
+# install_yq - install yq if not present
+install_yq() {
+	if ! command -v yq &> /dev/null; then
+		wget -O /usr/bin/yq https://github.com/mikefarah/yq/releases/download/v4.31.1/yq_linux_$(dpkg --print-architecture)
+		chmod +x /usr/bin/yq
+	fi
+	set +e
+	if ! command -v yq &> /dev/null; then
+		set -e
+		wget -O /usr/bin/yq https://github.com/mikefarah/yq/releases/download/v4.31.1/yq_linux_amd64
+		chmod +x /usr/bin/yq
+	fi
+	set -e
+	if ! command -v yq &> /dev/null; then
+		echo "failed to install yq. Please install yq and try again."
+		echo "https://github.com/mikefarah/yq/#install"
+		exit 1
+	fi	
 }
 }
 
 
 # collect_server_settings - retrieve server settings from existing compose file
 # collect_server_settings - retrieve server settings from existing compose file
@@ -358,7 +357,7 @@ set_compose() {
   STUN_PORT=3478
   STUN_PORT=3478
 
 
   # RELEASE_REPLACE - Use this once release is ready
   # RELEASE_REPLACE - Use this once release is ready
-  #sed -i "s/v0.17.1/v0.18.3/g" /root/docker-compose.yml
+  #sed -i "s/v0.17.1/v0.18.4/g" /root/docker-compose.yml
   yq ".services.netmaker.environment.SERVER_NAME = \"$SERVER_NAME\"" -i /root/docker-compose.yml
   yq ".services.netmaker.environment.SERVER_NAME = \"$SERVER_NAME\"" -i /root/docker-compose.yml
   yq ".services.netmaker.environment += {\"BROKER_ENDPOINT\": \"wss://$BROKER_NAME\"}" -i /root/docker-compose.yml  
   yq ".services.netmaker.environment += {\"BROKER_ENDPOINT\": \"wss://$BROKER_NAME\"}" -i /root/docker-compose.yml  
   yq ".services.netmaker.environment += {\"SERVER_BROKER_ENDPOINT\": \"ws://mq:1883\"}" -i /root/docker-compose.yml  
   yq ".services.netmaker.environment += {\"SERVER_BROKER_ENDPOINT\": \"ws://mq:1883\"}" -i /root/docker-compose.yml  
@@ -602,9 +601,16 @@ if [ $(id -u) -ne 0 ]; then
    exit 1
    exit 1
 fi
 fi
 
 
+set +e
+
 echo "...installing dependencies for script"
 echo "...installing dependencies for script"
 install_dependencies
 install_dependencies
 
 
+echo "...installing yq if necessary"
+install_yq
+
+set -e
+
 echo "...confirming version is correct"
 echo "...confirming version is correct"
 check_version
 check_version
 
 

+ 27 - 0
servercfg/serverconf.go

@@ -79,6 +79,7 @@ func GetServerConfig() config.ServerConfig {
 	if Is_EE {
 	if Is_EE {
 		cfg.IsEE = "yes"
 		cfg.IsEE = "yes"
 	}
 	}
+	cfg.DefaultProxyMode = GetDefaultProxyMode()
 
 
 	return cfg
 	return cfg
 }
 }
@@ -636,6 +637,32 @@ func IsProxyEnabled() bool {
 	return enabled
 	return enabled
 }
 }
 
 
+// GetDefaultProxyMode - default proxy mode for a server
+func GetDefaultProxyMode() config.ProxyMode {
+	var (
+		mode config.ProxyMode
+		def  string
+	)
+	if os.Getenv("DEFAULT_PROXY_MODE") != "" {
+		def = os.Getenv("DEFAULT_PROXY_MODE")
+	} else if config.Config.Server.DefaultProxyMode.Set {
+		return config.Config.Server.DefaultProxyMode
+	}
+	switch strings.ToUpper(def) {
+	case "ON":
+		mode.Set = true
+		mode.Value = true
+	case "OFF":
+		mode.Set = true
+		mode.Value = false
+	// AUTO or any other value
+	default:
+		mode.Set = false
+	}
+	return mode
+
+}
+
 // parseStunList - turn string into slice of StunServers
 // parseStunList - turn string into slice of StunServers
 func parseStunList(stunString string) ([]models.StunServer, error) {
 func parseStunList(stunString string) ([]models.StunServer, error) {
 	var err error
 	var err error

+ 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.3
+    version: 0.18.4
 paths:
 paths:
     /api/dns:
     /api/dns:
         get:
         get: