Sfoglia il codice sorgente

Merge branch 'develop' of https://github.com/gravitl/netmaker into feature_v0.18.0_upgrade_script

afeiszli 2 anni fa
parent
commit
52b4d45d5b
63 ha cambiato i file con 1631 aggiunte e 1516 eliminazioni
  1. 13 48
      .github/workflows/test.yml
  2. 101 53
      auth/templates.go
  3. 3 15
      cli/cmd/acl/allow.go
  4. 3 15
      cli/cmd/acl/deny.go
  5. 1 6
      cli/cmd/acl/list.go
  6. 0 10
      cli/cmd/acl/root.go
  7. 0 10
      cli/cmd/context/root.go
  8. 0 10
      cli/cmd/dns/root.go
  9. 0 10
      cli/cmd/ext_client/root.go
  10. 20 0
      cli/cmd/host/delete.go
  11. 20 0
      cli/cmd/host/list.go
  12. 28 0
      cli/cmd/host/root.go
  13. 65 0
      cli/cmd/host/update.go
  14. 22 0
      cli/cmd/host/update_networks.go
  15. 0 10
      cli/cmd/keys/root.go
  16. 0 10
      cli/cmd/metrics/root.go
  17. 0 12
      cli/cmd/network/root.go
  18. 0 10
      cli/cmd/network_user/root.go
  19. 0 6
      cli/cmd/node/flags.go
  20. 5 3
      cli/cmd/node/list.go
  21. 0 10
      cli/cmd/node/root.go
  22. 3 30
      cli/cmd/node/update.go
  23. 3 14
      cli/cmd/root.go
  24. 0 10
      cli/cmd/server/root.go
  25. 0 10
      cli/cmd/user/root.go
  26. 0 10
      cli/cmd/usergroup/root.go
  27. 33 0
      cli/functions/host.go
  28. 17 17
      cli/functions/node.go
  29. 1 1
      controllers/dns.go
  30. 7 4
      controllers/ext_client.go
  31. 116 25
      controllers/hosts.go
  32. 1 1
      controllers/network.go
  33. 71 119
      controllers/node.go
  34. 80 0
      controllers/relay.go
  35. 16 15
      go.mod
  36. 41 598
      go.sum
  37. 27 17
      k8s/server/README.md
  38. 32 7
      k8s/server/mosquitto.yaml
  39. 1 1
      k8s/server/netmaker-server.yaml
  40. 5 5
      logic/extpeers.go
  41. 77 44
      logic/hosts.go
  42. 16 36
      logic/metrics/metrics.go
  43. 29 11
      logic/networks.go
  44. 8 5
      logic/nodes.go
  45. 277 89
      logic/peers.go
  46. 87 0
      logic/relay.go
  47. 2 1
      logic/server.go
  48. 2 1
      logic/zombie.go
  49. 1 1
      main.go
  50. 41 25
      models/api_host.go
  51. 4 3
      models/api_node.go
  52. 25 1
      models/host.go
  53. 9 10
      models/metrics.go
  54. 16 0
      models/mqtt.go
  55. 49 39
      models/node.go
  56. 11 4
      models/structs.go
  57. 15 2
      mq/dynsec_clients.go
  58. 80 3
      mq/dynsec_helper.go
  59. 100 12
      mq/handlers.go
  60. 4 0
      mq/mq.go
  61. 28 80
      mq/publishers.go
  62. 15 19
      mq/util.go
  63. 0 8
      stun-server/stun-server.go

+ 13 - 48
.github/workflows/test.yml

@@ -6,6 +6,7 @@ on:
     types: [opened, synchronize, reopened]
 
 jobs:
+
   build:
     runs-on: ubuntu-latest
     steps:
@@ -19,11 +20,7 @@ jobs:
         run: |
          env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build main.go
          env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags=ee main.go
-         cd netclient
-         env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go
-         env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build main.go
-         env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go
-         env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go
+
   nmctl:
     runs-on: ubuntu-latest
     steps:
@@ -40,34 +37,9 @@ jobs:
           GOOS=darwin GOARCH=amd64 go build -o nmctl
           GOOS=darwin GOARCH=arm64 go build -o nmctl
           GOOS=windows GOARCH=amd64 go build -o nmctl
-  linux-gui:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-      - name: Setup Go
-        uses: actions/setup-go@v3
-        with:
-          go-version: 1.19
-      - name: Build
-        run: |
-         sudo apt-get update
-         sudo apt-get install -y gcc libgl1-mesa-dev xorg-dev
-         env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags=gui main.go
-  mac-gui:
-    runs-on: macos-latest
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-      - name: Setup Go
-        uses: actions/setup-go@v3
-        with:
-          go-version: 1.19
-      - name: Build mac
-        run: |
-          env CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 go build -tags=gui main.go
-  win-gui:
-    runs-on: windows-latest
+
+  tests:
+    runs-on: ubuntu-22.04
     steps:
       - name: Checkout
         uses: actions/checkout@v3
@@ -75,16 +47,14 @@ jobs:
         uses: actions/setup-go@v3
         with:
           go-version: 1.19
-      - name: Mysys2 setup
-        uses: msys2/setup-msys2@v2
-        with:
-          install: >-
-            git
-            mingw-w64-x86_64-toolchain
-      - name: Build win gui
+      - name: run tests
         run: |
-          env CGO_ENABLED=1 GOOS=windows GOARCH=amd64 go build -tags=gui main.go
-  tests:
+          go test -p 1 ./... -v
+        env:
+          DATABASE: sqlite
+          CLIENT_MODE: "off"
+
+  staticcheck:
     env:
       DATABASE: sqlite
     runs-on: ubuntu-22.04
@@ -95,13 +65,8 @@ jobs:
         uses: actions/setup-go@v3
         with:
           go-version: 1.19
-      - name: run tests
+      - name: run static checks
         run: |
           sudo apt update
-          sudo apt-get install -y gcc libgl1-mesa-dev xorg-dev
-          go test -p 1 ./... -v
           go install honnef.co/go/tools/cmd/staticcheck@latest
           { ~/go/bin/staticcheck  -tags=ee ./... ; }
-        env:
-          DATABASE: sqlite
-          CLIENT_MODE: "off"

+ 101 - 53
auth/templates.go

@@ -10,72 +10,120 @@ type ssoCallbackTemplateConfig struct {
 var ssoCallbackTemplate = template.Must(
 	template.New("ssocallback").Parse(`<!DOCTYPE html>
 	<html lang="en">
+	
 	<head>
-	  <meta charset="UTF-8">
-	  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-	  <meta http-equiv="X-UA-Compatible" content="ie=edge">
-	  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
-		integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
-	  <title>Netmaker</title>
+		<meta charset="UTF-8">
+		<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
+		<meta http-equiv="X-UA-Compatible" content="ie=edge">
+		<title>Netmaker :: SSO Success</title>
+	
+		<style>
+			html,
+			body {
+				margin: 0px;
+				padding: 0px;
+			}
+	
+			body {
+				height: 100vh;
+				overflow: hidden;
+				display: flex;
+				flex-flow: column nowrap;
+				justify-content: center;
+				align-items: center;
+			}
+	
+			#logo {
+				width: 150px;
+			}
+	
+			h3 {
+				margin-bottom: 3rem;
+				color: rgb(25, 135, 84);
+				font-size: xx-large;
+			}
+	
+			h4 {
+				margin-bottom: 0px;
+			}
+	
+			p {
+				margin-top: 0px;
+				margin-bottom: 0px;
+			}
+		</style>
 	</head>
-	<style>
-	  .text-responsive {
-		font-size: calc(100% + 1vw + 1vh);
-	  }
-	</style>
+	
 	<body>
-	  <div class="container">
-		<div class="row justify-content-center mt-5 p-5 align-items-center text-center">
-		  <a href="https://netmaker.io">
-			<img src="https://raw.githubusercontent.com/gravitl/netmaker/master/img/netmaker-teal.png" alt="Netmaker"
-			  width="75%" height="25%" class="img-fluid">
-		  </a>
-		</div>
-		<div class="row justify-content-center mt-5 p-3 text-center">
-		  <div class="col">
-			<h2 class="text-responsive">{{.User}} has been successfully {{.Verb}}</h2>
-			<br />
-			<h2 class="text-responsive">You may now close this window.</h2>
-		  </div>
-		</div>
-	  </div>
+		<img
+			src="https://raw.githubusercontent.com/gravitl/netmaker-docs/master/images/netmaker-github/netmaker-teal.png"
+			alt="netmaker logo"
+			id="logo"
+		>
+		<h3>Server SSO Success</h3>
+		<h4>User {{.User}} has been successfully {{.Verb}}.</h4>
+		<p>You can close this window now</p>
 	</body>
+	
 	</html>`),
 )
 
 var ssoErrCallbackTemplate = template.Must(
 	template.New("ssocallback").Parse(`<!DOCTYPE html>
 	<html lang="en">
+	
 	<head>
-	  <meta charset="UTF-8">
-	  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-	  <meta http-equiv="X-UA-Compatible" content="ie=edge">
-	  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
-		integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
-	  <title>Netmaker</title>
+		<meta charset="UTF-8">
+		<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
+		<meta http-equiv="X-UA-Compatible" content="ie=edge">
+		<title>Netmaker :: SSO Error</title>
+	
+		<style>
+			html, body {
+				margin: 0px;
+				padding: 0px;
+			}
+			body {
+				height: 100vh;
+				overflow: hidden;
+				display: flex;
+				flex-flow: column nowrap;
+				justify-content: center;
+				align-items: center;
+			}
+			#logo {
+				width: 150px;
+			}
+			h3 {
+				margin-bottom: 3rem;
+				color:rgb(223, 71, 89);
+				font-size: xx-large;
+			}
+			h4 {
+				margin-top: 0rem;
+			}
+			p {
+				margin-top: 3rem;
+			}
+		</style>
 	</head>
-	<style>
-	  .text-responsive {
-		font-size: calc(100% + 1vw + 1vh);
-		color: red;
-	  }
-	</style>
+
 	<body>
-	  <div class="container">
-		<div class="row justify-content-center mt-5 p-5 align-items-center text-center">
-		  <a href="https://netmaker.io">
-			<img src="https://raw.githubusercontent.com/gravitl/netmaker/master/img/netmaker-teal.png" alt="Netmaker"
-			  width="75%" height="25%" class="img-fluid">
-		  </a>
-		</div>
-		<div class="row justify-content-center mt-5 p-3 text-center">
-		  <div class="col">
-			<h2 class="text-responsive">{{.User}} unable to join network: {{.Verb}}</h2>
-			<br />
-			<h2 class="text-responsive">If you feel this is a mistake, please contact your network administrator.</h2>
-		  </div>
-		</div>
-	  </div>
+		<img
+			src="https://raw.githubusercontent.com/gravitl/netmaker-docs/master/images/netmaker-github/netmaker-teal.png"
+			alt="netmaker logo"
+			id="logo"
+		>
+		<h3>Server SSO Error</h3>
+		<h4>Error reason: {.Verb}</h4>
+		<em>Your Netmaker server may not have SSO configured properly.</em>
+		<em>
+			Please visit the <a href="https://docs.netmaker.org/oauth.html" target="_blank" rel="noopener">docs</a> for more information.
+		</em>
+		<p>
+			If you feel this is a mistake, please contact your network administrator.
+		</p>
 	</body>
+
 	</html>`),
 )

+ 3 - 15
cli/cmd/acl/allow.go

@@ -2,8 +2,6 @@ package acl
 
 import (
 	"fmt"
-	"log"
-	"strings"
 
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/gravitl/netmaker/logic/acls"
@@ -11,23 +9,13 @@ import (
 )
 
 var aclAllowCmd = &cobra.Command{
-	Use:   "allow [NETWORK NAME] [FROM_NODE_NAME] [TO_NODE_NAME]",
+	Use:   "allow [NETWORK NAME] [NODE_1_ID] [NODE_2_ID]",
 	Args:  cobra.ExactArgs(3),
 	Short: "Allow access from one node to another",
 	Long:  `Allow access from one node to another`,
 	Run: func(cmd *cobra.Command, args []string) {
-		nameIDMap := make(map[string]string)
-		for _, node := range *functions.GetNodes(args[0]) {
-			nameIDMap[strings.ToLower(node.Name)] = node.ID
-		}
-		fromNodeID, ok := nameIDMap[strings.ToLower(args[1])]
-		if !ok {
-			log.Fatalf("Node %s doesn't exist", args[1])
-		}
-		toNodeID, ok := nameIDMap[strings.ToLower(args[2])]
-		if !ok {
-			log.Fatalf("Node %s doesn't exist", args[2])
-		}
+		fromNodeID := args[1]
+		toNodeID := args[2]
 		payload := acls.ACLContainer(map[acls.AclID]acls.ACL{
 			acls.AclID(fromNodeID): map[acls.AclID]byte{
 				acls.AclID(toNodeID): acls.Allowed,

+ 3 - 15
cli/cmd/acl/deny.go

@@ -2,8 +2,6 @@ package acl
 
 import (
 	"fmt"
-	"log"
-	"strings"
 
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/gravitl/netmaker/logic/acls"
@@ -11,23 +9,13 @@ import (
 )
 
 var aclDenyCmd = &cobra.Command{
-	Use:   "deny [NETWORK NAME] [FROM_NODE_NAME] [TO_NODE_NAME]",
+	Use:   "deny [NETWORK NAME] [NODE_1_ID] [NODE_2_ID]",
 	Args:  cobra.ExactArgs(3),
 	Short: "Deny access from one node to another",
 	Long:  `Deny access from one node to another`,
 	Run: func(cmd *cobra.Command, args []string) {
-		nameIDMap := make(map[string]string)
-		for _, node := range *functions.GetNodes(args[0]) {
-			nameIDMap[strings.ToLower(node.Name)] = node.ID
-		}
-		fromNodeID, ok := nameIDMap[strings.ToLower(args[1])]
-		if !ok {
-			log.Fatalf("Node %s doesn't exist", args[1])
-		}
-		toNodeID, ok := nameIDMap[strings.ToLower(args[2])]
-		if !ok {
-			log.Fatalf("Node %s doesn't exist", args[2])
-		}
+		fromNodeID := args[1]
+		toNodeID := args[2]
 		payload := acls.ACLContainer(map[acls.AclID]acls.ACL{
 			acls.AclID(fromNodeID): map[acls.AclID]byte{
 				acls.AclID(toNodeID): acls.NotAllowed,

+ 1 - 6
cli/cmd/acl/list.go

@@ -16,16 +16,11 @@ var aclListCmd = &cobra.Command{
 	Long:  `List all ACLs associated with a network`,
 	Run: func(cmd *cobra.Command, args []string) {
 		aclSource := (map[acls.AclID]acls.ACL)(*functions.GetACL(args[0]))
-		nodes := functions.GetNodes(args[0])
-		idNameMap := make(map[string]string)
-		for _, node := range *nodes {
-			idNameMap[node.ID] = node.Name
-		}
 		table := tablewriter.NewWriter(os.Stdout)
 		table.SetHeader([]string{"From", "To", "Status"})
 		for id, acl := range aclSource {
 			for k, v := range (map[acls.AclID]byte)(acl) {
-				row := []string{idNameMap[string(id)], idNameMap[string(k)]}
+				row := []string{string(id), string(k)}
 				switch v {
 				case acls.NotAllowed:
 					row = append(row, "Not Allowed")

+ 0 - 10
cli/cmd/acl/root.go

@@ -11,7 +11,6 @@ var rootCmd = &cobra.Command{
 	Use:   "acl",
 	Short: "Manage Access Control Lists (ACLs)",
 	Long:  `Manage Access Control Lists (ACLs)`,
-	// Run: func(cmd *cobra.Command, args []string) { },
 }
 
 // GetRoot returns the root subcommand
@@ -27,12 +26,3 @@ func Execute() {
 		os.Exit(1)
 	}
 }
-
-func init() {
-	// Here you will define your flags and configuration settings.
-	// Cobra supports persistent flags, which, if defined here,
-	// will be global for your application.
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}

+ 0 - 10
cli/cmd/context/root.go

@@ -11,7 +11,6 @@ var rootCmd = &cobra.Command{
 	Use:   "context",
 	Short: "Manage various netmaker server configurations",
 	Long:  `Manage various netmaker server configurations`,
-	// Run: func(cmd *cobra.Command, args []string) { },
 }
 
 // GetRoot returns the root subcommand
@@ -27,12 +26,3 @@ func Execute() {
 		os.Exit(1)
 	}
 }
-
-func init() {
-	// Here you will define your flags and configuration settings.
-	// Cobra supports persistent flags, which, if defined here,
-	// will be global for your application.
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}

+ 0 - 10
cli/cmd/dns/root.go

@@ -11,7 +11,6 @@ var rootCmd = &cobra.Command{
 	Use:   "dns",
 	Short: "Manage DNS entries associated with a network",
 	Long:  `Manage DNS entries associated with a network`,
-	// Run: func(cmd *cobra.Command, args []string) { },
 }
 
 // GetRoot returns the root subcommand
@@ -27,12 +26,3 @@ func Execute() {
 		os.Exit(1)
 	}
 }
-
-func init() {
-	// Here you will define your flags and configuration settings.
-	// Cobra supports persistent flags, which, if defined here,
-	// will be global for your application.
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}

+ 0 - 10
cli/cmd/ext_client/root.go

@@ -11,7 +11,6 @@ var rootCmd = &cobra.Command{
 	Use:   "ext_client",
 	Short: "Manage External Clients",
 	Long:  `Manage External Clients`,
-	// Run: func(cmd *cobra.Command, args []string) { },
 }
 
 // GetRoot returns the root subcommand
@@ -27,12 +26,3 @@ func Execute() {
 		os.Exit(1)
 	}
 }
-
-func init() {
-	// Here you will define your flags and configuration settings.
-	// Cobra supports persistent flags, which, if defined here,
-	// will be global for your application.
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}

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

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

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

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

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

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

+ 65 - 0
cli/cmd/host/update.go

@@ -0,0 +1,65 @@
+package host
+
+import (
+	"encoding/json"
+	"log"
+	"os"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/spf13/cobra"
+)
+
+var (
+	apiHostFilePath string
+	endpoint        string
+	name            string
+	listenPort      int
+	proxyListenPort int
+	mtu             int
+	proxyEnabled    bool
+	isStatic        bool
+	isDefault       bool
+)
+
+var hostUpdateCmd = &cobra.Command{
+	Use:   "update HostID",
+	Args:  cobra.ExactArgs(1),
+	Short: "Update a host",
+	Long:  `Update a host`,
+	Run: func(cmd *cobra.Command, args []string) {
+		apiHost := &models.ApiHost{}
+		if apiHostFilePath != "" {
+			content, err := os.ReadFile(apiHostFilePath)
+			if err != nil {
+				log.Fatal("Error when opening file: ", err)
+			}
+			if err := json.Unmarshal(content, apiHost); err != nil {
+				log.Fatal(err)
+			}
+		} else {
+			apiHost.EndpointIP = endpoint
+			apiHost.Name = name
+			apiHost.ListenPort = listenPort
+			apiHost.ProxyListenPort = proxyListenPort
+			apiHost.MTU = mtu
+			apiHost.ProxyEnabled = proxyEnabled
+			apiHost.IsStatic = isStatic
+			apiHost.IsDefault = isDefault
+		}
+		functions.PrettyPrint(functions.UpdateHost(args[0], apiHost))
+	},
+}
+
+func init() {
+	hostUpdateCmd.Flags().StringVar(&apiHostFilePath, "file", "", "Path to host_definition.json")
+	hostUpdateCmd.Flags().StringVar(&endpoint, "endpoint", "", "Endpoint of the Host")
+	hostUpdateCmd.Flags().StringVar(&name, "name", "", "Host name")
+	hostUpdateCmd.Flags().IntVar(&listenPort, "listen_port", 0, "Listen port of the host")
+	hostUpdateCmd.Flags().IntVar(&proxyListenPort, "proxy_listen_port", 0, "Proxy listen port of the host")
+	hostUpdateCmd.Flags().IntVar(&mtu, "mtu", 0, "Host MTU size")
+	hostUpdateCmd.Flags().BoolVar(&proxyEnabled, "proxy", false, "Enable proxy ?")
+	hostUpdateCmd.Flags().BoolVar(&isStatic, "static", false, "Make Host Static ?")
+	hostUpdateCmd.Flags().BoolVar(&isDefault, "default", false, "Make Host Default ?")
+	rootCmd.AddCommand(hostUpdateCmd)
+}

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

@@ -0,0 +1,22 @@
+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)
+}

+ 0 - 10
cli/cmd/keys/root.go

@@ -11,7 +11,6 @@ var rootCmd = &cobra.Command{
 	Use:   "keys",
 	Short: "Manage access keys associated with a network",
 	Long:  `Manage access keys associated with a network`,
-	// Run: func(cmd *cobra.Command, args []string) { },
 }
 
 // GetRoot returns the root subcommand
@@ -27,12 +26,3 @@ func Execute() {
 		os.Exit(1)
 	}
 }
-
-func init() {
-	// Here you will define your flags and configuration settings.
-	// Cobra supports persistent flags, which, if defined here,
-	// will be global for your application.
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}

+ 0 - 10
cli/cmd/metrics/root.go

@@ -11,7 +11,6 @@ var rootCmd = &cobra.Command{
 	Use:   "metrics",
 	Short: "Fetch metrics of nodes/networks",
 	Long:  `Fetch metrics of nodes/networks`,
-	// Run: func(cmd *cobra.Command, args []string) { },
 }
 
 // GetRoot returns the root subcommand
@@ -27,12 +26,3 @@ func Execute() {
 		os.Exit(1)
 	}
 }
-
-func init() {
-	// Here you will define your flags and configuration settings.
-	// Cobra supports persistent flags, which, if defined here,
-	// will be global for your application.
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}

+ 0 - 12
cli/cmd/network/root.go

@@ -11,9 +11,6 @@ var rootCmd = &cobra.Command{
 	Use:   "network",
 	Short: "Manage Netmaker Networks",
 	Long:  `Manage Netmaker Networks`,
-	// Uncomment the following line if your bare application
-	// has an action associated with it:
-	// Run: func(cmd *cobra.Command, args []string) { },
 }
 
 // GetRoot returns the root subcommand
@@ -29,12 +26,3 @@ func Execute() {
 		os.Exit(1)
 	}
 }
-
-func init() {
-	// Here you will define your flags and configuration settings.
-	// Cobra supports persistent flags, which, if defined here,
-	// will be global for your application.
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}

+ 0 - 10
cli/cmd/network_user/root.go

@@ -11,7 +11,6 @@ var rootCmd = &cobra.Command{
 	Use:   "network_user",
 	Short: "Manage Network Users",
 	Long:  `Manage Network Users`,
-	// Run: func(cmd *cobra.Command, args []string) { },
 }
 
 // GetRoot returns the root subcommand
@@ -27,12 +26,3 @@ func Execute() {
 		os.Exit(1)
 	}
 }
-
-func init() {
-	// Here you will define your flags and configuration settings.
-	// Cobra supports persistent flags, which, if defined here,
-	// will be global for your application.
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}

+ 0 - 6
cli/cmd/node/flags.go

@@ -6,23 +6,17 @@ var (
 	failover               bool
 	networkName            string
 	nodeDefinitionFilePath string
-	endpoint               string
-	listenPort             int
 	address                string
 	address6               string
 	localAddress           string
 	name                   string
 	postUp                 string
 	postDown               string
-	allowedIPs             string
 	keepAlive              int
 	relayAddrs             string
 	egressGatewayRanges    string
-	localRange             string
-	mtu                    int
 	expirationDateTime     int
 	defaultACL             bool
 	dnsOn                  bool
 	disconnect             bool
-	networkHub             bool
 )

+ 5 - 3
cli/cmd/node/list.go

@@ -2,6 +2,7 @@ package node
 
 import (
 	"os"
+	"strconv"
 
 	"github.com/gravitl/netmaker/cli/functions"
 	"github.com/gravitl/netmaker/models"
@@ -16,14 +17,14 @@ var nodeListCmd = &cobra.Command{
 	Short: "List all nodes",
 	Long:  `List all nodes`,
 	Run: func(cmd *cobra.Command, args []string) {
-		var data []models.Node
+		var data []models.ApiNode
 		if networkName != "" {
 			data = *functions.GetNodes(networkName)
 		} else {
 			data = *functions.GetNodes()
 		}
 		table := tablewriter.NewWriter(os.Stdout)
-		table.SetHeader([]string{"Name", "Addresses", "Version", "Network", "Egress", "Ingress", "Relay", "ID"})
+		table.SetHeader([]string{"ID", "Addresses", "Network", "Egress", "Ingress", "Relay"})
 		for _, d := range data {
 			addresses := ""
 			if d.Address != "" {
@@ -35,7 +36,8 @@ var nodeListCmd = &cobra.Command{
 				}
 				addresses += d.Address6
 			}
-			table.Append([]string{d.Name, addresses, d.Version, d.Network, d.IsEgressGateway, d.IsIngressGateway, d.IsRelay, d.ID})
+			table.Append([]string{d.ID, addresses, d.Network,
+				strconv.FormatBool(d.IsEgressGateway), strconv.FormatBool(d.IsIngressGateway), strconv.FormatBool(d.IsRelay)})
 		}
 		table.Render()
 	},

+ 0 - 10
cli/cmd/node/root.go

@@ -11,7 +11,6 @@ var rootCmd = &cobra.Command{
 	Use:   "node",
 	Short: "Manage nodes associated with a network",
 	Long:  `Manage nodes associated with a network`,
-	// Run: func(cmd *cobra.Command, args []string) { },
 }
 
 // GetRoot returns the root subcommand
@@ -27,12 +26,3 @@ func Execute() {
 		os.Exit(1)
 	}
 }
-
-func init() {
-	// Here you will define your flags and configuration settings.
-	// Cobra supports persistent flags, which, if defined here,
-	// will be global for your application.
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}

+ 3 - 30
cli/cmd/node/update.go

@@ -18,7 +18,7 @@ var nodeUpdateCmd = &cobra.Command{
 	Long:  `Update a Node`,
 	Run: func(cmd *cobra.Command, args []string) {
 		var (
-			node        = &models.Node{}
+			node        = &models.ApiNode{}
 			networkName = args[0]
 			nodeID      = args[1]
 		)
@@ -31,20 +31,11 @@ var nodeUpdateCmd = &cobra.Command{
 				log.Fatal(err)
 			}
 		} else {
-			if endpoint != "" {
-				node.Endpoint = endpoint
-				node.IsStatic = "no"
-			}
-			node.ListenPort = int32(listenPort)
 			node.Address = address
 			node.Address6 = address6
 			node.LocalAddress = localAddress
-			node.Name = name
 			node.PostUp = postUp
 			node.PostDown = postDown
-			if allowedIPs != "" {
-				node.AllowedIPs = strings.Split(allowedIPs, ",")
-			}
 			node.PersistentKeepalive = int32(keepAlive)
 			if relayAddrs != "" {
 				node.RelayAddrs = strings.Split(relayAddrs, ",")
@@ -52,24 +43,12 @@ var nodeUpdateCmd = &cobra.Command{
 			if egressGatewayRanges != "" {
 				node.EgressGatewayRanges = strings.Split(egressGatewayRanges, ",")
 			}
-			if localRange != "" {
-				node.LocalRange = localRange
-				node.IsLocal = "yes"
-			}
-			node.MTU = int32(mtu)
 			node.ExpirationDateTime = int64(expirationDateTime)
 			if defaultACL {
 				node.DefaultACL = "yes"
 			}
-			if dnsOn {
-				node.DNSOn = "yes"
-			}
-			if disconnect {
-				node.Connected = "no"
-			}
-			if networkHub {
-				node.IsHub = "yes"
-			}
+			node.DNSOn = dnsOn
+			node.Connected = !disconnect
 		}
 		functions.PrettyPrint(functions.UpdateNode(networkName, nodeID, node))
 	},
@@ -77,24 +56,18 @@ var nodeUpdateCmd = &cobra.Command{
 
 func init() {
 	nodeUpdateCmd.Flags().StringVar(&nodeDefinitionFilePath, "file", "", "Filepath of updated node definition in JSON")
-	nodeUpdateCmd.Flags().StringVar(&endpoint, "endpoint", "", "Public endpoint of the node")
-	nodeUpdateCmd.Flags().IntVar(&listenPort, "listen_port", 0, "Default wireguard port for the node")
 	nodeUpdateCmd.Flags().StringVar(&address, "ipv4_addr", "", "IPv4 address of the node")
 	nodeUpdateCmd.Flags().StringVar(&address6, "ipv6_addr", "", "IPv6 address of the node")
 	nodeUpdateCmd.Flags().StringVar(&localAddress, "local_addr", "", "Locally reachable address of the node")
 	nodeUpdateCmd.Flags().StringVar(&name, "name", "", "Node name")
 	nodeUpdateCmd.Flags().StringVar(&postUp, "post_up", "", "Commands to run after node is up `;` separated")
 	nodeUpdateCmd.Flags().StringVar(&postDown, "post_down", "", "Commands to run after node is down `;` separated")
-	nodeUpdateCmd.Flags().StringVar(&allowedIPs, "allowed_addrs", "", "Additional private addresses given to the node (comma separated)")
 	nodeUpdateCmd.Flags().IntVar(&keepAlive, "keep_alive", 0, "Interval in which packets are sent to keep connections open with peers")
 	nodeUpdateCmd.Flags().StringVar(&relayAddrs, "relay_addrs", "", "Addresses for relaying connections if node acts as a relay")
 	nodeUpdateCmd.Flags().StringVar(&egressGatewayRanges, "egress_addrs", "", "Addresses for egressing traffic if node acts as an egress")
-	nodeUpdateCmd.Flags().StringVar(&localRange, "local_range", "", "Local range in which the node will look for private addresses to use as an endpoint if `LocalNetwork` is enabled")
-	nodeUpdateCmd.Flags().IntVar(&mtu, "mtu", 0, "MTU size")
 	nodeUpdateCmd.Flags().IntVar(&expirationDateTime, "expiry", 0, "UNIX timestamp after which node will lose access to the network")
 	nodeUpdateCmd.Flags().BoolVar(&defaultACL, "acl", false, "Enable default ACL ?")
 	nodeUpdateCmd.Flags().BoolVar(&dnsOn, "dns", false, "Setup DNS entries for peers locally ?")
 	nodeUpdateCmd.Flags().BoolVar(&disconnect, "disconnect", false, "Disconnect from the network ?")
-	nodeUpdateCmd.Flags().BoolVar(&networkHub, "hub", false, "On a point to site network, this node is the only one which all peers connect to ?")
 	rootCmd.AddCommand(nodeUpdateCmd)
 }

+ 3 - 14
cli/cmd/root.go

@@ -7,6 +7,7 @@ import (
 	"github.com/gravitl/netmaker/cli/cmd/context"
 	"github.com/gravitl/netmaker/cli/cmd/dns"
 	"github.com/gravitl/netmaker/cli/cmd/ext_client"
+	"github.com/gravitl/netmaker/cli/cmd/host"
 	"github.com/gravitl/netmaker/cli/cmd/keys"
 	"github.com/gravitl/netmaker/cli/cmd/metrics"
 	"github.com/gravitl/netmaker/cli/cmd/network"
@@ -23,9 +24,6 @@ var rootCmd = &cobra.Command{
 	Use:   "nmctl",
 	Short: "CLI for interacting with Netmaker Server",
 	Long:  `CLI for interacting with Netmaker Server`,
-	// Uncomment the following line if your bare application
-	// has an action associated with it:
-	// Run: func(cmd *cobra.Command, args []string) { },
 }
 
 // GetRoot returns the root of all subcommands
@@ -43,17 +41,7 @@ func Execute() {
 }
 
 func init() {
-	// Here you will define your flags and configuration settings.
-	// Cobra supports persistent flags, which, if defined here,
-	// will be global for your application.
-
-	// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.tctl.yaml)")
-
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-
-	// IMP: Bind subcommands here
+	// Bind subcommands here
 	rootCmd.AddCommand(network.GetRoot())
 	rootCmd.AddCommand(context.GetRoot())
 	rootCmd.AddCommand(keys.GetRoot())
@@ -66,4 +54,5 @@ func init() {
 	rootCmd.AddCommand(usergroup.GetRoot())
 	rootCmd.AddCommand(metrics.GetRoot())
 	rootCmd.AddCommand(network_user.GetRoot())
+	rootCmd.AddCommand(host.GetRoot())
 }

+ 0 - 10
cli/cmd/server/root.go

@@ -11,7 +11,6 @@ var rootCmd = &cobra.Command{
 	Use:   "server",
 	Short: "Get netmaker server information",
 	Long:  `Get netmaker server information`,
-	// Run: func(cmd *cobra.Command, args []string) { },
 }
 
 // GetRoot returns the root subcommand
@@ -27,12 +26,3 @@ func Execute() {
 		os.Exit(1)
 	}
 }
-
-func init() {
-	// Here you will define your flags and configuration settings.
-	// Cobra supports persistent flags, which, if defined here,
-	// will be global for your application.
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}

+ 0 - 10
cli/cmd/user/root.go

@@ -11,7 +11,6 @@ var rootCmd = &cobra.Command{
 	Use:   "user",
 	Short: "Manage users and permissions",
 	Long:  `Manage users and permissions`,
-	// Run: func(cmd *cobra.Command, args []string) { },
 }
 
 // GetRoot returns the root subcommand
@@ -27,12 +26,3 @@ func Execute() {
 		os.Exit(1)
 	}
 }
-
-func init() {
-	// Here you will define your flags and configuration settings.
-	// Cobra supports persistent flags, which, if defined here,
-	// will be global for your application.
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}

+ 0 - 10
cli/cmd/usergroup/root.go

@@ -11,7 +11,6 @@ var rootCmd = &cobra.Command{
 	Use:   "usergroup",
 	Short: "Manage User Groups",
 	Long:  `Manage User Groups`,
-	// Run: func(cmd *cobra.Command, args []string) { },
 }
 
 // GetRoot returns the root subcommand
@@ -27,12 +26,3 @@ func Execute() {
 		os.Exit(1)
 	}
 }
-
-func init() {
-	// Here you will define your flags and configuration settings.
-	// Cobra supports persistent flags, which, if defined here,
-	// will be global for your application.
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}

+ 33 - 0
cli/functions/host.go

@@ -0,0 +1,33 @@
+package functions
+
+import (
+	"net/http"
+
+	"github.com/gravitl/netmaker/models"
+)
+
+type hostNetworksUpdatePayload struct {
+	Networks []string `json:"networks"`
+}
+
+// GetHosts - fetch all host entries
+func GetHosts() *[]models.ApiHost {
+	return request[[]models.ApiHost](http.MethodGet, "/api/hosts", nil)
+}
+
+// DeleteHost - delete a host
+func DeleteHost(hostID string) *models.ApiHost {
+	return request[models.ApiHost](http.MethodDelete, "/api/hosts/"+hostID, nil)
+}
+
+// UpdateHost - update a host
+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,
+	})
+}

+ 17 - 17
cli/functions/node.go

@@ -8,11 +8,11 @@ import (
 )
 
 // GetNodes - fetch all nodes
-func GetNodes(networkName ...string) *[]models.Node {
+func GetNodes(networkName ...string) *[]models.ApiNode {
 	if len(networkName) == 1 {
-		return request[[]models.Node](http.MethodGet, "/api/nodes/"+networkName[0], nil)
+		return request[[]models.ApiNode](http.MethodGet, "/api/nodes/"+networkName[0], nil)
 	} else {
-		return request[[]models.Node](http.MethodGet, "/api/nodes", nil)
+		return request[[]models.ApiNode](http.MethodGet, "/api/nodes", nil)
 	}
 }
 
@@ -22,8 +22,8 @@ func GetNodeByID(networkName, nodeID string) *models.NodeGet {
 }
 
 // UpdateNode - update a single node
-func UpdateNode(networkName, nodeID string, node *models.Node) *models.Node {
-	return request[models.Node](http.MethodPut, fmt.Sprintf("/api/nodes/%s/%s", networkName, nodeID), node)
+func UpdateNode(networkName, nodeID string, node *models.ApiNode) *models.ApiNode {
+	return request[models.ApiNode](http.MethodPut, fmt.Sprintf("/api/nodes/%s/%s", networkName, nodeID), node)
 }
 
 // DeleteNode - delete a node
@@ -32,8 +32,8 @@ func DeleteNode(networkName, nodeID string) *models.SuccessResponse {
 }
 
 // CreateRelay - turn a node into a relay
-func CreateRelay(networkName, nodeID string, relayAddresses []string) *models.Node {
-	return request[models.Node](http.MethodPost, fmt.Sprintf("/api/nodes/%s/%s/createrelay", networkName, nodeID), &models.RelayRequest{
+func CreateRelay(networkName, nodeID string, relayAddresses []string) *models.ApiNode {
+	return request[models.ApiNode](http.MethodPost, fmt.Sprintf("/api/nodes/%s/%s/createrelay", networkName, nodeID), &models.RelayRequest{
 		NetID:      networkName,
 		NodeID:     nodeID,
 		RelayAddrs: relayAddresses,
@@ -41,30 +41,30 @@ func CreateRelay(networkName, nodeID string, relayAddresses []string) *models.No
 }
 
 // DeleteRelay - remove relay role from a node
-func DeleteRelay(networkName, nodeID string) *models.Node {
-	return request[models.Node](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s/deleterelay", networkName, nodeID), nil)
+func DeleteRelay(networkName, nodeID string) *models.ApiNode {
+	return request[models.ApiNode](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s/deleterelay", networkName, nodeID), nil)
 }
 
 // CreateEgress - turn a node into an egress
-func CreateEgress(networkName, nodeID string, payload *models.EgressGatewayRequest) *models.Node {
-	return request[models.Node](http.MethodPost, fmt.Sprintf("/api/nodes/%s/%s/creategateway", networkName, nodeID), payload)
+func CreateEgress(networkName, nodeID string, payload *models.EgressGatewayRequest) *models.ApiNode {
+	return request[models.ApiNode](http.MethodPost, fmt.Sprintf("/api/nodes/%s/%s/creategateway", networkName, nodeID), payload)
 }
 
 // DeleteEgress - remove egress role from a node
-func DeleteEgress(networkName, nodeID string) *models.Node {
-	return request[models.Node](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s/deletegateway", networkName, nodeID), nil)
+func DeleteEgress(networkName, nodeID string) *models.ApiNode {
+	return request[models.ApiNode](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s/deletegateway", networkName, nodeID), nil)
 }
 
 // CreateIngress - turn a node into an ingress
-func CreateIngress(networkName, nodeID string, failover bool) *models.Node {
-	return request[models.Node](http.MethodPost, fmt.Sprintf("/api/nodes/%s/%s/createingress", networkName, nodeID), &struct {
+func CreateIngress(networkName, nodeID string, failover bool) *models.ApiNode {
+	return request[models.ApiNode](http.MethodPost, fmt.Sprintf("/api/nodes/%s/%s/createingress", networkName, nodeID), &struct {
 		Failover bool `json:"failover"`
 	}{Failover: failover})
 }
 
 // DeleteIngress - remove ingress role from a node
-func DeleteIngress(networkName, nodeID string) *models.Node {
-	return request[models.Node](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s/deleteingress", networkName, nodeID), nil)
+func DeleteIngress(networkName, nodeID string) *models.ApiNode {
+	return request[models.ApiNode](http.MethodDelete, fmt.Sprintf("/api/nodes/%s/%s/deleteingress", networkName, nodeID), nil)
 }
 
 // UncordonNode - uncordon a node

+ 1 - 1
controllers/dns.go

@@ -176,7 +176,7 @@ func createDNS(w http.ResponseWriter, r *http.Request) {
 	}
 	logger.Log(1, "new DNS record added:", entry.Name)
 	if servercfg.IsMessageQueueBackend() {
-		if err = mq.PublishPeerUpdate(entry.Network, false); err != nil {
+		if err = mq.PublishPeerUpdate(); err != nil {
 			logger.Log(0, "failed to publish peer update after ACL update on", entry.Network)
 		}
 	}

+ 7 - 4
controllers/ext_client.go

@@ -339,10 +339,13 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	host, err := logic.GetHost(node.HostID.String())
-	logger.Log(0, r.Header.Get("user"),
-		fmt.Sprintf("failed to get ingress gateway host for node [%s] info: %v", nodeid, err))
-	logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-	listenPort := host.LocalListenPort
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"),
+			fmt.Sprintf("failed to get ingress gateway host for node [%s] info: %v", nodeid, err))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	listenPort := host.ListenPort
 	if host.ProxyEnabled {
 		listenPort = host.ProxyListenPort
 	}

+ 116 - 25
controllers/hosts.go

@@ -2,14 +2,16 @@ package controller
 
 import (
 	"encoding/json"
+	"errors"
+	"fmt"
 	"net/http"
+	"reflect"
 
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/mq"
-	"github.com/gravitl/netmaker/servercfg"
 )
 
 type hostNetworksUpdatePayload struct {
@@ -17,10 +19,13 @@ type hostNetworksUpdatePayload struct {
 }
 
 func hostHandlers(r *mux.Router) {
-	r.HandleFunc("/api/hosts", logic.SecurityCheck(true, http.HandlerFunc(getHosts))).Methods("GET")
-	r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(updateHost))).Methods("PUT")
-	r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(deleteHost))).Methods("DELETE")
-	r.HandleFunc("/api/hosts/{hostid}/networks", logic.SecurityCheck(true, http.HandlerFunc(updateHostNetworks))).Methods("PUT")
+	r.HandleFunc("/api/hosts", logic.SecurityCheck(true, http.HandlerFunc(getHosts))).Methods(http.MethodGet)
+	r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(updateHost))).Methods(http.MethodPut)
+	r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(deleteHost))).Methods(http.MethodDelete)
+	r.HandleFunc("/api/hosts/{hostid}/networks/{network}", logic.SecurityCheck(true, http.HandlerFunc(addHostToNetwork))).Methods(http.MethodPost)
+	r.HandleFunc("/api/hosts/{hostid}/networks/{network}", logic.SecurityCheck(true, http.HandlerFunc(deleteHostFromNetwork))).Methods(http.MethodDelete)
+	r.HandleFunc("/api/hosts/{hostid}/relay", logic.SecurityCheck(false, http.HandlerFunc(createHostRelay))).Methods(http.MethodPost)
+	r.HandleFunc("/api/hosts/{hostid}/relay", logic.SecurityCheck(false, http.HandlerFunc(deleteHostRelay))).Methods(http.MethodDelete)
 }
 
 // swagger:route GET /api/hosts hosts getHosts
@@ -77,6 +82,13 @@ func updateHost(w http.ResponseWriter, r *http.Request) {
 	}
 
 	newHost := newHostData.ConvertAPIHostToNMHost(currHost)
+	// check if relay information is changed
+	updateRelay := false
+	if newHost.IsRelay && len(newHost.RelayedHosts) > 0 {
+		if len(newHost.RelayedHosts) != len(currHost.RelayedHosts) || !reflect.DeepEqual(newHost.RelayedHosts, currHost.RelayedHosts) {
+			updateRelay = true
+		}
+	}
 
 	logic.UpdateHost(newHost, currHost) // update the in memory struct values
 	if err = logic.UpsertHost(newHost); err != nil {
@@ -84,6 +96,9 @@ func updateHost(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
+	if updateRelay {
+		logic.UpdateHostRelay(currHost.ID.String(), currHost.RelayedHosts, newHost.RelayedHosts)
+	}
 
 	newNetworks := logic.GetHostNetworks(newHost.ID.String())
 	if len(newNetworks) > 0 {
@@ -95,6 +110,18 @@ func updateHost(w http.ResponseWriter, r *http.Request) {
 			logger.Log(0, r.Header.Get("user"), "failed to update host networks roles in DynSec:", err.Error())
 		}
 	}
+	// publish host update through MQ
+	if err := mq.HostUpdate(&models.HostUpdate{
+		Action: models.UpdateHost,
+		Host:   *newHost,
+	}); err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to send host update: ", currHost.ID.String(), err.Error())
+	}
+	go func() {
+		if err := mq.PublishPeerUpdate(); err != nil {
+			logger.Log(0, "fail to publish peer update: ", err.Error())
+		}
+	}()
 
 	apiHostData := newHost.ConvertNMHostToAPI()
 	logger.Log(2, r.Header.Get("user"), "updated host", newHost.ID.String())
@@ -129,6 +156,12 @@ func deleteHost(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
+	if err = mq.HostUpdate(&models.HostUpdate{
+		Action: models.DeleteHost,
+		Host:   *currHost,
+	}); err != nil {
+		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())
@@ -140,9 +173,9 @@ func deleteHost(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(apiHostData)
 }
 
-// swagger:route PUT /api/hosts hosts updateHostNetworks
+// swagger:route POST /api/hosts/{hostid}/networks/{network} hosts addHostToNetwork
 //
-// Given a list of networks, a host is updated accordingly.
+// Given a network, a host is added to the network.
 //
 //			Schemes: https
 //
@@ -150,19 +183,74 @@ func deleteHost(w http.ResponseWriter, r *http.Request) {
 //	  		oauth
 //
 //			Responses:
-//				200: updateHostNetworks
-func updateHostNetworks(w http.ResponseWriter, r *http.Request) {
-	var payload hostNetworksUpdatePayload
-	err := json.NewDecoder(r.Body).Decode(&payload)
+//				200: addHostToNetworkResponse
+func addHostToNetwork(w http.ResponseWriter, r *http.Request) {
+
+	var params = mux.Vars(r)
+	hostid := params["hostid"]
+	network := params["network"]
+	if hostid == "" || network == "" {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("hostid or network cannot be empty"), "badrequest"))
+		return
+	}
+	// confirm host exists
+	currHost, err := logic.GetHost(hostid)
 	if err != nil {
-		logger.Log(0, r.Header.Get("user"), "failed to update host networks:", err.Error())
+		logger.Log(0, r.Header.Get("user"), "failed to find host:", hostid, err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
 
-	// confirm host exists
+	newNode, err := logic.UpdateHostNetwork(currHost, network, true)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to add host to network:", hostid, network, 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 update host to join network:", hostid, network, err.Error())
+	}
+	networks := logic.GetHostNetworks(currHost.ID.String())
+	if len(networks) > 0 {
+		if err = mq.ModifyClient(&mq.MqClient{
+			ID:       currHost.ID.String(),
+			Text:     currHost.Name,
+			Networks: networks,
+		}); err != nil {
+			logger.Log(0, r.Header.Get("user"), "failed to update host networks roles in DynSec:", hostid, err.Error())
+		}
+	}
+
+	logger.Log(2, r.Header.Get("user"), fmt.Sprintf("added host %s to network %s", currHost.Name, network))
+	w.WriteHeader(http.StatusOK)
+}
+
+// swagger:route DELETE /api/hosts/{hostid}/networks/{network} hosts deleteHostFromNetwork
+//
+// Given a network, a host is removed from the network.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: deleteHostFromNetworkResponse
+func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) {
+
 	var params = mux.Vars(r)
 	hostid := params["hostid"]
+	network := params["network"]
+	if hostid == "" || network == "" {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("hostid or network cannot be empty"), "badrequest"))
+		return
+	}
+	// confirm host exists
 	currHost, err := logic.GetHost(hostid)
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"), "failed to find host:", err.Error())
@@ -170,21 +258,24 @@ func updateHostNetworks(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if err = logic.UpdateHostNetworks(currHost, servercfg.GetServer(), payload.Networks[:]); err != nil {
-		logger.Log(0, r.Header.Get("user"), "failed to update host networks:", err.Error())
+	node, err := logic.UpdateHostNetwork(currHost, network, false)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to remove host from network:", hostid, network, err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
-
-	if err = mq.ModifyClient(&mq.MqClient{
-		ID:       currHost.ID.String(),
-		Text:     currHost.Name,
-		Networks: payload.Networks,
-	}); err != nil {
-		logger.Log(0, r.Header.Get("user"), "failed to update host networks roles in DynSec:", err.Error())
+	logger.Log(1, "deleting  node", node.ID.String(), "from host", currHost.Name)
+	if err := logic.DeleteNode(node, false); err != nil {
+		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
+		return
 	}
-
-	logger.Log(2, r.Header.Get("user"), "updated host networks", currHost.Name)
+	// notify node change
+	runUpdates(node, false)
+	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("removed host %s from network %s", currHost.Name, network))
 	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(payload)
 }

+ 1 - 1
controllers/network.go

@@ -307,7 +307,7 @@ func updateNetworkACL(w http.ResponseWriter, r *http.Request) {
 
 	// send peer updates
 	if servercfg.IsMessageQueueBackend() {
-		if err = mq.PublishPeerUpdate(netname, false); err != nil {
+		if err = mq.PublishPeerUpdate(); err != nil {
 			logger.Log(0, "failed to publish peer update after ACL update on", netname)
 		}
 	}

+ 71 - 119
controllers/node.go

@@ -4,12 +4,11 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"io"
 	"net/http"
 	"strings"
 
-	"github.com/google/uuid"
 	"github.com/gorilla/mux"
-	proxy_models "github.com/gravitl/netclient/nmproxy/models"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
@@ -27,7 +26,7 @@ func nodeHandlers(r *mux.Router) {
 	r.HandleFunc("/api/nodes/{network}", authorize(false, true, "network", http.HandlerFunc(getNetworkNodes))).Methods(http.MethodGet)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(getNode))).Methods(http.MethodGet)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(false, true, "node", http.HandlerFunc(updateNode))).Methods(http.MethodPut)
-	r.HandleFunc("/api/nodes/{network}/{nodeid}/migrate", authorize(true, true, "node", http.HandlerFunc(nodeNodeUpdate))).Methods("PUT")
+	r.HandleFunc("/api/nodes/{network}/{nodeid}/migrate", authorize(true, true, "node", http.HandlerFunc(migrate))).Methods(http.MethodPut)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}", authorize(true, true, "node", http.HandlerFunc(deleteNode))).Methods(http.MethodDelete)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/createrelay", authorize(false, true, "user", http.HandlerFunc(createRelay))).Methods(http.MethodPost)
 	r.HandleFunc("/api/nodes/{network}/{nodeid}/deleterelay", authorize(false, true, "user", http.HandlerFunc(deleteRelay))).Methods(http.MethodDelete)
@@ -177,6 +176,7 @@ func nodeauth(next http.Handler) http.HandlerFunc {
 			for _, key := range network.AccessKeys {
 				if key.Value == token {
 					found = true
+					logic.DecrimentKey(network.NetID, key.Value)
 					break
 				}
 			}
@@ -189,6 +189,7 @@ func nodeauth(next http.Handler) http.HandlerFunc {
 			logic.ReturnErrorResponse(w, r, errorResponse)
 			return
 		}
+
 		next.ServeHTTP(w, r)
 	}
 }
@@ -442,6 +443,13 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
+	hostPeerUpdate, err := logic.GetPeerUpdateForHost(host)
+	if err != nil && !database.IsEmptyRecord(err) {
+		logger.Log(0, r.Header.Get("user"),
+			fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", host.ID.String(), err))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
 	server := servercfg.GetServerInfo()
 	network, err := logic.GetNetwork(node.Network)
 	if err != nil {
@@ -453,13 +461,15 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 	legacy := node.Legacy(host, &server, &network)
 	response := models.NodeGet{
 		Node:         *legacy,
+		Host:         *host,
 		Peers:        peerUpdate.Peers,
+		HostPeers:    hostPeerUpdate.Peers,
 		ServerConfig: server,
 		PeerIDs:      peerUpdate.PeerIDs,
 	}
 
 	if servercfg.Is_EE && nodeRequest {
-		if err = logic.EnterpriseResetAllPeersFailovers(node.ID.String(), node.Network); err != nil {
+		if err = logic.EnterpriseResetAllPeersFailovers(node.ID, node.Network); err != nil {
 			logger.Log(1, "failed to reset failover list during node config pull", node.ID.String(), node.Network)
 		}
 	}
@@ -637,26 +647,25 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 	}
-	peerUpdate, err := logic.GetPeerUpdate(&data.Node, &data.Host)
+	hostPeerUpdate, err := logic.GetPeerUpdateForHost(&data.Host)
 	if err != nil && !database.IsEmptyRecord(err) {
 		logger.Log(0, r.Header.Get("user"),
-			fmt.Sprintf("error fetching wg peers config for node [ %s ]: %v", data.Node.ID.String(), err))
+			fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", data.Host.ID.String(), err))
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
-	data.Node.Peers = peerUpdate.Peers
-
 	response := models.NodeJoinResponse{
 		Node:         data.Node,
 		ServerConfig: server,
-		PeerIDs:      peerUpdate.PeerIDs,
+		Host:         data.Host,
+		Peers:        hostPeerUpdate.Peers,
 	}
 	logger.Log(1, r.Header.Get("user"), "created new node", data.Host.Name, "on network", networkName)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(response)
 
 	go func() {
-		if err := mq.PublishPeerUpdate(data.Node.Network, true); err != nil {
+		if err := mq.PublishPeerUpdate(); err != nil {
 			logger.Log(1, "failed a peer update after creation of node", data.Host.Name)
 		}
 	}()
@@ -834,88 +843,39 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) {
 //	  		oauth
 //
 //			Responses:
-//				200: nodeResponse
-func nodeNodeUpdate(w http.ResponseWriter, r *http.Request) {
-	// should only be used by nodes
-	w.Header().Set("Content-Type", "application/json")
-
-	var params = mux.Vars(r)
-
-	//start here
-	nodeid := params["nodeid"]
-	currentNode, err := logic.GetNodeByID(nodeid)
-	if err != nil {
-		logger.Log(0,
-			fmt.Sprintf("error fetching node [ %s ] info: %v during migrate", nodeid, err))
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-		return
-	}
-
-	var newNode models.Node
+//				200: nodeJoinResponse
+func migrate(w http.ResponseWriter, r *http.Request) {
 	// we decode our body request params
-	err = json.NewDecoder(r.Body).Decode(&newNode)
+	data := models.JoinData{}
+	err := json.NewDecoder(r.Body).Decode(&data)
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 	}
-	relayupdate := false
-	if currentNode.IsRelay && len(newNode.RelayAddrs) > 0 {
-		if len(newNode.RelayAddrs) != len(currentNode.RelayAddrs) {
-			relayupdate = true
-		} else {
-			for i, addr := range newNode.RelayAddrs {
-				if addr != currentNode.RelayAddrs[i] {
-					relayupdate = true
-				}
-			}
-		}
-	}
-	relayedUpdate := false
-	if currentNode.IsRelayed && (currentNode.Address.String() != newNode.Address.String() || currentNode.Address6.String() != newNode.Address6.String()) {
-		relayedUpdate = true
-	}
-
-	if !servercfg.GetRce() {
-		newNode.PostDown = currentNode.PostDown
-		newNode.PostUp = currentNode.PostUp
-	}
-
-	ifaceDelta := logic.IfaceDelta(&currentNode, &newNode)
-
-	if ifaceDelta && servercfg.Is_EE {
-		if err = logic.EnterpriseResetAllPeersFailovers(currentNode.ID.String(), currentNode.Network); err != nil {
-			logger.Log(0, "failed to reset failover lists during node update for node", currentNode.ID.String(), currentNode.Network)
-		}
-	}
-
-	err = logic.UpdateNode(&currentNode, &newNode)
+	params := mux.Vars(r)
+	network, err := logic.GetNetwork(params["network"])
 	if err != nil {
-		logger.Log(0, r.Header.Get("user"),
-			fmt.Sprintf("failed to update node info [ %s ] info: %v", nodeid, err))
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		logger.Log(0, "error retrieving network:  ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 	}
-	if relayupdate {
-		updatenodes := logic.UpdateRelay(currentNode.Network, currentNode.RelayAddrs, newNode.RelayAddrs)
-		if len(updatenodes) > 0 {
-			for _, relayedNode := range updatenodes {
-				runUpdates(&relayedNode, false)
-			}
-		}
-	}
-	if relayedUpdate {
-		updateRelay(&currentNode, &newNode)
+	key, err := logic.CreateAccessKey(models.AccessKey{}, network)
+	if err != nil {
+		logger.Log(0, "error creating key:  ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
 	}
-	if servercfg.IsDNSMode() {
-		logic.SetDNS()
+	data.Key = key.Value
+	payload, err := json.Marshal(data)
+	if err != nil {
+		logger.Log(0, "error encoding data:  ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
 	}
-
-	logger.Log(1, r.Header.Get("user"), "updated node", currentNode.ID.String(), "on network", currentNode.Network)
-	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(newNode)
-
-	runUpdates(&newNode, ifaceDelta)
+	r.Body = io.NopCloser(strings.NewReader(string(payload)))
+	r.ContentLength = int64(len(string(payload)))
+	createNode(w, r)
 }
 
 // swagger:route PUT /api/nodes/{network}/{nodeid} nodes updateNode
@@ -965,6 +925,23 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 			}
 		}
 	}
+	host, err := logic.GetHost(newNode.HostID.String())
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"),
+			fmt.Sprintf("failed to get host for node  [ %s ] info: %v", nodeid, err))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	if newNode.IsIngressGateway {
+		host.ProxyEnabled = true
+		err := logic.UpsertHost(host)
+		if err != nil {
+			logger.Log(0, r.Header.Get("user"),
+				fmt.Sprintf("failed to update host [ %s ]: %v", host.ID.String(), err))
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+			return
+		}
+	}
 	relayedUpdate := false
 	if currentNode.IsRelayed && (currentNode.Address.String() != newNode.Address.String() || currentNode.Address6.String() != newNode.Address6.String()) {
 		relayedUpdate = true
@@ -977,7 +954,7 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 	ifaceDelta := logic.IfaceDelta(&currentNode, newNode)
 
 	if ifaceDelta && servercfg.Is_EE {
-		if err = logic.EnterpriseResetAllPeersFailovers(currentNode.ID.String(), currentNode.Network); err != nil {
+		if err = logic.EnterpriseResetAllPeersFailovers(currentNode.ID, currentNode.Network); err != nil {
 			logger.Log(0, "failed to reset failover lists during node update for node", currentNode.ID.String(), currentNode.Network)
 		}
 	}
@@ -1054,50 +1031,25 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal"))
 		return
 	}
-	if host.ProxyEnabled {
-		mq.ProxyUpdate(&proxy_models.ProxyManagerPayload{
-			Action:  proxy_models.DeleteNetwork,
-			Network: node.Network,
-		}, &node)
-	}
-	if fromNode {
-		// check if server should be removed from mq
-		// err is irrelevent
-		nodes, _ := logic.GetAllNodes()
-		var foundNode models.Node
-		for _, nodetocheck := range nodes {
-			if nodetocheck.HostID == node.HostID {
-				foundNode = nodetocheck
-				break
-			}
-		}
-		// TODO: Address how to remove host
-		if foundNode.HostID != uuid.Nil {
-			if err = logic.DissasociateNodeFromHost(&foundNode, host); err == nil {
-				currNets := logic.GetHostNetworks(host.ID.String())
-				if len(currNets) > 0 {
-					mq.ModifyClient(&mq.MqClient{
-						ID:       host.ID.String(),
-						Text:     host.Name,
-						Networks: currNets,
-					})
-				}
-			}
-		}
-	}
 	logic.ReturnSuccessResponse(w, r, nodeid+" deleted.")
 	logger.Log(1, r.Header.Get("user"), "Deleted node", nodeid, "from network", params["network"])
-	if !fromNode {
+	if fromNode { // update networks for host mq client
+		currNets := logic.GetHostNetworks(host.ID.String())
+		if len(currNets) > 0 {
+			mq.ModifyClient(&mq.MqClient{
+				ID:       host.ID.String(),
+				Text:     host.Name,
+				Networks: currNets,
+			})
+		}
+	} else { // notify node change
 		runUpdates(&node, false)
-		return
 	}
-	go func() {
-		if err := mq.PublishPeerUpdate(node.Network, false); err != nil {
+	go func() { // notify of peer change
+		if err := mq.PublishPeerUpdate(); err != nil {
 			logger.Log(1, "error publishing peer update ", err.Error())
-			return
 		}
 	}()
-
 }
 
 func runUpdates(node *models.Node, ifaceDelta bool) {

+ 80 - 0
controllers/relay.go

@@ -92,3 +92,83 @@ func deleteRelay(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(apiNode)
 	runUpdates(&node, true)
 }
+
+// swagger:route POST /api/hosts/{hostid}/relay hosts createHostRelay
+//
+// Create a relay.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: nodeResponse
+func createHostRelay(w http.ResponseWriter, r *http.Request) {
+	var relay models.HostRelayRequest
+	var params = mux.Vars(r)
+	w.Header().Set("Content-Type", "application/json")
+	err := json.NewDecoder(r.Body).Decode(&relay)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	relay.HostID = params["hostid"]
+	relayHost, _, err := logic.CreateHostRelay(relay)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"),
+			fmt.Sprintf("failed to create relay on host [%s]: %v", relay.HostID, err))
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+
+	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.PublishPeerUpdate(); err != nil {
+			logger.Log(0, "fail to publish peer update: ", err.Error())
+		}
+
+	}(relay.HostID)
+
+	apiHostData := relayHost.ConvertNMHostToAPI()
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(apiHostData)
+}
+
+// swagger:route DELETE /api/hosts/{hostid}/relay hosts deleteHostRelay
+//
+// Remove a relay.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: nodeResponse
+func deleteHostRelay(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+	var params = mux.Vars(r)
+	hostid := params["hostid"]
+	relayHost, _, err := logic.DeleteHostRelay(hostid)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	logger.Log(1, r.Header.Get("user"), "deleted relay host", hostid)
+	go func() {
+		if err := mq.PublishPeerUpdate(); err != nil {
+			logger.Log(0, "fail to publish peer update: ", err.Error())
+		}
+	}()
+	apiHostData := relayHost.ConvertNMHostToAPI()
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(apiHostData)
+}

+ 16 - 15
go.mod

@@ -16,10 +16,10 @@ require (
 	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.2.0 // indirect
-	golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094
-	golang.org/x/sys v0.2.0 // indirect
-	golang.org/x/text v0.4.0 // indirect
+	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.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31
 	google.golang.org/protobuf v1.28.1 // indirect
@@ -28,13 +28,13 @@ require (
 
 require (
 	filippo.io/edwards25519 v1.0.0
-	github.com/c-robinson/iplib v1.0.3
-	github.com/go-ping/ping v1.1.0
+	github.com/c-robinson/iplib v1.0.6
+	github.com/go-ping/ping v1.1.0 // indirect
 	github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0
 )
 
 require (
-	github.com/coreos/go-oidc/v3 v3.4.0
+	github.com/coreos/go-oidc/v3 v3.5.0
 	github.com/gorilla/websocket v1.5.0
 	github.com/pkg/errors v0.9.1
 	github.com/sirupsen/logrus v1.9.0
@@ -43,37 +43,38 @@ require (
 )
 
 require (
-	github.com/gravitl/netclient v0.0.0-20221228022055-5bdb0bc7861d
+	github.com/gravitl/netclient v0.0.0-20230114051017-65ecaeffca09
 	github.com/guumaster/tablewriter v0.0.10
 	github.com/matryer/is v1.4.0
 	github.com/olekukonko/tablewriter v0.0.5
-	github.com/spf13/cobra v1.6.0
+	github.com/spf13/cobra v1.6.1
 )
 
 require (
+	cloud.google.com/go/compute/metadata v0.2.1 // indirect
+	github.com/go-jose/go-jose/v3 v3.0.0 // indirect
 	github.com/inconshreveable/mousetrap v1.0.1 // indirect
-	github.com/rivo/uniseg v0.1.0 // indirect
+	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 )
 
 require (
-	cloud.google.com/go/compute v1.7.0 // indirect
+	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/golang/protobuf v1.5.2 // indirect
-	github.com/google/go-cmp v0.5.8 // indirect
+	github.com/google/go-cmp v0.5.9 // indirect
 	github.com/hashicorp/go-version v1.6.0
 	github.com/josharian/native v1.0.0 // indirect
 	github.com/leodido/go-urn v1.2.1 // indirect
-	github.com/mattn/go-runewidth v0.0.10 // indirect
+	github.com/mattn/go-runewidth v0.0.13 // indirect
 	github.com/mdlayher/genetlink v1.2.0 // indirect
 	github.com/mdlayher/netlink v1.6.0 // indirect
 	github.com/mdlayher/socket v0.1.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
-	golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
+	golang.org/x/sync v0.1.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	gopkg.in/square/go-jose.v2 v2.6.0 // indirect
 )

+ 41 - 598
go.sum

@@ -1,89 +1,18 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
-cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
-cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
-cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
-cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
-cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
-cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
-cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
-cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
-cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
-cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
-cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
-cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
-cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
-cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
-cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
-cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
-cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
-cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA=
-cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A=
-cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
-cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
-cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
-cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
-cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
-cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
-cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
-cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
-cloud.google.com/go/compute v1.7.0 h1:v/k9Eueb8aAJ0vZuxKMrgm6kPhCLZU9HxFU+AFDs9Uk=
-cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
-cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
-cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
-cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
-cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0=
+cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
+cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
+cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48=
+cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
 filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
 filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/c-robinson/iplib v1.0.3 h1:NG0UF0GoEsrC1/vyfX1Lx2Ss7CySWl3KqqXh3q4DdPU=
-github.com/c-robinson/iplib v1.0.3/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
-github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/c-robinson/iplib v1.0.6 h1:FfZV9BWNrah3BgLCFl5/nDXe4RbOi/C9n+DeXFOv5CQ=
+github.com/c-robinson/iplib v1.0.6/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
-github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g=
-github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw=
+github.com/coreos/go-oidc/v3 v3.5.0 h1:VxKtbccHZxs8juq7RdJntSqtXFtde9YpNpGn0yqgEHw=
+github.com/coreos/go-oidc/v3 v3.5.0/go.mod h1:ecXRtV4romGPeO6ieExAsUK9cb/3fp9hXNz1tlv8PIM=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 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=
@@ -94,24 +23,12 @@ 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=
 github.com/eclipse/paho.mqtt.golang v1.4.2 h1:66wOzfUHSSI1zamx7jR6yMEI5EuHnT1G6rNA5PM12m4=
 github.com/eclipse/paho.mqtt.golang v1.4.2/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA=
-github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
-github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
-github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
-github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
 github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+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=
@@ -124,87 +41,20 @@ github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJ
 github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
 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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
-github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
-github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
-github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
-github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
-github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
-github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
-github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
-github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+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/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
-github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
-github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
-github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
-github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
-github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
 github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
 github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
@@ -212,26 +62,18 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
 github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/gravitl/netclient v0.0.0-20221228022055-5bdb0bc7861d h1:sAVmZR4L1WB/dTr5nFuaRNzq3nvWSmLS5+Y+4TfVSuQ=
-github.com/gravitl/netclient v0.0.0-20221228022055-5bdb0bc7861d/go.mod h1:DC4NIT30g4E1deqofUtYzD3J4Rc+zyC5m+qA2w4g7Ns=
-github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/gravitl/netclient v0.0.0-20230114051017-65ecaeffca09 h1:T0gLl+i8whnrdwtW91R4u8x8bmqFVfPTU9WfBratkMc=
+github.com/gravitl/netclient v0.0.0-20230114051017-65ecaeffca09/go.mod h1:g3q+vhLySW/6smOsWsVy5LrxoW++f+kqiBAp9BM6sbY=
 github.com/guumaster/tablewriter v0.0.10 h1:A0HD94yMdt4usgxBjoEceNeE0XMJ027euoHAzsPqBQs=
 github.com/guumaster/tablewriter v0.0.10/go.mod h1:p4FRFhyfo0UD9ZLmMRbbJooTUsxo6b80qZTERVDWrH8=
 github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
 github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
 github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 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/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
@@ -248,8 +90,9 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
 github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
 github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
-github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
+github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
+github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
 github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 github.com/mdlayher/genetlink v1.2.0 h1:4yrIkRV5Wfk1WfpWTcoOlGmsWgQj3OtQN9ZsbrE+XtU=
@@ -272,11 +115,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0 h1:Y2hUrkfuM0on62KZOci/VLijlkdF/yeWU262BQgvcjE=
 github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+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/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
@@ -290,12 +131,11 @@ github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0
 github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
-github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI=
-github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
+github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
+github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -305,8 +145,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -320,292 +158,76 @@ github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
 github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
-github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
-go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
-go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 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/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 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-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
-golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
-golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
-golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
-golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
-golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
-golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8=
-golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+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/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/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-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
-golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/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-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/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=
-golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/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-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
-golang.org/x/sys v0.2.0/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/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/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5/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.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
-golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
-golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
-golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
-golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
 golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d/go.mod h1:5yyfuiqVIJ7t+3MqrpTQ+QqRkMWiESiyDvPNvKYCecg=
 golang.zx2c4.com/wintun v0.0.0-20211104114900-415007cec224/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
 golang.zx2c4.com/wireguard v0.0.0-20220202223031-3b95c81cc178/go.mod h1:TjUWrnD5ATh7bFvmm/ALEJZQ4ivKbETb6pmyj1vUoNI=
@@ -613,176 +235,10 @@ golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c h1:Okh6a1xpnJslG9M
 golang.zx2c4.com/wireguard v0.0.0-20220920152132-bb719d3a6e2c/go.mod h1:enML0deDxY1ux+B6ANGiwtg0yAJi1rctkTpcHNAVPyg=
 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31 h1:AgW3hljgTzuRbCB0j+q9tXT0uy6ij7vMjEzSCeMlQY0=
 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20220324164955-056925b7df31/go.mod h1:8P32Ilp1kCpwB4ItaHyvSk4xAtnpQ+8gQVfg5WaO1TU=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
-google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
-google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
-google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
-google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
-google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
-google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
-google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
-google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
-google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
-google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
-google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
-google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
-google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
-google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
-google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
-google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
-google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
-google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
-google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
-google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs=
-google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA=
-google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw=
-google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg=
-google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
-google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
-google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
-google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
-google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
-google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
-google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
-google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
-google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
-google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
-google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
-google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
-google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
-google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
-google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
-google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
-google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
-google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
-google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
-google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
-google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
-google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
-google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
-google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
-google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
-google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
-google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
-google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
-google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
-google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
-google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
-google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
-google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
-google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
-google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
-google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
-google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
-google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
-google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
-google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
-google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
-google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
-google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
@@ -791,23 +247,10 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
 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/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
-gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.3/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=
 gortc.io/stun v1.23.0/go.mod h1:XD5lpONVyjvV3BgOyJFNo0iv6R2oZB4L+weMqxts+zg=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

+ 27 - 17
k8s/server/README.md

@@ -9,9 +9,10 @@ You may want a more simple Kubernetes setup. We recommend [this community projec
 Your cluster must meet a few conditions to host a netmaker server. Primarily:  
 a) **Nodes:** You must have at least 3 worker nodes available for Netmaker to deploy. Netmaker nodes have anti-affinity and will not deploy on the same kubernetes node.  
 b) **Storage:** RWX and RWO storage classes must be available  
-c) **Ingress:** Ingress must be configured with certs. Nginx + LetsEncrypt configs are provided by default. In addition, Traefik allows special ingress for TCP that may be preferable for HA MQTT. Additionally, be sure to have a wildcard DNS entry for use with Ingress/Netmaker  
+c) **Ingress:** Ingress must be configured with certs. Nginx + LetsEncrypt configs are provided by default. Netmaker uses MQTT with Secure Websockets (WSS), and Nginx Ingress supports Websockets. If your ingress controller does not support websockets, you **must** configure your cluster to get traffic to MQ correctly (see below).  
+d) **DNS:** You must have a wildcard DNS entry for use with Ingress/Netmaker  
 e) **Helm:** For our Postgresql installation we rely on a helm chart, so you must have helm installed and configured.  
-d) **MQ Broker Considerations:** Our method uses a NodePort for MQ. This can be used either with or without an external load balancer configured. If deploying without a load balancer, you must specify a node to host MQ, and this will not be HA. If using a load balancer, be aware that LB configuration could lead MQ connections to be lost. If this happens, on the client you will see a log like "unable to connect to broker, retrying ..." In either case DNS must be configured to point broker.domain either to the LB or directly to the hosting node. Finally, you can use a special TCPIngressRoute if Traefik is your Ingress provider ([see this repo](https://github.com/geragcp/netmaker-k3s) for an example). This is ideal, but is not a standard k8s object, so we avoid it to make installations possible across an array of k8s configurations.  
+f) **MQ Broker Considerations:** If your Ingress Controller does not support Websockets, we provide an alternative method for MQ using a NodePort. This can be used either with or without an external load balancer configured. If deploying without a load balancer, you must specify a node to host MQ, and this will not be HA. If using a load balancer, be aware that LB configuration could lead MQ connections to be lost. If this happens, on the client you will see a log like "unable to connect to broker, retrying ..." In either case DNS must be configured to point broker.domain either to the LB or directly to the hosting node. Finally, you can use a special TCPIngressRoute if Traefik is your Ingress provider ([see this repo](https://github.com/geragcp/netmaker-k3s) for an example). This is ideal, but is not a standard k8s object, so we avoid it to make installations possible across an array of k8s configurations.  
 
 Assuming you are prepared for the above, we can begin to deploy Netmaker.  
 
@@ -21,31 +22,38 @@ Assuming you are prepared for the above, we can begin to deploy Netmaker.
 
 ### 2. Deploy Database
 
-Netmaker can use sqlite, postgres, or rqlite as a backing database. For HA, we recommend using Postgres, as Bitnami provides a reliable helm chart for deploying an HA pqsql cluster.
-  
-Follow these instructions:  
-https://github.com/bitnami/charts/tree/master/bitnami/postgresql-ha  
-  
+Netmaker can use sqlite, postgres, or rqlite as a backing database. For HA, we recommend using Postgres, as Bitnami provides a reliable helm chart for deploying an HA pqsql cluster. [See here for more details](https://github.com/bitnami/charts/tree/master/bitnami/postgresql-ha):  
+
+`helm repo add bitnami https://charts.bitnami.com/bitnami`  
 `helm install postgres bitnami/postgresql`
-  
+
+Confirm the database is running:
+
+`kubectl get pods`  
+
 Once completed, retrieve the password to access postgres:
 
 `kubectl get secret --namespace netmaker postgres-postgresql -o jsonpath="{.data.postgres-password}" | base64 -d`  
 
 ### 3. Deploy MQTT
 
-Based on the prerequisites, you will have either an external load balancer, traefik + TCPIngressRoute, or neither:  
-a) **External LB:** Configure your LB to load balance 31883/tcp to your nodes. This will be for MQ traffic. **Important:** make sure that latency is not significant between clients and MQ. In testing, we found that some load balancers introduce too much latency, causing MQ to be unuseable.  
-b) **Traefik:** If you have Traefik as your Ingress provider, create a TCPIngressRoute from 443 to mq service on port 8883. [Example Here](https://github.com/geragcp/netmaker-k3s/blob/main/08-ingress.yaml)  
-c) **Neither (default):** Choose a cluster node to house MQTT and then run the following:  
+Based on the prerequisites, you will have one of the following scenarios. Configure accordingly:
+
+    a) **Nginx:** Uncomment the Ingress section from mosquitto.yaml and replace NETMAKER_SUBDOMAIN with your domain.
+
+    b) **External LB:** Configure an LB to load balance TLS traffic to the MQ service on port 8883. If using a port other than 443, change the value of MQ_PORT in netmaker-server.yaml. The LB must support Secure Websockets (WSS), which requires a valid TLS certificate.
 
-c.a) `kubectl label node <your node name> mqhost=true`  
+    c) **Traefik:** If you have Traefik as your Ingress provider, create a TCPIngressRoute from 443 to mq service on port 8883. [Example Here](https://github.com/geragcp/netmaker-k3s/blob/main/08-ingress.yaml)  
 
-c.b) `sed -i 's/MQ_NODE_NAME/<your node name>/g' mosquitto.yaml`  
+------------------------------------------------------------------------------
 
-(If you are not using option C, comment out the pod affinity section from mosquitto.yaml.)
+Next, deploy MQ using the provided template (mosquitto.yaml). Modify the template:
 
-You also need an RWX storage class. Run the following to input your RWX storage class:
+    a) **Nginx:** Remove the pod affinity section and the NodePort section. Uncomment the Ingress section at the bottom. Then, substitute in your wildcard domain:
+        sed -i 's/NETMAKER_SUBDOMAIN/<your subdomain>/g' mosquitto.yaml
+    b or c) **Ex. LB or Traefik:** Remove the pod affinity section
+
+Then, substitute in your RWX storage class:
 
 `sed -i 's/RWX_STORAGE_CLASS/<your storage class name>/g' mosquitto.yaml`
 
@@ -57,7 +65,9 @@ MQ should be in CrashLoopBackoff until Netmaker is deployed. If it's in pending
 
 ### 4. Deploy Netmaker Server
 
-Make sure Wildcard DNS is set up for a netmaker subdomain, for instance: nm.mydomain.com. If you do not wish to use wildcard, edit the YAML file directly. Note you will need entries for broker.domain, api.domain, and dashboard.domain.  
+Make sure Wildcard DNS is set up for a netmaker subdomain, for instance: nm.mydomain.com. If you do not wish to use wildcard, edit the YAML file directly. Note you will need entries for broker.domain, api.domain, and dashboard.domain.
+
+If you are using Nginx as your ingress controller, uncomment the Nginx section at the bottom of the yaml file.
 
 `sed -i 's/NETMAKER_SUBDOMAIN/<your subdomain>/g' netmaker-server.yaml`  
 

+ 32 - 7
k8s/server/mosquitto.yaml

@@ -102,16 +102,15 @@ spec:
 apiVersion: v1
 data:
   mosquitto.conf: |
-    per_listener_settings true
+    per_listener_settings false
     listener 8883
+    protocol websockets
     allow_anonymous false
-    require_certificate true
-    use_identity_as_username true
-    cafile /mosquitto/certs/root.pem
-    certfile /mosquitto/certs/server.pem
-    keyfile /mosquitto/certs/server.key
     listener 1883
-    allow_anonymous true
+    protocol websockets
+    allow_anonymous false
+    plugin /usr/lib/mosquitto_dynamic_security.so
+    plugin_opt_config_file /mosquitto/data/dynamic-security.json
 kind: ConfigMap
 metadata:
   labels:
@@ -149,3 +148,29 @@ spec:
     protocol: TCP
     targetPort: 8883
     name: nm-mqtt
+# ---
+# apiVersion: networking.k8s.io/v1
+# kind: Ingress
+# metadata:
+#   name: nm-mqtt-ingress-nginx
+#   annotations:
+#     nginx.ingress.kubernetes.io/rewrite-target: /
+#     cert-manager.io/cluster-issuer: "letsencrypt-nginx"
+#     nginx.ingress.kubernetes.io/ssl-redirect: 'true'
+# spec:
+#   ingressClassName: nginx
+#   tls:
+#   - hosts:
+#     - broker.NETMAKER_SUBDOMAIN
+#     secretName: nm-mqtt-tls
+#   rules:
+#   - host: broker.NETMAKER_SUBDOMAIN
+#     http:
+#       paths:
+#       - path: /
+#         pathType: Prefix
+#         backend:
+#           service:
+#             name: netmaker-mqtt
+#             port:
+#               number: 8883

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

@@ -76,7 +76,7 @@ spec:
         - name: MQ_HOST
           value: "mq"
         - name: MQ_PORT
-          value: "31883"
+          value: "443"
         - name: MQ_SERVER_PORT
           value: "1883"
         - name: PLATFORM

+ 5 - 5
logic/extpeers.go

@@ -116,6 +116,7 @@ func GetExtClient(clientid string, network string) (models.ExtClient, error) {
 
 // CreateExtClient - creates an extclient
 func CreateExtClient(extclient *models.ExtClient) error {
+
 	if extclient.PrivateKey == "" {
 		privateKey, err := wgtypes.GeneratePrivateKey()
 		if err != nil {
@@ -130,16 +131,15 @@ func CreateExtClient(extclient *models.ExtClient) error {
 	if err != nil {
 		return err
 	}
-
 	if extclient.Address == "" {
 		if parentNetwork.IsIPv4 == "yes" {
-			newAddress, err := UniqueAddress(extclient.Network, false)
+			newAddress, err := UniqueAddress(extclient.Network, true)
 			if err != nil {
 				return err
 			}
 			extclient.Address = newAddress.String()
 
-			extclientInternalAddr, err := UniqueAddress(extclient.Network, true)
+			extclientInternalAddr, err := UniqueAddress(extclient.Network, false)
 			if err != nil {
 				return err
 			}
@@ -149,12 +149,12 @@ func CreateExtClient(extclient *models.ExtClient) error {
 
 	if extclient.Address6 == "" {
 		if parentNetwork.IsIPv6 == "yes" {
-			addr6, err := UniqueAddress6(extclient.Network, false)
+			addr6, err := UniqueAddress6(extclient.Network, true)
 			if err != nil {
 				return err
 			}
 			extclient.Address6 = addr6.String()
-			extclientInternalAddr6, err := UniqueAddress6(extclient.Network, true)
+			extclientInternalAddr6, err := UniqueAddress6(extclient.Network, false)
 			if err != nil {
 				return err
 			}

+ 77 - 44
logic/hosts.go

@@ -9,6 +9,7 @@ import (
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/crypto/bcrypt"
 )
 
@@ -146,59 +147,43 @@ func UpsertHost(h *models.Host) error {
 // RemoveHost - removes a given host from server
 func RemoveHost(h *models.Host) error {
 	if len(h.Nodes) > 0 {
-		for i := range h.Nodes {
-			id := h.Nodes[i]
-			n, err := GetNodeByID(id)
-			if err == nil {
-				if err = DissasociateNodeFromHost(&n, h); err != nil {
-					return err // must remove associated nodes before removing a host
-				}
-			}
-		}
+		return fmt.Errorf("host still has associated nodes")
 	}
 	return database.DeleteRecord(database.HOSTS_TABLE_NAME, h.ID.String())
 }
 
-// UpdateHostNetworks - updates a given host's networks
-func UpdateHostNetworks(h *models.Host, server string, nets []string) error {
-	if len(h.Nodes) > 0 {
-		for i := range h.Nodes {
-			n, err := GetNodeByID(h.Nodes[i])
-			if err != nil {
-				return err
-			}
-			// loop through networks and remove need for updating existing networks
-			found := false
-			for j := range nets {
-				if len(nets[j]) > 0 && nets[j] == n.Network {
-					nets[j] = "" // mark as ignore
-					found = true
-				}
-			}
-			if !found { // remove the node/host from that network
-				if err = DissasociateNodeFromHost(&n, h); err != nil {
-					return err
-				}
+// RemoveHostByID - removes a given host by id from server
+func RemoveHostByID(hostID string) error {
+	return database.DeleteRecord(database.HOSTS_TABLE_NAME, hostID)
+}
+
+// UpdateHostNetwork - adds/deletes host from a network
+func UpdateHostNetwork(h *models.Host, network string, add bool) (*models.Node, error) {
+	for _, nodeID := range h.Nodes {
+		node, err := GetNodeByID(nodeID)
+		if err != nil || node.PendingDelete {
+			continue
+		}
+		if node.Network == network {
+			if !add {
+				return &node, nil
+			} else {
+				return nil, errors.New("host already part of network " + network)
 			}
+
 		}
-	} else {
-		h.Nodes = []string{}
 	}
-
-	for i := range nets {
-		// create a node for each non zero network remaining
-		if len(nets[i]) > 0 {
-			newNode := models.Node{}
-			newNode.Server = server
-			newNode.Network = nets[i]
-			if err := AssociateNodeToHost(&newNode, h); err != nil {
-				return err
-			}
-			logger.Log(1, "added new node", newNode.ID.String(), "to host", h.Name)
+	if !add {
+		return nil, errors.New("host not part of the network " + network)
+	} else {
+		newNode := models.Node{}
+		newNode.Server = servercfg.GetServer()
+		newNode.Network = network
+		if err := AssociateNodeToHost(&newNode, h); err != nil {
+			return nil, err
 		}
+		return &newNode, nil
 	}
-
-	return nil
 }
 
 // AssociateNodeToHost - associates and creates a node with a given host
@@ -249,6 +234,28 @@ func DissasociateNodeFromHost(n *models.Node, h *models.Host) error {
 	return UpsertHost(h)
 }
 
+// DisassociateAllNodesFromHost - deletes all nodes of the host
+func DisassociateAllNodesFromHost(hostID string) error {
+	host, err := GetHost(hostID)
+	if err != nil {
+		return err
+	}
+	for _, nodeID := range host.Nodes {
+		node, err := GetNodeByID(nodeID)
+		if err != nil {
+			logger.Log(0, "failed to get host node", err.Error())
+			continue
+		}
+		if err := DeleteNode(&node, true); err != nil {
+			logger.Log(0, "failed to delete node", node.ID.String(), err.Error())
+			continue
+		}
+		logger.Log(3, "deleted node", node.ID.String(), "of host", host.ID.String())
+	}
+	host.Nodes = []string{}
+	return UpsertHost(host)
+}
+
 // GetDefaultHosts - retrieve all hosts marked as default from DB
 func GetDefaultHosts() []models.Host {
 	defaultHostList := []models.Host{}
@@ -295,3 +302,29 @@ func GetHostNetworks(hostID string) []string {
 	}
 	return nets
 }
+
+// GetRelatedHosts - fetches related hosts of a given host
+func GetRelatedHosts(hostID string) []models.Host {
+	relatedHosts := []models.Host{}
+	networks := GetHostNetworks(hostID)
+	networkMap := make(map[string]struct{})
+	for _, network := range networks {
+		networkMap[network] = struct{}{}
+	}
+	hosts, err := GetAllHosts()
+	if err == nil {
+		for _, host := range hosts {
+			if host.ID.String() == hostID {
+				continue
+			}
+			networks := GetHostNetworks(host.ID.String())
+			for _, network := range networks {
+				if _, ok := networkMap[network]; ok {
+					relatedHosts = append(relatedHosts, host)
+					break
+				}
+			}
+		}
+	}
+	return relatedHosts
+}

+ 16 - 36
logic/metrics/metrics.go

@@ -3,9 +3,7 @@ package metrics
 import (
 	"time"
 
-	"github.com/go-ping/ping"
 	proxy_metrics "github.com/gravitl/netclient/nmproxy/metrics"
-	proxy_models "github.com/gravitl/netclient/nmproxy/models"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
@@ -13,7 +11,7 @@ import (
 )
 
 // Collect - collects metrics
-func Collect(iface, network string, proxy bool, peerMap models.PeerMap) (*models.Metrics, error) {
+func Collect(iface, server, network string, peerMap models.PeerMap) (*models.Metrics, error) {
 	var metrics models.Metrics
 	metrics.Connectivity = make(map[string]models.Metric)
 	var wgclient, err = wgctrl.New()
@@ -27,49 +25,30 @@ func Collect(iface, network string, proxy bool, peerMap models.PeerMap) (*models
 		fillUnconnectedData(&metrics, peerMap)
 		return &metrics, err
 	}
-	metrics.ProxyMetrics = make(map[string]proxy_models.Metric)
-
 	// TODO handle freebsd??
 	for i := range device.Peers {
 		currPeer := device.Peers[i]
+		if _, ok := peerMap[currPeer.PublicKey.String()]; !ok {
+			continue
+		}
 		id := peerMap[currPeer.PublicKey.String()].ID
 		address := peerMap[currPeer.PublicKey.String()].Address
 		if id == "" || address == "" {
 			logger.Log(0, "attempted to parse metrics for invalid peer from server", id, address)
 			continue
 		}
+		proxyMetrics := proxy_metrics.GetMetric(server, currPeer.PublicKey.String())
 		var newMetric = models.Metric{
 			NodeName: peerMap[currPeer.PublicKey.String()].Name,
-			IsServer: peerMap[currPeer.PublicKey.String()].IsServer,
 		}
 		logger.Log(2, "collecting metrics for peer", address)
-		newMetric.TotalReceived = currPeer.ReceiveBytes
-		newMetric.TotalSent = currPeer.TransmitBytes
-
-		// get latency
-		pinger, err := ping.NewPinger(address)
-		if err != nil {
-			logger.Log(0, "could not initiliaze ping for metrics on peer address", address, err.Error())
-			newMetric.Connected = false
-			newMetric.Latency = 999
-		} else {
-			pinger.Count = 1
-			pinger.Timeout = time.Second * 2
-			err = pinger.Run()
-			if err != nil {
-				logger.Log(0, "failed ping for metrics on peer address", address, err.Error())
-				newMetric.Connected = false
-				newMetric.Latency = 999
-			} else {
-				pingStats := pinger.Statistics()
-				if pingStats.PacketsRecv > 0 {
-					newMetric.Uptime = 1
-					newMetric.Connected = true
-					newMetric.Latency = pingStats.AvgRtt.Milliseconds()
-				}
-			}
+		newMetric.TotalReceived = int64(proxyMetrics.TrafficRecieved)
+		newMetric.TotalSent = int64(proxyMetrics.TrafficSent)
+		newMetric.Latency = int64(proxyMetrics.LastRecordedLatency)
+		newMetric.Connected = proxyMetrics.NodeConnectionStatus[id]
+		if newMetric.Connected {
+			newMetric.Uptime = 1
 		}
-
 		// check device peer to see if WG is working if ping failed
 		if !newMetric.Connected {
 			if currPeer.ReceiveBytes > 0 &&
@@ -79,11 +58,13 @@ func Collect(iface, network string, proxy bool, peerMap models.PeerMap) (*models
 				newMetric.Uptime = 1
 			}
 		}
-
 		newMetric.TotalTime = 1
 		metrics.Connectivity[id] = newMetric
-		metrics.ProxyMetrics[id] = proxy_metrics.GetMetric(network, currPeer.PublicKey.String())
-		proxy_metrics.ResetMetricsForPeer(network, currPeer.PublicKey.String())
+		if len(proxyMetrics.NodeConnectionStatus) == 1 {
+			proxy_metrics.ResetMetricsForPeer(server, currPeer.PublicKey.String())
+		} else {
+			proxy_metrics.ResetMetricForNode(server, currPeer.PublicKey.String(), id)
+		}
 	}
 
 	fillUnconnectedData(&metrics, peerMap)
@@ -133,7 +114,6 @@ func fillUnconnectedData(metrics *models.Metrics, peerMap models.PeerMap) {
 		if !metrics.Connectivity[id].Connected {
 			newMetric := models.Metric{
 				NodeName:  peerMap[r].Name,
-				IsServer:  peerMap[r].IsServer,
 				Uptime:    0,
 				TotalTime: 1,
 				Connected: false,

+ 29 - 11
logic/networks.go

@@ -211,25 +211,43 @@ func IsIPUnique(network string, ip string, tableName string, isIpv6 bool) bool {
 
 	isunique := true
 	collection, err := database.FetchRecords(tableName)
-
 	if err != nil {
 		return isunique
 	}
 
 	for _, value := range collection { // filter
-		var node models.Node
-		if err = json.Unmarshal([]byte(value), &node); err != nil {
-			continue
-		}
-		if isIpv6 {
-			if node.Address6.IP.String() == ip && node.Network == network {
-				return false
+
+		if tableName == database.NODES_TABLE_NAME {
+			var node models.Node
+			if err = json.Unmarshal([]byte(value), &node); err != nil {
+				continue
 			}
-		} else {
-			if node.Address.IP.String() == ip && node.Network == network {
-				return false
+			if isIpv6 {
+				if node.Address6.IP.String() == ip && node.Network == network {
+					return false
+				}
+			} else {
+				if node.Address.IP.String() == ip && node.Network == network {
+					return false
+				}
+			}
+		} else if tableName == database.EXT_CLIENT_TABLE_NAME {
+			var extClient models.ExtClient
+			if err = json.Unmarshal([]byte(value), &extClient); err != nil {
+				continue
+			}
+			if isIpv6 {
+				if (extClient.Address6 == ip || extClient.InternalIPAddr6 == ip) && extClient.Network == network {
+					return false
+				}
+
+			} else {
+				if (extClient.Address == ip || extClient.InternalIPAddr == ip) && extClient.Network == network {
+					return false
+				}
 			}
 		}
+
 	}
 
 	return isunique

+ 8 - 5
logic/nodes.go

@@ -48,9 +48,9 @@ func GetNetworkNodes(network string) ([]models.Node, error) {
 
 // UpdateNode - takes a node and updates another node with it's values
 func UpdateNode(currentNode *models.Node, newNode *models.Node) error {
-	if newNode.Address.String() != currentNode.Address.String() {
+	if newNode.Address.IP.String() != currentNode.Address.IP.String() {
 		if network, err := GetParentNetwork(newNode.Network); err == nil {
-			if !IsAddressInCIDR(newNode.Address.String(), network.AddressRange) {
+			if !IsAddressInCIDR(newNode.Address.IP.String(), network.AddressRange) {
 				return fmt.Errorf("invalid address provided; out of network range for node %s", newNode.ID)
 			}
 		}
@@ -82,7 +82,7 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error {
 	return fmt.Errorf("failed to update node " + currentNode.ID.String() + ", cannot change ID.")
 }
 
-// DeleteNode - marks node for deletion if called by UI or deletes node if called by node
+// DeleteNode - marks node for deletion (and adds to zombie list) if called by UI or deletes node if called by node
 func DeleteNode(node *models.Node, purge bool) error {
 	node.Action = models.NODE_DELETE
 	if !purge {
@@ -91,6 +91,7 @@ func DeleteNode(node *models.Node, purge bool) error {
 		if err := UpdateNode(node, &newnode); err != nil {
 			return err
 		}
+		newZombie <- node.ID
 		return nil
 	}
 	host, err := GetHost(node.HostID.String())
@@ -101,7 +102,7 @@ func DeleteNode(node *models.Node, purge bool) error {
 		return err
 	}
 	if servercfg.Is_EE {
-		if err := EnterpriseResetAllPeersFailovers(node.ID.String(), node.Network); err != nil {
+		if err := EnterpriseResetAllPeersFailovers(node.ID, node.Network); err != nil {
 			logger.Log(0, "failed to reset failover lists during node delete for node", host.Name, node.Network)
 		}
 	}
@@ -200,8 +201,10 @@ func GetAllNodes() ([]models.Node, error) {
 
 	for _, value := range collection {
 		var node models.Node
+		// ignore legacy nodes in database
 		if err := json.Unmarshal([]byte(value), &node); err != nil {
-			return []models.Node{}, err
+			logger.Log(1, "legacy node detected: ", err.Error())
+			continue
 		}
 		// add node to our array
 		nodes = append(nodes, node)

+ 277 - 89
logic/peers.go

@@ -10,7 +10,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/c-robinson/iplib"
 	proxy_models "github.com/gravitl/netclient/nmproxy/models"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
@@ -50,7 +49,7 @@ func GetPeersForProxy(node *models.Node, onlyPeers bool) (proxy_models.ProxyMana
 				if err != nil {
 					logger.Log(0, "error retrieving host for relay node", relayNode.HostID.String(), err.Error())
 				}
-				relayEndpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, host.LocalListenPort))
+				relayEndpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, host.ListenPort))
 				if err != nil {
 					logger.Log(1, "failed to resolve relay node endpoint: ", err.Error())
 				}
@@ -79,7 +78,7 @@ func GetPeersForProxy(node *models.Node, onlyPeers bool) (proxy_models.ProxyMana
 						if err != nil {
 							logger.Log(0, "error retrieving host for relayNode", relayedNode.ID.String(), err.Error())
 						}
-						relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, host.LocalListenPort))
+						relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, host.ListenPort))
 						if udpErr == nil {
 							relayPeersMap[host.PublicKey.String()] = proxy_models.RelayedConf{
 								RelayedPeerEndpoint: relayedEndpoint,
@@ -107,7 +106,7 @@ func GetPeersForProxy(node *models.Node, onlyPeers bool) (proxy_models.ProxyMana
 			continue
 		}
 		proxyStatus := host.ProxyEnabled
-		listenPort := host.LocalListenPort
+		listenPort := host.ListenPort
 		if proxyStatus {
 			listenPort = host.ProxyListenPort
 			if listenPort == 0 {
@@ -123,7 +122,7 @@ func GetPeersForProxy(node *models.Node, onlyPeers bool) (proxy_models.ProxyMana
 			logger.Log(1, "failed to resolve udp addr for node: ", peer.ID.String(), host.EndpointIP.String(), err.Error())
 			continue
 		}
-		allowedips := GetAllowedIPs(node, &peer, nil, false)
+		allowedips := GetAllowedIPs(node, &peer, nil)
 		var keepalive time.Duration
 		if node.PersistentKeepalive != 0 {
 			// set_keepalive
@@ -150,7 +149,7 @@ func GetPeersForProxy(node *models.Node, onlyPeers bool) (proxy_models.ProxyMana
 					logger.Log(0, "error retrieving host for relayNode", relayNode.ID.String(), err.Error())
 					continue
 				}
-				relayTo, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, relayHost.LocalListenPort))
+				relayTo, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, relayHost.ListenPort))
 				if err == nil {
 					peerConfMap[host.PublicKey.String()] = proxy_models.PeerConf{
 
@@ -182,10 +181,9 @@ func GetPeersForProxy(node *models.Node, onlyPeers bool) (proxy_models.ProxyMana
 	if addr.String() == "" {
 		addr = node.Address6
 	}
-	proxyPayload.WgAddr = addr.String()
 	proxyPayload.Peers = peers
 	proxyPayload.PeerMap = peerConfMap
-	proxyPayload.Network = node.Network
+	//proxyPayload.Network = node.Network
 	//proxyPayload.InterfaceName = node.Interface
 	//hardcode or read from host ??
 	proxyPayload.InterfaceName = models.WIREGUARD_INTERFACE
@@ -193,6 +191,257 @@ func GetPeersForProxy(node *models.Node, onlyPeers bool) (proxy_models.ProxyMana
 	return proxyPayload, nil
 }
 
+// GetProxyUpdateForHost - gets the proxy update for host
+func GetProxyUpdateForHost(host *models.Host) (proxy_models.ProxyManagerPayload, error) {
+	proxyPayload := proxy_models.ProxyManagerPayload{
+		Action: proxy_models.ProxyUpdate,
+	}
+	peerConfMap := make(map[string]proxy_models.PeerConf)
+	if host.IsRelayed {
+		relayHost, err := GetHost(host.RelayedBy)
+		if err == nil {
+			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())
+			}
+			proxyPayload.IsRelayed = true
+			proxyPayload.RelayedTo = relayEndpoint
+		} else {
+			logger.Log(0, "couldn't find relay host for:  ", host.ID.String())
+		}
+
+	}
+	if host.IsRelay {
+		relayedHosts := GetRelayedHosts(host)
+		relayPeersMap := make(map[string]proxy_models.RelayedConf)
+		for _, relayedHost := range relayedHosts {
+			payload, err := GetPeerUpdateForHost(&relayedHost)
+			if err == nil {
+				relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, getPeerListenPort(&relayedHost)))
+				if udpErr == nil {
+					relayPeersMap[relayedHost.PublicKey.String()] = proxy_models.RelayedConf{
+						RelayedPeerEndpoint: relayedEndpoint,
+						RelayedPeerPubKey:   relayedHost.PublicKey.String(),
+						Peers:               payload.Peers,
+					}
+				}
+
+			}
+		}
+		proxyPayload.IsRelay = true
+		proxyPayload.RelayedPeerConf = relayPeersMap
+
+	}
+	var ingressStatus bool
+	for _, nodeID := range host.Nodes {
+
+		node, err := GetNodeByID(nodeID)
+		if err != nil {
+			continue
+		}
+		currentPeers, err := GetNetworkNodes(node.Network)
+		if err != nil {
+			continue
+		}
+		for _, peer := range currentPeers {
+			if peer.ID == node.ID {
+				//skip yourself
+				continue
+			}
+			peerHost, err := GetHost(peer.HostID.String())
+			if err != nil {
+				continue
+			}
+
+			var currPeerConf proxy_models.PeerConf
+			var found bool
+			if currPeerConf, found = peerConfMap[peerHost.PublicKey.String()]; !found {
+				currPeerConf = proxy_models.PeerConf{
+					Proxy:            peerHost.ProxyEnabled,
+					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)))
+					if err == nil {
+						currPeerConf.IsRelayed = true
+						currPeerConf.RelayedTo = relayTo
+					}
+
+				}
+			}
+
+			peerConfMap[peerHost.PublicKey.String()] = currPeerConf
+		}
+		if node.IsIngressGateway {
+			ingressStatus = true
+			_, peerConfMap, err = getExtPeersForProxy(&node, peerConfMap)
+			if err == nil {
+
+			} else if !database.IsEmptyRecord(err) {
+				logger.Log(1, "error retrieving external clients:", err.Error())
+			}
+		}
+
+	}
+	proxyPayload.IsIngress = ingressStatus
+	proxyPayload.PeerMap = peerConfMap
+	return proxyPayload, nil
+}
+
+// GetPeerUpdateForHost - gets the consolidated peer update for the host from all networks
+func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
+	if host == nil {
+		return models.HostPeerUpdate{}, errors.New("host is nil")
+	}
+	hostPeerUpdate := models.HostPeerUpdate{
+		Host:          *host,
+		Network:       make(map[string]models.NetworkInfo),
+		PeerIDs:       make(models.HostPeerMap),
+		ServerVersion: servercfg.GetVersion(),
+		ServerAddrs:   []models.ServerAddr{},
+	}
+	log.Println("peer update for host ", host.ID.String())
+	peerIndexMap := make(map[string]int)
+	for _, nodeID := range host.Nodes {
+		node, err := GetNodeByID(nodeID)
+		if err != nil {
+			continue
+		}
+		if !node.Connected || node.Action == models.NODE_DELETE || node.PendingDelete {
+			continue
+		}
+		hostPeerUpdate.Network[node.Network] = models.NetworkInfo{
+			DNS: getPeerDNS(node.Network),
+		}
+		currentPeers, err := GetNetworkNodes(node.Network)
+		if err != nil {
+			log.Println("no network nodes")
+			return models.HostPeerUpdate{}, err
+		}
+		for _, peer := range currentPeers {
+			if peer.ID == node.ID {
+				log.Println("peer update, skipping self")
+				//skip yourself
+
+				continue
+			}
+			var peerConfig wgtypes.PeerConfig
+			peerHost, err := GetHost(peer.HostID.String())
+			if err != nil {
+				log.Println("no peer host", err)
+				return models.HostPeerUpdate{}, err
+			}
+
+			if !peer.Connected {
+				log.Println("peer update, skipping unconnected node")
+				//skip unconnected nodes
+				continue
+			}
+			if !nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) {
+				log.Println("peer update, skipping node for acl")
+				//skip if not permitted by acl
+				continue
+			}
+			peerConfig.PublicKey = peerHost.PublicKey
+			peerConfig.PersistentKeepaliveInterval = &peer.PersistentKeepalive
+			peerConfig.ReplaceAllowedIPs = true
+			uselocal := false
+			if host.EndpointIP.String() == peerHost.EndpointIP.String() {
+				//peer is on same network
+				// set to localaddress
+				uselocal = true
+				if node.LocalAddress.IP == nil {
+					// use public endpint
+					uselocal = false
+				}
+				if node.LocalAddress.String() == peer.LocalAddress.String() {
+					uselocal = false
+				}
+			}
+			peerConfig.Endpoint = &net.UDPAddr{
+				IP:   peerHost.EndpointIP,
+				Port: getPeerListenPort(peerHost),
+			}
+
+			if uselocal {
+				peerConfig.Endpoint.IP = peer.LocalAddress.IP
+			}
+			allowedips := GetAllowedIPs(&node, &peer, nil)
+			if peer.IsIngressGateway {
+				for _, entry := range peer.IngressGatewayRange {
+					_, cidr, err := net.ParseCIDR(string(entry))
+					if err == nil {
+						allowedips = append(allowedips, *cidr)
+					}
+				}
+			}
+			if peer.IsEgressGateway {
+				allowedips = append(allowedips, getEgressIPs(&node, &peer)...)
+			}
+			peerConfig.AllowedIPs = allowedips
+
+			if _, ok := hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()]; !ok {
+				hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()] = make(map[string]models.IDandAddr)
+				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
+				peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
+				hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
+					ID:      peer.ID.String(),
+					Address: peer.PrimaryAddress(),
+					Name:    peerHost.Name,
+					Network: peer.Network,
+				}
+			} else {
+				peerAllowedIPs := hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs
+				peerAllowedIPs = append(peerAllowedIPs, allowedips...)
+				hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]].AllowedIPs = peerAllowedIPs
+				hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
+					ID:      peer.ID.String(),
+					Address: peer.PrimaryAddress(),
+					Name:    peerHost.Name,
+					Network: peer.Network,
+				}
+			}
+
+		}
+		if node.IsIngressGateway {
+			extPeers, extPeerIDAndAddrs, err := getExtPeers(&node, true)
+			if err == nil {
+				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, extPeers...)
+				for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
+					hostPeerUpdate.PeerIDs[extPeerIdAndAddr.ID] = make(map[string]models.IDandAddr)
+					hostPeerUpdate.PeerIDs[extPeerIdAndAddr.ID][extPeerIdAndAddr.ID] = models.IDandAddr{
+						ID:      extPeerIdAndAddr.ID,
+						Address: extPeerIdAndAddr.Address,
+						Name:    extPeerIdAndAddr.Name,
+						Network: node.Network,
+					}
+				}
+
+			} else if !database.IsEmptyRecord(err) {
+				logger.Log(1, "error retrieving external clients:", err.Error())
+			}
+		}
+	}
+
+	return hostPeerUpdate, nil
+}
+
+func getPeerListenPort(host *models.Host) int {
+	peerPort := host.ListenPort
+	if host.ProxyEnabled {
+		if host.PublicListenPort != 0 {
+			peerPort = host.PublicListenPort
+		} else if host.ProxyListenPort != 0 {
+			peerPort = host.ProxyListenPort
+		}
+	}
+	return peerPort
+}
+
 // GetPeerUpdate - gets a wireguard peer config for each peer of a node
 func GetPeerUpdate(node *models.Node, host *models.Host) (models.PeerUpdate, error) {
 	log.Println("peer update for node ", node.ID)
@@ -200,6 +449,7 @@ func GetPeerUpdate(node *models.Node, host *models.Host) (models.PeerUpdate, err
 		Network:       node.Network,
 		ServerVersion: ncutils.Version,
 		DNS:           getPeerDNS(node.Network),
+		PeerIDs:       make(models.PeerMap),
 	}
 	currentPeers, err := GetNetworkNodes(node.Network)
 	if err != nil {
@@ -249,8 +499,8 @@ func GetPeerUpdate(node *models.Node, host *models.Host) (models.PeerUpdate, err
 			IP:   peerHost.EndpointIP,
 			Port: peerHost.ListenPort,
 		}
-		if !host.ProxyEnabled && peerHost.ProxyEnabled {
-			peerConfig.Endpoint.Port = peerHost.ProxyListenPort
+		if peerHost.ProxyEnabled {
+			peerConfig.Endpoint.Port = getPeerListenPort(peerHost)
 		}
 		if uselocal {
 			peerConfig.Endpoint.IP = peer.LocalAddress.IP
@@ -264,12 +514,16 @@ func GetPeerUpdate(node *models.Node, host *models.Host) (models.PeerUpdate, err
 				}
 			}
 		}
-		if peer.IsRelay {
-			allowedips = append(allowedips, getRelayAllowedIPs(node, &peer)...)
-		}
 		if peer.IsEgressGateway {
 			allowedips = append(allowedips, getEgressIPs(node, &peer)...)
 		}
+
+		peerUpdate.PeerIDs[peerHost.PublicKey.String()] = models.IDandAddr{
+			ID:      peer.ID.String(),
+			Address: peer.PrimaryAddress(),
+			Name:    peerHost.Name,
+			Network: peer.Network,
+		}
 		peerConfig.AllowedIPs = allowedips
 		peerUpdate.Peers = append(peerUpdate.Peers, peerConfig)
 	}
@@ -378,8 +632,8 @@ func GetPeerUpdateLegacy(node *models.Node) (models.PeerUpdate, error) {
 			// set_local
 			if node.LocalAddress.String() != peer.LocalAddress.String() && peer.LocalAddress.IP != nil {
 				peerHost.EndpointIP = peer.LocalAddress.IP
-				if peerHost.LocalListenPort != 0 {
-					peerHost.ListenPort = peerHost.LocalListenPort
+				if peerHost.ListenPort != 0 {
+					peerHost.ListenPort = getPeerListenPort(peerHost)
 				}
 			} else {
 				continue
@@ -411,8 +665,8 @@ func GetPeerUpdateLegacy(node *models.Node) (models.PeerUpdate, error) {
 			// if udp hole punching is on, but udp hole punching did not set it, use the LocalListenPort instead
 			// 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.LocalListenPort != 0 {
-				peerHost.ListenPort = peerHost.LocalListenPort
+			if ((!setUDPPort) || peerHost.ListenPort == 0) && peerHost.ListenPort != 0 {
+				peerHost.ListenPort = getPeerListenPort(peerHost)
 			}
 
 			endpoint := peerHost.EndpointIP.String() + ":" + strconv.FormatInt(int64(peerHost.ListenPort), 10)
@@ -421,14 +675,9 @@ func GetPeerUpdateLegacy(node *models.Node) (models.PeerUpdate, error) {
 				return models.PeerUpdate{}, err
 			}
 		}
-		fetchRelayedIps := true
-		if host.ProxyEnabled {
-			fetchRelayedIps = false
-		}
-		allowedips := GetAllowedIPs(node, &peer, metrics, fetchRelayedIps)
+		allowedips := GetAllowedIPs(node, &peer, metrics)
 		var keepalive time.Duration
 		if node.PersistentKeepalive != 0 {
-
 			// set_keepalive
 			keepalive = node.PersistentKeepalive
 		}
@@ -549,6 +798,7 @@ func getExtPeers(node *models.Node, forIngressNode bool) ([]wgtypes.PeerConfig,
 		peers = append(peers, peer)
 		idsAndAddr = append(idsAndAddr, models.IDandAddr{
 			ID:      peer.PublicKey.String(),
+			Name:    extPeer.ClientID,
 			Address: primaryAddr,
 		})
 	}
@@ -614,14 +864,6 @@ func getExtPeersForProxy(node *models.Node, proxyPeerConf map[string]proxy_model
 			Address:       net.ParseIP(extPeer.Address),
 			ExtInternalIp: net.ParseIP(extInternalPrimaryAddr),
 		}
-		if extPeer.IngressGatewayID == node.ID.String() {
-			extConf.IsAttachedExtClient = true
-		}
-		ingGatewayUdpAddr, err := net.ResolveUDPAddr("udp", extPeer.IngressGatewayEndpoint)
-		if err == nil {
-			extConf.IngressGatewayEndPoint = ingGatewayUdpAddr
-		}
-
 		proxyPeerConf[peer.PublicKey.String()] = extConf
 
 		peers = append(peers, peer)
@@ -631,7 +873,7 @@ func getExtPeersForProxy(node *models.Node, proxyPeerConf map[string]proxy_model
 }
 
 // GetAllowedIPs - calculates the wireguard allowedip field for a peer of a node based on the peer and node settings
-func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics, fetchRelayedIps bool) []net.IPNet {
+func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics) []net.IPNet {
 	var allowedips []net.IPNet
 	allowedips = getNodeAllowedIPs(peer, node)
 
@@ -666,61 +908,6 @@ func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics, fetchRelaye
 			}
 		}
 	}
-	// handle relay gateway peers
-	if fetchRelayedIps && peer.IsRelay {
-		for _, ip := range peer.RelayAddrs {
-			//find node ID of relayed peer
-			relayedPeer, err := findNode(ip)
-			if err != nil {
-				logger.Log(0, "failed to find node for ip ", ip, err.Error())
-				continue
-			}
-			if relayedPeer == nil {
-				continue
-			}
-			if relayedPeer.ID == node.ID {
-				//skip self
-				continue
-			}
-			//check if acl permits comms
-			if !nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(relayedPeer.ID.String())) {
-				continue
-			}
-			if iplib.Version(net.ParseIP(ip)) == 4 {
-				relayAddr := net.IPNet{
-					IP:   net.ParseIP(ip),
-					Mask: net.CIDRMask(32, 32),
-				}
-				allowedips = append(allowedips, relayAddr)
-			}
-			if iplib.Version(net.ParseIP(ip)) == 6 {
-				relayAddr := net.IPNet{
-					IP:   net.ParseIP(ip),
-					Mask: net.CIDRMask(128, 128),
-				}
-				allowedips = append(allowedips, relayAddr)
-			}
-			relayedNode, err := findNode(ip)
-			if err != nil {
-				logger.Log(1, "unable to find node for relayed address", ip, err.Error())
-				continue
-			}
-			if relayedNode.IsEgressGateway {
-				extAllowedIPs := getEgressIPs(node, relayedNode)
-				allowedips = append(allowedips, extAllowedIPs...)
-			}
-			if relayedNode.IsIngressGateway {
-				extPeers, _, err := getExtPeers(relayedNode, false)
-				if err == nil {
-					for _, extPeer := range extPeers {
-						allowedips = append(allowedips, extPeer.AllowedIPs...)
-					}
-				} else {
-					logger.Log(0, "failed to retrieve extclients from relayed ingress", err.Error())
-				}
-			}
-		}
-	}
 	return allowedips
 }
 
@@ -731,6 +918,7 @@ func getPeerDNS(network string) string {
 			host, err := GetHost(node.HostID.String())
 			if err != nil {
 				logger.Log(0, "error retrieving host for node", node.ID.String(), err.Error())
+				continue
 			}
 			dns = dns + fmt.Sprintf("%s %s.%s\n", nodes[i].Address, host.Name, nodes[i].Network)
 		}
@@ -845,8 +1033,8 @@ func GetPeerUpdateForRelayedNode(node *models.Node, udppeers map[string]string)
 	// if udp hole punching is on, but udp hole punching did not set it, use the LocalListenPort instead
 	// or, if port is for some reason zero use the LocalListenPort
 	// but only do this if LocalListenPort is not zero
-	if ((!setUDPPort) || relayHost.ListenPort == 0) && relayHost.LocalListenPort != 0 {
-		listenPort = relayHost.LocalListenPort
+	if ((!setUDPPort) || relayHost.ListenPort == 0) && relayHost.ListenPort != 0 {
+		listenPort = relayHost.ListenPort
 	}
 
 	endpoint := relayHost.EndpointIP.String() + ":" + strconv.FormatInt(int64(listenPort), 10)

+ 87 - 0
logic/relay.go

@@ -48,6 +48,51 @@ func CreateRelay(relay models.RelayRequest) ([]models.Node, models.Node, error)
 	return returnnodes, node, nil
 }
 
+// CreateHostRelay - creates a host relay
+func CreateHostRelay(relay models.HostRelayRequest) (relayHost *models.Host, relayedHosts []models.Host, err error) {
+
+	relayHost, err = GetHost(relay.HostID)
+	if err != nil {
+		return
+	}
+	err = validateHostRelay(relay)
+	if err != nil {
+		return
+	}
+	relayHost.IsRelay = true
+	relayHost.ProxyEnabled = true
+	relayHost.RelayedHosts = relay.RelayedHosts
+	err = UpsertHost(relayHost)
+	if err != nil {
+		return
+	}
+	relayedHosts = SetRelayedHosts(true, relay.HostID, relay.RelayedHosts)
+	return
+}
+
+// SetRelayedHosts - updates the relayed hosts status
+func SetRelayedHosts(setRelayed bool, relayHostID string, relayedHostIDs []string) []models.Host {
+	var relayedHosts []models.Host
+	for _, relayedHostID := range relayedHostIDs {
+		host, err := GetHost(relayedHostID)
+		if err == nil {
+			if setRelayed {
+				host.IsRelayed = true
+				host.RelayedBy = relayHostID
+				host.ProxyEnabled = true
+			} else {
+				host.IsRelayed = false
+				host.RelayedBy = ""
+			}
+			err = UpsertHost(host)
+			if err == nil {
+				relayedHosts = append(relayedHosts, *host)
+			}
+		}
+	}
+	return relayedHosts
+}
+
 // SetRelayedNodes- set relayed nodes
 func SetRelayedNodes(setRelayed bool, networkName string, addrs []string) ([]models.Node, error) {
 	var returnnodes []models.Node
@@ -90,6 +135,19 @@ func GetRelayedNodes(relayNode *models.Node) ([]models.Node, error) {
 	return returnnodes, nil
 }
 
+// GetRelayedHosts - gets the relayed hosts of a relay host
+func GetRelayedHosts(relayHost *models.Host) []models.Host {
+	relayedHosts := []models.Host{}
+
+	for _, hostID := range relayHost.RelayedHosts {
+		relayedHost, err := GetHost(hostID)
+		if err == nil {
+			relayedHosts = append(relayedHosts, *relayedHost)
+		}
+	}
+	return relayedHosts
+}
+
 // ValidateRelay - checks if relay is valid
 func ValidateRelay(relay models.RelayRequest) error {
 	var err error
@@ -101,6 +159,13 @@ func ValidateRelay(relay models.RelayRequest) error {
 	return err
 }
 
+func validateHostRelay(relay models.HostRelayRequest) error {
+	if len(relay.RelayedHosts) == 0 {
+		return errors.New("relayed hosts are empty")
+	}
+	return nil
+}
+
 // UpdateRelay - updates a relay
 func UpdateRelay(network string, oldAddrs []string, newAddrs []string) []models.Node {
 	var returnnodes []models.Node
@@ -141,3 +206,25 @@ func DeleteRelay(network, nodeid string) ([]models.Node, models.Node, error) {
 	}
 	return returnnodes, node, nil
 }
+
+// DeleteHostRelay - removes host as relay
+func DeleteHostRelay(relayHostID string) (relayHost *models.Host, relayedHosts []models.Host, err error) {
+	relayHost, err = GetHost(relayHostID)
+	if err != nil {
+		return
+	}
+	relayedHosts = SetRelayedHosts(false, relayHostID, relayHost.RelayedHosts)
+	relayHost.IsRelay = false
+	relayHost.RelayedHosts = []string{}
+	err = UpsertHost(relayHost)
+	if err != nil {
+		return
+	}
+	return
+}
+
+// UpdateHostRelay - updates the relay host with new relayed hosts
+func UpdateHostRelay(relayHostID string, oldRelayedHosts, newRelayedHosts []string) {
+	_ = SetRelayedHosts(false, relayHostID, oldRelayedHosts)
+	_ = SetRelayedHosts(true, relayHostID, newRelayedHosts)
+}

+ 2 - 1
logic/server.go

@@ -3,6 +3,7 @@ package logic
 import (
 	"strings"
 
+	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/models"
 )
 
@@ -16,7 +17,7 @@ var EnterpriseFailoverFunc func(node *models.Node) error
 var EnterpriseResetFailoverFunc func(network string) error
 
 // EnterpriseResetAllPeersFailovers - resets all nodes that are considering a node to be failover worthy (inclusive)
-var EnterpriseResetAllPeersFailovers func(nodeid, network string) error
+var EnterpriseResetAllPeersFailovers func(nodeid uuid.UUID, network string) error
 
 // == Join, Checkin, and Leave for Server ==
 

+ 2 - 1
logic/zombie.go

@@ -34,7 +34,8 @@ func CheckZombies(newnode *models.Node, mac net.HardwareAddr) {
 	for _, node := range nodes {
 		host, err := GetHost(node.HostID.String())
 		if err != nil {
-
+			// should we delete the node if host not found ??
+			continue
 		}
 		if host.MacAddress.String() == mac.String() {
 			logger.Log(0, "adding ", node.ID.String(), " to zombie list")

+ 1 - 1
main.go

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

+ 41 - 25
models/api_host.go

@@ -4,27 +4,32 @@ import "net"
 
 // ApiHost - the host struct for API usage
 type ApiHost struct {
-	ID              string   `json:"id"`
-	Verbosity       int      `json:"verbosity"`
-	FirewallInUse   string   `json:"firewallinuse"`
-	Version         string   `json:"version"`
-	Name            string   `json:"name"`
-	OS              string   `json:"os"`
-	Debug           bool     `json:"debug"`
-	IsStatic        bool     `json:"isstatic"`
-	ListenPort      int      `json:"listenport"`
-	LocalRange      string   `json:"localrange"`
-	LocalListenPort int      `json:"locallistenport"`
-	ProxyListenPort int      `json:"proxy_listen_port"`
-	MTU             int      `json:"mtu" yaml:"mtu"`
-	Interfaces      []Iface  `json:"interfaces" yaml:"interfaces"`
-	EndpointIP      string   `json:"endpointip" yaml:"endpointip"`
-	PublicKey       string   `json:"publickey"`
-	MacAddress      string   `json:"macaddress"`
-	InternetGateway string   `json:"internetgateway"`
-	Nodes           []string `json:"nodes"`
-	ProxyEnabled    bool     `json:"proxy_enabled" yaml:"proxy_enabled"`
-	IsDefault       bool     `json:"isdefault" yaml:"isdefault"`
+	ID               string   `json:"id"`
+	Verbosity        int      `json:"verbosity"`
+	FirewallInUse    string   `json:"firewallinuse"`
+	Version          string   `json:"version"`
+	Name             string   `json:"name"`
+	OS               string   `json:"os"`
+	Debug            bool     `json:"debug"`
+	IsStatic         bool     `json:"isstatic"`
+	ListenPort       int      `json:"listenport"`
+	LocalRange       string   `json:"localrange"`
+	LocalListenPort  int      `json:"locallistenport"`
+	ProxyListenPort  int      `json:"proxy_listen_port"`
+	MTU              int      `json:"mtu" yaml:"mtu"`
+	Interfaces       []Iface  `json:"interfaces" yaml:"interfaces"`
+	DefaultInterface string   `json:"defaultinterface" yaml:"defautlinterface"`
+	EndpointIP       string   `json:"endpointip" yaml:"endpointip"`
+	PublicKey        string   `json:"publickey"`
+	MacAddress       string   `json:"macaddress"`
+	InternetGateway  string   `json:"internetgateway"`
+	Nodes            []string `json:"nodes"`
+	ProxyEnabled     bool     `json:"proxy_enabled" yaml:"proxy_enabled"`
+	IsDefault        bool     `json:"isdefault" yaml:"isdefault"`
+	IsRelayed        bool     `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
+	RelayedBy        string   `json:"relayed_by" bson:"relayed_by" yaml:"relayed_by"`
+	IsRelay          bool     `json:"isrelay" bson:"isrelay" yaml:"isrelay"`
+	RelayedHosts     []string `json:"relay_hosts" bson:"relay_hosts" yaml:"relay_hosts"`
 }
 
 // Host.ConvertNMHostToAPI - converts a Netmaker host to an API editable host
@@ -35,13 +40,16 @@ func (h *Host) ConvertNMHostToAPI() *ApiHost {
 	a.FirewallInUse = h.FirewallInUse
 	a.ID = h.ID.String()
 	a.Interfaces = h.Interfaces
+	for i := range a.Interfaces {
+		a.Interfaces[i].AddressString = a.Interfaces[i].Address.String()
+	}
+	a.DefaultInterface = h.DefaultInterface
 	a.InternetGateway = h.InternetGateway.String()
 	if isEmptyAddr(a.InternetGateway) {
 		a.InternetGateway = ""
 	}
 	a.IsStatic = h.IsStatic
 	a.ListenPort = h.ListenPort
-	a.LocalListenPort = h.LocalListenPort
 	a.LocalRange = h.LocalRange.String()
 	if isEmptyAddr(a.LocalRange) {
 		a.LocalRange = ""
@@ -57,7 +65,10 @@ func (h *Host) ConvertNMHostToAPI() *ApiHost {
 	a.Verbosity = h.Verbosity
 	a.Version = h.Version
 	a.IsDefault = h.IsDefault
-
+	a.IsRelay = h.IsRelay
+	a.RelayedHosts = h.RelayedHosts
+	a.IsRelayed = h.IsRelayed
+	a.RelayedBy = h.RelayedBy
 	return &a
 }
 
@@ -74,12 +85,14 @@ func (a *ApiHost) ConvertAPIHostToNMHost(currentHost *Host) *Host {
 	h.IPForwarding = currentHost.IPForwarding
 	h.Interface = currentHost.Interface
 	h.Interfaces = currentHost.Interfaces
+	h.DefaultInterface = currentHost.DefaultInterface
 	h.InternetGateway = currentHost.InternetGateway
 	h.IsDocker = currentHost.IsDocker
 	h.IsK8S = currentHost.IsK8S
 	h.IsStatic = a.IsStatic
 	h.ListenPort = a.ListenPort
-	h.LocalListenPort = currentHost.ListenPort
+	h.ProxyListenPort = a.ProxyListenPort
+	h.PublicListenPort = currentHost.PublicListenPort
 	h.MTU = a.MTU
 	h.MacAddress = currentHost.MacAddress
 	h.PublicKey = currentHost.PublicKey
@@ -89,7 +102,10 @@ func (a *ApiHost) ConvertAPIHostToNMHost(currentHost *Host) *Host {
 	h.Nodes = currentHost.Nodes
 	h.TrafficKeyPublic = currentHost.TrafficKeyPublic
 	h.OS = currentHost.OS
-
+	h.RelayedBy = a.RelayedBy
+	h.RelayedHosts = a.RelayedHosts
+	h.IsRelay = a.IsRelay
+	h.IsRelayed = a.IsRelayed
 	if len(a.LocalRange) > 0 {
 		_, localRange, err := net.ParseCIDR(a.LocalRange)
 		if err == nil {

+ 4 - 3
models/api_node.go

@@ -59,7 +59,6 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node {
 	convertedNode.IsRelay = a.IsRelay
 	convertedNode.IsRelayed = a.IsRelayed
 	convertedNode.PendingDelete = a.PendingDelete
-	convertedNode.Peers = currentNode.Peers
 	convertedNode.Failover = a.Failover
 	convertedNode.IsEgressGateway = a.IsEgressGateway
 	convertedNode.IsIngressGateway = a.IsIngressGateway
@@ -93,13 +92,15 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node {
 	if err == nil {
 		convertedNode.InternetGateway = udpAddr
 	}
-	_, addr, err := net.ParseCIDR(a.Address)
+	ip, addr, err := net.ParseCIDR(a.Address)
 	if err == nil {
 		convertedNode.Address = *addr
+		convertedNode.Address.IP = ip
 	}
-	_, addr6, err := net.ParseCIDR(a.Address6)
+	ip6, addr6, err := net.ParseCIDR(a.Address6)
 	if err == nil {
 		convertedNode.Address = *addr6
+		convertedNode.Address.IP = ip6
 	}
 	convertedNode.FailoverNode, _ = uuid.Parse(a.FailoverNode)
 	convertedNode.LastModified = time.Unix(a.LastModified, 0)

+ 25 - 1
models/host.go

@@ -26,7 +26,7 @@ type Host struct {
 	ListenPort       int              `json:"listenport" yaml:"listenport"`
 	LocalAddress     net.IPNet        `json:"localaddress" yaml:"localaddress"`
 	LocalRange       net.IPNet        `json:"localrange" yaml:"localrange"`
-	LocalListenPort  int              `json:"locallistenport" yaml:"locallistenport"`
+	PublicListenPort int              `json:"public_listen_port" yaml:"public_listen_port"`
 	ProxyListenPort  int              `json:"proxy_listen_port" yaml:"proxy_listen_port"`
 	MTU              int              `json:"mtu" yaml:"mtu"`
 	PublicKey        wgtypes.Key      `json:"publickey" yaml:"publickey"`
@@ -34,7 +34,12 @@ type Host struct {
 	TrafficKeyPublic []byte           `json:"traffickeypublic" yaml:"trafficekeypublic"`
 	InternetGateway  net.UDPAddr      `json:"internetgateway" yaml:"internetgateway"`
 	Nodes            []string         `json:"nodes" yaml:"nodes"`
+	IsRelayed        bool             `json:"isrelayed" yaml:"isrelayed"`
+	RelayedBy        string           `json:"relayed_by" yaml:"relayed_by"`
+	IsRelay          bool             `json:"isrelay" yaml:"isrelay"`
+	RelayedHosts     []string         `json:"relay_hosts" yaml:"relay_hosts"`
 	Interfaces       []Iface          `json:"interfaces" yaml:"interfaces"`
+	DefaultInterface string           `json:"defaultinterface" yaml:"defautlinterface"`
 	EndpointIP       net.IP           `json:"endpointip" yaml:"endpointip"`
 	ProxyEnabled     bool             `json:"proxy_enabled" yaml:"proxy_enabled"`
 	IsDocker         bool             `json:"isdocker" yaml:"isdocker"`
@@ -60,3 +65,22 @@ func ParseBool(s string) bool {
 	}
 	return b
 }
+
+// HostMqAction - type for host update action
+type HostMqAction string
+
+const (
+	// UpdateHost - constant for host update action
+	UpdateHost = "UPDATE_HOST"
+	// DeleteHost - constant for host delete action
+	DeleteHost = "DELETE_HOST"
+	// JoinHostToNetwork - constant for host network join action
+	JoinHostToNetwork = "JOIN_HOST_TO_NETWORK"
+)
+
+// HostUpdate - struct for host update
+type HostUpdate struct {
+	Action HostMqAction
+	Host   Host
+	Node   Node
+}

+ 9 - 10
models/metrics.go

@@ -2,25 +2,20 @@ package models
 
 import (
 	"time"
-
-	proxy_models "github.com/gravitl/netclient/nmproxy/models"
 )
 
 // Metrics - metrics struct
 type Metrics struct {
-	Network       string                         `json:"network" bson:"network" yaml:"network"`
-	NodeID        string                         `json:"node_id" bson:"node_id" yaml:"node_id"`
-	NodeName      string                         `json:"node_name" bson:"node_name" yaml:"node_name"`
-	IsServer      string                         `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"`
-	Connectivity  map[string]Metric              `json:"connectivity" bson:"connectivity" yaml:"connectivity"`
-	FailoverPeers map[string]string              `json:"needsfailover" bson:"needsfailover" yaml:"needsfailover"`
-	ProxyMetrics  map[string]proxy_models.Metric `json:"proxy_metrics" bson:"proxy_metrics" yaml:"proxy_metrics"`
+	Network       string            `json:"network" bson:"network" yaml:"network"`
+	NodeID        string            `json:"node_id" bson:"node_id" yaml:"node_id"`
+	NodeName      string            `json:"node_name" bson:"node_name" yaml:"node_name"`
+	Connectivity  map[string]Metric `json:"connectivity" bson:"connectivity" yaml:"connectivity"`
+	FailoverPeers map[string]string `json:"needsfailover" bson:"needsfailover" yaml:"needsfailover"`
 }
 
 // Metric - holds a metric for data between nodes
 type Metric struct {
 	NodeName      string        `json:"node_name" bson:"node_name" yaml:"node_name"`
-	IsServer      string        `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"`
 	Uptime        int64         `json:"uptime" bson:"uptime" yaml:"uptime"`
 	TotalTime     int64         `json:"totaltime" bson:"totaltime" yaml:"totaltime"`
 	Latency       int64         `json:"latency" bson:"latency" yaml:"latency"`
@@ -37,11 +32,15 @@ type IDandAddr struct {
 	Address  string `json:"address" bson:"address" yaml:"address"`
 	Name     string `json:"name" bson:"name" yaml:"name"`
 	IsServer string `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"`
+	Network  string `json:"network" bson:"network" yaml:"network" validate:"network"`
 }
 
 // PeerMap - peer map for ids and addresses in metrics
 type PeerMap map[string]IDandAddr
 
+// HostPeerMap - host peer map for ids and addresses
+type HostPeerMap map[string]map[string]IDandAddr
+
 // MetricsMap - map for holding multiple metrics in memory
 type MetricsMap map[string]Metrics
 

+ 16 - 0
models/mqtt.go

@@ -16,6 +16,22 @@ type PeerUpdate struct {
 	ProxyUpdate   proxy_models.ProxyManagerPayload `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"`
 }
 
+// HostPeerUpdate - struct for host peer updates
+type HostPeerUpdate struct {
+	Host          Host                             `json:"host" bson:"host" yaml:"host"`
+	ServerVersion string                           `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
+	ServerAddrs   []ServerAddr                     `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
+	Network       map[string]NetworkInfo           `json:"network" bson:"network" yaml:"network"`
+	Peers         []wgtypes.PeerConfig             `json:"peers" bson:"peers" yaml:"peers"`
+	PeerIDs       HostPeerMap                      `json:"peerids" bson:"peerids" yaml:"peerids"`
+	ProxyUpdate   proxy_models.ProxyManagerPayload `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"`
+}
+
+// NetworkInfo - struct for network info
+type NetworkInfo struct {
+	DNS string `json:"dns" bson:"dns" yaml:"dns"`
+}
+
 // KeyUpdate - key update struct
 type KeyUpdate struct {
 	Network   string `json:"network" bson:"network"`

+ 49 - 39
models/node.go

@@ -56,26 +56,25 @@ type Iface struct {
 
 // CommonNode - represents a commonn node data elements shared by netmaker and netclient
 type CommonNode struct {
-	ID                  uuid.UUID            `json:"id" yaml:"id"`
-	HostID              uuid.UUID            `json:"hostid" yaml:"hostid"`
-	Network             string               `json:"network" yaml:"network"`
-	NetworkRange        net.IPNet            `json:"networkrange" yaml:"networkrange"`
-	NetworkRange6       net.IPNet            `json:"networkrange6" yaml:"networkrange6"`
-	InternetGateway     *net.UDPAddr         `json:"internetgateway" yaml:"internetgateway"`
-	Server              string               `json:"server" yaml:"server"`
-	Connected           bool                 `json:"connected" yaml:"connected"`
-	Address             net.IPNet            `json:"address" yaml:"address"`
-	Address6            net.IPNet            `json:"address6" yaml:"address6"`
-	PostUp              string               `json:"postup" yaml:"postup"`
-	PostDown            string               `json:"postdown" yaml:"postdown"`
-	Action              string               `json:"action" yaml:"action"`
-	LocalAddress        net.IPNet            `json:"localaddress" yaml:"localaddress"`
-	IsLocal             bool                 `json:"islocal" yaml:"islocal"`
-	IsEgressGateway     bool                 `json:"isegressgateway" yaml:"isegressgateway"`
-	IsIngressGateway    bool                 `json:"isingressgateway" yaml:"isingressgateway"`
-	DNSOn               bool                 `json:"dnson" yaml:"dnson"`
-	PersistentKeepalive time.Duration        `json:"persistentkeepalive" yaml:"persistentkeepalive"`
-	Peers               []wgtypes.PeerConfig `json:"peers" yaml:"peers"`
+	ID                  uuid.UUID     `json:"id" yaml:"id"`
+	HostID              uuid.UUID     `json:"hostid" yaml:"hostid"`
+	Network             string        `json:"network" yaml:"network"`
+	NetworkRange        net.IPNet     `json:"networkrange" yaml:"networkrange"`
+	NetworkRange6       net.IPNet     `json:"networkrange6" yaml:"networkrange6"`
+	InternetGateway     *net.UDPAddr  `json:"internetgateway" yaml:"internetgateway"`
+	Server              string        `json:"server" yaml:"server"`
+	Connected           bool          `json:"connected" yaml:"connected"`
+	Address             net.IPNet     `json:"address" yaml:"address"`
+	Address6            net.IPNet     `json:"address6" yaml:"address6"`
+	PostUp              string        `json:"postup" yaml:"postup"`
+	PostDown            string        `json:"postdown" yaml:"postdown"`
+	Action              string        `json:"action" yaml:"action"`
+	LocalAddress        net.IPNet     `json:"localaddress" yaml:"localaddress"`
+	IsLocal             bool          `json:"islocal" yaml:"islocal"`
+	IsEgressGateway     bool          `json:"isegressgateway" yaml:"isegressgateway"`
+	IsIngressGateway    bool          `json:"isingressgateway" yaml:"isingressgateway"`
+	DNSOn               bool          `json:"dnson" yaml:"dnson"`
+	PersistentKeepalive time.Duration `json:"persistentkeepalive" yaml:"persistentkeepalive"`
 }
 
 // Node - a model of a network node
@@ -365,7 +364,7 @@ func (node *LegacyNode) SetDefaultFailover() {
 // Node.Fill - fills other node data into calling node data if not set on calling node
 func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftables present
 	newNode.ID = currentNode.ID
-
+	newNode.HostID = currentNode.HostID
 	// Revisit the logic for boolean values
 	// TODO ---- !!!!!!!!!!!!!!!!!!!!!!!!!!!!
 	// TODO ---- !!!!!!!!!!!!!!!!!!!!!!!!!!
@@ -435,9 +434,6 @@ func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftable
 	if newNode.Server == "" {
 		newNode.Server = currentNode.Server
 	}
-	if newNode.Connected != currentNode.Connected {
-		newNode.Connected = currentNode.Connected
-	}
 	if newNode.DefaultACL == "" {
 		newNode.DefaultACL = currentNode.DefaultACL
 	}
@@ -499,17 +495,22 @@ func (ln *LegacyNode) ConvertToNewNode() (*Host, *Node) {
 		host.HostPass = ln.Password
 		host.Name = ln.Name
 		host.ListenPort = int(ln.ListenPort)
-		_, cidr, _ := net.ParseCIDR(ln.LocalAddress)
-		_, cidr, _ = net.ParseCIDR(ln.LocalRange)
-		host.LocalRange = *cidr
-		host.LocalListenPort = int(ln.LocalListenPort)
+		if _, cidr, err := net.ParseCIDR(ln.LocalAddress); err == nil {
+			host.LocalRange = *cidr
+		} else {
+			if _, cidr, err := net.ParseCIDR(ln.LocalRange); err == nil {
+				host.LocalRange = *cidr
+			}
+		}
 		host.ProxyListenPort = int(ln.ProxyListenPort)
 		host.MTU = int(ln.MTU)
 		host.PublicKey, _ = wgtypes.ParseKey(ln.PublicKey)
 		host.MacAddress, _ = net.ParseMAC(ln.MacAddress)
 		host.TrafficKeyPublic = ln.TrafficKeys.Mine
-		gateway, _ := net.ResolveUDPAddr("udp", ln.InternetGateway)
-		host.InternetGateway = *gateway
+		gateway, err := net.ResolveUDPAddr("udp", ln.InternetGateway)
+		if err == nil {
+			host.InternetGateway = *gateway
+		}
 		id, _ := uuid.Parse(ln.ID)
 		host.Nodes = append(host.Nodes, id.String())
 		host.Interfaces = ln.Interfaces
@@ -519,16 +520,26 @@ func (ln *LegacyNode) ConvertToNewNode() (*Host, *Node) {
 	id, _ := uuid.Parse(ln.ID)
 	node.ID = id
 	node.Network = ln.Network
-	_, cidr, _ := net.ParseCIDR(ln.NetworkSettings.AddressRange)
-	node.NetworkRange = *cidr
-	_, cidr, _ = net.ParseCIDR(ln.NetworkSettings.AddressRange6)
-	node.NetworkRange6 = *cidr
+	if _, cidr, err := net.ParseCIDR(ln.NetworkSettings.AddressRange); err == nil {
+		node.NetworkRange = *cidr
+	}
+	if _, cidr, err := net.ParseCIDR(ln.NetworkSettings.AddressRange6); err == nil {
+		node.NetworkRange6 = *cidr
+	}
 	node.Server = ln.Server
 	node.Connected = parseBool(ln.Connected)
-	_, cidr, _ = net.ParseCIDR(ln.Address)
-	node.Address = *cidr
-	_, cidr, _ = net.ParseCIDR(ln.Address6)
-	node.Address6 = *cidr
+	if ln.Address != "" {
+		node.Address = net.IPNet{
+			IP:   net.ParseIP(ln.Address),
+			Mask: net.CIDRMask(32, 32),
+		}
+	}
+	if ln.Address6 != "" {
+		node.Address = net.IPNet{
+			IP:   net.ParseIP(ln.Address6),
+			Mask: net.CIDRMask(128, 128),
+		}
+	}
 	node.PostUp = ln.PostUp
 	node.PostDown = ln.PostDown
 	node.Action = ln.Action
@@ -551,7 +562,6 @@ func (n *Node) Legacy(h *Host, s *ServerConfig, net *Network) *LegacyNode {
 	l.Name = h.Name
 	l.NetworkSettings = *net
 	l.ListenPort = int32(h.ListenPort)
-	l.LocalListenPort = int32(h.LocalListenPort)
 	l.ProxyListenPort = int32(h.ProxyListenPort)
 	l.PublicKey = h.PublicKey.String()
 	l.Endpoint = h.EndpointIP.String()

+ 11 - 4
models/structs.go

@@ -172,6 +172,12 @@ type RelayRequest struct {
 	RelayAddrs []string `json:"relayaddrs" bson:"relayaddrs"`
 }
 
+// HostRelayRequest - struct for host relay creation
+type HostRelayRequest struct {
+	HostID       string   `json:"host_id"`
+	RelayedHosts []string `json:"relayed_hosts"`
+}
+
 // ServerUpdateData - contains data to configure server
 // and if it should set peers
 type ServerUpdateData struct {
@@ -205,16 +211,17 @@ type NodeGet struct {
 	Node         LegacyNode           `json:"node" bson:"node" yaml:"node"`
 	Host         Host                 `json:"host" yaml:"host"`
 	Peers        []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
+	HostPeers    []wgtypes.PeerConfig `json:"host_peers" bson:"host_peers" yaml:"host_peers"`
 	ServerConfig ServerConfig         `json:"serverconfig" bson:"serverconfig" yaml:"serverconfig"`
 	PeerIDs      PeerMap              `json:"peerids,omitempty" bson:"peerids,omitempty" yaml:"peerids,omitempty"`
 }
 
 // NodeJoinResponse data returned to node in response to join
 type NodeJoinResponse struct {
-	Node         Node         `json:"node" bson:"node" yaml:"node"`
-	Host         Host         `json:"host" yaml:"host"`
-	ServerConfig ServerConfig `json:"serverconfig" bson:"serverconfig" yaml:"serverconfig"`
-	PeerIDs      PeerMap      `json:"peerids,omitempty" bson:"peerids,omitempty" yaml:"peerids,omitempty"`
+	Node         Node                 `json:"node" bson:"node" yaml:"node"`
+	Host         Host                 `json:"host" yaml:"host"`
+	ServerConfig ServerConfig         `json:"serverconfig" bson:"serverconfig" yaml:"serverconfig"`
+	Peers        []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
 }
 
 // ServerConfig - struct for dealing with the server information for a netclient

+ 15 - 2
mq/dynsec_clients.go

@@ -13,7 +13,11 @@ func ModifyClient(client *MqClient) error {
 
 	roles := []MqDynSecRole{
 		{
-			Rolename: HostRole,
+			Rolename: HostGenericRole,
+			Priority: -1,
+		},
+		{
+			Rolename: getHostRoleName(client.ID),
 			Priority: -1,
 		},
 	}
@@ -43,6 +47,7 @@ func ModifyClient(client *MqClient) error {
 
 // DeleteMqClient - removes a client from the DynSec system
 func DeleteMqClient(hostID string) error {
+	deleteHostRole(hostID)
 	event := MqDynsecPayload{
 		Commands: []MqDynSecCmd{
 			{
@@ -57,9 +62,17 @@ func DeleteMqClient(hostID string) error {
 // CreateMqClient - creates an MQ DynSec client
 func CreateMqClient(client *MqClient) error {
 
+	err := createHostRole(client.ID)
+	if err != nil {
+		return err
+	}
 	roles := []MqDynSecRole{
 		{
-			Rolename: HostRole,
+			Rolename: HostGenericRole,
+			Priority: -1,
+		},
+		{
+			Rolename: getHostRoleName(client.ID),
 			Priority: -1,
 		},
 	}

+ 80 - 3
mq/dynsec_helper.go

@@ -19,8 +19,8 @@ const (
 	exporterRole = "exporter"
 	// constant for node role
 	NodeRole = "node"
-	// HostRole constant for host role
-	HostRole = "host"
+	// HostGenericRole constant for host role
+	HostGenericRole = "host"
 
 	// const for dynamic security file
 	dynamicSecurityFile = "dynamic-security.json"
@@ -66,7 +66,7 @@ var (
 				Acls:     fetchServerAcls(),
 			},
 			{
-				Rolename: HostRole,
+				Rolename: HostGenericRole,
 				Acls:     fetchNodeAcls(),
 			},
 			exporterMQRole,
@@ -169,6 +169,30 @@ func ListClients(client mqtt.Client) (ListClientsData, error) {
 	return resp, errors.New("resp not found")
 }
 
+// fetches host related acls
+func fetchHostAcls(hostID string) []Acl {
+	return []Acl{
+		{
+			AclType:  "publishClientReceive",
+			Topic:    fmt.Sprintf("peers/host/%s/#", hostID),
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientReceive",
+			Topic:    fmt.Sprintf("host/update/%s/#", hostID),
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientSend",
+			Topic:    fmt.Sprintf("host/serverupdate/%s", hostID),
+			Priority: -1,
+			Allow:    true,
+		},
+	}
+}
+
 // FetchNetworkAcls - fetches network acls
 func FetchNetworkAcls(network string) []Acl {
 	return []Acl{
@@ -220,6 +244,20 @@ func DeleteNetworkRole(network string) error {
 	return publishEventToDynSecTopic(event)
 }
 
+func deleteHostRole(hostID string) error {
+	// Deletes the hostID role from MQ
+	event := MqDynsecPayload{
+		Commands: []MqDynSecCmd{
+			{
+				Command:  DeleteRoleCmd,
+				RoleName: getHostRoleName(hostID),
+			},
+		},
+	}
+
+	return publishEventToDynSecTopic(event)
+}
+
 // CreateNetworkRole - createss a network role from DynSec system
 func CreateNetworkRole(network string) error {
 	// Create Role with acls for the network
@@ -237,6 +275,27 @@ func CreateNetworkRole(network string) error {
 	return publishEventToDynSecTopic(event)
 }
 
+// creates role for the host with ID.
+func createHostRole(hostID string) error {
+	// Create Role with acls for the host
+	event := MqDynsecPayload{
+		Commands: []MqDynSecCmd{
+			{
+				Command:  CreateRoleCmd,
+				RoleName: getHostRoleName(hostID),
+				Textname: "host role with Acls for hosts",
+				Acls:     fetchHostAcls(hostID),
+			},
+		},
+	}
+
+	return publishEventToDynSecTopic(event)
+}
+
+func getHostRoleName(hostID string) string {
+	return fmt.Sprintf("host-%s", hostID)
+}
+
 // serverAcls - fetches server role related acls
 func fetchServerAcls() []Acl {
 	return []Acl{
@@ -252,6 +311,12 @@ func fetchServerAcls() []Acl {
 			Priority: -1,
 			Allow:    true,
 		},
+		{
+			AclType:  "publishClientSend",
+			Topic:    "peers/host/#",
+			Priority: -1,
+			Allow:    true,
+		},
 		{
 			AclType:  "publishClientSend",
 			Topic:    "update/#",
@@ -264,6 +329,12 @@ func fetchServerAcls() []Acl {
 			Priority: -1,
 			Allow:    true,
 		},
+		{
+			AclType:  "publishClientSend",
+			Topic:    "host/update/#",
+			Priority: -1,
+			Allow:    true,
+		},
 		{
 			AclType:  "publishClientReceive",
 			Topic:    "ping/#",
@@ -300,6 +371,12 @@ func fetchServerAcls() []Acl {
 			Priority: -1,
 			Allow:    true,
 		},
+		{
+			AclType:  "publishClientReceive",
+			Topic:    "host/serverupdate/#",
+			Priority: -1,
+			Allow:    true,
+		},
 	}
 }
 

+ 100 - 12
mq/handlers.go

@@ -95,9 +95,10 @@ func UpdateNode(client mqtt.Client, msg mqtt.Message) {
 			logger.Log(1, "error unmarshaling payload ", err.Error())
 			return
 		}
+
 		ifaceDelta := logic.IfaceDelta(&currentNode, &newNode)
 		if servercfg.Is_EE && ifaceDelta {
-			if err = logic.EnterpriseResetAllPeersFailovers(currentNode.ID.String(), currentNode.Network); err != nil {
+			if err = logic.EnterpriseResetAllPeersFailovers(currentNode.ID, currentNode.Network); err != nil {
 				logger.Log(1, "failed to reset failover list during node update", currentNode.ID.String(), currentNode.Network)
 			}
 		}
@@ -107,7 +108,7 @@ func UpdateNode(client mqtt.Client, msg mqtt.Message) {
 			return
 		}
 		if ifaceDelta { // reduce number of unneeded updates, by only sending on iface changes
-			if err = PublishPeerUpdate(currentNode.Network, true); err != nil {
+			if err = PublishPeerUpdate(); err != nil {
 				logger.Log(0, "error updating peers when node", currentNode.ID.String(), "informed the server of an interface change", err.Error())
 			}
 		}
@@ -117,6 +118,96 @@ func UpdateNode(client mqtt.Client, msg mqtt.Message) {
 	}()
 }
 
+// UpdateHost  message Handler -- handles host updates from clients
+func UpdateHost(client mqtt.Client, msg mqtt.Message) {
+	go func(msg mqtt.Message) {
+		id, err := getID(msg.Topic())
+		if err != nil {
+			logger.Log(1, "error getting host.ID sent on ", msg.Topic(), err.Error())
+			return
+		}
+		currentHost, err := logic.GetHost(id)
+		if err != nil {
+			logger.Log(1, "error getting host ", id, err.Error())
+			return
+		}
+		decrypted, decryptErr := decryptMsgWithHost(currentHost, msg.Payload())
+		if decryptErr != nil {
+			logger.Log(1, "failed to decrypt message for host ", id, decryptErr.Error())
+			return
+		}
+		var hostUpdate models.HostUpdate
+		if err := json.Unmarshal(decrypted, &hostUpdate); err != nil {
+			logger.Log(1, "error unmarshaling payload ", err.Error())
+			return
+		}
+		logger.Log(0, "recieved host update for host: ", id)
+		var sendPeerUpdate bool
+		switch hostUpdate.Action {
+		case models.UpdateHost:
+			sendPeerUpdate = updateHostFromClient(&hostUpdate.Host, currentHost)
+			err := logic.UpsertHost(currentHost)
+			if err != nil {
+				logger.Log(0, "failed to update host: ", currentHost.ID.String(), err.Error())
+				return
+			}
+		case models.DeleteHost:
+			if err := logic.DisassociateAllNodesFromHost(currentHost.ID.String()); err != nil {
+				logger.Log(0, "failed to delete all nodes of host: ", currentHost.ID.String(), err.Error())
+				return
+			}
+			if err := logic.RemoveHostByID(currentHost.ID.String()); err != nil {
+				logger.Log(0, "failed to delete host: ", currentHost.ID.String(), err.Error())
+				return
+			}
+			sendPeerUpdate = true
+		}
+		if sendPeerUpdate {
+			err := PublishPeerUpdate()
+			if err != nil {
+				logger.Log(0, "failed to pulish peer update: ", err.Error())
+			}
+		}
+		// if servercfg.Is_EE && ifaceDelta {
+		// 	if err = logic.EnterpriseResetAllPeersFailovers(currentHost.ID.String(), currentHost.Network); err != nil {
+		// 		logger.Log(1, "failed to reset failover list during node update", currentHost.ID.String(), currentHost.Network)
+		// 	}
+		// }
+
+	}(msg)
+}
+
+// used for updating host on server with update recieved from client
+func updateHostFromClient(newHost, currHost *models.Host) (sendPeerUpdate bool) {
+
+	if newHost.ListenPort != 0 && currHost.ListenPort != newHost.ListenPort {
+		currHost.ListenPort = newHost.ListenPort
+		sendPeerUpdate = true
+	}
+	if newHost.ProxyListenPort != 0 && currHost.ProxyListenPort != newHost.ProxyListenPort {
+		currHost.ProxyListenPort = newHost.ProxyListenPort
+		sendPeerUpdate = true
+	}
+	if newHost.PublicListenPort != 0 && currHost.PublicListenPort != newHost.PublicListenPort {
+		currHost.PublicListenPort = newHost.PublicListenPort
+		sendPeerUpdate = true
+	}
+	if currHost.ProxyEnabled != newHost.ProxyEnabled {
+		currHost.ProxyEnabled = newHost.ProxyEnabled
+		sendPeerUpdate = true
+	}
+	if currHost.EndpointIP.String() != newHost.EndpointIP.String() {
+		currHost.EndpointIP = newHost.EndpointIP
+		sendPeerUpdate = true
+	}
+	currHost.DaemonInstalled = newHost.DaemonInstalled
+	currHost.Debug = newHost.Debug
+	currHost.Verbosity = newHost.Verbosity
+	currHost.Version = newHost.Version
+	currHost.Name = newHost.Name
+	return
+}
+
 // UpdateMetrics  message Handler -- handles updates from client nodes for metrics
 func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
 	if servercfg.Is_EE {
@@ -165,9 +256,13 @@ func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
 
 			if shouldUpdate {
 				logger.Log(2, "updating peers after node", currentNode.ID.String(), currentNode.Network, "detected connectivity issues")
-				if err = PublishSinglePeerUpdate(&currentNode); err != nil {
-					logger.Log(0, "failed to publish update after failover peer change for node", currentNode.ID.String(), currentNode.Network)
+				host, err := logic.GetHost(currentNode.HostID.String())
+				if err == nil {
+					if err = PublishSingleHostUpdate(host); err != nil {
+						logger.Log(0, "failed to publish update after failover peer change for node", currentNode.ID.String(), currentNode.Network)
+					}
 				}
+
 			}
 
 			logger.Log(1, "updated node metrics", id)
@@ -205,7 +300,7 @@ func ClientPeerUpdate(client mqtt.Client, msg mqtt.Message) {
 }
 
 func updateNodePeers(currentNode *models.Node) {
-	if err := PublishPeerUpdate(currentNode.Network, false); err != nil {
+	if err := PublishPeerUpdate(); err != nil {
 		logger.Log(1, "error publishing peer update ", err.Error())
 		return
 	}
@@ -243,7 +338,6 @@ func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) boo
 				}
 			}
 			extMetric.NodeName = attachedClients[i].ClientID
-			extMetric.IsServer = "no"
 			delete(newMetrics.Connectivity, attachedClients[i].PublicKey)
 			newMetrics.Connectivity[attachedClients[i].ClientID] = extMetric
 		}
@@ -264,12 +358,6 @@ func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) boo
 		currMetric.ActualUptime = time.Duration(totalUpMinutes) * time.Minute
 		delete(oldMetrics.Connectivity, k) // remove from old data
 		newMetrics.Connectivity[k] = currMetric
-		if oldProxyMetric, ok := oldMetrics.ProxyMetrics[k]; ok {
-			newProxyMetric := newMetrics.ProxyMetrics[k]
-			newProxyMetric.TrafficSent += oldProxyMetric.TrafficSent
-			newProxyMetric.TrafficRecieved += oldProxyMetric.TrafficRecieved
-			newMetrics.ProxyMetrics[k] = newProxyMetric
-		}
 
 	}
 

+ 4 - 0
mq/mq.go

@@ -83,6 +83,10 @@ func SetupMQTT() {
 			client.Disconnect(240)
 			logger.Log(0, "node update subscription failed")
 		}
+		if token := client.Subscribe("host/serverupdate/#", 0, mqtt.MessageHandler(UpdateHost)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
+			client.Disconnect(240)
+			logger.Log(0, "host update subscription failed")
+		}
 		if token := client.Subscribe("signal/#", 0, mqtt.MessageHandler(ClientPeerUpdate)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
 			client.Disconnect(240)
 			logger.Log(0, "node client subscription failed")

+ 28 - 80
mq/publishers.go

@@ -14,95 +14,53 @@ import (
 	"github.com/gravitl/netmaker/serverctl"
 )
 
-// PublishPeerUpdate --- deterines and publishes a peer update to all the peers of a node
-func PublishPeerUpdate(network string, publishToSelf bool) error {
+// PublishPeerUpdate --- determines and publishes a peer update to all the hosts
+func PublishPeerUpdate() error {
 	if !servercfg.IsMessageQueueBackend() {
 		return nil
 	}
-	networkNodes, err := logic.GetNetworkNodes(network)
+
+	hosts, err := logic.GetAllHosts()
 	if err != nil {
-		logger.Log(1, "err getting Network Nodes", err.Error())
+		logger.Log(1, "err getting all hosts", err.Error())
 		return err
 	}
-	for _, node := range networkNodes {
-		err = PublishSinglePeerUpdate(&node)
+	for _, host := range hosts {
+		err = PublishSingleHostUpdate(&host)
 		if err != nil {
-			logger.Log(1, "failed to publish peer update to node", node.ID.String(), "on network", node.Network, ":", err.Error())
+			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
 		}
 	}
 	return err
 }
 
-func PublishProxyPeerUpdate(node *models.Node) error {
-	proxyUpdate, err := logic.GetPeersForProxy(node, false)
-	if err != nil {
-		return err
-	}
-	proxyUpdate.Action = proxy_models.AddNetwork
-	err = ProxyUpdate(&proxyUpdate, node)
-	if err != nil {
-		logger.Log(1, "failed to send proxy update: ", err.Error())
-		return err
-	}
-	return nil
-}
-
-// PublishSinglePeerUpdate --- determines and publishes a peer update to one node
-func PublishSinglePeerUpdate(node *models.Node) error {
-	host, err := logic.GetHost(node.HostID.String())
-	if err != nil {
-		return nil
-	}
+// PublishSingleHostUpdate --- determines and publishes a peer update to one host
+func PublishSingleHostUpdate(host *models.Host) error {
 
-	peerUpdate, err := logic.GetPeerUpdate(node, host)
+	peerUpdate, err := logic.GetPeerUpdateForHost(host)
 	if err != nil {
 		return err
 	}
 	if host.ProxyEnabled {
-		proxyUpdate, err := logic.GetPeersForProxy(node, false)
+		proxyUpdate, err := logic.GetProxyUpdateForHost(host)
 		if err != nil {
 			return err
 		}
-		proxyUpdate.Action = proxy_models.AddNetwork
+		proxyUpdate.Action = proxy_models.ProxyUpdate
 		peerUpdate.ProxyUpdate = proxyUpdate
-
 	}
 
 	data, err := json.Marshal(&peerUpdate)
 	if err != nil {
 		return err
 	}
-	return publish(node, fmt.Sprintf("peers/%s/%s", node.Network, node.ID), data)
+	return publish(host, fmt.Sprintf("peers/host/%s/%s", host.ID.String(), servercfg.GetServer()), data)
 }
 
 // PublishPeerUpdate --- publishes a peer update to all the peers of a node
 func PublishExtPeerUpdate(node *models.Node) error {
-	host, err := logic.GetHost(node.HostID.String())
-	if err != nil {
-		return nil
-	}
-	if !servercfg.IsMessageQueueBackend() {
-		return nil
-	}
-	peerUpdate, err := logic.GetPeerUpdate(node, host)
-	if err != nil {
-		return err
-	}
-	data, err := json.Marshal(&peerUpdate)
-	if err != nil {
-		return err
-	}
-	if host.ProxyEnabled {
-		proxyUpdate, err := logic.GetPeersForProxy(node, false)
-		if err == nil {
-			peerUpdate.ProxyUpdate = proxyUpdate
-		}
-	}
 
-	if err = publish(node, fmt.Sprintf("peers/%s/%s", node.Network, node.ID), data); err != nil {
-		return err
-	}
-	go PublishPeerUpdate(node.Network, false)
+	go PublishPeerUpdate()
 	return nil
 }
 
@@ -126,47 +84,38 @@ func NodeUpdate(node *models.Node) error {
 		logger.Log(2, "error marshalling node update ", err.Error())
 		return err
 	}
-	if err = publish(node, fmt.Sprintf("update/%s/%s", node.Network, node.ID), data); err != nil {
+	if err = publish(host, fmt.Sprintf("update/%s/%s", node.Network, node.ID), data); err != nil {
 		logger.Log(2, "error publishing node update to peer ", node.ID.String(), err.Error())
 		return err
 	}
-	if host.ProxyEnabled {
-		err = PublishProxyPeerUpdate(node)
-		if err != nil {
-			logger.Log(1, "failed to publish proxy update to node", node.ID.String(), "on network", node.Network, ":", err.Error())
-		}
-	}
 
 	return nil
 }
 
-// ProxyUpdate -- publishes updates to peers related to proxy
-func ProxyUpdate(proxyPayload *proxy_models.ProxyManagerPayload, node *models.Node) error {
-	host, err := logic.GetHost(node.HostID.String())
-	if err != nil {
-		return nil
-	}
-	if !servercfg.IsMessageQueueBackend() || !host.ProxyEnabled {
+// HostUpdate -- publishes a host update to clients
+func HostUpdate(hostUpdate *models.HostUpdate) error {
+	if !servercfg.IsMessageQueueBackend() {
 		return nil
 	}
-	logger.Log(3, "publishing proxy update to "+node.ID.String())
+	logger.Log(3, "publishing host update to "+hostUpdate.Host.ID.String())
 
-	data, err := json.Marshal(proxyPayload)
+	data, err := json.Marshal(hostUpdate)
 	if err != nil {
 		logger.Log(2, "error marshalling node update ", err.Error())
 		return err
 	}
-	if err = publish(node, fmt.Sprintf("proxy/%s/%s", node.Network, node.ID), data); err != nil {
-		logger.Log(2, "error publishing proxy update to peer ", node.ID.String(), err.Error())
+	if err = publish(&hostUpdate.Host, fmt.Sprintf("host/update/%s/%s", hostUpdate.Host.ID.String(), servercfg.GetServer()), data); err != nil {
+		logger.Log(2, "error publishing host update to", hostUpdate.Host.ID.String(), err.Error())
 		return err
 	}
+
 	return nil
 }
 
 // sendPeers - retrieve networks, send peer ports to all peers
 func sendPeers() {
 
-	networks, err := logic.GetNetworks()
+	hosts, err := logic.GetAllHosts()
 	if err != nil {
 		logger.Log(1, "error retrieving networks for keepalive", err.Error())
 	}
@@ -191,13 +140,12 @@ func sendPeers() {
 		//collectServerMetrics(networks[:])
 	}
 
-	for _, network := range networks {
+	for _, host := range hosts {
 		if force {
 			logger.Log(2, "sending scheduled peer update (5 min)")
-			err = PublishPeerUpdate(network.NetID, false)
+			err = PublishSingleHostUpdate(&host)
 			if err != nil {
-				logger.Log(1, "error publishing udp port updates for network", network.NetID)
-				logger.Log(1, err.Error())
+				logger.Log(1, "error publishing peer updates for host: ", host.ID.String(), " Err: ", err.Error())
 			}
 		}
 	}

+ 15 - 19
mq/util.go

@@ -11,15 +11,7 @@ import (
 	"github.com/gravitl/netmaker/netclient/ncutils"
 )
 
-func decryptMsg(node *models.Node, msg []byte) ([]byte, error) {
-	if len(msg) <= 24 { // make sure message is of appropriate length
-		return nil, fmt.Errorf("recieved invalid message from broker %v", msg)
-	}
-	host, err := logic.GetHost(node.HostID.String())
-	if err != nil {
-		return nil, err
-	}
-
+func decryptMsgWithHost(host *models.Host, msg []byte) ([]byte, error) {
 	trafficKey, trafficErr := logic.RetrievePrivateTrafficKey() // get server private key
 	if trafficErr != nil {
 		return nil, trafficErr
@@ -33,14 +25,22 @@ func decryptMsg(node *models.Node, msg []byte) ([]byte, error) {
 		return nil, err
 	}
 
-	if strings.Contains(host.Version, "0.10.0") {
-		return ncutils.BoxDecrypt(msg, nodePubTKey, serverPrivTKey)
+	return ncutils.DeChunk(msg, nodePubTKey, serverPrivTKey)
+}
+
+func decryptMsg(node *models.Node, msg []byte) ([]byte, error) {
+	if len(msg) <= 24 { // make sure message is of appropriate length
+		return nil, fmt.Errorf("recieved invalid message from broker %v", msg)
+	}
+	host, err := logic.GetHost(node.HostID.String())
+	if err != nil {
+		return nil, err
 	}
 
-	return ncutils.DeChunk(msg, nodePubTKey, serverPrivTKey)
+	return decryptMsgWithHost(host, msg)
 }
 
-func encryptMsg(node *models.Node, msg []byte) ([]byte, error) {
+func encryptMsg(host *models.Host, msg []byte) ([]byte, error) {
 	// fetch server public key to be certain hasn't changed in transit
 	trafficKey, trafficErr := logic.RetrievePrivateTrafficKey()
 	if trafficErr != nil {
@@ -52,10 +52,6 @@ func encryptMsg(node *models.Node, msg []byte) ([]byte, error) {
 		return nil, err
 	}
 
-	host, err := logic.GetHost(node.HostID.String())
-	if err != nil {
-		return nil, err
-	}
 	nodePubKey, err := ncutils.ConvertBytesToKey(host.TrafficKeyPublic)
 	if err != nil {
 		return nil, err
@@ -68,8 +64,8 @@ func encryptMsg(node *models.Node, msg []byte) ([]byte, error) {
 	return ncutils.Chunk(msg, nodePubKey, serverPrivKey)
 }
 
-func publish(node *models.Node, dest string, msg []byte) error {
-	encrypted, encryptErr := encryptMsg(node, msg)
+func publish(host *models.Host, dest string, msg []byte) error {
+	encrypted, encryptErr := encryptMsg(host, msg)
 	if encryptErr != nil {
 		return encryptErr
 	}

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

@@ -14,7 +14,6 @@ import (
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/pkg/errors"
-	"github.com/sirupsen/logrus"
 	"gortc.io/stun"
 )
 
@@ -28,14 +27,7 @@ type Server struct {
 	Ctx  context.Context
 }
 
-// Logger is used for logging formatted messages.
-type Logger interface {
-	// Printf must have the same semantics as log.Printf.
-	Printf(format string, args ...interface{})
-}
-
 var (
-	defaultLogger     = logrus.New()
 	software          = stun.NewSoftware("netmaker-stun")
 	errNotSTUNMessage = errors.New("not stun message")
 )