Browse Source

Merge pull request #2153 from gravitl/release_v0.18.5

Release v0.18.5
dcarns 2 years ago
parent
commit
1d3e3ad181
100 changed files with 2096 additions and 1827 deletions
  1. 7 1
      .github/ISSUE_TEMPLATE/bug-report.yml
  2. 58 0
      .github/workflows/branchtest.yml
  3. 0 583
      .github/workflows/buildandrelease.yml
  4. 38 0
      .github/workflows/deletedroplets.yml
  5. 3 2
      .github/workflows/docker-builder.yml
  6. 46 0
      .github/workflows/packages.yml
  7. 5 4
      .github/workflows/publish-docker.yml
  8. 0 51
      .github/workflows/publish-netclient-docker-userspace.yml
  9. 0 99
      .github/workflows/publish-netclient-docker.yml
  10. 32 0
      .github/workflows/pull-request.yml
  11. 0 29
      .github/workflows/purgeGHCR.yml
  12. 50 0
      .github/workflows/release-assets.yml
  13. 39 0
      .github/workflows/release-branch.yml
  14. 49 0
      .github/workflows/release.yml
  15. 21 39
      .github/workflows/test.yml
  16. 41 0
      .github/workflows/upgraderelease.yml
  17. 1 0
      .gitignore
  18. 34 0
      .goreleaser.prerelease.yaml
  19. 4 0
      .goreleaser.update.yaml
  20. 34 0
      .goreleaser.yaml
  21. 1 5
      Dockerfile
  22. 2 2
      README.md
  23. 98 17
      auth/auth.go
  24. 4 7
      auth/azure-ad.go
  25. 9 0
      auth/error.go
  26. 4 7
      auth/github.go
  27. 4 7
      auth/google.go
  28. 93 0
      auth/headless_callback.go
  29. 48 47
      auth/nodecallback.go
  30. 3 9
      auth/nodesession.go
  31. 4 7
      auth/oidc.go
  32. 101 53
      auth/templates.go
  33. 3 15
      cli/cmd/acl/allow.go
  34. 3 15
      cli/cmd/acl/deny.go
  35. 20 19
      cli/cmd/acl/list.go
  36. 0 10
      cli/cmd/acl/root.go
  37. 9 0
      cli/cmd/commons/globals.go
  38. 0 10
      cli/cmd/context/root.go
  39. 5 1
      cli/cmd/context/set.go
  40. 11 5
      cli/cmd/dns/list.go
  41. 0 10
      cli/cmd/dns/root.go
  42. 47 0
      cli/cmd/enrollment_key/create.go
  43. 23 0
      cli/cmd/enrollment_key/delete.go
  44. 20 0
      cli/cmd/enrollment_key/list.go
  45. 28 0
      cli/cmd/enrollment_key/root.go
  46. 11 5
      cli/cmd/ext_client/list.go
  47. 0 10
      cli/cmd/ext_client/root.go
  48. 20 0
      cli/cmd/host/add_network.go
  49. 22 0
      cli/cmd/host/create_relay.go
  50. 20 0
      cli/cmd/host/delete.go
  51. 20 0
      cli/cmd/host/delete_network.go
  52. 20 0
      cli/cmd/host/delete_relay.go
  53. 20 0
      cli/cmd/host/list.go
  54. 28 0
      cli/cmd/host/root.go
  55. 66 0
      cli/cmd/host/update.go
  56. 0 35
      cli/cmd/keys/create.go
  57. 0 23
      cli/cmd/keys/delete.go
  58. 0 20
      cli/cmd/keys/list.go
  59. 0 38
      cli/cmd/keys/root.go
  60. 0 10
      cli/cmd/metrics/root.go
  61. 0 14
      cli/cmd/network/create.go
  62. 0 5
      cli/cmd/network/flags.go
  63. 13 7
      cli/cmd/network/list.go
  64. 0 12
      cli/cmd/network/root.go
  65. 0 14
      cli/cmd/network/update.go
  66. 0 10
      cli/cmd/network_user/root.go
  67. 3 5
      cli/cmd/node/create_egress.go
  68. 0 22
      cli/cmd/node/create_relay.go
  69. 0 20
      cli/cmd/node/delete_relay.go
  70. 0 7
      cli/cmd/node/flags.go
  71. 21 13
      cli/cmd/node/list.go
  72. 0 10
      cli/cmd/node/root.go
  73. 4 32
      cli/cmd/node/update.go
  74. 8 17
      cli/cmd/root.go
  75. 0 10
      cli/cmd/server/root.go
  76. 12 5
      cli/cmd/user/list.go
  77. 0 10
      cli/cmd/user/root.go
  78. 0 10
      cli/cmd/usergroup/root.go
  79. 1 0
      cli/config/config.go
  80. 22 0
      cli/functions/enrollment_keys.go
  81. 50 0
      cli/functions/host.go
  82. 71 0
      cli/functions/http_client.go
  83. 0 23
      cli/functions/keys.go
  84. 13 27
      cli/functions/node.go
  85. 0 2
      cli/samples/network.json
  86. 0 4
      cli/samples/node.json
  87. 84 0
      compose/docker-compose-emqx.yml
  88. 23 38
      compose/docker-compose.ee.yml
  89. 17 0
      compose/docker-compose.netclient.yml
  90. 17 26
      compose/docker-compose.reference.yml
  91. 13 25
      compose/docker-compose.yml
  92. 49 44
      config/config.go
  93. 4 4
      config/config_test.go
  94. 0 2
      config/environments/dev.yaml
  95. 1 2
      controllers/config/environments/dev.yaml
  96. 8 12
      controllers/controller.go
  97. 58 71
      controllers/dns.go
  98. 93 86
      controllers/dns_test.go
  99. 18 43
      controllers/docs.go
  100. 264 0
      controllers/enrollmentkeys.go

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

@@ -31,7 +31,13 @@ body:
       label: Version
       description: What version are you running?
       options:
-        - v0.17.1      
+        - v0.18.5
+        - v0.18.4
+        - v0.18.3
+        - v0.18.2
+        - v0.18.1
+        - v0.18.0
+        - v0.17.1
         - v0.17.0
         - v0.16.3
         - v0.16.2

+ 58 - 0
.github/workflows/branchtest.yml

@@ -0,0 +1,58 @@
+name: Deploy and Test Branch
+
+on:
+  workflow_dispatch:
+  pull_request:
+      types: [opened, synchronize, reopened]
+      branches: [develop]
+
+jobs:
+  skip-check:
+    runs-on: ubuntu-latest
+    outputs:
+      skip: ${{ steps.check.outputs.skip }}
+    steps:
+      - id: skip
+        uses: fkirc/skip-duplicate-actions@v5
+        with:
+          concurrent_skipping: 'always'
+  getbranch:
+    runs-on: ubuntu-latest
+    needs: skip-check
+    if: ${{ needs.skip-check.outputs.skip != 'true' }}
+    outputs:
+      netclientbranch: ${{ steps.checkbranch.outputs.netclientbranch }}
+    steps:
+      - name: checkout
+        uses: actions/checkout@v3
+        with:
+          repository: gravitl/netclient
+          ref: develop
+      - name: check if branch exists
+        id: checkbranch
+        run: |
+          if git show-ref ${{ github.head_ref}}; then
+            echo branch exists
+            echo "netclientbranch=${{ github.head_ref }}" >> $GITHUB_OUTPUT
+          else
+            echo branch does not exist
+            echo "netclientbranch=develop" >> $GITHUB_OUTPUT
+          fi
+  
+  terraform:
+    needs: getbranch
+    uses: gravitl/devops/.github/workflows/terraform.yml@master
+    with:
+      netmakerbranch: ${{ github.head_ref }}
+      netclientbranch: ${{ needs.getbranch.outputs.netclientbranch }}
+    secrets: inherit
+
+
+  testbranch:
+    needs: [getbranch, terraform]
+    uses: gravitl/devops/.github/workflows/branchtest.yml@master
+    with:
+      tag: ${{ github.run_id }}-${{ github.run_attempt }}
+      network: terraform
+    secrets: inherit
+    

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

@@ -1,583 +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
-
-  netclient-x86:
-    runs-on: ubuntu-latest
-    needs: version
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-      - name: Set Variables
-        run: |
-          TAG=${{needs.version.outputs.tag}}
-          VERSION=${{needs.version.outputs.version}}
-          echo "NETMAKER_VERSION=${TAG}"  >> $GITHUB_ENV
-          echo "PACKAGE_VERSION=${VERSION}" >> $GITHUB_ENV
-      - name: Setup go
-        uses: actions/setup-go@v3
-        with:
-          go-version: 1.19
-
-      - name: Build cli
-        run: |
-          cd netclient
-          env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient main.go
-
-      - name: Upload netclient x86 to Release
-        continue-on-error: true
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient
-
-      - name: build gui
-        run: |
-          sudo apt-get update
-          sudo apt-get install -y gcc libgl1-mesa-dev xorg-dev
-          go build -tags=gui -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-gui .
-
-      - name: Upload netclient x86 gui to Release
-        continue-on-error: true
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-gui
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-gui
-
-      - name: Package x86 deb
-        continue-on-error: true
-        uses: gravitl/github-action-fpm@master
-        with:
-          fpm_args: './netclient/build/netclient=/sbin/netclient ./netclient/build/netclient.service=/lib/systemd/system/netclient.service'
-          fpm_opts: '-s dir -t deb --architecture amd64 --version ${{ env.PACKAGE_VERSION }}'
-
-      - name: Upload x86 deb to Release
-        continue-on-error: true
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient_${{ env.PACKAGE_VERSION }}_amd64.deb
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient_${{ env.PACKAGE_VERSION }}_amd64.deb
-
-      - name: Package x86 rpm
-        continue-on-error: true
-        uses: gravitl/github-action-fpm@master
-        with:
-          fpm_args: './netclient/build/netclient=/sbin/netclient ./netclient/build/netclient.service=/lib/systemd/system/netclient.service'
-          fpm_opts: '-s dir -t rpm --architecture amd64 --version ${{ env.PACKAGE_VERSION }}'
-
-      - name: Upload x86 rpm to Release
-        continue-on-error: true
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient-${{ env.PACKAGE_VERSION }}-1.x86_64.rpm
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-${{ env.PACKAGE_VERSION }}-1.x86_64.rpm
-
-      - name: Package x86 pacman
-        continue-on-error: true
-        uses: gravitl/github-action-fpm@master
-        with:
-          # arch has particular path requirements --- cannot write to a symbolic link e.g. /sbin and /lib
-          fpm_args: './netclient/build/netclient=/usr/bin/netclient ./netclient/build/netclient.service=/usr/lib/systemd/system/netclient.service'
-          fpm_opts: '-s dir -t pacman --architecture amd64 --version ${{ env.PACKAGE_VERSION }}'
-
-      - name: Upload x86 pacman to Release
-        continue-on-error: true
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient-${{ env.PACKAGE_VERSION }}-1-x86_64.pkg.tar.zst
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-${{ env.PACKAGE_VERSION }}-1-x86_64.pkg.tar.zst
-
-  netclient-arm:
-    runs-on: ubuntu-latest
-    needs: version
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-      - name: Set Variables
-        run: |
-          TAG=${{needs.version.outputs.tag}}
-          VERSION=${{needs.version.outputs.version}}
-          echo "NETMAKER_VERSION=${TAG}"  >> $GITHUB_ENV
-          echo "PACKAGE_VERSION=${VERSION}" >> $GITHUB_ENV
-      - name: Setup go
-        uses: actions/setup-go@v3
-        with:
-          go-version: 1.19
-      - name: Build
-        run: |
-          cd netclient
-          env CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-arm5/netclient main.go
-          env CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-arm6/netclient main.go
-          env CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-arm7/netclient main.go
-          env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-arm64/netclient main.go
-
-      - name: Upload arm5 to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-arm5/netclient
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-arm5
-
-      - name: Upload arm6 to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-arm6/netclient
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-arm6
-
-      - name: Upload arm7 to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-arm7/netclient
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-arm7
-
-      - name: Upload arm64 to Release
-        continue-on-error: true
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-arm64/netclient
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-arm64
-
-      - name: Package arm64 deb
-        continue-on-error: true
-        uses: gravitl/github-action-fpm@master
-        with:
-          fpm_args: './netclient/build/netclient-arm64/netclient=/sbin/netclient ./netclient/build/netclient.service=/lib/systemd/netclient.service'
-          fpm_opts: '-s dir -t deb --architecture arm64 --version ${{ env.PACKAGE_VERSION }}'
-      - name: Upload arm deb to Release
-        continue-on-error: true
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient_${{ env.PACKAGE_VERSION }}_arm64.deb
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient_${{ env.PACKAGE_VERSION }}_arm64.deb
-
-      - name: Package arm64 rpm
-        continue-on-error: true
-        uses: gravitl/github-action-fpm@master
-        with:
-          fpm_args: './netclient/build/netclient-arm64/netclient=/sbin/netclient ./netclient/build/netclient.service=/lib/systemd/netclient.service'
-          fpm_opts: '-s dir -t rpm --architecture arm64 --version ${{ env.PACKAGE_VERSION }}'
-
-      - name: Upload arm64 rpm to Release
-        continue-on-error: true
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient-${{ env.PACKAGE_VERSION }}-1.aarch64.rpm
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-${{ env.PACKAGE_VERSION }}-1.aarch64.rpm
-
-  netclient-mipsle:
-    runs-on: ubuntu-latest
-    needs: version
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-      - name: Set Variables
-        run: |
-          TAG=${{needs.version.outputs.tag}}
-          VERSION=${{needs.version.outputs.version}}
-          echo "NETMAKER_VERSION=${TAG}"  >> $GITHUB_ENV
-          echo "PACKAGE_VERSION=${VERSION}" >> $GITHUB_ENV
-      - name: Setup go
-        uses: actions/setup-go@v3
-        with:
-          go-version: 1.19
-      - name: Build
-        run: |
-          cd netclient
-          env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle go build -ldflags "-s -w -X 'main.version=$NETMAKER_VERSION'" -o build/netclient-mipsle/netclient-mipsle main.go && upx -o build/netclient-mipsle/netclient-mipsle-upx build/netclient-mipsle/netclient-mipsle
-          env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "-s -w -X 'main.version=$NETMAKER_VERSION'" -o build/netclient-mipsle/netclient-mipsle-softfloat main.go && upx -o build/netclient-mipsle/netclient-mipsle-softfloat-upx build/netclient-mipsle/netclient-mipsle-softfloat
-
-      - name: Upload mipsle to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-mipsle/netclient-mipsle
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-mipsle
-
-      - name: Upload mipsle-upx to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-mipsle/netclient-mipsle-upx
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-mipsle-upx
-
-      - name: Upload mipsle-softfloat to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-mipsle/netclient-mipsle-softfloat
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-mipsle-softfloat
-
-      - name: Upload mipsle-softfloat-upx to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-mipsle/netclient-mipsle-softfloat-upx 
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-mipsle-softfloat-upx 
-
-  netclient-mips:
-    runs-on: ubuntu-latest
-    needs: version
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-      - name: Set Variables
-        run: |
-          TAG=${{needs.version.outputs.tag}}
-          VERSION=${{needs.version.outputs.version}}
-          echo "NETMAKER_VERSION=${TAG}"  >> $GITHUB_ENV
-          echo "PACKAGE_VERSION=${VERSION}" >> $GITHUB_ENV
-      - name: Setup go
-        uses: actions/setup-go@v3
-        with:
-          go-version: 1.19
-      - name: Build
-        run: |
-          cd netclient
-          env CGO_ENABLED=0 GOOS=linux GOARCH=mips go build -ldflags "-s -w -X 'main.version=$NETMAKER_VERSION'" -o build/netclient-mips/netclient-mips main.go && upx -o build/netclient-mips/netclient-mips-upx build/netclient-mips/netclient-mips 
-          env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "-s -w -X 'main.version=$NETMAKER_VERSION'" -o build/netclient-mips/netclient-mips-softfloat main.go && upx -o build/netclient-mips/netclient-mips-softfloat-upx build/netclient-mips/netclient-mips-softfloat 
-
-      - name: Upload mips to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-mips/netclient-mips
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-mips
-
-      - name: Upload mips-upx to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-mips/netclient-mips-upx
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-mips-upx
-
-      - name: Upload netclient-mips-softfloat to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-mips/netclient-mips-softfloat
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-mips-softfloat
-
-      - name: Upload netclient-mips-softfloat-upx to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-mips/netclient-mips-softfloat-upx
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-mips-softfloat-upx
-
-  netclient-freebsd:
-    runs-on: ubuntu-latest
-    needs: version
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-      - name: Set Variables
-        run: |
-          TAG=${{needs.version.outputs.tag}}
-          VERSION=${{needs.version.outputs.version}}
-          echo "NETMAKER_VERSION=${TAG}"  >> $GITHUB_ENV
-          echo "PACKAGE_VERSION=${VERSION}" >> $GITHUB_ENV
-      - name: Setup go
-        uses: actions/setup-go@v3
-        with:
-          go-version: 1.19
-      - name: Build
-        run: |
-          cd netclient
-          env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-freebsd/netclient .
-          env CGO_ENABLED=0 GOOS=freebsd GOARCH=arm GOARM=5 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-freebsd-arm5/netclient .
-          env CGO_ENABLED=0 GOOS=freebsd GOARCH=arm GOARM=6 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-freebsd-arm6/netclient .
-          env CGO_ENABLED=0 GOOS=freebsd GOARCH=arm GOARM=7 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-freebsd-arm7/netclient .
-            env CGO_ENABLED=0 GOOS=freebsd GOARCH=arm64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-freebsd-arm64/netclient .
-
-      - name: Upload freebsd to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-freebsd/netclient
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-freebsd
-
-      - name: Upload freebsd-arm5 to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-freebsd-arm5/netclient
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-freebsd-arm5
-
-      - name: Upload freebsd-arm6 to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-freebsd-arm6/netclient
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-freebsd-arm6
-
-      - name: Upload freebsd-arm7 to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-freebsd-arm7/netclient
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-freebsd-arm7
-
-      - name: Upload freebsd-arm64 to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-freebsd-arm64/netclient
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-freebsd-arm64
-
-  netclient-darwin:
-    runs-on: macos-latest
-    needs: version
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-      - name: Set Variables
-        run: |
-          TAG=${{needs.version.outputs.tag}}
-          VERSION=${{needs.version.outputs.version}}
-          echo "NETMAKER_VERSION=${TAG}"  >> $GITHUB_ENV
-          echo "PACKAGE_VERSION=${VERSION}" >> $GITHUB_ENV
-      - name: Setup go
-        uses: actions/setup-go@v3
-        with:
-          go-version: 1.19
-      - name: Build
-        run: |
-          cd netclient
-          env GOOS=darwin GOARCH=amd64 go build -tags=gui -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin/netclient .
-          env CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 go build -tags=gui -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin-arm64/netclient main.go
-          env GOOS=darwin GOARCH=amd64 go build -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient-darwin-headless/netclient .
-      - name: Upload darwin-amd64 to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-darwin/netclient
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-darwin
-
-      - name: Upload darwin-arm64 to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-darwin-arm64/netclient
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-darwin-arm64
- 
-      - name: Upload darwin-headless to Release
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient-darwin-headless/netclient
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient-darwin-headless
- 
-  netclient-windows:
-    runs-on: windows-latest
-    needs: version
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v3
-      - name: Set Variables
-        run: |
-          TAG=${{needs.version.outputs.tag}}
-          VERSION=${{needs.version.outputs.version}}
-          echo "NETMAKER_VERSION=${TAG}"  >> $GITHUB_ENV
-          echo "PACKAGE_VERSION=${VERSION}" >> $GITHUB_ENV
-        shell: bash
-      - name: Setup go
-        uses: actions/setup-go@v3
-        with:
-          go-version: 1.19
-      - name: Mysys2 setup
-        uses: msys2/setup-msys2@v2
-        with:
-          install: >-
-            git
-            mingw-w64-x86_64-toolchain
-      - name: Build
-        run: |
-          echo $(go env GOPATH)/bin >> $GITHUB_PATH
-          cd netclient
-          go get -v github.com/josephspurrier/goversioninfo
-          go install -v github.com/josephspurrier/goversioninfo/cmd/goversioninfo
-          go generate
-          go build -tags=gui -ldflags="-X 'main.version=${NETMAKER_VERSION}'" -o build/netclient.exe .
-
-      - name: Upload netclient windows to Release
-        continue-on-error: true
-        uses: svenstaro/upload-release-action@v2
-        with:
-          repo_token: ${{ secrets.GITHUB_TOKEN }}
-          file: netclient/build/netclient.exe
-          tag: ${{ env.NETMAKER_VERSION }}
-          overwrite: true
-          prerelease: true
-          asset_name: netclient.exe
-
-  linux-packages:
-    runs-on: ubuntu-latest
-    needs: [version, netclient-x86, netclient-arm]
-    steps:
-      - name: Repository Dispatch
-        uses: peter-evans/[email protected]
-        with:
-          token: ${{ secrets.PERS_TOKEN_FOR_NETMAKER_DEVOPS}}
-          repository: gravitl/netmaker-devops
-          event-type: build-packages
-          client-payload: '{"VERSION": "${{ env.PACKAGE_VERSION }}"}'

+ 38 - 0
.github/workflows/deletedroplets.yml

@@ -0,0 +1,38 @@
+name: Delete Droplets
+
+on:
+  workflow_run:
+    workflows: [Deploy and Test Branch]
+    types:
+      - completed
+
+jobs:
+  on-success:
+    runs-on: ubuntu-latest
+    if: ${{ github.event.workflow_run.conclusion == 'success' }}
+    steps:
+      - name: delete droplets
+        run: |
+          sleep 15m
+          curl -X GET \
+            -H "Content-Type: application/json" \
+            -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
+            "https://api.digitalocean.com/v2/droplets?tag_name=$TAG"
+        env:
+          DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
+          TAG: ${{ github.event.workflow_run.run_id }}-${{ github.event.workflow_run.run_attempt }}
+
+  on-failure:
+    runs-on: ubuntu-latest
+    if: ${{ github.event.workflow_run.conclusion == 'failure' }}
+    steps:
+      - name: delete droplets
+        run: |
+          sleep 6h
+          curl -X GET \
+            -H "Content-Type: application/json" \
+            -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
+            "https://api.digitalocean.com/v2/droplets?tag_name=$TAG"
+        env:
+          DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }}
+          TAG: ${{ github.event.workflow_run.run_id }}-${{ github.event.workflow_run.run_attempt }}

+ 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

+ 0 - 51
.github/workflows/publish-netclient-docker-userspace.yml

@@ -1,51 +0,0 @@
-name: Publish Netclient-Userspace Docker
-
-on:
-  workflow_dispatch:
-    inputs:
-      tag:
-        description: 'docker tag'
-        required: true
-  release:
-    types: [published]
-
-jobs:
-  docker:
-    runs-on: ubuntu-latest
-    steps:
-      - 
-        name: Set tag
-        run: |
-            if [[ -n "${{ github.event.inputs.tag }}" ]]; then
-              TAG=${{ github.event.inputs.tag }}
-            elif [[ "${{ github.ref_name }}" == 'master' ]]; then
-              TAG="latest"
-            else
-              TAG="${{ github.ref_name }}"
-            fi
-            echo "TAG=${TAG}" >> $GITHUB_ENV
-      - 
-        name: Checkout
-        uses: actions/checkout@v3
-      - 
-        name: Set up QEMU
-        uses: docker/setup-qemu-action@v2
-      - 
-        name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v2
-      - 
-        name: Login to DockerHub
-        uses: docker/login-action@v2
-        with:
-          username: ${{ secrets.DOCKERHUB_USERNAME }}
-          password: ${{ secrets.DOCKERHUB_TOKEN }}
-      -
-        name: Build and push
-        uses: docker/build-push-action@v3
-        with:
-          context: .
-          platforms: linux/amd64, linux/arm64, linux/arm/v7
-          file: ./docker/Dockerfile-netclient-multiarch-userspace
-          push: true
-          tags: gravitl/netclient-go:${{ env.TAG }}, gravitl/netclient-userspace:latest
-          build-args: version=${{ env.TAG }}  

+ 0 - 99
.github/workflows/publish-netclient-docker.yml

@@ -1,99 +0,0 @@
-name: Publish Netclient Docker
-
-on:
-  workflow_dispatch:
-    inputs:
-      tag:
-        description: 'docker tag'
-        required: true
-  release:
-    types: [published]
-
-jobs:
-  docker:
-    runs-on: ubuntu-latest
-    steps:
-      - 
-        name: Set tag
-        run: |
-            if [[ -n "${{ github.event.inputs.tag }}" ]]; then
-              TAG=${{ github.event.inputs.tag }}
-            elif [[ "${{ github.ref_name }}" == 'master' ]]; then
-              TAG="latest"
-            else
-              TAG="${{ github.ref_name }}"
-            fi
-            echo "TAG=${TAG}" >> $GITHUB_ENV
-      - 
-        name: Checkout
-        uses: actions/checkout@v3
-      - 
-        name: Set up QEMU
-        uses: docker/setup-qemu-action@v2
-      - 
-        name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v2
-      - 
-        name: Login to DockerHub
-        uses: docker/login-action@v2
-        with:
-          username: ${{ secrets.DOCKERHUB_USERNAME }}
-          password: ${{ secrets.DOCKERHUB_TOKEN }}
-      - 
-        name: Build x86 and export to Docker
-        uses: docker/build-push-action@v3
-        with:
-          context: .
-          load: true
-          platforms: linux/amd64
-          file: ./docker/Dockerfile-netclient-multiarch
-          tags: ${{ env.TAG }}
-          build-args: version=${{ env.TAG }}  
-      -
-        name: Test x86
-        run: |
-            docker run --rm ${{ env.TAG }}&
-            sleep 10
-            kill %1
-      -
-        name: Build arm64 and export to Docker
-        uses: docker/build-push-action@v3
-        with:
-          context: .
-          load: true
-          platforms: linux/arm64
-          file: ./docker/Dockerfile-netclient-multiarch
-          tags: ${{ env.TAG }}
-          build-args: version=${{ env.TAG }}  
-      -
-        name: Test arm64
-        run: |
-            docker run --rm ${{ env.TAG }}&
-            sleep 10
-            kill %1
-      -
-        name: Build armv7l and export to Docker
-        uses: docker/build-push-action@v3
-        with:
-          context: .
-          load: true
-          platforms: linux/arm/v7
-          file: ./docker/Dockerfile-netclient-multiarch
-          tags: ${{ env.TAG }}
-          build-args: version=${{ env.TAG }}  
-      -
-        name: Test armv7l
-        run: |
-            docker run --rm ${{ env.TAG }}&
-            sleep 10
-            kill %1
-      -
-        name: Build and push
-        uses: docker/build-push-action@v3
-        with:
-          context: .
-          platforms: linux/amd64, linux/arm64, linux/arm/v7
-          file: ./docker/Dockerfile-netclient-multiarch
-          push: true
-          tags: gravitl/netclient:${{ env.TAG }}, gravitl/netclient:latest
-          build-args: version=${{ env.TAG }}  

+ 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 }}

+ 21 - 39
.github/workflows/test.yml

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

+ 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 - 5
Dockerfile

@@ -1,18 +1,14 @@
 #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.17.1-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.18.5-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. 
 

+ 98 - 17
auth/auth.go

@@ -7,10 +7,12 @@ import (
 	"fmt"
 	"net/http"
 	"strings"
+	"time"
 
 	"golang.org/x/crypto/bcrypt"
 	"golang.org/x/oauth2"
 
+	"github.com/gorilla/websocket"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic/pro/netcache"
@@ -32,6 +34,7 @@ const (
 	auth_key               = "netmaker_auth"
 	user_signin_length     = 16
 	node_signin_length     = 64
+	headless_signin_length = 32
 )
 
 // OAuthUser - generic OAuth strategy user
@@ -43,7 +46,10 @@ type OAuthUser struct {
 	AccessToken       string `json:"accesstoken" bson:"accesstoken"`
 }
 
-var auth_provider *oauth2.Config
+var (
+	auth_provider *oauth2.Config
+	upgrader      = websocket.Upgrader{}
+)
 
 func getCurrentAuthFunctions() map[string]interface{} {
 	var authInfo = servercfg.GetAuthProviderInfo()
@@ -73,10 +79,6 @@ func InitializeAuthProvider() string {
 		logger.Log(0, err.Error())
 		return ""
 	}
-	var currentFrontendURL = servercfg.GetFrontendURL()
-	if currentFrontendURL == "" {
-		return ""
-	}
 	var authInfo = servercfg.GetAuthProviderInfo()
 	var serverConn = servercfg.GetAPIHost()
 	if strings.Contains(serverConn, "localhost") || strings.Contains(serverConn, "127.0.0.1") {
@@ -100,8 +102,7 @@ func InitializeAuthProvider() string {
 // Note: not included in API reference as part of the OAuth process itself.
 func HandleAuthCallback(w http.ResponseWriter, r *http.Request) {
 	if auth_provider == nil {
-		w.Header().Set("Content-Type", "text/html; charset=utf-8")
-		_, _ = fmt.Fprintln(w, oauthNotConfigured)
+		handleOauthNotConfigured(w)
 		return
 	}
 	var functions = getCurrentAuthFunctions()
@@ -110,9 +111,17 @@ func HandleAuthCallback(w http.ResponseWriter, r *http.Request) {
 	}
 	state, _ := getStateAndCode(r)
 	_, err := netcache.Get(state) // if in netcache proceeed with node registration login
-	if err == nil || len(state) == node_signin_length || errors.Is(err, netcache.ErrExpired) {
-		logger.Log(0, "proceeding with node SSO callback")
-		HandleNodeSSOCallback(w, r)
+	if err == nil || errors.Is(err, netcache.ErrExpired) {
+		switch len(state) {
+		case node_signin_length:
+			logger.Log(0, "proceeding with node SSO callback")
+			HandleNodeSSOCallback(w, r)
+		case headless_signin_length:
+			logger.Log(0, "proceeding with headless SSO callback")
+			HandleHeadlessSSOCallback(w, r)
+		default:
+			logger.Log(1, "invalid state length: ", fmt.Sprintf("%d", len(state)))
+		}
 	} else { // handle normal login
 		functions[handle_callback].(func(http.ResponseWriter, *http.Request))(w, r)
 	}
@@ -128,19 +137,17 @@ func HandleAuthCallback(w http.ResponseWriter, r *http.Request) {
 //	  		oauth
 func HandleAuthLogin(w http.ResponseWriter, r *http.Request) {
 	if auth_provider == nil {
-		var referer = r.Header.Get("referer")
-		if referer != "" {
-			http.Redirect(w, r, referer+"login?oauth=callback-error", http.StatusTemporaryRedirect)
-			return
-		}
-		w.Header().Set("Content-Type", "text/html; charset=utf-8")
-		_, _ = fmt.Fprintln(w, oauthNotConfigured)
+		handleOauthNotConfigured(w)
 		return
 	}
 	var functions = getCurrentAuthFunctions()
 	if functions == nil {
 		return
 	}
+	if servercfg.GetFrontendURL() == "" {
+		handleOauthNotConfigured(w)
+		return
+	}
 	functions[handle_login].(func(http.ResponseWriter, *http.Request))(w, r)
 }
 
@@ -154,6 +161,80 @@ func IsOauthUser(user *models.User) error {
 	return bCryptErr
 }
 
+// HandleHeadlessSSO - handles the OAuth login flow for headless interfaces such as Netmaker CLI via websocket
+func HandleHeadlessSSO(w http.ResponseWriter, r *http.Request) {
+	conn, err := upgrader.Upgrade(w, r, nil)
+	if err != nil {
+		logger.Log(0, "error during connection upgrade for headless sign-in:", err.Error())
+		return
+	}
+	if conn == nil {
+		logger.Log(0, "failed to establish web-socket connection during headless sign-in")
+		return
+	}
+	defer conn.Close()
+
+	req := &netcache.CValue{User: "", Pass: ""}
+	stateStr := logic.RandomString(headless_signin_length)
+	if err = netcache.Set(stateStr, req); err != nil {
+		logger.Log(0, "Failed to process sso request -", err.Error())
+		return
+	}
+
+	timeout := make(chan bool, 1)
+	answer := make(chan string, 1)
+	defer close(answer)
+	defer close(timeout)
+
+	if auth_provider == nil {
+		if err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")); err != nil {
+			logger.Log(0, "error during message writing:", err.Error())
+		}
+		return
+	}
+	redirectUrl = fmt.Sprintf("https://%s/api/oauth/register/%s", servercfg.GetAPIConnString(), stateStr)
+	if err = conn.WriteMessage(websocket.TextMessage, []byte(redirectUrl)); err != nil {
+		logger.Log(0, "error during message writing:", err.Error())
+	}
+
+	go func() {
+		for {
+			cachedReq, err := netcache.Get(stateStr)
+			if err != nil {
+				if strings.Contains(err.Error(), "expired") {
+					logger.Log(0, "timeout occurred while waiting for SSO")
+					timeout <- true
+					break
+				}
+				continue
+			} else if cachedReq.Pass != "" {
+				logger.Log(0, "SSO process completed for user ", cachedReq.User)
+				answer <- cachedReq.Pass
+				break
+			}
+			time.Sleep(500) // try it 2 times per second to see if auth is completed
+		}
+	}()
+
+	select {
+	case result := <-answer:
+		if err = conn.WriteMessage(websocket.TextMessage, []byte(result)); err != nil {
+			logger.Log(0, "Error during message writing:", err.Error())
+		}
+	case <-timeout:
+		logger.Log(0, "Authentication server time out for headless SSO login")
+		if err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")); err != nil {
+			logger.Log(0, "Error during message writing:", err.Error())
+		}
+	}
+	if err = netcache.Del(stateStr); err != nil {
+		logger.Log(0, "failed to remove SSO cache entry", err.Error())
+	}
+	if err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")); err != nil {
+		logger.Log(0, "write close:", err.Error())
+	}
+}
+
 // == private methods ==
 
 func addUser(email string) error {

+ 4 - 7
auth/azure-ad.go

@@ -37,16 +37,13 @@ func initAzureAD(redirectURL string, clientID string, clientSecret string) {
 
 func handleAzureLogin(w http.ResponseWriter, r *http.Request) {
 	var oauth_state_string = logic.RandomString(user_signin_length)
-	if auth_provider == nil && servercfg.GetFrontendURL() != "" {
-		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
-		return
-	} else if auth_provider == nil {
-		fmt.Fprintf(w, "%s", []byte("no frontend URL was provided and an OAuth login was attempted\nplease reconfigure server to use OAuth or use basic credentials"))
+	if auth_provider == nil {
+		handleOauthNotConfigured(w)
 		return
 	}
 
 	if err := logic.SetState(oauth_state_string); err != nil {
-		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
+		handleOauthNotConfigured(w)
 		return
 	}
 
@@ -60,7 +57,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) {
 	var content, err = getAzureUserInfo(rState, rCode)
 	if err != nil {
 		logger.Log(1, "error when getting user info from azure:", err.Error())
-		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
+		handleOauthNotConfigured(w)
 		return
 	}
 	_, err = logic.GetUser(content.UserPrincipalName)

+ 9 - 0
auth/error.go

@@ -1,5 +1,7 @@
 package auth
 
+import "net/http"
+
 // == define error HTML here ==
 const oauthNotConfigured = `<!DOCTYPE html><html>
 <body>
@@ -7,3 +9,10 @@ const oauthNotConfigured = `<!DOCTYPE html><html>
 <p>Please visit the docs <a href="https://docs.netmaker.org/oauth.html" target="_blank" rel="noopener">here</a> to learn how to.</p>
 </body>
 </html>`
+
+// handleOauthNotConfigured - returns an appropriate html page when oauth is not configured on netmaker server but an oauth login was attempted
+func handleOauthNotConfigured(response http.ResponseWriter) {
+	response.Header().Set("Content-Type", "text/html; charset=utf-8")
+	response.WriteHeader(http.StatusInternalServerError)
+	response.Write([]byte(oauthNotConfigured))
+}

+ 4 - 7
auth/github.go

@@ -37,16 +37,13 @@ func initGithub(redirectURL string, clientID string, clientSecret string) {
 
 func handleGithubLogin(w http.ResponseWriter, r *http.Request) {
 	var oauth_state_string = logic.RandomString(user_signin_length)
-	if auth_provider == nil && servercfg.GetFrontendURL() != "" {
-		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
-		return
-	} else if auth_provider == nil {
-		fmt.Fprintf(w, "%s", []byte("no frontend URL was provided and an OAuth login was attempted\nplease reconfigure server to use OAuth or use basic credentials"))
+	if auth_provider == nil {
+		handleOauthNotConfigured(w)
 		return
 	}
 
 	if err := logic.SetState(oauth_state_string); err != nil {
-		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
+		handleOauthNotConfigured(w)
 		return
 	}
 
@@ -60,7 +57,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) {
 	var content, err = getGithubUserInfo(rState, rCode)
 	if err != nil {
 		logger.Log(1, "error when getting user info from github:", err.Error())
-		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
+		handleOauthNotConfigured(w)
 		return
 	}
 	_, err = logic.GetUser(content.Login)

+ 4 - 7
auth/google.go

@@ -38,16 +38,13 @@ func initGoogle(redirectURL string, clientID string, clientSecret string) {
 
 func handleGoogleLogin(w http.ResponseWriter, r *http.Request) {
 	var oauth_state_string = logic.RandomString(user_signin_length)
-	if auth_provider == nil && servercfg.GetFrontendURL() != "" {
-		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
-		return
-	} else if auth_provider == nil {
-		fmt.Fprintf(w, "%s", []byte("no frontend URL was provided and an OAuth login was attempted\nplease reconfigure server to use OAuth or use basic credentials"))
+	if auth_provider == nil {
+		handleOauthNotConfigured(w)
 		return
 	}
 
 	if err := logic.SetState(oauth_state_string); err != nil {
-		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
+		handleOauthNotConfigured(w)
 		return
 	}
 
@@ -62,7 +59,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) {
 	var content, err = getGoogleUserInfo(rState, rCode)
 	if err != nil {
 		logger.Log(1, "error when getting user info from google:", err.Error())
-		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
+		handleOauthNotConfigured(w)
 		return
 	}
 	_, err = logic.GetUser(content.Email)

+ 93 - 0
auth/headless_callback.go

@@ -0,0 +1,93 @@
+package auth
+
+import (
+	"bytes"
+	"fmt"
+	"net/http"
+
+	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/logic/pro/netcache"
+	"github.com/gravitl/netmaker/models"
+)
+
+// HandleHeadlessSSOCallback - handle OAuth callback for headless logins such as Netmaker CLI
+func HandleHeadlessSSOCallback(w http.ResponseWriter, r *http.Request) {
+	functions := getCurrentAuthFunctions()
+	if functions == nil {
+		w.WriteHeader(http.StatusBadRequest)
+		w.Write([]byte("bad conf"))
+		logger.Log(0, "Missing Oauth config in HandleHeadlessSSOCallback")
+		return
+	}
+	state, code := getStateAndCode(r)
+
+	userClaims, err := functions[get_user_info].(func(string, string) (*OAuthUser, error))(state, code)
+	if err != nil {
+		logger.Log(0, "error when getting user info from callback:", err.Error())
+		w.WriteHeader(http.StatusBadRequest)
+		w.Write([]byte("Failed to retrieve OAuth user claims"))
+		return
+	}
+
+	if code == "" || state == "" {
+		w.WriteHeader(http.StatusBadRequest)
+		w.Write([]byte("Wrong params"))
+		logger.Log(0, "Missing params in HandleHeadlessSSOCallback")
+		return
+	}
+
+	// all responses should be in html format from here on out
+	w.Header().Add("content-type", "text/html; charset=utf-8")
+
+	// retrieve machinekey from state cache
+	reqKeyIf, machineKeyFoundErr := netcache.Get(state)
+	if machineKeyFoundErr != nil {
+		logger.Log(0, "requested machine state key expired before authorisation completed -", machineKeyFoundErr.Error())
+		response := returnErrTemplate("", "requested machine state key expired before authorisation completed", state, reqKeyIf)
+		w.WriteHeader(http.StatusInternalServerError)
+		w.Write(response)
+		return
+	}
+
+	_, err = logic.GetUser(userClaims.getUserName())
+	if err != nil { // user must not exists, so try to make one
+		if err = addUser(userClaims.getUserName()); err != nil {
+			logger.Log(1, "could not create new user: ", userClaims.getUserName())
+			return
+		}
+	}
+	newPass, fetchErr := fetchPassValue("")
+	if fetchErr != nil {
+		return
+	}
+	jwt, jwtErr := logic.VerifyAuthRequest(models.UserAuthParams{
+		UserName: userClaims.getUserName(),
+		Password: newPass,
+	})
+	if jwtErr != nil {
+		logger.Log(1, "could not parse jwt for user", userClaims.getUserName())
+		return
+	}
+
+	logger.Log(1, "headless SSO login by user:", userClaims.getUserName())
+
+	// Send OK to user in the browser
+	var response bytes.Buffer
+	if err := ssoCallbackTemplate.Execute(&response, ssoCallbackTemplateConfig{
+		User: userClaims.getUserName(),
+		Verb: "Authenticated",
+	}); err != nil {
+		logger.Log(0, "Could not render SSO callback template ", err.Error())
+		response := returnErrTemplate(userClaims.getUserName(), "Could not render SSO callback template", state, reqKeyIf)
+		w.WriteHeader(http.StatusInternalServerError)
+		w.Write(response)
+	} else {
+		w.WriteHeader(http.StatusOK)
+		w.Write(response.Bytes())
+	}
+	reqKeyIf.Pass = fmt.Sprintf("JWT: %s", jwt)
+	if err = netcache.Set(state, reqKeyIf); err != nil {
+		logger.Log(0, "failed to set netcache for user", reqKeyIf.User, "-", err.Error())
+	}
+}

+ 48 - 47
auth/nodecallback.go

@@ -13,7 +13,6 @@ import (
 	"github.com/gravitl/netmaker/logic/pro/netcache"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models/promodels"
-	"github.com/gravitl/netmaker/servercfg"
 )
 
 var (
@@ -41,7 +40,7 @@ func HandleNodeSSOCallback(w http.ResponseWriter, r *http.Request) {
 	var userClaims, err = functions[get_user_info].(func(string, string) (*OAuthUser, error))(state, code)
 	if err != nil {
 		logger.Log(0, "error when getting user info from callback:", err.Error())
-		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
+		handleOauthNotConfigured(w)
 		return
 	}
 
@@ -107,12 +106,12 @@ func HandleNodeSSOCallback(w http.ResponseWriter, r *http.Request) {
 	var answer string
 	// The registation logic is starting here:
 	// we request access key with 1 use for the required network
-	accessToken, err := requestAccessKey(reqKeyIf.Network, 1, userClaims.getUserName())
-	if err != nil {
-		answer = fmt.Sprintf("Error from the netmaker controller %s", err.Error())
-	} else {
-		answer = fmt.Sprintf("AccessToken: %s", accessToken)
-	}
+	// accessToken, err := requestAccessKey(reqKeyIf.Network, 1, userClaims.getUserName())
+	// if err != nil {
+	// 	answer = fmt.Sprintf("Error from the netmaker controller %s", err.Error())
+	// } else {
+	// 	answer = fmt.Sprintf("AccessToken: %s", accessToken)
+	// }
 	logger.Log(0, "Updating the token for the client request ... ")
 	// Give the user the access token via Pass in the DB
 	reqKeyIf.Pass = answer
@@ -135,7 +134,9 @@ func setNetcache(ncache *netcache.CValue, state string) error {
 
 func returnErrTemplate(uname, message, state string, ncache *netcache.CValue) []byte {
 	var response bytes.Buffer
-	ncache.Pass = message
+	if ncache != nil {
+		ncache.Pass = message
+	}
 	err := ssoErrCallbackTemplate.Execute(&response, ssoCallbackTemplateConfig{
 		User: uname,
 		Verb: message,
@@ -183,44 +184,44 @@ func RegisterNodeSSO(w http.ResponseWriter, r *http.Request) {
 
 // == private ==
 // API to create an access key for a given network with a given name
-func requestAccessKey(network string, uses int, name string) (accessKey string, err error) {
-
-	var sAccessKey models.AccessKey
-	var sNetwork models.Network
-
-	sNetwork, err = logic.GetParentNetwork(network)
-	if err != nil {
-		logger.Log(0, "err calling GetParentNetwork API=%s", err.Error())
-		return "", fmt.Errorf("internal controller error %s", err.Error())
-	}
-	// If a key already exists, we recreate it.
-	// @TODO Is that a preferred handling ? We could also trying to re-use.
-	// can happen if user started log in but did not finish
-	for _, currentkey := range sNetwork.AccessKeys {
-		if currentkey.Name == name {
-			logger.Log(0, "erasing existing AccessKey for: ", name)
-			err = logic.DeleteKey(currentkey.Name, network)
-			if err != nil {
-				logger.Log(0, "err calling CreateAccessKey API ", err.Error())
-				return "", fmt.Errorf("key already exists. Contact admin to resolve")
-			}
-			break
-		}
-	}
-	// Only one usage is needed - for the next time new access key will be required
-	// it will be created next time after another IdP approval
-	sAccessKey.Uses = 1
-	sAccessKey.Name = name
-
-	accessToken, err := logic.CreateAccessKey(sAccessKey, sNetwork)
-	if err != nil {
-		logger.Log(0, "err calling CreateAccessKey API ", err.Error())
-		return "", fmt.Errorf("error from the netmaker controller %s", err.Error())
-	} else {
-		logger.Log(1, "created access key", sAccessKey.Name, "on", network)
-	}
-	return accessToken.AccessString, nil
-}
+// func requestAccessKey(network string, uses int, name string) (accessKey string, err error) {
+
+// 	var sAccessKey models.AccessKey
+// 	var sNetwork models.Network
+
+// 	sNetwork, err = logic.GetParentNetwork(network)
+// 	if err != nil {
+// 		logger.Log(0, "err calling GetParentNetwork API=%s", err.Error())
+// 		return "", fmt.Errorf("internal controller error %s", err.Error())
+// 	}
+// 	// If a key already exists, we recreate it.
+// 	// @TODO Is that a preferred handling ? We could also trying to re-use.
+// 	// can happen if user started log in but did not finish
+// 	for _, currentkey := range sNetwork.AccessKeys {
+// 		if currentkey.Name == name {
+// 			logger.Log(0, "erasing existing AccessKey for: ", name)
+// 			err = logic.DeleteKey(currentkey.Name, network)
+// 			if err != nil {
+// 				logger.Log(0, "err calling CreateAccessKey API ", err.Error())
+// 				return "", fmt.Errorf("key already exists. Contact admin to resolve")
+// 			}
+// 			break
+// 		}
+// 	}
+// 	// Only one usage is needed - for the next time new access key will be required
+// 	// it will be created next time after another IdP approval
+// 	sAccessKey.Uses = 1
+// 	sAccessKey.Name = name
+
+// 	accessToken, err := logic.CreateAccessKey(sAccessKey, sNetwork)
+// 	if err != nil {
+// 		logger.Log(0, "err calling CreateAccessKey API ", err.Error())
+// 		return "", fmt.Errorf("error from the netmaker controller %s", err.Error())
+// 	} else {
+// 		logger.Log(1, "created access key", sAccessKey.Name, "on", network)
+// 	}
+// 	return accessToken.AccessString, nil
+// }
 
 func isUserIsAllowed(username, network string, shouldAddUser bool) (*models.User, error) {
 

+ 3 - 9
auth/nodesession.go

@@ -1,7 +1,6 @@
 package auth
 
 import (
-	"encoding/hex"
 	"encoding/json"
 	"fmt"
 	"strings"
@@ -45,7 +44,7 @@ func SessionHandler(conn *websocket.Conn) {
 	req.Pass = ""
 	req.User = ""
 	// Add any extra parameter provided in the configuration to the Authorize Endpoint request??
-	stateStr := hex.EncodeToString([]byte(logic.RandomString(node_signin_length)))
+	stateStr := logic.RandomString(node_signin_length)
 	if err := netcache.Set(stateStr, req); err != nil {
 		logger.Log(0, "Failed to process sso request -", err.Error())
 		return
@@ -86,7 +85,7 @@ func SessionHandler(conn *websocket.Conn) {
 			}
 			return
 		}
-		user, err := isUserIsAllowed(loginMessage.User, loginMessage.Network, false)
+		_, err = isUserIsAllowed(loginMessage.User, loginMessage.Network, false)
 		if err != nil {
 			err = conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
 			if err != nil {
@@ -94,12 +93,7 @@ func SessionHandler(conn *websocket.Conn) {
 			}
 			return
 		}
-		accessToken, err := requestAccessKey(loginMessage.Network, 1, user.UserName)
-		if err != nil {
-			req.Pass = fmt.Sprintf("Error from the netmaker controller %s", err.Error())
-		} else {
-			req.Pass = fmt.Sprintf("AccessToken: %s", accessToken)
-		}
+
 		// Give the user the access token via Pass in the DB
 		if err = netcache.Set(stateStr, req); err != nil {
 			logger.Log(0, "machine failed to complete join on network,", loginMessage.Network, "-", err.Error())

+ 4 - 7
auth/oidc.go

@@ -50,16 +50,13 @@ func initOIDC(redirectURL string, clientID string, clientSecret string, issuer s
 
 func handleOIDCLogin(w http.ResponseWriter, r *http.Request) {
 	var oauth_state_string = logic.RandomString(user_signin_length)
-	if auth_provider == nil && servercfg.GetFrontendURL() != "" {
-		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
-		return
-	} else if auth_provider == nil {
-		fmt.Fprintf(w, "%s", []byte("no frontend URL was provided and an OAuth login was attempted\nplease reconfigure server to use OAuth or use basic credentials"))
+	if auth_provider == nil {
+		handleOauthNotConfigured(w)
 		return
 	}
 
 	if err := logic.SetState(oauth_state_string); err != nil {
-		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
+		handleOauthNotConfigured(w)
 		return
 	}
 	var url = auth_provider.AuthCodeURL(oauth_state_string)
@@ -73,7 +70,7 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) {
 	var content, err = getOIDCUserInfo(rState, rCode)
 	if err != nil {
 		logger.Log(1, "error when getting user info from callback:", err.Error())
-		http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?oauth=callback-error", http.StatusTemporaryRedirect)
+		handleOauthNotConfigured(w)
 		return
 	}
 	_, err = logic.GetUser(content.Email)

+ 101 - 53
auth/templates.go

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 5 - 1
cli/cmd/context/set.go

@@ -12,6 +12,7 @@ var (
 	username  string
 	password  string
 	masterKey string
+	sso       bool
 )
 
 var contextSetCmd = &cobra.Command{
@@ -25,8 +26,9 @@ var contextSetCmd = &cobra.Command{
 			Username:  username,
 			Password:  password,
 			MasterKey: masterKey,
+			SSO:       sso,
 		}
-		if ctx.Username == "" && ctx.MasterKey == "" {
+		if ctx.Username == "" && ctx.MasterKey == "" && !ctx.SSO {
 			cmd.Usage()
 			log.Fatal("Either username/password or master key is required")
 		}
@@ -36,9 +38,11 @@ var contextSetCmd = &cobra.Command{
 
 func init() {
 	contextSetCmd.Flags().StringVar(&endpoint, "endpoint", "", "Endpoint of the API Server")
+	contextSetCmd.MarkFlagRequired("endpoint")
 	contextSetCmd.Flags().StringVar(&username, "username", "", "Username")
 	contextSetCmd.Flags().StringVar(&password, "password", "", "Password")
 	contextSetCmd.MarkFlagsRequiredTogether("username", "password")
+	contextSetCmd.Flags().BoolVar(&sso, "sso", false, "Login via Single Sign On (SSO) ?")
 	contextSetCmd.Flags().StringVar(&masterKey, "master_key", "", "Master Key")
 	rootCmd.AddCommand(contextSetCmd)
 }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 20 - 0
cli/cmd/host/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)
+}

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

@@ -0,0 +1,22 @@
+package host
+
+import (
+	"strings"
+
+	"github.com/gravitl/netmaker/cli/functions"
+	"github.com/spf13/cobra"
+)
+
+var hostCreateRelayCmd = &cobra.Command{
+	Use:   "create_relay [HOST ID] [RELAYED HOST IDS (comma separated)]",
+	Args:  cobra.ExactArgs(2),
+	Short: "Turn a Host into a Relay",
+	Long:  `Turn a Host into a Relay`,
+	Run: func(cmd *cobra.Command, args []string) {
+		functions.PrettyPrint(functions.CreateRelay(args[0], strings.Split(args[1], ",")))
+	},
+}
+
+func init() {
+	rootCmd.AddCommand(hostCreateRelayCmd)
+}

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

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

+ 20 - 0
cli/cmd/host/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)
+}

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

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

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

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

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

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

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

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

+ 0 - 35
cli/cmd/keys/create.go

@@ -1,35 +0,0 @@
-package keys
-
-import (
-	"log"
-	"strconv"
-
-	"github.com/gravitl/netmaker/cli/functions"
-	"github.com/gravitl/netmaker/models"
-	"github.com/spf13/cobra"
-)
-
-var keyName string
-
-var keysCreateCmd = &cobra.Command{
-	Use:   "create [NETWORK NAME] [NUM USES]",
-	Args:  cobra.ExactArgs(2),
-	Short: "Create an access key",
-	Long:  `Create an access key`,
-	Run: func(cmd *cobra.Command, args []string) {
-		keyUses, err := strconv.ParseInt(args[1], 10, 64)
-		if err != nil {
-			log.Fatal(err)
-		}
-		key := &models.AccessKey{Uses: int(keyUses)}
-		if keyName != "" {
-			key.Name = keyName
-		}
-		functions.PrettyPrint(functions.CreateKey(args[0], key))
-	},
-}
-
-func init() {
-	keysCreateCmd.Flags().StringVar(&keyName, "name", "", "Name of the key")
-	rootCmd.AddCommand(keysCreateCmd)
-}

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

@@ -1,23 +0,0 @@
-package keys
-
-import (
-	"fmt"
-
-	"github.com/gravitl/netmaker/cli/functions"
-	"github.com/spf13/cobra"
-)
-
-var keysDeleteCmd = &cobra.Command{
-	Use:   "delete [NETWORK NAME] [KEY NAME]",
-	Args:  cobra.ExactArgs(2),
-	Short: "Delete a key",
-	Long:  `Delete a key`,
-	Run: func(cmd *cobra.Command, args []string) {
-		functions.DeleteKey(args[0], args[1])
-		fmt.Println("Success")
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(keysDeleteCmd)
-}

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

@@ -1,20 +0,0 @@
-package keys
-
-import (
-	"github.com/gravitl/netmaker/cli/functions"
-	"github.com/spf13/cobra"
-)
-
-var keysListCmd = &cobra.Command{
-	Use:   "list [NETWORK NAME]",
-	Args:  cobra.ExactArgs(1),
-	Short: "List all keys associated with a network",
-	Long:  `List all keys associated with a network`,
-	Run: func(cmd *cobra.Command, args []string) {
-		functions.PrettyPrint(functions.GetKeys(args[0]))
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(keysListCmd)
-}

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

@@ -1,38 +0,0 @@
-package keys
-
-import (
-	"os"
-
-	"github.com/spf13/cobra"
-)
-
-// rootCmd represents the base command when called without any subcommands
-var rootCmd = &cobra.Command{
-	Use:   "keys",
-	Short: "Manage access keys associated with a network",
-	Long:  `Manage access keys associated with a network`,
-	// Run: func(cmd *cobra.Command, args []string) { },
-}
-
-// GetRoot returns the root subcommand
-func GetRoot() *cobra.Command {
-	return rootCmd
-}
-
-// Execute adds all child commands to the root command and sets flags appropriately.
-// This is called by main.main(). It only needs to happen once to the rootCmd.
-func Execute() {
-	err := rootCmd.Execute()
-	if err != nil {
-		os.Exit(1)
-	}
-}
-
-func init() {
-	// Here you will define your flags and configuration settings.
-	// Cobra supports persistent flags, which, if defined here,
-	// will be global for your application.
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-}

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

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

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

@@ -35,25 +35,16 @@ var networkCreateCmd = &cobra.Command{
 			if udpHolePunch {
 				network.DefaultUDPHolePunch = "yes"
 			}
-			if localNetwork {
-				network.IsLocal = "yes"
-			}
 			if defaultACL {
 				network.DefaultACL = "yes"
 			}
-			if pointToSite {
-				network.IsPointToSite = "yes"
-			}
 			network.DefaultInterface = defaultInterface
 			network.DefaultListenPort = int32(defaultListenPort)
 			network.NodeLimit = int32(nodeLimit)
-			network.DefaultPostUp = defaultPostUp
-			network.DefaultPostDown = defaultPostDown
 			network.DefaultKeepalive = int32(defaultKeepalive)
 			if allowManualSignUp {
 				network.AllowManualSignUp = "yes"
 			}
-			network.LocalRange = localRange
 			network.DefaultExtClientDNS = defaultExtClientDNS
 			network.DefaultMTU = int32(defaultMTU)
 		}
@@ -68,13 +59,8 @@ 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().BoolVar(&pointToSite, "point_to_site", false, "Enforce all clients to have only 1 central peer ?")
 	networkCreateCmd.Flags().StringVar(&defaultInterface, "interface", "", "Name of the network interface")
-	networkCreateCmd.Flags().StringVar(&defaultPostUp, "post_up", "", "Commands to run after server is up `;` separated")
-	networkCreateCmd.Flags().StringVar(&defaultPostDown, "post_down", "", "Commands to run after server is down `;` separated")
-	networkCreateCmd.Flags().StringVar(&localRange, "local_range", "", "Local CIDR range")
 	networkCreateCmd.Flags().StringVar(&defaultExtClientDNS, "ext_client_dns", "", "IPv4 address of DNS server to be used by external clients")
 	networkCreateCmd.Flags().IntVar(&defaultListenPort, "listen_port", 51821, "Default wireguard port each node will attempt to use")
 	networkCreateCmd.Flags().IntVar(&nodeLimit, "node_limit", 999999999, "Maximum number of nodes that can be associated with this network")

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

@@ -6,17 +6,12 @@ var (
 	address                   string
 	address6                  string
 	udpHolePunch              bool
-	localNetwork              bool
 	defaultACL                bool
-	pointToSite               bool
 	defaultInterface          string
 	defaultListenPort         int
 	nodeLimit                 int
-	defaultPostUp             string
-	defaultPostDown           string
 	defaultKeepalive          int
 	allowManualSignUp         bool
-	localRange                string
 	defaultExtClientDNS       string
 	defaultMTU                int
 )

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

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

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

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

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

@@ -38,25 +38,16 @@ var networkUpdateCmd = &cobra.Command{
 			if udpHolePunch {
 				network.DefaultUDPHolePunch = "yes"
 			}
-			if localNetwork {
-				network.IsLocal = "yes"
-			}
 			if defaultACL {
 				network.DefaultACL = "yes"
 			}
-			if pointToSite {
-				network.IsPointToSite = "yes"
-			}
 			network.DefaultInterface = defaultInterface
 			network.DefaultListenPort = int32(defaultListenPort)
 			network.NodeLimit = int32(nodeLimit)
-			network.DefaultPostUp = defaultPostUp
-			network.DefaultPostDown = defaultPostDown
 			network.DefaultKeepalive = int32(defaultKeepalive)
 			if allowManualSignUp {
 				network.AllowManualSignUp = "yes"
 			}
-			network.LocalRange = localRange
 			network.DefaultExtClientDNS = defaultExtClientDNS
 			network.DefaultMTU = int32(defaultMTU)
 		}
@@ -69,13 +60,8 @@ 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().BoolVar(&pointToSite, "point_to_site", false, "Enforce all clients to have only 1 central peer ?")
 	networkUpdateCmd.Flags().StringVar(&defaultInterface, "interface", "", "Name of the network interface")
-	networkUpdateCmd.Flags().StringVar(&defaultPostUp, "post_up", "", "Commands to run after server is up `;` separated")
-	networkUpdateCmd.Flags().StringVar(&defaultPostDown, "post_down", "", "Commands to run after server is down `;` separated")
-	networkUpdateCmd.Flags().StringVar(&localRange, "local_range", "", "Local CIDR range")
 	networkUpdateCmd.Flags().StringVar(&defaultExtClientDNS, "ext_client_dns", "", "IPv4 address of DNS server to be used by external clients")
 	networkUpdateCmd.Flags().IntVar(&defaultListenPort, "listen_port", 0, "Default wireguard port each node will attempt to use")
 	networkUpdateCmd.Flags().IntVar(&nodeLimit, "node_limit", 0, "Maximum number of nodes that can be associated with this network")

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

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

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

@@ -15,10 +15,9 @@ var nodeCreateEgressCmd = &cobra.Command{
 	Long:  `Turn a Node into a Egress`,
 	Run: func(cmd *cobra.Command, args []string) {
 		egress := &models.EgressGatewayRequest{
-			NetID:     args[0],
-			NodeID:    args[1],
-			Interface: networkInterface,
-			Ranges:    strings.Split(args[2], ","),
+			NetID:  args[0],
+			NodeID: args[1],
+			Ranges: strings.Split(args[2], ","),
 		}
 		if natEnabled {
 			egress.NatEnabled = "yes"
@@ -28,7 +27,6 @@ var nodeCreateEgressCmd = &cobra.Command{
 }
 
 func init() {
-	nodeCreateEgressCmd.Flags().StringVar(&networkInterface, "interface", "", "Network interface name (ex:- eth0)")
 	nodeCreateEgressCmd.Flags().BoolVar(&natEnabled, "nat", false, "Enable NAT for Egress Traffic ?")
 	rootCmd.AddCommand(nodeCreateEgressCmd)
 }

+ 0 - 22
cli/cmd/node/create_relay.go

@@ -1,22 +0,0 @@
-package node
-
-import (
-	"strings"
-
-	"github.com/gravitl/netmaker/cli/functions"
-	"github.com/spf13/cobra"
-)
-
-var nodeCreateRelayCmd = &cobra.Command{
-	Use:   "create_relay [NETWORK NAME] [NODE ID] [RELAY ADDRESSES (comma separated)]",
-	Args:  cobra.ExactArgs(3),
-	Short: "Turn a Node into a Relay",
-	Long:  `Turn a Node into a Relay`,
-	Run: func(cmd *cobra.Command, args []string) {
-		functions.PrettyPrint(functions.CreateRelay(args[0], args[1], strings.Split(args[2], ",")))
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(nodeCreateRelayCmd)
-}

+ 0 - 20
cli/cmd/node/delete_relay.go

@@ -1,20 +0,0 @@
-package node
-
-import (
-	"github.com/gravitl/netmaker/cli/functions"
-	"github.com/spf13/cobra"
-)
-
-var nodeDeleteRelayCmd = &cobra.Command{
-	Use:   "delete_relay [NETWORK NAME] [NODE ID]",
-	Args:  cobra.ExactArgs(2),
-	Short: "Delete Relay role from a Node",
-	Long:  `Delete Relay role from a Node`,
-	Run: func(cmd *cobra.Command, args []string) {
-		functions.PrettyPrint(functions.DeleteRelay(args[0], args[1]))
-	},
-}
-
-func init() {
-	rootCmd.AddCommand(nodeDeleteRelayCmd)
-}

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

@@ -1,28 +1,21 @@
 package node
 
 var (
-	networkInterface       string
 	natEnabled             bool
 	failover               bool
 	networkName            string
 	nodeDefinitionFilePath string
-	endpoint               string
-	listenPort             int
 	address                string
 	address6               string
 	localAddress           string
 	name                   string
 	postUp                 string
 	postDown               string
-	allowedIPs             string
 	keepAlive              int
 	relayAddrs             string
 	egressGatewayRanges    string
-	localRange             string
-	mtu                    int
 	expirationDateTime     int
 	defaultACL             bool
 	dnsOn                  bool
 	disconnect             bool
-	networkHub             bool
 )

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

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

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

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

+ 4 - 32
cli/cmd/node/update.go

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

+ 8 - 17
cli/cmd/root.go

@@ -4,10 +4,12 @@ import (
 	"os"
 
 	"github.com/gravitl/netmaker/cli/cmd/acl"
+	"github.com/gravitl/netmaker/cli/cmd/commons"
 	"github.com/gravitl/netmaker/cli/cmd/context"
 	"github.com/gravitl/netmaker/cli/cmd/dns"
+	"github.com/gravitl/netmaker/cli/cmd/enrollment_key"
 	"github.com/gravitl/netmaker/cli/cmd/ext_client"
-	"github.com/gravitl/netmaker/cli/cmd/keys"
+	"github.com/gravitl/netmaker/cli/cmd/host"
 	"github.com/gravitl/netmaker/cli/cmd/metrics"
 	"github.com/gravitl/netmaker/cli/cmd/network"
 	"github.com/gravitl/netmaker/cli/cmd/network_user"
@@ -20,12 +22,9 @@ import (
 
 // rootCmd represents the base command when called without any subcommands
 var rootCmd = &cobra.Command{
-	Use:   "netmaker",
+	Use:   "nmctl",
 	Short: "CLI for interacting with Netmaker Server",
 	Long:  `CLI for interacting with Netmaker Server`,
-	// Uncomment the following line if your bare application
-	// has an action associated with it:
-	// Run: func(cmd *cobra.Command, args []string) { },
 }
 
 // GetRoot returns the root of all subcommands
@@ -43,20 +42,10 @@ func Execute() {
 }
 
 func init() {
-	// Here you will define your flags and configuration settings.
-	// Cobra supports persistent flags, which, if defined here,
-	// will be global for your application.
-
-	// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.tctl.yaml)")
-
-	// Cobra also supports local flags, which will only run
-	// when this action is called directly.
-	rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
-
-	// IMP: Bind subcommands here
+	rootCmd.PersistentFlags().StringVarP(&commons.OutputFormat, "output", "o", "", "List output in specific format (Enum:- json)")
+	// Bind subcommands here
 	rootCmd.AddCommand(network.GetRoot())
 	rootCmd.AddCommand(context.GetRoot())
-	rootCmd.AddCommand(keys.GetRoot())
 	rootCmd.AddCommand(acl.GetRoot())
 	rootCmd.AddCommand(node.GetRoot())
 	rootCmd.AddCommand(dns.GetRoot())
@@ -66,4 +55,6 @@ func init() {
 	rootCmd.AddCommand(usergroup.GetRoot())
 	rootCmd.AddCommand(metrics.GetRoot())
 	rootCmd.AddCommand(network_user.GetRoot())
+	rootCmd.AddCommand(host.GetRoot())
+	rootCmd.AddCommand(enrollment_key.GetRoot())
 }

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

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

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

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

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

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

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

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

+ 1 - 0
cli/config/config.go

@@ -17,6 +17,7 @@ type Context struct {
 	MasterKey string `yaml:"masterkey,omitempty"`
 	Current   bool   `yaml:"current,omitempty"`
 	AuthToken string `yaml:"auth_token,omitempty"`
+	SSO       bool   `yaml:"sso,omitempty"`
 }
 
 var (

+ 22 - 0
cli/functions/enrollment_keys.go

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

+ 50 - 0
cli/functions/host.go

@@ -0,0 +1,50 @@
+package functions
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/gravitl/netmaker/models"
+)
+
+type hostNetworksUpdatePayload struct {
+	Networks []string `json:"networks"`
+}
+
+// GetHosts - fetch all host entries
+func GetHosts() *[]models.ApiHost {
+	return request[[]models.ApiHost](http.MethodGet, "/api/hosts", nil)
+}
+
+// DeleteHost - delete a host
+func DeleteHost(hostID string) *models.ApiHost {
+	return request[models.ApiHost](http.MethodDelete, "/api/hosts/"+hostID, nil)
+}
+
+// UpdateHost - update a host
+func UpdateHost(hostID string, body *models.ApiHost) *models.ApiHost {
+	return request[models.ApiHost](http.MethodPut, "/api/hosts/"+hostID, body)
+}
+
+// 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
+func CreateRelay(hostID string, relayedHosts []string) *models.ApiHost {
+	return request[models.ApiHost](http.MethodPost, fmt.Sprintf("/api/hosts/%s/relay", hostID), &models.HostRelayRequest{
+		HostID:       hostID,
+		RelayedHosts: relayedHosts,
+	})
+}
+
+// DeleteRelay - remove relay role from a host
+func DeleteRelay(hostID string) *models.ApiHost {
+	return request[models.ApiHost](http.MethodDelete, fmt.Sprintf("/api/hosts/%s/relay", hostID), nil)
+}

+ 71 - 0
cli/functions/http_client.go

@@ -3,18 +3,89 @@ package functions
 import (
 	"bytes"
 	"encoding/json"
+	"fmt"
 	"io"
 	"log"
 	"net/http"
+	"net/url"
+	"os"
+	"os/signal"
+	"strings"
 
+	"github.com/gorilla/websocket"
 	"github.com/gravitl/netmaker/cli/config"
+	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 )
 
+func ssoLogin(endpoint string) string {
+	var (
+		authToken string
+		interrupt = make(chan os.Signal, 1)
+		url, _    = url.Parse(endpoint)
+		socketURL = fmt.Sprintf("wss://%s/api/oauth/headless", url.Host)
+	)
+	signal.Notify(interrupt, os.Interrupt)
+	conn, _, err := websocket.DefaultDialer.Dial(socketURL, nil)
+	if err != nil {
+		log.Fatal("error connecting to endpoint ", socketURL, err.Error())
+	}
+	defer conn.Close()
+	_, msg, err := conn.ReadMessage()
+	if err != nil {
+		log.Fatal("error reading from server: ", err.Error())
+	}
+	fmt.Printf("Please visit:\n %s \n to authenticate\n", string(msg))
+	done := make(chan struct{})
+	defer close(done)
+	go func() {
+		for {
+			msgType, msg, err := conn.ReadMessage()
+			if err != nil {
+				if msgType < 0 {
+					done <- struct{}{}
+					return
+				}
+				if !strings.Contains(err.Error(), "normal") {
+					log.Fatal("read error: ", err.Error())
+				}
+				return
+			}
+			if msgType == websocket.CloseMessage {
+				done <- struct{}{}
+				return
+			}
+			if strings.Contains(string(msg), "JWT: ") {
+				authToken = strings.TrimPrefix(string(msg), "JWT: ")
+			} else {
+				logger.Log(0, "Message from server:", string(msg))
+				return
+			}
+		}
+	}()
+	for {
+		select {
+		case <-done:
+			return authToken
+		case <-interrupt:
+			err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+			if err != nil {
+				logger.Log(0, "write close:", err.Error())
+			}
+			return authToken
+		}
+	}
+}
+
 func getAuthToken(ctx config.Context, force bool) string {
 	if !force && ctx.AuthToken != "" {
 		return ctx.AuthToken
 	}
+	if ctx.SSO {
+		authToken := ssoLogin(ctx.Endpoint)
+		config.SetAuthToken(authToken)
+		return authToken
+	}
 	authParams := &models.UserAuthParams{UserName: ctx.Username, Password: ctx.Password}
 	payload, err := json.Marshal(authParams)
 	if err != nil {

+ 0 - 23
cli/functions/keys.go

@@ -1,23 +0,0 @@
-package functions
-
-import (
-	"fmt"
-	"net/http"
-
-	"github.com/gravitl/netmaker/models"
-)
-
-// GetKeys - fetch all access keys of a network
-func GetKeys(networkName string) *[]models.AccessKey {
-	return request[[]models.AccessKey](http.MethodGet, fmt.Sprintf("/api/networks/%s/keys", networkName), nil)
-}
-
-// CreateKey - create an access key
-func CreateKey(networkName string, key *models.AccessKey) *models.AccessKey {
-	return request[models.AccessKey](http.MethodPost, fmt.Sprintf("/api/networks/%s/keys", networkName), key)
-}
-
-// DeleteKey - delete an access key
-func DeleteKey(networkName, keyName string) {
-	request[string](http.MethodDelete, fmt.Sprintf("/api/networks/%s/keys/%s", networkName, keyName), nil)
-}

+ 13 - 27
cli/functions/node.go

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

+ 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,

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

@@ -0,0 +1,84 @@
+version: "3.4"
+
+services:
+  netmaker:
+    container_name: netmaker
+    image: gravitl/netmaker:v0.18.5
+    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_LIST: "stun.NETMAKER_BASE_DOMAIN:3478,stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302"
+      SERVER_HOST: "SERVER_PUBLIC_IP"
+      SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
+      COREDNS_ADDR: "SERVER_PUBLIC_IP"
+      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"
+      DEFAULT_PROXY_MODE: "off"
+    ports:
+      - "3478:3478/udp"
+  netmaker-ui:
+    container_name: netmaker-ui
+    image: gravitl/netmaker-ui:v0.18.4
+    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: {}

+ 23 - 38
compose/docker-compose.ee.yml

@@ -3,51 +3,42 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.17.1-ee
-    cap_add: 
-      - NET_ADMIN
-      - NET_RAW
-      - SYS_MODULE
-    sysctls:
-      - net.ipv4.ip_forward=1
-      - net.ipv4.conf.all.src_valid_mark=1
-      - net.ipv6.conf.all.disable_ipv6=0
-      - net.ipv6.conf.all.forwarding=1
+    image: gravitl/netmaker:REPLACE_SERVER_IMAGE_TAG
     restart: always
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
-      - mosquitto_data:/etc/netmaker
     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_LIST: "stun.NETMAKER_BASE_DOMAIN:3478,stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302"
       SERVER_HOST: "SERVER_PUBLIC_IP"
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
       COREDNS_ADDR: "SERVER_PUBLIC_IP"
       DNS_MODE: "on"
       SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN"
       API_PORT: "8081"
-      CLIENT_MODE: "on"
       MASTER_KEY: "REPLACE_MASTER_KEY"
       CORS_ALLOWED_ORIGIN: "*"
       DISPLAY_KEYS: "on"
       DATABASE: "sqlite"
       NODE_ID: "netmaker-server-1"
-      MQ_HOST: "mq"
-      MQ_PORT: "443"
-      MQ_SERVER_PORT: "1883"
-      HOST_NETWORK: "off"
+      SERVER_BROKER_ENDPOINT: "ws://mq:8083/mqtt"
+      MQ_USERNAME: "REPLACE_MQ_USERNAME"
+      MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
+      STUN_PORT: "3478"
       VERBOSITY: "1"
-      MANAGE_IPTABLES: "on"
-      PORT_FORWARD_SERVICES: "dns"
       METRICS_EXPORTER: "on"
       LICENSE_KEY: "YOUR_LICENSE_KEY"
       NETMAKER_ACCOUNT_ID: "YOUR_ACCOUNT_ID"
-      MQ_ADMIN_PASSWORD: "REPLACE_MQ_ADMIN_PASSWORD"
+      DEFAULT_PROXY_MODE: "off"
     ports:
-      - "51821-51830:51821-51830/udp"
+      - "3478:3478/udp"
   netmaker-ui:
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.17.1
+    image: gravitl/netmaker-ui:REPLACE_UI_IMAGE_TAG
     depends_on:
       - netmaker
     links:
@@ -77,21 +68,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:
-      NETMAKER_SERVER_HOST: "https://api.NETMAKER_BASE_DOMAIN"
-    volumes:
-      - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf
-      - /root/wait.sh:/mosquitto/config/wait.sh
-      - mosquitto_data:/mosquitto/data
-      - 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
@@ -125,9 +112,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"
@@ -138,7 +124,6 @@ volumes:
   caddy_conf: {}
   sqldata: {}
   dnsconfig: {}
-  mosquitto_data: {}
   mosquitto_logs: {}
   prometheus_data: {}
   grafana_data: {}

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

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

+ 17 - 26
compose/docker-compose.reference.yml

@@ -3,46 +3,34 @@ version: "3.4"
 services:
   netmaker: # The Primary Server for running Netmaker
     container_name: netmaker
-    image: gravitl/netmaker:v0.17.1
-    cap_add: 
-      - NET_ADMIN
-      - NET_RAW
-      - SYS_MODULE
-    sysctls:
-      - net.ipv4.ip_forward=1
-      - net.ipv4.conf.all.src_valid_mark=1
-      - net.ipv6.conf.all.disable_ipv6=0
-      - net.ipv6.conf.all.forwarding=1
+    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"
       COREDNS_ADDR: "SERVER_PUBLIC_IP" # Address of the CoreDNS server. Defaults to SERVER_HOST
       DNS_MODE: "on" # Enables DNS Mode, meaning all nodes will set hosts file for private dns settings.
       API_PORT: "8081" # The HTTP API port for Netmaker. Used for API calls / communication from front end. If changed, need to change port of BACKEND_URL for netmaker-ui.
-      CLIENT_MODE: "on" # Depricated. CLIENT_MODE should always be ON
       REST_BACKEND: "on" # Enables the REST backend (API running on API_PORT at SERVER_HTTP_HOST). Change to "off" to turn off.
       DISABLE_REMOTE_IP_CHECK: "off" # If turned "on", Server will not set Host based on remote IP check. This is already overridden if SERVER_HOST is set. Turned "off" by default.
       TELEMETRY: "on" # Whether or not to send telemetry data to help improve Netmaker. Switch to "off" to opt out of sending telemetry.
-      RCE: "off" # Enables setting PostUp and PostDown (arbitrary commands) on nodes from the server. Off by default.
       MASTER_KEY: "REPLACE_MASTER_KEY" # The admin master key for accessing the API. Change this in any production installation.
       CORS_ALLOWED_ORIGIN: "*" # The "allowed origin" for API requests. Change to restrict where API requests can come from with comma-separated URLs. ex:- https://dashboard.netmaker.domain1.com,https://dashboard.netmaker.domain2.com
       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)
-      HOST_NETWORK: "off" # whether or not host networking is turned on. Only turn on if configured for host networking (see docker-compose.hostnetwork.yml). Will set host-level settings like iptables.
+      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
-      MANAGE_IPTABLES: "on" # deprecated
-      PORT_FORWARD_SERVICES: "dns" # decide which services to port forward ("dns","ssh", or "mq")
       # this section is for OAuth
       AUTH_PROVIDER: "" # "<azure-ad|github|google|oidc>"
       CLIENT_ID: "" # "<client id of your oauth provider>"
@@ -50,11 +38,12 @@ services:
       FRONTEND_URL: "" # "https://dashboard.<netmaker base domain>"
       AZURE_TENANT: "" # "<only for azure, you may optionally specify the tenant for the OAuth>"
       OIDC_ISSUER: "" # https://oidc.yourprovider.com - URL of oidc provider
+      DEFAULT_PROXY_MODE: "off" # if ON, all new clients will enable proxy by default if OFF, all new clients will disable proxy by default, if AUTO, stick with the existing logic for NAT detection
     ports:
-      - "51821-51830:51821-51830/udp" # wireguard ports
+      - "3478:3478/udp" # the stun port
   netmaker-ui:  # The Netmaker UI Component
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.17.1
+    image: gravitl/netmaker-ui:REPLACE_UI_IMAGE_TAG
     depends_on:
       - netmaker
     links:
@@ -82,17 +71,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"
@@ -102,5 +94,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

+ 13 - 25
compose/docker-compose.yml

@@ -3,48 +3,37 @@ version: "3.4"
 services:
   netmaker:
     container_name: netmaker
-    image: gravitl/netmaker:v0.17.1
-    cap_add: 
-      - NET_ADMIN
-      - NET_RAW
-      - SYS_MODULE
-    sysctls:
-      - net.ipv4.ip_forward=1
-      - net.ipv4.conf.all.src_valid_mark=1
-      - net.ipv6.conf.all.disable_ipv6=0
-      - net.ipv6.conf.all.forwarding=1
+    image: gravitl/netmaker:REPLACE_SERVER_IMAGE_TAG
     restart: always
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
-      - mosquitto_data:/etc/netmaker
     environment:
-      SERVER_NAME: "broker.NETMAKER_BASE_DOMAIN"
+      BROKER_ENDPOINT: "wss://broker.NETMAKER_BASE_DOMAIN"
+      SERVER_NAME: "NETMAKER_BASE_DOMAIN"
+      STUN_LIST: "stun.NETMAKER_BASE_DOMAIN:3478,stun1.netmaker.io:3478,stun2.netmaker.io:3478,stun1.l.google.com:19302,stun2.l.google.com:19302"
       SERVER_HOST: "SERVER_PUBLIC_IP"
       SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443"
       COREDNS_ADDR: "SERVER_PUBLIC_IP"
       DNS_MODE: "on"
       SERVER_HTTP_HOST: "api.NETMAKER_BASE_DOMAIN"
       API_PORT: "8081"
-      CLIENT_MODE: "on"
       MASTER_KEY: "REPLACE_MASTER_KEY"
       CORS_ALLOWED_ORIGIN: "*"
       DISPLAY_KEYS: "on"
       DATABASE: "sqlite"
       NODE_ID: "netmaker-server-1"
-      MQ_HOST: "mq"
-      MQ_PORT: "443"
-      MQ_SERVER_PORT: "1883"
-      HOST_NETWORK: "off"
+      SERVER_BROKER_ENDPOINT: "ws://mq:1883"
       VERBOSITY: "1"
-      MANAGE_IPTABLES: "on"
-      PORT_FORWARD_SERVICES: "dns"
-      MQ_ADMIN_PASSWORD: "REPLACE_MQ_ADMIN_PASSWORD"
+      MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
+      MQ_USERNAME: "REPLACE_MQ_USERNAME"
+      STUN_PORT: "3478"
+      DEFAULT_PROXY_MODE: "off"
     ports:
-      - "51821-51830:51821-51830/udp"
+      - "3478:3478/udp"
   netmaker-ui:
     container_name: netmaker-ui
-    image: gravitl/netmaker-ui:v0.17.1
+    image: gravitl/netmaker-ui:REPLACE_UI_IMAGE_TAG
     depends_on:
       - netmaker
     links:
@@ -80,16 +69,15 @@ services:
     restart: unless-stopped
     command: ["/mosquitto/config/wait.sh"]
     environment:
-      NETMAKER_SERVER_HOST: "https://api.NETMAKER_BASE_DOMAIN"
+      MQ_PASSWORD: "REPLACE_MQ_PASSWORD"
+      MQ_USERNAME: "REPLACE_MQ_USERNAME"
     volumes:
       - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf
       - /root/wait.sh:/mosquitto/config/wait.sh
-      - mosquitto_data:/mosquitto/data
       - mosquitto_logs:/mosquitto/log
 volumes:
   caddy_data: {}
   caddy_conf: {}
   sqldata: {}
   dnsconfig: {}
-  mosquitto_data: {}
   mosquitto_logs: {}

+ 49 - 44
config/config.go

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

+ 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 - 2
config/environments/dev.yaml

@@ -4,8 +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)
-  clientmode: "" # defaults to "on" or CLIENT_MODE (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

+ 8 - 12
controllers/controller.go

@@ -4,11 +4,8 @@ import (
 	"context"
 	"fmt"
 	"net/http"
-	"os"
-	"os/signal"
 	"strings"
 	"sync"
-	"syscall"
 	"time"
 
 	"github.com/gorilla/handlers"
@@ -28,10 +25,13 @@ var HttpHandlers = []interface{}{
 	extClientHandlers,
 	ipHandlers,
 	loggerHandlers,
+	hostHandlers,
+	enrollmentKeyHandlers,
+	legacyHandlers,
 }
 
 // HandleRESTRequests - handles the rest requests
-func HandleRESTRequests(wg *sync.WaitGroup) {
+func HandleRESTRequests(wg *sync.WaitGroup, ctx context.Context) {
 	defer wg.Done()
 
 	r := mux.NewRouter()
@@ -40,7 +40,7 @@ func HandleRESTRequests(wg *sync.WaitGroup) {
 	// should consider analyzing the allowed methods further
 	headersOk := handlers.AllowedHeaders([]string{"Access-Control-Allow-Origin", "X-Requested-With", "Content-Type", "authorization"})
 	originsOk := handlers.AllowedOrigins(strings.Split(servercfg.GetAllowedOrigin(), ","))
-	methodsOk := handlers.AllowedMethods([]string{"GET", "PUT", "POST", "DELETE"})
+	methodsOk := handlers.AllowedMethods([]string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete})
 
 	for _, handler := range HttpHandlers {
 		handler.(func(*mux.Router))(r)
@@ -57,18 +57,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())
 }

+ 58 - 71
controllers/dns.go

@@ -16,23 +16,23 @@ import (
 
 func dnsHandlers(r *mux.Router) {
 
-	r.HandleFunc("/api/dns", logic.SecurityCheck(true, http.HandlerFunc(getAllDNS))).Methods("GET")
-	r.HandleFunc("/api/dns/adm/{network}/nodes", logic.SecurityCheck(false, http.HandlerFunc(getNodeDNS))).Methods("GET")
-	r.HandleFunc("/api/dns/adm/{network}/custom", logic.SecurityCheck(false, http.HandlerFunc(getCustomDNS))).Methods("GET")
-	r.HandleFunc("/api/dns/adm/{network}", logic.SecurityCheck(false, http.HandlerFunc(getDNS))).Methods("GET")
-	r.HandleFunc("/api/dns/{network}", logic.SecurityCheck(false, http.HandlerFunc(createDNS))).Methods("POST")
-	r.HandleFunc("/api/dns/adm/pushdns", logic.SecurityCheck(false, http.HandlerFunc(pushDNS))).Methods("POST")
-	r.HandleFunc("/api/dns/{network}/{domain}", logic.SecurityCheck(false, http.HandlerFunc(deleteDNS))).Methods("DELETE")
+	r.HandleFunc("/api/dns", logic.SecurityCheck(true, http.HandlerFunc(getAllDNS))).Methods(http.MethodGet)
+	r.HandleFunc("/api/dns/adm/{network}/nodes", logic.SecurityCheck(false, http.HandlerFunc(getNodeDNS))).Methods(http.MethodGet)
+	r.HandleFunc("/api/dns/adm/{network}/custom", logic.SecurityCheck(false, http.HandlerFunc(getCustomDNS))).Methods(http.MethodGet)
+	r.HandleFunc("/api/dns/adm/{network}", logic.SecurityCheck(false, http.HandlerFunc(getDNS))).Methods(http.MethodGet)
+	r.HandleFunc("/api/dns/{network}", logic.SecurityCheck(false, http.HandlerFunc(createDNS))).Methods(http.MethodPost)
+	r.HandleFunc("/api/dns/adm/pushdns", logic.SecurityCheck(false, http.HandlerFunc(pushDNS))).Methods(http.MethodPost)
+	r.HandleFunc("/api/dns/{network}/{domain}", logic.SecurityCheck(false, http.HandlerFunc(deleteDNS))).Methods(http.MethodDelete)
 }
 
 // swagger:route GET /api/dns/adm/{network}/nodes dns getNodeDNS
 //
 // Gets node DNS entries associated with a network.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
+//			Security:
+//	  		oauth
 func getNodeDNS(w http.ResponseWriter, r *http.Request) {
 
 	w.Header().Set("Content-Type", "application/json")
@@ -55,14 +55,13 @@ func getNodeDNS(w http.ResponseWriter, r *http.Request) {
 //
 // Gets all DNS entries.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
-//
-// 		Responses:
-//   		200: dnsResponse
+//			Security:
+//	  		oauth
 //
+//			Responses:
+//	  		200: dnsResponse
 func getAllDNS(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	dns, err := logic.GetAllDNS()
@@ -79,14 +78,13 @@ func getAllDNS(w http.ResponseWriter, r *http.Request) {
 //
 // Gets custom DNS entries associated with a network.
 //
-//		Schemes: https
-//
-// 		Security:
-//   		oauth
+//			Schemes: https
 //
-// 		Responses:
-//   		200: dnsResponse
+//			Security:
+//	  		oauth
 //
+//			Responses:
+//	  		200: dnsResponse
 func getCustomDNS(w http.ResponseWriter, r *http.Request) {
 
 	w.Header().Set("Content-Type", "application/json")
@@ -109,14 +107,13 @@ func getCustomDNS(w http.ResponseWriter, r *http.Request) {
 //
 // Gets all DNS entries associated with the network.
 //
-//		Schemes: https
-//
-// 		Security:
-//   		oauth
+//			Schemes: https
 //
-// 		Responses:
-//   		200: dnsResponse
+//			Security:
+//	  		oauth
 //
+//			Responses:
+//	  		200: dnsResponse
 func getDNS(w http.ResponseWriter, r *http.Request) {
 
 	w.Header().Set("Content-Type", "application/json")
@@ -139,14 +136,13 @@ func getDNS(w http.ResponseWriter, r *http.Request) {
 //
 // Create a DNS entry.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
-//
-// 		Responses:
-//   		200: dnsResponse
+//			Security:
+//	  		oauth
 //
+//			Responses:
+//	  		200: dnsResponse
 func createDNS(w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 
@@ -164,7 +160,7 @@ func createDNS(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	entry, err = CreateDNS(entry)
+	entry, err = logic.CreateDNS(entry)
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"),
 			fmt.Sprintf("Failed to create DNS entry %+v: %v", entry, err))
@@ -180,17 +176,14 @@ func createDNS(w http.ResponseWriter, r *http.Request) {
 	}
 	logger.Log(1, "new DNS record added:", entry.Name)
 	if servercfg.IsMessageQueueBackend() {
-		serverNode, err := logic.GetNetworkServerLocal(entry.Network)
-		if err != nil {
-			logger.Log(1, "failed to find server node after DNS update on", entry.Network)
-		} else {
-			if err = logic.ServerUpdate(&serverNode, false); err != nil {
-				logger.Log(1, "failed to update server node after DNS update on", entry.Network)
-			}
-			if err = mq.PublishPeerUpdate(&serverNode, false); err != nil {
+		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))
@@ -202,14 +195,14 @@ func createDNS(w http.ResponseWriter, r *http.Request) {
 //
 // Delete a DNS entry.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
+//			Security:
+//	  		oauth
 //
-//		Responses:
-//			200: stringJSONResponse
-//			*: stringJSONResponse
+//			Responses:
+//				200: stringJSONResponse
+//				*: stringJSONResponse
 func deleteDNS(w http.ResponseWriter, r *http.Request) {
 	// Set header
 	w.Header().Set("Content-Type", "application/json")
@@ -233,22 +226,16 @@ func deleteDNS(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	json.NewEncoder(w).Encode(entrytext + " deleted.")
-}
-
-// CreateDNS - creates a DNS entry
-func CreateDNS(entry models.DNSEntry) (models.DNSEntry, error) {
-
-	data, err := json.Marshal(&entry)
-	if err != nil {
-		return models.DNSEntry{}, err
-	}
-	key, err := logic.GetRecordKey(entry.Name, entry.Network)
-	if err != nil {
-		return models.DNSEntry{}, err
-	}
-	err = database.Insert(key, string(data), database.DNS_TABLE_NAME)
+	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())
+		}
+	}()
 
-	return entry, err
 }
 
 // GetDNSEntry - gets a DNS entry
@@ -270,14 +257,14 @@ func GetDNSEntry(domain string, network string) (models.DNSEntry, error) {
 //
 // Push DNS entries to nameserver.
 //
-//		Schemes: https
+//			Schemes: https
 //
-// 		Security:
-//   		oauth
+//			Security:
+//	  		oauth
 //
-//		Responses:
-//			200: dnsStringJSONResponse
-//			*: dnsStringJSONResponse
+//			Responses:
+//				200: dnsStringJSONResponse
+//				*: dnsStringJSONResponse
 func pushDNS(w http.ResponseWriter, r *http.Request) {
 	// Set header
 	w.Header().Set("Content-Type", "application/json")

+ 93 - 86
controllers/dns_test.go

@@ -1,35 +1,44 @@
 package controller
 
 import (
+	"net"
 	"os"
 	"testing"
 
-	"github.com/gravitl/netmaker/database"
+	"github.com/google/uuid"
+	"github.com/stretchr/testify/assert"
+	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
-	"github.com/stretchr/testify/assert"
 )
 
+var dnsHost models.Host
+
 func TestGetAllDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
+	createHost()
 	t.Run("NoEntries", func(t *testing.T) {
 		entries, err := logic.GetAllDNS()
 		assert.Nil(t, err)
 		assert.Equal(t, []models.DNSEntry(nil), entries)
 	})
 	t.Run("OneEntry", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.3", "", "newhost", "skynet"}
-		CreateDNS(entry)
+		entry := models.DNSEntry{
+			Address: "10.0.0.3", Name: "newhost", Network: "skynet",
+		}
+		_, err := logic.CreateDNS(entry)
+		assert.Nil(t, err)
 		entries, err := logic.GetAllDNS()
 		assert.Nil(t, err)
 		assert.Equal(t, 1, len(entries))
 	})
 	t.Run("MultipleEntry", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.7", "", "anotherhost", "skynet"}
-		CreateDNS(entry)
+		entry := models.DNSEntry{Address: "10.0.0.7", Name: "anotherhost", Network: "skynet"}
+		_, err := logic.CreateDNS(entry)
+		assert.Nil(t, err)
 		entries, err := logic.GetAllDNS()
 		assert.Nil(t, err)
 		assert.Equal(t, 2, len(entries))
@@ -37,26 +46,45 @@ func TestGetAllDNS(t *testing.T) {
 }
 
 func TestGetNodeDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
+	createHost()
 	t.Run("NoNodes", func(t *testing.T) {
 		dns, err := logic.GetNodeDNS("skynet")
 		assert.EqualError(t, err, "could not find any records")
 		assert.Equal(t, []models.DNSEntry(nil), dns)
 	})
 	t.Run("NodeExists", func(t *testing.T) {
-		createnode := models.Node{PublicKey: "DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=", Name: "testnode", Endpoint: "10.0.0.1", MacAddress: "01:02:03:04:05:06", Password: "password", Network: "skynet", OS: "linux", DNSOn: "yes"}
-		err := logic.CreateNode(&createnode)
+		createHost()
+		_, ipnet, _ := net.ParseCIDR("10.0.0.1/32")
+		tmpCNode := models.CommonNode{
+			ID:      uuid.New(),
+			Network: "skynet",
+			Address: *ipnet,
+			DNSOn:   true,
+		}
+		createnode := models.Node{
+			CommonNode: tmpCNode,
+		}
+		err := logic.AssociateNodeToHost(&createnode, &dnsHost)
 		assert.Nil(t, err)
 		dns, err := logic.GetNodeDNS("skynet")
 		assert.Nil(t, err)
 		assert.Equal(t, "10.0.0.1", dns[0].Address)
 	})
 	t.Run("MultipleNodes", func(t *testing.T) {
-		createnode := &models.Node{PublicKey: "DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=", Endpoint: "10.100.100.3", MacAddress: "01:02:03:04:05:07", Password: "password", Network: "skynet"}
-		err := logic.CreateNode(createnode)
+		_, ipnet, _ := net.ParseCIDR("10.100.100.3/32")
+		tmpCNode := models.CommonNode{
+			ID:      uuid.New(),
+			Network: "skynet",
+			Address: *ipnet,
+			DNSOn:   true,
+		}
+		createnode := models.Node{
+			CommonNode: tmpCNode,
+		}
+		err := logic.AssociateNodeToHost(&createnode, &dnsHost)
 		assert.Nil(t, err)
 		dns, err := logic.GetNodeDNS("skynet")
 		assert.Nil(t, err)
@@ -64,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) {
@@ -85,15 +112,17 @@ 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", "", "newhost", "skynet"}
-		CreateDNS(entry)
+		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")
 		assert.Nil(t, err)
 		assert.Equal(t, 1, len(dns))
 	})
 	t.Run("MultipleEntries", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.4", "", "host4", "skynet"}
-		CreateDNS(entry)
+		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")
 		assert.Nil(t, err)
 		assert.Equal(t, 2, len(dns))
@@ -101,7 +130,6 @@ func TestGetCustomDNS(t *testing.T) {
 }
 
 func TestGetDNSEntryNum(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
@@ -111,8 +139,8 @@ 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"}
-		_, err := CreateDNS(entry)
+		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")
 		assert.Nil(t, err)
@@ -120,7 +148,6 @@ func TestGetDNSEntryNum(t *testing.T) {
 	})
 }
 func TestGetDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
@@ -130,8 +157,8 @@ 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"}
-		_, err := CreateDNS(entry)
+		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")
 		t.Log(dns)
@@ -150,8 +177,8 @@ 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"}
-		_, err := CreateDNS(entry)
+		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")
 		t.Log(dns)
@@ -164,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"}
-	dns, err := CreateDNS(entry)
+	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) {
@@ -204,12 +229,13 @@ func TestSetDNS(t *testing.T) {
 		assert.False(t, info.IsDir())
 		content, err := os.ReadFile("./config/dnsconfig/netmaker.hosts")
 		assert.Nil(t, err)
-		assert.Contains(t, string(content), "testnode.skynet")
+		assert.Contains(t, string(content), "linuxhost.skynet")
 	})
 	t.Run("EntryExists", func(t *testing.T) {
-		entry := models.DNSEntry{"10.0.0.3", "", "newhost", "skynet"}
-		CreateDNS(entry)
-		err := logic.SetDNS()
+		entry := models.DNSEntry{Address: "10.0.0.3", Name: "newhost", Network: "skynet"}
+		_, err := logic.CreateDNS(entry)
+		assert.Nil(t, err)
+		err = logic.SetDNS()
 		assert.Nil(t, err)
 		info, err := os.Stat("./config/dnsconfig/netmaker.hosts")
 		assert.Nil(t, err)
@@ -222,13 +248,12 @@ 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"}
-	CreateDNS(entry)
+	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")
 		assert.EqualError(t, err, "no result found")
@@ -251,40 +276,12 @@ func TestGetDNSEntry(t *testing.T) {
 	})
 }
 
-//	func TestUpdateDNS(t *testing.T) {
-//		var newentry models.DNSEntry
-//		database.InitializeDatabase()
-//		deleteAllDNS(t)
-//		deleteAllNetworks()
-//		createNet()
-//		entry := models.DNSEntry{"10.0.0.2", "newhost", "skynet"}
-//		CreateDNS(entry)
-//		t.Run("change address", func(t *testing.T) {
-//			newentry.Address = "10.0.0.75"
-//			updated, err := UpdateDNS(newentry, entry)
-//			assert.Nil(t, err)
-//			assert.Equal(t, newentry.Address, updated.Address)
-//		})
-//		t.Run("change name", func(t *testing.T) {
-//			newentry.Name = "newname"
-//			updated, err := UpdateDNS(newentry, entry)
-//			assert.Nil(t, err)
-//			assert.Equal(t, newentry.Name, updated.Name)
-//		})
-//		t.Run("change network", func(t *testing.T) {
-//			newentry.Network = "wirecat"
-//			updated, err := UpdateDNS(newentry, entry)
-//			assert.Nil(t, err)
-//			assert.NotEqual(t, newentry.Network, updated.Network)
-//		})
-//	}
 func TestDeleteDNS(t *testing.T) {
-	database.InitializeDatabase()
 	deleteAllDNS(t)
 	deleteAllNetworks()
 	createNet()
-	entry := models.DNSEntry{"10.0.0.2", "", "newhost", "skynet"}
-	CreateDNS(entry)
+	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")
 		assert.Nil(t, err)
@@ -301,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")
@@ -327,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")
@@ -344,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"}
-		CreateDNS(entry)
-		CreateDNS(change)
+		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")
@@ -378,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")
@@ -394,20 +389,32 @@ 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"}
-		_, _ = CreateDNS(entry)
+		entry := models.DNSEntry{Address: "10.0.0.2", Name: "myhost", Network: "skynet"}
+		_, _ = logic.CreateDNS(entry)
 		err := logic.ValidateDNSCreate(entry)
 		assert.NotNil(t, err)
 		assert.Contains(t, err.Error(), "Field validation for 'Name' failed on the 'name_unique' tag")
 	})
 }
 
+func createHost() {
+	k, _ := wgtypes.ParseKey("DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=")
+	dnsHost = models.Host{
+		ID:        uuid.New(),
+		PublicKey: k.PublicKey(),
+		HostPass:  "password",
+		OS:        "linux",
+		Name:      "dnshost",
+	}
+	_ = logic.CreateHost(&dnsHost)
+}
+
 func deleteAllDNS(t *testing.T) {
 	dns, err := logic.GetAllDNS()
 	assert.Nil(t, err)

+ 18 - 43
controllers/docs.go

@@ -10,7 +10,7 @@
 //
 //	Schemes: https
 //	BasePath: /
-//	Version: 0.17.1
+//	Version: 0.18.5
 //	Host: netmaker.io
 //
 //	Consumes:
@@ -29,7 +29,6 @@ import (
 	serverconfigpkg "github.com/gravitl/netmaker/config"
 	"github.com/gravitl/netmaker/logic/acls"
 	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/netclient/config"
 )
 
 var _ = useUnused() // "use" the function to prevent "unused function" errors
@@ -204,27 +203,6 @@ type networkBodyResponse struct {
 	Network models.Network `json:"network"`
 }
 
-// swagger:parameters createAccessKey
-type accessKeyBodyParam struct {
-	// Access Key
-	// in: body
-	AccessKey models.AccessKey `json:"access_key"`
-}
-
-// swagger:response accessKeyBodyResponse
-type accessKeyBodyResponse struct {
-	// Access Key
-	// in: body
-	AccessKey models.AccessKey `json:"access_key"`
-}
-
-// swagger:response accessKeySliceBodyResponse
-type accessKeySliceBodyResponse struct {
-	// Access Keys
-	// in: body
-	AccessKey []models.AccessKey `json:"access_key"`
-}
-
 // swagger:parameters updateNetworkACL getNetworkACL
 type aclContainerBodyParam struct {
 	// ACL Container
@@ -243,21 +221,21 @@ type aclContainerResponse struct {
 type nodeSliceResponse struct {
 	// Nodes
 	// in: body
-	Nodes []models.Node `json:"nodes"`
+	Nodes []models.LegacyNode `json:"nodes"`
 }
 
 // swagger:response nodeResponse
 type nodeResponse struct {
 	// Node
 	// in: body
-	Node models.Node `json:"node"`
+	Node models.LegacyNode `json:"node"`
 }
 
 // swagger:parameters updateNode deleteNode
 type nodeBodyParam struct {
 	// Node
 	// in: body
-	Node models.Node `json:"node"`
+	Node models.LegacyNode `json:"node"`
 }
 
 // swagger:parameters createRelay
@@ -303,18 +281,18 @@ type nodeLastModifiedResponse struct {
 }
 
 // swagger:parameters register
-type registerRequestBodyParam struct {
-	// Register Request
-	// in: body
-	RegisterRequest config.RegisterRequest `json:"register_request"`
-}
-
-// swagger:response registerResponse
-type registerResponse struct {
-	// Register Response
-	// in: body
-	RegisterResponse config.RegisterResponse `json:"register_response"`
-}
+//type registerRequestBodyParam struct {
+//	// Register Request
+//	// in: body
+//	RegisterRequest config.RegisterRequest `json:"register_request"`
+//}
+//
+//// swagger:response registerResponse
+//type registerResponse struct {
+//	// Register Response
+//	// in: body
+//	RegisterResponse config.RegisterResponse `json:"register_response"`
+//}
 
 // swagger:response boolResponse
 type boolResponse struct {
@@ -374,9 +352,6 @@ func useUnused() bool {
 	_ = networkPathParam{}
 	_ = networkAccessKeyNamePathParam{}
 	_ = networkBodyResponse{}
-	_ = accessKeyBodyParam{}
-	_ = accessKeyBodyResponse{}
-	_ = accessKeySliceBodyResponse{}
 	_ = aclContainerBodyParam{}
 	_ = aclContainerResponse{}
 	_ = nodeSliceResponse{}
@@ -388,8 +363,8 @@ func useUnused() bool {
 	_ = serverConfigResponse{}
 	_ = nodeGetResponse{}
 	_ = nodeLastModifiedResponse{}
-	_ = registerRequestBodyParam{}
-	_ = registerResponse{}
+	//	_ = registerRequestBodyParam{}
+	//	_ = registerResponse{}
 	_ = boolResponse{}
 	_ = userBodyParam{}
 	_ = userBodyResponse{}

+ 264 - 0
controllers/enrollmentkeys.go

@@ -0,0 +1,264 @@
+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) {
+		err := fmt.Errorf("bad client version on register: %s", newHost.Version)
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	if newHost.TrafficKeyPublic == nil && newHost.OS != models.OS_Types.IoT {
+		err := fmt.Errorf("missing traffic key")
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		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
+	}
+	hostPass := newHost.HostPass
+	if !hostExists {
+		// register host
+		logic.CheckHostPorts(&newHost)
+		// create EMQX credentials and ACLs for host
+		if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
+			if err := mq.CreateEmqxUser(newHost.ID.String(), newHost.HostPass, false); err != nil {
+				logger.Log(0, "failed to create host credentials for EMQX: ", err.Error())
+				return
+			}
+			if err := mq.CreateHostACL(newHost.ID.String(), servercfg.GetServerInfo().Server); err != nil {
+				logger.Log(0, "failed to add host ACL rules to EMQX: ", err.Error())
+				return
+			}
+		}
+		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
+	if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
+		// set MQ username and password for EMQX clients
+		server.MQUserName = newHost.ID.String()
+		server.MQPassword = hostPass
+	}
+	response := models.RegisterResponse{
+		ServerConf:    server,
+		RequestedHost: newHost,
+	}
+	logger.Log(0, newHost.Name, newHost.ID.String(), "registered with Netmaker")
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(&response)
+	// 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())
+		}
+	}
+}

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