Browse Source

Merge branch 'develop' into gra-1088_dns_updates

Matthew R Kasun 2 years ago
parent
commit
bd9cf14c02
57 changed files with 547 additions and 1138 deletions
  1. 1 0
      .github/ISSUE_TEMPLATE/bug-report.yml
  2. 1 1
      README.md
  3. 20 0
      cli/cmd/host/add_network.go
  4. 20 0
      cli/cmd/host/delete_network.go
  5. 0 22
      cli/cmd/host/update_networks.go
  6. 8 5
      cli/functions/host.go
  7. 11 9
      compose/docker-compose.ee.yml
  8. 16 10
      compose/docker-compose.reference.yml
  9. 8 11
      compose/docker-compose.yml
  10. 2 1
      config/config.go
  11. 0 12
      controllers/dns_test.go
  12. 1 1
      controllers/docs.go
  13. 1 1
      controllers/ext_client.go
  14. 5 5
      controllers/hosts.go
  15. 17 5
      controllers/network.go
  16. 22 24
      controllers/network_test.go
  17. 0 14
      controllers/node.go
  18. 0 3
      controllers/node_test.go
  19. 12 0
      controllers/relay.go
  20. 31 0
      controllers/server.go
  21. 1 11
      controllers/user_test.go
  22. 20 0
      database/database.go
  23. 6 0
      database/postgres.go
  24. 6 0
      database/rqlite.go
  25. 6 0
      database/sqlite.go
  26. 1 2
      docker/mosquitto.conf
  27. 5 10
      docker/wait.sh
  28. 22 8
      functions/helpers_test.go
  29. 8 9
      go.mod
  30. 18 32
      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. 22 1
      logic/host_test.go
  36. 2 15
      logic/hosts.go
  37. 1 1
      logic/nodes.go
  38. 34 23
      logic/peers.go
  39. 5 1
      logic/pro/networkuser_test.go
  40. 0 2
      logic/pro/usergroups_test.go
  41. 57 23
      logic/zombie.go
  42. 0 6
      main.go
  43. 0 28
      metrics/metrics.go
  44. 9 2
      models/api_host.go
  45. 2 1
      models/api_node.go
  46. 1 1
      models/network_test.go
  47. 2 0
      models/structs.go
  48. 0 214
      mq/dynsec.go
  49. 0 47
      mq/dynsec_clients.go
  50. 0 187
      mq/dynsec_helper.go
  51. 0 6
      mq/handlers.go
  52. 6 35
      mq/mq.go
  53. 113 32
      scripts/nm-quick-interactive.sh
  54. 0 304
      scripts/nm-quick.sh
  55. 19 6
      servercfg/serverconf.go
  56. 0 2
      stun-server/stun-server.go
  57. 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.0      
         - v0.17.1      
         - v0.17.0
         - v0.16.3

+ 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.17.1-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.18.0-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 - 0
cli/cmd/host/add_network.go

@@ -0,0 +1,20 @@
+package host
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var addHostNetworkCmd = &cobra.Command{
+	Use:   "add_network HostID Network",
+	Args:  cobra.ExactArgs(2),
+	Short: "Add a network to a host",
+	Long:  `Add a network to a host`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.AddHostToNetwork(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(addHostNetworkCmd)
+}

+ 20 - 0
cli/cmd/host/delete_network.go

@@ -0,0 +1,20 @@
+package host
+
+import (
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var deleteHostNetworkCmd = &cobra.Command{
+	Use:   "delete_network HostID Network",
+	Args:  cobra.ExactArgs(2),
+	Short: "Delete a network from a host",
+	Long:  `Delete a network from a host`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.DeleteHostFromNetwork(args[0], args[1]))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(deleteHostNetworkCmd)
+}

+ 0 - 22
cli/cmd/host/update_networks.go

@@ -1,22 +0,0 @@
-package host
-
-import (
-	"strings"
-
-	"github.com/gravitl/netmaker/cli/functions"
-	"github.com/spf13/cobra"
-)
-
-var hostUpdateNetworksCmd = &cobra.Command{
-	Use:   "update_network HostID Networks(comma separated list)",
-	Args:  cobra.ExactArgs(2),
-	Short: "Update a host's networks",
-	Long:  `Update a host's networks`,
-	Run: func(cmd *cobra.Command, args []string) {
-		functions.PrettyPrint(functions.UpdateHostNetworks(args[0], strings.Split(args[1], ",")))
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(hostUpdateNetworksCmd)
-}

+ 8 - 5
cli/functions/host.go

@@ -26,11 +26,14 @@ func UpdateHost(hostID string, body *models.ApiHost) *models.ApiHost {
 	return request[models.ApiHost](http.MethodPut, "/api/hosts/"+hostID, body)
 }
 
-// UpdateHostNetworks - update a host's networks
-func UpdateHostNetworks(hostID string, networks []string) *hostNetworksUpdatePayload {
-	return request[hostNetworksUpdatePayload](http.MethodPut, "/api/hosts/"+hostID+"/networks", &hostNetworksUpdatePayload{
-		Networks: networks,
-	})
+// AddHostToNetwork - add a network to host
+func AddHostToNetwork(hostID, network string) *hostNetworksUpdatePayload {
+	return request[hostNetworksUpdatePayload](http.MethodPost, "/api/hosts/"+hostID+"/networks/"+network, nil)
+}
+
+// DeleteHostFromNetwork - deletes a network from host
+func DeleteHostFromNetwork(hostID, network string) *hostNetworksUpdatePayload {
+	return request[hostNetworksUpdatePayload](http.MethodDelete, "/api/hosts/"+hostID+"/networks/"+network, nil)
 }
 
 // CreateRelay - turn a host into a relay

+ 11 - 9
compose/docker-compose.ee.yml

@@ -3,14 +3,15 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.17.1-ee
+    image: gravitl/netmaker:v0.18.0-ee
     restart: always
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
-      - mosquitto_data:/etc/netmaker
     environment:
-      SERVER_NAME: "broker.NETMAKER_BASE_DOMAIN"
+      BROKER_NAME: "broker.NETMAKER_BASE_DOMAIN"
+      SERVER_NAME: "NETMAKER_BASE_DOMAIN"
+      STUN_DOMAIN: "stun.NETMAKER_BASE_DOMAIN"
       SERVER_HOST: "SERVER_PUBLIC_IP"
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
       COREDNS_ADDR: "SERVER_PUBLIC_IP"
@@ -25,16 +26,18 @@ services:
       MQ_HOST: "mq"
       MQ_PORT: "443"
       MQ_SERVER_PORT: "1883"
+      MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
+      MQ_USERNAME: "REPLACE_MQ_USERNAME"
+      STUN_PORT: "3478"
       VERBOSITY: "1"
       METRICS_EXPORTER: "on"
       LICENSE_KEY: "YOUR_LICENSE_KEY"
       NETMAKER_ACCOUNT_ID: "YOUR_ACCOUNT_ID"
-      MQ_ADMIN_PASSWORD: "REPLACE_MQ_ADMIN_PASSWORD"
     ports:
-      - "51821-51830:51821-51830/udp"
+      - "3478:3478/udp"
   netmaker-ui:
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.17.1
+    image: gravitl/netmaker-ui:v0.18.0
     depends_on:
       - netmaker
     links:
@@ -70,11 +73,11 @@ services:
     restart: unless-stopped
     command: ["/mosquitto/config/wait.sh"]
     environment:
-      NETMAKER_SERVER_HOST: "https://api.NETMAKER_BASE_DOMAIN"
+      MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
+      MQ_USERNAME: "REPLACE_MQ_USERNAME"
     volumes:
       - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf
       - /root/wait.sh:/mosquitto/config/wait.sh
-      - mosquitto_data:/mosquitto/data
       - mosquitto_logs:/mosquitto/log
     ports:
       - "1883:1883"
@@ -125,7 +128,6 @@ volumes:
   caddy_conf: {}
   sqldata: {}
   dnsconfig: {}
-  mosquitto_data: {}
   mosquitto_logs: {}
   prometheus_data: {}
   grafana_data: {}

+ 16 - 10
compose/docker-compose.reference.yml

@@ -3,14 +3,15 @@ version: "3.4"
 services:
   netmaker: # The Primary Server for running Netmaker
     container_name: netmaker
-    image: gravitl/netmaker:v0.17.1
+    image: gravitl/netmaker:v0.18.0
     restart: always
     volumes: # Volume mounts necessary for sql, coredns, and mqtt
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
       - shared_certs:/etc/netmaker
     environment: # Necessary capabilities to set iptables when running in container
-      SERVER_NAME: "broker.NETMAKER_BASE_DOMAIN" # The domain/host IP indicating the mq broker address
+      BROKER_NAME: "broker.NETMAKER_BASE_DOMAIN" # The domain/host IP indicating the mq broker address
+      SERVER_NAME: "NETMAKER_BASE_DOMAIN" # The base domain of netmaker
       SERVER_HOST: "SERVER_PUBLIC_IP" # Set to public IP of machine.
       SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN" # Overrides SERVER_HOST if set. Useful for making HTTP available via different interfaces/networks.
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
@@ -26,8 +27,11 @@ services:
       DATABASE: "sqlite" # Database to use - sqlite, postgres, or rqlite
       NODE_ID: "netmaker-server-1" # used for HA - identifies this server vs other servers
       MQ_HOST: "mq"  # the address of the mq server. If running from docker compose it will be "mq". Otherwise, need to input address. If using "host networking", it will find and detect the IP of the mq container.
-      MQ_SERVER_PORT: "1883" # the reachable port of MQ by the server - change if internal MQ port changes (or use external port if MQ is not on the same machine)
       MQ_PORT: "443" # the reachable port of MQ - change if external MQ port changes (port on proxy, not necessarily the one exposed in docker-compose)
+      MQ_SERVER_PORT: "1883" # the reachable port of MQ by the server - change if internal MQ port changes (or use external port if MQ is not on the same machine)
+      MQ_USERNAME: "REPLACE_MQ_USERNAME" # the username to set for MQ access
+      MQ_PASSWORD: "REPLACE_MQ_PASSWORD" # the password to set for MQ access
+      STUN_PORT: "3478" # the reachable port of STUN on the server
       VERBOSITY: "1" # logging verbosity level - 1, 2, or 3
       # this section is for OAuth
       AUTH_PROVIDER: "" # "<azure-ad|github|google|oidc>"
@@ -37,10 +41,10 @@ services:
       AZURE_TENANT: "" # "<only for azure, you may optionally specify the tenant for the OAuth>"
       OIDC_ISSUER: "" # https://oidc.yourprovider.com - URL of oidc provider
     ports:
-      - "51821-51830:51821-51830/udp" # wireguard ports
+      - "3478:3478/udp" # the stun port
   netmaker-ui:  # The Netmaker UI Component
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.17.1
+    image: gravitl/netmaker-ui:v0.18.0
     depends_on:
       - netmaker
     links:
@@ -68,17 +72,20 @@ services:
     restart: always
     volumes:
       - dnsconfig:/root/dnsconfig
-  mq: # the MQTT broker for netmaker
+  mq: # the mqtt broker for netmaker
     container_name: mq
     image: eclipse-mosquitto:2.0.15-openssl
     depends_on:
       - netmaker
     restart: unless-stopped
+    command: ["/mosquitto/config/wait.sh"]
+    environment:
+      MQ_PASSWORD: "REPLACE_MQ_PASSWORD" # must be same value as in netmaker env 
+      MQ_USERNAME: "REPLACE_MQ_USERNAME" # must be same value as in netmaker env
     volumes:
-      - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf # need to pull conf file from github before running (under docker/mosquitto.conf)
-      - mosquitto_data:/mosquitto/data
+      - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf
+      - /root/wait.sh:/mosquitto/config/wait.sh
       - mosquitto_logs:/mosquitto/log
-      - shared_certs:/mosquitto/certs
     ports:
       - "1883:1883"
       - "8883:8883"
@@ -88,5 +95,4 @@ volumes:
   shared_certs: {} # netmaker certs generated for MQ comms - used by nodes/servers
   sqldata: {} # storage for embedded sqlite
   dnsconfig: {} # storage for coredns
-  mosquitto_data: {} # storage for mqtt data
   mosquitto_logs: {} # storage for mqtt logs

+ 8 - 11
compose/docker-compose.yml

@@ -3,12 +3,11 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.17.1
+    image: gravitl/netmaker:v0.18.0
     restart: always
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
-      - mosquitto_data:/etc/netmaker
     environment:
       BROKER_NAME: "broker.NETMAKER_BASE_DOMAIN"
       SERVER_NAME: "NETMAKER_BASE_DOMAIN"
@@ -25,18 +24,17 @@ services:
       DATABASE: "sqlite"
       NODE_ID: "netmaker-server-1"
       MQ_HOST: "mq"
-      MQ_PORT: "443"
+      MQ_PORT: "443"      
       MQ_SERVER_PORT: "1883"
+      STUN_PORT: "3478"      
       VERBOSITY: "1"
-      MQ_ADMIN_PASSWORD: "REPLACE_MQ_ADMIN_PASSWORD"
-      STUN_PORT: "3478"
-      PROXY: "on"
+      MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
+      MQ_USERNAME: "REPLACE_MQ_USERNAME"
     ports:
-      - "51821-51830:51821-51830/udp"
       - "3478:3478/udp"
   netmaker-ui:
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.17.1
+    image: gravitl/netmaker-ui:v0.18.0
     depends_on:
       - netmaker
     links:
@@ -72,16 +70,15 @@ services:
     restart: unless-stopped
     command: ["/mosquitto/config/wait.sh"]
     environment:
-      NETMAKER_SERVER_HOST: "https://api.NETMAKER_BASE_DOMAIN"
+      MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
+      MQ_USERNAME: "REPLACE_MQ_USERNAME"
     volumes:
       - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf
       - /root/wait.sh:/mosquitto/config/wait.sh
-      - mosquitto_data:/mosquitto/data
       - mosquitto_logs:/mosquitto/log
 volumes:
   caddy_data: {}
   caddy_conf: {}
   sqldata: {}
   dnsconfig: {}
-  mosquitto_data: {}
   mosquitto_logs: {}

+ 2 - 1
config/config.go

@@ -67,7 +67,8 @@ type ServerConfig struct {
 	Server                string `yaml:"server"`
 	Broker                string `yam:"broker"`
 	PublicIPService       string `yaml:"publicipservice"`
-	MQAdminPassword       string `yaml:"mqadminpassword"`
+	MQPassword            string `yaml:"mqpassword"`
+	MQUserName            string `yaml:"mqusername"`
 	MetricsExporter       string `yaml:"metrics_exporter"`
 	BasicAuth             string `yaml:"basic_auth"`
 	LicenseValue          string `yaml:"license_value"`

+ 0 - 12
controllers/dns_test.go

@@ -6,7 +6,6 @@ import (
 	"testing"
 
 	"github.com/google/uuid"
-	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/stretchr/testify/assert"
@@ -16,7 +15,6 @@ import (
 var dnsHost models.Host
 
 func TestGetAllDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
@@ -47,7 +45,6 @@ func TestGetAllDNS(t *testing.T) {
 }
 
 func TestGetNodeDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
@@ -94,7 +91,6 @@ func TestGetNodeDNS(t *testing.T) {
 	})
 }
 func TestGetCustomDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	t.Run("NoNetworks", func(t *testing.T) {
@@ -133,7 +129,6 @@ func TestGetCustomDNS(t *testing.T) {
 }
 
 func TestGetDNSEntryNum(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
@@ -152,7 +147,6 @@ func TestGetDNSEntryNum(t *testing.T) {
 	})
 }
 func TestGetDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
@@ -196,7 +190,6 @@ func TestGetDNS(t *testing.T) {
 }
 
 func TestCreateDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
@@ -207,7 +200,6 @@ func TestCreateDNS(t *testing.T) {
 }
 
 func TestSetDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	t.Run("NoNetworks", func(t *testing.T) {
@@ -255,7 +247,6 @@ func TestSetDNS(t *testing.T) {
 }
 
 func TestGetDNSEntry(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
@@ -285,7 +276,6 @@ func TestGetDNSEntry(t *testing.T) {
 }
 
 func TestDeleteDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
@@ -307,7 +297,6 @@ func TestDeleteDNS(t *testing.T) {
 }
 
 func TestValidateDNSUpdate(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
@@ -369,7 +358,6 @@ func TestValidateDNSUpdate(t *testing.T) {
 
 }
 func TestValidateDNSCreate(t *testing.T) {
-	database.InitializeDatabase()
 	_ = logic.DeleteDNS("mynode", "skynet")
 	t.Run("NoNetwork", func(t *testing.T) {
 		entry := models.DNSEntry{"10.0.0.2", "", "myhost", "badnet"}

+ 1 - 1
controllers/docs.go

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

+ 1 - 1
controllers/ext_client.go

@@ -214,7 +214,7 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
 	if network.DefaultKeepalive != 0 {
 		keepalive = "PersistentKeepalive = " + strconv.Itoa(int(network.DefaultKeepalive))
 	}
-	gwendpoint := host.EndpointIP.String() + ":" + strconv.Itoa(host.ListenPort)
+	gwendpoint := host.EndpointIP.String() + ":" + strconv.Itoa(logic.GetPeerListenPort(host))
 	newAllowedIPs := network.AddressRange
 	if newAllowedIPs != "" && network.AddressRange6 != "" {
 		newAllowedIPs += ","

+ 5 - 5
controllers/hosts.go

@@ -97,7 +97,6 @@ func updateHost(w http.ResponseWriter, r *http.Request) {
 	if updateRelay {
 		logic.UpdateHostRelay(currHost.ID.String(), currHost.RelayedHosts, newHost.RelayedHosts)
 	}
-
 	// publish host update through MQ
 	if err := mq.HostUpdate(&models.HostUpdate{
 		Action: models.UpdateHost,
@@ -190,10 +189,6 @@ func deleteHost(w http.ResponseWriter, r *http.Request) {
 		logger.Log(0, r.Header.Get("user"), "failed to send delete host update: ", currHost.ID.String(), err.Error())
 	}
 
-	if err = mq.DeleteMqClient(currHost.ID.String()); err != nil {
-		logger.Log(0, "error removing DynSec credentials for host:", currHost.Name, err.Error())
-	}
-
 	apiHostData := currHost.ConvertNMHostToAPI()
 	logger.Log(2, r.Header.Get("user"), "removed host", currHost.Name)
 	w.WriteHeader(http.StatusOK)
@@ -242,6 +237,11 @@ func addHostToNetwork(w http.ResponseWriter, r *http.Request) {
 	}); err != nil {
 		logger.Log(0, r.Header.Get("user"), "failed to update host to join network:", hostid, network, err.Error())
 	}
+	go func() { // notify of peer change
+		if err := mq.PublishPeerUpdate(); err != nil {
+			logger.Log(1, "error publishing peer update ", err.Error())
+		}
+	}()
 
 	logger.Log(2, r.Header.Get("user"), fmt.Sprintf("added host %s to network %s", currHost.Name, network))
 	w.WriteHeader(http.StatusOK)

+ 17 - 5
controllers/network.go

@@ -403,13 +403,25 @@ func createNetwork(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if err = logic.AddDefaultHostsToNetwork(network.NetID, servercfg.GetServer()); err != nil {
-		logger.Log(0, fmt.Sprintf("failed to add default hosts to network [%v]: %v",
-			network.NetID, err.Error()))
+	defaultHosts := logic.GetDefaultHosts()
+	for i := range defaultHosts {
+		currHost := &defaultHosts[i]
+		newNode, err := logic.UpdateHostNetwork(currHost, network.NetID, true)
+		if err != nil {
+			logger.Log(0, r.Header.Get("user"), "failed to add host to network:", currHost.ID.String(), network.NetID, err.Error())
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+			return
+		}
+		logger.Log(1, "added new node", newNode.ID.String(), "to host", currHost.Name)
+		if err = mq.HostUpdate(&models.HostUpdate{
+			Action: models.JoinHostToNetwork,
+			Host:   *currHost,
+			Node:   *newNode,
+		}); err != nil {
+			logger.Log(0, r.Header.Get("user"), "failed to add host to network:", currHost.ID.String(), network.NetID, err.Error())
+		}
 	}
 
-	// TODO: Send message notifying host of new peers/network conf
-
 	logger.Log(1, r.Header.Get("user"), "created network", network.NetID)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(network)

+ 22 - 24
controllers/network_test.go

@@ -1,11 +1,13 @@
 package controller
 
 import (
+	"context"
 	"os"
 	"testing"
 
 	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/stretchr/testify/assert"
@@ -20,8 +22,27 @@ type NetworkValidationTestCase struct {
 
 var netHost models.Host
 
+func TestMain(m *testing.M) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	logic.CreateAdmin(&models.User{
+		UserName: "admin",
+		Password: "password",
+		IsAdmin:  true,
+		Networks: []string{},
+		Groups:   []string{},
+	})
+	peerUpdate := make(chan *models.Node)
+	go logic.ManageZombies(context.Background(), peerUpdate)
+	go func() {
+		for update := range peerUpdate {
+			//do nothing
+			logger.Log(3, "received node update", update.Action)
+		}
+	}()
+}
+
 func TestCreateNetwork(t *testing.T) {
-	initialize()
 	deleteAllNetworks()
 
 	var network models.Network
@@ -34,7 +55,6 @@ func TestCreateNetwork(t *testing.T) {
 	assert.Nil(t, err)
 }
 func TestGetNetwork(t *testing.T) {
-	initialize()
 	createNet()
 
 	t.Run("GetExistingNetwork", func(t *testing.T) {
@@ -50,7 +70,6 @@ func TestGetNetwork(t *testing.T) {
 }
 
 func TestDeleteNetwork(t *testing.T) {
-	initialize()
 	createNet()
 	//create nodes
 	t.Run("NetworkwithNodes", func(t *testing.T) {
@@ -66,7 +85,6 @@ func TestDeleteNetwork(t *testing.T) {
 }
 
 func TestCreateKey(t *testing.T) {
-	initialize()
 	createNet()
 	keys, _ := logic.GetKeys("skynet")
 	for _, key := range keys {
@@ -138,7 +156,6 @@ func TestCreateKey(t *testing.T) {
 }
 
 func TestGetKeys(t *testing.T) {
-	initialize()
 	deleteAllNetworks()
 	createNet()
 	network, err := logic.GetNetwork("skynet")
@@ -161,7 +178,6 @@ func TestGetKeys(t *testing.T) {
 	})
 }
 func TestDeleteKey(t *testing.T) {
-	initialize()
 	createNet()
 	network, err := logic.GetNetwork("skynet")
 	assert.Nil(t, err)
@@ -183,7 +199,6 @@ func TestDeleteKey(t *testing.T) {
 func TestSecurityCheck(t *testing.T) {
 	//these seem to work but not sure it the tests are really testing the functionality
 
-	initialize()
 	os.Setenv("MASTER_KEY", "secretkey")
 	t.Run("NoNetwork", func(t *testing.T) {
 		networks, username, err := logic.UserPermissions(false, "", "Bearer secretkey")
@@ -214,7 +229,6 @@ func TestValidateNetwork(t *testing.T) {
 	//t.Skip()
 	//This functions is not called by anyone
 	//it panics as validation function 'display_name_valid' is not defined
-	initialize()
 	//yes := true
 	//no := false
 	//deleteNet(t)
@@ -291,7 +305,6 @@ func TestValidateNetwork(t *testing.T) {
 func TestIpv6Network(t *testing.T) {
 	//these seem to work but not sure it the tests are really testing the functionality
 
-	initialize()
 	os.Setenv("MASTER_KEY", "secretkey")
 	deleteAllNetworks()
 	createNet()
@@ -318,21 +331,6 @@ func deleteAllNetworks() {
 	}
 }
 
-func initialize() {
-	database.InitializeDatabase()
-	createAdminUser()
-}
-
-func createAdminUser() {
-	logic.CreateAdmin(&models.User{
-		UserName: "admin",
-		Password: "password",
-		IsAdmin:  true,
-		Networks: []string{},
-		Groups:   []string{},
-	})
-}
-
 func createNet() {
 	var network models.Network
 	network.NetID = "skynet"

+ 0 - 14
controllers/node.go

@@ -577,8 +577,6 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 	}
 	server := servercfg.GetServerInfo()
 	server.TrafficKey = key
-	// consume password before hashing for mq client creation
-	hostPassword := data.Host.HostPass
 	data.Node.Server = servercfg.GetServer()
 	if !logic.HostExists(&data.Host) {
 		logic.CheckHostPorts(&data.Host)
@@ -606,18 +604,6 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 			return
 		}
-	} else {
-		// Create client for this host in Mq
-		if err := mq.CreateMqClient(&mq.MqClient{
-			ID:       data.Host.ID.String(),
-			Text:     data.Host.Name,
-			Password: hostPassword,
-			Networks: []string{networkName},
-		}); err != nil {
-			logger.Log(0, fmt.Sprintf("failed to create DynSec client: %v", err.Error()))
-			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-			return
-		}
 	}
 
 	err = logic.AssociateNodeToHost(&data.Node, &data.Host)

+ 0 - 3
controllers/node_test.go

@@ -21,7 +21,6 @@ func TestCreateEgressGateway(t *testing.T) {
 	var gateway models.EgressGatewayRequest
 	gateway.Ranges = []string{"10.100.100.0/24"}
 	gateway.NetID = "skynet"
-	database.InitializeDatabase()
 	deleteAllNetworks()
 	createNet()
 	t.Run("NoNodes", func(t *testing.T) {
@@ -78,7 +77,6 @@ func TestCreateEgressGateway(t *testing.T) {
 }
 func TestDeleteEgressGateway(t *testing.T) {
 	var gateway models.EgressGatewayRequest
-	database.InitializeDatabase()
 	deleteAllNetworks()
 	createNet()
 	testnode := createTestNode()
@@ -110,7 +108,6 @@ func TestDeleteEgressGateway(t *testing.T) {
 }
 
 func TestGetNetworkNodes(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllNetworks()
 	createNet()
 	t.Run("BadNet", func(t *testing.T) {

+ 12 - 0
controllers/relay.go

@@ -123,12 +123,24 @@ func createHostRelay(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	if err := mq.HostUpdate(&models.HostUpdate{
+		Action: models.UpdateHost,
+		Host:   *relayHost,
+	}); err != nil {
+		logger.Log(0, "failed to send host update: ", relayHost.ID.String(), err.Error())
+	}
 	logger.Log(1, r.Header.Get("user"), "created relay on host", relay.HostID)
 	go func(relayHostID string) {
 		relatedhosts := logic.GetRelatedHosts(relayHostID)
 		for _, relatedHost := range relatedhosts {
 			relatedHost.ProxyEnabled = true
 			logic.UpsertHost(&relatedHost)
+			if err := mq.HostUpdate(&models.HostUpdate{
+				Action: models.UpdateHost,
+				Host:   relatedHost,
+			}); err != nil {
+				logger.Log(0, "failed to send host update: ", relatedHost.ID.String(), err.Error())
+			}
 		}
 		if err := mq.PublishPeerUpdate(); err != nil {
 			logger.Log(0, "fail to publish peer update: ", err.Error())

+ 31 - 0
controllers/server.go

@@ -6,8 +6,10 @@ import (
 	"strings"
 
 	"github.com/gorilla/mux"
+	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/servercfg"
 )
 
@@ -19,6 +21,35 @@ func serverHandlers(r *mux.Router) {
 	}))
 	r.HandleFunc("/api/server/getconfig", allowUsers(http.HandlerFunc(getConfig))).Methods(http.MethodGet)
 	r.HandleFunc("/api/server/getserverinfo", authorize(true, false, "node", http.HandlerFunc(getServerInfo))).Methods(http.MethodGet)
+	r.HandleFunc("/api/server/status", http.HandlerFunc(getStatus)).Methods(http.MethodGet)
+}
+
+// swagger:route GET /api/server/status server getStatus
+//
+// Get the server configuration.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: serverConfigResponse
+func getStatus(w http.ResponseWriter, r *http.Request) {
+	// TODO
+	// - check health of broker
+	type status struct {
+		DB     bool `json:"db_connected"`
+		Broker bool `json:"broker_connected"`
+	}
+
+	currentServerStatus := status{
+		DB:     database.IsConnected(),
+		Broker: mq.IsConnected(),
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(&currentServerStatus)
 }
 
 // allowUsers - allow all authenticated (valid) users - only used by getConfig, may be able to remove during refactor

+ 1 - 11
controllers/user_test.go

@@ -3,7 +3,6 @@ package controller
 import (
 	"testing"
 
-	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/stretchr/testify/assert"
@@ -18,7 +17,6 @@ func deleteAllUsers() {
 
 func TestHasAdmin(t *testing.T) {
 	//delete all current users
-	database.InitializeDatabase()
 	users, _ := logic.GetUsers()
 	for _, user := range users {
 		success, err := logic.DeleteUser(user.UserName)
@@ -48,7 +46,7 @@ func TestHasAdmin(t *testing.T) {
 	})
 	t.Run("multiple admins", func(t *testing.T) {
 		var user = models.User{"admin1", "password", nil, true, nil}
-		 err := logic.CreateUser(&user)
+		err := logic.CreateUser(&user)
 		assert.Nil(t, err)
 		found, err := logic.HasAdmin()
 		assert.Nil(t, err)
@@ -57,7 +55,6 @@ func TestHasAdmin(t *testing.T) {
 }
 
 func TestCreateUser(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllUsers()
 	user := models.User{"admin", "password", nil, true, nil}
 	t.Run("NoUser", func(t *testing.T) {
@@ -72,7 +69,6 @@ func TestCreateUser(t *testing.T) {
 }
 
 func TestCreateAdmin(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllUsers()
 	var user models.User
 	t.Run("NoAdmin", func(t *testing.T) {
@@ -90,7 +86,6 @@ func TestCreateAdmin(t *testing.T) {
 }
 
 func TestDeleteUser(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllUsers()
 	t.Run("NonExistent User", func(t *testing.T) {
 		deleted, err := logic.DeleteUser("admin")
@@ -107,7 +102,6 @@ func TestDeleteUser(t *testing.T) {
 }
 
 func TestValidateUser(t *testing.T) {
-	database.InitializeDatabase()
 	var user models.User
 	t.Run("Valid Create", func(t *testing.T) {
 		user.UserName = "admin"
@@ -155,7 +149,6 @@ func TestValidateUser(t *testing.T) {
 }
 
 func TestGetUser(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllUsers()
 	t.Run("NonExistantUser", func(t *testing.T) {
 		admin, err := logic.GetUser("admin")
@@ -172,7 +165,6 @@ func TestGetUser(t *testing.T) {
 }
 
 func TestGetUsers(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllUsers()
 	t.Run("NonExistantUser", func(t *testing.T) {
 		admin, err := logic.GetUsers()
@@ -203,7 +195,6 @@ func TestGetUsers(t *testing.T) {
 }
 
 func TestUpdateUser(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllUsers()
 	user := models.User{"admin", "password", nil, true, nil}
 	newuser := models.User{"hello", "world", []string{"wirecat, netmaker"}, true, []string{}}
@@ -246,7 +237,6 @@ func TestUpdateUser(t *testing.T) {
 // }
 
 func TestVerifyAuthRequest(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllUsers()
 	var authRequest models.UserAuthParams
 	t.Run("EmptyUserName", func(t *testing.T) {

+ 20 - 0
database/database.go

@@ -4,6 +4,7 @@ import (
 	"crypto/rand"
 	"encoding/json"
 	"errors"
+	"sync"
 	"time"
 
 	"github.com/google/uuid"
@@ -80,8 +81,12 @@ const (
 	FETCH_ALL = "fetchall"
 	// CLOSE_DB - graceful close of db const
 	CLOSE_DB = "closedb"
+	// isconnected
+	isConnected = "isconnected"
 )
 
+var dbMutex sync.RWMutex
+
 func getCurrentDB() map[string]interface{} {
 	switch servercfg.GetDB() {
 	case "rqlite":
@@ -148,6 +153,8 @@ func IsJSONString(value string) bool {
 
 // Insert - inserts object into db
 func Insert(key string, value string, tableName string) error {
+	dbMutex.Lock()
+	defer dbMutex.Unlock()
 	if key != "" && value != "" && IsJSONString(value) {
 		return getCurrentDB()[INSERT].(func(string, string, string) error)(key, value, tableName)
 	} else {
@@ -157,6 +164,8 @@ func Insert(key string, value string, tableName string) error {
 
 // InsertPeer - inserts peer into db
 func InsertPeer(key string, value string) error {
+	dbMutex.Lock()
+	defer dbMutex.Unlock()
 	if key != "" && value != "" && IsJSONString(value) {
 		return getCurrentDB()[INSERT_PEER].(func(string, string) error)(key, value)
 	} else {
@@ -166,11 +175,15 @@ func InsertPeer(key string, value string) error {
 
 // DeleteRecord - deletes a record from db
 func DeleteRecord(tableName string, key string) error {
+	dbMutex.Lock()
+	defer dbMutex.Unlock()
 	return getCurrentDB()[DELETE].(func(string, string) error)(tableName, key)
 }
 
 // DeleteAllRecords - removes a table and remakes
 func DeleteAllRecords(tableName string) error {
+	dbMutex.Lock()
+	defer dbMutex.Unlock()
 	err := getCurrentDB()[DELETE_ALL].(func(string) error)(tableName)
 	if err != nil {
 		return err
@@ -196,6 +209,8 @@ func FetchRecord(tableName string, key string) (string, error) {
 
 // FetchRecords - fetches all records in given table
 func FetchRecords(tableName string) (map[string]string, error) {
+	dbMutex.RLock()
+	defer dbMutex.RUnlock()
 	return getCurrentDB()[FETCH_ALL].(func(string) (map[string]string, error))(tableName)
 }
 
@@ -241,3 +256,8 @@ func initializeUUID() error {
 func CloseDB() {
 	getCurrentDB()[CLOSE_DB].(func())()
 }
+
+// IsConnected - tell if the database is connected or not
+func IsConnected() bool {
+	return getCurrentDB()[isConnected].(func() bool)()
+}

+ 6 - 0
database/postgres.go

@@ -22,6 +22,7 @@ var PG_FUNCTIONS = map[string]interface{}{
 	DELETE_ALL:   pgDeleteAllRecords,
 	FETCH_ALL:    pgFetchRecords,
 	CLOSE_DB:     pgCloseDB,
+	isConnected:  pgIsConnected,
 }
 
 func getPGConnString() string {
@@ -135,3 +136,8 @@ func pgFetchRecords(tableName string) (map[string]string, error) {
 func pgCloseDB() {
 	PGDB.Close()
 }
+
+func pgIsConnected() bool {
+	stats := PGDB.Stats()
+	return stats.OpenConnections > 0
+}

+ 6 - 0
database/rqlite.go

@@ -20,6 +20,7 @@ var RQLITE_FUNCTIONS = map[string]interface{}{
 	DELETE_ALL:   rqliteDeleteAllRecords,
 	FETCH_ALL:    rqliteFetchRecords,
 	CLOSE_DB:     rqliteCloseDB,
+	isConnected:  rqliteConnected,
 }
 
 func initRqliteDatabase() error {
@@ -104,3 +105,8 @@ func rqliteFetchRecords(tableName string) (map[string]string, error) {
 func rqliteCloseDB() {
 	RQliteDatabase.Close()
 }
+
+func rqliteConnected() bool {
+	leader, err := RQliteDatabase.Leader()
+	return err == nil && len(leader) > 0
+}

+ 6 - 0
database/sqlite.go

@@ -25,6 +25,7 @@ var SQLITE_FUNCTIONS = map[string]interface{}{
 	DELETE_ALL:   sqliteDeleteAllRecords,
 	FETCH_ALL:    sqliteFetchRecords,
 	CLOSE_DB:     sqliteCloseDB,
+	isConnected:  sqliteConnected,
 }
 
 func initSqliteDB() error {
@@ -135,3 +136,8 @@ func sqliteFetchRecords(tableName string) (map[string]string, error) {
 func sqliteCloseDB() {
 	SqliteDB.Close()
 }
+
+func sqliteConnected() bool {
+	stats := SqliteDB.Stats()
+	return stats.OpenConnections > 0
+}

+ 1 - 2
docker/mosquitto.conf

@@ -7,5 +7,4 @@ listener 1883
 protocol websockets
 allow_anonymous false
 
-plugin /usr/lib/mosquitto_dynamic_security.so
-plugin_opt_config_file /mosquitto/data/dynamic-security.json
+password_file /mosquitto/password.txt

+ 5 - 10
docker/wait.sh

@@ -1,18 +1,13 @@
 #!/bin/ash
 
-wait_for_netmaker() {
-  echo "SERVER: ${NETMAKER_SERVER_HOST}"
-  until curl --output /dev/null --silent --fail --head \
-    --location "${NETMAKER_SERVER_HOST}/api/server/health"; do
-    echo "Waiting for netmaker server to startup"
-    sleep 1
-  done
+encrypt_password() {
+  echo "${MQ_USERNAME}:${MQ_PASSWORD}" > /mosquitto/password.txt
+  mosquitto_passwd -U /mosquitto/password.txt
 }
 
 main(){
- # wait for netmaker to startup
- apk add curl
- wait_for_netmaker
+
+ encrypt_password
  echo "Starting MQ..."
  # Run the main container command.
  /docker-entrypoint.sh

+ 22 - 8
functions/helpers_test.go

@@ -1,10 +1,12 @@
 package functions
 
 import (
+	"context"
 	"encoding/json"
 	"testing"
 
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 )
@@ -19,11 +21,27 @@ var (
 	}
 )
 
+func TestMain(m *testing.M) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	logic.CreateAdmin(&models.User{
+		UserName: "admin",
+		Password: "password",
+		IsAdmin:  true,
+		Networks: []string{},
+		Groups:   []string{},
+	})
+	peerUpdate := make(chan *models.Node)
+	go logic.ManageZombies(context.Background(), peerUpdate)
+	go func() {
+		for update := range peerUpdate {
+			//do nothing
+			logger.Log(3, "received node update", update.Action)
+		}
+	}()
+}
+
 func TestNetworkExists(t *testing.T) {
-	err := database.InitializeDatabase()
-	if err != nil {
-		t.Fatalf("error initilizing database: %s", err)
-	}
 	database.DeleteRecord(database.NETWORKS_TABLE_NAME, testNetwork.NetID)
 	defer database.CloseDB()
 	exists, err := logic.NetworkExists(testNetwork.NetID)
@@ -53,10 +71,6 @@ func TestNetworkExists(t *testing.T) {
 }
 
 func TestGetAllExtClients(t *testing.T) {
-	err := database.InitializeDatabase()
-	if err != nil {
-		t.Fatalf("error initilizing database: %s", err)
-	}
 	defer database.CloseDB()
 	database.DeleteRecord(database.EXT_CLIENT_TABLE_NAME, testExternalClient.ClientID)
 

+ 8 - 9
go.mod

@@ -4,7 +4,7 @@ go 1.19
 
 require (
 	github.com/eclipse/paho.mqtt.golang v1.4.2
-	github.com/go-playground/validator/v10 v10.11.1
+	github.com/go-playground/validator/v10 v10.11.2
 	github.com/golang-jwt/jwt/v4 v4.4.3
 	github.com/google/uuid v1.3.0
 	github.com/gorilla/handlers v1.5.1
@@ -15,11 +15,11 @@ require (
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/stretchr/testify v1.8.1
 	github.com/txn2/txeh v1.3.0
-	golang.org/x/crypto v0.3.0
-	golang.org/x/net v0.4.0 // indirect
-	golang.org/x/oauth2 v0.3.0
-	golang.org/x/sys v0.3.0 // indirect
-	golang.org/x/text v0.5.0 // indirect
+	golang.org/x/crypto v0.6.0
+	golang.org/x/net v0.6.0 // indirect
+	golang.org/x/oauth2 v0.4.0
+	golang.org/x/sys v0.5.0 // indirect
+	golang.org/x/text v0.7.0 // indirect
 	golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	google.golang.org/protobuf v1.28.1 // indirect
@@ -29,7 +29,6 @@ require (
 require (
 	filippo.io/edwards25519 v1.0.0
 	github.com/c-robinson/iplib v1.0.6
-	github.com/go-ping/ping v1.1.0
 	github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0
 )
 
@@ -60,8 +59,8 @@ require (
 	cloud.google.com/go/compute v1.12.1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/felixge/httpsnoop v1.0.3 // indirect
-	github.com/go-playground/locales v0.14.0 // indirect
-	github.com/go-playground/universal-translator v0.18.0 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/google/go-cmp v0.5.9 // indirect
 	github.com/hashicorp/go-version v1.6.0

+ 18 - 32
go.sum

@@ -17,7 +17,6 @@ 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=
@@ -29,16 +28,13 @@ github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
 github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
-github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
-github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
-github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
-github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
-github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
-github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
-github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
-github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
-github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
-github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
+github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
 github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
 github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -52,7 +48,6 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8
 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
@@ -73,13 +68,10 @@ github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
 github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
 github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 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=
@@ -106,7 +98,6 @@ 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=
@@ -116,9 +107,7 @@ 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=
@@ -160,10 +149,9 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
-golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
+golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -172,17 +160,18 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
 golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
+golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
+golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
+golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -193,10 +182,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -205,8 +192,9 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
@@ -215,8 +203,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
 golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -240,11 +229,8 @@ 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/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-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.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gortc.io/stun v1.23.0 h1:CpRQFjakCZMwVKTwInKbcCzlBklj62LGzD3NPdFyGrE=

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

@@ -16,7 +16,7 @@ spec:
       hostNetwork: true
       containers:
       - name: netclient
-        image: gravitl/netclient:v0.17.1
+        image: gravitl/netclient:v0.18.0
         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.17.1
+        image: gravitl/netclient:v0.18.0
         env:
         - name: TOKEN
           value: "TOKEN_VALUE"

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

@@ -81,7 +81,7 @@ spec:
           value: "Kubernetes"
         - name: VERBOSITY
           value: "3"
-        image: gravitl/netmaker:v0.17.1
+        image: gravitl/netmaker:v0.18.0
         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.17.1
+        image: gravitl/netmaker-ui:v0.18.0
         ports:
         - containerPort: 443
         env:

+ 22 - 1
logic/host_test.go

@@ -1,17 +1,38 @@
 package logic
 
 import (
+	"context"
 	"net"
 	"testing"
 
 	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/matryer/is"
 )
 
-func TestCheckPorts(t *testing.T) {
+func TestMain(m *testing.M) {
 	database.InitializeDatabase()
+	defer database.CloseDB()
+	CreateAdmin(&models.User{
+		UserName: "admin",
+		Password: "password",
+		IsAdmin:  true,
+		Networks: []string{},
+		Groups:   []string{},
+	})
+	peerUpdate := make(chan *models.Node)
+	go ManageZombies(context.Background(), peerUpdate)
+	go func() {
+		for update := range peerUpdate {
+			//do nothing
+			logger.Log(3, "received node update", update.Action)
+		}
+	}()
+}
+
+func TestCheckPorts(t *testing.T) {
 	h := models.Host{
 		ID:              uuid.New(),
 		EndpointIP:      net.ParseIP("192.168.1.1"),

+ 2 - 15
logic/hosts.go

@@ -96,6 +96,7 @@ func CreateHost(h *models.Host) error {
 		return err
 	}
 	h.HostPass = string(hash)
+	checkForZombieHosts(h)
 	return UpsertHost(h)
 }
 
@@ -216,6 +217,7 @@ func UpdateHostNetwork(h *models.Host, network string, add bool) (*models.Node,
 		newNode := models.Node{}
 		newNode.Server = servercfg.GetServer()
 		newNode.Network = network
+		newNode.HostID = h.ID
 		if err := AssociateNodeToHost(&newNode, h); err != nil {
 			return nil, err
 		}
@@ -308,21 +310,6 @@ func GetDefaultHosts() []models.Host {
 	return defaultHostList[:]
 }
 
-// AddDefaultHostsToNetwork - adds a node to network for every default host on Netmaker server
-func AddDefaultHostsToNetwork(network, server string) error {
-	// add default hosts to network
-	defaultHosts := GetDefaultHosts()
-	for i := range defaultHosts {
-		newNode := models.Node{}
-		newNode.Network = network
-		newNode.Server = server
-		if err := AssociateNodeToHost(&newNode, &defaultHosts[i]); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
 // GetHostNetworks - fetches all the networks
 func GetHostNetworks(hostID string) []string {
 	currHost, err := GetHost(hostID)

+ 1 - 1
logic/nodes.go

@@ -534,7 +534,7 @@ func createNode(node *models.Node) error {
 	if err != nil {
 		return err
 	}
-	CheckZombies(node, host.MacAddress)
+	CheckZombies(node)
 
 	nodebytes, err := json.Marshal(&node)
 	if err != nil {

+ 34 - 23
logic/peers.go

@@ -201,7 +201,7 @@ func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error
 	if host.IsRelayed {
 		relayHost, err := GetHost(host.RelayedBy)
 		if err == nil {
-			relayEndpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, getPeerListenPort(relayHost)))
+			relayEndpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, GetPeerListenPort(relayHost)))
 			if err != nil {
 				logger.Log(1, "failed to resolve relay node endpoint: ", err.Error())
 			}
@@ -219,7 +219,7 @@ func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error
 			relayedHost := relayedHost
 			payload, err := GetPeerUpdateForHost(&relayedHost)
 			if err == nil {
-				relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, getPeerListenPort(&relayedHost)))
+				relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, GetPeerListenPort(&relayedHost)))
 				if udpErr == nil {
 					relayPeersMap[relayedHost.PublicKey.String()] = models.RelayedConf{
 						RelayedPeerEndpoint: relayedEndpoint,
@@ -259,14 +259,14 @@ func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error
 			if currPeerConf, found = peerConfMap[peerHost.PublicKey.String()]; !found {
 				currPeerConf = models.PeerConf{
 					Proxy:            peerHost.ProxyEnabled,
-					PublicListenPort: int32(getPeerListenPort(peerHost)),
+					PublicListenPort: int32(GetPeerListenPort(peerHost)),
 				}
 			}
 
 			if peerHost.IsRelayed && peerHost.RelayedBy != host.ID.String() {
 				relayHost, err := GetHost(peerHost.RelayedBy)
 				if err == nil {
-					relayTo, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, getPeerListenPort(peerHost)))
+					relayTo, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, GetPeerListenPort(relayHost)))
 					if err == nil {
 						currPeerConf.IsRelayed = true
 						currPeerConf.RelayedTo = relayTo
@@ -370,7 +370,7 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 			}
 			peerConfig.Endpoint = &net.UDPAddr{
 				IP:   peerHost.EndpointIP,
-				Port: getPeerListenPort(peerHost),
+				Port: GetPeerListenPort(peerHost),
 			}
 
 			if uselocal {
@@ -390,7 +390,21 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 			}
 			peerConfig.AllowedIPs = allowedips
 			if node.IsIngressGateway || node.IsEgressGateway {
-
+				if peer.IsIngressGateway {
+					_, extPeerIDAndAddrs, err := getExtPeers(&peer)
+					if err == nil {
+						for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
+							nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
+								PeerAddr: net.IPNet{
+									IP:   net.ParseIP(extPeerIdAndAddr.Address),
+									Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
+								},
+								PeerKey: extPeerIdAndAddr.ID,
+								Allow:   true,
+							}
+						}
+					}
+				}
 				nodePeerMap[peerHost.PublicKey.String()] = models.PeerRouteInfo{
 					PeerAddr: net.IPNet{
 						IP:   net.ParseIP(peer.PrimaryAddress()),
@@ -429,6 +443,16 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 		if node.IsIngressGateway {
 			extPeers, extPeerIDAndAddrs, err = getExtPeers(&node)
 			if err == nil {
+				for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
+					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 {
 					hostPeerUpdate.PeerIDs[extPeerIdAndAddr.ID] = make(map[string]models.IDandAddr)
@@ -459,19 +483,6 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 			}
 		}
 		if node.IsEgressGateway {
-			if node.IsIngressGateway {
-				for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
-					nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
-						PeerAddr: net.IPNet{
-							IP:   net.ParseIP(extPeerIdAndAddr.Address),
-							Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
-						},
-						PeerKey: extPeerIdAndAddr.ID,
-						Allow:   true,
-					}
-				}
-
-			}
 			hostPeerUpdate.EgressInfo[node.ID.String()] = models.EgressInfo{
 				EgressID: node.ID.String(),
 				Network:  node.PrimaryNetworkRange(),
@@ -488,7 +499,7 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 	return hostPeerUpdate, nil
 }
 
-func getPeerListenPort(host *models.Host) int {
+func GetPeerListenPort(host *models.Host) int {
 	peerPort := host.ListenPort
 	if host.ProxyEnabled {
 		if host.PublicListenPort != 0 {
@@ -557,7 +568,7 @@ func GetPeerUpdate(node *models.Node, host *models.Host) (models.PeerUpdate, err
 			Port: peerHost.ListenPort,
 		}
 		if peerHost.ProxyEnabled {
-			peerConfig.Endpoint.Port = getPeerListenPort(peerHost)
+			peerConfig.Endpoint.Port = GetPeerListenPort(peerHost)
 		}
 		if uselocal {
 			peerConfig.Endpoint.IP = peer.LocalAddress.IP
@@ -690,7 +701,7 @@ func GetPeerUpdateLegacy(node *models.Node) (models.PeerUpdate, error) {
 			if node.LocalAddress.String() != peer.LocalAddress.String() && peer.LocalAddress.IP != nil {
 				peerHost.EndpointIP = peer.LocalAddress.IP
 				if peerHost.ListenPort != 0 {
-					peerHost.ListenPort = getPeerListenPort(peerHost)
+					peerHost.ListenPort = GetPeerListenPort(peerHost)
 				}
 			} else {
 				continue
@@ -723,7 +734,7 @@ func GetPeerUpdateLegacy(node *models.Node) (models.PeerUpdate, error) {
 			// or, if port is for some reason zero use the LocalListenPort
 			// but only do this if LocalListenPort is not zero
 			if ((!setUDPPort) || peerHost.ListenPort == 0) && peerHost.ListenPort != 0 {
-				peerHost.ListenPort = getPeerListenPort(peerHost)
+				peerHost.ListenPort = GetPeerListenPort(peerHost)
 			}
 
 			endpoint := peerHost.EndpointIP.String() + ":" + strconv.FormatInt(int64(peerHost.ListenPort), 10)

+ 5 - 1
logic/pro/networkuser_test.go

@@ -10,8 +10,12 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestNetworkUserLogic(t *testing.T) {
+func TestMain(m *testing.M) {
 	database.InitializeDatabase()
+	defer database.CloseDB()
+}
+
+func TestNetworkUserLogic(t *testing.T) {
 	networkUser := promodels.NetworkUser{
 		ID: "helloworld",
 	}

+ 0 - 2
logic/pro/usergroups_test.go

@@ -3,13 +3,11 @@ package pro
 import (
 	"testing"
 
-	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/models/promodels"
 	"github.com/stretchr/testify/assert"
 )
 
 func TestUserGroupLogic(t *testing.T) {
-	database.InitializeDatabase()
 
 	t.Run("User Groups initialized successfully", func(t *testing.T) {
 		err := InitializeGroups()

+ 57 - 23
logic/zombie.go

@@ -2,7 +2,6 @@ package logic
 
 import (
 	"context"
-	"net"
 	"time"
 
 	"github.com/google/uuid"
@@ -18,15 +17,16 @@ const (
 )
 
 var (
-	zombies      []uuid.UUID
-	removeZombie chan uuid.UUID = make(chan (uuid.UUID), 10)
-	newZombie    chan uuid.UUID = make(chan (uuid.UUID), 10)
+	zombies       []uuid.UUID
+	hostZombies   []uuid.UUID
+	newZombie     chan uuid.UUID = make(chan (uuid.UUID), 10)
+	newHostZombie chan uuid.UUID = make(chan (uuid.UUID), 10)
 )
 
-// CheckZombies - checks if new node has same macaddress as existing node
+// CheckZombies - checks if new node has same hostid as existing node
 // if so, existing node is added to zombie node quarantine list
 // also cleans up nodes past their expiration date
-func CheckZombies(newnode *models.Node, mac net.HardwareAddr) {
+func CheckZombies(newnode *models.Node) {
 	nodes, err := GetNetworkNodes(newnode.Network)
 	if err != nil {
 		logger.Log(1, "Failed to retrieve network nodes", newnode.Network, err.Error())
@@ -44,6 +44,35 @@ func CheckZombies(newnode *models.Node, mac net.HardwareAddr) {
 	}
 }
 
+// checkForZombieHosts - checks if new host has the same macAddress as an existing host
+// if true, existing host is added to host zombie collection
+func checkForZombieHosts(h *models.Host) {
+	hosts, err := GetAllHosts()
+	if err != nil {
+		logger.Log(3, "errror retrieving all hosts", err.Error())
+	}
+	for _, existing := range hosts {
+		if existing.ID == h.ID {
+			//probably an unnecessary check as new host should not be in database yet, but just in case
+			//skip self
+			continue
+		}
+		if existing.MacAddress.String() == h.MacAddress.String() {
+			//add to hostZombies
+			newHostZombie <- existing.ID
+			//add all nodes belonging to host to zombile list
+			for _, node := range existing.Nodes {
+				id, err := uuid.Parse(node)
+				if err != nil {
+					logger.Log(3, "error parsing uuid from host.Nodes", err.Error())
+					continue
+				}
+				newHostZombie <- id
+			}
+		}
+	}
+}
+
 // ManageZombies - goroutine which adds/removes/deletes nodes from the zombie node quarantine list
 func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 	logger.Log(2, "Zombie management started")
@@ -51,24 +80,12 @@ func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 	for {
 		select {
 		case <-ctx.Done():
+			close(peerUpdate)
 			return
 		case id := <-newZombie:
-			logger.Log(1, "adding", id.String(), "to zombie quaratine list")
 			zombies = append(zombies, id)
-		case id := <-removeZombie:
-			found := false
-			if len(zombies) > 0 {
-				for i := len(zombies) - 1; i >= 0; i-- {
-					if zombies[i] == id {
-						logger.Log(1, "removing zombie from quaratine list", zombies[i].String())
-						zombies = append(zombies[:i], zombies[i+1:]...)
-						found = true
-					}
-				}
-			}
-			if !found {
-				logger.Log(3, "no zombies found")
-			}
+		case id := <-newHostZombie:
+			hostZombies = append(hostZombies, id)
 		case <-time.After(time.Second * ZOMBIE_TIMEOUT):
 			logger.Log(3, "checking for zombie nodes")
 			if len(zombies) > 0 {
@@ -92,6 +109,23 @@ func ManageZombies(ctx context.Context, peerUpdate chan *models.Node) {
 					}
 				}
 			}
+			if len(hostZombies) > 0 {
+				logger.Log(3, "checking host zombies")
+				for i := len(hostZombies) - 1; i >= 0; i-- {
+					host, err := GetHost(hostZombies[i].String())
+					if err != nil {
+						logger.Log(1, "error retrieving zombie host", err.Error())
+						logger.Log(1, "deleting ", host.ID.String(), " from zombie list")
+						zombies = append(zombies[:i], zombies[i+1:]...)
+						continue
+					}
+					if len(host.Nodes) == 0 {
+						if err := RemoveHost(host); err != nil {
+							logger.Log(0, "error deleting zombie host", host.ID.String(), err.Error())
+						}
+					}
+				}
+			}
 		}
 	}
 }
@@ -115,10 +149,10 @@ func InitializeZombies() {
 			}
 			if node.HostID == othernode.HostID {
 				if node.LastCheckIn.After(othernode.LastCheckIn) {
-					zombies = append(zombies, othernode.ID)
+					newZombie <- othernode.ID
 					logger.Log(1, "adding", othernode.ID.String(), "to zombie list")
 				} else {
-					zombies = append(zombies, node.ID)
+					newZombie <- node.ID
 					logger.Log(1, "adding", node.ID.String(), "to zombie list")
 				}
 			}

+ 0 - 6
main.go

@@ -118,11 +118,6 @@ func startControllers() {
 			logger.Log(0, "error occurred initializing DNS: ", err.Error())
 		}
 	}
-	if servercfg.IsMessageQueueBackend() {
-		if err := mq.Configure(); err != nil {
-			logger.FatalLog("failed to configure MQ: ", err.Error())
-		}
-	}
 
 	//Run Rest Server
 	if servercfg.IsRestBackend() {
@@ -172,7 +167,6 @@ func runMessageQueue(wg *sync.WaitGroup) {
 	defer wg.Done()
 	brokerHost, secure := servercfg.GetMessageQueueEndpoint()
 	logger.Log(0, "connecting to mq broker at", brokerHost, "with TLS?", fmt.Sprintf("%v", secure))
-	mq.SetUpAdminClient()
 	mq.SetupMQTT()
 	ctx, cancel := context.WithCancel(context.Background())
 	go mq.Keepalive(ctx)

+ 0 - 28
metrics/metrics.go

@@ -1,12 +1,9 @@
 package metrics
 
 import (
-	"fmt"
 	"sync"
 	"time"
 
-	"github.com/go-ping/ping"
-	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 )
 
@@ -84,28 +81,3 @@ func ResetMetricForNode(server, peerKey, peerID string) {
 
 // MetricCollectionInterval - collection interval for metrics
 const MetricCollectionInterval = time.Second * 25
-
-// PeerConnectionStatus - get peer connection status by pinging
-func PeerConnectionStatus(address string) (connected bool) {
-	fmt.Println("PINGER ADDR: ", address)
-	pinger, err := ping.NewPinger(address)
-	if err != nil {
-		logger.Log(0, "could not initiliaze ping peer address", address, err.Error())
-		connected = false
-	} else {
-		pinger.Timeout = time.Second * 2
-		err = pinger.Run()
-		if err != nil {
-			logger.Log(0, "failed to ping on peer address", address, err.Error())
-			return false
-		} else {
-			pingStats := pinger.Statistics()
-			if pingStats.PacketsRecv > 0 {
-				connected = true
-				return
-			}
-		}
-	}
-
-	return
-}

+ 9 - 2
models/api_host.go

@@ -1,6 +1,9 @@
 package models
 
-import "net"
+import (
+	"net"
+	"strings"
+)
 
 // ApiHost - the host struct for API usage
 type ApiHost struct {
@@ -76,7 +79,11 @@ func (a *ApiHost) ConvertAPIHostToNMHost(currentHost *Host) *Host {
 	h.ID = currentHost.ID
 	h.HostPass = currentHost.HostPass
 	h.DaemonInstalled = currentHost.DaemonInstalled
-	h.EndpointIP = net.ParseIP(a.EndpointIP)
+	if len(a.EndpointIP) == 0 || strings.Contains(a.EndpointIP, "nil") {
+		h.EndpointIP = currentHost.EndpointIP
+	} else {
+		h.EndpointIP = net.ParseIP(a.EndpointIP)
+	}
 	h.Debug = a.Debug
 	h.FirewallInUse = a.FirewallInUse
 	h.IPForwarding = currentHost.IPForwarding

+ 2 - 1
models/api_node.go

@@ -58,7 +58,8 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node {
 	convertedNode.Failover = a.Failover
 	convertedNode.IsEgressGateway = a.IsEgressGateway
 	convertedNode.IsIngressGateway = a.IsIngressGateway
-	convertedNode.EgressGatewayRanges = a.EgressGatewayRanges
+	// prevents user from changing ranges, must delete and recreate
+	convertedNode.EgressGatewayRanges = currentNode.EgressGatewayRanges
 	convertedNode.IngressGatewayRange = currentNode.IngressGatewayRange
 	convertedNode.IngressGatewayRange6 = currentNode.IngressGatewayRange6
 	convertedNode.DNSOn = a.DNSOn

+ 1 - 1
models/network_test.go

@@ -2,7 +2,7 @@ package models
 
 // moved from controllers need work
 //func TestUpdateNetwork(t *testing.T) {
-//	database.InitializeDatabase()
+//	initialize()
 //	createNet()
 //	network := getNet()
 //	t.Run("NetID", func(t *testing.T) {

+ 2 - 0
models/structs.go

@@ -229,6 +229,8 @@ type ServerConfig struct {
 	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"`

+ 0 - 214
mq/dynsec.go

@@ -1,214 +0,0 @@
-package mq
-
-import (
-	"crypto/sha512"
-	"encoding/base64"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"os"
-	"time"
-
-	mqtt "github.com/eclipse/paho.mqtt.golang"
-	"github.com/gravitl/netmaker/functions"
-	"github.com/gravitl/netmaker/logger"
-	"github.com/gravitl/netmaker/logic"
-	"github.com/gravitl/netmaker/netclient/ncutils"
-	"github.com/gravitl/netmaker/servercfg"
-	"golang.org/x/crypto/pbkdf2"
-)
-
-// mq client for admin
-var mqAdminClient mqtt.Client
-
-const (
-	// constant for client command
-	CreateClientCmd = "createClient"
-	// constant for disable command
-	DisableClientCmd = "disableClient"
-	// constant for delete client command
-	DeleteClientCmd = "deleteClient"
-	// constant for modify client command
-	ModifyClientCmd = "modifyClient"
-
-	// constant for create role command
-	CreateRoleCmd = "createRole"
-	// constant for delete role command
-	DeleteRoleCmd = "deleteRole"
-
-	// constant for admin user name
-	mqAdminUserName = "Netmaker-Admin"
-	// constant for server user name
-	mqNetmakerServerUserName = "Netmaker-Server"
-	// constant for exporter user name
-	mqExporterUserName = "Netmaker-Exporter"
-
-	// DynamicSecSubTopic - constant for dynamic security subscription topic
-	dynamicSecSubTopic = "$CONTROL/dynamic-security/#"
-	// DynamicSecPubTopic - constant for dynamic security subscription topic
-	dynamicSecPubTopic = "$CONTROL/dynamic-security/v1"
-)
-
-// struct for dynamic security file
-type dynJSON struct {
-	Clients    []client         `json:"clients"`
-	Roles      []role           `json:"roles"`
-	DefaultAcl defaultAccessAcl `json:"defaultACLAccess"`
-}
-
-// struct for client role
-type clientRole struct {
-	Rolename string `json:"rolename"`
-}
-
-// struct for MQ client
-type client struct {
-	Username   string       `json:"username"`
-	TextName   string       `json:"textName"`
-	Password   string       `json:"password"`
-	Salt       string       `json:"salt"`
-	Iterations int          `json:"iterations"`
-	Roles      []clientRole `json:"roles"`
-}
-
-// struct for MQ role
-type role struct {
-	Rolename string `json:"rolename"`
-	Acls     []Acl  `json:"acls"`
-}
-
-// struct for default acls
-type defaultAccessAcl struct {
-	PublishClientSend    bool `json:"publishClientSend"`
-	PublishClientReceive bool `json:"publishClientReceive"`
-	Subscribe            bool `json:"subscribe"`
-	Unsubscribe          bool `json:"unsubscribe"`
-}
-
-// MqDynSecGroup - struct for MQ client group
-type MqDynSecGroup struct {
-	Groupname string `json:"groupname"`
-	Priority  int    `json:"priority"`
-}
-
-// MqDynSecRole - struct for MQ client role
-type MqDynSecRole struct {
-	Rolename string `json:"rolename"`
-	Priority int    `json:"priority"`
-}
-
-// Acl - struct for MQ acls
-type Acl struct {
-	AclType  string `json:"acltype"`
-	Topic    string `json:"topic"`
-	Priority int    `json:"priority,omitempty"`
-	Allow    bool   `json:"allow"`
-}
-
-// MqDynSecCmd - struct for MQ dynamic security command
-type MqDynSecCmd struct {
-	Command         string          `json:"command"`
-	Username        string          `json:"username"`
-	Password        string          `json:"password"`
-	RoleName        string          `json:"rolename,omitempty"`
-	Acls            []Acl           `json:"acls,omitempty"`
-	Clientid        string          `json:"clientid"`
-	Textname        string          `json:"textname"`
-	Textdescription string          `json:"textdescription"`
-	Groups          []MqDynSecGroup `json:"groups"`
-	Roles           []MqDynSecRole  `json:"roles"`
-}
-
-// MqDynsecPayload - struct for dynamic security command payload
-type MqDynsecPayload struct {
-	Commands []MqDynSecCmd `json:"commands"`
-}
-
-// encodePasswordToPBKDF2 - encodes the given password with PBKDF2 hashing for MQ
-func encodePasswordToPBKDF2(password string, salt string, iterations int, keyLength int) string {
-	binaryEncoded := pbkdf2.Key([]byte(password), []byte(salt), iterations, keyLength, sha512.New)
-	return base64.StdEncoding.EncodeToString(binaryEncoded)
-}
-
-// Configure - configures the dynamic initial configuration for MQ
-func Configure() error {
-
-	logger.Log(0, "Configuring MQ...")
-	dynConfig := dynConfigInI
-	path := functions.GetNetmakerPath() + ncutils.GetSeparator() + dynamicSecurityFile
-
-	password := servercfg.GetMqAdminPassword()
-	if password == "" {
-		return errors.New("MQ admin password not provided")
-	}
-	if logic.CheckIfFileExists(path) {
-		data, err := os.ReadFile(path)
-		if err == nil {
-			var cfg dynJSON
-			err = json.Unmarshal(data, &cfg)
-			if err == nil {
-				logger.Log(0, "MQ config exists already, So Updating Existing Config...")
-				dynConfig = cfg
-			}
-		}
-	}
-	exporter := false
-	for i, cI := range dynConfig.Clients {
-		if cI.Username == mqAdminUserName || cI.Username == mqNetmakerServerUserName {
-			salt := logic.RandomString(12)
-			hashed := encodePasswordToPBKDF2(password, salt, 101, 64)
-			cI.Password = hashed
-			cI.Iterations = 101
-			cI.Salt = base64.StdEncoding.EncodeToString([]byte(salt))
-			dynConfig.Clients[i] = cI
-		} else if servercfg.Is_EE && cI.Username == mqExporterUserName {
-			exporter = true
-			exporterPassword := servercfg.GetLicenseKey()
-			salt := logic.RandomString(12)
-			hashed := encodePasswordToPBKDF2(exporterPassword, salt, 101, 64)
-			cI.Password = hashed
-			cI.Iterations = 101
-			cI.Salt = base64.StdEncoding.EncodeToString([]byte(salt))
-			dynConfig.Clients[i] = cI
-		}
-	}
-	if servercfg.Is_EE && !exporter {
-		exporterPassword := servercfg.GetLicenseKey()
-		salt := logic.RandomString(12)
-		hashed := encodePasswordToPBKDF2(exporterPassword, salt, 101, 64)
-		exporterMQClient.Password = hashed
-		exporterMQClient.Iterations = 101
-		exporterMQClient.Salt = base64.StdEncoding.EncodeToString([]byte(salt))
-		dynConfig.Clients = append(dynConfig.Clients, exporterMQClient)
-	}
-	data, err := json.MarshalIndent(dynConfig, "", " ")
-	if err != nil {
-		return err
-	}
-	return os.WriteFile(path, data, 0755)
-}
-
-// publishes the message to dynamic security topic
-func publishEventToDynSecTopic(payload MqDynsecPayload) error {
-
-	d, err := json.Marshal(payload)
-	if err != nil {
-		return err
-	}
-	var connecterr error
-	if token := mqAdminClient.Publish(dynamicSecPubTopic, 2, false, d); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
-		if token.Error() == nil {
-			connecterr = errors.New("connect timeout")
-		} else {
-			connecterr = token.Error()
-		}
-	}
-	return connecterr
-}
-
-// watchDynSecTopic - message handler for dynamic security responses
-func watchDynSecTopic(client mqtt.Client, msg mqtt.Message) {
-
-	logger.Log(1, fmt.Sprintf("----->WatchDynSecTopic Message: %+v", string(msg.Payload())))
-
-}

+ 0 - 47
mq/dynsec_clients.go

@@ -1,47 +0,0 @@
-package mq
-
-// MqClient - type for taking in an MQ client's data
-type MqClient struct {
-	ID       string
-	Text     string
-	Password string
-	Networks []string
-}
-
-// DeleteMqClient - removes a client from the DynSec system
-func DeleteMqClient(hostID string) error {
-
-	event := MqDynsecPayload{
-		Commands: []MqDynSecCmd{
-			{
-				Command:  DeleteClientCmd,
-				Username: hostID,
-			},
-		},
-	}
-	return publishEventToDynSecTopic(event)
-}
-
-// CreateMqClient - creates an MQ DynSec client
-func CreateMqClient(client *MqClient) error {
-
-	event := MqDynsecPayload{
-		Commands: []MqDynSecCmd{
-			{
-				Command:  CreateClientCmd,
-				Username: client.ID,
-				Password: client.Password,
-				Textname: client.Text,
-				Roles: []MqDynSecRole{
-					{
-						Rolename: genericRole,
-						Priority: -1,
-					},
-				},
-				Groups: make([]MqDynSecGroup, 0),
-			},
-		},
-	}
-
-	return publishEventToDynSecTopic(event)
-}

+ 0 - 187
mq/dynsec_helper.go

@@ -1,187 +0,0 @@
-package mq
-
-import (
-	"errors"
-	"time"
-
-	mqtt "github.com/eclipse/paho.mqtt.golang"
-	"github.com/gravitl/netmaker/servercfg"
-)
-
-const (
-	// constant for admin role
-	adminRole = "admin"
-	// constant for generic role
-	genericRole = "generic"
-
-	// const for dynamic security file
-	dynamicSecurityFile = "dynamic-security.json"
-)
-
-var (
-	// default configuration of dynamic security
-	dynConfigInI = dynJSON{
-		Clients: []client{
-			{
-				Username:   mqAdminUserName,
-				TextName:   "netmaker admin user",
-				Password:   "",
-				Salt:       "",
-				Iterations: 0,
-				Roles: []clientRole{
-					{
-						Rolename: adminRole,
-					},
-				},
-			},
-			{
-				Username:   mqNetmakerServerUserName,
-				TextName:   "netmaker server user",
-				Password:   "",
-				Salt:       "",
-				Iterations: 0,
-				Roles: []clientRole{
-					{
-						Rolename: genericRole,
-					},
-				},
-			},
-			exporterMQClient,
-		},
-		Roles: []role{
-			{
-				Rolename: adminRole,
-				Acls:     fetchAdminAcls(),
-			},
-			{
-				Rolename: genericRole,
-				Acls:     fetchGenericAcls(),
-			},
-		},
-		DefaultAcl: defaultAccessAcl{
-			PublishClientSend:    false,
-			PublishClientReceive: true,
-			Subscribe:            false,
-			Unsubscribe:          true,
-		},
-	}
-
-	exporterMQClient = client{
-		Username:   mqExporterUserName,
-		TextName:   "netmaker metrics exporter",
-		Password:   "",
-		Salt:       "",
-		Iterations: 101,
-		Roles: []clientRole{
-			{
-				Rolename: genericRole,
-			},
-		},
-	}
-)
-
-// GetAdminClient - fetches admin client of the MQ
-func GetAdminClient() (mqtt.Client, error) {
-	opts := mqtt.NewClientOptions()
-	setMqOptions(mqAdminUserName, servercfg.GetMqAdminPassword(), opts)
-	mqclient := mqtt.NewClient(opts)
-	var connecterr error
-	if token := mqclient.Connect(); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
-		if token.Error() == nil {
-			connecterr = errors.New("connect timeout")
-		} else {
-			connecterr = token.Error()
-		}
-	}
-	return mqclient, connecterr
-}
-
-// genericAcls - fetches generice role related acls
-func fetchGenericAcls() []Acl {
-	return []Acl{
-		{
-			AclType:  "publishClientSend",
-			Topic:    "#",
-			Priority: -1,
-			Allow:    true,
-		},
-		{
-			AclType:  "publishClientReceive",
-			Topic:    "#",
-			Priority: -1,
-			Allow:    true,
-		},
-		{
-			AclType:  "subscribePattern",
-			Topic:    "#",
-			Priority: -1,
-			Allow:    true,
-		},
-		{
-			AclType:  "unsubscribePattern",
-			Topic:    "#",
-			Priority: -1,
-			Allow:    true,
-		},
-	}
-}
-
-// fetchAdminAcls - fetches admin role related acls
-func fetchAdminAcls() []Acl {
-	return []Acl{
-		{
-			AclType:  "publishClientSend",
-			Topic:    "$CONTROL/dynamic-security/#",
-			Priority: -1,
-			Allow:    true,
-		},
-		{
-			AclType:  "publishClientReceive",
-			Topic:    "$CONTROL/dynamic-security/#",
-			Priority: -1,
-			Allow:    true,
-		},
-		{
-			AclType:  "subscribePattern",
-			Topic:    "$CONTROL/dynamic-security/#",
-			Priority: -1,
-			Allow:    true,
-		},
-		{
-			AclType:  "publishClientReceive",
-			Topic:    "$SYS/#",
-			Priority: -1,
-			Allow:    true,
-		},
-		{
-			AclType:  "subscribePattern",
-			Topic:    "$SYS/#",
-			Priority: -1,
-			Allow:    true,
-		},
-		{
-			AclType:  "publishClientReceive",
-			Topic:    "#",
-			Priority: -1,
-			Allow:    true,
-		},
-		{
-			AclType:  "subscribePattern",
-			Topic:    "#",
-			Priority: -1,
-			Allow:    true,
-		},
-		{
-			AclType:  "unsubscribePattern",
-			Topic:    "#",
-			Priority: -1,
-			Allow:    true,
-		},
-		{
-			AclType:  "publishClientSend",
-			Topic:    "#",
-			Priority: -1,
-			Allow:    true,
-		},
-	}
-}

+ 0 - 6
mq/handlers.go

@@ -168,12 +168,6 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 				logger.Log(0, "failed to pulish peer update: ", err.Error())
 			}
 		}
-		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)

+ 6 - 35
mq/mq.go

@@ -2,7 +2,6 @@ package mq
 
 import (
 	"context"
-	"fmt"
 	"time"
 
 	mqtt "github.com/eclipse/paho.mqtt.golang"
@@ -23,39 +22,6 @@ var peer_force_send = 0
 
 var mqclient mqtt.Client
 
-// SetUpAdminClient - sets up admin client for the MQ
-func SetUpAdminClient() {
-	opts := mqtt.NewClientOptions()
-	setMqOptions(mqAdminUserName, servercfg.GetMqAdminPassword(), opts)
-	mqAdminClient = mqtt.NewClient(opts)
-	opts.SetOnConnectHandler(func(client mqtt.Client) {
-		if token := client.Subscribe(dynamicSecSubTopic, 2, mqtt.MessageHandler(watchDynSecTopic)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
-			client.Disconnect(240)
-			logger.Log(0, fmt.Sprintf("Dynamic security client subscription failed: %v ", token.Error()))
-		}
-
-		opts.SetOrderMatters(true)
-		opts.SetResumeSubs(true)
-	})
-	tperiod := time.Now().Add(10 * time.Second)
-	for {
-		if token := mqAdminClient.Connect(); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
-			logger.Log(2, "Admin: unable to connect to broker, retrying ...")
-			if time.Now().After(tperiod) {
-				if token.Error() == nil {
-					logger.FatalLog("Admin: could not connect to broker, token timeout, exiting ...")
-				} else {
-					logger.FatalLog("Admin: could not connect to broker, exiting ...", token.Error().Error())
-				}
-			}
-		} else {
-			break
-		}
-		time.Sleep(2 * time.Second)
-	}
-
-}
-
 func setMqOptions(user, password string, opts *mqtt.ClientOptions) {
 	broker, _ := servercfg.GetMessageQueueEndpoint()
 	opts.AddBroker(broker)
@@ -73,7 +39,7 @@ func setMqOptions(user, password string, opts *mqtt.ClientOptions) {
 // SetupMQTT creates a connection to broker and return client
 func SetupMQTT() {
 	opts := mqtt.NewClientOptions()
-	setMqOptions(mqNetmakerServerUserName, servercfg.GetMqAdminPassword(), opts)
+	setMqOptions(servercfg.GetMqUserName(), servercfg.GetMqPassword(), opts)
 	opts.SetOnConnectHandler(func(client mqtt.Client) {
 		if token := client.Subscribe("ping/#", 2, mqtt.MessageHandler(Ping)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
 			client.Disconnect(240)
@@ -129,3 +95,8 @@ func Keepalive(ctx context.Context) {
 		}
 	}
 }
+
+// IsConnected - function for determining if the mqclient is connected or not
+func IsConnected() bool {
+	return mqclient != nil && mqclient.IsConnected()
+}

+ 113 - 32
scripts/nm-quick-interactive.sh

@@ -1,5 +1,51 @@
 #!/bin/bash
 
+# setup_netclient - installs netclient locally
+setup_netclient() {
+
+	# DEV_TEMP - Temporary instructions for testing
+	# wget https://fileserver.netmaker.org/testing/netclient
+	# chmod +x netclient
+	# ./netclient install
+
+	# RELEASE_REPLACE - Use this once release is ready
+	# if [ -f /etc/debian_version ]; then
+	#     curl -sL 'https://apt.netmaker.org/gpg.key' | sudo tee /etc/apt/trusted.gpg.d/netclient.asc
+	#     curl -sL 'https://apt.netmaker.org/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/netclient.list
+	#     sudo apt update
+	#     sudo apt install netclient
+	# elif [ -f /etc/centos-release ]; then
+	#     curl -sL 'https://rpm.netmaker.org/gpg.key' | sudo tee /tmp/gpg.key
+	#     curl -sL 'https://rpm.netmaker.org/netclient-repo' | sudo tee /etc/yum.repos.d/netclient.repo
+	#     sudo rpm --import /tmp/gpg.key
+	#     sudo dnf check-update
+	#     sudo dnf install netclient
+	# elif [ -f /etc/fedora-release ]; then
+	#     curl -sL 'https://rpm.netmaker.org/gpg.key' | sudo tee /tmp/gpg.key
+	#     curl -sL 'https://rpm.netmaker.org/netclient-repo' | sudo tee /etc/yum.repos.d/netclient.repo
+	#     sudo rpm --import /tmp/gpg.key
+	#     sudo dnf check-update
+	#     sudo dnf install netclient
+	# elif [ -f /etc/redhat-release ]; then
+	#     curl -sL 'https://rpm.netmaker.org/gpg.key' | sudo tee /tmp/gpg.key
+	#     curl -sL 'https://rpm.netmaker.org/netclient-repo' | sudo tee /etc/yum.repos.d/netclient.repo
+	#     sudo rpm --import /tmp/gpg.key
+	#     sudo dnf check-update(
+	#     sudo dnf install netclient
+	# elif [ -f /etc/arch-release ]; then
+	#     yay -S netclient
+	# else
+	# 	echo "OS not supported for automatic install"
+	#     exit 1
+	# fi
+
+	# if [ -z "${install_cmd}" ]; then
+	#         echo "OS unsupported for automatic dependency install"
+	# 	exit 1
+	# fi
+}
+
+
 cat << "EOF"
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -184,13 +230,11 @@ wait_seconds 3
 
 set -e
 
-NETMAKER_BASE_DOMAIN=nm.$(curl -s ifconfig.me | tr . -).nip.io
+NETMAKER_BASE_DOMAIN=nm.$(dig myip.opendns.com @resolver1.opendns.com +short | tr . -).nip.io
 COREDNS_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p')
-SERVER_PUBLIC_IP=$(curl -s ifconfig.me)
+SERVER_PUBLIC_IP=$(dig myip.opendns.com @resolver1.opendns.com +short)
 MASTER_KEY=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
-MQ_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
 DOMAIN_TYPE=""
-
 echo "-----------------------------------------------------"
 echo "Would you like to use your own domain for netmaker, or an auto-generated domain?"
 echo "To use your own domain, add a Wildcard DNS record (e.x: *.netmaker.example.com) pointing to $SERVER_PUBLIC_IP"
@@ -267,6 +311,49 @@ else
   EMAIL="$GET_EMAIL"
 fi
 
+wait_seconds 1
+
+unset GET_MQ_USERNAME
+unset GET_MQ_PASSWORD
+unset CONFIRM_MQ_PASSWORD
+echo "Enter Credentials For MQ..."
+read -p "MQ Username (click 'enter' to use 'netmaker'): " GET_MQ_USERNAME
+if [ -z "$GET_MQ_USERNAME" ]; then
+  echo "using default username for mq"
+  MQ_USERNAME="netmaker"
+else
+  MQ_USERNAME="$GET_MQ_USERNAME"
+fi
+
+select domain_option in "Auto Generated Password" "Input Your Own Password"; do
+	case $REPLY in
+	1)
+	echo "generating random password for mq"
+	MQ_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
+	break
+	;;      
+    2)
+	while true
+    do
+        echo "Enter your Password For MQ: " 
+        read -s GET_MQ_PASSWORD
+        echo "Enter your password again to confirm: "
+        read -s CONFIRM_MQ_PASSWORD
+        if [ ${GET_MQ_PASSWORD} != ${CONFIRM_MQ_PASSWORD} ]; then
+            echo "wrong password entered, try again..."
+            continue
+        fi
+		MQ_PASSWORD="$GET_MQ_PASSWORD"
+        echo "MQ Password Saved Successfully!!"
+        break
+    done
+      break
+      ;;
+    *) echo "invalid option $REPLY";;
+  esac
+done
+
+
 wait_seconds 2
 
 echo "-----------------------------------------------------------------"
@@ -301,8 +388,9 @@ if [ "$INSTALL_TYPE" = "ee" ]; then
 	CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/docker/Caddyfile-EE"
 fi
 
-wget -O /root/docker-compose.yml $COMPOSE_URL && wget -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/master/docker/mosquitto.conf && wget -O /root/Caddyfile $CADDY_URL && wget -q -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/master/docker/wait.sh && chmod +x /root/wait.sh
-
+wget -O /root/docker-compose.yml $COMPOSE_URL && wget -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/master/docker/mosquitto.conf && wget -O /root/Caddyfile $CADDY_URL
+wget -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/master/docker/wait.sh
+chmod +x /root/wait.sh
 mkdir -p /etc/netmaker
 
 echo "Setting docker-compose and Caddyfile..."
@@ -312,7 +400,8 @@ sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/Caddyfile
 sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/docker-compose.yml
 sed -i "s/REPLACE_MASTER_KEY/$MASTER_KEY/g" /root/docker-compose.yml
 sed -i "s/YOUR_EMAIL/$EMAIL/g" /root/Caddyfile
-sed -i "s/REPLACE_MQ_ADMIN_PASSWORD/$MQ_PASSWORD/g" /root/docker-compose.yml 
+sed -i "s/REPLACE_MQ_PASSWORD/$MQ_PASSWORD/g" /root/docker-compose.yml
+sed -i "s/REPLACE_MQ_USERNAME/$MQ_USERNAME/g" /root/docker-compose.yml 
 if [ "$INSTALL_TYPE" = "ee" ]; then
 	sed -i "s~YOUR_LICENSE_KEY~$LICENSE_KEY~g" /root/docker-compose.yml
 	sed -i "s/YOUR_ACCOUNT_ID/$ACCOUNT_ID/g" /root/docker-compose.yml
@@ -366,36 +455,28 @@ ACCESS_TOKEN=$(jq -r '.accessstring' <<< ${curlresponse})
 
 wait_seconds 3
 
-echo "Configuring netmaker server as ingress gateway"
+# echo "Installing Netclient"
+# setup_netclient
 
-for i in 1 2 3 4 5 6
-do
-	echo "    waiting for server node to become available"
-	wait_seconds 10
-	curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/netmaker)
-	SERVER_ID=$(jq -r '.[0].id' <<< ${curlresponse})
-	echo "    Server ID: $SERVER_ID"
-	if [ $SERVER_ID == "null" ]; then
-		SERVER_ID=""
-	fi
-	if [[ "$i" -ge "6" && -z "$SERVER_ID" ]]; then
-		echo "    Netmaker is having issues configuring itself, please investigate (docker logs netmaker)"
-		echo "    Exiting..."
-		exit 1
-	elif [ -z "$SERVER_ID" ]; then
-		echo "    server node not yet configured, retrying..."
-	elif [[ ! -z "$SERVER_ID" ]]; then
-		echo "    server node is now availble, continuing"
-		break
-	fi
-done
+# echo "Adding Netclient to Network"
+# netclient join -t $ACCESS_TOKEN
+
+# # TODO - Get Host ID
 
 
-if [[ ! -z "$SERVER_ID"  ]]; then
-	curl -o /dev/null -s -X POST -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/netmaker/$SERVER_ID/createingress
-fi 
+# echo "Setting Netclient as Default Host"
+# HOST_ID=$(grep 'id:' /etc/netclient/netclient.yml | awk '{print $2}')
+# echo $HOST_ID
+# # TODO - API call to make host default
+
+# echo "Setting Netclient as Ingress Gateway"
+# if [[ ! -z "$SERVER_ID"  ]]; then
+# 	curl -o /dev/null -s -X POST -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/netmaker/$HOST_ID/createingress
+# fi 
 )}
 
+
+
 set +e
 test_connection
 

+ 0 - 304
scripts/nm-quick.sh

@@ -1,304 +0,0 @@
-#!/bin/bash
-echo "checking for root permissions..."
-
-echo "setting flags..."
-
-while getopts d:e:m:v:c: flag
-do
-    case "${flag}" in
-    	d) domain=${OPTARG};;
-        e) email=${OPTARG};;
-        m) addmesh=${OPTARG};;
-        v) addvpn=${OPTARG};;
-        c) num_clients=${OPTARG};;
-    esac
-done
-
-echo "checking for root permissions..."
-
-
-if [ $EUID -ne 0 ]; then
-   echo "This script must be run as root" 
-   exit 1
-fi
-
-
-
-
-echo "checking dependencies..."
-
-declare -A osInfo;
-osInfo[/etc/debian_version]="apt-get install -y"u
-osInfo[/etc/alpine-release]="apk --update add"
-osInfo[/etc/centos-release]="yum install -y"
-osInfo[/etc/fedora-release]="dnf install -y"
-
-for f in ${!osInfo[@]}
-do
-    if [[ -f $f ]];then
-        install_cmd=${osInfo[$f]}
-    fi
-done
-
-if [ -f /etc/debian_version ]; then
-	apt update
-elif [ -f /etc/alpine-release ]; then
-  apk update
-elif [ -f /etc/centos-release ]; then
-	yum update
-elif [ -f /etc/fedora-release ]; then
-	dnf update
-fi
-
-dependencies=( "docker.io" "docker-compose" "wireguard" "jq" )
-
-for dependency in ${dependencies[@]}; do
-    is_installed=$(dpkg-query -W --showformat='${Status}\n' ${dependency} | grep "install ok installed")
-
-    if [ "${is_installed}" == "install ok installed" ]; then
-        echo "    " ${dependency} is installed
-    else
-            echo "    " ${dependency} is not installed. Attempting install.
-            ${install_cmd} ${dependency}
-            sleep 5
-            is_installed=$(dpkg-query -W --showformat='${Status}\n' ${dependency} | grep "install ok installed")
-            if [ "${is_installed}" == "install ok installed" ]; then
-                echo "    " ${dependency} is installed
-            elif [ -x "$(command -v ${dependency})" ]; then
-                echo "    " ${dependency} is installed
-            else
-                echo "    " failed to install ${dependency}. Exiting.
-                exit 1
-            fi
-    fi
-done
-
-set -e
-
-NETMAKER_BASE_DOMAIN=nm.$(curl -s ifconfig.me | tr . -).nip.io
-COREDNS_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p')
-SERVER_PUBLIC_IP=$(curl -s ifconfig.me)
-MASTER_KEY=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
-EMAIL="$(echo $RANDOM | md5sum  | head -c 32)@email.com"
-MQ_ADMIN_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 64 ; echo '')
-if [ -n "$domain" ]; then
-  NETMAKER_BASE_DOMAIN=$domain
-fi
-
-if [ -n "$email" ]; then
-  EMAIL=$email
-fi
-
-if [ -n "$addmesh" ]; then
-  MESH_SETUP=$addmesh
-else
-  MESH_SETUP="true"
-fi
-
-if [ -n "$addvpn" ]; then
-  VPN_SETUP=$addvpn
-else
-  VPN_SETUP="false"
-fi
-
-if [ -n "$num_clients" ]; then
-  NUM_CLIENTS=$num_clients
-else
-  NUM_CLIENTS=5
-fi
-
-
-echo "   ----------------------------"
-echo "                SETUP ARGUMENTS"
-echo "   ----------------------------"
-echo "        domain: $NETMAKER_BASE_DOMAIN"
-echo "         email: $EMAIL"
-echo "    coredns ip: $COREDNS_IP"
-echo "     public ip: $SERVER_PUBLIC_IP"
-echo "    master key: $MASTER_KEY"
-echo "   setup mesh?: $MESH_SETUP"
-echo "    setup vpn?: $VPN_SETUP"
-if [ "${VPN_SETUP}" == "true" ]; then
-echo "     # clients: $NUM_CLIENTS"
-fi
-echo "   ----------------------------"
-
-sleep 5
-
-echo "setting mosquitto.conf..."
-
-wget -q -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/master/docker/mosquitto.conf
-wget -q -O /root/Caddyfile https://raw.githubusercontent.com/gravitl/netmaker/master/docker/Caddyfile
-wget -q -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/master/docker/wait.sh
-chmod +x /root/wait.sh
-echo "setting docker-compose..."
-
-mkdir -p /etc/netmaker
-
-wget -q -O /root/docker-compose.yml https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.yml
-sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/docker-compose.yml
-sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/Caddyfile
-sed -i "s/SERVER_PUBLIC_IP/$SERVER_PUBLIC_IP/g" /root/docker-compose.yml
-sed -i "s/COREDNS_IP/$COREDNS_IP/g" /root/docker-compose.yml
-sed -i "s/REPLACE_MASTER_KEY/$MASTER_KEY/g" /root/docker-compose.yml
-sed -i "s/YOUR_EMAIL/$EMAIL/g" /root/Caddyfile
-sed -i "s/REPLACE_MQ_ADMIN_PASSWORD/$MQ_ADMIN_PASSWORD/g" /root/docker-compose.yml
-echo "starting containers..."
-
-docker-compose -f /root/docker-compose.yml up -d
-
-test_connection() {
-
-echo "testing Caddy setup (please be patient, this may take 1-2 minutes)"
-for i in 1 2 3 4 5 6 7 8
-do
-curlresponse=$(curl -vIs https://api.${NETMAKER_BASE_DOMAIN} 2>&1)
-
-if [[ "$i" == 8 ]]; then
-  echo "    Caddy is having an issue setting up certificates, please investigate (docker logs caddy)"
-  echo "    exiting..."
-  exit 1
-elif [[ "$curlresponse" == *"failed to verify the legitimacy of the server"* ]]; then
-  echo "    certificates not yet configured, retrying..."
-
-elif [[ "$curlresponse" == *"left intact"* ]]; then
-  echo "    certificates ok"
-  break
-else
-  secs=$(($i*5+10))
-  echo "    issue establishing connection...retrying in $secs seconds..."       
-fi
-sleep $secs
-done
-}
-
-set +e
-test_connection
-
-
-cat << "EOF"
-
-                                                                                         
- __   __     ______     ______   __    __     ______     __  __     ______     ______    
-/\ "-.\ \   /\  ___\   /\__  _\ /\ "-./  \   /\  __ \   /\ \/ /    /\  ___\   /\  == \   
-\ \ \-.  \  \ \  __\   \/_/\ \/ \ \ \-./\ \  \ \  __ \  \ \  _"-.  \ \  __\   \ \  __<   
- \ \_\\"\_\  \ \_____\    \ \_\  \ \_\ \ \_\  \ \_\ \_\  \ \_\ \_\  \ \_____\  \ \_\ \_\ 
-  \/_/ \/_/   \/_____/     \/_/   \/_/  \/_/   \/_/\/_/   \/_/\/_/   \/_____/   \/_/ /_/ 
-                                                                                         													 
-
-EOF
-
-
-echo "visit https://dashboard.$NETMAKER_BASE_DOMAIN to log in"
-echo "visit https://grafana.$NETMAKER_BASE_DOMAIN to view metrics on grafana dashboard"
-echo "visit https://prometheus.$NETMAKER_BASE_DOMAIN to view metrics on prometheus" 
-sleep 7
-
-setup_mesh() {( set -e
-echo "creating netmaker network (10.101.0.0/16)"
-
-curl -s -o /dev/null -d '{"addressrange":"10.101.0.0/16","netid":"netmaker"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks
-
-sleep 5
-
-echo "creating netmaker access key"
-
-curlresponse=$(curl -s -d '{"uses":99999,"name":"netmaker-key"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks/netmaker/keys)
-ACCESS_TOKEN=$(jq -r '.accessstring' <<< ${curlresponse})
-
-sleep 5
-
-echo "configuring netmaker server as ingress gateway"
-
-curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/netmaker)
-SERVER_ID=$(jq -r '.[0].id' <<< ${curlresponse})
-
-curl -o /dev/null -s -X POST -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/netmaker/$SERVER_ID/createingress
-
-sleep 5
-
-echo "finished configuring server and network. You can now add clients."
-echo ""
-echo "For Linux, Mac, Windows, and FreeBSD:"
-echo "        1. Install the netclient: https://docs.netmaker.org/netclient.html#installation"
-echo "        2. Join the network: netclient join -t $ACCESS_TOKEN"
-echo ""
-echo "For Android and iOS clients, perform the following steps:"
-echo "        1. Log into UI at dashboard.$NETMAKER_BASE_DOMAIN"
-echo "        2. Navigate to \"EXTERNAL CLIENTS\" tab"
-echo "        3. Select the gateway and create clients"
-echo "        4. Scan the QR Code from WireGuard app in iOS or Android"
-echo ""
-echo "Netmaker setup is now complete. You are ready to begin using Netmaker."
-)}
-
-setup_vpn() {( set -e
-echo "creating vpn network (10.201.0.0/16)"
-
-curl -s -o /dev/null -d '{"addressrange":"10.201.0.0/16","netid":"vpn","defaultextclientdns":"10.201.255.254"}' -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/networks
-
-sleep 5
-
-echo "configuring netmaker server as vpn inlet..."
-
-curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn)
-SERVER_ID=$(jq -r '.[0].id' <<< ${curlresponse})
-
-curl -s -o /dev/null -X POST -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn/$SERVER_ID/createingress
-
-echo "waiting 5 seconds for server to apply configuration..."
-
-sleep 5
-
-
-echo "configuring netmaker server vpn gateway..."
-
-[ -z "$GATEWAY_IFACE" ] && GATEWAY_IFACE=$(ip -4 route ls | grep default | grep -Po '(?<=dev )(\S+)')
-
-echo "gateway iface: $GATEWAY_IFACE"
-
-curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn)
-SERVER_ID=$(jq -r '.[0].id' <<< ${curlresponse})
-
-EGRESS_JSON=$( jq -n \
-                  --arg gw "$GATEWAY_IFACE" \
-                  '{ranges: ["0.0.0.0/0"], interface: $gw}' )
-
-echo "egress json: $EGRESS_JSON"
-curl -s -o /dev/null -X POST -d "$EGRESS_JSON" -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/vpn/$SERVER_ID/creategateway
-
-sleep 3
-
-echo "creating client configs..."
-
-for ((a=1; a <= $NUM_CLIENTS; a++))
-do
-        CLIENT_JSON=$( jq -n \
-                  --arg clientid "vpnclient-$a" \
-                  '{clientid: $clientid}' )
-
-        curl -s -o /dev/null -d "$CLIENT_JSON" -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/extclients/vpn/$SERVER_ID
-        sleep 2
-done
-
-echo "finished configuring vpn server."
-echo ""
-echo "To configure clients, perform the following steps:"
-echo "        1. log into dashboard.$NETMAKER_BASE_DOMAIN"
-echo "        2. Navigate to \"EXTERNAL CLIENTS\" tab"
-echo "        3. Download or scan a client config (vpnclient-x) to the appropriate device"
-echo "        4. Follow the steps for your system to configure WireGuard on the appropriate device"
-echo "        5. Create and delete clients as necessary. Changes to netmaker server settings require regenerating ext clients."
-
-)}
-
-if [ "${MESH_SETUP}" != "false" ]; then
-        setup_mesh
-fi
-
-if [ "${VPN_SETUP}" == "true" ]; then
-        setup_vpn
-fi
-
-echo ""
-echo "Netmaker setup is now complete. You are ready to begin using Netmaker."

+ 19 - 6
servercfg/serverconf.go

@@ -88,6 +88,8 @@ func GetServerInfo() models.ServerConfig {
 	var cfg models.ServerConfig
 	cfg.Server = GetServer()
 	cfg.Broker = GetBroker()
+	cfg.MQUserName = GetMqUserName()
+	cfg.MQPassword = GetMqPassword()
 	cfg.API = GetAPIConnString()
 	cfg.CoreDNSAddr = GetCoreDNSAddr()
 	cfg.APIPort = GetAPIPort()
@@ -589,13 +591,24 @@ func GetMQServerPort() string {
 	return port
 }
 
-// GetMqAdminPassword - fetches the MQ Admin password
-func GetMqAdminPassword() string {
+// GetMqPassword - fetches the MQ password
+func GetMqPassword() string {
 	password := ""
-	if os.Getenv("MQ_ADMIN_PASSWORD") != "" {
-		password = os.Getenv("MQ_ADMIN_PASSWORD")
-	} else if config.Config.Server.MQAdminPassword != "" {
-		password = config.Config.Server.MQAdminPassword
+	if os.Getenv("MQ_PASSWORD") != "" {
+		password = os.Getenv("MQ_PASSWORD")
+	} else if config.Config.Server.MQPassword != "" {
+		password = config.Config.Server.MQPassword
+	}
+	return password
+}
+
+// GetMqUserName - fetches the MQ username
+func GetMqUserName() string {
+	password := ""
+	if os.Getenv("MQ_USERNAME") != "" {
+		password = os.Getenv("MQ_USERNAME")
+	} else if config.Config.Server.MQUserName != "" {
+		password = config.Config.Server.MQUserName
 	}
 	return password
 }

+ 0 - 2
stun-server/stun-server.go

@@ -3,7 +3,6 @@ package stunserver
 import (
 	"context"
 	"fmt"
-	"log"
 	"net"
 	"os"
 	"os/signal"
@@ -71,7 +70,6 @@ func (s *Server) serveConn(c net.PacketConn, res, req *stun.Message) error {
 		logger.Log(1, "ReadFrom: %v", err.Error())
 		return nil
 	}
-	log.Printf("read %d bytes from %s\n", n, addr)
 	if _, err = req.Write(buf[:n]); err != nil {
 		logger.Log(1, "Write: %v", err.Error())
 		return err

+ 1 - 1
swagger.yaml

@@ -718,7 +718,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.17.1
+    version: 0.18.0
 paths:
     /api/dns:
         get: