Browse Source

moving back to host install

afeiszli 2 years ago
parent
commit
7325c46719
100 changed files with 2560 additions and 2525 deletions
  1. 4 2
      .github/ISSUE_TEMPLATE/bug-report.yml
  2. 15 0
      .github/pull_request_template.md
  3. 0 73
      .github/workflows/buildandrelease.yml
  4. 3 2
      .github/workflows/docker-builder.yml
  5. 46 0
      .github/workflows/packages.yml
  6. 5 4
      .github/workflows/publish-docker.yml
  7. 32 0
      .github/workflows/pull-request.yml
  8. 0 29
      .github/workflows/purgeGHCR.yml
  9. 50 0
      .github/workflows/release-assets.yml
  10. 39 0
      .github/workflows/release-branch.yml
  11. 49 0
      .github/workflows/release.yml
  12. 1 0
      .github/workflows/test.yml
  13. 41 0
      .github/workflows/upgraderelease.yml
  14. 1 0
      .gitignore
  15. 34 0
      .goreleaser.prerelease.yaml
  16. 4 0
      .goreleaser.update.yaml
  17. 34 0
      .goreleaser.yaml
  18. 1 6
      Dockerfile
  19. 2 2
      README.md
  20. 20 0
      cli/cmd/host/add_network.go
  21. 20 0
      cli/cmd/host/delete_network.go
  22. 0 22
      cli/cmd/host/update_networks.go
  23. 0 4
      cli/cmd/network/create.go
  24. 0 1
      cli/cmd/network/flags.go
  25. 0 4
      cli/cmd/network/update.go
  26. 8 5
      cli/functions/host.go
  27. 0 2
      cli/samples/network.json
  28. 0 4
      cli/samples/node.json
  29. 83 0
      compose/docker-compose-emqx.yml
  30. 19 24
      compose/docker-compose.ee.yml
  31. 16 12
      compose/docker-compose.reference.yml
  32. 5 9
      compose/docker-compose.yml
  33. 4 7
      config/config.go
  34. 4 4
      config/config_test.go
  35. 0 1
      config/environments/dev.yaml
  36. 1 2
      controllers/config/environments/dev.yaml
  37. 5 11
      controllers/controller.go
  38. 18 3
      controllers/dns.go
  39. 29 40
      controllers/dns_test.go
  40. 1 1
      controllers/docs.go
  41. 238 0
      controllers/enrollmentkeys.go
  42. 28 12
      controllers/ext_client.go
  43. 28 8
      controllers/hosts.go
  44. 17 5
      controllers/network.go
  45. 22 24
      controllers/network_test.go
  46. 58 25
      controllers/node.go
  47. 0 3
      controllers/node_test.go
  48. 62 49
      controllers/user_test.go
  49. 3 0
      database/database.go
  50. 0 7
      dev.yaml
  51. 1 1
      docker/Caddyfile
  52. 2 1
      docker/Caddyfile-EE
  53. 22 8
      functions/helpers_test.go
  54. 9 10
      go.mod
  55. 20 34
      go.sum
  56. 1 1
      k8s/client/netclient-daemonset.yaml
  57. 1 1
      k8s/client/netclient.yaml
  58. 5 7
      k8s/server/netmaker-server.yaml
  59. 1 1
      k8s/server/netmaker-ui.yaml
  60. 0 2
      logic/accesskeys_test.go
  61. 221 0
      logic/enrollmentkey.go
  62. 206 0
      logic/enrollmentkey_test.go
  63. 12 0
      logic/host_test.go
  64. 38 0
      logic/hostactions/hostactions.go
  65. 9 17
      logic/hosts.go
  66. 0 37
      logic/metrics/metrics.go
  67. 1 47
      logic/nodes.go
  68. 96 649
      logic/peers.go
  69. 5 1
      logic/pro/networkuser_test.go
  70. 0 2
      logic/pro/usergroups_test.go
  71. 60 26
      logic/zombie.go
  72. 27 40
      main.go
  73. 9 2
      models/api_host.go
  74. 0 3
      models/api_node.go
  75. 39 0
      models/dnsEntry.go
  76. 59 0
      models/enrollment_key.go
  77. 4 0
      models/host.go
  78. 13 27
      models/mqtt.go
  79. 0 4
      models/network.go
  80. 1 1
      models/network_test.go
  81. 0 14
      models/node.go
  82. 1 1
      models/structs.go
  83. 154 0
      mq/emqx.go
  84. 23 8
      mq/handlers.go
  85. 17 0
      mq/mq.go
  86. 299 85
      mq/publishers.go
  87. 0 9
      netclient/global_settings/globalsettings.go
  88. 0 78
      netclient/ncutils/iface.go
  89. 0 593
      netclient/ncutils/netclientutils.go
  90. 0 39
      netclient/ncutils/netclientutils_darwin.go
  91. 0 50
      netclient/ncutils/netclientutils_freebsd.go
  92. 0 32
      netclient/ncutils/netclientutils_linux.go
  93. 0 61
      netclient/ncutils/netclientutils_windows.go
  94. 0 97
      netclient/ncutils/peerhelper.go
  95. 0 46
      netclient/ncutils/pid.go
  96. 0 25
      netclient/ncutils/util.go
  97. BIN
      netclient/ncutils/windowsdaemon/winsw.exe
  98. 7 0
      release.md
  99. 146 55
      scripts/nm-quick.sh
  100. 1 3
      scripts/nm-upgrade.sh

+ 4 - 2
.github/ISSUE_TEMPLATE/bug-report.yml

@@ -31,8 +31,10 @@ body:
       label: Version
       description: What version are you running?
       options:
-        - v0.18.0      
-        - v0.17.1      
+        - v0.18.2
+        - v0.18.1
+        - v0.18.0
+        - v0.17.1
         - v0.17.0
         - v0.16.3
         - v0.16.2

+ 15 - 0
.github/pull_request_template.md

@@ -0,0 +1,15 @@
+## Describe your changes
+
+## Provide Issue ticket number if applicable/not in title
+
+## Provide testing steps
+
+## Checklist before requesting a review
+- [ ] My changes affect only 10 files or less.
+- [ ] I have performed a self-review of my code and tested it.
+- [ ] If it is a new feature, I have added thorough tests, my code is <= 1450 lines.
+- [ ] If it is a bugfix, my code is <= 200 lines.
+- [ ] My functions are <= 80 lines.
+- [ ] I have had my code reviewed by a peer.
+- [ ] My unit tests pass locally.
+- [ ] Netmaker is awesome.

+ 0 - 73
.github/workflows/buildandrelease.yml

@@ -1,73 +0,0 @@
-name: Build and Release
-
-on:
-  workflow_dispatch:
-    inputs:
-      version:
-        description: 'Netmaker version'
-        required: true
-  release:
-    types: [published]
-
-jobs:
-  version:
-    runs-on: ubuntu-latest
-    outputs:
-      tag: ${{ steps.echo.outputs.tag }}
-      version: ${{ steps.echo.outputs.version }}
-    steps:
-      - name: Get Version Number
-        run: |
-          if [[ -n "${{ github.event.inputs.version }}" ]]; then
-            NETMAKER_VERSION=${{ github.event.inputs.version }}
-          else
-            NETMAKER_VERSION=$(curl -fsSL https://api.github.com/repos/gravitl/netmaker/tags | grep 'name' | head -1 | cut -d'"' -f4)
-          fi
-          echo "NETMAKER_VERSION=${NETMAKER_VERSION}" >> $GITHUB_ENV
-          # remove everything but digits and . for package (deb, rpm, etc) versions
-          PACKAGE_VERSION=$(echo ${NETMAKER_VERSION} | tr -cd '[:digit:].')
-          echo "PACKAGE_VERSION=${PACKAGE_VERSION}" >> $GITHUB_ENV
-      - name: Echo
-        id: echo
-        run: |
-          echo ${{ env.NETMAKER_VERSION }}
-          echo ${{ env.PACKAGE_VERSION }}
-          if [[ -z ${{ env.NETMAKER_VERSION }} || -z ${{ env.PACKAGE_VERSION }} ]]
-          then
-            exit 1
-          fi
-          echo "::set-output name=tag::${{ env.NETMAKER_VERSION }}"
-          echo "::set-output name=version::${{ env.PACKAGE_VERSION }}"
-  netmaker:
-    runs-on: ubuntu-latest
-    needs: version
-    steps:
-      - name: set variables
-        run: |
-          echo ${{ needs.version.outputs.tag }} ${{ needs.version.outputs.version }}
-          TAG=${{needs.version.outputs.tag}}
-          VERSION=${{needs.version.outputs.version}}
-          if [[ -z ${VERSION} || -z ${TAG} ]]; then
-            exit 1
-          fi
-          echo "NETMAKER_VERSION=${TAG}"  >> $GITHUB_ENV
-          echo "PACKAGE_VERSION=${VERSION}" >> $GITHUB_ENV
-      - name: Checkout
-        uses: actions/checkout@v3
-      - name: Setup go
-        uses: actions/setup-go@v3
-        with:
-          go-version: 1.19
-      - name: Build
-        run: |
-          env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netmaker main.go
-      - name: Upload netmaker x86 to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: build/netmaker
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netmaker
-

+ 3 - 2
.github/workflows/docker-builder.yml

@@ -2,8 +2,9 @@ name: Build go-builder images
 
 on:
   workflow_dispatch:
-  schedule:
-    - cron: '00 21 * * SUN'
+  push:
+    branches:
+      - 'develop'
 
 jobs:
   go-builder:

+ 46 - 0
.github/workflows/packages.yml

@@ -0,0 +1,46 @@
+name: Build linux packages
+on:
+  workflow_call:
+    inputs:
+      version:
+        required: true
+        type: string
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "netmaker version"
+        required: true
+  release:
+    types: [released]
+
+jobs:
+
+  packages:
+    runs-on: ubuntu-latest
+    steps:
+      - name: setup ssh
+        run: |
+          mkdir -p ~/.ssh/
+          echo "$SSH_KEY" > ~/.ssh/id_devops
+          chmod 600 ~/.ssh/id_devops
+          cat >>~/.ssh/config <<END
+          Host *.clustercat.com
+            User root
+            IdentityFile ~/.ssh/id_devops
+            StrictHostKeyChecking no
+          END
+        env:
+          SSH_KEY: ${{ secrets.TESTING_SSH_KEY }}
+      - name: set version
+        run: |
+          if [[ -n "${{ github.event.inputs.version }}" ]]; then
+            # remove everything but digits and . for package (deb, rpm, etc) versions
+            VERSION=$(echo ${{ github.event.inputs.version }} | tr -cd '[:digit:].')
+          else
+            VERSION=$(echo ${{ github.ref.name }} | tr -cd '[:digit:].')
+          fi
+            echo "VERSION=${VERSION}" >> $GITHUB_ENV
+            echo ${VERSION}
+      - name: apt/rpm
+        run: |
+          ssh fileserver.clustercat.com "cd packages/nmcli; export VERSION=${{ ENV.version }}; export REVISION=0; ./buildall.sh "

+ 5 - 4
.github/workflows/publish-docker.yml

@@ -6,8 +6,11 @@ on:
       tag:
         description: 'docker tag'
         required: true
-  release:
-    types: [published]
+  workflow_call:
+    inputs:
+      tag:
+        type: string
+        required: true
 
 jobs:
   docker:
@@ -48,7 +51,6 @@ jobs:
           push: true
           tags: ${{ github.repository }}:${{ env.TAG }}, ${{ github.repository }}:latest
           build-args: | 
-            version=${{ env.TAG }}
             tags=ce
 
   docker-ee:
@@ -89,5 +91,4 @@ jobs:
           push: true
           tags: ${{ github.repository }}:${{ env.TAG }}-ee
           build-args: |
-            version=${{ env.TAG }}
             tags=ee

+ 32 - 0
.github/workflows/pull-request.yml

@@ -0,0 +1,32 @@
+# creates a PR from release branch to master
+name: Create Release PR to master
+on:
+  workflow_call:
+    inputs:
+      version:
+        required: true
+        type: string
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "netmaker version"
+        required: true
+  release:
+    types: [released]
+
+jobs:
+  pr-to-main:
+    runs-on: ubuntu-latest
+    steps:
+      - name: create pr
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: |
+          gh api --method POST \
+          -H 'Accept: application/vnd.github+json' -H 'X-GitHub-Api-Version: 2022-11-28'  \
+          /repos/${{ github.repository }}/pulls \
+          -f title='${{ github.event.inputs.version }}' \
+          -f head='release_${{ github.event.inputs.version }}' \
+          -f base="master"
+
+

+ 0 - 29
.github/workflows/purgeGHCR.yml

@@ -1,29 +0,0 @@
-name: Purge untagged images from GHCR
-
-on: 
-    workflow_dispatch:
-    schedule:
-      - cron: '1 1 1 * *'
-jobs:
-  purge:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-      - name: Prune Netmaker
-        uses: vlaurin/action-ghcr-prune@main
-        with:
-          token: ${{ secrets.GITHUB_TOKEN }}
-          organization: gravitl
-          container: netmaker
-          dry-run: false # Dry-run first, then change to `false`
-          untagged: true
-      - name: Prune Netclient
-        uses: vlaurin/action-ghcr-prune@main
-        with:
-          token: ${{ secrets.GITHUB_TOKEN }}
-          organization: gravitl
-          container: netclient
-          dry-run: false # Dry-run first, then change to `false`
-          untagged: true
-          

+ 50 - 0
.github/workflows/release-assets.yml

@@ -0,0 +1,50 @@
+# generates release assets and uploads to release 
+name: Upload Release Assets
+on:
+  workflow_call:
+    inputs:
+      version:
+        required: true
+        type: string
+      prerelease:
+        required: true
+        type: boolean
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "netmaker version"
+        required: true
+      prerelease:
+        required: true
+        type: boolean
+        description: "prerelease"
+jobs:
+  release-assets:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+        with:
+          ref: release_${{ github.event.inputs.version}}
+          fetch-depth: 0
+      - name: Get Tags
+        run: |
+          git fetch --force --tags
+      - name: Setup go
+        uses: actions/setup-go@v3
+        with:
+          go-version: 1.19
+      - name: GoReleaser (full release)
+        if: ${{ github.event.inputs.prerelease == 'false'}}
+        uses: goreleaser/goreleaser-action@v4
+        with:
+          args: release --clean --release-notes release.md
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      - name: GoReleaser (prerelease)
+        if: ${{ github.event.inputs.prerelease == 'false'}}
+        uses: goreleaser/goreleaser-action@v4
+        with:
+          args: release --clean --release-notes release.md -f .goreleaser.prerelease.yaml
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

+ 39 - 0
.github/workflows/release-branch.yml

@@ -0,0 +1,39 @@
+# creates new branch (release_{{ version }} with tag {{ version }} from develop 
+# will fail if branch or tag already exists on github
+name: Create Release Branch
+on:
+  workflow_call:
+    inputs:
+      version:
+        required: true
+        type: string
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "netmaker version"
+        required: true
+jobs:
+  release-branch:
+    runs-on: ubuntu-latest
+    steps:
+      - name: checkout develop
+        uses: actions/checkout@v3
+        with:
+          ref: develop
+      - name: setup go
+        uses: actions/setup-go@v3
+        with:
+          go-version: 1.19
+      - name: setup git
+        run: |
+          git config user.name "Github Actions"
+          git config user.emaail "[email protected]"
+      - name: create release Branch
+        run: |
+          git switch -c release_${{ github.event.inputs.version }}
+          git tag -f ${{ github.event.inputs.version }}
+          #push branch
+          git push origin release_${{ github.event.inputs.version }}
+          #push tag
+          git push origin ${{ github.event.inputs.version }}
+  

+ 49 - 0
.github/workflows/release.yml

@@ -0,0 +1,49 @@
+# creates a release from develop
+# creates release branch, generates release assets, publishes docker image and copies release.md to release
+# if formal release, linux packages are generated and a PR from release branch to master is created 
+name: Release
+
+on:
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "new version number"
+        required: true
+      prerelease:
+        required: true
+        type: boolean
+        description: Is this a pre-release
+
+jobs:
+
+  release-branch:
+    uses: ./.github/workflows/release-branch.yml
+    with:
+      version: ${{ github.event.inputs.version }}
+  
+  release-assets:
+    needs: release-branch
+    uses: ./.github/workflows/release-assets.yml
+    with:
+      version: ${{ github.event.inputs.version }}
+      prerelease: ${{ github.event.inputs.prerelease == 'true' }}
+
+  docker:
+    needs: release-branch
+    uses: ./.github/workflows/publish-docker.yml
+    with:
+      tag: ${{ github.event.inputs.version }}
+
+  packages:
+    if: ${{ github.event.inputs.prerelease == 'false' }}
+    needs: release-branch
+    uses: ./.github/workflows/packages.yml
+    with:
+      version: ${{ github.event.inputs.version }}
+
+  pull-request:
+    if: ${{ github.event.inputs.prerelease == 'false' }}
+    needs: release-branch
+    uses: ./.github/workflows/pull-request.yml
+    with:
+      version: ${{ github.event.inputs.version }}

+ 1 - 0
.github/workflows/test.yml

@@ -49,6 +49,7 @@ jobs:
           go-version: 1.19
       - name: run tests
         run: |
+          go vet ./...
           go test -p 1 ./... -v
         env:
           DATABASE: sqlite

+ 41 - 0
.github/workflows/upgraderelease.yml

@@ -0,0 +1,41 @@
+name: UpgradeRelease
+
+on:
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "release/version to update"
+        required: true
+
+jobs:
+
+  update:
+    runs-on: ubuntu-latest
+    steps:
+      - name: checkout
+        uses: actions/checkout@v3
+        with:
+          ref: release_${{ github.event.inputs.version }}
+          fetch-depth: 0
+      - run: |
+          git fetch --force --tags
+      - name: Setup go
+        uses: actions/setup-go@v3
+        with:
+          go-version: 1.19
+      - name: goreleaser
+        uses: goreleaser/goreleaser-action@v4
+        with:
+          args: release --clean --release-notes release.md -f .goreleaser.update.yaml
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN  }}
+
+  packages:
+    uses: ./.github/workflows/packages.yml
+    with:
+      version: ${{ github.event.inputs.version }}
+
+  pull-request:
+    uses: ./.github/workflows/pull-request.yml
+    with:
+      version: ${{ github.event.inputs.version }}

+ 1 - 0
.gitignore

@@ -24,3 +24,4 @@ data/
 .idea/
 netmaker.exe
 netmaker.code-workspace
+dist/

+ 34 - 0
.goreleaser.prerelease.yaml

@@ -0,0 +1,34 @@
+before:
+  hooks:
+    # You may remove this if you don't use go modules.
+    - go mod tidy
+builds:
+  - main: ./
+    env:
+      - CGO_ENABLED=1
+    ldflags:
+      - -s -w
+    targets:
+      - linux_amd64
+    binary: '{{ .ProjectName }}'
+
+  - main: ./cli
+    id: 'nmctl'
+    env:
+      - CGO_ENABLED=0
+    ldflags:
+      - -s -w
+    targets:
+      - linux_amd64
+      - linux_arm64
+      - darwin_amd64
+      - darwin_arm64
+      - freebsd_amd64
+      - windows_amd64
+    binary: 'nmctl'
+archives:
+  - format: binary
+    name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}'
+release:
+  prerelease: true
+    

+ 4 - 0
.goreleaser.update.yaml

@@ -0,0 +1,4 @@
+builds:
+  - skip: true
+release:
+  prerelease: false

+ 34 - 0
.goreleaser.yaml

@@ -0,0 +1,34 @@
+before:
+  hooks:
+    # You may remove this if you don't use go modules.
+    - go mod tidy
+builds:
+  - main: ./
+    env:
+      - CGO_ENABLED=1
+    ldflags:
+      - -s -w
+    targets:
+      - linux_amd64
+    binary: '{{ .ProjectName }}'
+
+  - main: ./cli
+    id: 'nmctl'
+    env:
+      - CGO_ENABLED=0
+    ldflags:
+      - -s -w
+    targets:
+      - linux_amd64
+      - linux_arm64
+      - darwin_amd64
+      - darwin_arm64
+      - freebsd_amd64
+      - windows_amd64
+    binary: 'nmctl'
+archives:
+  - format: binary
+    name_template: '{{ .Binary }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}'
+release:
+  prerelease: false
+    

+ 1 - 6
Dockerfile

@@ -1,18 +1,13 @@
 #first stage - builder
 FROM gravitl/go-builder as builder
-ARG version
 ARG tags 
 WORKDIR /app
 COPY . .
-ENV GO111MODULE=auto
 
-RUN apk add git
-RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -X 'main.version=${version}'" -tags ${tags} .
+RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -w" -tags ${tags} .
 # RUN go build -tags=ee . -o netmaker main.go
 FROM alpine:3.16.2
 
-# add a c lib
-RUN apk add gcompat iptables wireguard-tools
 # set the working directory
 WORKDIR /root/
 RUN mkdir -p /etc/netclient/config

+ 2 - 2
README.md

@@ -17,7 +17,7 @@
 
 <p align="center">
   <a href="https://github.com/gravitl/netmaker/releases">
-    <img src="https://img.shields.io/badge/Version-0.18.0-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.18.2-informational?style=flat-square" />
   </a>
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />
@@ -57,7 +57,7 @@
 3. (optional) Prepare DNS - Set a wildcard subdomain in your DNS for Netmaker, e.g. *.netmaker.example.com
 4. Run the script: 
 
-`sudo wget -qO /root/nm-quick-interactive.sh https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/nm-quick-interactive.sh && sudo chmod +x /root/nm-quick-interactive.sh && sudo /root/nm-quick-interactive.sh`  
+`sudo wget -qO /root/nm-quick.sh https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/nm-quick.sh && sudo chmod +x /root/nm-quick.sh && sudo /root/nm-quick.sh`  
 
 This script gives you the option to deploy the Community or Enterprise version of Netmaker. If deploying Enterprise, you get a free account with a 50 node limit by default. It also gives you the option to use your own domain (recommended) or an auto-generated domain. 
 

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

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

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

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

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

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

+ 0 - 4
cli/cmd/network/create.go

@@ -35,9 +35,6 @@ var networkCreateCmd = &cobra.Command{
 			if udpHolePunch {
 				network.DefaultUDPHolePunch = "yes"
 			}
-			if localNetwork {
-				network.IsLocal = "yes"
-			}
 			if defaultACL {
 				network.DefaultACL = "yes"
 			}
@@ -62,7 +59,6 @@ func init() {
 	networkCreateCmd.Flags().StringVar(&address, "ipv4_addr", "", "IPv4 address of the network")
 	networkCreateCmd.Flags().StringVar(&address6, "ipv6_addr", "", "IPv6 address of the network")
 	networkCreateCmd.Flags().BoolVar(&udpHolePunch, "udp_hole_punch", false, "Enable UDP Hole Punching ?")
-	networkCreateCmd.Flags().BoolVar(&localNetwork, "local", false, "Is the network local (LAN) ?")
 	networkCreateCmd.Flags().BoolVar(&defaultACL, "default_acl", false, "Enable default Access Control List ?")
 	networkCreateCmd.Flags().StringVar(&defaultInterface, "interface", "", "Name of the network interface")
 	networkCreateCmd.Flags().StringVar(&defaultExtClientDNS, "ext_client_dns", "", "IPv4 address of DNS server to be used by external clients")

+ 0 - 1
cli/cmd/network/flags.go

@@ -6,7 +6,6 @@ var (
 	address                   string
 	address6                  string
 	udpHolePunch              bool
-	localNetwork              bool
 	defaultACL                bool
 	defaultInterface          string
 	defaultListenPort         int

+ 0 - 4
cli/cmd/network/update.go

@@ -38,9 +38,6 @@ var networkUpdateCmd = &cobra.Command{
 			if udpHolePunch {
 				network.DefaultUDPHolePunch = "yes"
 			}
-			if localNetwork {
-				network.IsLocal = "yes"
-			}
 			if defaultACL {
 				network.DefaultACL = "yes"
 			}
@@ -63,7 +60,6 @@ func init() {
 	networkUpdateCmd.Flags().StringVar(&address, "ipv4_addr", "", "IPv4 address of the network")
 	networkUpdateCmd.Flags().StringVar(&address6, "ipv6_addr", "", "IPv6 address of the network")
 	networkUpdateCmd.Flags().BoolVar(&udpHolePunch, "udp_hole_punch", false, "Enable UDP Hole Punching ?")
-	networkUpdateCmd.Flags().BoolVar(&localNetwork, "local", false, "Is the network local (LAN) ?")
 	networkUpdateCmd.Flags().BoolVar(&defaultACL, "default_acl", false, "Enable default Access Control List ?")
 	networkUpdateCmd.Flags().StringVar(&defaultInterface, "interface", "", "Name of the network interface")
 	networkUpdateCmd.Flags().StringVar(&defaultExtClientDNS, "ext_client_dns", "", "IPv4 address of DNS server to be used by external clients")

+ 8 - 5
cli/functions/host.go

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

+ 0 - 2
cli/samples/network.json

@@ -10,11 +10,9 @@
     "defaultinterface": "nm-test3",
     "accesskeys": [],
     "allowmanualsignup": "no",
-    "islocal": "no",
     "isipv4": "yes",
     "isipv6": "no",
     "ispointtosite": "no",
-    "localrange": "",
     "defaultudpholepunch": "yes",
     "defaultextclientdns": "",
     "defaultmtu": 1280,

+ 0 - 4
cli/samples/node.json

@@ -18,11 +18,9 @@
       "defaultkeepalive": 20,
       "accesskeys": [],
       "allowmanualsignup": "no",
-      "islocal": "no",
       "isipv4": "no",
       "isipv6": "yes",
       "ispointtosite": "no",
-      "localrange": "",
       "defaultudpholepunch": "yes",
       "defaultextclientdns": "",
       "defaultmtu": 1280,
@@ -82,8 +80,6 @@
     "dnson": "no",
     "isserver": "yes",
     "action": "noop",
-    "islocal": "no",
-    "localrange": "",
     "ipforwarding": "yes",
     "os": "linux",
     "mtu": 1280,

+ 83 - 0
compose/docker-compose-emqx.yml

@@ -0,0 +1,83 @@
+version: "3.4"
+
+services:
+  netmaker:
+    container_name: netmaker
+    image: gravitl/netmaker:v0.18.2
+    restart: always
+    volumes:
+      - dnsconfig:/root/config/dnsconfig
+      - sqldata:/root/data
+    environment:
+      BROKER_ENDPOINT: "wss://broker.NETMAKER_BASE_DOMAIN/mqtt"
+      BROKER_TYPE: "emqx"
+      EMQX_REST_ENDPOINT: "http://mq:18083"
+      SERVER_NAME: "NETMAKER_BASE_DOMAIN"
+      STUN_DOMAIN: "stun.NETMAKER_BASE_DOMAIN"
+      SERVER_HOST: "SERVER_PUBLIC_IP"
+      SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
+      COREDNS_ADDR: "SERVER_PUBLIC_IP"
+      DNS_MODE: "on"
+      SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN"
+      API_PORT: "8081"
+      MASTER_KEY: "REPLACE_MASTER_KEY"
+      CORS_ALLOWED_ORIGIN: "*"
+      DISPLAY_KEYS: "on"
+      DATABASE: "sqlite"
+      NODE_ID: "netmaker-server-1"
+      SERVER_BROKER_ENDPOINT: "ws://mq:8083/mqtt"
+      STUN_PORT: "3478"      
+      VERBOSITY: "1"
+      MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
+      MQ_USERNAME: "REPLACE_MQ_USERNAME"
+    ports:
+      - "3478:3478/udp"
+  netmaker-ui:
+    container_name: netmaker-ui
+    image: gravitl/netmaker-ui:v0.18.2
+    depends_on:
+      - netmaker
+    links:
+      - "netmaker:api"
+    restart: always
+    environment:
+      BACKEND_URL: "https://api.NETMAKER_BASE_DOMAIN"
+  caddy:
+    image: caddy:2.6.2
+    container_name: caddy
+    restart: unless-stopped
+    volumes:
+      - /root/Caddyfile:/etc/caddy/Caddyfile
+      - caddy_data:/data
+      - caddy_conf:/config
+    ports:
+      - "80:80"
+      - "443:443"
+  coredns:
+    container_name: coredns
+    image: coredns/coredns
+    command: -conf /root/dnsconfig/Corefile
+    depends_on:
+      - netmaker
+    restart: always
+    volumes:
+      - dnsconfig:/root/dnsconfig
+  mq:
+    container_name: mq
+    image: emqx/emqx:5.0.17
+    restart: unless-stopped
+    environment:
+      EMQX_NAME: "emqx"
+      EMQX_DASHBOARD__DEFAULT_PASSWORD: "REPLACE_MQ_PASSWORD"
+      EMQX_DASHBOARD__DEFAULT_USERNAME: "REPLACE_MQ_USERNAME"
+    ports:
+      - "1883:1883" # MQTT
+      - "8883:8883" # SSL MQTT
+      - "8083:8083" # Websockets
+      - "18083:18083" # Dashboard/REST_API
+volumes:
+  caddy_data: {}
+  caddy_conf: {}
+  sqldata: {}
+  dnsconfig: {}
+  mosquitto_logs: {}

+ 19 - 24
compose/docker-compose.ee.yml

@@ -3,13 +3,16 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.18.0-ee
+    image: gravitl/netmaker:REPLACE_SERVER_IMAGE_TAG
     restart: always
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
     environment:
-      SERVER_NAME: "broker.NETMAKER_BASE_DOMAIN"
+      BROKER_ENDPOINT: "wss://broker.NETMAKER_BASE_DOMAIN/mqtt"
+      BROKER_TYPE: "emqx"
+      EMQX_REST_ENDPOINT: "http://mq:18083"
+      SERVER_NAME: "NETMAKER_BASE_DOMAIN"
       STUN_DOMAIN: "stun.NETMAKER_BASE_DOMAIN"
       SERVER_HOST: "SERVER_PUBLIC_IP"
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
@@ -22,22 +25,19 @@ services:
       DISPLAY_KEYS: "on"
       DATABASE: "sqlite"
       NODE_ID: "netmaker-server-1"
-      MQ_HOST: "mq"
-      MQ_PORT: "443"
+      SERVER_BROKER_ENDPOINT: "ws://mq:8083/mqtt"
+      MQ_USERNAME: "REPLACE_MQ_USERNAME"
+      MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
       STUN_PORT: "3478"
-      MQ_SERVER_PORT: "1883"
       VERBOSITY: "1"
       METRICS_EXPORTER: "on"
       LICENSE_KEY: "YOUR_LICENSE_KEY"
       NETMAKER_ACCOUNT_ID: "YOUR_ACCOUNT_ID"
-      MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
-      MQ_USERNAME: "REPLACE_MQ_USERNAME"
     ports:
-      - "51821-51830:51821-51830/udp"
       - "3478:3478/udp"
   netmaker-ui:
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.18.0
+    image: gravitl/netmaker-ui:REPLACE_UI_IMAGE_TAG
     depends_on:
       - netmaker
     links:
@@ -67,21 +67,17 @@ services:
       - dnsconfig:/root/dnsconfig
   mq:
     container_name: mq
-    image: eclipse-mosquitto:2.0.15-openssl
-    depends_on:
-      - netmaker
+    image: emqx/emqx:5.0.17
     restart: unless-stopped
-    command: ["/mosquitto/config/wait.sh"]
     environment:
-      MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
-      MQ_USERNAME: "REPLACE_MQ_USERNAME"
-    volumes:
-      - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf
-      - /root/wait.sh:/mosquitto/config/wait.sh
-      - mosquitto_logs:/mosquitto/log
+      EMQX_NAME: "emqx"
+      EMQX_DASHBOARD__DEFAULT_PASSWORD: "REPLACE_MQ_PASSWORD"
+      EMQX_DASHBOARD__DEFAULT_USERNAME: "REPLACE_MQ_USERNAME"
     ports:
-      - "1883:1883"
-      - "8883:8883"
+      - "1883:1883" # MQTT
+      - "8883:8883" # SSL MQTT
+      - "8083:8083" # Websockets
+      - "18083:18083" # Dashboard/REST_API
   prometheus:
     container_name: prometheus
     image: gravitl/netmaker-prometheus:latest
@@ -115,9 +111,8 @@ services:
     depends_on:
       - netmaker
     environment:
-      MQ_HOST: "mq"
-      MQ_PORT: "443"
-      MQ_SERVER_PORT: "1883"
+      SERVER_BROKER_ENDPOINT: "ws://mq:8083/mqtt"
+      BROKER_ENDPOINT: "wss://broker.NETMAKER_BASE_DOMAIN/mqtt"
       PROMETHEUS: "on"
       VERBOSITY: "1"
       API_PORT: "8085"

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

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

+ 5 - 9
compose/docker-compose.yml

@@ -3,13 +3,13 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.18.0
+    image: gravitl/netmaker:REPLACE_SERVER_IMAGE_TAG
     restart: always
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
     environment:
-      BROKER_NAME: "broker.NETMAKER_BASE_DOMAIN"
+      BROKER_ENDPOINT: "wss://broker.NETMAKER_BASE_DOMAIN"
       SERVER_NAME: "NETMAKER_BASE_DOMAIN"
       STUN_DOMAIN: "stun.NETMAKER_BASE_DOMAIN"
       SERVER_HOST: "SERVER_PUBLIC_IP"
@@ -23,20 +23,16 @@ services:
       DISPLAY_KEYS: "on"
       DATABASE: "sqlite"
       NODE_ID: "netmaker-server-1"
-      MQ_HOST: "mq"
-      MQ_PORT: "443"
-      MQ_SERVER_PORT: "1883"
+      SERVER_BROKER_ENDPOINT: "ws://mq:1883"
+      STUN_PORT: "3478"      
       VERBOSITY: "1"
       MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
       MQ_USERNAME: "REPLACE_MQ_USERNAME"
-      STUN_PORT: "3478"
-      PROXY: "on"
     ports:
-      - "51821-51830:51821-51830/udp"
       - "3478:3478/udp"
   netmaker-ui:
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.18.0
+    image: gravitl/netmaker-ui:REPLACE_UI_IMAGE_TAG
     depends_on:
       - netmaker
     links:

+ 4 - 7
config/config.go

@@ -36,13 +36,15 @@ type ServerConfig struct {
 	APIConnString         string `yaml:"apiconn"`
 	APIHost               string `yaml:"apihost"`
 	APIPort               string `yaml:"apiport"`
-	MQHOST                string `yaml:"mqhost"`
+	Broker                string `yam:"broker"`
+	ServerBrokerEndpoint  string `yaml:"serverbrokerendpoint"`
+	BrokerType            string `yaml:"brokertype"`
+	EmqxRestEndpoint      string `yaml:"emqxrestendpoint"`
 	MasterKey             string `yaml:"masterkey"`
 	DNSKey                string `yaml:"dnskey"`
 	AllowedOrigin         string `yaml:"allowedorigin"`
 	NodeID                string `yaml:"nodeid"`
 	RestBackend           string `yaml:"restbackend"`
-	AgentBackend          string `yaml:"agentbackend"`
 	MessageQueueBackend   string `yaml:"messagequeuebackend"`
 	DNSMode               string `yaml:"dnsmode"`
 	DisableRemoteIPCheck  string `yaml:"disableremoteipcheck"`
@@ -50,9 +52,7 @@ type ServerConfig struct {
 	SQLConn               string `yaml:"sqlconn"`
 	Platform              string `yaml:"platform"`
 	Database              string `yaml:"database"`
-	DefaultNodeLimit      int32  `yaml:"defaultnodelimit"`
 	Verbosity             int32  `yaml:"verbosity"`
-	ServerCheckinInterval int64  `yaml:"servercheckininterval"`
 	AuthProvider          string `yaml:"authprovider"`
 	OIDCIssuer            string `yaml:"oidcissuer"`
 	ClientID              string `yaml:"clientid"`
@@ -62,10 +62,7 @@ type ServerConfig struct {
 	AzureTenant           string `yaml:"azuretenant"`
 	Telemetry             string `yaml:"telemetry"`
 	HostNetwork           string `yaml:"hostnetwork"`
-	MQPort                string `yaml:"mqport"`
-	MQServerPort          string `yaml:"mqserverport"`
 	Server                string `yaml:"server"`
-	Broker                string `yam:"broker"`
 	PublicIPService       string `yaml:"publicipservice"`
 	MQPassword            string `yaml:"mqpassword"`
 	MQUserName            string `yaml:"mqusername"`

+ 4 - 4
config/config_test.go

@@ -1,7 +1,7 @@
-//Environment file for getting variables
-//Currently the only thing it does is set the master password
-//Should probably have it take over functions from OS such as port and mongodb connection details
-//Reads from the config/environments/dev.yaml file by default
+// Environment file for getting variables
+// Currently the only thing it does is set the master password
+// Should probably have it take over functions from OS such as port and mongodb connection details
+// Reads from the config/environments/dev.yaml file by default
 package config
 
 import (

+ 0 - 1
config/environments/dev.yaml

@@ -4,7 +4,6 @@ server:
   masterkey: "" # defaults to 'secretkey' or MASTER_KEY (if set)
   allowedorigin: "" # defaults to '*' or CORS_ALLOWED_ORIGIN (if set)
   restbackend: "" # defaults to "on" or REST_BACKEND (if set)
-  agentbackend: "" # defaults to "on" or AGENT_BACKEND (if set)
   dnsmode: "" # defaults to "on" or DNS_MODE (if set)
   sqlconn: "" # defaults to "http://" or SQL_CONN (if set)
   disableremoteipcheck: "" # defaults to "false" or DISABLE_REMOTE_IP_CHECK (if set)

+ 1 - 2
controllers/config/environments/dev.yaml

@@ -4,7 +4,6 @@ server:
   masterkey: ""
   allowedorigin: "*"
   restbackend: true            
-  agentbackend: true
   defaultnetname: "default"
   defaultnetrange: "10.10.10.0/24"
-  createdefault: true
+  createdefault: true

+ 5 - 11
controllers/controller.go

@@ -4,11 +4,8 @@ import (
 	"context"
 	"fmt"
 	"net/http"
-	"os"
-	"os/signal"
 	"strings"
 	"sync"
-	"syscall"
 	"time"
 
 	"github.com/gorilla/handlers"
@@ -29,10 +26,11 @@ var HttpHandlers = []interface{}{
 	ipHandlers,
 	loggerHandlers,
 	hostHandlers,
+	enrollmentKeyHandlers,
 }
 
 // HandleRESTRequests - handles the rest requests
-func HandleRESTRequests(wg *sync.WaitGroup) {
+func HandleRESTRequests(wg *sync.WaitGroup, ctx context.Context) {
 	defer wg.Done()
 
 	r := mux.NewRouter()
@@ -58,18 +56,14 @@ func HandleRESTRequests(wg *sync.WaitGroup) {
 	}()
 	logger.Log(0, "REST Server successfully started on port ", port, " (REST)")
 
-	// Relay os.Interrupt to our channel (os.Interrupt = CTRL+C)
-	// Ignore other incoming signals
-	ctx, stop := signal.NotifyContext(context.TODO(), syscall.SIGTERM, os.Interrupt)
-	defer stop()
-
 	// Block main routine until a signal is received
 	// As long as user doesn't press CTRL+C a message is not passed and our main routine keeps running
 	<-ctx.Done()
-
 	// After receiving CTRL+C Properly stop the server
 	logger.Log(0, "Stopping the REST server...")
+	if err := srv.Shutdown(context.TODO()); err != nil {
+		logger.Log(0, "REST shutdown error occurred -", err.Error())
+	}
 	logger.Log(0, "REST Server closed.")
 	logger.DumpFile(fmt.Sprintf("data/netmaker.log.%s", time.Now().Format(logger.TimeFormatDay)))
-	srv.Shutdown(context.TODO())
 }

+ 18 - 3
controllers/dns.go

@@ -176,9 +176,14 @@ func createDNS(w http.ResponseWriter, r *http.Request) {
 	}
 	logger.Log(1, "new DNS record added:", entry.Name)
 	if servercfg.IsMessageQueueBackend() {
-		if err = mq.PublishPeerUpdate(); err != nil {
-			logger.Log(0, "failed to publish peer update after ACL update on", entry.Network)
-		}
+		go func() {
+			if err = mq.PublishPeerUpdate(); err != nil {
+				logger.Log(0, "failed to publish peer update after ACL update on", entry.Network)
+			}
+			if err := mq.PublishCustomDNS(&entry); err != nil {
+				logger.Log(0, "error publishing custom dns", err.Error())
+			}
+		}()
 	}
 	logger.Log(2, r.Header.Get("user"),
 		fmt.Sprintf("DNS entry is set: %+v", entry))
@@ -221,6 +226,16 @@ func deleteDNS(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	json.NewEncoder(w).Encode(entrytext + " deleted.")
+	go func() {
+		dns := models.DNSUpdate{
+			Action: models.DNSDeleteByName,
+			Name:   entrytext,
+		}
+		if err := mq.PublishDNSUpdate(params["network"], dns); err != nil {
+			logger.Log(0, "failed to publish dns update", err.Error())
+		}
+	}()
+
 }
 
 // GetDNSEntry - gets a DNS entry

+ 29 - 40
controllers/dns_test.go

@@ -6,17 +6,16 @@ import (
 	"testing"
 
 	"github.com/google/uuid"
-	"github.com/gravitl/netmaker/database"
-	"github.com/gravitl/netmaker/logic"
-	"github.com/gravitl/netmaker/models"
 	"github.com/stretchr/testify/assert"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/models"
 )
 
 var dnsHost models.Host
 
 func TestGetAllDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
@@ -28,7 +27,7 @@ func TestGetAllDNS(t *testing.T) {
 	})
 	t.Run("OneEntry", func(t *testing.T) {
 		entry := models.DNSEntry{
-			"10.0.0.3", "", "newhost", "skynet",
+			Address: "10.0.0.3", Name: "newhost", Network: "skynet",
 		}
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
@@ -37,7 +36,7 @@ func TestGetAllDNS(t *testing.T) {
 		assert.Equal(t, 1, len(entries))
 	})
 	t.Run("MultipleEntry", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.7", "", "anotherhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.7", Name: "anotherhost", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		entries, err := logic.GetAllDNS()
@@ -47,7 +46,6 @@ func TestGetAllDNS(t *testing.T) {
 }
 
 func TestGetNodeDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
@@ -94,7 +92,6 @@ func TestGetNodeDNS(t *testing.T) {
 	})
 }
 func TestGetCustomDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	t.Run("NoNetworks", func(t *testing.T) {
@@ -115,7 +112,7 @@ func TestGetCustomDNS(t *testing.T) {
 		assert.Equal(t, 0, len(dns))
 	})
 	t.Run("EntryExist", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.3", "", "custom1", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.3", Name: "custom1", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		dns, err := logic.GetCustomDNS("skynet")
@@ -123,7 +120,7 @@ func TestGetCustomDNS(t *testing.T) {
 		assert.Equal(t, 1, len(dns))
 	})
 	t.Run("MultipleEntries", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.4", "", "host4", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.4", Name: "host4", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		dns, err := logic.GetCustomDNS("skynet")
@@ -133,7 +130,6 @@ func TestGetCustomDNS(t *testing.T) {
 }
 
 func TestGetDNSEntryNum(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
@@ -143,7 +139,7 @@ func TestGetDNSEntryNum(t *testing.T) {
 		assert.Equal(t, 0, num)
 	})
 	t.Run("NodeExists", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		num, err := logic.GetDNSEntryNum("newhost", "skynet")
@@ -152,7 +148,6 @@ func TestGetDNSEntryNum(t *testing.T) {
 	})
 }
 func TestGetDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
@@ -162,7 +157,7 @@ func TestGetDNS(t *testing.T) {
 		assert.Nil(t, dns)
 	})
 	t.Run("CustomDNSExists", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		dns, err := logic.GetDNS("skynet")
@@ -182,7 +177,7 @@ func TestGetDNS(t *testing.T) {
 		assert.Equal(t, 1, len(dns))
 	})
 	t.Run("NodeAndCustomDNS", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		dns, err := logic.GetDNS("skynet")
@@ -196,18 +191,16 @@ func TestGetDNS(t *testing.T) {
 }
 
 func TestCreateDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
-	entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+	entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 	dns, err := logic.CreateDNS(entry)
 	assert.Nil(t, err)
 	assert.Equal(t, "newhost", dns.Name)
 }
 
 func TestSetDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	t.Run("NoNetworks", func(t *testing.T) {
@@ -239,7 +232,7 @@ func TestSetDNS(t *testing.T) {
 		assert.Contains(t, string(content), "linuxhost.skynet")
 	})
 	t.Run("EntryExists", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.3", "", "newhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.3", Name: "newhost", Network: "skynet"}
 		_, err := logic.CreateDNS(entry)
 		assert.Nil(t, err)
 		err = logic.SetDNS()
@@ -255,12 +248,11 @@ func TestSetDNS(t *testing.T) {
 }
 
 func TestGetDNSEntry(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
 	createTestNode()
-	entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+	entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 	_, _ = logic.CreateDNS(entry)
 	t.Run("wrong net", func(t *testing.T) {
 		entry, err := GetDNSEntry("newhost", "w286 Toronto Street South, Uxbridge, ONirecat")
@@ -285,11 +277,10 @@ func TestGetDNSEntry(t *testing.T) {
 }
 
 func TestDeleteDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
-	entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
+	entry := models.DNSEntry{Address: "10.0.0.2", Name: "newhost", Network: "skynet"}
 	_, _ = logic.CreateDNS(entry)
 	t.Run("EntryExists", func(t *testing.T) {
 		err := logic.DeleteDNS("newhost", "skynet")
@@ -307,20 +298,19 @@ func TestDeleteDNS(t *testing.T) {
 }
 
 func TestValidateDNSUpdate(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
-	entry := models.DNSEntry{"10.0.0.2", "", "myhost", "skynet"}
+	entry := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "skynet"}
 	t.Run("BadNetwork", func(t *testing.T) {
-		change := models.DNSEntry{"10.0.0.2", "", "myhost", "badnet"}
+		change := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "badnet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
 	})
 	t.Run("EmptyNetwork", func(t *testing.T) {
-		//this can't actually happen as change.Network is populated if is blank
-		change := models.DNSEntry{"10.0.0.2", "", "myhost", ""}
+		// this can't actually happen as change.Network is populated if is blank
+		change := models.DNSEntry{Address: "10.0.0.2", Name: "myhost"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
@@ -333,14 +323,14 @@ func TestValidateDNSUpdate(t *testing.T) {
 	// 	assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'required' tag")
 	// })
 	t.Run("BadAddress", func(t *testing.T) {
-		change := models.DNSEntry{"10.0.256.1", "", "myhost", "skynet"}
+		change := models.DNSEntry{Address: "10.0.256.1", Name: "myhost", Network: "skynet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'ip' tag")
 	})
 	t.Run("EmptyName", func(t *testing.T) {
-		//this can't actually happen as change.Name is populated if is blank
-		change := models.DNSEntry{"10.0.0.2", "", "", "skynet"}
+		// this can't actually happen as change.Name is populated if is blank
+		change := models.DNSEntry{Address: "10.0.0.2", Network: "skynet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'required' tag")
@@ -350,29 +340,28 @@ func TestValidateDNSUpdate(t *testing.T) {
 		for i := 1; i < 194; i++ {
 			name = name + "a"
 		}
-		change := models.DNSEntry{"10.0.0.2", "", name, "skynet"}
+		change := models.DNSEntry{Address: "10.0.0.2", Name: name, Network: "skynet"}
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'max' tag")
 	})
 	t.Run("NameUnique", func(t *testing.T) {
-		change := models.DNSEntry{"10.0.0.2", "", "myhost", "wirecat"}
+		change := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "wirecat"}
 		_, _ = logic.CreateDNS(entry)
 		_, _ = logic.CreateDNS(change)
 		err := logic.ValidateDNSUpdate(change, entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'name_unique' tag")
-		//cleanup
+		// cleanup
 		err = logic.DeleteDNS("myhost", "wirecat")
 		assert.Nil(t, err)
 	})
 
 }
 func TestValidateDNSCreate(t *testing.T) {
-	database.InitializeDatabase()
 	_ = logic.DeleteDNS("mynode", "skynet")
 	t.Run("NoNetwork", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "myhost", "badnet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "badnet"}
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Network' failed on the 'network_exists' tag")
@@ -384,13 +373,13 @@ func TestValidateDNSCreate(t *testing.T) {
 	// 	assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'required' tag")
 	// })
 	t.Run("BadAddress", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.256.1", "", "myhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.256.1", Name: "myhost", Network: "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Address' failed on the 'ip' tag")
 	})
 	t.Run("EmptyName", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Network: "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'required' tag")
@@ -400,13 +389,13 @@ func TestValidateDNSCreate(t *testing.T) {
 		for i := 1; i < 194; i++ {
 			name = name + "a"
 		}
-		entry := models.DNSEntry{"10.0.0.2", "", name, "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: name, Network: "skynet"}
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'max' tag")
 	})
 	t.Run("NameUnique", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.2", "", "myhost", "skynet"}
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "skynet"}
 		_, _ = logic.CreateDNS(entry)
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)

+ 1 - 1
controllers/docs.go

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

+ 238 - 0
controllers/enrollmentkeys.go

@@ -0,0 +1,238 @@
+package controller
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/gorilla/mux"
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/logic/hostactions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/mq"
+	"github.com/gravitl/netmaker/servercfg"
+)
+
+func enrollmentKeyHandlers(r *mux.Router) {
+	r.HandleFunc("/api/v1/enrollment-keys", logic.SecurityCheck(true, http.HandlerFunc(createEnrollmentKey))).Methods(http.MethodPost)
+	r.HandleFunc("/api/v1/enrollment-keys", logic.SecurityCheck(true, http.HandlerFunc(getEnrollmentKeys))).Methods(http.MethodGet)
+	r.HandleFunc("/api/v1/enrollment-keys/{keyID}", logic.SecurityCheck(true, http.HandlerFunc(deleteEnrollmentKey))).Methods(http.MethodDelete)
+	r.HandleFunc("/api/v1/host/register/{token}", http.HandlerFunc(handleHostRegister)).Methods(http.MethodPost)
+}
+
+// swagger:route GET /api/v1/enrollment-keys enrollmentKeys getEnrollmentKeys
+//
+// Lists all EnrollmentKeys for admins.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: getEnrollmentKeysSlice
+func getEnrollmentKeys(w http.ResponseWriter, r *http.Request) {
+	currentKeys, err := logic.GetAllEnrollmentKeys()
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to fetch enrollment keys: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	for i := range currentKeys {
+		currentKey := currentKeys[i]
+		if err = logic.Tokenize(currentKey, servercfg.GetAPIHost()); err != nil {
+			logger.Log(0, r.Header.Get("user"), "failed to get token values for keys:", err.Error())
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+			return
+		}
+	}
+	// return JSON/API formatted keys
+	logger.Log(2, r.Header.Get("user"), "fetched enrollment keys")
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(currentKeys)
+}
+
+// swagger:route DELETE /api/v1/enrollment-keys/{keyID} enrollmentKeys deleteEnrollmentKey
+//
+// Deletes an EnrollmentKey from Netmaker server.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: deleteEnrollmentKeyResponse
+func deleteEnrollmentKey(w http.ResponseWriter, r *http.Request) {
+	var params = mux.Vars(r)
+	keyID := params["keyID"]
+	err := logic.DeleteEnrollmentKey(keyID)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to remove enrollment key: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	logger.Log(2, r.Header.Get("user"), "deleted enrollment key", keyID)
+	w.WriteHeader(http.StatusOK)
+}
+
+// swagger:route POST /api/v1/enrollment-keys enrollmentKeys createEnrollmentKey
+//
+// Creates an EnrollmentKey for hosts to use on Netmaker server.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: createEnrollmentKeyResponse
+func createEnrollmentKey(w http.ResponseWriter, r *http.Request) {
+
+	var enrollmentKeyBody models.APIEnrollmentKey
+
+	err := json.NewDecoder(r.Body).Decode(&enrollmentKeyBody)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "error decoding request body: ",
+			err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	var newTime time.Time
+	if enrollmentKeyBody.Expiration > 0 {
+		newTime = time.Unix(enrollmentKeyBody.Expiration, 0)
+	}
+
+	newEnrollmentKey, err := logic.CreateEnrollmentKey(enrollmentKeyBody.UsesRemaining, newTime, enrollmentKeyBody.Networks, enrollmentKeyBody.Tags, enrollmentKeyBody.Unlimited)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to create enrollment key:", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+
+	if err = logic.Tokenize(newEnrollmentKey, servercfg.GetAPIHost()); err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to create enrollment key:", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	logger.Log(2, r.Header.Get("user"), "created enrollment key")
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(newEnrollmentKey)
+}
+
+// swagger:route POST /api/v1/enrollment-keys/{token} enrollmentKeys handleHostRegister
+//
+// Handles a Netclient registration with server and add nodes accordingly.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: handleHostRegisterResponse
+func handleHostRegister(w http.ResponseWriter, r *http.Request) {
+	var params = mux.Vars(r)
+	token := params["token"]
+	logger.Log(0, "received registration attempt with token", token)
+	// check if token exists
+	enrollmentKey, err := logic.DeTokenize(token)
+	if err != nil {
+		logger.Log(0, "invalid enrollment key used", token, err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	// get the host
+	var newHost models.Host
+	if err = json.NewDecoder(r.Body).Decode(&newHost); err != nil {
+		logger.Log(0, r.Header.Get("user"), "error decoding request body: ",
+			err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	hostExists := false
+	// check if host already exists
+	if hostExists = logic.HostExists(&newHost); hostExists && len(enrollmentKey.Networks) == 0 {
+		logger.Log(0, "host", newHost.ID.String(), newHost.Name, "attempted to re-register with no networks")
+		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("host already exists"), "badrequest"))
+		return
+	}
+	// version check
+	if !logic.IsVersionComptatible(newHost.Version) || newHost.TrafficKeyPublic == nil {
+		err := fmt.Errorf("incompatible netclient")
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	key, keyErr := logic.RetrievePublicTrafficKey()
+	if keyErr != nil {
+		logger.Log(0, "error retrieving key:", keyErr.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	// use the token
+	if ok := logic.TryToUseEnrollmentKey(enrollmentKey); !ok {
+		logger.Log(0, "host", newHost.ID.String(), newHost.Name, "failed registration")
+		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("invalid enrollment key"), "badrequest"))
+		return
+	}
+	if !hostExists {
+		// register host
+		logic.CheckHostPorts(&newHost)
+		if err = logic.CreateHost(&newHost); err != nil {
+			logger.Log(0, "host", newHost.ID.String(), newHost.Name, "failed registration -", err.Error())
+			logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+			return
+		}
+	} else {
+		// need to revise the list of networks from key
+		// based on the ones host currently has
+		var networksToAdd = []string{}
+		currentNets := logic.GetHostNetworks(newHost.ID.String())
+		for _, newNet := range enrollmentKey.Networks {
+			if !logic.StringSliceContains(currentNets, newNet) {
+				networksToAdd = append(networksToAdd, newNet)
+			}
+		}
+		enrollmentKey.Networks = networksToAdd
+	}
+	// ready the response
+	server := servercfg.GetServerInfo()
+	server.TrafficKey = key
+	logger.Log(0, newHost.Name, newHost.ID.String(), "registered with Netmaker")
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(&server)
+	// notify host of changes, peer and node updates
+	go checkNetRegAndHostUpdate(enrollmentKey.Networks, &newHost)
+}
+
+// run through networks and send a host update
+func checkNetRegAndHostUpdate(networks []string, h *models.Host) {
+	// publish host update through MQ
+	for i := range networks {
+		network := networks[i]
+		if ok, _ := logic.NetworkExists(network); ok {
+			newNode, err := logic.UpdateHostNetwork(h, network, true)
+			if err != nil {
+				logger.Log(0, "failed to add host to network:", h.ID.String(), h.Name, network, err.Error())
+				continue
+			}
+			logger.Log(1, "added new node", newNode.ID.String(), "to host", h.Name)
+			hostactions.AddAction(models.HostUpdate{
+				Action: models.JoinHostToNetwork,
+				Host:   *h,
+				Node:   *newNode,
+			})
+		}
+	}
+	if servercfg.IsMessageQueueBackend() {
+		mq.HostUpdate(&models.HostUpdate{
+			Action: models.RequestAck,
+			Host:   *h,
+		})
+		if err := mq.PublishPeerUpdate(); err != nil {
+			logger.Log(0, "failed to publish peer update during registration -", err.Error())
+		}
+	}
+}

+ 28 - 12
controllers/ext_client.go

@@ -214,7 +214,7 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
 	if network.DefaultKeepalive != 0 {
 		keepalive = "PersistentKeepalive = " + strconv.Itoa(int(network.DefaultKeepalive))
 	}
-	gwendpoint := host.EndpointIP.String() + ":" + strconv.Itoa(host.ListenPort)
+	gwendpoint := host.EndpointIP.String() + ":" + strconv.Itoa(logic.GetPeerListenPort(host))
 	newAllowedIPs := network.AddressRange
 	if newAllowedIPs != "" && network.AddressRange6 != "" {
 		newAllowedIPs += ","
@@ -388,10 +388,15 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 
 	logger.Log(0, r.Header.Get("user"), "created new ext client on network", networkName)
 	w.WriteHeader(http.StatusOK)
-	err = mq.PublishExtPeerUpdate(&node)
-	if err != nil {
-		logger.Log(1, "error setting ext peers on "+nodeid+": "+err.Error())
-	}
+	go func() {
+		err = mq.PublishPeerUpdate()
+		if err != nil {
+			logger.Log(1, "error setting ext peers on "+nodeid+": "+err.Error())
+		}
+		if err := mq.PublishExtCLientDNS(&extclient); err != nil {
+			logger.Log(1, "error publishing extclient dns", err.Error())
+		}
+	}()
 }
 
 // swagger:route PUT /api/extclients/{network}/{clientid} ext_client updateExtClient
@@ -459,7 +464,6 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 	}
-
 	if changedID && oldExtClient.OwnerID != "" {
 		if err := pro.DissociateNetworkUserClient(oldExtClient.OwnerID, networkName, oldExtClient.ClientID); err != nil {
 			logger.Log(0, "failed to dissociate client", oldExtClient.ClientID, "from user", oldExtClient.OwnerID)
@@ -471,7 +475,8 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	// == END PRO ==
 
 	var changedEnabled = newExtClient.Enabled != oldExtClient.Enabled // indicates there was a change in enablement
-
+	// extra var need as logic.Update changes oldExtClient
+	currentClient := oldExtClient
 	newclient, err := logic.UpdateExtClient(newExtClient.ClientID, params["network"], newExtClient.Enabled, &oldExtClient)
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"),
@@ -483,13 +488,20 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	logger.Log(0, r.Header.Get("user"), "updated ext client", newExtClient.ClientID)
 	if changedEnabled { // need to send a peer update to the ingress node as enablement of one of it's clients has changed
 		if ingressNode, err := logic.GetNodeByID(newclient.IngressGatewayID); err == nil {
-			if err = mq.PublishExtPeerUpdate(&ingressNode); err != nil {
+			if err = mq.PublishPeerUpdate(); err != nil {
 				logger.Log(1, "error setting ext peers on", ingressNode.ID.String(), ":", err.Error())
 			}
 		}
 	}
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(newclient)
+	if changedID {
+		go func() {
+			if err := mq.PublishExtClientDNSUpdate(currentClient, newExtClient, networkName); err != nil {
+				logger.Log(1, "error pubishing dns update for extcient update", err.Error())
+			}
+		}()
+	}
 }
 
 // swagger:route DELETE /api/extclients/{network}/{clientid} ext_client deleteExtClient
@@ -554,10 +566,14 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	err = mq.PublishExtPeerUpdate(&ingressnode)
-	if err != nil {
-		logger.Log(1, "error setting ext peers on "+ingressnode.ID.String()+": "+err.Error())
-	}
+	go func() {
+		if err := mq.PublishPeerUpdate(); err != nil {
+			logger.Log(1, "error setting ext peers on "+ingressnode.ID.String()+": "+err.Error())
+		}
+		if err = mq.PublishDeleteExtClientDNS(&extclient); err != nil {
+			logger.Log(1, "error publishing dns update for extclient deletion", err.Error())
+		}
+	}()
 
 	logger.Log(0, r.Header.Get("user"),
 		"Deleted extclient client", params["clientid"], "from network", params["network"])

+ 28 - 8
controllers/hosts.go

@@ -10,8 +10,10 @@ import (
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/logic/hostactions"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/mq"
+	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/crypto/bcrypt"
 )
 
@@ -108,6 +110,19 @@ func updateHost(w http.ResponseWriter, r *http.Request) {
 		if err := mq.PublishPeerUpdate(); err != nil {
 			logger.Log(0, "fail to publish peer update: ", err.Error())
 		}
+		if newHost.Name != currHost.Name {
+			networks := logic.GetHostNetworks(currHost.ID.String())
+			if err := mq.PublishHostDNSUpdate(currHost, newHost, networks); err != nil {
+				var dnsError *models.DNSError
+				if errors.Is(err, dnsError) {
+					for _, message := range err.(models.DNSError).ErrorStrings {
+						logger.Log(0, message)
+					}
+				} else {
+					logger.Log(0, err.Error())
+				}
+			}
+		}
 	}()
 
 	apiHostData := newHost.ConvertNMHostToAPI()
@@ -217,18 +232,17 @@ func addHostToNetwork(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	logger.Log(1, "added new node", newNode.ID.String(), "to host", currHost.Name)
-	if err = mq.HostUpdate(&models.HostUpdate{
+	hostactions.AddAction(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())
+	})
+	if servercfg.IsMessageQueueBackend() {
+		mq.HostUpdate(&models.HostUpdate{
+			Action: models.RequestAck,
+			Host:   *currHost,
+		})
 	}
-	go func() { // notify of peer change
-		if err := mq.PublishPeerUpdate(); err != nil {
-			logger.Log(1, "error publishing peer update ", err.Error())
-		}
-	}()
 
 	logger.Log(2, r.Header.Get("user"), fmt.Sprintf("added host %s to network %s", currHost.Name, network))
 	w.WriteHeader(http.StatusOK)
@@ -268,17 +282,23 @@ func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
+	node.Action = models.NODE_DELETE
+	node.PendingDelete = true
 	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
 	}
 	// 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())
 		}
+		if err := mq.PublishDNSDelete(node, currHost); err != nil {
+			logger.Log(1, "error publishing dns 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)

+ 17 - 5
controllers/network.go

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

+ 22 - 24
controllers/network_test.go

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

+ 58 - 25
controllers/node.go

@@ -415,7 +415,6 @@ func getUsersNodes(user models.User) ([]models.Node, error) {
 func getNode(w http.ResponseWriter, r *http.Request) {
 	// set header.
 	w.Header().Set("Content-Type", "application/json")
-
 	nodeRequest := r.Header.Get("requestfrom") == "node"
 
 	var params = mux.Vars(r)
@@ -434,14 +433,7 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
-	peerUpdate, err := logic.GetPeerUpdate(&node, 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", nodeid, err))
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-		return
-	}
-	hostPeerUpdate, err := logic.GetPeerUpdateForHost(host)
+	hostPeerUpdate, err := logic.GetPeerUpdateForHost(node.Network, host, nil)
 	if err != nil && !database.IsEmptyRecord(err) {
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", host.ID.String(), err))
@@ -449,21 +441,13 @@ func getNode(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	server := servercfg.GetServerInfo()
-	network, err := logic.GetNetwork(node.Network)
-	if err != nil {
-		logger.Log(0, r.Header.Get("user"),
-			fmt.Sprintf("error fetching network for node [ %s ] info: %v", nodeid, err))
-		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
-		return
-	}
-	legacy := node.Legacy(host, &server, &network)
 	response := models.NodeGet{
-		Node:         *legacy,
+		Node:         node,
 		Host:         *host,
-		Peers:        peerUpdate.Peers,
 		HostPeers:    hostPeerUpdate.Peers,
+		Peers:        hostPeerUpdate.NodePeers,
 		ServerConfig: server,
-		PeerIDs:      peerUpdate.PeerIDs,
+		PeerIDs:      hostPeerUpdate.PeerIDs,
 	}
 
 	if servercfg.Is_EE && nodeRequest {
@@ -580,6 +564,13 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 	data.Node.Server = servercfg.GetServer()
 	if !logic.HostExists(&data.Host) {
 		logic.CheckHostPorts(&data.Host)
+		if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
+			// create EMQX credentials for host if it doesn't exists
+			if err := mq.CreateEmqxUser(data.Host.ID.String(), data.Host.HostPass, false); err != nil {
+				logger.Log(0, "failed to add host credentials to EMQX: ", data.Host.ID.String(), err.Error())
+				return
+			}
+		}
 	}
 	if err := logic.CreateHost(&data.Host); err != nil {
 		if errors.Is(err, logic.ErrHostExists) {
@@ -605,7 +596,6 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 	}
-
 	err = logic.AssociateNodeToHost(&data.Node, &data.Host)
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"),
@@ -632,7 +622,7 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 	}
-	hostPeerUpdate, err := logic.GetPeerUpdateForHost(&data.Host)
+	hostPeerUpdate, err := logic.GetPeerUpdateForHost(networkName, &data.Host, nil)
 	if err != nil && !database.IsEmptyRecord(err) {
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("error fetching wg peers config for host [ %s ]: %v", data.Host.ID.String(), err))
@@ -657,6 +647,30 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 		}
 	}()
 	//runForceServerUpdate(&data.Node, true)
+	go func() {
+		dns := models.DNSUpdate{
+			Action: models.DNSInsert,
+			Name:   data.Host.Name + "." + data.Node.Network,
+		}
+		if data.Node.Address.IP != nil {
+			dns.Address = data.Node.Address.IP.String()
+			//publish new node dns entry to all nodes on network
+			if err := mq.PublishDNSUpdate(data.Node.Network, dns); err != nil {
+				logger.Log(1, "failed to publish dns update on node creation", err.Error())
+			}
+		}
+		if data.Node.Address6.IP != nil {
+			dns.Address = data.Node.Address6.IP.String()
+			//publish new node dns entry to all nodes on network
+			if err := mq.PublishDNSUpdate(data.Node.Network, dns); err != nil {
+				logger.Log(1, "failed to publish dns update on node creation", err.Error())
+			}
+		}
+		//publish add dns records for network to new node
+		if err := mq.PublishAllDNS(&data.Node); err != nil {
+			logger.Log(1, "failed to publish dns update on node creation", err.Error())
+		}
+	}()
 }
 
 // == EGRESS ==
@@ -929,6 +943,11 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(apiNode)
 
 	runUpdates(newNode, ifaceDelta)
+	go func() {
+		if err := mq.PublishReplaceDNS(&currentNode, newNode, host); err != nil {
+			logger.Log(1, "failed to publish dns update", err.Error())
+		}
+	}()
 }
 
 // swagger:route DELETE /api/nodes/{network}/{nodeid} nodes deleteNode
@@ -972,11 +991,25 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 	if !fromNode { // notify node change
 		runUpdates(&node, false)
 	}
-	go func() { // notify of peer change
-		if err := mq.PublishPeerUpdate(); err != nil {
+	go func(deletedNode *models.Node, fromNode bool) { // notify of peer change
+		var err error
+		if fromNode {
+			err = mq.PublishDeletedNodePeerUpdate(deletedNode)
+		} else {
+			err = mq.PublishPeerUpdate()
+		}
+		if err != nil {
 			logger.Log(1, "error publishing peer update ", err.Error())
 		}
-	}()
+
+		host, err := logic.GetHost(node.HostID.String())
+		if err != nil {
+			logger.Log(1, "failed to retrieve host for node", node.ID.String(), err.Error())
+		}
+		if err := mq.PublishDNSDelete(&node, host); err != nil {
+			logger.Log(1, "error publishing dns update", err.Error())
+		}
+	}(&node, fromNode)
 }
 
 func runUpdates(node *models.Node, ifaceDelta bool) {

+ 0 - 3
controllers/node_test.go

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

+ 62 - 49
controllers/user_test.go

@@ -3,22 +3,24 @@ package controller
 import (
 	"testing"
 
-	"github.com/gravitl/netmaker/database"
+	"github.com/stretchr/testify/assert"
+
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
-	"github.com/stretchr/testify/assert"
 )
 
-func deleteAllUsers() {
+func deleteAllUsers(t *testing.T) {
+	t.Helper()
 	users, _ := logic.GetUsers()
 	for _, user := range users {
-		logic.DeleteUser(user.UserName)
+		if _, err := logic.DeleteUser(user.UserName); err != nil {
+			t.Fatal(err)
+		}
 	}
 }
 
 func TestHasAdmin(t *testing.T) {
-	//delete all current users
-	database.InitializeDatabase()
+	// delete all current users
 	users, _ := logic.GetUsers()
 	for _, user := range users {
 		success, err := logic.DeleteUser(user.UserName)
@@ -31,7 +33,7 @@ func TestHasAdmin(t *testing.T) {
 		assert.False(t, found)
 	})
 	t.Run("No admin user", func(t *testing.T) {
-		var user = models.User{"noadmin", "password", nil, false, nil}
+		var user = models.User{UserName: "noadmin", Password: "password"}
 		err := logic.CreateUser(&user)
 		assert.Nil(t, err)
 		found, err := logic.HasAdmin()
@@ -39,7 +41,7 @@ func TestHasAdmin(t *testing.T) {
 		assert.False(t, found)
 	})
 	t.Run("admin user", func(t *testing.T) {
-		var user = models.User{"admin", "password", nil, true, nil}
+		var user = models.User{UserName: "admin", Password: "password", IsAdmin: true}
 		err := logic.CreateUser(&user)
 		assert.Nil(t, err)
 		found, err := logic.HasAdmin()
@@ -47,8 +49,8 @@ func TestHasAdmin(t *testing.T) {
 		assert.True(t, found)
 	})
 	t.Run("multiple admins", func(t *testing.T) {
-		var user = models.User{"admin1", "password", nil, true, nil}
-		 err := logic.CreateUser(&user)
+		var user = models.User{UserName: "admin1", Password: "password", IsAdmin: true}
+		err := logic.CreateUser(&user)
 		assert.Nil(t, err)
 		found, err := logic.HasAdmin()
 		assert.Nil(t, err)
@@ -57,9 +59,8 @@ func TestHasAdmin(t *testing.T) {
 }
 
 func TestCreateUser(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
-	user := models.User{"admin", "password", nil, true, nil}
+	deleteAllUsers(t)
+	user := models.User{UserName: "admin", Password: "password", IsAdmin: true}
 	t.Run("NoUser", func(t *testing.T) {
 		err := logic.CreateUser(&user)
 		assert.Nil(t, err)
@@ -72,8 +73,7 @@ func TestCreateUser(t *testing.T) {
 }
 
 func TestCreateAdmin(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
+	deleteAllUsers(t)
 	var user models.User
 	t.Run("NoAdmin", func(t *testing.T) {
 		user.UserName = "admin"
@@ -90,16 +90,17 @@ func TestCreateAdmin(t *testing.T) {
 }
 
 func TestDeleteUser(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
+	deleteAllUsers(t)
 	t.Run("NonExistent User", func(t *testing.T) {
 		deleted, err := logic.DeleteUser("admin")
 		assert.EqualError(t, err, "user does not exist")
 		assert.False(t, deleted)
 	})
 	t.Run("Existing User", func(t *testing.T) {
-		user := models.User{"admin", "password", nil, true, nil}
-		logic.CreateUser(&user)
+		user := models.User{UserName: "admin", Password: "password", IsAdmin: true}
+		if err := logic.CreateUser(&user); err != nil {
+			t.Fatal(err)
+		}
 		deleted, err := logic.DeleteUser("admin")
 		assert.Nil(t, err)
 		assert.True(t, deleted)
@@ -107,7 +108,6 @@ func TestDeleteUser(t *testing.T) {
 }
 
 func TestValidateUser(t *testing.T) {
-	database.InitializeDatabase()
 	var user models.User
 	t.Run("Valid Create", func(t *testing.T) {
 		user.UserName = "admin"
@@ -126,21 +126,21 @@ func TestValidateUser(t *testing.T) {
 		user.UserName = "*invalid"
 		err := logic.ValidateUser(&user)
 		assert.Error(t, err)
-		//assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
+		// assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
 	})
 	t.Run("Short UserName", func(t *testing.T) {
 		t.Skip()
 		user.UserName = "1"
 		err := logic.ValidateUser(&user)
 		assert.NotNil(t, err)
-		//assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
+		// assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
 	})
 	t.Run("Empty UserName", func(t *testing.T) {
 		t.Skip()
 		user.UserName = ""
 		err := logic.ValidateUser(&user)
 		assert.EqualError(t, err, "some string")
-		//assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
+		// assert.Contains(t, err.Error(), "Field validation for 'UserName' failed")
 	})
 	t.Run("EmptyPassword", func(t *testing.T) {
 		user.Password = ""
@@ -155,16 +155,19 @@ func TestValidateUser(t *testing.T) {
 }
 
 func TestGetUser(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
+	deleteAllUsers(t)
+
+	user := models.User{UserName: "admin", Password: "password", Networks: nil, IsAdmin: true, Groups: nil}
+
 	t.Run("NonExistantUser", func(t *testing.T) {
 		admin, err := logic.GetUser("admin")
 		assert.EqualError(t, err, "could not find any records")
 		assert.Equal(t, "", admin.UserName)
 	})
 	t.Run("UserExisits", func(t *testing.T) {
-		user := models.User{"admin", "password", nil, true, nil}
-		logic.CreateUser(&user)
+		if err := logic.CreateUser(&user); err != nil {
+			t.Error(err)
+		}
 		admin, err := logic.GetUser("admin")
 		assert.Nil(t, err)
 		assert.Equal(t, user.UserName, admin.UserName)
@@ -172,30 +175,36 @@ func TestGetUser(t *testing.T) {
 }
 
 func TestGetUsers(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
+	deleteAllUsers(t)
+
+	adminUser := models.User{UserName: "admin", Password: "password", IsAdmin: true}
+	user := models.User{UserName: "admin", Password: "password", IsAdmin: false}
+
 	t.Run("NonExistantUser", func(t *testing.T) {
 		admin, err := logic.GetUsers()
 		assert.EqualError(t, err, "could not find any records")
 		assert.Equal(t, []models.ReturnUser(nil), admin)
 	})
 	t.Run("UserExisits", func(t *testing.T) {
-		user := models.User{"admin", "password", nil, true, nil}
-		logic.CreateUser(&user)
+		if err := logic.CreateUser(&adminUser); err != nil {
+			t.Error(err)
+		}
 		admins, err := logic.GetUsers()
 		assert.Nil(t, err)
-		assert.Equal(t, user.UserName, admins[0].UserName)
+		assert.Equal(t, adminUser.UserName, admins[0].UserName)
 	})
 	t.Run("MulipleUsers", func(t *testing.T) {
-		user := models.User{"user", "password", nil, true, nil}
-		logic.CreateUser(&user)
+		if err := logic.CreateUser(&user); err != nil {
+			t.Error(err)
+		}
 		admins, err := logic.GetUsers()
 		assert.Nil(t, err)
 		for _, u := range admins {
 			if u.UserName == "admin" {
-				assert.Equal(t, "admin", u.UserName)
+				assert.Equal(t, true, u.IsAdmin)
 			} else {
 				assert.Equal(t, user.UserName, u.UserName)
+				assert.Equal(t, user.IsAdmin, u.IsAdmin)
 			}
 		}
 	})
@@ -203,10 +212,9 @@ func TestGetUsers(t *testing.T) {
 }
 
 func TestUpdateUser(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
-	user := models.User{"admin", "password", nil, true, nil}
-	newuser := models.User{"hello", "world", []string{"wirecat, netmaker"}, true, []string{}}
+	deleteAllUsers(t)
+	user := models.User{UserName: "admin", Password: "password", IsAdmin: true}
+	newuser := models.User{UserName: "hello", Password: "world", Networks: []string{"wirecat, netmaker"}, IsAdmin: true, Groups: []string{}}
 	t.Run("NonExistantUser", func(t *testing.T) {
 		admin, err := logic.UpdateUser(&newuser, &user)
 		assert.EqualError(t, err, "could not find any records")
@@ -214,7 +222,9 @@ func TestUpdateUser(t *testing.T) {
 	})
 
 	t.Run("UserExists", func(t *testing.T) {
-		logic.CreateUser(&user)
+		if err := logic.CreateUser(&user); err != nil {
+			t.Error(err)
+		}
 		admin, err := logic.UpdateUser(&newuser, &user)
 		assert.Nil(t, err)
 		assert.Equal(t, newuser.UserName, admin.UserName)
@@ -246,8 +256,8 @@ func TestUpdateUser(t *testing.T) {
 // }
 
 func TestVerifyAuthRequest(t *testing.T) {
-	database.InitializeDatabase()
-	deleteAllUsers()
+	deleteAllUsers(t)
+	user := models.User{UserName: "admin", Password: "password", Networks: nil, IsAdmin: true, Groups: nil}
 	var authRequest models.UserAuthParams
 	t.Run("EmptyUserName", func(t *testing.T) {
 		authRequest.UserName = ""
@@ -271,23 +281,26 @@ func TestVerifyAuthRequest(t *testing.T) {
 		assert.EqualError(t, err, "error retrieving user from db: could not find any records")
 	})
 	t.Run("Non-Admin", func(t *testing.T) {
-		user := models.User{"nonadmin", "somepass", nil, false, []string{}}
-		logic.CreateUser(&user)
-		authRequest := models.UserAuthParams{"nonadmin", "somepass"}
+		if err := logic.CreateUser(&user); err != nil {
+			t.Error(err)
+		}
+		authRequest := models.UserAuthParams{UserName: "nonadmin", Password: "somepass"}
 		jwt, err := logic.VerifyAuthRequest(authRequest)
 		assert.NotNil(t, jwt)
 		assert.Nil(t, err)
 	})
 	t.Run("WrongPassword", func(t *testing.T) {
-		user := models.User{"admin", "password", nil, false, []string{}}
-		logic.CreateUser(&user)
-		authRequest := models.UserAuthParams{"admin", "badpass"}
+		user := models.User{UserName: "admin", Password: "password", Groups: []string{}}
+		if err := logic.CreateUser(&user); err != nil {
+			t.Error(err)
+		}
+		authRequest := models.UserAuthParams{UserName: "admin", Password: "badpass"}
 		jwt, err := logic.VerifyAuthRequest(authRequest)
 		assert.Equal(t, "", jwt)
 		assert.EqualError(t, err, "incorrect credentials")
 	})
 	t.Run("Success", func(t *testing.T) {
-		authRequest := models.UserAuthParams{"admin", "password"}
+		authRequest := models.UserAuthParams{UserName: "admin", Password: "password"}
 		jwt, err := logic.VerifyAuthRequest(authRequest)
 		assert.Nil(t, err)
 		assert.NotNil(t, jwt)

+ 3 - 0
database/database.go

@@ -57,6 +57,8 @@ const (
 	CACHE_TABLE_NAME = "cache"
 	// HOSTS_TABLE_NAME - the table name for hosts
 	HOSTS_TABLE_NAME = "hosts"
+	// ENROLLMENT_KEYS_TABLE_NAME - table name for enrollmentkeys
+	ENROLLMENT_KEYS_TABLE_NAME = "enrollmentkeys"
 
 	// == ERROR CONSTS ==
 	// NO_RECORD - no singular result found
@@ -138,6 +140,7 @@ func createTables() {
 	createTable(USER_GROUPS_TABLE_NAME)
 	createTable(CACHE_TABLE_NAME)
 	createTable(HOSTS_TABLE_NAME)
+	createTable(ENROLLMENT_KEYS_TABLE_NAME)
 }
 
 func createTable(tableName string) error {

+ 0 - 7
dev.yaml

@@ -3,17 +3,12 @@ server:
   apiconn: "api.ping.clustercat.com:443"
   apihost: ""
   apiport: "8081"
-  grpcconn: "grpc.ping.clustercat.com:443"
-  grpchost: ""
-  grpcport: "50051"
-  grpcsecure: "on"
   mqhost: "localhost"
   masterkey: "secretkey"
   dnskey: ""
   allowedorigin: "*"
   nodeid: "netmaker"
   restbackend: "on"
-  agentbackend: "on"
   messagequeuebackend: "on"
   dnsmode: "on"
   disableremoteipcheck: ""
@@ -22,9 +17,7 @@ server:
   sqlconn: ""
   platform: ""
   database: "sqlite"
-  defaultnodelimit: ""
   verbosity: 3
-  servercheckininterval: ""
   authprovider: ""
   clientid: ""
   clientsecret: ""

+ 1 - 1
docker/Caddyfile

@@ -38,5 +38,5 @@ https://stun.NETMAKER_BASE_DOMAIN {
 
 # MQ
 wss://broker.NETMAKER_BASE_DOMAIN {
-        reverse_proxy ws://mq:8883
+        reverse_proxy ws://mq:8883 # For EMQX websockets use `reverse_proxy ws://mq:8083`
 }

+ 2 - 1
docker/Caddyfile-EE

@@ -1,5 +1,6 @@
 {
-        # LetsEncrypt account
+        # ZeroSSL account
+        acme_ca https://acme.zerossl.com/v2/DV90
         email YOUR_EMAIL
 }
 

+ 22 - 8
functions/helpers_test.go

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

+ 9 - 10
go.mod

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

+ 20 - 34
go.sum

@@ -17,7 +17,6 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -29,18 +28,15 @@ github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
 github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
-github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
-github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
-github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
-github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
-github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
-github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
-github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
-github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
-github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
-github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
-github.com/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/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
+github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
+github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
+github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
@@ -52,7 +48,6 @@ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8
 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
@@ -73,13 +68,10 @@ github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
 github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
 github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
-github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
 github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
 github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
@@ -106,7 +98,6 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -116,9 +107,7 @@ github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sA
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
-github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f h1:BSnJgAfHzEp7o8PYJ7YfwAVHhqu7BYUTggcn/LGlUWY=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f/go.mod h1:UW/gxgQwSePTvL1KA8QEHsXeYHP4xkoXgbDdN781p34=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -160,10 +149,9 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
-golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
+golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
+golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -172,17 +160,18 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
 golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
+golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
+golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
+golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -193,10 +182,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -205,8 +192,9 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
@@ -215,8 +203,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
 golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -240,11 +229,8 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
-gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gortc.io/stun v1.23.0 h1:CpRQFjakCZMwVKTwInKbcCzlBklj62LGzD3NPdFyGrE=

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

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

+ 1 - 1
k8s/client/netclient.yaml

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

+ 5 - 7
k8s/server/netmaker-server.yaml

@@ -71,17 +71,15 @@ spec:
           value: REPLACE_MASTER_KEY
         - name: CORS_ALLOWED_ORIGIN
           value: '*'
-        - name: MQ_HOST
-          value: "mq"
-        - name: MQ_PORT
-          value: "443"
-        - name: MQ_SERVER_PORT
-          value: "1883"
+        - name: SERVER_BROKER_ENDPOINT
+          value: "ws://mq:1883"
+        - name: BROKER_ENDPOINT
+          value: "wss://broker.NETMAKER_BASE_DOMAIN"
         - name: PLATFORM
           value: "Kubernetes"
         - name: VERBOSITY
           value: "3"
-        image: gravitl/netmaker:v0.18.0
+        image: gravitl/netmaker:v0.18.2
         imagePullPolicy: Always
         name: netmaker
         ports:

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

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

+ 0 - 2
logic/accesskeys_test.go

@@ -5,7 +5,6 @@ import "testing"
 func Test_genKeyName(t *testing.T) {
 	for i := 0; i < 100; i++ {
 		kname := genKeyName()
-		t.Log(kname)
 		if len(kname) != 20 {
 			t.Fatalf("improper length of key name, expected 20 got :%d", len(kname))
 		}
@@ -15,7 +14,6 @@ func Test_genKeyName(t *testing.T) {
 func Test_genKey(t *testing.T) {
 	for i := 0; i < 100; i++ {
 		kname := GenKey()
-		t.Log(kname)
 		if len(kname) != 16 {
 			t.Fatalf("improper length of key name, expected 16 got :%d", len(kname))
 		}

+ 221 - 0
logic/enrollmentkey.go

@@ -0,0 +1,221 @@
+package logic
+
+import (
+	b64 "encoding/base64"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"time"
+
+	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/netclient/ncutils"
+)
+
+// EnrollmentErrors - struct for holding EnrollmentKey error messages
+var EnrollmentErrors = struct {
+	InvalidCreate      error
+	NoKeyFound         error
+	InvalidKey         error
+	NoUsesRemaining    error
+	FailedToTokenize   error
+	FailedToDeTokenize error
+}{
+	InvalidCreate:      fmt.Errorf("invalid enrollment key created"),
+	NoKeyFound:         fmt.Errorf("no enrollmentkey found"),
+	InvalidKey:         fmt.Errorf("invalid key provided"),
+	NoUsesRemaining:    fmt.Errorf("no uses remaining"),
+	FailedToTokenize:   fmt.Errorf("failed to tokenize"),
+	FailedToDeTokenize: fmt.Errorf("failed to detokenize"),
+}
+
+// CreateEnrollmentKey - creates a new enrollment key in db
+func CreateEnrollmentKey(uses int, expiration time.Time, networks, tags []string, unlimited bool) (k *models.EnrollmentKey, err error) {
+	newKeyID, err := getUniqueEnrollmentID()
+	if err != nil {
+		return nil, err
+	}
+	k = &models.EnrollmentKey{
+		Value:         newKeyID,
+		Expiration:    time.Time{},
+		UsesRemaining: 0,
+		Unlimited:     unlimited,
+		Networks:      []string{},
+		Tags:          []string{},
+	}
+	if uses > 0 {
+		k.UsesRemaining = uses
+	}
+	if !expiration.IsZero() {
+		k.Expiration = expiration
+	}
+	if len(networks) > 0 {
+		k.Networks = networks
+	}
+	if len(tags) > 0 {
+		k.Tags = tags
+	}
+	if ok := k.Validate(); !ok {
+		return nil, EnrollmentErrors.InvalidCreate
+	}
+	if err = upsertEnrollmentKey(k); err != nil {
+		return nil, err
+	}
+	return
+}
+
+// GetAllEnrollmentKeys - fetches all enrollment keys from DB
+func GetAllEnrollmentKeys() ([]*models.EnrollmentKey, error) {
+	currentKeys, err := getEnrollmentKeysMap()
+	if err != nil {
+		return nil, err
+	}
+	var currentKeysList = []*models.EnrollmentKey{}
+	for k := range currentKeys {
+		currentKeysList = append(currentKeysList, currentKeys[k])
+	}
+	return currentKeysList, nil
+}
+
+// GetEnrollmentKey - fetches a single enrollment key
+// returns nil and error if not found
+func GetEnrollmentKey(value string) (*models.EnrollmentKey, error) {
+	currentKeys, err := getEnrollmentKeysMap()
+	if err != nil {
+		return nil, err
+	}
+	if key, ok := currentKeys[value]; ok {
+		return key, nil
+	}
+	return nil, EnrollmentErrors.NoKeyFound
+}
+
+// DeleteEnrollmentKey - delete's a given enrollment key by value
+func DeleteEnrollmentKey(value string) error {
+	_, err := GetEnrollmentKey(value)
+	if err != nil {
+		return err
+	}
+	return database.DeleteRecord(database.ENROLLMENT_KEYS_TABLE_NAME, value)
+}
+
+// TryToUseEnrollmentKey - checks first if key can be decremented
+// returns true if it is decremented or isvalid
+func TryToUseEnrollmentKey(k *models.EnrollmentKey) bool {
+	key, err := decrementEnrollmentKey(k.Value)
+	if err != nil {
+		if errors.Is(err, EnrollmentErrors.NoUsesRemaining) {
+			return k.IsValid()
+		}
+	} else {
+		k.UsesRemaining = key.UsesRemaining
+		return true
+	}
+	return false
+}
+
+// Tokenize - tokenizes an enrollment key to be used via registration
+// and attaches it to the Token field on the struct
+func Tokenize(k *models.EnrollmentKey, serverAddr string) error {
+	if len(serverAddr) == 0 || k == nil {
+		return EnrollmentErrors.FailedToTokenize
+	}
+	newToken := models.EnrollmentToken{
+		Server: serverAddr,
+		Value:  k.Value,
+	}
+	data, err := json.Marshal(&newToken)
+	if err != nil {
+		return err
+	}
+	k.Token = b64.StdEncoding.EncodeToString(data)
+	return nil
+}
+
+// DeTokenize - detokenizes a base64 encoded string
+// and finds the associated enrollment key
+func DeTokenize(b64Token string) (*models.EnrollmentKey, error) {
+	if len(b64Token) == 0 {
+		return nil, EnrollmentErrors.FailedToDeTokenize
+	}
+	tokenData, err := b64.StdEncoding.DecodeString(b64Token)
+	if err != nil {
+		return nil, err
+	}
+
+	var newToken models.EnrollmentToken
+	err = json.Unmarshal(tokenData, &newToken)
+	if err != nil {
+		return nil, err
+	}
+	k, err := GetEnrollmentKey(newToken.Value)
+	if err != nil {
+		return nil, err
+	}
+	return k, nil
+}
+
+// == private ==
+
+// decrementEnrollmentKey - decrements the uses on a key if above 0 remaining
+func decrementEnrollmentKey(value string) (*models.EnrollmentKey, error) {
+	k, err := GetEnrollmentKey(value)
+	if err != nil {
+		return nil, err
+	}
+	if k.UsesRemaining == 0 {
+		return nil, EnrollmentErrors.NoUsesRemaining
+	}
+	k.UsesRemaining = k.UsesRemaining - 1
+	if err = upsertEnrollmentKey(k); err != nil {
+		return nil, err
+	}
+
+	return k, nil
+}
+
+func upsertEnrollmentKey(k *models.EnrollmentKey) error {
+	if k == nil {
+		return EnrollmentErrors.InvalidKey
+	}
+	data, err := json.Marshal(k)
+	if err != nil {
+		return err
+	}
+	return database.Insert(k.Value, string(data), database.ENROLLMENT_KEYS_TABLE_NAME)
+}
+
+func getUniqueEnrollmentID() (string, error) {
+	currentKeys, err := getEnrollmentKeysMap()
+	if err != nil {
+		return "", err
+	}
+	newID := ncutils.MakeRandomString(models.EnrollmentKeyLength)
+	for _, ok := currentKeys[newID]; ok; {
+		newID = ncutils.MakeRandomString(models.EnrollmentKeyLength)
+	}
+	return newID, nil
+}
+
+func getEnrollmentKeysMap() (map[string]*models.EnrollmentKey, error) {
+	records, err := database.FetchRecords(database.ENROLLMENT_KEYS_TABLE_NAME)
+	if err != nil {
+		if !database.IsEmptyRecord(err) {
+			return nil, err
+		}
+	}
+	if records == nil {
+		records = make(map[string]string)
+	}
+	currentKeys := make(map[string]*models.EnrollmentKey, 0)
+	if len(records) > 0 {
+		for k := range records {
+			var currentKey models.EnrollmentKey
+			if err = json.Unmarshal([]byte(records[k]), &currentKey); err != nil {
+				continue
+			}
+			currentKeys[k] = &currentKey
+		}
+	}
+	return currentKeys, nil
+}

+ 206 - 0
logic/enrollmentkey_test.go

@@ -0,0 +1,206 @@
+package logic
+
+import (
+	"testing"
+	"time"
+
+	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/models"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCreateEnrollmentKey(t *testing.T) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	t.Run("Can_Not_Create_Key", func(t *testing.T) {
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, false)
+		assert.Nil(t, newKey)
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.InvalidCreate)
+	})
+	t.Run("Can_Create_Key_Uses", func(t *testing.T) {
+		newKey, err := CreateEnrollmentKey(1, time.Time{}, nil, nil, false)
+		assert.Nil(t, err)
+		assert.Equal(t, 1, newKey.UsesRemaining)
+		assert.True(t, newKey.IsValid())
+	})
+	t.Run("Can_Create_Key_Time", func(t *testing.T) {
+		newKey, err := CreateEnrollmentKey(0, time.Now().Add(time.Minute), nil, nil, false)
+		assert.Nil(t, err)
+		assert.True(t, newKey.IsValid())
+	})
+	t.Run("Can_Create_Key_Unlimited", func(t *testing.T) {
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, true)
+		assert.Nil(t, err)
+		assert.True(t, newKey.IsValid())
+	})
+	t.Run("Can_Create_Key_WithNetworks", func(t *testing.T) {
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, true)
+		assert.Nil(t, err)
+		assert.True(t, newKey.IsValid())
+		assert.True(t, len(newKey.Networks) == 2)
+	})
+	t.Run("Can_Create_Key_WithTags", func(t *testing.T) {
+		newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, []string{"tag1", "tag2"}, true)
+		assert.Nil(t, err)
+		assert.True(t, newKey.IsValid())
+		assert.True(t, len(newKey.Tags) == 2)
+	})
+
+	t.Run("Can_Get_List_of_Keys", func(t *testing.T) {
+		keys, err := GetAllEnrollmentKeys()
+		assert.Nil(t, err)
+		assert.True(t, len(keys) > 0)
+		for i := range keys {
+			assert.Equal(t, len(keys[i].Value), models.EnrollmentKeyLength)
+		}
+	})
+	removeAllEnrollments()
+}
+
+func TestDelete_EnrollmentKey(t *testing.T) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, true)
+	t.Run("Can_Delete_Key", func(t *testing.T) {
+		assert.True(t, newKey.IsValid())
+		err := DeleteEnrollmentKey(newKey.Value)
+		assert.Nil(t, err)
+		oldKey, err := GetEnrollmentKey(newKey.Value)
+		assert.Nil(t, oldKey)
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.NoKeyFound)
+	})
+	t.Run("Can_Not_Delete_Invalid_Key", func(t *testing.T) {
+		err := DeleteEnrollmentKey("notakey")
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.NoKeyFound)
+	})
+	removeAllEnrollments()
+}
+
+func TestDecrement_EnrollmentKey(t *testing.T) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	newKey, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, false)
+	t.Run("Check_initial_uses", func(t *testing.T) {
+		assert.True(t, newKey.IsValid())
+		assert.Equal(t, newKey.UsesRemaining, 1)
+	})
+	t.Run("Check can decrement", func(t *testing.T) {
+		assert.Equal(t, newKey.UsesRemaining, 1)
+		k, err := decrementEnrollmentKey(newKey.Value)
+		assert.Nil(t, err)
+		newKey = k
+	})
+	t.Run("Check can not decrement", func(t *testing.T) {
+		assert.Equal(t, newKey.UsesRemaining, 0)
+		_, err := decrementEnrollmentKey(newKey.Value)
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.NoUsesRemaining)
+	})
+
+	removeAllEnrollments()
+}
+
+func TestUsability_EnrollmentKey(t *testing.T) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	key1, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, false)
+	key2, _ := CreateEnrollmentKey(0, time.Now().Add(time.Minute<<4), nil, nil, false)
+	key3, _ := CreateEnrollmentKey(0, time.Time{}, nil, nil, true)
+	t.Run("Check if valid use key can be used", func(t *testing.T) {
+		assert.Equal(t, key1.UsesRemaining, 1)
+		ok := TryToUseEnrollmentKey(key1)
+		assert.True(t, ok)
+		assert.Equal(t, 0, key1.UsesRemaining)
+	})
+
+	t.Run("Check if valid time key can be used", func(t *testing.T) {
+		assert.True(t, !key2.Expiration.IsZero())
+		ok := TryToUseEnrollmentKey(key2)
+		assert.True(t, ok)
+	})
+
+	t.Run("Check if valid unlimited key can be used", func(t *testing.T) {
+		assert.True(t, key3.Unlimited)
+		ok := TryToUseEnrollmentKey(key3)
+		assert.True(t, ok)
+	})
+
+	t.Run("check invalid key can not be used", func(t *testing.T) {
+		ok := TryToUseEnrollmentKey(key1)
+		assert.False(t, ok)
+	})
+}
+
+func removeAllEnrollments() {
+	database.DeleteAllRecords(database.ENROLLMENT_KEYS_TABLE_NAME)
+}
+
+//Test that cheks if it can tokenize
+//Test that cheks if it can't tokenize
+
+func TestTokenize_EnrollmentKeys(t *testing.T) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, true)
+	const defaultValue = "MwE5MwE5MwE5MwE5MwE5MwE5MwE5MwE5"
+	const b64value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0U1TXdFNU13RTVNd0U1TXdFNU13RTVNd0U1TXdFNSJ9"
+	const serverAddr = "api.myserver.com"
+	t.Run("Can_Not_Tokenize_Nil_Key", func(t *testing.T) {
+		err := Tokenize(nil, "ServerAddress")
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.FailedToTokenize)
+	})
+	t.Run("Can_Not_Tokenize_Empty_Server_Address", func(t *testing.T) {
+		err := Tokenize(newKey, "")
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.FailedToTokenize)
+	})
+
+	t.Run("Can_Tokenize", func(t *testing.T) {
+		err := Tokenize(newKey, serverAddr)
+		assert.Nil(t, err)
+		assert.True(t, len(newKey.Token) > 0)
+	})
+
+	t.Run("Is_Correct_B64_Token", func(t *testing.T) {
+		newKey.Value = defaultValue
+		err := Tokenize(newKey, serverAddr)
+		assert.Nil(t, err)
+		assert.Equal(t, newKey.Token, b64value)
+	})
+	removeAllEnrollments()
+}
+
+func TestDeTokenize_EnrollmentKeys(t *testing.T) {
+	database.InitializeDatabase()
+	defer database.CloseDB()
+	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, true)
+	const b64Value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0U1TXdFNU13RTVNd0U1TXdFNU13RTVNd0U1TXdFNSJ9"
+	const serverAddr = "api.myserver.com"
+
+	t.Run("Can_Not_DeTokenize", func(t *testing.T) {
+		value, err := DeTokenize("")
+		assert.Nil(t, value)
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.FailedToDeTokenize)
+	})
+	t.Run("Can_Not_Find_Key", func(t *testing.T) {
+		value, err := DeTokenize(b64Value)
+		assert.Nil(t, value)
+		assert.NotNil(t, err)
+		assert.Equal(t, err, EnrollmentErrors.NoKeyFound)
+	})
+	t.Run("Can_DeTokenize", func(t *testing.T) {
+		err := Tokenize(newKey, serverAddr)
+		assert.Nil(t, err)
+		output, err := DeTokenize(newKey.Token)
+		assert.Nil(t, err)
+		assert.NotNil(t, output)
+		assert.Equal(t, newKey.Value, output.Value)
+	})
+
+	removeAllEnrollments()
+}

+ 12 - 0
logic/host_test.go

@@ -1,6 +1,8 @@
 package logic
 
 import (
+	"context"
+	"fmt"
 	"net"
 	"testing"
 
@@ -12,6 +14,16 @@ import (
 
 func TestCheckPorts(t *testing.T) {
 	database.InitializeDatabase()
+	defer database.CloseDB()
+	peerUpdate := make(chan *models.Node)
+	go ManageZombies(context.Background(), peerUpdate)
+	go func() {
+		for y := range peerUpdate {
+			fmt.Printf("Pointless %v\n", y)
+			//do nothing
+		}
+	}()
+
 	h := models.Host{
 		ID:              uuid.New(),
 		EndpointIP:      net.ParseIP("192.168.1.1"),

+ 38 - 0
logic/hostactions/hostactions.go

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

+ 9 - 17
logic/hosts.go

@@ -90,12 +90,13 @@ func CreateHost(h *models.Host) error {
 	if (err != nil && !database.IsEmptyRecord(err)) || (err == nil) {
 		return ErrHostExists
 	}
-	//encrypt that password so we never see it
+	// encrypt that password so we never see it
 	hash, err := bcrypt.GenerateFromPassword([]byte(h.HostPass), 5)
 	if err != nil {
 		return err
 	}
 	h.HostPass = string(hash)
+	checkForZombieHosts(h)
 	return UpsertHost(h)
 }
 
@@ -216,6 +217,7 @@ func UpdateHostNetwork(h *models.Host, network string, add bool) (*models.Node,
 		newNode := models.Node{}
 		newNode.Server = servercfg.GetServer()
 		newNode.Network = network
+		newNode.HostID = h.ID
 		if err := AssociateNodeToHost(&newNode, h); err != nil {
 			return nil, err
 		}
@@ -234,7 +236,12 @@ func AssociateNodeToHost(n *models.Node, h *models.Host) error {
 	if err != nil {
 		return err
 	}
-	h.Nodes = append(h.Nodes, n.ID.String())
+	currentHost, err := GetHost(h.ID.String())
+	if err != nil {
+		return err
+	}
+	h.HostPass = currentHost.HostPass
+	h.Nodes = append(currentHost.Nodes, n.ID.String())
 	return UpsertHost(h)
 }
 
@@ -308,21 +315,6 @@ func GetDefaultHosts() []models.Host {
 	return defaultHostList[:]
 }
 
-// AddDefaultHostsToNetwork - adds a node to network for every default host on Netmaker server
-func AddDefaultHostsToNetwork(network, server string) error {
-	// add default hosts to network
-	defaultHosts := GetDefaultHosts()
-	for i := range defaultHosts {
-		newNode := models.Node{}
-		newNode.Network = network
-		newNode.Server = server
-		if err := AssociateNodeToHost(&newNode, &defaultHosts[i]); err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
 // GetHostNetworks - fetches all the networks
 func GetHostNetworks(hostID string) []string {
 	currHost, err := GetHost(hostID)

+ 0 - 37
logic/metrics/metrics.go

@@ -4,7 +4,6 @@ import (
 	"time"
 
 	"github.com/gravitl/netmaker/logger"
-	"github.com/gravitl/netmaker/logic"
 	proxy_metrics "github.com/gravitl/netmaker/metrics"
 	"github.com/gravitl/netmaker/models"
 	"golang.zx2c4.com/wireguard/wgctrl"
@@ -71,42 +70,6 @@ func Collect(iface, server, network string, peerMap models.PeerMap) (*models.Met
 	return &metrics, nil
 }
 
-// GetExchangedBytesForNode - get exchanged bytes for current node peers
-func GetExchangedBytesForNode(node *models.Node, metrics *models.Metrics) error {
-	host, err := logic.GetHost(node.HostID.String())
-	if err != nil {
-		return err
-	}
-	peers, err := logic.GetPeerUpdate(node, host)
-	if err != nil {
-		logger.Log(0, "Failed to get peers: ", err.Error())
-		return err
-	}
-	wgclient, err := wgctrl.New()
-	if err != nil {
-		return err
-	}
-	defer wgclient.Close()
-	device, err := wgclient.Device(models.WIREGUARD_INTERFACE)
-	if err != nil {
-		return err
-	}
-	for _, currPeer := range device.Peers {
-		id := peers.PeerIDs[currPeer.PublicKey.String()].ID
-		address := peers.PeerIDs[currPeer.PublicKey.String()].Address
-		if id == "" || address == "" {
-			logger.Log(0, "attempted to parse metrics for invalid peer from server", id, address)
-			continue
-		}
-		logger.Log(2, "collecting exchanged bytes info for peer: ", address)
-		peerMetric := metrics.Connectivity[id]
-		peerMetric.TotalReceived = currPeer.ReceiveBytes
-		peerMetric.TotalSent = currPeer.TransmitBytes
-		metrics.Connectivity[id] = peerMetric
-	}
-	return nil
-}
-
 // == used to fill zero value data for non connected peers ==
 func fillUnconnectedData(metrics *models.Metrics, peerMap models.PeerMap) {
 	for r := range peerMap {

+ 1 - 47
logic/nodes.go

@@ -1,7 +1,6 @@
 package logic
 
 import (
-	"context"
 	"encoding/json"
 	"errors"
 	"fmt"
@@ -382,22 +381,6 @@ func FindRelay(node *models.Node) *models.Node {
 	return nil
 }
 
-func findNode(ip string) (*models.Node, error) {
-	nodes, err := GetAllNodes()
-	if err != nil {
-		return nil, err
-	}
-	for _, node := range nodes {
-		if node.Address.IP.String() == ip {
-			return &node, nil
-		}
-		if node.Address6.IP.String() == ip {
-			return &node, nil
-		}
-	}
-	return nil, errors.New("node not found")
-}
-
 // GetNetworkIngresses - gets the gateways of a network
 func GetNetworkIngresses(network string) ([]models.Node, error) {
 	var ingresses []models.Node
@@ -437,35 +420,6 @@ func updateProNodeACLS(node *models.Node) error {
 	return nil
 }
 
-func PurgePendingNodes(ctx context.Context) {
-	ticker := time.NewTicker(NodePurgeCheckTime)
-	defer ticker.Stop()
-	for {
-		select {
-		case <-ctx.Done():
-			return
-		case <-ticker.C:
-			nodes, err := GetAllNodes()
-			if err != nil {
-				logger.Log(0, "PurgePendingNodes failed to retrieve nodes", err.Error())
-				continue
-			}
-			for _, node := range nodes {
-				if node.PendingDelete {
-					modified := node.LastModified
-					if time.Since(modified) > NodePurgeTime {
-						if err := DeleteNode(&node, true); err != nil {
-							logger.Log(0, "failed to purge node", node.ID.String(), err.Error())
-						} else {
-							logger.Log(0, "purged node ", node.ID.String())
-						}
-					}
-				}
-			}
-		}
-	}
-}
-
 // createNode - creates a node in database
 func createNode(node *models.Node) error {
 	host, err := GetHost(node.HostID.String())
@@ -534,7 +488,7 @@ func createNode(node *models.Node) error {
 	if err != nil {
 		return err
 	}
-	CheckZombies(node, host.MacAddress)
+	CheckZombies(node)
 
 	nodebytes, err := json.Marshal(&node)
 	if err != nil {

+ 96 - 649
logic/peers.go

@@ -6,192 +6,16 @@ import (
 	"log"
 	"net"
 	"net/netip"
-	"sort"
-	"strconv"
-	"strings"
-	"time"
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"golang.org/x/exp/slices"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
-// GetPeersforProxy calculates the peers for a proxy
-// TODO ==========================
-// TODO ==========================
-// TODO ==========================
-// TODO ==========================
-// TODO ==========================
-// revisit this logic with new host/node models.
-func GetPeersForProxy(node *models.Node, onlyPeers bool) (models.ProxyManagerPayload, error) {
-	proxyPayload := models.ProxyManagerPayload{}
-	var peers []wgtypes.PeerConfig
-	peerConfMap := make(map[string]models.PeerConf)
-	var err error
-	currentPeers, err := GetNetworkNodes(node.Network)
-	if err != nil {
-		return proxyPayload, err
-	}
-	if !onlyPeers {
-		if node.IsRelayed {
-			relayNode := FindRelay(node)
-			relayHost, err := GetHost(relayNode.HostID.String())
-			if err != nil {
-				return proxyPayload, err
-			}
-			if relayNode != nil {
-				host, err := GetHost(relayNode.HostID.String())
-				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.ListenPort))
-				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 node for:  ", node.ID.String())
-			}
-
-		}
-		if node.IsRelay {
-			host, err := GetHost(node.HostID.String())
-			if err != nil {
-				logger.Log(0, "error retrieving host for relay node", node.ID.String(), err.Error())
-			}
-			relayedNodes, err := GetRelayedNodes(node)
-			if err != nil {
-				logger.Log(1, "failed to relayed nodes: ", node.ID.String(), err.Error())
-				proxyPayload.IsRelay = false
-			} else {
-				relayPeersMap := make(map[string]models.RelayedConf)
-				for _, relayedNode := range relayedNodes {
-					relayedNode := relayedNode
-					payload, err := GetPeersForProxy(&relayedNode, true)
-					if err == nil {
-						relayedHost, err := GetHost(relayedNode.HostID.String())
-						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.ListenPort))
-						if udpErr == nil {
-							relayPeersMap[host.PublicKey.String()] = models.RelayedConf{
-								RelayedPeerEndpoint: relayedEndpoint,
-								RelayedPeerPubKey:   relayedHost.PublicKey.String(),
-								Peers:               payload.Peers,
-							}
-						}
-
-					}
-				}
-				proxyPayload.IsRelay = true
-				proxyPayload.RelayedPeerConf = relayPeersMap
-			}
-		}
-
-	}
-
-	for _, peer := range currentPeers {
-		if peer.ID == node.ID {
-			//skip yourself
-			continue
-		}
-		host, err := GetHost(peer.HostID.String())
-		if err != nil {
-			continue
-		}
-		proxyStatus := host.ProxyEnabled
-		listenPort := host.ListenPort
-		if proxyStatus {
-			listenPort = host.ProxyListenPort
-			if listenPort == 0 {
-				listenPort = models.NmProxyPort
-			}
-		} else if listenPort == 0 {
-			listenPort = host.ListenPort
-
-		}
-
-		endpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", host.EndpointIP, listenPort))
-		if err != nil {
-			logger.Log(1, "failed to resolve udp addr for node: ", peer.ID.String(), host.EndpointIP.String(), err.Error())
-			continue
-		}
-		allowedips := GetAllowedIPs(node, &peer, nil)
-		var keepalive time.Duration
-		if node.PersistentKeepalive != 0 {
-			// set_keepalive
-			keepalive = node.PersistentKeepalive
-		}
-		peers = append(peers, wgtypes.PeerConfig{
-			PublicKey:                   host.PublicKey,
-			Endpoint:                    endpoint,
-			AllowedIPs:                  allowedips,
-			PersistentKeepaliveInterval: &keepalive,
-			ReplaceAllowedIPs:           true,
-		})
-		peerConfMap[host.PublicKey.String()] = models.PeerConf{
-			Address:          net.ParseIP(peer.PrimaryAddress()),
-			Proxy:            proxyStatus,
-			PublicListenPort: int32(listenPort),
-		}
-
-		if !onlyPeers && peer.IsRelayed {
-			relayNode := FindRelay(&peer)
-			if relayNode != nil {
-				relayHost, err := GetHost(relayNode.HostID.String())
-				if err != nil {
-					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.ListenPort))
-				if err == nil {
-					peerConfMap[host.PublicKey.String()] = models.PeerConf{
-
-						IsRelayed:        true,
-						RelayedTo:        relayTo,
-						Address:          net.ParseIP(peer.PrimaryAddress()),
-						Proxy:            proxyStatus,
-						PublicListenPort: int32(listenPort),
-					}
-				}
-
-			}
-
-		}
-	}
-	if node.IsIngressGateway {
-		var extPeers []wgtypes.PeerConfig
-		extPeers, peerConfMap, err = getExtPeersForProxy(node, peerConfMap)
-		if err == nil {
-			peers = append(peers, extPeers...)
-
-		} else if !database.IsEmptyRecord(err) {
-			logger.Log(1, "error retrieving external clients:", err.Error())
-		}
-	}
-
-	proxyPayload.IsIngress = node.IsIngressGateway
-	addr := node.Address
-	if addr.String() == "" {
-		addr = node.Address6
-	}
-	proxyPayload.Peers = peers
-	proxyPayload.PeerMap = peerConfMap
-	//proxyPayload.Network = node.Network
-	//proxyPayload.InterfaceName = node.Interface
-	//hardcode or read from host ??
-	proxyPayload.InterfaceName = models.WIREGUARD_INTERFACE
-
-	return proxyPayload, nil
-}
-
 // GetProxyUpdateForHost - gets the proxy update for host
 func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error) {
 	proxyPayload := models.ProxyManagerPayload{
@@ -201,7 +25,7 @@ func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error
 	if host.IsRelayed {
 		relayHost, err := GetHost(host.RelayedBy)
 		if err == nil {
-			relayEndpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, getPeerListenPort(relayHost)))
+			relayEndpoint, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, GetPeerListenPort(relayHost)))
 			if err != nil {
 				logger.Log(1, "failed to resolve relay node endpoint: ", err.Error())
 			}
@@ -210,16 +34,15 @@ func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error
 		} else {
 			logger.Log(0, "couldn't find relay host for:  ", host.ID.String())
 		}
-
 	}
 	if host.IsRelay {
 		relayedHosts := GetRelayedHosts(host)
 		relayPeersMap := make(map[string]models.RelayedConf)
 		for _, relayedHost := range relayedHosts {
 			relayedHost := relayedHost
-			payload, err := GetPeerUpdateForHost(&relayedHost)
+			payload, err := GetPeerUpdateForHost("", &relayedHost, nil)
 			if err == nil {
-				relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, getPeerListenPort(&relayedHost)))
+				relayedEndpoint, udpErr := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayedHost.EndpointIP, GetPeerListenPort(&relayedHost)))
 				if udpErr == nil {
 					relayPeersMap[relayedHost.PublicKey.String()] = models.RelayedConf{
 						RelayedPeerEndpoint: relayedEndpoint,
@@ -259,14 +82,14 @@ func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error
 			if currPeerConf, found = peerConfMap[peerHost.PublicKey.String()]; !found {
 				currPeerConf = models.PeerConf{
 					Proxy:            peerHost.ProxyEnabled,
-					PublicListenPort: int32(getPeerListenPort(peerHost)),
+					PublicListenPort: int32(GetPeerListenPort(peerHost)),
 				}
 			}
 
 			if peerHost.IsRelayed && peerHost.RelayedBy != host.ID.String() {
 				relayHost, err := GetHost(peerHost.RelayedBy)
 				if err == nil {
-					relayTo, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, getPeerListenPort(relayHost)))
+					relayTo, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", relayHost.EndpointIP, GetPeerListenPort(relayHost)))
 					if err == nil {
 						currPeerConf.IsRelayed = true
 						currPeerConf.RelayedTo = relayTo
@@ -294,36 +117,41 @@ func GetProxyUpdateForHost(host *models.Host) (models.ProxyManagerPayload, error
 }
 
 // GetPeerUpdateForHost - gets the consolidated peer update for the host from all networks
-func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
+func GetPeerUpdateForHost(network string, host *models.Host, deletedNode *models.Node) (models.HostPeerUpdate, error) {
 	if host == nil {
 		return models.HostPeerUpdate{}, errors.New("host is nil")
 	}
+	// track which nodes are deleted
+	// after peer calculation, if peer not in list, add delete config of peer
 	hostPeerUpdate := models.HostPeerUpdate{
 		Host:          *host,
 		Server:        servercfg.GetServer(),
-		Network:       make(map[string]models.NetworkInfo),
-		PeerIDs:       make(models.HostPeerMap),
+		HostPeerIDs:   make(models.HostPeerMap, 0),
 		ServerVersion: servercfg.GetVersion(),
 		ServerAddrs:   []models.ServerAddr{},
 		IngressInfo: models.IngressInfo{
 			ExtPeers: make(map[string]models.ExtClientInfo),
 		},
 		EgressInfo: make(map[string]models.EgressInfo),
+		PeerIDs:    make(models.PeerMap, 0),
+		Peers:      []wgtypes.PeerConfig{},
+		NodePeers:  []wgtypes.PeerConfig{},
+	}
+	var deletedNodes = []models.Node{} // used to track deleted nodes
+	if deletedNode != nil {
+		deletedNodes = append(deletedNodes, *deletedNode)
 	}
-	logger.Log(1, "peer update for host ", host.ID.String())
+	logger.Log(1, "peer update for host", host.ID.String())
 	peerIndexMap := make(map[string]int)
 	for _, nodeID := range host.Nodes {
+		nodeID := nodeID
 		node, err := GetNodeByID(nodeID)
 		if err != nil {
 			continue
 		}
-		if !node.Connected || node.Action == models.NODE_DELETE || node.PendingDelete {
+		if !node.Connected || node.PendingDelete || node.Action == models.NODE_DELETE {
 			continue
 		}
-
-		hostPeerUpdate.Network[node.Network] = models.NetworkInfo{
-			DNS: getPeerDNS(node.Network),
-		}
 		currentPeers, err := GetNetworkNodes(node.Network)
 		if err != nil {
 			log.Println("no network nodes")
@@ -334,10 +162,14 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 			nodePeerMap = make(map[string]models.PeerRouteInfo)
 		}
 		for _, peer := range currentPeers {
-			if peer.ID == node.ID {
+			peer := peer
+			if peer.ID.String() == node.ID.String() {
 				logger.Log(2, "peer update, skipping self")
 				//skip yourself
-
+				continue
+			}
+			if peer.Action == models.NODE_DELETE || peer.PendingDelete {
+				deletedNodes = append(deletedNodes, peer) // track deleted node for peer update
 				continue
 			}
 			var peerConfig wgtypes.PeerConfig
@@ -347,13 +179,13 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 				return models.HostPeerUpdate{}, err
 			}
 
-			if !peer.Connected || peer.Action == models.NODE_DELETE || peer.PendingDelete {
+			if !peer.Connected {
 				logger.Log(2, "peer update, skipping unconnected node", peer.ID.String())
 				//skip unconnected nodes
 				continue
 			}
 			if !nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) {
-				log.Println("peer update, skipping node for acl")
+				logger.Log(2, "peer update, skipping node for acl")
 				//skip if not permitted by acl
 				continue
 			}
@@ -375,7 +207,7 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 			}
 			peerConfig.Endpoint = &net.UDPAddr{
 				IP:   peerHost.EndpointIP,
-				Port: getPeerListenPort(peerHost),
+				Port: GetPeerListenPort(peerHost),
 			}
 
 			if uselocal {
@@ -395,7 +227,26 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 			}
 			peerConfig.AllowedIPs = allowedips
 			if node.IsIngressGateway || node.IsEgressGateway {
-
+				if peer.IsIngressGateway {
+					_, extPeerIDAndAddrs, err := getExtPeers(&peer)
+					if err == nil {
+						for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
+							extPeerIdAndAddr := extPeerIdAndAddr
+							nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
+								PeerAddr: net.IPNet{
+									IP:   net.ParseIP(extPeerIdAndAddr.Address),
+									Mask: getCIDRMaskFromAddr(extPeerIdAndAddr.Address),
+								},
+								PeerKey: extPeerIdAndAddr.ID,
+								Allow:   true,
+							}
+						}
+					}
+				}
+				if node.IsIngressGateway && peer.IsEgressGateway {
+					hostPeerUpdate.IngressInfo.EgressRanges = append(hostPeerUpdate.IngressInfo.EgressRanges,
+						peer.EgressGatewayRanges...)
+				}
 				nodePeerMap[peerHost.PublicKey.String()] = models.PeerRouteInfo{
 					PeerAddr: net.IPNet{
 						IP:   net.ParseIP(peer.PrimaryAddress()),
@@ -406,28 +257,40 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 				}
 			}
 
-			if _, ok := hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()]; !ok {
-				hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()] = make(map[string]models.IDandAddr)
+			var nodePeer wgtypes.PeerConfig
+			if _, ok := hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()]; !ok {
+				hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()] = make(map[string]models.IDandAddr)
 				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
 				peerIndexMap[peerHost.PublicKey.String()] = len(hostPeerUpdate.Peers) - 1
-				hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
+				hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
 					ID:      peer.ID.String(),
 					Address: peer.PrimaryAddress(),
 					Name:    peerHost.Name,
 					Network: peer.Network,
 				}
+				nodePeer = peerConfig
 			} 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{
+				hostPeerUpdate.HostPeerIDs[peerHost.PublicKey.String()][peer.ID.String()] = models.IDandAddr{
 					ID:      peer.ID.String(),
 					Address: peer.PrimaryAddress(),
 					Name:    peerHost.Name,
 					Network: peer.Network,
 				}
+				nodePeer = hostPeerUpdate.Peers[peerIndexMap[peerHost.PublicKey.String()]]
 			}
 
+			if node.Network == network { // add to peers map for metrics
+				hostPeerUpdate.PeerIDs[peerHost.PublicKey.String()] = models.IDandAddr{
+					ID:      peer.ID.String(),
+					Address: peer.PrimaryAddress(),
+					Name:    peerHost.Name,
+					Network: peer.Network,
+				}
+				hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, nodePeer)
+			}
 		}
 		var extPeers []wgtypes.PeerConfig
 		var extPeerIDAndAddrs []models.IDandAddr
@@ -435,6 +298,7 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 			extPeers, extPeerIDAndAddrs, err = getExtPeers(&node)
 			if err == nil {
 				for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
+					extPeerIdAndAddr := extPeerIdAndAddr
 					nodePeerMap[extPeerIdAndAddr.ID] = models.PeerRouteInfo{
 						PeerAddr: net.IPNet{
 							IP:   net.ParseIP(extPeerIdAndAddr.Address),
@@ -446,8 +310,9 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 				}
 				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{
+					extPeerIdAndAddr := extPeerIdAndAddr
+					hostPeerUpdate.HostPeerIDs[extPeerIdAndAddr.ID] = make(map[string]models.IDandAddr)
+					hostPeerUpdate.HostPeerIDs[extPeerIdAndAddr.ID][extPeerIdAndAddr.ID] = models.IDandAddr{
 						ID:      extPeerIdAndAddr.ID,
 						Address: extPeerIdAndAddr.Address,
 						Name:    extPeerIdAndAddr.Name,
@@ -467,8 +332,11 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 						ExtPeerKey: extPeerIdAndAddr.ID,
 						Peers:      nodePeerMap,
 					}
+					if node.Network == network {
+						hostPeerUpdate.PeerIDs[extPeerIdAndAddr.ID] = extPeerIdAndAddr
+						hostPeerUpdate.NodePeers = append(hostPeerUpdate.NodePeers, extPeers...)
+					}
 				}
-
 			} else if !database.IsEmptyRecord(err) {
 				logger.Log(1, "error retrieving external clients:", err.Error())
 			}
@@ -487,10 +355,33 @@ func GetPeerUpdateForHost(host *models.Host) (models.HostPeerUpdate, error) {
 		}
 	}
 
+	// run through delete nodes
+	if len(deletedNodes) > 0 {
+		for i := range deletedNodes {
+			delNode := deletedNodes[i]
+			delHost, err := GetHost(delNode.HostID.String())
+			if err != nil {
+				continue
+			}
+			if _, ok := hostPeerUpdate.HostPeerIDs[delHost.PublicKey.String()]; !ok {
+				var peerConfig = wgtypes.PeerConfig{}
+				peerConfig.PublicKey = delHost.PublicKey
+				peerConfig.Endpoint = &net.UDPAddr{
+					IP:   delHost.EndpointIP,
+					Port: GetPeerListenPort(delHost),
+				}
+				peerConfig.Remove = true
+				peerConfig.AllowedIPs = []net.IPNet{delNode.Address, delNode.Address6}
+				hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, peerConfig)
+			}
+		}
+	}
+
 	return hostPeerUpdate, nil
 }
 
-func getPeerListenPort(host *models.Host) int {
+// GetPeerListenPort - given a host, retrieve it's appropriate listening port
+func GetPeerListenPort(host *models.Host) int {
 	peerPort := host.ListenPort
 	if host.ProxyEnabled {
 		if host.PublicListenPort != 0 {
@@ -502,286 +393,6 @@ func getPeerListenPort(host *models.Host) int {
 	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)
-	peerUpdate := models.PeerUpdate{
-		Network:       node.Network,
-		ServerVersion: ncutils.Version,
-		DNS:           getPeerDNS(node.Network),
-		PeerIDs:       make(models.PeerMap),
-	}
-	currentPeers, err := GetNetworkNodes(node.Network)
-	if err != nil {
-		log.Println("no network nodes")
-		return models.PeerUpdate{}, err
-	}
-	for _, peer := range currentPeers {
-		var peerConfig wgtypes.PeerConfig
-		peerHost, err := GetHost(peer.HostID.String())
-		if err != nil {
-			log.Println("no peer host", err)
-			return models.PeerUpdate{}, err
-		}
-		if peer.ID == node.ID {
-			log.Println("peer update, skipping self")
-			//skip yourself
-
-			continue
-		}
-		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: peerHost.ListenPort,
-		}
-		if peerHost.ProxyEnabled {
-			peerConfig.Endpoint.Port = getPeerListenPort(peerHost)
-		}
-		if uselocal {
-			peerConfig.Endpoint.IP = peer.LocalAddress.IP
-		}
-		allowedips := getNodeAllowedIPs(&peer, node)
-		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)...)
-		}
-
-		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)
-	}
-	return peerUpdate, nil
-}
-
-// func getRelayAllowedIPs(node, peer *models.Node) []net.IPNet {
-// 	var allowedips []net.IPNet
-// 	var allowedip net.IPNet
-// 	for _, addr := range peer.RelayAddrs {
-// 		if node.Address.IP.String() == addr {
-// 			continue
-// 		}
-// 		if node.Address6.IP.String() == addr {
-// 			continue
-// 		}
-// 		allowedip.IP = net.ParseIP(addr)
-// 		allowedips = append(allowedips, allowedip)
-// 	}
-// 	return allowedips
-// }
-
-// GetPeerUpdateLegacy - gets a wireguard peer config for each peer of a node
-func GetPeerUpdateLegacy(node *models.Node) (models.PeerUpdate, error) {
-	var peerUpdate models.PeerUpdate
-	var peers []wgtypes.PeerConfig
-	var serverNodeAddresses = []models.ServerAddr{}
-	var peerMap = make(models.PeerMap)
-	var metrics *models.Metrics
-	if servercfg.Is_EE {
-		metrics, _ = GetMetrics(node.ID.String())
-	}
-	if metrics == nil {
-		metrics = &models.Metrics{}
-	}
-	if metrics.FailoverPeers == nil {
-		metrics.FailoverPeers = make(map[string]string)
-	}
-	// udppeers = the peers parsed from the local interface
-	// gives us correct port to reach
-	udppeers, errN := database.GetPeers(node.Network)
-	if errN != nil {
-		logger.Log(2, errN.Error())
-	}
-
-	currentPeers, err := GetNetworkNodes(node.Network)
-	if err != nil {
-		return models.PeerUpdate{}, err
-	}
-	host, err := GetHost(node.HostID.String())
-	if err != nil {
-		return peerUpdate, err
-	}
-	if node.IsRelayed && !host.ProxyEnabled {
-		return GetPeerUpdateForRelayedNode(node, udppeers)
-	}
-
-	// #1 Set Keepalive values: set_keepalive
-	// #2 Set local address: set_local - could be a LOT BETTER and fix some bugs with additional logic
-	// #3 Set allowedips: set_allowedips
-	for _, peer := range currentPeers {
-		peerHost, err := GetHost(peer.HostID.String())
-		if err != nil {
-			logger.Log(0, "error retrieving host for peer", node.ID.String(), err.Error())
-			return models.PeerUpdate{}, err
-		}
-		if peer.ID == node.ID {
-			//skip yourself
-			continue
-		}
-		if node.Connected {
-			//skip unconnected nodes
-			continue
-		}
-
-		// if the node is not a server, set the endpoint
-		var setEndpoint = true
-
-		if peer.IsRelayed {
-			if !peerHost.ProxyEnabled && !(node.IsRelay && ncutils.StringSliceContains(node.RelayAddrs, peer.PrimaryAddress())) {
-				//skip -- will be added to relay
-				continue
-			} else if node.IsRelay && ncutils.StringSliceContains(node.RelayAddrs, peer.PrimaryAddress()) {
-				// dont set peer endpoint if it's relayed by node
-				setEndpoint = false
-			}
-		}
-		if !nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) {
-			//skip if not permitted by acl
-			continue
-		}
-		if len(metrics.FailoverPeers[peer.ID.String()]) > 0 && IsFailoverPresent(node.Network) {
-			logger.Log(2, "peer", peer.ID.String(), peer.PrimaryAddress(), "was found to be in failover peers list for node", node.ID.String(), node.PrimaryAddress())
-			continue
-		}
-		if err != nil {
-			return models.PeerUpdate{}, err
-		}
-		host, err := GetHost(node.HostID.String())
-		if err != nil {
-			logger.Log(0, "error retrieving host for node", node.ID.String(), err.Error())
-			return models.PeerUpdate{}, err
-		}
-		if host.EndpointIP.String() == peerHost.EndpointIP.String() {
-			//peer is on same network
-			// set_local
-			if node.LocalAddress.String() != peer.LocalAddress.String() && peer.LocalAddress.IP != nil {
-				peerHost.EndpointIP = peer.LocalAddress.IP
-				if peerHost.ListenPort != 0 {
-					peerHost.ListenPort = getPeerListenPort(peerHost)
-				}
-			} else {
-				continue
-			}
-		}
-
-		// set address if setEndpoint is true
-		// otherwise, will get inserted as empty value
-		var address *net.UDPAddr
-
-		// Sets ListenPort to UDP Hole Punching Port assuming:
-		// - UDP Hole Punching is enabled
-		// - udppeers retrieval did not return an error
-		// - the endpoint is valid
-		if setEndpoint {
-
-			var setUDPPort = false
-			if CheckEndpoint(udppeers[peerHost.PublicKey.String()]) {
-				endpointstring := udppeers[peerHost.PublicKey.String()]
-				endpointarr := strings.Split(endpointstring, ":")
-				if len(endpointarr) == 2 {
-					port, err := strconv.Atoi(endpointarr[1])
-					if err == nil {
-						setUDPPort = true
-						peerHost.ListenPort = port
-					}
-				}
-			}
-			// 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.ListenPort != 0 {
-				peerHost.ListenPort = getPeerListenPort(peerHost)
-			}
-
-			endpoint := peerHost.EndpointIP.String() + ":" + strconv.FormatInt(int64(peerHost.ListenPort), 10)
-			address, err = net.ResolveUDPAddr("udp", endpoint)
-			if err != nil {
-				return models.PeerUpdate{}, err
-			}
-		}
-		allowedips := GetAllowedIPs(node, &peer, metrics)
-		var keepalive time.Duration
-		if node.PersistentKeepalive != 0 {
-			// set_keepalive
-			keepalive = node.PersistentKeepalive
-		}
-		var peerData = wgtypes.PeerConfig{
-			PublicKey:                   peerHost.PublicKey,
-			Endpoint:                    address,
-			ReplaceAllowedIPs:           true,
-			AllowedIPs:                  allowedips,
-			PersistentKeepaliveInterval: &keepalive,
-		}
-
-		peers = append(peers, peerData)
-		peerMap[peerHost.PublicKey.String()] = models.IDandAddr{
-			Name:     peerHost.Name,
-			ID:       peer.ID.String(),
-			Address:  peer.PrimaryAddress(),
-			IsServer: "no",
-		}
-
-	}
-	if node.IsIngressGateway {
-		extPeers, idsAndAddr, err := getExtPeers(node)
-		if err == nil {
-			peers = append(peers, extPeers...)
-			for i := range idsAndAddr {
-				peerMap[idsAndAddr[i].ID] = idsAndAddr[i]
-			}
-		} else if !database.IsEmptyRecord(err) {
-			logger.Log(1, "error retrieving external clients:", err.Error())
-		}
-	}
-
-	peerUpdate.Network = node.Network
-	peerUpdate.ServerVersion = servercfg.Version
-	sort.SliceStable(peers[:], func(i, j int) bool {
-		return peers[i].PublicKey.String() < peers[j].PublicKey.String()
-	})
-	peerUpdate.Peers = peers
-	peerUpdate.ServerAddrs = serverNodeAddresses
-	peerUpdate.DNS = getPeerDNS(node.Network)
-	peerUpdate.PeerIDs = peerMap
-	return peerUpdate, nil
-}
-
 func getExtPeers(node *models.Node) ([]wgtypes.PeerConfig, []models.IDandAddr, error) {
 	var peers []wgtypes.PeerConfig
 	var idsAndAddr []models.IDandAddr
@@ -948,170 +559,6 @@ func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics) []net.IPNet
 	return allowedips
 }
 
-func getPeerDNS(network string) string {
-	var dns string
-	if nodes, err := GetNetworkNodes(network); err == nil {
-		for i, node := range nodes {
-			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)
-		}
-	}
-
-	if customDNSEntries, err := GetCustomDNS(network); err == nil {
-		for _, entry := range customDNSEntries {
-			// TODO - filter entries based on ACLs / given peers vs nodes in network
-			dns = dns + fmt.Sprintf("%s %s.%s\n", entry.Address, entry.Name, entry.Network)
-		}
-	}
-	return dns
-}
-
-// GetPeerUpdateForRelayedNode - calculates peer update for a relayed node by getting the relay
-// copying the relay node's allowed ips and making appropriate substitutions
-func GetPeerUpdateForRelayedNode(node *models.Node, udppeers map[string]string) (models.PeerUpdate, error) {
-	var peerUpdate models.PeerUpdate
-	var peers []wgtypes.PeerConfig
-	var serverNodeAddresses = []models.ServerAddr{}
-	var allowedips []net.IPNet
-	//find node that is relaying us
-	relay := FindRelay(node)
-	if relay == nil {
-		return models.PeerUpdate{}, errors.New("not found")
-	}
-
-	//add relay to lists of allowed ip
-	if relay.Address.IP != nil {
-		relayIP := relay.Address
-		allowedips = append(allowedips, relayIP)
-	}
-	if relay.Address6.IP != nil {
-		relayIP6 := relay.Address6
-		allowedips = append(allowedips, relayIP6)
-	}
-	//get PeerUpdate for relayed node
-	relayHost, err := GetHost(relay.HostID.String())
-	if err != nil {
-		return models.PeerUpdate{}, err
-	}
-	relayPeerUpdate, err := GetPeerUpdate(relay, relayHost)
-	if err != nil {
-		return models.PeerUpdate{}, err
-	}
-	//add the relays allowed ips from all of the relay's peers
-	for _, peer := range relayPeerUpdate.Peers {
-		allowedips = append(allowedips, peer.AllowedIPs...)
-	}
-	//delete any ips not permitted by acl
-	for i := len(allowedips) - 1; i >= 0; i-- {
-		target, err := findNode(allowedips[i].IP.String())
-		if err != nil {
-			logger.Log(0, "failed to find node for ip", allowedips[i].IP.String(), err.Error())
-			continue
-		}
-		if target == nil {
-			logger.Log(0, "failed to find node for ip", allowedips[i].IP.String())
-			continue
-		}
-		if !nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(target.ID.String())) {
-			logger.Log(0, "deleting node from relayednode per acl", node.ID.String(), target.ID.String())
-			allowedips = append(allowedips[:i], allowedips[i+1:]...)
-		}
-	}
-	//delete self from allowed ips
-	for i := len(allowedips) - 1; i >= 0; i-- {
-		if allowedips[i].IP.String() == node.Address.IP.String() || allowedips[i].IP.String() == node.Address6.IP.String() {
-			allowedips = append(allowedips[:i], allowedips[i+1:]...)
-		}
-	}
-	//delete egressrange from allowedip if we are egress gateway
-	if node.IsEgressGateway {
-		for i := len(allowedips) - 1; i >= 0; i-- {
-			if StringSliceContains(node.EgressGatewayRanges, allowedips[i].String()) {
-				allowedips = append(allowedips[:i], allowedips[i+1:]...)
-			}
-		}
-	}
-	//delete extclients from allowedip if we are ingress gateway
-	if node.IsIngressGateway {
-		for i := len(allowedips) - 1; i >= 0; i-- {
-			if strings.Contains(node.IngressGatewayRange, allowedips[i].IP.String()) {
-				allowedips = append(allowedips[:i], allowedips[i+1:]...)
-			}
-		}
-	}
-	//add egress range if relay is egress
-	if relay.IsEgressGateway {
-		var ip *net.IPNet
-		for _, cidr := range relay.EgressGatewayRanges {
-			_, ip, err = net.ParseCIDR(cidr)
-			if err != nil {
-				continue
-			}
-		}
-		allowedips = append(allowedips, *ip)
-	}
-	var setUDPPort = false
-	var listenPort int
-	if CheckEndpoint(udppeers[relayHost.PublicKey.String()]) {
-		endpointstring := udppeers[relayHost.PublicKey.String()]
-		endpointarr := strings.Split(endpointstring, ":")
-		if len(endpointarr) == 2 {
-			port, err := strconv.Atoi(endpointarr[1])
-			if err == nil {
-				setUDPPort = true
-				listenPort = port
-			}
-		}
-	}
-	// 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.ListenPort != 0 {
-		listenPort = relayHost.ListenPort
-	}
-
-	endpoint := relayHost.EndpointIP.String() + ":" + strconv.FormatInt(int64(listenPort), 10)
-	address, err := net.ResolveUDPAddr("udp", endpoint)
-	if err != nil {
-		return models.PeerUpdate{}, err
-	}
-	var keepalive time.Duration
-	if node.PersistentKeepalive != 0 {
-		// set_keepalive
-		keepalive = node.PersistentKeepalive
-	}
-	var peerData = wgtypes.PeerConfig{
-		PublicKey:                   relayHost.PublicKey,
-		Endpoint:                    address,
-		ReplaceAllowedIPs:           true,
-		AllowedIPs:                  allowedips,
-		PersistentKeepaliveInterval: &keepalive,
-	}
-	peers = append(peers, peerData)
-	//if ingress add extclients
-	if node.IsIngressGateway {
-		extPeers, _, err := getExtPeers(node)
-		if err == nil {
-			peers = append(peers, extPeers...)
-		} else {
-			logger.Log(2, "could not retrieve ext peers for ", node.ID.String(), err.Error())
-		}
-	}
-	peerUpdate.Network = node.Network
-	peerUpdate.ServerVersion = servercfg.Version
-	sort.SliceStable(peers[:], func(i, j int) bool {
-		return peers[i].PublicKey.String() < peers[j].PublicKey.String()
-	})
-	peerUpdate.Peers = peers
-	peerUpdate.ServerAddrs = serverNodeAddresses
-	peerUpdate.DNS = getPeerDNS(node.Network)
-	return peerUpdate, nil
-}
-
 func getEgressIPs(node, peer *models.Node) []net.IPNet {
 	host, err := GetHost(node.HostID.String())
 	if err != nil {

+ 5 - 1
logic/pro/networkuser_test.go

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

+ 0 - 2
logic/pro/usergroups_test.go

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

+ 60 - 26
logic/zombie.go

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

+ 27 - 40
main.go

@@ -27,7 +27,7 @@ import (
 	stunserver "github.com/gravitl/netmaker/stun-server"
 )
 
-var version = "v0.18.0"
+var version = "v0.18.2"
 
 // Start DB Connection and start API Request Handler
 func main() {
@@ -36,12 +36,16 @@ func main() {
 	setupConfig(*absoluteConfigPath)
 	servercfg.SetVersion(version)
 	fmt.Println(models.RetrieveLogo()) // print the logo
-	// fmt.Println(models.ProLogo())
-	initialize() // initial db and acls; gen cert if required
+	initialize()                       // initial db and acls
 	setGarbageCollection()
 	setVerbosity()
 	defer database.CloseDB()
-	startControllers() // start the api endpoint and mq
+	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, os.Interrupt)
+	defer stop()
+	var waitGroup sync.WaitGroup
+	startControllers(&waitGroup, ctx) // start the api endpoint and mq and stun
+	<-ctx.Done()
+	waitGroup.Wait()
 }
 
 func setupConfig(absoluteConfigPath string) {
@@ -110,8 +114,7 @@ func initialize() { // Client Mode Prereq Check
 	}
 }
 
-func startControllers() {
-	var waitnetwork sync.WaitGroup
+func startControllers(wg *sync.WaitGroup, ctx context.Context) {
 	if servercfg.IsDNSMode() {
 		err := logic.SetDNS()
 		if err != nil {
@@ -127,48 +130,36 @@ func startControllers() {
 				logger.FatalLog("Unable to Set host. Exiting...", err.Error())
 			}
 		}
-		waitnetwork.Add(1)
-		go controller.HandleRESTRequests(&waitnetwork)
+		wg.Add(1)
+		go controller.HandleRESTRequests(wg, ctx)
 	}
 	//Run MessageQueue
 	if servercfg.IsMessageQueueBackend() {
-		waitnetwork.Add(1)
-		go runMessageQueue(&waitnetwork)
+		wg.Add(1)
+		go runMessageQueue(wg, ctx)
 	}
 
-	if !servercfg.IsAgentBackend() && !servercfg.IsRestBackend() && !servercfg.IsMessageQueueBackend() {
-		logger.Log(0, "No Server Mode selected, so nothing is being served! Set Agent mode (AGENT_BACKEND) or Rest mode (REST_BACKEND) or MessageQueue (MESSAGEQUEUE_BACKEND) to 'true'.")
+	if !servercfg.IsRestBackend() && !servercfg.IsMessageQueueBackend() {
+		logger.Log(0, "No Server Mode selected, so nothing is being served! Set Rest mode (REST_BACKEND) or MessageQueue (MESSAGEQUEUE_BACKEND) to 'true'.")
 	}
 
 	// starts the stun server
-	waitnetwork.Add(1)
-	go stunserver.Start(&waitnetwork)
-	if servercfg.IsProxyEnabled() {
-
-		waitnetwork.Add(1)
-		go func() {
-			defer waitnetwork.Done()
-			_, cancel := context.WithCancel(context.Background())
-			waitnetwork.Add(1)
-
-			//go nmproxy.Start(ctx, logic.ProxyMgmChan, servercfg.GetAPIHost())
-			quit := make(chan os.Signal, 1)
-			signal.Notify(quit, syscall.SIGTERM, os.Interrupt)
-			<-quit
-			cancel()
-		}()
-	}
-
-	waitnetwork.Wait()
+	wg.Add(1)
+	go stunserver.Start(wg, ctx)
 }
 
 // Should we be using a context vice a waitgroup????????????
-func runMessageQueue(wg *sync.WaitGroup) {
+func runMessageQueue(wg *sync.WaitGroup, ctx context.Context) {
 	defer wg.Done()
-	brokerHost, secure := servercfg.GetMessageQueueEndpoint()
-	logger.Log(0, "connecting to mq broker at", brokerHost, "with TLS?", fmt.Sprintf("%v", secure))
+	brokerHost, _ := servercfg.GetMessageQueueEndpoint()
+	logger.Log(0, "connecting to mq broker at", brokerHost)
 	mq.SetupMQTT()
-	ctx, cancel := context.WithCancel(context.Background())
+	if mq.IsConnected() {
+		logger.Log(0, "connected to MQ Broker")
+	} else {
+		logger.FatalLog("error connecting to MQ Broker")
+	}
+	defer mq.CloseClient()
 	go mq.Keepalive(ctx)
 	go func() {
 		peerUpdate := make(chan *models.Node)
@@ -179,11 +170,7 @@ func runMessageQueue(wg *sync.WaitGroup) {
 			}
 		}
 	}()
-	go logic.PurgePendingNodes(ctx)
-	quit := make(chan os.Signal, 1)
-	signal.Notify(quit, syscall.SIGTERM, os.Interrupt)
-	<-quit
-	cancel()
+	<-ctx.Done()
 	logger.Log(0, "Message Queue shutting down")
 }
 

+ 9 - 2
models/api_host.go

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

+ 0 - 3
models/api_node.go

@@ -32,7 +32,6 @@ type ApiNode struct {
 	RelayAddrs              []string `json:"relayaddrs"`
 	FailoverNode            string   `json:"failovernode"`
 	DNSOn                   bool     `json:"dnson"`
-	IsLocal                 bool     `json:"islocal"`
 	Server                  string   `json:"server"`
 	InternetGateway         string   `json:"internetgateway"`
 	Connected               bool     `json:"connected"`
@@ -51,7 +50,6 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node {
 	convertedNode.Connected = a.Connected
 	convertedNode.ID, _ = uuid.Parse(a.ID)
 	convertedNode.HostID, _ = uuid.Parse(a.HostID)
-	convertedNode.IsLocal = a.IsLocal
 	convertedNode.IsRelay = a.IsRelay
 	convertedNode.IsRelayed = a.IsRelayed
 	convertedNode.PendingDelete = a.PendingDelete
@@ -150,7 +148,6 @@ func (nm *Node) ConvertToAPINode() *ApiNode {
 		apiNode.FailoverNode = ""
 	}
 	apiNode.DNSOn = nm.DNSOn
-	apiNode.IsLocal = nm.IsLocal
 	apiNode.Server = nm.Server
 	apiNode.InternetGateway = nm.InternetGateway.String()
 	if isEmptyAddr(apiNode.InternetGateway) {

+ 39 - 0
models/dnsEntry.go

@@ -1,6 +1,45 @@
 // TODO:  Either add a returnNetwork and returnKey, or delete this
 package models
 
+// DNSUpdateAction identifies the action to be performed with the dns update data
+type DNSUpdateAction int
+
+const (
+	// DNSDeleteByIP delete the dns entry
+	DNSDeleteByIP = iota
+	// DNSDeleteByName delete the dns entry
+	DNSDeleteByName
+	// DNSReplaceName replace the dns entry
+	DNSReplaceName
+	// DNSReplaceIP resplace the dns entry
+	DNSReplaceIP
+	// DNSInsert insert a new dns entry
+	DNSInsert
+)
+
+func (action DNSUpdateAction) String() string {
+	return [...]string{"DNSDeleteByIP", "DNSDeletByName", "DNSReplaceName", "DNSReplaceIP", "DNSInsert"}[action]
+}
+
+// DNSError.Error implementation of error interface
+func (e DNSError) Error() string {
+	return "error publishing dns update"
+}
+
+// DNSError error struct capable of holding multiple error messages
+type DNSError struct {
+	ErrorStrings []string
+}
+
+// DNSUpdate data for updating entries in /etc/hosts
+type DNSUpdate struct {
+	Action     DNSUpdateAction
+	Name       string
+	NewName    string
+	Address    string
+	NewAddress string
+}
+
 // DNSEntry - a DNS entry represented as struct
 type DNSEntry struct {
 	Address  string `json:"address" bson:"address" validate:"ip"`

+ 59 - 0
models/enrollment_key.go

@@ -0,0 +1,59 @@
+package models
+
+import (
+	"time"
+)
+
+// EnrollmentToken - the tokenized version of an enrollmentkey;
+// to be used for host registration
+type EnrollmentToken struct {
+	Server string `json:"server"`
+	Value  string `json:"value"`
+}
+
+// EnrollmentKeyLength - the length of an enrollment key - 62^16 unique possibilities
+const EnrollmentKeyLength = 32
+
+// EnrollmentKey - the key used to register hosts and join them to specific networks
+type EnrollmentKey struct {
+	Expiration    time.Time `json:"expiration"`
+	UsesRemaining int       `json:"uses_remaining"`
+	Value         string    `json:"value"`
+	Networks      []string  `json:"networks"`
+	Unlimited     bool      `json:"unlimited"`
+	Tags          []string  `json:"tags"`
+	Token         string    `json:"token,omitempty"` // B64 value of EnrollmentToken
+}
+
+// APIEnrollmentKey - used to create enrollment keys via API
+type APIEnrollmentKey struct {
+	Expiration    int64    `json:"expiration"`
+	UsesRemaining int      `json:"uses_remaining"`
+	Networks      []string `json:"networks"`
+	Unlimited     bool     `json:"unlimited"`
+	Tags          []string `json:"tags"`
+}
+
+// EnrollmentKey.IsValid - checks if the key is still valid to use
+func (k *EnrollmentKey) IsValid() bool {
+	if k == nil {
+		return false
+	}
+	if k.UsesRemaining > 0 {
+		return true
+	}
+	if !k.Expiration.IsZero() && time.Now().Before(k.Expiration) {
+		return true
+	}
+
+	return k.Unlimited
+}
+
+// EnrollmentKey.Validate - validate's an EnrollmentKey
+// should be used during creation
+func (k *EnrollmentKey) Validate() bool {
+	return k.Networks != nil &&
+		k.Tags != nil &&
+		len(k.Value) == EnrollmentKeyLength &&
+		k.IsValid()
+}

+ 4 - 0
models/host.go

@@ -74,6 +74,10 @@ const (
 	DeleteHost = "DELETE_HOST"
 	// JoinHostToNetwork - constant for host network join action
 	JoinHostToNetwork = "JOIN_HOST_TO_NETWORK"
+	// Acknowledgement - ACK response for hosts
+	Acknowledgement = "ACK"
+	// RequestAck - request an ACK
+	RequestAck = "REQ_ACK"
 )
 
 // HostUpdate - struct for host update

+ 13 - 27
models/mqtt.go

@@ -6,34 +6,25 @@ import (
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
-// PeerUpdate - struct
-type PeerUpdate struct {
-	Network       string               `json:"network" bson:"network" yaml:"network"`
-	ServerVersion string               `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
-	ServerAddrs   []ServerAddr         `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
-	Peers         []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
-	DNS           string               `json:"dns" bson:"dns" yaml:"dns"`
-	PeerIDs       PeerMap              `json:"peerids" bson:"peerids" yaml:"peerids"`
-	ProxyUpdate   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"`
-	Server        string                 `json:"server" bson:"server" yaml:"server"`
-	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   ProxyManagerPayload    `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"`
-	EgressInfo    map[string]EgressInfo  `json:"egress_info" bson:"egress_info" yaml:"egress_info"` // map key is node ID
-	IngressInfo   IngressInfo            `json:"ingress_info" bson:"ext_peers" yaml:"ext_peers"`
+	Host          Host                 `json:"host" bson:"host" yaml:"host"`
+	Server        string               `json:"server" bson:"server" yaml:"server"`
+	ServerVersion string               `json:"serverversion" bson:"serverversion" yaml:"serverversion"`
+	ServerAddrs   []ServerAddr         `json:"serveraddrs" bson:"serveraddrs" yaml:"serveraddrs"`
+	NodePeers     []wgtypes.PeerConfig `json:"peers" bson:"peers" yaml:"peers"`
+	Peers         []wgtypes.PeerConfig
+	HostPeerIDs   HostPeerMap           `json:"hostpeerids" bson:"hostpeerids" yaml:"hostpeerids"`
+	ProxyUpdate   ProxyManagerPayload   `json:"proxy_update" bson:"proxy_update" yaml:"proxy_update"`
+	EgressInfo    map[string]EgressInfo `json:"egress_info" bson:"egress_info" yaml:"egress_info"` // map key is node ID
+	IngressInfo   IngressInfo           `json:"ingress_info" bson:"ext_peers" yaml:"ext_peers"`
+	PeerIDs       PeerMap               `json:"peerids" bson:"peerids" yaml:"peerids"`
 }
 
 // IngressInfo - struct for ingress info
 type IngressInfo struct {
-	ExtPeers map[string]ExtClientInfo `json:"ext_peers" yaml:"ext_peers"`
+	ExtPeers     map[string]ExtClientInfo `json:"ext_peers" yaml:"ext_peers"`
+	EgressRanges []string                 `json:"egress_ranges" yaml:"egress_ranges"`
 }
 
 // EgressInfo - struct for egress info
@@ -62,11 +53,6 @@ type ExtClientInfo struct {
 	Peers       map[string]PeerRouteInfo `json:"peers" yaml:"peers"`
 }
 
-// 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"`

+ 0 - 4
models/network.go

@@ -21,7 +21,6 @@ type Network struct {
 	DefaultKeepalive    int32                 `json:"defaultkeepalive" bson:"defaultkeepalive" validate:"omitempty,max=1000"`
 	AccessKeys          []AccessKey           `json:"accesskeys" bson:"accesskeys"`
 	AllowManualSignUp   string                `json:"allowmanualsignup" bson:"allowmanualsignup" validate:"checkyesorno"`
-	IsLocal             string                `json:"islocal" bson:"islocal" validate:"checkyesorno"`
 	IsIPv4              string                `json:"isipv4" bson:"isipv4" validate:"checkyesorno"`
 	IsIPv6              string                `json:"isipv6" bson:"isipv6" validate:"checkyesorno"`
 	DefaultUDPHolePunch string                `json:"defaultudpholepunch" bson:"defaultudpholepunch" validate:"checkyesorno"`
@@ -51,9 +50,6 @@ func (network *Network) SetDefaults() {
 	if network.DefaultUDPHolePunch == "" {
 		network.DefaultUDPHolePunch = "no"
 	}
-	if network.IsLocal == "" {
-		network.IsLocal = "no"
-	}
 	if network.DefaultInterface == "" {
 		if len(network.NetID) < 13 {
 			network.DefaultInterface = "nm-" + network.NetID

+ 1 - 1
models/network_test.go

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

+ 0 - 14
models/node.go

@@ -68,7 +68,6 @@ type CommonNode struct {
 	Address6            net.IPNet     `json:"address6" yaml:"address6"`
 	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"`
 	EgressGatewayRanges []string      `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"`
 	IsIngressGateway    bool          `json:"isingressgateway" yaml:"isingressgateway"`
@@ -145,7 +144,6 @@ type LegacyNode struct {
 	DNSOn           string      `json:"dnson" bson:"dnson" yaml:"dnson" validate:"checkyesorno"`
 	IsServer        string      `json:"isserver" bson:"isserver" yaml:"isserver" validate:"checkyesorno"`
 	Action          string      `json:"action" bson:"action" yaml:"action"`
-	IsLocal         string      `json:"islocal" bson:"islocal" yaml:"islocal" validate:"checkyesorno"`
 	IPForwarding    string      `json:"ipforwarding" bson:"ipforwarding" yaml:"ipforwarding" validate:"checkyesorno"`
 	OS              string      `json:"os" bson:"os" yaml:"os"`
 	MTU             int32       `json:"mtu" bson:"mtu" yaml:"mtu"`
@@ -295,13 +293,6 @@ func (node *LegacyNode) SetIPForwardingDefault() {
 	}
 }
 
-// Node.SetIsLocalDefault - set is local default
-func (node *LegacyNode) SetIsLocalDefault() {
-	if node.IsLocal == "" {
-		node.IsLocal = "no"
-	}
-}
-
 // Node.SetDNSOnDefault - sets dns on default
 func (node *LegacyNode) SetDNSOnDefault() {
 	if node.DNSOn == "" {
@@ -408,9 +399,6 @@ func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftable
 	if newNode.DNSOn != currentNode.DNSOn {
 		newNode.DNSOn = currentNode.DNSOn
 	}
-	if newNode.IsLocal != currentNode.IsLocal {
-		newNode.IsLocal = currentNode.IsLocal
-	}
 	if newNode.Action == "" {
 		newNode.Action = currentNode.Action
 	}
@@ -526,7 +514,6 @@ func (ln *LegacyNode) ConvertToNewNode() (*Host, *Node) {
 		}
 	}
 	node.Action = ln.Action
-	node.IsLocal = parseBool(ln.IsLocal)
 	node.IsEgressGateway = parseBool(ln.IsEgressGateway)
 	node.IsIngressGateway = parseBool(ln.IsIngressGateway)
 	node.DNSOn = parseBool(ln.DNSOn)
@@ -574,7 +561,6 @@ func (n *Node) Legacy(h *Host, s *ServerConfig, net *Network) *LegacyNode {
 	l.UDPHolePunch = formatBool(true)
 	l.DNSOn = formatBool(n.DNSOn)
 	l.Action = n.Action
-	l.IsLocal = formatBool(n.IsLocal)
 	l.IPForwarding = formatBool(h.IPForwarding)
 	l.OS = h.OS
 	l.MTU = int32(h.MTU)

+ 1 - 1
models/structs.go

@@ -205,7 +205,7 @@ type TrafficKeys struct {
 
 // NodeGet - struct for a single node get response
 type NodeGet struct {
-	Node         LegacyNode           `json:"node" bson:"node" yaml:"node"`
+	Node         Node                 `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"`

+ 154 - 0
mq/emqx.go

@@ -0,0 +1,154 @@
+package mq
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+
+	"github.com/gravitl/netmaker/servercfg"
+)
+
+type (
+	emqxUser struct {
+		UserID   string `json:"user_id"`
+		Password string `json:"password"`
+		Admin    bool   `json:"is_superuser"`
+	}
+
+	emqxLogin struct {
+		Username string `json:"username"`
+		Password string `json:"password"`
+	}
+
+	emqxLoginResponse struct {
+		License struct {
+			Edition string `json:"edition"`
+		} `json:"license"`
+		Token   string `json:"token"`
+		Version string `json:"version"`
+	}
+)
+
+func getEmqxAuthToken() (string, error) {
+	payload, err := json.Marshal(&emqxLogin{
+		Username: servercfg.GetMqUserName(),
+		Password: servercfg.GetMqPassword(),
+	})
+	if err != nil {
+		return "", err
+	}
+	resp, err := http.Post(servercfg.GetEmqxRestEndpoint()+"/api/v5/login", "application/json", bytes.NewReader(payload))
+	if err != nil {
+		return "", err
+	}
+	msg, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return "", err
+	}
+	if resp.StatusCode != http.StatusOK {
+		return "", fmt.Errorf("error during EMQX login %v", string(msg))
+	}
+	var loginResp emqxLoginResponse
+	if err := json.Unmarshal(msg, &loginResp); err != nil {
+		return "", err
+	}
+	return loginResp.Token, nil
+}
+
+// CreateEmqxUser - creates an EMQX user
+func CreateEmqxUser(username, password string, admin bool) error {
+	token, err := getEmqxAuthToken()
+	if err != nil {
+		return err
+	}
+	payload, err := json.Marshal(&emqxUser{
+		UserID:   username,
+		Password: password,
+		Admin:    admin,
+	})
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest(http.MethodPost, servercfg.GetEmqxRestEndpoint()+"/api/v5/authentication/password_based:built_in_database/users", bytes.NewReader(payload))
+	if err != nil {
+		return err
+	}
+	req.Header.Add("content-type", "application/json")
+	req.Header.Add("authorization", "Bearer "+token)
+	resp, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode >= 300 {
+		msg, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		return fmt.Errorf("error creating EMQX user %v", string(msg))
+	}
+	return nil
+}
+
+// DeleteEmqxUser - deletes an EMQX user
+func DeleteEmqxUser(username string) error {
+	token, err := getEmqxAuthToken()
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest(http.MethodDelete, servercfg.GetEmqxRestEndpoint()+"/api/v5/authentication/password_based:built_in_database/users/"+username, nil)
+	if err != nil {
+		return err
+	}
+	req.Header.Add("authorization", "Bearer "+token)
+	resp, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode >= 300 {
+		msg, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		return fmt.Errorf("error deleting EMQX user %v", string(msg))
+	}
+	return nil
+}
+
+// CreateEmqxDefaultAuthenticator - creates a default authenticator based on password and using EMQX's built in database as storage
+func CreateEmqxDefaultAuthenticator() error {
+	token, err := getEmqxAuthToken()
+	if err != nil {
+		return err
+	}
+	payload, err := json.Marshal(&struct {
+		Mechanism  string `json:"mechanism"`
+		Backend    string `json:"backend"`
+		UserIDType string `json:"user_id_type"`
+	}{Mechanism: "password_based", Backend: "built_in_database", UserIDType: "username"})
+	if err != nil {
+		return err
+	}
+	req, err := http.NewRequest(http.MethodPost, servercfg.GetEmqxRestEndpoint()+"/api/v5/authentication", bytes.NewReader(payload))
+	if err != nil {
+		return err
+	}
+	req.Header.Add("content-type", "application/json")
+	req.Header.Add("authorization", "Bearer "+token)
+	resp, err := (&http.Client{}).Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != http.StatusOK {
+		msg, err := io.ReadAll(resp.Body)
+		if err != nil {
+			return err
+		}
+		return fmt.Errorf("error creating default EMQX authenticator %v", string(msg))
+	}
+	return nil
+}

+ 23 - 8
mq/handlers.go

@@ -9,6 +9,7 @@ import (
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/logic/hostactions"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
@@ -144,6 +145,19 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 		logger.Log(3, fmt.Sprintf("recieved host update: %s\n", hostUpdate.Host.ID.String()))
 		var sendPeerUpdate bool
 		switch hostUpdate.Action {
+		case models.Acknowledgement:
+			hu := hostactions.GetAction(currentHost.ID.String())
+			if hu != nil {
+				if err = HostUpdate(hu); err != nil {
+					logger.Log(0, "failed to send new node to host", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
+					return
+				} else {
+					if err = PublishSingleHostPeerUpdate(currentHost, nil); err != nil {
+						logger.Log(0, "failed peers publish after join acknowledged", hostUpdate.Host.Name, currentHost.ID.String(), err.Error())
+						return
+					}
+				}
+			}
 		case models.UpdateHost:
 			sendPeerUpdate = logic.UpdateHostFromClient(&hostUpdate.Host, currentHost)
 			err := logic.UpsertHost(currentHost)
@@ -152,6 +166,13 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 				return
 			}
 		case models.DeleteHost:
+			if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
+				// delete EMQX credentials for host
+				if err := DeleteEmqxUser(currentHost.ID.String()); err != nil {
+					logger.Log(0, "failed to remove host credentials from EMQX: ", currentHost.ID.String(), err.Error())
+					return
+				}
+			}
 			if err := logic.DisassociateAllNodesFromHost(currentHost.ID.String()); err != nil {
 				logger.Log(0, "failed to delete all nodes of host: ", currentHost.ID.String(), err.Error())
 				return
@@ -162,12 +183,7 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) {
 			}
 			sendPeerUpdate = true
 		}
-		if sendPeerUpdate {
-			err := PublishPeerUpdate()
-			if err != nil {
-				logger.Log(0, "failed to pulish peer update: ", err.Error())
-			}
-		}
+
 		if sendPeerUpdate {
 			err := PublishPeerUpdate()
 			if err != nil {
@@ -233,11 +249,10 @@ func UpdateMetrics(client mqtt.Client, msg mqtt.Message) {
 				logger.Log(2, "updating peers after node", currentNode.ID.String(), currentNode.Network, "detected connectivity issues")
 				host, err := logic.GetHost(currentNode.HostID.String())
 				if err == nil {
-					if err = PublishSingleHostUpdate(host); err != nil {
+					if err = PublishSingleHostPeerUpdate(host, nil); err != nil {
 						logger.Log(0, "failed to publish update after failover peer change for node", currentNode.ID.String(), currentNode.Network)
 					}
 				}
-
 			}
 
 			logger.Log(1, "updated node metrics", id)

+ 17 - 0
mq/mq.go

@@ -2,6 +2,7 @@ package mq
 
 import (
 	"context"
+	"log"
 	"time"
 
 	mqtt "github.com/eclipse/paho.mqtt.golang"
@@ -38,6 +39,17 @@ func setMqOptions(user, password string, opts *mqtt.ClientOptions) {
 
 // SetupMQTT creates a connection to broker and return client
 func SetupMQTT() {
+	if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
+		time.Sleep(10 * time.Second) // wait for the REST endpoint to be ready
+		// setup authenticator and create admin user
+		if err := CreateEmqxDefaultAuthenticator(); err != nil {
+			logger.Log(0, err.Error())
+		}
+		DeleteEmqxUser(servercfg.GetMqUserName())
+		if err := CreateEmqxUser(servercfg.GetMqUserName(), servercfg.GetMqPassword(), true); err != nil {
+			log.Fatal(err)
+		}
+	}
 	opts := mqtt.NewClientOptions()
 	setMqOptions(servercfg.GetMqUserName(), servercfg.GetMqPassword(), opts)
 	opts.SetOnConnectHandler(func(client mqtt.Client) {
@@ -100,3 +112,8 @@ func Keepalive(ctx context.Context) {
 func IsConnected() bool {
 	return mqclient != nil && mqclient.IsConnected()
 }
+
+// CloseClient - function to close the mq connection from server
+func CloseClient() {
+	mqclient.Disconnect(250)
+}

+ 299 - 85
mq/publishers.go

@@ -25,7 +25,7 @@ func PublishPeerUpdate() error {
 	}
 	for _, host := range hosts {
 		host := host
-		err = PublishSingleHostUpdate(&host)
+		err = PublishSingleHostPeerUpdate(&host, nil)
 		if err != nil {
 			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
 		}
@@ -33,13 +33,37 @@ func PublishPeerUpdate() error {
 	return err
 }
 
-// PublishSingleHostUpdate --- determines and publishes a peer update to one host
-func PublishSingleHostUpdate(host *models.Host) error {
+// PublishDeletedNodePeerUpdate --- determines and publishes a peer update
+// to all the hosts with a deleted node to account for
+func PublishDeletedNodePeerUpdate(delNode *models.Node) error {
+	if !servercfg.IsMessageQueueBackend() {
+		return nil
+	}
+
+	hosts, err := logic.GetAllHosts()
+	if err != nil {
+		logger.Log(1, "err getting all hosts", err.Error())
+		return err
+	}
+	for _, host := range hosts {
+		host := host
+		if err = PublishSingleHostPeerUpdate(&host, delNode); err != nil {
+			logger.Log(1, "failed to publish peer update to host", host.ID.String(), ": ", err.Error())
+		}
+	}
+	return err
+}
+
+// PublishSingleHostPeerUpdate --- determines and publishes a peer update to one host
+func PublishSingleHostPeerUpdate(host *models.Host, deletedNode *models.Node) error {
 
-	peerUpdate, err := logic.GetPeerUpdateForHost(host)
+	peerUpdate, err := logic.GetPeerUpdateForHost("", host, deletedNode)
 	if err != nil {
 		return err
 	}
+	if len(peerUpdate.Peers) == 0 { // no peers to send
+		return nil
+	}
 	if host.ProxyEnabled {
 		proxyUpdate, err := logic.GetProxyUpdateForHost(host)
 		if err != nil {
@@ -56,13 +80,6 @@ func PublishSingleHostUpdate(host *models.Host) error {
 	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 {
-
-	go PublishPeerUpdate()
-	return nil
-}
-
 // NodeUpdate -- publishes a node update
 func NodeUpdate(node *models.Node) error {
 	host, err := logic.GetHost(node.HostID.String())
@@ -83,7 +100,7 @@ func NodeUpdate(node *models.Node) error {
 		logger.Log(2, "error marshalling node update ", err.Error())
 		return err
 	}
-	if err = publish(host, fmt.Sprintf("update/%s/%s", node.Network, node.ID), data); err != nil {
+	if err = publish(host, fmt.Sprintf("node/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
 	}
@@ -111,97 +128,195 @@ func HostUpdate(hostUpdate *models.HostUpdate) error {
 	return nil
 }
 
-// sendPeers - retrieve networks, send peer ports to all peers
-func sendPeers() {
-
-	hosts, err := logic.GetAllHosts()
+// ServerStartNotify - notifies all non server nodes to pull changes after a restart
+func ServerStartNotify() error {
+	nodes, err := logic.GetAllNodes()
 	if err != nil {
-		logger.Log(1, "error retrieving networks for keepalive", err.Error())
+		return err
+	}
+	for i := range nodes {
+		nodes[i].Action = models.NODE_FORCE_UPDATE
+		if err = NodeUpdate(&nodes[i]); err != nil {
+			logger.Log(1, "error when notifying node", nodes[i].ID.String(), "of a server startup")
+		}
 	}
+	return nil
+}
 
-	var force bool
-	peer_force_send++
-	if peer_force_send == 5 {
-		servercfg.SetHost()
-		force = true
-		peer_force_send = 0
-		err := logic.TimerCheckpoint() // run telemetry & log dumps if 24 hours has passed..
+// PublishDNSUpdate publishes a dns update to all nodes on a network
+func PublishDNSUpdate(network string, dns models.DNSUpdate) error {
+	nodes, err := logic.GetNetworkNodes(network)
+	if err != nil {
+		return err
+	}
+	for _, node := range nodes {
+		host, err := logic.GetHost(node.HostID.String())
 		if err != nil {
-			logger.Log(3, "error occurred on timer,", err.Error())
+			logger.Log(0, "error retrieving host for dns update", host.ID.String(), err.Error())
+			continue
+		}
+		data, err := json.Marshal(dns)
+		if err != nil {
+			logger.Log(0, "failed to encode dns data for node", node.ID.String(), err.Error())
 		}
+		if err := publish(host, "dns/update/"+host.ID.String()+"/"+servercfg.GetServer(), data); err != nil {
+			logger.Log(0, "error publishing dns update to host", host.ID.String(), err.Error())
+			continue
+		}
+		logger.Log(3, "published dns update to host", host.ID.String())
+	}
+	return nil
+}
 
-		//collectServerMetrics(networks[:])
+// PublishAllDNS publishes an array of dns updates (ip / host.network) for each peer to a node joining a network
+func PublishAllDNS(newnode *models.Node) error {
+	alldns := []models.DNSUpdate{}
+	newnodeHost, err := logic.GetHost(newnode.HostID.String())
+	if err != nil {
+		return fmt.Errorf("error retrieving host for dns update %w", err)
+	}
+	alldns = append(alldns, getNodeDNS(newnode.Network)...)
+	alldns = append(alldns, getExtClientDNS(newnode.Network)...)
+	alldns = append(alldns, getCustomDNS(newnode.Network)...)
+	data, err := json.Marshal(alldns)
+	if err != nil {
+		return fmt.Errorf("error encoding dns data %w", err)
 	}
+	if err := publish(newnodeHost, "dns/all/"+newnodeHost.ID.String()+"/"+servercfg.GetServer(), data); err != nil {
+		return fmt.Errorf("error publishing full dns update to %s, %w", newnodeHost.ID.String(), err)
+	}
+	logger.Log(3, "published full dns update to %s", newnodeHost.ID.String())
+	return nil
+}
 
-	for _, host := range hosts {
-		if force {
-			host := host
-			logger.Log(2, "sending scheduled peer update (5 min)")
-			err = PublishSingleHostUpdate(&host)
-			if err != nil {
-				logger.Log(1, "error publishing peer updates for host: ", host.ID.String(), " Err: ", err.Error())
-			}
+// PublishDNSDelete publish a dns update deleting a node to all hosts on a network
+func PublishDNSDelete(node *models.Node, host *models.Host) error {
+	dns := models.DNSUpdate{
+		Action: models.DNSDeleteByIP,
+		Name:   host.Name + "." + node.Network,
+	}
+	if node.Address.IP != nil {
+		dns.Address = node.Address.IP.String()
+		if err := PublishDNSUpdate(node.Network, dns); err != nil {
+			return fmt.Errorf("dns update node deletion %w", err)
 		}
 	}
+	if node.Address6.IP != nil {
+		dns.Address = node.Address6.IP.String()
+		if err := PublishDNSUpdate(node.Network, dns); err != nil {
+			return fmt.Errorf("dns update node deletion %w", err)
+		}
+	}
+	return nil
 }
 
-// ServerStartNotify - notifies all non server nodes to pull changes after a restart
-func ServerStartNotify() error {
-	nodes, err := logic.GetAllNodes()
-	if err != nil {
-		return err
+// PublishReplaceDNS publish a dns update to replace a dns entry on all hosts in network
+func PublishReplaceDNS(oldNode, newNode *models.Node, host *models.Host) error {
+	dns := models.DNSUpdate{
+		Action: models.DNSReplaceIP,
+		Name:   host.Name + "." + oldNode.Network,
 	}
-	for i := range nodes {
-		nodes[i].Action = models.NODE_FORCE_UPDATE
-		if err = NodeUpdate(&nodes[i]); err != nil {
-			logger.Log(1, "error when notifying node", nodes[i].ID.String(), "of a server startup")
+	if !oldNode.Address.IP.Equal(newNode.Address.IP) {
+		dns.Address = oldNode.Address.IP.String()
+		dns.NewAddress = newNode.Address.IP.String()
+		if err := PublishDNSUpdate(oldNode.Network, dns); err != nil {
+			return err
+		}
+	}
+	if !oldNode.Address6.IP.Equal(newNode.Address6.IP) {
+		dns.Address = oldNode.Address6.IP.String()
+		dns.NewAddress = newNode.Address6.IP.String()
+		if err := PublishDNSUpdate(oldNode.Network, dns); err != nil {
+			return err
 		}
 	}
 	return nil
 }
 
-// function to collect and store metrics for server nodes
-//func collectServerMetrics(networks []models.Network) {
-//	if !servercfg.Is_EE {
-//		return
-//	}
-//	if len(networks) > 0 {
-//		for i := range networks {
-//			currentNetworkNodes, err := logic.GetNetworkNodes(networks[i].NetID)
-//			if err != nil {
-//				continue
-//			}
-//			currentServerNodes := logic.GetServerNodes(networks[i].NetID)
-//			if len(currentServerNodes) > 0 {
-//				for i := range currentServerNodes {
-//					if logic.IsLocalServer(&currentServerNodes[i]) {
-//						serverMetrics := logic.CollectServerMetrics(currentServerNodes[i].ID, currentNetworkNodes)
-//						if serverMetrics != nil {
-//							serverMetrics.NodeName = currentServerNodes[i].Name
-//							serverMetrics.NodeID = currentServerNodes[i].ID
-//							serverMetrics.IsServer = "yes"
-//							serverMetrics.Network = currentServerNodes[i].Network
-//							if err = metrics.GetExchangedBytesForNode(&currentServerNodes[i], serverMetrics); err != nil {
-//								logger.Log(1, fmt.Sprintf("failed to update exchanged bytes info for server: %s, err: %v",
-//									currentServerNodes[i].Name, err))
-//							}
-//							updateNodeMetrics(&currentServerNodes[i], serverMetrics)
-//							if err = logic.UpdateMetrics(currentServerNodes[i].ID, serverMetrics); err != nil {
-//								logger.Log(1, "failed to update metrics for server node", currentServerNodes[i].ID)
-//							}
-//							if servercfg.IsMetricsExporter() {
-//								logger.Log(2, "-------------> SERVER METRICS: ", fmt.Sprintf("%+v", serverMetrics))
-//								if err := pushMetricsToExporter(*serverMetrics); err != nil {
-//									logger.Log(2, "failed to push server metrics to exporter: ", err.Error())
-//								}
-//							}
-//						}
-//					}
-//				}
-//			}
-//		}
-//	}
-//}
+// PublishExtClientDNS publish dns update for new extclient
+func PublishExtCLientDNS(client *models.ExtClient) error {
+	errMsgs := models.DNSError{}
+	dns := models.DNSUpdate{
+		Action:  models.DNSInsert,
+		Name:    client.ClientID + "." + client.Network,
+		Address: client.Address,
+	}
+	if client.Address != "" {
+		dns.Address = client.Address
+		if err := PublishDNSUpdate(client.Network, dns); err != nil {
+			errMsgs.ErrorStrings = append(errMsgs.ErrorStrings, err.Error())
+		}
+
+	}
+	if client.Address6 != "" {
+		dns.Address = client.Address6
+		if err := PublishDNSUpdate(client.Network, dns); err != nil {
+			errMsgs.ErrorStrings = append(errMsgs.ErrorStrings, err.Error())
+		}
+	}
+	if len(errMsgs.ErrorStrings) > 0 {
+		return errMsgs
+	}
+	return nil
+}
+
+// PublishExtClientDNSUpdate update for extclient name change
+func PublishExtClientDNSUpdate(old, new models.ExtClient, network string) error {
+	dns := models.DNSUpdate{
+		Action:  models.DNSReplaceName,
+		Name:    old.ClientID + "." + network,
+		NewName: new.ClientID + "." + network,
+	}
+	if err := PublishDNSUpdate(network, dns); err != nil {
+		return err
+	}
+	return nil
+}
+
+// PublishDeleteExtClientDNS publish dns update to delete extclient entry
+func PublishDeleteExtClientDNS(client *models.ExtClient) error {
+	dns := models.DNSUpdate{
+		Action: models.DNSDeleteByName,
+		Name:   client.ClientID + "." + client.Network,
+	}
+	if err := PublishDNSUpdate(client.Network, dns); err != nil {
+		return err
+	}
+	return nil
+}
+
+// PublishCustomDNS publish dns update for new custom dns entry
+func PublishCustomDNS(entry *models.DNSEntry) error {
+	dns := models.DNSUpdate{
+		Action: models.DNSInsert,
+		Name:   entry.Name + "." + entry.Network,
+		//entry.Address6 is never used
+		Address: entry.Address,
+	}
+	if err := PublishDNSUpdate(entry.Network, dns); err != nil {
+		return err
+	}
+	return nil
+}
+
+// PublishHostDNSUpdate publishes dns update on host name change
+func PublishHostDNSUpdate(old, new *models.Host, networks []string) error {
+	errMsgs := models.DNSError{}
+	for _, network := range networks {
+		dns := models.DNSUpdate{
+			Action:  models.DNSReplaceName,
+			Name:    old.Name + "." + network,
+			NewName: new.Name + "." + network,
+		}
+		if err := PublishDNSUpdate(network, dns); err != nil {
+			errMsgs.ErrorStrings = append(errMsgs.ErrorStrings, err.Error())
+		}
+	}
+	if len(errMsgs.ErrorStrings) > 0 {
+		return errMsgs
+	}
+	return nil
+}
 
 func pushMetricsToExporter(metrics models.Metrics) error {
 	logger.Log(2, "----> Pushing metrics to exporter")
@@ -220,3 +335,102 @@ func pushMetricsToExporter(metrics models.Metrics) error {
 	}
 	return nil
 }
+
+func getNodeDNS(network string) []models.DNSUpdate {
+	alldns := []models.DNSUpdate{}
+	dns := models.DNSUpdate{}
+	nodes, err := logic.GetNetworkNodes(network)
+	if err != nil {
+		logger.Log(0, "error retreiving network nodes for network", network, err.Error())
+	}
+	for _, node := range nodes {
+		host, err := logic.GetHost(node.HostID.String())
+		if err != nil {
+			logger.Log(0, "error retrieving host for dns update", host.ID.String(), err.Error())
+			continue
+		}
+		dns.Action = models.DNSInsert
+		dns.Name = host.Name + "." + node.Network
+		if node.Address.IP != nil {
+			dns.Address = node.Address.IP.String()
+			alldns = append(alldns, dns)
+		}
+		if node.Address6.IP != nil {
+			dns.Address = node.Address6.IP.String()
+			alldns = append(alldns, dns)
+		}
+	}
+	return alldns
+}
+
+func getExtClientDNS(network string) []models.DNSUpdate {
+	alldns := []models.DNSUpdate{}
+	dns := models.DNSUpdate{}
+	clients, err := logic.GetNetworkExtClients(network)
+	if err != nil {
+		logger.Log(0, "error retrieving extclients", err.Error())
+	}
+	for _, client := range clients {
+		dns.Action = models.DNSInsert
+		dns.Name = client.ClientID + "." + client.Network
+		if client.Address != "" {
+			dns.Address = client.Address
+			alldns = append(alldns, dns)
+		}
+		if client.Address6 != "" {
+			dns.Address = client.Address
+			alldns = append(alldns, dns)
+		}
+	}
+	return alldns
+}
+
+func getCustomDNS(network string) []models.DNSUpdate {
+	alldns := []models.DNSUpdate{}
+	dns := models.DNSUpdate{}
+	customdns, err := logic.GetCustomDNS(network)
+	if err != nil {
+		logger.Log(0, "error retrieving custom dns entries", err.Error())
+	}
+	for _, custom := range customdns {
+		dns.Action = models.DNSInsert
+		dns.Address = custom.Address
+		dns.Name = custom.Name + "." + custom.Network
+		alldns = append(alldns, dns)
+	}
+	return alldns
+}
+
+// sendPeers - retrieve networks, send peer ports to all peers
+func sendPeers() {
+
+	hosts, err := logic.GetAllHosts()
+	if err != nil && len(hosts) > 0 {
+		logger.Log(1, "error retrieving networks for keepalive", err.Error())
+	}
+
+	var force bool
+	peer_force_send++
+	if peer_force_send == 5 {
+		servercfg.SetHost()
+		force = true
+		peer_force_send = 0
+		err := logic.TimerCheckpoint() // run telemetry & log dumps if 24 hours has passed..
+		if err != nil {
+			logger.Log(3, "error occurred on timer,", err.Error())
+		}
+
+		//collectServerMetrics(networks[:])
+	}
+
+	for _, host := range hosts {
+		if force {
+			host := host
+			logger.Log(2, "sending scheduled peer update (5 min)")
+			err = PublishSingleHostPeerUpdate(&host, nil)
+			if err != nil {
+				logger.Log(1, "error publishing peer updates for host: ", host.ID.String(), " Err: ", err.Error())
+			}
+		}
+	}
+}

+ 0 - 9
netclient/global_settings/globalsettings.go

@@ -1,9 +0,0 @@
-package global_settings
-
-// globalsettings - settings that are global in nature.  Avoids circular dependencies between config loading and usage.
-
-// PublicIPServices - the list of user-specified IP services to use to obtain the node's public IP
-var PublicIPServices map[string]string = make(map[string]string)
-
-// User - holds a user string for joins when using basic auth
-var User string

+ 0 - 78
netclient/ncutils/iface.go

@@ -2,62 +2,8 @@ package ncutils
 
 import (
 	"net"
-
-	"github.com/gravitl/netmaker/models"
 )
 
-// IfaceDelta - checks if the new node causes an interface change
-func IfaceDelta(currentNode *models.LegacyNode, newNode *models.LegacyNode) bool {
-	// single comparison statements
-	if newNode.Endpoint != currentNode.Endpoint ||
-		newNode.PublicKey != currentNode.PublicKey ||
-		newNode.Address != currentNode.Address ||
-		newNode.Address6 != currentNode.Address6 ||
-		newNode.IsEgressGateway != currentNode.IsEgressGateway ||
-		newNode.IsIngressGateway != currentNode.IsIngressGateway ||
-		newNode.IsRelay != currentNode.IsRelay ||
-		newNode.ListenPort != currentNode.ListenPort ||
-		newNode.UDPHolePunch != currentNode.UDPHolePunch ||
-		newNode.MTU != currentNode.MTU ||
-		newNode.IsPending != currentNode.IsPending ||
-		newNode.PersistentKeepalive != currentNode.PersistentKeepalive ||
-		newNode.DNSOn != currentNode.DNSOn ||
-		newNode.Connected != currentNode.Connected ||
-		len(newNode.AllowedIPs) != len(currentNode.AllowedIPs) {
-		return true
-	}
-
-	// multi-comparison statements
-	if newNode.IsEgressGateway == "yes" {
-		if len(currentNode.EgressGatewayRanges) != len(newNode.EgressGatewayRanges) {
-			return true
-		}
-		for _, address := range newNode.EgressGatewayRanges {
-			if !StringSliceContains(currentNode.EgressGatewayRanges, address) {
-				return true
-			}
-		}
-	}
-
-	if newNode.IsRelay == "yes" {
-		if len(currentNode.RelayAddrs) != len(newNode.RelayAddrs) {
-			return true
-		}
-		for _, address := range newNode.RelayAddrs {
-			if !StringSliceContains(currentNode.RelayAddrs, address) {
-				return true
-			}
-		}
-	}
-
-	for _, address := range newNode.AllowedIPs {
-		if !StringSliceContains(currentNode.AllowedIPs, address) {
-			return true
-		}
-	}
-	return false
-}
-
 // StringSliceContains - sees if a string slice contains a string element
 func StringSliceContains(slice []string, item string) bool {
 	for _, s := range slice {
@@ -68,30 +14,6 @@ func StringSliceContains(slice []string, item string) bool {
 	return false
 }
 
-// IPNetSliceContains - sees if a string slice contains a string element
-func IPNetSliceContains(slice []net.IPNet, item net.IPNet) bool {
-	for _, s := range slice {
-		if s.String() == item.String() {
-			return true
-		}
-	}
-	return false
-}
-
-// IfaceExists - return true if you can find the iface
-func IfaceExists(ifacename string) bool {
-	localnets, err := net.Interfaces()
-	if err != nil {
-		return false
-	}
-	for _, localnet := range localnets {
-		if ifacename == localnet.Name {
-			return true
-		}
-	}
-	return false
-}
-
 func IpIsPrivate(ipnet net.IP) bool {
 	return ipnet.IsPrivate() || ipnet.IsLoopback()
 }

+ 0 - 593
netclient/ncutils/netclientutils.go

@@ -4,559 +4,13 @@ import (
 	"bytes"
 	"crypto/rand"
 	"encoding/gob"
-	"errors"
-	"fmt"
-	"io"
-	"log"
-	"net"
-	"net/http"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"regexp"
-	"runtime"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/c-robinson/iplib"
-
-	"github.com/gravitl/netmaker/logger"
-	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/netclient/global_settings"
-)
-
-var (
-	// Version - version of the netclient
-	Version = "dev"
 )
 
-// MAX_NAME_LENGTH - maximum node name length
-const MAX_NAME_LENGTH = 62
-
-// NO_DB_RECORD - error message result
-const NO_DB_RECORD = "no result found"
-
-// NO_DB_RECORDS - error record result
-const NO_DB_RECORDS = "could not find any records"
-
-// LINUX_APP_DATA_PATH - linux path
-const LINUX_APP_DATA_PATH = "/etc/netclient"
-
-// MAC_APP_DATA_PATH - mac path
-const MAC_APP_DATA_PATH = "/Applications/Netclient"
-
-// WINDOWS_APP_DATA_PATH - windows path
-const WINDOWS_APP_DATA_PATH = "C:\\Program Files (x86)\\Netclient"
-
-// WINDOWS_SVC_NAME - service name
-const WINDOWS_SVC_NAME = "netclient"
-
-// NETCLIENT_DEFAULT_PORT - default port
-const NETCLIENT_DEFAULT_PORT = 51821
-
 // DEFAULT_GC_PERCENT - garbage collection percent
 const DEFAULT_GC_PERCENT = 10
 
-// KEY_SIZE = ideal length for keys
-const KEY_SIZE = 2048
-
-// SetVersion -- set netclient version for use by other packages
-func SetVersion(ver string) {
-	Version = ver
-}
-
-// IsWindows - checks if is windows
-func IsWindows() bool {
-	return runtime.GOOS == "windows"
-}
-
-// IsMac - checks if is a mac
-func IsMac() bool {
-	return runtime.GOOS == "darwin"
-}
-
-// IsLinux - checks if is linux
-func IsLinux() bool {
-	return runtime.GOOS == "linux"
-}
-
-// IsFreeBSD - checks if is freebsd
-func IsFreeBSD() bool {
-	return runtime.GOOS == "freebsd"
-}
-
-// HasWGQuick - checks if WGQuick command is present
-func HasWgQuick() bool {
-	cmd, err := exec.LookPath("wg-quick")
-	return err == nil && cmd != ""
-}
-
-// GetWireGuard - checks if wg is installed
-func GetWireGuard() string {
-	userspace := os.Getenv("WG_QUICK_USERSPACE_IMPLEMENTATION")
-	if userspace != "" && (userspace == "boringtun" || userspace == "wireguard-go") {
-		return userspace
-	}
-	return "wg"
-}
-
-// IsNFTablesPresent - returns true if nftables is present, false otherwise.
-// Does not consider OS, up to the caller to determine if the OS supports nftables/whether this check is valid.
-func IsNFTablesPresent() bool {
-	found := false
-	_, err := exec.LookPath("nft")
-	if err == nil {
-		found = true
-	}
-	return found
-}
-
-// IsIPTablesPresent - returns true if iptables is present, false otherwise
-// Does not consider OS, up to the caller to determine if the OS supports iptables/whether this check is valid.
-func IsIPTablesPresent() bool {
-	found := false
-	_, err := exec.LookPath("iptables")
-	if err == nil {
-		found = true
-	}
-	return found
-}
-
-// IsKernel - checks if running kernel WireGuard
-func IsKernel() bool {
-	// TODO
-	// Replace && true with some config file value
-	// This value should be something like kernelmode, which should be 'on' by default.
-	return IsLinux() && os.Getenv("WG_QUICK_USERSPACE_IMPLEMENTATION") == ""
-}
-
-// IsEmptyRecord - repeat from database
-func IsEmptyRecord(err error) bool {
-	if err == nil {
-		return false
-	}
-	return strings.Contains(err.Error(), NO_DB_RECORD) || strings.Contains(err.Error(), NO_DB_RECORDS)
-}
-
-// GetPublicIP - gets public ip
-func GetPublicIP(api string) (string, error) {
-
-	iplist := []string{"https://ip.client.gravitl.com", "https://ifconfig.me", "https://api.ipify.org", "https://ipinfo.io/ip"}
-
-	for network, ipService := range global_settings.PublicIPServices {
-		logger.Log(3, "User provided public IP service defined for network", network, "is", ipService)
-
-		// prepend the user-specified service so it's checked first
-		iplist = append([]string{ipService}, iplist...)
-	}
-	if api != "" {
-		api = "https://" + api + "/api/getip"
-		iplist = append([]string{api}, iplist...)
-	}
-
-	endpoint := ""
-	var err error
-	for _, ipserver := range iplist {
-		client := &http.Client{
-			Timeout: time.Second * 10,
-		}
-		var resp *http.Response
-		resp, err = client.Get(ipserver)
-		if err != nil {
-			continue
-		}
-		defer resp.Body.Close()
-		if resp.StatusCode == http.StatusOK {
-			var bodyBytes []byte
-			bodyBytes, err = io.ReadAll(resp.Body)
-			if err != nil {
-				continue
-			}
-			endpoint = string(bodyBytes)
-			break
-		}
-	}
-	if err == nil && endpoint == "" {
-		err = errors.New("public address not found")
-	}
-	return endpoint, err
-}
-
-// GetMacAddr - get's mac address
-func GetMacAddr() ([]string, error) {
-	ifas, err := net.Interfaces()
-	if err != nil {
-		return nil, err
-	}
-	var as []string
-	for _, ifa := range ifas {
-		a := ifa.HardwareAddr.String()
-		if a != "" {
-			as = append(as, a)
-		}
-	}
-	return as, nil
-}
-
-// GetLocalIP - gets local ip of machine
-func GetLocalIP(localrange string) (string, error) {
-	_, localRange, err := net.ParseCIDR(localrange)
-	if err != nil {
-		return "", err
-	}
-	ifaces, err := net.Interfaces()
-	if err != nil {
-		return "", err
-	}
-	var local string
-	found := false
-	for _, i := range ifaces {
-		if i.Flags&net.FlagUp == 0 {
-			continue // interface down
-		}
-		if i.Flags&net.FlagLoopback != 0 {
-			continue // loopback interface
-		}
-		addrs, err := i.Addrs()
-		if err != nil {
-			return "", err
-		}
-		for _, addr := range addrs {
-			var ip net.IP
-			switch v := addr.(type) {
-			case *net.IPNet:
-				if !found {
-					ip = v.IP
-					local = ip.String()
-					found = localRange.Contains(ip)
-				}
-			case *net.IPAddr:
-				if !found {
-					ip = v.IP
-					local = ip.String()
-					found = localRange.Contains(ip)
-				}
-			}
-		}
-	}
-	if !found || local == "" {
-		return "", errors.New("Failed to find local IP in range " + localrange)
-	}
-	return local, nil
-}
-
-// GetNetworkIPMask - Pulls the netmask out of the network
-func GetNetworkIPMask(networkstring string) (string, string, error) {
-	ip, ipnet, err := net.ParseCIDR(networkstring)
-	if err != nil {
-		return "", "", err
-	}
-	ipstring := ip.String()
-	mask := ipnet.Mask
-	maskstring := fmt.Sprintf("%d.%d.%d.%d", mask[0], mask[1], mask[2], mask[3])
-	// maskstring := ipnet.Mask.String()
-	return ipstring, maskstring, err
-}
-
-// GetFreePort - gets free port of machine
-func GetFreePort(rangestart int32) (int32, error) {
-	addr := net.UDPAddr{}
-	if rangestart == 0 {
-		rangestart = NETCLIENT_DEFAULT_PORT
-	}
-	for x := rangestart; x <= 65535; x++ {
-		addr.Port = int(x)
-		conn, err := net.ListenUDP("udp", &addr)
-		if err != nil {
-			continue
-		}
-		defer conn.Close()
-		return x, nil
-	}
-	return rangestart, errors.New("no free ports")
-}
-
 // == OS PATH FUNCTIONS ==
 
-// GetHomeDirWindows - gets home directory in windows
-func GetHomeDirWindows() string {
-	if IsWindows() {
-		home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
-		if home == "" {
-			home = os.Getenv("USERPROFILE")
-		}
-		return home
-	}
-	return os.Getenv("HOME")
-}
-
-// GetNetclientPath - gets netclient path locally
-func GetNetclientPath() string {
-	if IsWindows() {
-		return WINDOWS_APP_DATA_PATH
-	} else if IsMac() {
-		return MAC_APP_DATA_PATH
-	} else {
-		return LINUX_APP_DATA_PATH
-	}
-}
-
-// GetSeparator - gets the separator for OS
-func GetSeparator() string {
-	if IsWindows() {
-		return "\\"
-	} else {
-		return "/"
-	}
-}
-
-// GetFileWithRetry - retry getting file X number of times before failing
-func GetFileWithRetry(path string, retryCount int) ([]byte, error) {
-	var data []byte
-	var err error
-	for count := 0; count < retryCount; count++ {
-		data, err = os.ReadFile(path)
-		if err == nil {
-			return data, err
-		} else {
-			logger.Log(1, "failed to retrieve file ", path, ", retrying...")
-			time.Sleep(time.Second >> 2)
-		}
-	}
-	return data, err
-}
-
-// GetNetclientServerPath - gets netclient server path
-func GetNetclientServerPath(server string) string {
-	if IsWindows() {
-		return WINDOWS_APP_DATA_PATH + "\\" + server + "\\"
-	} else if IsMac() {
-		return MAC_APP_DATA_PATH + "/" + server + "/"
-	} else {
-		return LINUX_APP_DATA_PATH + "/" + server
-	}
-}
-
-// GetNetclientPathSpecific - gets specific netclient config path
-func GetNetclientPathSpecific() string {
-	if IsWindows() {
-		return WINDOWS_APP_DATA_PATH + "\\"
-	} else if IsMac() {
-		return MAC_APP_DATA_PATH + "/config/"
-	} else {
-		return LINUX_APP_DATA_PATH + "/config/"
-	}
-}
-
-func CheckIPAddress(ip string) error {
-	if net.ParseIP(ip) == nil {
-		return fmt.Errorf("ip address %s is invalid", ip)
-	}
-	return nil
-}
-
-// GetNewIface - Gets the name of the real interface created on Mac
-func GetNewIface(dir string) (string, error) {
-	files, _ := os.ReadDir(dir)
-	var newestFile string
-	var newestTime int64 = 0
-	var err error
-	for _, f := range files {
-		fi, err := os.Stat(dir + f.Name())
-		if err != nil {
-			return "", err
-		}
-		currTime := fi.ModTime().Unix()
-		if currTime > newestTime && strings.Contains(f.Name(), ".sock") {
-			newestTime = currTime
-			newestFile = f.Name()
-		}
-	}
-	resultArr := strings.Split(newestFile, ".")
-	if resultArr[0] == "" {
-		err = errors.New("sock file does not exist")
-	}
-	return resultArr[0], err
-}
-
-// GetFileAsString - returns the string contents of a given file
-func GetFileAsString(path string) (string, error) {
-	content, err := os.ReadFile(path)
-	if err != nil {
-		return "", err
-	}
-	return string(content), err
-}
-
-// GetNetclientPathSpecific - gets specific netclient config path
-func GetWGPathSpecific() string {
-	if IsWindows() {
-		return WINDOWS_APP_DATA_PATH + "\\"
-	} else {
-		return "/etc/wireguard/"
-	}
-}
-
-// Copy - copies a src file to dest
-func Copy(src, dst string) error {
-	sourceFileStat, err := os.Stat(src)
-	if err != nil {
-		return err
-	}
-
-	if !sourceFileStat.Mode().IsRegular() {
-		return errors.New(src + " is not a regular file")
-	}
-
-	source, err := os.Open(src)
-	if err != nil {
-		return err
-	}
-	defer source.Close()
-
-	destination, err := os.Create(dst)
-	if err != nil {
-		return err
-	}
-	defer destination.Close()
-	_, err = io.Copy(destination, source)
-	if err != nil {
-		return err
-	}
-	err = os.Chmod(dst, 0755)
-
-	return err
-}
-
-// RunsCmds - runs cmds
-func RunCmds(commands []string, printerr bool) error {
-	var err error
-	for _, command := range commands {
-		// prevent panic
-		if len(strings.Trim(command, " ")) == 0 {
-			continue
-		}
-		args := strings.Fields(command)
-		out, err := exec.Command(args[0], args[1:]...).CombinedOutput()
-		if err != nil && printerr {
-			logger.Log(0, "error running command:", command)
-			logger.Log(0, strings.TrimSuffix(string(out), "\n"))
-		}
-	}
-	return err
-}
-
-// FileExists - checks if file exists locally
-func FileExists(f string) bool {
-	info, err := os.Stat(f)
-	if os.IsNotExist(err) {
-		return false
-	}
-	if err != nil && strings.Contains(err.Error(), "not a directory") {
-		return false
-	}
-	if err != nil {
-		logger.Log(0, "error reading file: "+f+", "+err.Error())
-	}
-	return !info.IsDir()
-}
-
-// GetSystemNetworks - get networks locally
-func GetSystemNetworks() ([]string, error) {
-	var networks []string
-	files, err := filepath.Glob(GetNetclientPathSpecific() + "netconfig-*")
-	if err != nil {
-		return nil, err
-	}
-	for _, file := range files {
-		// don't want files such as *.bak, *.swp
-		if filepath.Ext(file) != "" {
-			continue
-		}
-		file := filepath.Base(file)
-		temp := strings.Split(file, "-")
-		networks = append(networks, strings.Join(temp[1:], "-"))
-	}
-	return networks, nil
-}
-
-// ShortenString - Brings string down to specified length. Stops names from being too long
-func ShortenString(input string, length int) string {
-	output := input
-	if len(input) > length {
-		output = input[0:length]
-	}
-	return output
-}
-
-// DNSFormatString - Formats a string with correct usage for DNS
-func DNSFormatString(input string) string {
-	reg, err := regexp.Compile("[^a-zA-Z0-9-]+")
-	if err != nil {
-		logger.Log(0, "error with regex: "+err.Error())
-		return ""
-	}
-	return reg.ReplaceAllString(input, "")
-}
-
-// GetHostname - Gets hostname of machine
-func GetHostname() string {
-	hostname, err := os.Hostname()
-	if err != nil {
-		return ""
-	}
-	if len(hostname) > MAX_NAME_LENGTH {
-		hostname = hostname[0:MAX_NAME_LENGTH]
-	}
-	return hostname
-}
-
-// CheckUID - Checks to make sure user has root privileges
-func CheckUID() {
-	// start our application
-	out, err := RunCmd("id -u", true)
-
-	if err != nil {
-		log.Fatal(out, err)
-	}
-	id, err := strconv.Atoi(string(out[:len(out)-1]))
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	if id != 0 {
-		log.Fatal("This program must be run with elevated privileges (sudo). This program installs a SystemD service and configures WireGuard and networking rules. Please re-run with sudo/root.")
-	}
-}
-
-// CheckFirewall - checks if iptables of nft install, if not exit
-func CheckFirewall() {
-	if !IsIPTablesPresent() && !IsNFTablesPresent() {
-		log.Fatal("neither iptables nor nft is installed - please install one or the other and try again")
-	}
-}
-
-// CheckWG - Checks if WireGuard is installed. If not, exit
-func CheckWG() {
-	uspace := GetWireGuard()
-	if !HasWG() {
-		if uspace == "wg" {
-			log.Fatal("WireGuard not installed. Please install WireGuard (wireguard-tools) and try again.")
-		}
-		logger.Log(0, "running with userspace wireguard: ", uspace)
-	} else if uspace != "wg" {
-		logger.Log(0, "running userspace WireGuard with ", uspace)
-	}
-}
-
-// HasWG - returns true if wg command exists
-func HasWG() bool {
-	var _, err = exec.LookPath("wg")
-	return err == nil
-}
-
 // ConvertKeyToBytes - util to convert a key to bytes to use elsewhere
 func ConvertKeyToBytes(key *[32]byte) ([]byte, error) {
 	var buffer bytes.Buffer
@@ -579,16 +33,6 @@ func ConvertBytesToKey(data []byte) (*[32]byte, error) {
 	return result, err
 }
 
-// ServerAddrSliceContains - sees if a string slice contains a string element
-func ServerAddrSliceContains(slice []models.ServerAddr, item models.ServerAddr) bool {
-	for _, s := range slice {
-		if s.Address == item.Address && s.IsLeader == item.IsLeader {
-			return true
-		}
-	}
-	return false
-}
-
 // MakeRandomString - generates a random string of len n
 func MakeRandomString(n int) string {
 	const validChars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
@@ -601,40 +45,3 @@ func MakeRandomString(n int) string {
 	}
 	return string(result)
 }
-
-func GetIPNetFromString(ip string) (net.IPNet, error) {
-	var ipnet *net.IPNet
-	var err error
-	// parsing as a CIDR first. If valid CIDR, append
-	if _, cidr, err := net.ParseCIDR(ip); err == nil {
-		ipnet = cidr
-	} else { // parsing as an IP second. If valid IP, check if ipv4 or ipv6, then append
-		if iplib.Version(net.ParseIP(ip)) == 4 {
-			ipnet = &net.IPNet{
-				IP:   net.ParseIP(ip),
-				Mask: net.CIDRMask(32, 32),
-			}
-		} else if iplib.Version(net.ParseIP(ip)) == 6 {
-			ipnet = &net.IPNet{
-				IP:   net.ParseIP(ip),
-				Mask: net.CIDRMask(128, 128),
-			}
-		}
-	}
-	if ipnet == nil {
-		err = errors.New(ip + " is not a valid ip or cidr")
-		return net.IPNet{}, err
-	}
-	return *ipnet, err
-}
-
-// ModPort - Change Node Port if UDP Hole Punching or ListenPort is not free
-func ModPort(node *models.LegacyNode) error {
-	var err error
-	if node.UDPHolePunch == "yes" {
-		node.ListenPort = 0
-	} else {
-		node.ListenPort, err = GetFreePort(node.ListenPort)
-	}
-	return err
-}

+ 0 - 39
netclient/ncutils/netclientutils_darwin.go

@@ -1,39 +0,0 @@
-package ncutils
-
-import (
-	"os/exec"
-	"strings"
-
-	"github.com/gravitl/netmaker/logger"
-)
-
-// WHITESPACE_PLACEHOLDER - used with RunCMD - if a path has whitespace, use this to avoid running path as 2 args in RunCMD
-const WHITESPACE_PLACEHOLDER = "+-+-+-+"
-
-// RunCmd - runs a local command
-func RunCmd(command string, printerr bool) (string, error) {
-
-	args := strings.Fields(command)
-	// return whitespace after split
-	for i, arg := range args {
-		args[i] = strings.Replace(arg, WHITESPACE_PLACEHOLDER, " ", -1)
-	}
-	cmd := exec.Command(args[0], args[1:]...)
-	cmd.Wait()
-	out, err := cmd.CombinedOutput()
-	if err != nil && printerr {
-		logger.Log(0, "error running command:", strings.Join(args, " "))
-		logger.Log(0, strings.TrimSuffix(string(out), "\n"))
-	}
-	return string(out), err
-}
-
-// RunCmdFormatted - run a command formatted for MacOS
-func RunCmdFormatted(command string, printerr bool) (string, error) {
-	return "", nil
-}
-
-// GetEmbedded - if files required for MacOS, put here
-func GetEmbedded() error {
-	return nil
-}

+ 0 - 50
netclient/ncutils/netclientutils_freebsd.go

@@ -1,50 +0,0 @@
-package ncutils
-
-import (
-	"context"
-	"os/exec"
-	"strings"
-	"syscall"
-	"time"
-
-	"github.com/gravitl/netmaker/logger"
-)
-
-// RunCmdFormatted - run a command formatted for freebsd
-func RunCmdFormatted(command string, printerr bool) (string, error) {
-
-	args := strings.Fields(command)
-	cmd := exec.Command(args[0], args[1:]...)
-	cmd.Start()
-	cmd.Wait()
-	out, err := cmd.CombinedOutput()
-	if err != nil && printerr {
-		logger.Log(0, "error running command: ", command)
-		logger.Log(0, strings.TrimSuffix(string(out), "\n"))
-	}
-	return string(out), err
-}
-
-// GetEmbedded - if files required for freebsd, put here
-func GetEmbedded() error {
-	return nil
-}
-
-// Runs Commands for FreeBSD
-func RunCmd(command string, printerr bool) (string, error) {
-	args := strings.Fields(command)
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
-	defer cancel()
-	cmd := exec.Command(args[0], args[1:]...)
-	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
-	go func() {
-		<-ctx.Done()
-		_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
-	}()
-	out, err := cmd.CombinedOutput()
-	if err != nil && printerr {
-		logger.Log(0, "error running command:", command)
-		logger.Log(0, strings.TrimSuffix(string(out), "\n"))
-	}
-	return string(out), err
-}

+ 0 - 32
netclient/ncutils/netclientutils_linux.go

@@ -1,32 +0,0 @@
-package ncutils
-
-import (
-	"fmt"
-	"os/exec"
-	"strings"
-
-	"github.com/gravitl/netmaker/logger"
-)
-
-// RunCmd - runs a local command
-func RunCmd(command string, printerr bool) (string, error) {
-	args := strings.Fields(command)
-	cmd := exec.Command(args[0], args[1:]...)
-	cmd.Wait()
-	out, err := cmd.CombinedOutput()
-	if err != nil && printerr {
-		logger.Log(0, fmt.Sprintf("error running command: %s", command))
-		logger.Log(0, strings.TrimSuffix(string(out), "\n"))
-	}
-	return string(out), err
-}
-
-// RunCmdFormatted - does nothing for linux
-func RunCmdFormatted(command string, printerr bool) (string, error) {
-	return "", nil
-}
-
-// GetEmbedded - if files required for linux, put here
-func GetEmbedded() error {
-	return nil
-}

+ 0 - 61
netclient/ncutils/netclientutils_windows.go

@@ -1,61 +0,0 @@
-package ncutils
-
-import (
-	"embed"
-	"fmt"
-	"os"
-	"os/exec"
-	"strings"
-	"syscall"
-
-	"github.com/gravitl/netmaker/logger"
-)
-
-//go:embed windowsdaemon/winsw.exe
-var winswContent embed.FS
-
-// RunCmd - runs a local command
-func RunCmd(command string, printerr bool) (string, error) {
-	args := strings.Fields(command)
-	cmd := exec.Command(args[0], args[1:]...)
-	cmd.Wait()
-	//cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: "/C \"" + command + "\""}
-	out, err := cmd.CombinedOutput()
-	if err != nil && printerr {
-		logger.Log(0, "error running command:", command)
-		logger.Log(0, strings.TrimSuffix(string(out), "\n"))
-	}
-	return string(out), err
-}
-
-// RunCmd - runs a local command
-func RunCmdFormatted(command string, printerr bool) (string, error) {
-	var comSpec = os.Getenv("COMSPEC")
-	if comSpec == "" {
-		comSpec = os.Getenv("SystemRoot") + "\\System32\\cmd.exe"
-	}
-	cmd := exec.Command(comSpec)
-	cmd.SysProcAttr = &syscall.SysProcAttr{CmdLine: "/C \"" + command + "\""}
-	cmd.Wait()
-	out, err := cmd.CombinedOutput()
-	if err != nil && printerr {
-		logger.Log(0, "error running command:", command)
-		logger.Log(0, strings.TrimSuffix(string(out), "\n"))
-	}
-	return string(out), err
-}
-
-// GetEmbedded - Gets the Windows daemon creator
-func GetEmbedded() error {
-	data, err := winswContent.ReadFile("windowsdaemon/winsw.exe")
-	if err != nil {
-		return err
-	}
-	fileName := fmt.Sprintf("%swinsw.exe", GetNetclientPathSpecific())
-	err = os.WriteFile(fileName, data, 0700)
-	if err != nil {
-		logger.Log(0, "could not mount winsw.exe")
-		return err
-	}
-	return nil
-}

+ 0 - 97
netclient/ncutils/peerhelper.go

@@ -1,97 +0,0 @@
-package ncutils
-
-import (
-	"net"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/gravitl/netmaker/logger"
-	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
-)
-
-// GetPeers - gets the peers from a given WireGuard interface
-func GetPeers(iface string) ([]wgtypes.Peer, error) {
-
-	var peers []wgtypes.Peer
-	output, err := RunCmd("wg show "+iface+" dump", true)
-	if err != nil {
-		return peers, err
-	}
-	for i, line := range strings.Split(strings.TrimSuffix(output, "\n"), "\n") {
-		if i == 0 {
-			continue
-		}
-		var allowedIPs []net.IPNet
-		fields := strings.Fields(line)
-		if len(fields) < 4 {
-			logger.Log(0, "error parsing peer: "+line)
-			continue
-		}
-		pubkeystring := fields[0]
-		endpointstring := fields[2]
-		allowedipstring := fields[3]
-		var pkeepalivestring string
-		if len(fields) > 7 {
-			pkeepalivestring = fields[7]
-		}
-		// AllowedIPs = private IP + defined networks
-
-		pubkey, err := wgtypes.ParseKey(pubkeystring)
-		if err != nil {
-			logger.Log(0, "error parsing peer key "+pubkeystring)
-			continue
-		}
-		ipstrings := strings.Split(allowedipstring, ",")
-		for _, ipstring := range ipstrings {
-			var netip net.IP
-			if netip = net.ParseIP(strings.Split(ipstring, "/")[0]); netip != nil {
-				allowedIPs = append(
-					allowedIPs,
-					net.IPNet{
-						IP:   netip,
-						Mask: netip.DefaultMask(),
-					},
-				)
-			}
-		}
-		if len(allowedIPs) == 0 {
-			logger.Log(0, "error parsing peer "+pubkeystring+", no allowedips found")
-			continue
-		}
-		var endpointarr []string
-		var endpointip net.IP
-		if endpointarr = strings.Split(endpointstring, ":"); len(endpointarr) != 2 {
-			logger.Log(0, "error parsing peer "+pubkeystring+", could not parse endpoint: "+endpointstring)
-			continue
-		}
-		if endpointip = net.ParseIP(endpointarr[0]); endpointip == nil {
-			logger.Log(0, "error parsing peer "+pubkeystring+", could not parse endpoint: "+endpointarr[0])
-			continue
-		}
-		var port int
-		if port, err = strconv.Atoi(endpointarr[1]); err != nil {
-			logger.Log(0, "error parsing peer "+pubkeystring+", could not parse port: "+err.Error())
-			continue
-		}
-		var endpoint = net.UDPAddr{
-			IP:   endpointip,
-			Port: port,
-		}
-		var dur time.Duration
-		if pkeepalivestring != "" {
-			if dur, err = time.ParseDuration(pkeepalivestring + "s"); err != nil {
-				logger.Log(0, "error parsing peer "+pubkeystring+", could not parse keepalive: "+err.Error())
-			}
-		}
-
-		peers = append(peers, wgtypes.Peer{
-			PublicKey:                   pubkey,
-			Endpoint:                    &endpoint,
-			AllowedIPs:                  allowedIPs,
-			PersistentKeepaliveInterval: dur,
-		})
-	}
-
-	return peers, err
-}

+ 0 - 46
netclient/ncutils/pid.go

@@ -1,46 +0,0 @@
-package ncutils
-
-import (
-	"fmt"
-	"os"
-	"strconv"
-)
-
-// PIDFILE - path/name of pid file
-const PIDFILE = "/var/run/netclient.pid"
-
-// WindowsPIDError - error returned from pid function on windows
-type WindowsPIDError struct{}
-
-// Error generates error for windows os
-func (*WindowsPIDError) Error() string {
-	return "pid tracking not supported on windows"
-}
-
-// SavePID - saves the pid of running program to disk
-func SavePID() error {
-	if IsWindows() {
-		return nil
-	}
-	pid := os.Getpid()
-	if err := os.WriteFile(PIDFILE, []byte(fmt.Sprintf("%d", pid)), 0644); err != nil {
-		return fmt.Errorf("could not write to pid file %w", err)
-	}
-	return nil
-}
-
-// ReadPID - reads a previously saved pid from disk
-func ReadPID() (int, error) {
-	if IsWindows() {
-		return 0, nil
-	}
-	bytes, err := os.ReadFile(PIDFILE)
-	if err != nil {
-		return 0, fmt.Errorf("could not read pid file %w", err)
-	}
-	pid, err := strconv.Atoi(string(bytes))
-	if err != nil {
-		return 0, fmt.Errorf("pid file contents invalid %w", err)
-	}
-	return pid, nil
-}

+ 0 - 25
netclient/ncutils/util.go

@@ -1,29 +1,4 @@
 package ncutils
 
-import (
-	"fmt"
-	"time"
-
-	"github.com/gravitl/netmaker/logger"
-)
-
 // CheckInInterval - the interval for check-in time in units/minute
 const CheckInInterval = 1
-
-// BackOff - back off any function while there is an error
-func BackOff(isExponential bool, maxTime int, f interface{}) (interface{}, error) {
-	// maxTime seconds
-	startTime := time.Now()
-	sleepTime := time.Second
-	for time.Now().Before(startTime.Add(time.Second * time.Duration(maxTime))) {
-		if result, err := f.(func() (interface{}, error))(); err == nil {
-			return result, nil
-		}
-		time.Sleep(sleepTime)
-		if isExponential {
-			sleepTime = sleepTime << 1
-		}
-		logger.Log(1, "retrying...")
-	}
-	return nil, fmt.Errorf("could not find result")
-}

BIN
netclient/ncutils/windowsdaemon/winsw.exe


+ 7 - 0
release.md

@@ -0,0 +1,7 @@
+# Netmaker v0.18.1
+
+## whats new
+
+## whats fixed
+
+## known issues

+ 146 - 55
scripts/nm-quick-interactive.sh → scripts/nm-quick.sh

@@ -1,5 +1,8 @@
 #!/bin/bash
 
+LATEST="v0.18.2"
+
+print_logo() {(
 cat << "EOF"
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -16,13 +19,82 @@ cat << "EOF"
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 EOF
+)}
 
 if [ $(id -u) -ne 0 ]; then
    echo "This script must be run as root"
    exit 1
 fi
 
-if [ -z "$1" ]; then
+unset INSTALL_TYPE
+unset BUILD_TYPE
+unset BUILD_TAG
+unset IMAGE_TAG
+
+usage () {(
+    echo "usage: ./nm-quick.sh [-e] [-b buildtype] [-t tag]"
+    echo "  -e      if specified, will install netmaker EE"
+    echo "  -b      type of build; options:"
+	echo "          \"version\" - will install a specific version of Netmaker using remote git and dockerhub"
+	echo "          \"local\": - will install by cloning repo and and building images from git"
+	echo "          \"branch\": - will install a specific branch using remote git and dockerhub "
+    echo "  -t      tag of build; if buildtype=version, tag=version. If builtype=branch or builtype=local, tag=branch"
+    echo "examples:"
+	echo "          nm-quick.sh -e -b version -t v0.18.2"
+	echo "          nm-quick.sh -e -b local -t feature_v0.17.2_newfeature"	
+	echo "          nm-quick.sh -e -b branch -t develop"
+    exit 1
+)}
+
+while getopts evb:t: flag
+do
+    case "${flag}" in
+        e) 
+			INSTALL_TYPE="ee"
+			;;
+		v) 
+			usage
+			exit 0
+			;;
+        b) 
+			BUILD_TYPE=${OPTARG}
+			if [[ ! "$BUILD_TYPE" =~ ^(version|local|branch)$ ]]; then
+    			echo "error: $BUILD_TYPE is invalid"
+				echo "valid options: version, local, branch"
+				usage
+				exit 1
+			fi
+			;;
+        t) 
+			BUILD_TAG=${OPTARG}
+			;;
+    esac
+done
+
+if [ -z "$BUILD_TYPE" ]; then
+	BUILD_TYPE="version"
+	BUILD_TAG=$LATEST
+fi
+
+if [ -z "$BUILD_TAG" ] && [ "$BUILD_TYPE" = "version" ]; then
+	BUILD_TAG=$LATEST
+fi
+
+if [ -z "$BUILD_TAG" ] && [ ! -z "$BUILD_TYPE" ]; then
+	echo "error: must specify build tag when build type \"$BUILD_TYPE\" is specified"
+	usage		
+	exit 1
+fi
+
+IMAGE_TAG=$(sed 's/\//-/g' <<< "$BUILD_TAG")
+
+if [ "$1" = "ce" ]; then
+	INSTALL_TYPE="ce"
+elif [ "$1" = "ee" ]; then
+	INSTALL_TYPE="ee"
+fi
+
+if [ -z "$INSTALL_TYPE" ]; then
 	echo "-----------------------------------------------------"
 	echo "Would you like to install Netmaker Community Edition (CE), or Netmaker Enterprise Edition (EE)?"
 	echo "EE will require you to create an account at https://dashboard.license.netmaker.io"
@@ -42,16 +114,15 @@ if [ -z "$1" ]; then
 		*) echo "invalid option $REPLY";;
 	esac
 	done
-elif [ "$1" = "ce" ]; then
-	echo "installing Netmaker CE"
-	INSTALL_TYPE="ce"
-elif [ "$1" = "ee" ]; then
-	echo "installing Netmaker EE"
-	INSTALL_TYPE="ee"
-else
-	echo "install type invalid (options: 'ce, ee')"
-	exit 1
 fi
+echo "-----------Build Options-----------------------------"
+echo "    EE or CE: $INSTALL_TYPE";
+echo "  Build Type: $BUILD_TYPE";
+echo "   Build Tag: $BUILD_TAG";
+echo "   Image Tag: $IMAGE_TAG";
+echo "-----------------------------------------------------"
+
+print_logo
 
 wait_seconds() {(
   for ((a=1; a <= $1; a++))
@@ -72,45 +143,67 @@ confirm() {(
   done
 )}
 
+local_install_setup() {(
+	rm -rf netmaker-tmp
+	mkdir netmaker-tmp
+	cd netmaker-tmp
+	git clone https://www.github.com/gravitl/netmaker
+	cd netmaker
+	git checkout $BUILD_TAG
+	git pull origin $BUILD_TAG
+	docker build --no-cache --build-arg version=$IMAGE_TAG -t gravitl/netmaker:$IMAGE_TAG .
+	if [ "$INSTALL_TYPE" = "ee" ]; then
+		cp compose/docker-compose.ee.yml /root/docker-compose.yml 
+		cp docker/Caddyfile-EE /root/Caddyfile
+	else
+		cp compose/docker-compose.yml /root/docker-compose.yml 
+		cp docker/Caddyfile /root/Caddyfile
+	fi
+	cp docker/mosquitto.conf /root/mosquitto.conf
+	cp docker/wait.sh /root/wait.sh
+	cd ../../
+	rm -rf netmaker-tmp
+)}
+
 echo "checking dependencies..."
 
 OS=$(uname)
 
 if [ -f /etc/debian_version ]; then
-	dependencies="wireguard wireguard-tools jq docker.io docker-compose"
+	dependencies="git wireguard wireguard-tools jq docker.io docker-compose"
 	update_cmd='apt update'
 	install_cmd='apt-get install -y'
 elif [ -f /etc/alpine-release ]; then
-	dependencies="wireguard jq docker.io docker-compose"
+	dependencies="git wireguard jq docker.io docker-compose"
 	update_cmd='apk update'
 	install_cmd='apk --update add'
 elif [ -f /etc/centos-release ]; then
-	dependencies="wireguard jq docker.io docker-compose"
+	dependencies="git wireguard jq docker.io docker-compose"
 	update_cmd='yum update'
 	install_cmd='yum install -y'
 elif [ -f /etc/fedora-release ]; then
-	dependencies="wireguard jq docker.io docker-compose"
+	dependencies="git wireguard jq docker.io docker-compose"
 	update_cmd='dnf update'
 	install_cmd='dnf install -y'
 elif [ -f /etc/redhat-release ]; then
-	dependencies="wireguard jq docker.io docker-compose"
+	dependencies="git wireguard jq docker.io docker-compose"
 	update_cmd='yum update'
 	install_cmd='yum install -y'
 elif [ -f /etc/arch-release ]; then
-    	dependecies="wireguard-tools jq docker.io docker-compose"
+    	dependecies="git wireguard-tools jq docker.io docker-compose"
 	update_cmd='pacman -Sy'
 	install_cmd='pacman -S --noconfirm'
 elif [ "${OS}" = "FreeBSD" ]; then
-	dependencies="wireguard wget jq docker.io docker-compose"
+	dependencies="git wireguard wget jq docker.io docker-compose"
 	update_cmd='pkg update'
 	install_cmd='pkg install -y'
 elif [ -f /etc/turris-version ]; then
-	dependencies="wireguard-tools bash jq docker.io docker-compose"
+	dependencies="git wireguard-tools bash jq docker.io docker-compose"
 	OS="TurrisOS"
 	update_cmd='opkg update'	
 	install_cmd='opkg install'
 elif [ -f /etc/openwrt_release ]; then
-	dependencies="wireguard-tools bash jq docker.io docker-compose"
+	dependencies="git wireguard-tools bash jq docker.io docker-compose"
 	OS="OpenWRT"
 	update_cmd='opkg update'	
 	install_cmd='opkg install'
@@ -182,11 +275,21 @@ echo "-----------------------------------------------------"
 
 wait_seconds 3
 
+
+if [ "$BUILD_TYPE" = "local" ]; then
+	local_install_setup
+fi
+
 set -e
 
-NETMAKER_BASE_DOMAIN=nm.$(curl -s ifconfig.me | tr . -).nip.io
+IP_ADDR=$(dig -4 myip.opendns.com @resolver1.opendns.com +short)
+if [ "$IP_ADDR" = "" ]; then
+	IP_ADDR=$(curl -s ifconfig.me)
+fi
+
+NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io
 COREDNS_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p')
-SERVER_PUBLIC_IP=$(curl -s ifconfig.me)
+SERVER_PUBLIC_IP=$IP_ADDR
 MASTER_KEY=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
 DOMAIN_TYPE=""
 echo "-----------------------------------------------------"
@@ -335,15 +438,18 @@ wait_seconds 3
 
 echo "Pulling config files..."
 
-COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.yml" 
-CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/docker/Caddyfile"
+
+COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/compose/docker-compose.yml" 
+CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/Caddyfile"
 if [ "$INSTALL_TYPE" = "ee" ]; then
-	COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.ee.yml" 
-	CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/docker/Caddyfile-EE"
+	COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/compose/docker-compose.ee.yml" 
+	CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/Caddyfile-EE"
+fi
+if [ ! "$BUILD_TYPE" = "local" ]; then
+	wget -O /root/docker-compose.yml $COMPOSE_URL && wget -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/mosquitto.conf && wget -O /root/Caddyfile $CADDY_URL
+	wget -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/$BUILD_TAG/docker/wait.sh
 fi
 
-wget -O /root/docker-compose.yml $COMPOSE_URL && wget -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/master/docker/mosquitto.conf && wget -O /root/Caddyfile $CADDY_URL
-wget -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/master/docker/wait.sh
 chmod +x /root/wait.sh
 mkdir -p /etc/netmaker
 
@@ -360,6 +466,19 @@ if [ "$INSTALL_TYPE" = "ee" ]; then
 	sed -i "s~YOUR_LICENSE_KEY~$LICENSE_KEY~g" /root/docker-compose.yml
 	sed -i "s/YOUR_ACCOUNT_ID/$ACCOUNT_ID/g" /root/docker-compose.yml
 fi
+
+if [ "$BUILD_TYPE" = "version" ] && [ "$INSTALL_TYPE" = "ee" ]; then
+	sed -i "s/REPLACE_SERVER_IMAGE_TAG/$IMAGE_TAG-ee/g" /root/docker-compose.yml
+else
+	sed -i "s/REPLACE_SERVER_IMAGE_TAG/$IMAGE_TAG/g" /root/docker-compose.yml
+fi
+
+if [ "$BUILD_TYPE" = "local" ]; then
+	sed -i "s/REPLACE_UI_IMAGE_TAG/$LATEST/g" /root/docker-compose.yml
+else
+	sed -i "s/REPLACE_UI_IMAGE_TAG/$IMAGE_TAG/g" /root/docker-compose.yml
+fi
+
 echo "Starting containers..."
 
 docker-compose -f /root/docker-compose.yml up -d
@@ -409,34 +528,6 @@ ACCESS_TOKEN=$(jq -r '.accessstring' <<< ${curlresponse})
 
 wait_seconds 3
 
-echo "Configuring netmaker server as ingress gateway"
-
-for i in 1 2 3 4 5 6
-do
-	echo "    waiting for server node to become available"
-	wait_seconds 10
-	curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/netmaker)
-	SERVER_ID=$(jq -r '.[0].id' <<< ${curlresponse})
-	echo "    Server ID: $SERVER_ID"
-	if [ $SERVER_ID == "null" ]; then
-		SERVER_ID=""
-	fi
-	if [[ "$i" -ge "6" && -z "$SERVER_ID" ]]; then
-		echo "    Netmaker is having issues configuring itself, please investigate (docker logs netmaker)"
-		echo "    Exiting..."
-		exit 1
-	elif [ -z "$SERVER_ID" ]; then
-		echo "    server node not yet configured, retrying..."
-	elif [[ ! -z "$SERVER_ID" ]]; then
-		echo "    server node is now availble, continuing"
-		break
-	fi
-done
-
-
-if [[ ! -z "$SERVER_ID"  ]]; then
-	curl -o /dev/null -s -X POST -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/netmaker/$SERVER_ID/createingress
-fi 
 )}
 
 set +e

+ 1 - 3
scripts/nm-upgrade.sh

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

Some files were not shown because too many files changed in this diff