Browse Source

resolve conflicts

afeiszli 2 years ago
parent
commit
532f061f2a
49 changed files with 1084 additions and 585 deletions
  1. 1 0
      .github/ISSUE_TEMPLATE/bug-report.yml
  2. 2 1
      Dockerfile
  3. 1 1
      README.md
  4. 20 14
      cli/cmd/acl/list.go
  5. 9 0
      cli/cmd/commons/globals.go
  6. 11 5
      cli/cmd/dns/list.go
  7. 47 0
      cli/cmd/enrollment_key/create.go
  8. 23 0
      cli/cmd/enrollment_key/delete.go
  9. 20 0
      cli/cmd/enrollment_key/list.go
  10. 28 0
      cli/cmd/enrollment_key/root.go
  11. 11 5
      cli/cmd/ext_client/list.go
  12. 13 7
      cli/cmd/network/list.go
  13. 19 13
      cli/cmd/node/list.go
  14. 4 0
      cli/cmd/root.go
  15. 12 5
      cli/cmd/user/list.go
  16. 22 0
      cli/functions/enrollment_keys.go
  17. 1 1
      compose/docker-compose-emqx.yml
  18. 1 1
      compose/docker-compose.ee.yml
  19. 2 2
      compose/docker-compose.yml
  20. 42 42
      config/config.go
  21. 1 0
      controllers/controller.go
  22. 1 1
      controllers/docs.go
  23. 14 11
      controllers/ext_client.go
  24. 35 0
      controllers/legacy.go
  25. 25 0
      controllers/migrate.go
  26. 19 9
      controllers/node.go
  27. 4 1
      controllers/regex.go
  28. 3 0
      database/database.go
  29. 4 1
      go.mod
  30. 10 3
      go.sum
  31. 1 1
      k8s/client/netclient-daemonset.yaml
  32. 1 1
      k8s/client/netclient.yaml
  33. 1 1
      k8s/server/netmaker-server.yaml
  34. 1 1
      k8s/server/netmaker-ui.yaml
  35. 3 3
      logic/extpeers.go
  36. 34 16
      logic/hostactions/hostactions.go
  37. 15 0
      logic/hosts.go
  38. 46 0
      logic/legacy.go
  39. 19 4
      logic/nodes.go
  40. 206 199
      logic/peers.go
  41. 1 1
      main.go
  42. 28 17
      models/structs.go
  43. 214 191
      mq/handlers.go
  44. 12 11
      mq/publishers.go
  45. 22 2
      release.md
  46. 2 2
      scripts/nm-quick.sh
  47. 1 1
      scripts/nm-upgrade.sh
  48. 71 10
      servercfg/serverconf.go
  49. 1 1
      swagger.yaml

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

@@ -31,6 +31,7 @@ body:
       label: Version
       description: What version are you running?
       options:
+        - v0.18.3
         - v0.18.2
         - v0.18.1
         - v0.18.0

+ 2 - 1
Dockerfile

@@ -4,10 +4,11 @@ ARG tags
 WORKDIR /app
 COPY . .
 
-RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -w" -tags ${tags} .
+RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -w " -tags ${tags} .
 # RUN go build -tags=ee . -o netmaker main.go
 FROM alpine:3.16.2
 
+# add a c lib
 # set the working directory
 WORKDIR /root/
 RUN mkdir -p /etc/netclient/config

+ 1 - 1
README.md

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

+ 20 - 14
cli/cmd/acl/list.go

@@ -3,6 +3,7 @@ package acl
 import (
 	"os"
 
+	"github.com/gravitl/netmaker/cli/cmd/commons"
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/gravitl/netmaker/logic/acls"
 	"github.com/guumaster/tablewriter"
@@ -16,23 +17,28 @@ var aclListCmd = &cobra.Command{
 	Long:  `List all ACLs associated with a network`,
 	Run: func(cmd *cobra.Command, args []string) {
 		aclSource := (map[acls.AclID]acls.ACL)(*functions.GetACL(args[0]))
-		table := tablewriter.NewWriter(os.Stdout)
-		table.SetHeader([]string{"From", "To", "Status"})
-		for id, acl := range aclSource {
-			for k, v := range (map[acls.AclID]byte)(acl) {
-				row := []string{string(id), string(k)}
-				switch v {
-				case acls.NotAllowed:
-					row = append(row, "Not Allowed")
-				case acls.NotPresent:
-					row = append(row, "Not Present")
-				case acls.Allowed:
-					row = append(row, "Allowed")
+		switch commons.OutputFormat {
+		case commons.JsonOutput:
+			functions.PrettyPrint(aclSource)
+		default:
+			table := tablewriter.NewWriter(os.Stdout)
+			table.SetHeader([]string{"From", "To", "Status"})
+			for id, acl := range aclSource {
+				for k, v := range (map[acls.AclID]byte)(acl) {
+					row := []string{string(id), string(k)}
+					switch v {
+					case acls.NotAllowed:
+						row = append(row, "Not Allowed")
+					case acls.NotPresent:
+						row = append(row, "Not Present")
+					case acls.Allowed:
+						row = append(row, "Allowed")
+					}
+					table.Append(row)
 				}
-				table.Append(row)
 			}
+			table.Render()
 		}
-		table.Render()
 	},
 }
 

+ 9 - 0
cli/cmd/commons/globals.go

@@ -0,0 +1,9 @@
+package commons
+
+// OutputFormat flag defines the output format to stdout (Enum:- json)
+var OutputFormat string
+
+const (
+	// JsonOutput refers to json format output to stdout
+	JsonOutput = "json"
+)

+ 11 - 5
cli/cmd/dns/list.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"os"
 
+	"github.com/gravitl/netmaker/cli/cmd/commons"
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/gravitl/netmaker/models"
 	"github.com/guumaster/tablewriter"
@@ -31,12 +32,17 @@ var dnsListCmd = &cobra.Command{
 		} else {
 			data = *functions.GetDNS()
 		}
-		table := tablewriter.NewWriter(os.Stdout)
-		table.SetHeader([]string{"Name", "Network", "IPv4 Address", "IPv6 Address"})
-		for _, d := range data {
-			table.Append([]string{d.Name, d.Network, d.Address, d.Address6})
+		switch commons.OutputFormat {
+		case commons.JsonOutput:
+			functions.PrettyPrint(data)
+		default:
+			table := tablewriter.NewWriter(os.Stdout)
+			table.SetHeader([]string{"Name", "Network", "IPv4 Address", "IPv6 Address"})
+			for _, d := range data {
+				table.Append([]string{d.Name, d.Network, d.Address, d.Address6})
+			}
+			table.Render()
 		}
-		table.Render()
 	},
 }
 

+ 47 - 0
cli/cmd/enrollment_key/create.go

@@ -0,0 +1,47 @@
+package enrollment_key
+
+import (
+	"strings"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/spf13/cobra"
+)
+
+var (
+	expiration    int
+	usesRemaining int
+	networks      string
+	unlimited     bool
+	tags          string
+)
+
+var enrollmentKeyCreateCmd = &cobra.Command{
+	Use:   "create",
+	Args:  cobra.NoArgs,
+	Short: "Create an enrollment key",
+	Long:  `Create an enrollment key`,
+	Run: func(cmd *cobra.Command, args []string) {
+		enrollKey := &models.APIEnrollmentKey{
+			Expiration:    int64(expiration),
+			UsesRemaining: usesRemaining,
+			Unlimited:     unlimited,
+		}
+		if networks != "" {
+			enrollKey.Networks = strings.Split(networks, ",")
+		}
+		if tags != "" {
+			enrollKey.Tags = strings.Split(tags, ",")
+		}
+		functions.PrettyPrint(functions.CreateEnrollmentKey(enrollKey))
+	},
+}
+
+func init() {
+	enrollmentKeyCreateCmd.Flags().IntVar(&expiration, "expiration", 0, "Expiration time of the key in UNIX timestamp format")
+	enrollmentKeyCreateCmd.Flags().IntVar(&usesRemaining, "uses", 0, "Number of times this key can be used")
+	enrollmentKeyCreateCmd.Flags().StringVar(&networks, "networks", "", "Comma-separated list of networks which the enrollment key can access")
+	enrollmentKeyCreateCmd.Flags().BoolVar(&unlimited, "unlimited", false, "Should the key have unlimited uses ?")
+	enrollmentKeyCreateCmd.Flags().StringVar(&tags, "tags", "", "Comma-separated list of any additional tags")
+	rootCmd.AddCommand(enrollmentKeyCreateCmd)
+}

+ 23 - 0
cli/cmd/enrollment_key/delete.go

@@ -0,0 +1,23 @@
+package enrollment_key
+
+import (
+	"fmt"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var enrollmentKeyDeleteCmd = &cobra.Command{
+	Use:   "delete keyID",
+	Args:  cobra.ExactArgs(1),
+	Short: "Delete an enrollment key",
+	Long:  `Delete an enrollment key`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.DeleteEnrollmentKey(args[0])
+		fmt.Println("Enrollment key ", args[0], " deleted")
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(enrollmentKeyDeleteCmd)
+}

+ 20 - 0
cli/cmd/enrollment_key/list.go

@@ -0,0 +1,20 @@
+package enrollment_key
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var enrollmentKeyListCmd = &cobra.Command{
+	Use:   "list",
+	Args:  cobra.NoArgs,
+	Short: "List enrollment keys",
+	Long:  `List enrollment keys`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.GetEnrollmentKeys())
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(enrollmentKeyListCmd)
+}

+ 28 - 0
cli/cmd/enrollment_key/root.go

@@ -0,0 +1,28 @@
+package enrollment_key
+
+import (
+	"os"
+
+	"github.com/spf13/cobra"
+)
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+	Use:   "enrollment_key",
+	Short: "Manage Enrollment Keys",
+	Long:  `Manage Enrollment Keys`,
+}
+
+// GetRoot returns the root subcommand
+func GetRoot() *cobra.Command {
+	return rootCmd
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+	err := rootCmd.Execute()
+	if err != nil {
+		os.Exit(1)
+	}
+}

+ 11 - 5
cli/cmd/ext_client/list.go

@@ -5,6 +5,7 @@ import (
 	"strconv"
 	"time"
 
+	"github.com/gravitl/netmaker/cli/cmd/commons"
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/gravitl/netmaker/models"
 	"github.com/guumaster/tablewriter"
@@ -25,12 +26,17 @@ var extClientListCmd = &cobra.Command{
 		} else {
 			data = *functions.GetAllExtClients()
 		}
-		table := tablewriter.NewWriter(os.Stdout)
-		table.SetHeader([]string{"Client ID", "Network", "IPv4 Address", "IPv6 Address", "Enabled", "Last Modified"})
-		for _, d := range data {
-			table.Append([]string{d.ClientID, d.Network, d.Address, d.Address6, strconv.FormatBool(d.Enabled), time.Unix(d.LastModified, 0).String()})
+		switch commons.OutputFormat {
+		case commons.JsonOutput:
+			functions.PrettyPrint(data)
+		default:
+			table := tablewriter.NewWriter(os.Stdout)
+			table.SetHeader([]string{"Client ID", "Network", "IPv4 Address", "IPv6 Address", "Enabled", "Last Modified"})
+			for _, d := range data {
+				table.Append([]string{d.ClientID, d.Network, d.Address, d.Address6, strconv.FormatBool(d.Enabled), time.Unix(d.LastModified, 0).String()})
+			}
+			table.Render()
 		}
-		table.Render()
 	},
 }
 

+ 13 - 7
cli/cmd/network/list.go

@@ -4,6 +4,7 @@ import (
 	"os"
 	"time"
 
+	"github.com/gravitl/netmaker/cli/cmd/commons"
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/olekukonko/tablewriter"
 	"github.com/spf13/cobra"
@@ -16,14 +17,19 @@ var networkListCmd = &cobra.Command{
 	Args:  cobra.NoArgs,
 	Run: func(cmd *cobra.Command, args []string) {
 		networks := functions.GetNetworks()
-		table := tablewriter.NewWriter(os.Stdout)
-		table.SetHeader([]string{"NetId", "Address Range (IPv4)", "Address Range (IPv6)", "Network Last Modified", "Nodes Last Modified"})
-		for _, n := range *networks {
-			networkLastModified := time.Unix(n.NetworkLastModified, 0).Format(time.RFC3339)
-			nodesLastModified := time.Unix(n.NodesLastModified, 0).Format(time.RFC3339)
-			table.Append([]string{n.NetID, n.AddressRange, n.AddressRange6, networkLastModified, nodesLastModified})
+		switch commons.OutputFormat {
+		case commons.JsonOutput:
+			functions.PrettyPrint(networks)
+		default:
+			table := tablewriter.NewWriter(os.Stdout)
+			table.SetHeader([]string{"NetId", "Address Range (IPv4)", "Address Range (IPv6)", "Network Last Modified", "Nodes Last Modified"})
+			for _, n := range *networks {
+				networkLastModified := time.Unix(n.NetworkLastModified, 0).Format(time.RFC3339)
+				nodesLastModified := time.Unix(n.NodesLastModified, 0).Format(time.RFC3339)
+				table.Append([]string{n.NetID, n.AddressRange, n.AddressRange6, networkLastModified, nodesLastModified})
+			}
+			table.Render()
 		}
-		table.Render()
 	},
 }
 

+ 19 - 13
cli/cmd/node/list.go

@@ -4,6 +4,7 @@ import (
 	"os"
 	"strconv"
 
+	"github.com/gravitl/netmaker/cli/cmd/commons"
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/gravitl/netmaker/models"
 	"github.com/guumaster/tablewriter"
@@ -23,23 +24,28 @@ var nodeListCmd = &cobra.Command{
 		} else {
 			data = *functions.GetNodes()
 		}
-		table := tablewriter.NewWriter(os.Stdout)
-		table.SetHeader([]string{"ID", "Addresses", "Network", "Egress", "Ingress", "Relay"})
-		for _, d := range data {
-			addresses := ""
-			if d.Address != "" {
-				addresses += d.Address
-			}
-			if d.Address6 != "" {
+		switch commons.OutputFormat {
+		case commons.JsonOutput:
+			functions.PrettyPrint(data)
+		default:
+			table := tablewriter.NewWriter(os.Stdout)
+			table.SetHeader([]string{"ID", "Addresses", "Network", "Egress", "Ingress", "Relay"})
+			for _, d := range data {
+				addresses := ""
 				if d.Address != "" {
-					addresses += ", "
+					addresses += d.Address
+				}
+				if d.Address6 != "" {
+					if d.Address != "" {
+						addresses += ", "
+					}
+					addresses += d.Address6
 				}
-				addresses += d.Address6
+				table.Append([]string{d.ID, addresses, d.Network,
+					strconv.FormatBool(d.IsEgressGateway), strconv.FormatBool(d.IsIngressGateway), strconv.FormatBool(d.IsRelay)})
 			}
-			table.Append([]string{d.ID, addresses, d.Network,
-				strconv.FormatBool(d.IsEgressGateway), strconv.FormatBool(d.IsIngressGateway), strconv.FormatBool(d.IsRelay)})
+			table.Render()
 		}
-		table.Render()
 	},
 }
 

+ 4 - 0
cli/cmd/root.go

@@ -4,8 +4,10 @@ import (
 	"os"
 
 	"github.com/gravitl/netmaker/cli/cmd/acl"
+	"github.com/gravitl/netmaker/cli/cmd/commons"
 	"github.com/gravitl/netmaker/cli/cmd/context"
 	"github.com/gravitl/netmaker/cli/cmd/dns"
+	"github.com/gravitl/netmaker/cli/cmd/enrollment_key"
 	"github.com/gravitl/netmaker/cli/cmd/ext_client"
 	"github.com/gravitl/netmaker/cli/cmd/host"
 	"github.com/gravitl/netmaker/cli/cmd/keys"
@@ -41,6 +43,7 @@ func Execute() {
 }
 
 func init() {
+	rootCmd.PersistentFlags().StringVarP(&commons.OutputFormat, "output", "o", "", "List output in specific format (Enum:- json)")
 	// Bind subcommands here
 	rootCmd.AddCommand(network.GetRoot())
 	rootCmd.AddCommand(context.GetRoot())
@@ -55,4 +58,5 @@ func init() {
 	rootCmd.AddCommand(metrics.GetRoot())
 	rootCmd.AddCommand(network_user.GetRoot())
 	rootCmd.AddCommand(host.GetRoot())
+	rootCmd.AddCommand(enrollment_key.GetRoot())
 }

+ 12 - 5
cli/cmd/user/list.go

@@ -5,6 +5,7 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/gravitl/netmaker/cli/cmd/commons"
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/guumaster/tablewriter"
 	"github.com/spf13/cobra"
@@ -16,12 +17,18 @@ var userListCmd = &cobra.Command{
 	Short: "List all users",
 	Long:  `List all users`,
 	Run: func(cmd *cobra.Command, args []string) {
-		table := tablewriter.NewWriter(os.Stdout)
-		table.SetHeader([]string{"Name", "Admin", "Networks", "Groups"})
-		for _, d := range *functions.ListUsers() {
-			table.Append([]string{d.UserName, strconv.FormatBool(d.IsAdmin), strings.Join(d.Networks, ", "), strings.Join(d.Groups, ", ")})
+		data := functions.ListUsers()
+		switch commons.OutputFormat {
+		case commons.JsonOutput:
+			functions.PrettyPrint(data)
+		default:
+			table := tablewriter.NewWriter(os.Stdout)
+			table.SetHeader([]string{"Name", "Admin", "Networks", "Groups"})
+			for _, d := range *data {
+				table.Append([]string{d.UserName, strconv.FormatBool(d.IsAdmin), strings.Join(d.Networks, ", "), strings.Join(d.Groups, ", ")})
+			}
+			table.Render()
 		}
-		table.Render()
 	},
 }
 

+ 22 - 0
cli/functions/enrollment_keys.go

@@ -0,0 +1,22 @@
+package functions
+
+import (
+	"net/http"
+
+	"github.com/gravitl/netmaker/models"
+)
+
+// CreateEnrollmentKey - create an enrollment key
+func CreateEnrollmentKey(key *models.APIEnrollmentKey) *models.EnrollmentKey {
+	return request[models.EnrollmentKey](http.MethodPost, "/api/v1/enrollment-keys", key)
+}
+
+// GetEnrollmentKeys - gets all enrollment keys
+func GetEnrollmentKeys() *[]models.EnrollmentKey {
+	return request[[]models.EnrollmentKey](http.MethodGet, "/api/v1/enrollment-keys", nil)
+}
+
+// DeleteEnrollmentKey - delete an enrollment key
+func DeleteEnrollmentKey(keyID string) {
+	request[any](http.MethodDelete, "/api/v1/enrollment-keys/"+keyID, nil)
+}

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

@@ -13,7 +13,7 @@ services:
       BROKER_TYPE: "emqx"
       EMQX_REST_ENDPOINT: "http://mq:18083"
       SERVER_NAME: "NETMAKER_BASE_DOMAIN"
-      STUN_DOMAIN: "stun.NETMAKER_BASE_DOMAIN"
+      STUN_LIST: "stun.NETMAKER_BASE_DOMAIN:3478,stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302"
       SERVER_HOST: "SERVER_PUBLIC_IP"
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
       COREDNS_ADDR: "SERVER_PUBLIC_IP"

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

@@ -13,7 +13,7 @@ services:
       BROKER_TYPE: "emqx"
       EMQX_REST_ENDPOINT: "http://mq:18083"
       SERVER_NAME: "NETMAKER_BASE_DOMAIN"
-      STUN_DOMAIN: "stun.NETMAKER_BASE_DOMAIN"
+      STUN_LIST: "stun.NETMAKER_BASE_DOMAIN:3478,stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302"
       SERVER_HOST: "SERVER_PUBLIC_IP"
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
       COREDNS_ADDR: "SERVER_PUBLIC_IP"

+ 2 - 2
compose/docker-compose.yml

@@ -11,7 +11,7 @@ services:
     environment:
       BROKER_ENDPOINT: "wss://broker.NETMAKER_BASE_DOMAIN"
       SERVER_NAME: "NETMAKER_BASE_DOMAIN"
-      STUN_DOMAIN: "stun.NETMAKER_BASE_DOMAIN"
+      STUN_LIST: "stun.NETMAKER_BASE_DOMAIN:3478,stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302"
       SERVER_HOST: "SERVER_PUBLIC_IP"
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
       COREDNS_ADDR: "SERVER_PUBLIC_IP"
@@ -24,10 +24,10 @@ services:
       DATABASE: "sqlite"
       NODE_ID: "netmaker-server-1"
       SERVER_BROKER_ENDPOINT: "ws://mq:1883"
-      STUN_PORT: "3478"      
       VERBOSITY: "1"
       MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
       MQ_USERNAME: "REPLACE_MQ_USERNAME"
+      STUN_PORT: "3478"
     ports:
       - "3478:3478/udp"
   netmaker-ui:

+ 42 - 42
config/config.go

@@ -32,48 +32,48 @@ type EnvironmentConfig struct {
 
 // ServerConfig - server conf 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"`
-	StunHost              string `yaml:"stun_host"`
-	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"`
 }
 
 // SQLConfig - Generic SQL Config

+ 1 - 0
controllers/controller.go

@@ -27,6 +27,7 @@ var HttpHandlers = []interface{}{
 	loggerHandlers,
 	hostHandlers,
 	enrollmentKeyHandlers,
+	legacyHandlers,
 }
 
 // HandleRESTRequests - handles the rest requests

+ 1 - 1
controllers/docs.go

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

+ 14 - 11
controllers/ext_client.go

@@ -17,6 +17,7 @@ import (
 	"github.com/gravitl/netmaker/models/promodels"
 	"github.com/gravitl/netmaker/mq"
 	"github.com/skip2/go-qrcode"
+	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
 func extClientHandlers(r *mux.Router) {
@@ -317,16 +318,22 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 	}
 
 	var extclient models.ExtClient
-	var CustomExtClient models.CustomExtClient
-
-	err := json.NewDecoder(r.Body).Decode(&CustomExtClient)
+	var customExtClient models.CustomExtClient
 
+	err := json.NewDecoder(r.Body).Decode(&customExtClient)
 	if err == nil {
-		if CustomExtClient.ClientID != "" && !validName(CustomExtClient.ClientID) {
+		if customExtClient.ClientID != "" && !validName(customExtClient.ClientID) {
 			logic.ReturnErrorResponse(w, r, logic.FormatError(errInvalidExtClientID, "badrequest"))
 			return
 		}
-		extclient.ClientID = CustomExtClient.ClientID
+		extclient.ClientID = customExtClient.ClientID
+		if len(customExtClient.PublicKey) > 0 {
+			if _, err := wgtypes.ParseKey(customExtClient.PublicKey); err != nil {
+				logic.ReturnErrorResponse(w, r, logic.FormatError(errInvalidExtClientPubKey, "badrequest"))
+				return
+			}
+			extclient.PublicKey = customExtClient.PublicKey
+		}
 	}
 
 	extclient.Network = networkName
@@ -350,16 +357,13 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 		listenPort = host.ProxyListenPort
 	}
 	extclient.IngressGatewayEndpoint = host.EndpointIP.String() + ":" + strconv.FormatInt(int64(listenPort), 10)
-
 	extclient.Enabled = true
 	parentNetwork, err := logic.GetNetwork(networkName)
 	if err == nil { // check if parent network default ACL is enabled (yes) or not (no)
 		extclient.Enabled = parentNetwork.DefaultACL == "yes"
 	}
-	// check pro settings
 
-	err = logic.CreateExtClient(&extclient)
-	if err != nil {
+	if err = logic.CreateExtClient(&extclient); err != nil {
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("failed to create new ext client on network [%s]: %v", networkName, err))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
@@ -389,8 +393,7 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 	logger.Log(0, r.Header.Get("user"), "created new ext client on network", networkName)
 	w.WriteHeader(http.StatusOK)
 	go func() {
-		err = mq.PublishPeerUpdate()
-		if err != nil {
+		if err := mq.PublishPeerUpdate(); err != nil {
 			logger.Log(1, "error setting ext peers on "+nodeid+": "+err.Error())
 		}
 		if err := mq.PublishExtCLientDNS(&extclient); err != nil {

+ 35 - 0
controllers/legacy.go

@@ -0,0 +1,35 @@
+package controller
+
+import (
+	"net/http"
+
+	"github.com/gorilla/mux"
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/logic"
+)
+
+func legacyHandlers(r *mux.Router) {
+	r.HandleFunc("/api/v1/legacy/nodes", logic.SecurityCheck(true, http.HandlerFunc(wipeLegacyNodes))).Methods(http.MethodDelete)
+}
+
+// swagger:route DELETE /api/v1/legacy/nodes nodes wipeLegacyNodes
+//
+// Delete all legacy nodes from DB.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: wipeLegacyNodesResponse
+func wipeLegacyNodes(w http.ResponseWriter, r *http.Request) {
+	// Set header
+	w.Header().Set("Content-Type", "application/json")
+	if err := logic.RemoveAllLegacyNodes(); err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		logger.Log(0, "error occurred when removing legacy nodes", err.Error())
+	}
+	logger.Log(0, r.Header.Get("user"), "wiped legacy nodes")
+	logic.ReturnSuccessResponse(w, r, "wiped all legacy nodes")
+}

+ 25 - 0
controllers/migrate.go

@@ -74,5 +74,30 @@ func migrate(w http.ResponseWriter, r *http.Request) {
 	}
 	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)
+	//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())
+		}
+	}
 }

+ 19 - 9
controllers/node.go

@@ -1,6 +1,7 @@
 package controller
 
 import (
+	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -433,7 +434,7 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
-	hostPeerUpdate, err := logic.GetPeerUpdateForHost(node.Network, host, nil)
+	hostPeerUpdate, err := logic.GetPeerUpdateForHost(context.Background(), node.Network, host, nil)
 	if err != nil && !database.IsEmptyRecord(err) {
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", host.ID.String(), err))
@@ -622,7 +623,7 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 	}
-	hostPeerUpdate, err := logic.GetPeerUpdateForHost(networkName, &data.Host, nil)
+	hostPeerUpdate, err := logic.GetPeerUpdateForHost(context.Background(), networkName, &data.Host, nil)
 	if err != nil && !database.IsEmptyRecord(err) {
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", data.Host.ID.String(), err))
@@ -637,7 +638,7 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 		Host:         data.Host,
 		Peers:        hostPeerUpdate.Peers,
 	}
-	logger.Log(1, r.Header.Get("user"), "created new node", data.Host.Name, "on network", networkName)
+	logger.Log(1, r.Header.Get("user"), "created new node", data.Host.Name, data.Node.ID.String(), "on network", networkName)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(response)
 
@@ -908,7 +909,7 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 		relayedUpdate = true
 	}
 	ifaceDelta := logic.IfaceDelta(&currentNode, newNode)
-
+	aclUpdate := currentNode.DefaultACL != newNode.DefaultACL
 	if ifaceDelta && servercfg.Is_EE {
 		if err = logic.EnterpriseResetAllPeersFailovers(currentNode.ID, currentNode.Network); err != nil {
 			logger.Log(0, "failed to reset failover lists during node update for node", currentNode.ID.String(), currentNode.Network)
@@ -941,13 +942,17 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 	logger.Log(1, r.Header.Get("user"), "updated node", currentNode.ID.String(), "on network", currentNode.Network)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(apiNode)
-
 	runUpdates(newNode, ifaceDelta)
-	go func() {
+	go func(aclUpdate bool, newNode *models.Node) {
+		if aclUpdate {
+			if err := mq.PublishPeerUpdate(); err != nil {
+				logger.Log(0, "error during node ACL update for node", newNode.ID.String())
+			}
+		}
 		if err := mq.PublishReplaceDNS(&currentNode, newNode, host); err != nil {
 			logger.Log(1, "failed to publish dns update", err.Error())
 		}
-	}()
+	}(aclUpdate, newNode)
 }
 
 // swagger:route DELETE /api/nodes/{network}/{nodeid} nodes deleteNode
@@ -971,8 +976,13 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 	fromNode := r.Header.Get("requestfrom") == "node"
 	node, err := logic.GetNodeByID(nodeid)
 	if err != nil {
-		logger.Log(0, "error retrieving node to delete", err.Error())
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		if logic.CheckAndRemoveLegacyNode(nodeid) {
+			logger.Log(0, "removed legacy node", nodeid)
+			logic.ReturnSuccessResponse(w, r, nodeid+" deleted.")
+		} else {
+			logger.Log(0, "error retrieving node to delete", err.Error())
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		}
 		return
 	}
 	if r.Header.Get("ismaster") != "yes" {

+ 4 - 1
controllers/regex.go

@@ -5,7 +5,10 @@ import (
 	"regexp"
 )
 
-var errInvalidExtClientID = errors.New("ext client ID must be alphanumderic and/or dashes")
+var (
+	errInvalidExtClientPubKey = errors.New("incorrect ext client public key")
+	errInvalidExtClientID     = errors.New("ext client ID must be alphanumderic and/or dashes")
+)
 
 // allow only dashes and alphaneumeric for ext client and node names
 func validName(name string) bool {

+ 3 - 0
database/database.go

@@ -59,6 +59,8 @@ const (
 	HOSTS_TABLE_NAME = "hosts"
 	// ENROLLMENT_KEYS_TABLE_NAME - table name for enrollmentkeys
 	ENROLLMENT_KEYS_TABLE_NAME = "enrollmentkeys"
+	// HOST_ACTIONS_TABLE_NAME - table name for enrollmentkeys
+	HOST_ACTIONS_TABLE_NAME = "hostactions"
 
 	// == ERROR CONSTS ==
 	// NO_RECORD - no singular result found
@@ -141,6 +143,7 @@ func createTables() {
 	createTable(CACHE_TABLE_NAME)
 	createTable(HOSTS_TABLE_NAME)
 	createTable(ENROLLMENT_KEYS_TABLE_NAME)
+	createTable(HOST_ACTIONS_TABLE_NAME)
 }
 
 func createTable(tableName string) error {

+ 4 - 1
go.mod

@@ -13,7 +13,7 @@ require (
 	github.com/mattn/go-sqlite3 v1.14.16
 	github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
-	github.com/stretchr/testify v1.8.1
+	github.com/stretchr/testify v1.8.2
 	github.com/txn2/txeh v1.3.0
 	golang.org/x/crypto v0.6.0
 	golang.org/x/net v0.6.0 // indirect
@@ -42,6 +42,7 @@ require (
 
 require (
 	github.com/guumaster/tablewriter v0.0.10
+	github.com/kr/pretty v0.3.0
 	github.com/matryer/is v1.4.0
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/spf13/cobra v1.6.1
@@ -51,7 +52,9 @@ require (
 	cloud.google.com/go/compute/metadata v0.2.1 // indirect
 	github.com/go-jose/go-jose/v3 v3.0.0 // indirect
 	github.com/inconshreveable/mousetrap v1.0.1 // indirect
+	github.com/kr/text 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
 )
 

+ 10 - 3
go.sum

@@ -17,6 +17,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -69,9 +70,11 @@ github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTx
 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.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
 github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
 github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
@@ -98,6 +101,7 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -107,7 +111,9 @@ 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.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 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/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/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -134,8 +140,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/txn2/txeh v1.3.0 h1:vnbv63htVMZCaQgLqVBxKvj2+HHHFUzNW7I183zjg3E=
 github.com/txn2/txeh v1.3.0/go.mod h1:O7M6gUTPeMF+vsa4c4Ipx3JDkOYrruB1Wry8QRsMcw8=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
@@ -229,6 +235,7 @@ 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 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

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

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

+ 1 - 1
k8s/client/netclient.yaml

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

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

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

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

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

+ 3 - 3
logic/extpeers.go

@@ -117,14 +117,15 @@ func GetExtClient(clientid string, network string) (models.ExtClient, error) {
 // CreateExtClient - creates an extclient
 func CreateExtClient(extclient *models.ExtClient) error {
 
-	if extclient.PrivateKey == "" {
+	if len(extclient.PublicKey) == 0 {
 		privateKey, err := wgtypes.GeneratePrivateKey()
 		if err != nil {
 			return err
 		}
-
 		extclient.PrivateKey = privateKey.String()
 		extclient.PublicKey = privateKey.PublicKey().String()
+	} else {
+		extclient.PrivateKey = "[ENTER PRIVATE KEY]"
 	}
 
 	parentNetwork, err := GetNetwork(extclient.Network)
@@ -156,7 +157,6 @@ func CreateExtClient(extclient *models.ExtClient) error {
 	}
 
 	extclient.LastModified = time.Now().Unix()
-
 	key, err := GetRecordKey(extclient.ClientID, extclient.Network)
 	if err != nil {
 		return err

+ 34 - 16
logic/hostactions/hostactions.go

@@ -1,37 +1,55 @@
 package hostactions
 
 import (
-	"sync"
+	"encoding/json"
 
+	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/models"
 )
 
-// nodeActionHandler - handles the storage of host action updates
-var nodeActionHandler sync.Map
-
 // AddAction - adds a host action to a host's list to be retrieved from broker update
 func AddAction(hu models.HostUpdate) {
-	currentRecords, ok := nodeActionHandler.Load(hu.Host.ID.String())
-	if !ok { // no list exists yet
-		nodeActionHandler.Store(hu.Host.ID.String(), []models.HostUpdate{hu})
-	} else { // list exists, append to it
-		currentList := currentRecords.([]models.HostUpdate)
-		currentList = append(currentList, hu)
-		nodeActionHandler.Store(hu.Host.ID.String(), currentList)
+	hostID := hu.Host.ID.String()
+	currentRecords, err := database.FetchRecord(database.HOST_ACTIONS_TABLE_NAME, hostID)
+	if err != nil {
+		if database.IsEmptyRecord(err) { // no list exists yet
+			newEntry, err := json.Marshal([]models.HostUpdate{hu})
+			if err != nil {
+				return
+			}
+			_ = database.Insert(hostID, string(newEntry), database.HOST_ACTIONS_TABLE_NAME)
+		}
+		return
+	}
+	var currentList []models.HostUpdate
+	if err := json.Unmarshal([]byte(currentRecords), &currentList); err != nil {
+		return
+	}
+	currentList = append(currentList, hu)
+	newData, err := json.Marshal(currentList)
+	if err != nil {
+		return
 	}
+	_ = database.Insert(hostID, string(newData), database.HOST_ACTIONS_TABLE_NAME)
 }
 
 // GetAction - gets an action if exists
-// TODO: may need to move to DB rather than sync map for HA
 func GetAction(id string) *models.HostUpdate {
-	currentRecords, ok := nodeActionHandler.Load(id)
-	if !ok {
+	currentRecords, err := database.FetchRecord(database.HOST_ACTIONS_TABLE_NAME, id)
+	if err != nil {
+		return nil
+	}
+	var currentList []models.HostUpdate
+	if err = json.Unmarshal([]byte(currentRecords), &currentList); err != nil {
 		return nil
 	}
-	currentList := currentRecords.([]models.HostUpdate)
 	if len(currentList) > 0 {
 		hu := currentList[0]
-		nodeActionHandler.Store(hu.Host.ID.String(), currentList[1:])
+		newData, err := json.Marshal(currentList[1:])
+		if err != nil {
+			newData, _ = json.Marshal([]models.HostUpdate{})
+		}
+		_ = database.Insert(id, string(newData), database.HOST_ACTIONS_TABLE_NAME)
 		return &hu
 	}
 	return nil

+ 15 - 0
logic/hosts.go

@@ -395,6 +395,21 @@ func HostExists(h *models.Host) bool {
 	return (err != nil && !database.IsEmptyRecord(err)) || (err == nil)
 }
 
+// GetHostByNodeID - returns a host if found to have a node's ID, else nil
+func GetHostByNodeID(id string) *models.Host {
+	hosts, err := GetAllHosts()
+	if err != nil {
+		return nil
+	}
+	for i := range hosts {
+		h := hosts[i]
+		if StringSliceContains(h.Nodes, id) {
+			return &h
+		}
+	}
+	return nil
+}
+
 func updatePort(p *int) {
 	*p++
 	if *p > maxPort {

+ 46 - 0
logic/legacy.go

@@ -0,0 +1,46 @@
+package logic
+
+import (
+	"encoding/json"
+
+	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/models"
+)
+
+// IsLegacyNode - checks if a node is legacy or not
+func IsLegacyNode(nodeID string) bool {
+	record, err := database.FetchRecord(database.NODES_TABLE_NAME, nodeID)
+	if err != nil {
+		return false
+	}
+	var currentNode models.Node
+	var legacyNode models.LegacyNode
+	currentNodeErr := json.Unmarshal([]byte(record), &currentNode)
+	legacyNodeErr := json.Unmarshal([]byte(record), &legacyNode)
+	return currentNodeErr != nil && legacyNodeErr == nil
+}
+
+// CheckAndRemoveLegacyNode - checks for legacy node and removes
+func CheckAndRemoveLegacyNode(nodeID string) bool {
+	if IsLegacyNode(nodeID) {
+		if err := database.DeleteRecord(database.NODES_TABLE_NAME, nodeID); err == nil {
+			return true
+		}
+	}
+	return false
+}
+
+// RemoveAllLegacyNodes - fetches all legacy nodes from DB and removes
+func RemoveAllLegacyNodes() error {
+	records, err := database.FetchRecords(database.NODES_TABLE_NAME)
+	if err != nil {
+		return err
+	}
+	for k := range records {
+		if CheckAndRemoveLegacyNode(k) {
+			logger.Log(0, "removed legacy node", k)
+		}
+	}
+	return nil
+}

+ 19 - 4
logic/nodes.go

@@ -32,17 +32,24 @@ const (
 
 // GetNetworkNodes - gets the nodes of a network
 func GetNetworkNodes(network string) ([]models.Node, error) {
-	var nodes []models.Node
 	allnodes, err := GetAllNodes()
 	if err != nil {
 		return []models.Node{}, err
 	}
-	for _, node := range allnodes {
+
+	return GetNetworkNodesMemory(allnodes, network), nil
+}
+
+// GetNetworkNodesMemory - gets all nodes belonging to a network from list in memory
+func GetNetworkNodesMemory(allNodes []models.Node, network string) []models.Node {
+	var nodes = []models.Node{}
+	for i := range allNodes {
+		node := allNodes[i]
 		if node.Network == network {
 			nodes = append(nodes, node)
 		}
 	}
-	return nodes, nil
+	return nodes
 }
 
 // UpdateNode - takes a node and updates another node with it's values
@@ -83,8 +90,9 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error {
 
 // DeleteNode - marks node for deletion (and adds to zombie list) if called by UI or deletes node if called by node
 func DeleteNode(node *models.Node, purge bool) error {
+	alreadyDeleted := node.PendingDelete || node.Action == models.NODE_DELETE
 	node.Action = models.NODE_DELETE
-	if !purge {
+	if !purge && !alreadyDeleted {
 		newnode := *node
 		newnode.PendingDelete = true
 		if err := UpdateNode(node, &newnode); err != nil {
@@ -93,8 +101,15 @@ func DeleteNode(node *models.Node, purge bool) error {
 		newZombie <- node.ID
 		return nil
 	}
+	if alreadyDeleted {
+		logger.Log(1, "forcibly deleting node", node.ID.String())
+	}
 	host, err := GetHost(node.HostID.String())
 	if err != nil {
+		logger.Log(1, "no host found for node", node.ID.String(), "deleting..")
+		if delErr := deleteNodeByID(node); delErr != nil {
+			logger.Log(0, "failed to delete node", node.ID.String(), delErr.Error())
+		}
 		return err
 	}
 	if err := DissasociateNodeFromHost(node, host); err != nil {

+ 206 - 199
logic/peers.go

@@ -1,9 +1,9 @@
 package logic
 
 import (
+	"context"
 	"errors"
 	"fmt"
-	"log"
 	"net"
 	"net/netip"
 
@@ -16,8 +16,15 @@ import (
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
+var (
+	// PeerUpdateCtx context to send to host peer updates
+	PeerUpdateCtx context.Context
+	// PeerUpdateStop - the cancel for PeerUpdateCtx
+	PeerUpdateStop context.CancelFunc
+)
+
 // GetProxyUpdateForHost - gets the proxy update for host
-func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error) {
+func GetProxyUpdateForHost(ctx context.Context, host *models.Host) (models.ProxyManagerPayload, error) {
 	proxyPayload := models.ProxyManagerPayload{
 		Action: models.ProxyUpdate,
 	}
@@ -40,7 +47,7 @@ func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error
 		relayPeersMap := make(map[string]models.RelayedConf)
 		for _, relayedHost := range relayedHosts {
 			relayedHost := relayedHost
-			payload, err := GetPeerUpdateForHost("", &relayedHost, nil)
+			payload, err := GetPeerUpdateForHost(ctx, "", &relayedHost, nil)
 			if err == nil {
 				relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, GetPeerListenPort(&relayedHost)))
 				if udpErr == nil {
@@ -116,11 +123,24 @@ func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error
 	return proxyPayload, nil
 }
 
+// ResetPeerUpdateContext - kills any current peer updates and resets the context
+func ResetPeerUpdateContext() {
+	if PeerUpdateCtx != nil && PeerUpdateStop != nil {
+		PeerUpdateStop() // tell any current peer updates to stop
+	}
+
+	PeerUpdateCtx, PeerUpdateStop = context.WithCancel(context.Background())
+}
+
 // GetPeerUpdateForHost - gets the consolidated peer update for the host from all networks
-func GetPeerUpdateForHost(network string, host *models.Host, deletedNode *models.Node) (models.HostPeerUpdate, error) {
+func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host, deletedNode *models.Node) (models.HostPeerUpdate, error) {
 	if host == nil {
 		return models.HostPeerUpdate{}, errors.New("host is nil")
 	}
+	allNodes, err := GetAllNodes()
+	if err != nil {
+		return models.HostPeerUpdate{}, err
+	}
 	// track which nodes are deleted
 	// after peer calculation, if peer not in list, add delete config of peer
 	hostPeerUpdate := models.HostPeerUpdate{
@@ -137,10 +157,7 @@ func GetPeerUpdateForHost(network string, host *models.Host, deletedNode *models
 		Peers:      []wgtypes.PeerConfig{},
 		NodePeers:  []wgtypes.PeerConfig{},
 	}
-	var deletedNodes = []models.Node{} // used to track deleted nodes
-	if deletedNode != nil {
-		deletedNodes = append(deletedNodes, *deletedNode)
-	}
+
 	logger.Log(1, "peer update for host", host.ID.String())
 	peerIndexMap := make(map[string]int)
 	for _, nodeID := range host.Nodes {
@@ -152,229 +169,219 @@ func GetPeerUpdateForHost(network string, host *models.Host, deletedNode *models
 		if !node.Connected || node.PendingDelete || node.Action == models.NODE_DELETE {
 			continue
 		}
-		currentPeers, err := GetNetworkNodes(node.Network)
-		if err != nil {
-			log.Println("no network nodes")
-			return models.HostPeerUpdate{}, err
-		}
+		currentPeers := GetNetworkNodesMemory(allNodes, node.Network)
 		var nodePeerMap map[string]models.PeerRouteInfo
 		if node.IsIngressGateway || node.IsEgressGateway {
 			nodePeerMap = make(map[string]models.PeerRouteInfo)
 		}
 		for _, peer := range currentPeers {
-			peer := peer
-			if peer.ID.String() == node.ID.String() {
-				logger.Log(2, "peer update, skipping self")
-				//skip yourself
-				continue
-			}
-			if peer.Action == models.NODE_DELETE || peer.PendingDelete {
-				deletedNodes = append(deletedNodes, peer) // track deleted node for peer update
-				continue
-			}
-			var peerConfig wgtypes.PeerConfig
-			peerHost, err := GetHost(peer.HostID.String())
-			if err != nil {
-				logger.Log(1, "no peer host", peer.HostID.String(), err.Error())
-				return models.HostPeerUpdate{}, err
-			}
-
-			if !peer.Connected {
-				logger.Log(2, "peer update, skipping unconnected node", peer.ID.String())
-				//skip unconnected nodes
-				continue
-			}
-			if !nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) {
-				logger.Log(2, "peer update, skipping node for acl")
-				//skip if not permitted by acl
-				continue
-			}
-			peerConfig.PublicKey = peerHost.PublicKey
-			peerConfig.PersistentKeepaliveInterval = &peer.PersistentKeepalive
-			peerConfig.ReplaceAllowedIPs = true
-			uselocal := false
-			if host.EndpointIP.String() == peerHost.EndpointIP.String() {
-				//peer is on same network
-				// set to localaddress
-				uselocal = true
-				if node.LocalAddress.IP == nil {
-					// use public endpint
-					uselocal = false
+			select {
+			case <-ctx.Done():
+				logger.Log(2, "cancelled peer update for host", host.Name, host.ID.String())
+				return models.HostPeerUpdate{}, fmt.Errorf("peer update cancelled")
+			default:
+				peer := peer
+				if peer.ID.String() == node.ID.String() {
+					logger.Log(2, "peer update, skipping self")
+					//skip yourself
+					continue
 				}
-				if node.LocalAddress.String() == peer.LocalAddress.String() {
-					uselocal = false
+				var peerConfig wgtypes.PeerConfig
+				peerHost, err := GetHost(peer.HostID.String())
+				if err != nil {
+					logger.Log(1, "no peer host", peer.HostID.String(), err.Error())
+					return models.HostPeerUpdate{}, err
 				}
-			}
-			peerConfig.Endpoint = &net.UDPAddr{
-				IP:   peerHost.EndpointIP,
-				Port: GetPeerListenPort(peerHost),
-			}
 
-			if uselocal {
-				peerConfig.Endpoint.IP = peer.LocalAddress.IP
-			}
-			allowedips := GetAllowedIPs(&node, &peer, nil)
-			if peer.IsIngressGateway {
-				for _, entry := range peer.IngressGatewayRange {
-					_, cidr, err := net.ParseCIDR(string(entry))
-					if err == nil {
-						allowedips = append(allowedips, *cidr)
+				peerConfig.PublicKey = peerHost.PublicKey
+				peerConfig.PersistentKeepaliveInterval = &peer.PersistentKeepalive
+				peerConfig.ReplaceAllowedIPs = true
+				uselocal := false
+				if host.EndpointIP.String() == peerHost.EndpointIP.String() {
+					//peer is on same network
+					// set to localaddress
+					uselocal = true
+					if node.LocalAddress.IP == nil {
+						// use public endpint
+						uselocal = false
+					}
+					if node.LocalAddress.String() == peer.LocalAddress.String() {
+						uselocal = false
 					}
 				}
-			}
-			if peer.IsEgressGateway {
-				allowedips = append(allowedips, getEgressIPs(&node, &peer)...)
-			}
-			peerConfig.AllowedIPs = allowedips
-			if node.IsIngressGateway || node.IsEgressGateway {
+				peerConfig.Endpoint = &net.UDPAddr{
+					IP:   peerHost.EndpointIP,
+					Port: GetPeerListenPort(peerHost),
+				}
+
+				if uselocal {
+					peerConfig.Endpoint.IP = peer.LocalAddress.IP
+				}
+				allowedips := GetAllowedIPs(&node, &peer, nil)
 				if peer.IsIngressGateway {
-					_, extPeerIDAndAddrs, err := getExtPeers(&peer)
-					if err == nil {
-						for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
-							extPeerIdAndAddr := extPeerIdAndAddr
-							nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
-								PeerAddr: net.IPNet{
-									IP:   net.ParseIP(extPeerIdAndAddr.Address),
-									Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
-								},
-								PeerKey: extPeerIdAndAddr.ID,
-								Allow:   true,
-							}
+					for _, entry := range peer.IngressGatewayRange {
+						_, cidr, err := net.ParseCIDR(string(entry))
+						if err == nil {
+							allowedips = append(allowedips, *cidr)
 						}
 					}
 				}
-				if node.IsIngressGateway && peer.IsEgressGateway {
-					hostPeerUpdate.IngressInfo.EgressRanges = append(hostPeerUpdate.IngressInfo.EgressRanges,
-						peer.EgressGatewayRanges...)
-				}
-				nodePeerMap[peerHost.PublicKey.String()] = models.PeerRouteInfo{
-					PeerAddr: net.IPNet{
-						IP:   net.ParseIP(peer.PrimaryAddress()),
-						Mask: getCIDRMaskFromAddr(peer.PrimaryAddress()),
-					},
-					PeerKey: peerHost.PublicKey.String(),
-					Allow:   true,
+				if peer.IsEgressGateway {
+					allowedips = append(allowedips, getEgressIPs(&node, &peer)...)
 				}
-			}
-
-			var nodePeer wgtypes.PeerConfig
-			if _, ok := hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()]; !ok {
-				hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()] = make(map[string]models.IDandAddr)
-				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
-				peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
-				hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
-					ID:      peer.ID.String(),
-					Address: peer.PrimaryAddress(),
-					Name:    peerHost.Name,
-					Network: peer.Network,
+				if peer.Action != models.NODE_DELETE &&
+					!peer.PendingDelete &&
+					peer.Connected &&
+					nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) &&
+					(deletedNode == nil || (deletedNode != nil && peer.ID.String() != deletedNode.ID.String())) {
+					peerConfig.AllowedIPs = allowedips // only append allowed IPs if valid connection
 				}
-				nodePeer = peerConfig
-			} else {
-				peerAllowedIPs := hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs
-				peerAllowedIPs = append(peerAllowedIPs, allowedips...)
-				hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs = peerAllowedIPs
-				hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
-					ID:      peer.ID.String(),
-					Address: peer.PrimaryAddress(),
-					Name:    peerHost.Name,
-					Network: peer.Network,
-				}
-				nodePeer = hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]]
-			}
 
-			if node.Network == network { // add to peers map for metrics
-				hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()] = models.IDandAddr{
-					ID:      peer.ID.String(),
-					Address: peer.PrimaryAddress(),
-					Name:    peerHost.Name,
-					Network: peer.Network,
-				}
-				hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, nodePeer)
-			}
-		}
-		var extPeers []wgtypes.PeerConfig
-		var extPeerIDAndAddrs []models.IDandAddr
-		if node.IsIngressGateway {
-			extPeers, extPeerIDAndAddrs, err = getExtPeers(&node)
-			if err == nil {
-				for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
-					extPeerIdAndAddr := extPeerIdAndAddr
-					nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
+				if node.IsIngressGateway || node.IsEgressGateway {
+					if peer.IsIngressGateway {
+						_, extPeerIDAndAddrs, err := getExtPeers(&peer)
+						if err == nil {
+							for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
+								extPeerIdAndAddr := extPeerIdAndAddr
+								nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
+									PeerAddr: net.IPNet{
+										IP:   net.ParseIP(extPeerIdAndAddr.Address),
+										Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
+									},
+									PeerKey: extPeerIdAndAddr.ID,
+									Allow:   true,
+								}
+							}
+						}
+					}
+					if node.IsIngressGateway && peer.IsEgressGateway {
+						hostPeerUpdate.IngressInfo.EgressRanges = append(hostPeerUpdate.IngressInfo.EgressRanges,
+							peer.EgressGatewayRanges...)
+					}
+					nodePeerMap[peerHost.PublicKey.String()] = models.PeerRouteInfo{
 						PeerAddr: net.IPNet{
-							IP:   net.ParseIP(extPeerIdAndAddr.Address),
-							Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
+							IP:   net.ParseIP(peer.PrimaryAddress()),
+							Mask: getCIDRMaskFromAddr(peer.PrimaryAddress()),
 						},
-						PeerKey: extPeerIdAndAddr.ID,
+						PeerKey: peerHost.PublicKey.String(),
 						Allow:   true,
 					}
 				}
-				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, extPeers...)
-				for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
-					extPeerIdAndAddr := extPeerIdAndAddr
-					hostPeerUpdate.HostPeerIDs[extPeerIdAndAddr.ID] = make(map[string]models.IDandAddr)
-					hostPeerUpdate.HostPeerIDs[extPeerIdAndAddr.ID][extPeerIdAndAddr.ID] = models.IDandAddr{
-						ID:      extPeerIdAndAddr.ID,
-						Address: extPeerIdAndAddr.Address,
-						Name:    extPeerIdAndAddr.Name,
-						Network: node.Network,
+
+				var nodePeer wgtypes.PeerConfig
+				if _, ok := hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()]; !ok {
+					hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()] = make(map[string]models.IDandAddr)
+					hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
+					peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
+					hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
+						ID:      peer.ID.String(),
+						Address: peer.PrimaryAddress(),
+						Name:    peerHost.Name,
+						Network: peer.Network,
 					}
-					hostPeerUpdate.IngressInfo.ExtPeers[extPeerIdAndAddr.ID] = models.ExtClientInfo{
-						Masquerade: true,
-						IngGwAddr: net.IPNet{
-							IP:   net.ParseIP(node.PrimaryAddress()),
-							Mask: getCIDRMaskFromAddr(node.PrimaryAddress()),
-						},
-						Network: node.PrimaryNetworkRange(),
-						ExtPeerAddr: net.IPNet{
-							IP:   net.ParseIP(extPeerIdAndAddr.Address),
-							Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
-						},
-						ExtPeerKey: extPeerIdAndAddr.ID,
-						Peers:      nodePeerMap,
+					nodePeer = peerConfig
+				} else {
+					peerAllowedIPs := hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs
+					peerAllowedIPs = append(peerAllowedIPs, allowedips...)
+					hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs = peerAllowedIPs
+					hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
+						ID:      peer.ID.String(),
+						Address: peer.PrimaryAddress(),
+						Name:    peerHost.Name,
+						Network: peer.Network,
 					}
-					if node.Network == network {
-						hostPeerUpdate.PeerIDs[extPeerIdAndAddr.ID] = extPeerIdAndAddr
-						hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, extPeers...)
+					nodePeer = hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]]
+				}
+
+				if node.Network == network { // add to peers map for metrics
+					hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()] = models.IDandAddr{
+						ID:      peer.ID.String(),
+						Address: peer.PrimaryAddress(),
+						Name:    peerHost.Name,
+						Network: peer.Network,
 					}
+					hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, nodePeer)
 				}
-			} else if !database.IsEmptyRecord(err) {
-				logger.Log(1, "error retrieving external clients:", err.Error())
 			}
-		}
-		if node.IsEgressGateway {
-			hostPeerUpdate.EgressInfo[node.ID.String()] = models.EgressInfo{
-				EgressID: node.ID.String(),
-				Network:  node.PrimaryNetworkRange(),
-				EgressGwAddr: net.IPNet{
-					IP:   net.ParseIP(node.PrimaryAddress()),
-					Mask: getCIDRMaskFromAddr(node.PrimaryAddress()),
-				},
-				GwPeers:     nodePeerMap,
-				EgressGWCfg: node.EgressGatewayRequest,
+			var extPeers []wgtypes.PeerConfig
+			var extPeerIDAndAddrs []models.IDandAddr
+			if node.IsIngressGateway {
+				extPeers, extPeerIDAndAddrs, err = getExtPeers(&node)
+				if err == nil {
+					for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
+						extPeerIdAndAddr := extPeerIdAndAddr
+						nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
+							PeerAddr: net.IPNet{
+								IP:   net.ParseIP(extPeerIdAndAddr.Address),
+								Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
+							},
+							PeerKey: extPeerIdAndAddr.ID,
+							Allow:   true,
+						}
+					}
+					hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, extPeers...)
+					for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
+						extPeerIdAndAddr := extPeerIdAndAddr
+						hostPeerUpdate.HostPeerIDs[extPeerIdAndAddr.ID] = make(map[string]models.IDandAddr)
+						hostPeerUpdate.HostPeerIDs[extPeerIdAndAddr.ID][extPeerIdAndAddr.ID] = models.IDandAddr{
+							ID:      extPeerIdAndAddr.ID,
+							Address: extPeerIdAndAddr.Address,
+							Name:    extPeerIdAndAddr.Name,
+							Network: node.Network,
+						}
+						hostPeerUpdate.IngressInfo.ExtPeers[extPeerIdAndAddr.ID] = models.ExtClientInfo{
+							Masquerade: true,
+							IngGwAddr: net.IPNet{
+								IP:   net.ParseIP(node.PrimaryAddress()),
+								Mask: getCIDRMaskFromAddr(node.PrimaryAddress()),
+							},
+							Network: node.PrimaryNetworkRange(),
+							ExtPeerAddr: net.IPNet{
+								IP:   net.ParseIP(extPeerIdAndAddr.Address),
+								Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
+							},
+							ExtPeerKey: extPeerIdAndAddr.ID,
+							Peers:      nodePeerMap,
+						}
+						if node.Network == network {
+							hostPeerUpdate.PeerIDs[extPeerIdAndAddr.ID] = extPeerIdAndAddr
+							hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, extPeers...)
+						}
+					}
+				} else if !database.IsEmptyRecord(err) {
+					logger.Log(1, "error retrieving external clients:", err.Error())
+				}
+			}
+			if node.IsEgressGateway {
+				hostPeerUpdate.EgressInfo[node.ID.String()] = models.EgressInfo{
+					EgressID: node.ID.String(),
+					Network:  node.PrimaryNetworkRange(),
+					EgressGwAddr: net.IPNet{
+						IP:   net.ParseIP(node.PrimaryAddress()),
+						Mask: getCIDRMaskFromAddr(node.PrimaryAddress()),
+					},
+					GwPeers:     nodePeerMap,
+					EgressGWCfg: node.EgressGatewayRequest,
+				}
 			}
 		}
 	}
+	// == post peer calculations ==
+	// indicate removal if no allowed IPs were calculated
+	for i := range hostPeerUpdate.Peers {
+		peer := hostPeerUpdate.Peers[i]
+		if len(peer.AllowedIPs) == 0 {
+			peer.Remove = true
+		}
+		hostPeerUpdate.Peers[i] = peer
+	}
 
-	// run through delete nodes
-	if len(deletedNodes) > 0 {
-		for i := range deletedNodes {
-			delNode := deletedNodes[i]
-			delHost, err := GetHost(delNode.HostID.String())
-			if err != nil {
-				continue
-			}
-			if _, ok := hostPeerUpdate.HostPeerIDs[delHost.PublicKey.String()]; !ok {
-				var peerConfig = wgtypes.PeerConfig{}
-				peerConfig.PublicKey = delHost.PublicKey
-				peerConfig.Endpoint = &net.UDPAddr{
-					IP:   delHost.EndpointIP,
-					Port: GetPeerListenPort(delHost),
-				}
-				peerConfig.Remove = true
-				peerConfig.AllowedIPs = []net.IPNet{delNode.Address, delNode.Address6}
-				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
-			}
+	for i := range hostPeerUpdate.NodePeers {
+		peer := hostPeerUpdate.NodePeers[i]
+		if len(peer.AllowedIPs) == 0 {
+			peer.Remove = true
 		}
+		hostPeerUpdate.NodePeers[i] = peer
 	}
 
 	return hostPeerUpdate, nil

+ 1 - 1
main.go

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

+ 28 - 17
models/structs.go

@@ -8,12 +8,17 @@ import (
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
-const PLACEHOLDER_KEY_TEXT = "ACCESS_KEY"
-const PLACEHOLDER_TOKEN_TEXT = "ACCESS_TOKEN"
+const (
+	// PLACEHOLDER_KEY_TEXT - access key placeholder text if option turned off
+	PLACEHOLDER_KEY_TEXT = "ACCESS_KEY"
+	// PLACEHOLDER_TOKEN_TEXT - access key token placeholder text if option turned off
+	PLACEHOLDER_TOKEN_TEXT = "ACCESS_TOKEN"
+)
 
 // CustomExtClient - struct for CustomExtClient params
 type CustomExtClient struct {
-	ClientID string `json:"clientid"`
+	ClientID  string `json:"clientid"`
+	PublicKey string `json:"publickey,omitempty"`
 }
 
 // AuthParams - struct for auth params
@@ -223,20 +228,20 @@ type NodeJoinResponse struct {
 
 // ServerConfig - struct for dealing with the server information for a netclient
 type ServerConfig struct {
-	CoreDNSAddr string `yaml:"corednsaddr"`
-	API         string `yaml:"api"`
-	APIPort     string `yaml:"apiport"`
-	DNSMode     string `yaml:"dnsmode"`
-	Version     string `yaml:"version"`
-	MQPort      string `yaml:"mqport"`
-	MQUserName  string `yaml:"mq_username"`
-	MQPassword  string `yaml:"mq_password"`
-	Server      string `yaml:"server"`
-	Broker      string `yaml:"broker"`
-	Is_EE       bool   `yaml:"isee"`
-	StunPort    int    `yaml:"stun_port"`
-	StunHost    string `yaml:"stun_host"`
-	TrafficKey  []byte `yaml:"traffickey"`
+	CoreDNSAddr string       `yaml:"corednsaddr"`
+	API         string       `yaml:"api"`
+	APIPort     string       `yaml:"apiport"`
+	DNSMode     string       `yaml:"dnsmode"`
+	Version     string       `yaml:"version"`
+	MQPort      string       `yaml:"mqport"`
+	MQUserName  string       `yaml:"mq_username"`
+	MQPassword  string       `yaml:"mq_password"`
+	Server      string       `yaml:"server"`
+	Broker      string       `yaml:"broker"`
+	Is_EE       bool         `yaml:"isee"`
+	StunPort    int          `yaml:"stun_port"`
+	StunList    []StunServer `yaml:"stun_list"`
+	TrafficKey  []byte       `yaml:"traffickey"`
 }
 
 // User.NameInCharset - returns if name is in charset below or not
@@ -261,3 +266,9 @@ type JoinData struct {
 	Node Node   `json:"node" yaml:"node"`
 	Key  string `json:"key" yaml:"key"`
 }
+
+// StunServer - struct to hold data required for using stun server
+type StunServer struct {
+	Domain string `json:"domain" yaml:"domain"`
+	Port   int    `json:"port" yaml:"port"`
+}

+ 214 - 191
mq/handlers.go

@@ -1,11 +1,13 @@
 package mq
 
 import (
+	"context"
 	"encoding/json"
 	"fmt"
 	"time"
 
 	mqtt "github.com/eclipse/paho.mqtt.golang"
+	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
@@ -22,22 +24,29 @@ func DefaultHandler(client mqtt.Client, msg mqtt.Message) {
 
 // Ping message Handler -- handles ping topic from client nodes
 func Ping(client mqtt.Client, msg mqtt.Message) {
-	go func() {
-		id, err := getID(msg.Topic())
-		if err != nil {
-			logger.Log(0, "error getting node.ID sent on ping topic ")
-			return
-		}
+	id, err := getID(msg.Topic())
+	if err != nil {
+		logger.Log(0, "error getting node.ID sent on ping topic ")
+		return
+	}
+	node, err := logic.GetNodeByID(id)
+	if err != nil {
+		logger.Log(3, "mq-ping error getting node: ", err.Error())
 		node, err := logic.GetNodeByID(id)
 		if err != nil {
 			logger.Log(3, "mq-ping error getting node: ", err.Error())
-			record, err := database.FetchRecord(database.NODES_TABLE_NAME, id)
-			if err != nil {
-				logger.Log(3, "error reading database ", err.Error())
-				return
+			if database.IsEmptyRecord(err) {
+				h := logic.GetHostByNodeID(id) // check if a host is still associated
+				if h != nil {                  // inform host that node should be removed
+					fakeNode := models.Node{}
+					fakeNode.ID, _ = uuid.Parse(id)
+					fakeNode.Action = models.NODE_DELETE
+					fakeNode.PendingDelete = true
+					if err := NodeUpdate(&fakeNode); err != nil {
+						logger.Log(0, "failed to inform host", h.Name, h.ID.String(), "to remove node", id, err.Error())
+					}
+				}
 			}
-			logger.Log(3, "record from database")
-			logger.Log(3, record)
 			return
 		}
 		decrypted, decryptErr := decryptMsg(&node, msg.Payload())
@@ -67,233 +76,247 @@ func Ping(client mqtt.Client, msg mqtt.Message) {
 			return
 		}
 
-		logger.Log(3, "ping processed for node", node.ID.String())
-		// --TODO --set client version once feature is implemented.
-		//node.SetClientVersion(msg.Payload())
-	}()
+		return
+	}
+	decrypted, decryptErr := decryptMsg(&node, msg.Payload())
+	if decryptErr != nil {
+		logger.Log(0, "error decrypting when updating node ", node.ID.String(), decryptErr.Error())
+		return
+	}
+	var checkin models.NodeCheckin
+	if err := json.Unmarshal(decrypted, &checkin); err != nil {
+		logger.Log(1, "error unmarshaling payload ", err.Error())
+		return
+	}
+	host, err := logic.GetHost(node.HostID.String())
+	if err != nil {
+		logger.Log(0, "error retrieving host for node ", node.ID.String(), err.Error())
+		return
+	}
+	node.SetLastCheckIn()
+	host.Version = checkin.Version
+	node.Connected = checkin.Connected
+	host.Interfaces = checkin.Ifaces
+	for i := range host.Interfaces {
+		host.Interfaces[i].AddressString = host.Interfaces[i].Address.String()
+	}
+	if err := logic.UpdateNode(&node, &node); err != nil {
+		logger.Log(0, "error updating node", node.ID.String(), " on checkin", err.Error())
+		return
+	}
+
+	logger.Log(3, "ping processed for node", node.ID.String())
+	// --TODO --set client version once feature is implemented.
+	//node.SetClientVersion(msg.Payload())
 }
 
 // UpdateNode  message Handler -- handles updates from client nodes
 func UpdateNode(client mqtt.Client, msg mqtt.Message) {
-	go func() {
-		id, err := getID(msg.Topic())
-		if err != nil {
-			logger.Log(1, "error getting node.ID sent on ", msg.Topic(), err.Error())
-			return
+	id, err := getID(msg.Topic())
+	if err != nil {
+		logger.Log(1, "error getting node.ID sent on ", msg.Topic(), err.Error())
+		return
+	}
+	currentNode, err := logic.GetNodeByID(id)
+	if err != nil {
+		logger.Log(1, "error getting node ", id, err.Error())
+		return
+	}
+	decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
+	if decryptErr != nil {
+		logger.Log(1, "failed to decrypt message for node ", id, decryptErr.Error())
+		return
+	}
+	var newNode models.Node
+	if err := json.Unmarshal(decrypted, &newNode); err != nil {
+		logger.Log(1, "error unmarshaling payload ", err.Error())
+		return
+	}
+
+	ifaceDelta := logic.IfaceDelta(&currentNode, &newNode)
+	if servercfg.Is_EE && ifaceDelta {
+		if err = logic.EnterpriseResetAllPeersFailovers(currentNode.ID, currentNode.Network); err != nil {
+			logger.Log(1, "failed to reset failover list during node update", currentNode.ID.String(), currentNode.Network)
 		}
-		currentNode, err := logic.GetNodeByID(id)
-		if err != nil {
-			logger.Log(1, "error getting node ", id, err.Error())
-			return
+	}
+	newNode.SetLastCheckIn()
+	if err := logic.UpdateNode(&currentNode, &newNode); err != nil {
+		logger.Log(1, "error saving node", err.Error())
+		return
+	}
+	if ifaceDelta { // reduce number of unneeded updates, by only sending on iface changes
+		if err = PublishPeerUpdate(); err != nil {
+			logger.Log(0, "error updating peers when node", currentNode.ID.String(), "informed the server of an interface change", err.Error())
 		}
-		decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
-		if decryptErr != nil {
-			logger.Log(1, "failed to decrypt message for node ", id, decryptErr.Error())
-			return
+	}
+
+	logger.Log(1, "updated node", id, newNode.ID.String())
+}
+
+// UpdateHost  message Handler -- handles host updates from clients
+func UpdateHost(client mqtt.Client, msg mqtt.Message) {
+	id, err := getID(msg.Topic())
+	if err != nil {
+		logger.Log(1, "error getting host.ID sent on ", msg.Topic(), err.Error())
+		return
+	}
+	currentHost, err := logic.GetHost(id)
+	if err != nil {
+		logger.Log(1, "error getting host ", id, err.Error())
+		return
+	}
+	decrypted, decryptErr := decryptMsgWithHost(currentHost, msg.Payload())
+	if decryptErr != nil {
+		logger.Log(1, "failed to decrypt message for host ", id, decryptErr.Error())
+		return
+	}
+	var hostUpdate models.HostUpdate
+	if err := json.Unmarshal(decrypted, &hostUpdate); err != nil {
+		logger.Log(1, "error unmarshaling payload ", err.Error())
+		return
+	}
+	logger.Log(3, fmt.Sprintf("recieved host update: %s\n", hostUpdate.Host.ID.String()))
+	var sendPeerUpdate bool
+	switch hostUpdate.Action {
+	case models.Acknowledgement:
+		hu := hostactions.GetAction(currentHost.ID.String())
+		if hu != nil {
+			if err = HostUpdate(hu); err != nil {
+				logger.Log(0, "failed to send new node to host", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
+				return
+			} else {
+				if err = PublishSingleHostPeerUpdate(context.Background(), currentHost, nil); err != nil {
+					logger.Log(0, "failed peers publish after join acknowledged", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
+					return
+				}
+			}
 		}
-		var newNode models.Node
-		if err := json.Unmarshal(decrypted, &newNode); err != nil {
-			logger.Log(1, "error unmarshaling payload ", err.Error())
+	case models.UpdateHost:
+		sendPeerUpdate = logic.UpdateHostFromClient(&hostUpdate.Host, currentHost)
+		err := logic.UpsertHost(currentHost)
+		if err != nil {
+			logger.Log(0, "failed to update host: ", currentHost.ID.String(), err.Error())
 			return
 		}
-
-		ifaceDelta := logic.IfaceDelta(&currentNode, &newNode)
-		if servercfg.Is_EE && ifaceDelta {
-			if err = logic.EnterpriseResetAllPeersFailovers(currentNode.ID, currentNode.Network); err != nil {
-				logger.Log(1, "failed to reset failover list during node update", currentNode.ID.String(), currentNode.Network)
+	case models.DeleteHost:
+		if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
+			// delete EMQX credentials for host
+			if err := DeleteEmqxUser(currentHost.ID.String()); err != nil {
+				logger.Log(0, "failed to remove host credentials from EMQX: ", currentHost.ID.String(), err.Error())
+				return
 			}
 		}
-		newNode.SetLastCheckIn()
-		if err := logic.UpdateNode(&currentNode, &newNode); err != nil {
-			logger.Log(1, "error saving node", err.Error())
+		if err := logic.DisassociateAllNodesFromHost(currentHost.ID.String()); err != nil {
+			logger.Log(0, "failed to delete all nodes of host: ", currentHost.ID.String(), err.Error())
 			return
 		}
-		if ifaceDelta { // reduce number of unneeded updates, by only sending on iface changes
-			if err = PublishPeerUpdate(); err != nil {
-				logger.Log(0, "error updating peers when node", currentNode.ID.String(), "informed the server of an interface change", err.Error())
-			}
+		if err := logic.RemoveHostByID(currentHost.ID.String()); err != nil {
+			logger.Log(0, "failed to delete host: ", currentHost.ID.String(), err.Error())
+			return
 		}
+		sendPeerUpdate = true
+	}
 
-		logger.Log(1, "updated node", id, newNode.ID.String())
-
-	}()
+	if sendPeerUpdate {
+		err := PublishPeerUpdate()
+		if err != nil {
+			logger.Log(0, "failed to pulish peer update: ", err.Error())
+		}
+	}
+	// if servercfg.Is_EE && ifaceDelta {
+	// 	if err = logic.EnterpriseResetAllPeersFailovers(currentHost.ID.String(), currentHost.Network); err != nil {
+	// 		logger.Log(1, "failed to reset failover list during node update", currentHost.ID.String(), currentHost.Network)
+	// 	}
+	// }
 }
 
-// UpdateHost  message Handler -- handles host updates from clients
-func UpdateHost(client mqtt.Client, msg mqtt.Message) {
-	go func(msg mqtt.Message) {
+// UpdateMetrics  message Handler -- handles updates from client nodes for metrics
+func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
+	if servercfg.Is_EE {
 		id, err := getID(msg.Topic())
 		if err != nil {
-			logger.Log(1, "error getting host.ID sent on ", msg.Topic(), err.Error())
+			logger.Log(1, "error getting node.ID sent on ", msg.Topic(), err.Error())
 			return
 		}
-		currentHost, err := logic.GetHost(id)
+		currentNode, err := logic.GetNodeByID(id)
 		if err != nil {
-			logger.Log(1, "error getting host ", id, err.Error())
+			logger.Log(1, "error getting node ", id, err.Error())
 			return
 		}
-		decrypted, decryptErr := decryptMsgWithHost(currentHost, msg.Payload())
+		decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
 		if decryptErr != nil {
-			logger.Log(1, "failed to decrypt message for host ", id, decryptErr.Error())
+			logger.Log(1, "failed to decrypt message for node ", id, decryptErr.Error())
 			return
 		}
-		var hostUpdate models.HostUpdate
-		if err := json.Unmarshal(decrypted, &hostUpdate); err != nil {
+
+		var newMetrics models.Metrics
+		if err := json.Unmarshal(decrypted, &newMetrics); err != nil {
 			logger.Log(1, "error unmarshaling payload ", err.Error())
 			return
 		}
-		logger.Log(3, fmt.Sprintf("recieved host update: %s\n", hostUpdate.Host.ID.String()))
-		var sendPeerUpdate bool
-		switch hostUpdate.Action {
-		case models.Acknowledgement:
-			hu := hostactions.GetAction(currentHost.ID.String())
-			if hu != nil {
-				if err = HostUpdate(hu); err != nil {
-					logger.Log(0, "failed to send new node to host", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
-					return
-				} else {
-					if err = PublishSingleHostPeerUpdate(currentHost, nil); err != nil {
-						logger.Log(0, "failed peers publish after join acknowledged", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
-						return
-					}
-				}
-			}
-		case models.UpdateHost:
-			sendPeerUpdate = logic.UpdateHostFromClient(&hostUpdate.Host, currentHost)
-			err := logic.UpsertHost(currentHost)
-			if err != nil {
-				logger.Log(0, "failed to update host: ", currentHost.ID.String(), err.Error())
-				return
-			}
-		case models.DeleteHost:
-			if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
-				// delete EMQX credentials for host
-				if err := DeleteEmqxUser(currentHost.ID.String()); err != nil {
-					logger.Log(0, "failed to remove host credentials from EMQX: ", currentHost.ID.String(), err.Error())
-					return
-				}
-			}
-			if err := logic.DisassociateAllNodesFromHost(currentHost.ID.String()); err != nil {
-				logger.Log(0, "failed to delete all nodes of host: ", currentHost.ID.String(), err.Error())
-				return
-			}
-			if err := logic.RemoveHostByID(currentHost.ID.String()); err != nil {
-				logger.Log(0, "failed to delete host: ", currentHost.ID.String(), err.Error())
-				return
-			}
-			sendPeerUpdate = true
-		}
 
-		if sendPeerUpdate {
-			err := PublishPeerUpdate()
-			if err != nil {
-				logger.Log(0, "failed to pulish peer update: ", err.Error())
+		shouldUpdate := updateNodeMetrics(&currentNode, &newMetrics)
+
+		if err = logic.UpdateMetrics(id, &newMetrics); err != nil {
+			logger.Log(1, "faield to update node metrics", id, err.Error())
+			return
+		}
+		if servercfg.IsMetricsExporter() {
+			if err := pushMetricsToExporter(newMetrics); err != nil {
+				logger.Log(2, fmt.Sprintf("failed to push node: [%s] metrics to exporter, err: %v",
+					currentNode.ID, err))
 			}
 		}
-		// if servercfg.Is_EE && ifaceDelta {
-		// 	if err = logic.EnterpriseResetAllPeersFailovers(currentHost.ID.String(), currentHost.Network); err != nil {
-		// 		logger.Log(1, "failed to reset failover list during node update", currentHost.ID.String(), currentHost.Network)
-		// 	}
-		// }
-
-	}(msg)
-}
 
-// UpdateMetrics  message Handler -- handles updates from client nodes for metrics
-func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
-	if servercfg.Is_EE {
-		go func() {
-			id, err := getID(msg.Topic())
-			if err != nil {
-				logger.Log(1, "error getting node.ID sent on ", msg.Topic(), err.Error())
-				return
-			}
-			currentNode, err := logic.GetNodeByID(id)
+		if newMetrics.Connectivity != nil {
+			err := logic.EnterpriseFailoverFunc(&currentNode)
 			if err != nil {
-				logger.Log(1, "error getting node ", id, err.Error())
-				return
-			}
-			decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
-			if decryptErr != nil {
-				logger.Log(1, "failed to decrypt message for node ", id, decryptErr.Error())
-				return
-			}
-
-			var newMetrics models.Metrics
-			if err := json.Unmarshal(decrypted, &newMetrics); err != nil {
-				logger.Log(1, "error unmarshaling payload ", err.Error())
-				return
-			}
-
-			shouldUpdate := updateNodeMetrics(&currentNode, &newMetrics)
-
-			if err = logic.UpdateMetrics(id, &newMetrics); err != nil {
-				logger.Log(1, "faield to update node metrics", id, err.Error())
-				return
-			}
-			if servercfg.IsMetricsExporter() {
-				if err := pushMetricsToExporter(newMetrics); err != nil {
-					logger.Log(2, fmt.Sprintf("failed to push node: [%s] metrics to exporter, err: %v",
-						currentNode.ID, err))
-				}
-			}
-
-			if newMetrics.Connectivity != nil {
-				err := logic.EnterpriseFailoverFunc(&currentNode)
-				if err != nil {
-					logger.Log(0, "failed to failover for node", currentNode.ID.String(), "on network", currentNode.Network, "-", err.Error())
-				}
+				logger.Log(0, "failed to failover for node", currentNode.ID.String(), "on network", currentNode.Network, "-", err.Error())
 			}
+		}
 
-			if shouldUpdate {
-				logger.Log(2, "updating peers after node", currentNode.ID.String(), currentNode.Network, "detected connectivity issues")
-				host, err := logic.GetHost(currentNode.HostID.String())
-				if err == nil {
-					if err = PublishSingleHostPeerUpdate(host, nil); err != nil {
-						logger.Log(0, "failed to publish update after failover peer change for node", currentNode.ID.String(), currentNode.Network)
-					}
+		if shouldUpdate {
+			logger.Log(2, "updating peers after node", currentNode.ID.String(), currentNode.Network, "detected connectivity issues")
+			host, err := logic.GetHost(currentNode.HostID.String())
+			if err == nil {
+				if err = PublishSingleHostPeerUpdate(context.Background(), host, nil); err != nil {
+					logger.Log(0, "failed to publish update after failover peer change for node", currentNode.ID.String(), currentNode.Network)
 				}
 			}
+		}
 
-			logger.Log(1, "updated node metrics", id)
-		}()
+		logger.Log(1, "updated node metrics", id)
 	}
 }
 
 // ClientPeerUpdate  message handler -- handles updating peers after signal from client nodes
 func ClientPeerUpdate(client mqtt.Client, msg mqtt.Message) {
-	go func() {
-		id, err := getID(msg.Topic())
-		if err != nil {
-			logger.Log(1, "error getting node.ID sent on ", msg.Topic(), err.Error())
-			return
-		}
-		currentNode, err := logic.GetNodeByID(id)
-		if err != nil {
-			logger.Log(1, "error getting node ", id, err.Error())
-			return
-		}
-		decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
-		if decryptErr != nil {
-			logger.Log(1, "failed to decrypt message during client peer update for node ", id, decryptErr.Error())
+	id, err := getID(msg.Topic())
+	if err != nil {
+		logger.Log(1, "error getting node.ID sent on ", msg.Topic(), err.Error())
+		return
+	}
+	currentNode, err := logic.GetNodeByID(id)
+	if err != nil {
+		logger.Log(1, "error getting node ", id, err.Error())
+		return
+	}
+	decrypted, decryptErr := decryptMsg(&currentNode, msg.Payload())
+	if decryptErr != nil {
+		logger.Log(1, "failed to decrypt message during client peer update for node ", id, decryptErr.Error())
+		return
+	}
+	switch decrypted[0] {
+	case ncutils.ACK:
+		// do we still need this
+	case ncutils.DONE:
+		if err = PublishPeerUpdate(); err != nil {
+			logger.Log(1, "error publishing peer update for node", currentNode.ID.String(), err.Error())
 			return
 		}
-		switch decrypted[0] {
-		case ncutils.ACK:
-			//do we still need this
-		case ncutils.DONE:
-			updateNodePeers(&currentNode)
-		}
-
-		logger.Log(1, "sent peer updates after signal received from", id)
-	}()
-}
-
-func updateNodePeers(currentNode *models.Node) {
-	if err := PublishPeerUpdate(); err != nil {
-		logger.Log(1, "error publishing peer update ", err.Error())
-		return
 	}
+
+	logger.Log(1, "sent peer updates after signal received from", id)
 }
 
 func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) bool {

+ 12 - 11
mq/publishers.go

@@ -1,6 +1,7 @@
 package mq
 
 import (
+	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -23,10 +24,10 @@ func PublishPeerUpdate() error {
 		logger.Log(1, "err getting all hosts", err.Error())
 		return err
 	}
+	logic.ResetPeerUpdateContext()
 	for _, host := range hosts {
 		host := host
-		err = PublishSingleHostPeerUpdate(&host, nil)
-		if err != nil {
+		if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil); err != nil {
 			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
 		}
 	}
@@ -45,9 +46,10 @@ func PublishDeletedNodePeerUpdate(delNode *models.Node) error {
 		logger.Log(1, "err getting all hosts", err.Error())
 		return err
 	}
+	logic.ResetPeerUpdateContext()
 	for _, host := range hosts {
 		host := host
-		if err = PublishSingleHostPeerUpdate(&host, delNode); err != nil {
+		if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, delNode); err != nil {
 			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
 		}
 	}
@@ -55,9 +57,9 @@ func PublishDeletedNodePeerUpdate(delNode *models.Node) error {
 }
 
 // PublishSingleHostPeerUpdate --- determines and publishes a peer update to one host
-func PublishSingleHostPeerUpdate(host *models.Host, deletedNode *models.Node) error {
+func PublishSingleHostPeerUpdate(ctx context.Context, host *models.Host, deletedNode *models.Node) error {
 
-	peerUpdate, err := logic.GetPeerUpdateForHost("", host, deletedNode)
+	peerUpdate, err := logic.GetPeerUpdateForHost(ctx, "", host, deletedNode)
 	if err != nil {
 		return err
 	}
@@ -65,7 +67,7 @@ func PublishSingleHostPeerUpdate(host *models.Host, deletedNode *models.Node) er
 		return nil
 	}
 	if host.ProxyEnabled {
-		proxyUpdate, err := logic.GetProxyUpdateForHost(host)
+		proxyUpdate, err := logic.GetProxyUpdateForHost(ctx, host)
 		if err != nil {
 			return err
 		}
@@ -422,13 +424,12 @@ func sendPeers() {
 
 		//collectServerMetrics(networks[:])
 	}
-
-	for _, host := range hosts {
-		if force {
+	if force {
+		logic.ResetPeerUpdateContext()
+		for _, host := range hosts {
 			host := host
 			logger.Log(2, "sending scheduled peer update (5 min)")
-			err = PublishSingleHostPeerUpdate(&host, nil)
-			if err != nil {
+			if err = PublishSingleHostPeerUpdate(logic.PeerUpdateCtx, &host, nil); err != nil {
 				logger.Log(1, "error publishing peer updates for host: ", host.ID.String(), " Err: ", err.Error())
 			}
 		}

+ 22 - 2
release.md

@@ -1,7 +1,27 @@
-# Netmaker v0.18.1
+# Netmaker v0.18.3
 
-## whats new
+## **Do not attempt upgrade from 0.17.x quite yet**
 
+## whats new
+- Enrollment Keys, give the ability for an admin to enroll clients into multiple networks, can be unlimited, time, or usage based
+- EMQX broker support and better MQTT support in general
+  - Now you must specify BROKER_ENDPOINT
+  - Also specify SERVER_BROKER_ENDPOINT, if not provided server will connect to broker over BROKER_ENDPOINT
+  - Thsi gives ability for user to specify any broker endpoint and use any protocal on clients desired, such as, `mqtts://mybroker.com:8083`
+    (we will still default to wss)
+    
 ## whats fixed
+- Fixed default ACL behavior, should work as expected
+- Peer calculations enhancement
+- main routines share a context and docker stop/ctrl+c give expected results now
+- Github workflow edits
+- Removed Deprecated Local Network Range from client + server
 
 ## known issues
+- EnrollmentKeys may not function as intended in an HA setup
+- If a host does not receive a message to delete a node, it could become orphaned and un-deletable
+- Network interface routes may be removed after sometime/unintended network update
+- Upgrade script does not handle clients
+- Caddy does not handle netmaker exporter well for EE
+- Incorrect latency on metrics (EE)
+- Swagger docs not up to date

+ 2 - 2
scripts/nm-quick.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-LATEST="v0.18.2"
+LATEST="v0.18.3"
 
 print_logo() {(
 cat << "EOF"
@@ -40,7 +40,7 @@ usage () {(
 	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 "examples:"
-	echo "          nm-quick.sh -e -b version -t v0.18.2"
+	echo "          nm-quick.sh -e -b version -t v0.18.3"
 	echo "          nm-quick.sh -e -b local -t feature_v0.17.2_newfeature"	
 	echo "          nm-quick.sh -e -b branch -t develop"
     exit 1

+ 1 - 1
scripts/nm-upgrade.sh

@@ -356,7 +356,7 @@ set_compose() {
   sed -i "s/v0.17.1/$LATEST/g" /root/docker-compose.yml
 
   # RELEASE_REPLACE - Use this once release is ready
-  # sed -i "s/v0.17.1/v0.18.1/g" /root/docker-compose.yml
+  #sed -i "s/v0.17.1/v0.18.3/g" /root/docker-compose.yml
   yq ".services.netmaker.environment.SERVER_NAME = \"$SERVER_NAME\"" -i /root/docker-compose.yml
   yq ".services.netmaker.environment += {\"BROKER_NAME\": \"$BROKER_NAME\"}" -i /root/docker-compose.yml  
   yq ".services.netmaker.environment += {\"STUN_DOMAIN\": \"$STUN_DOMAIN\"}" -i /root/docker-compose.yml  

+ 71 - 10
servercfg/serverconf.go

@@ -43,7 +43,6 @@ func GetServerConfig() config.ServerConfig {
 	cfg.AllowedOrigin = GetAllowedOrigin()
 	cfg.RestBackend = "off"
 	cfg.NodeID = GetNodeID()
-	cfg.StunHost = GetStunAddr()
 	cfg.StunPort = GetStunPort()
 	cfg.BrokerType = GetBrokerType()
 	cfg.EmqxRestEndpoint = GetEmqxRestEndpoint()
@@ -74,6 +73,7 @@ func GetServerConfig() config.ServerConfig {
 	cfg.FrontendURL = GetFrontendURL()
 	cfg.Telemetry = Telemetry()
 	cfg.Server = GetServer()
+	cfg.StunList = GetStunListString()
 	cfg.Verbosity = GetVerbosity()
 	cfg.IsEE = "no"
 	if Is_EE {
@@ -99,8 +99,8 @@ func GetServerInfo() models.ServerConfig {
 	}
 	cfg.Version = GetVersion()
 	cfg.Is_EE = Is_EE
-	cfg.StunHost = GetStunAddr()
 	cfg.StunPort = GetStunPort()
+	cfg.StunList = GetStunList()
 
 	return cfg
 }
@@ -177,15 +177,44 @@ func GetAPIPort() string {
 	return apiport
 }
 
-// GetStunAddr - gets the stun host address
-func GetStunAddr() string {
-	stunAddr := ""
-	if os.Getenv("STUN_DOMAIN") != "" {
-		stunAddr = os.Getenv("STUN_DOMAIN")
-	} else if config.Config.Server.StunHost != "" {
-		stunAddr = config.Config.Server.StunHost
+// GetStunList - gets the stun servers
+func GetStunList() []models.StunServer {
+	stunList := []models.StunServer{
+		models.StunServer{
+			Domain: "stun1.netmaker.io",
+			Port:   3478,
+		},
+		models.StunServer{
+			Domain: "stun2.netmaker.io",
+			Port:   3478,
+		},
+	}
+	parsed := false
+	if os.Getenv("STUN_LIST") != "" {
+		stuns, err := parseStunList(os.Getenv("STUN_LIST"))
+		if err == nil {
+			parsed = true
+			stunList = stuns
+		}
+	}
+	if !parsed && config.Config.Server.StunList != "" {
+		stuns, err := parseStunList(config.Config.Server.StunList)
+		if err == nil {
+			stunList = stuns
+		}
 	}
-	return stunAddr
+	return stunList
+}
+
+// GetStunList - gets the stun servers w/o parsing to struct
+func GetStunListString() string {
+	stunList := "stun1.netmaker.io:3478,stun2.netmaker.io:3478"
+	if os.Getenv("STUN_LIST") != "" {
+		stunList = os.Getenv("STUN_LIST")
+	} else if config.Config.Server.StunList != "" {
+		stunList = config.Config.Server.StunList
+	}
+	return stunList
 }
 
 // GetCoreDNSAddr - gets the core dns address
@@ -582,6 +611,7 @@ func GetNetmakerAccountID() string {
 	return netmakerAccountID
 }
 
+// GetStunPort - Get the port to run the stun server on
 func GetStunPort() int {
 	port := 3478 //default
 	if os.Getenv("STUN_PORT") != "" {
@@ -595,6 +625,7 @@ func GetStunPort() int {
 	return port
 }
 
+// IsProxyEnabled - is proxy on or off
 func IsProxyEnabled() bool {
 	var enabled = false //default
 	if os.Getenv("PROXY") != "" {
@@ -604,3 +635,33 @@ func IsProxyEnabled() bool {
 	}
 	return enabled
 }
+
+// parseStunList - turn string into slice of StunServers
+func parseStunList(stunString string) ([]models.StunServer, error) {
+	var err error
+	stunServers := []models.StunServer{}
+	stuns := strings.Split(stunString, ",")
+	if len(stuns) == 0 {
+		return stunServers, errors.New("no stun servers provided")
+	}
+	for _, stun := range stuns {
+		stun = strings.Trim(stun, " ")
+		stunInfo := strings.Split(stun, ":")
+		if len(stunInfo) != 2 {
+			continue
+		}
+		port, err := strconv.Atoi(stunInfo[1])
+		if err != nil || port == 0 {
+			continue
+		}
+		stunServers = append(stunServers, models.StunServer{
+			Domain: stunInfo[0],
+			Port:   port,
+		})
+
+	}
+	if len(stunServers) == 0 {
+		err = errors.New("no stun entries parsable")
+	}
+	return stunServers, err
+}

+ 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.
     title: Netmaker
-    version: 0.18.2
+    version: 0.18.3
 paths:
     /api/dns:
         get: