Преглед изворни кода

Merge branch 'develop' of https://github.com/gravitl/netmaker into GRA-1298

Abhishek Kondur пре 2 година
родитељ
комит
2ee666f45f

+ 3 - 1
.dockerignore

@@ -1,2 +1,4 @@
 config/dnsconfig/
-data/
+data/
+/.git
+/*.tar

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

@@ -31,6 +31,7 @@ body:
       label: Version
       description: What version are you running?
       options:
+        - v0.20.1
         - v0.20.0
         - v0.19.0
         - v0.18.7

+ 3 - 21
.github/workflows/deletedroplets.yml

@@ -23,17 +23,8 @@ jobs:
           webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
           color: "#42f545"
           username: "GitHub Bot"
-          message: "${{ github.repository }}: ${{ github.event.workflow_run.name }} was successful"
+          message: "${{ github.repository }}: ${{ github.event.workflow_run.name }} was successful: droplets from this workflow (tag ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}) will be deleted in 15 min"
           file: ./results/results.log
-      - name: discord server message
-        uses: appleboy/discord-action@master
-        with:
-          webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }}
-          webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
-          color: "#42f545"
-          username: "GitHub Bot"
-          message: "droplets from this workflow (tag ${{ github.event.workflow_run.id }}-{{ $github.event.workflow_run.run_number }}) will be deleted in 15 min"
-          file: ./server/serverinfo.txt
       - name: delete droplets
         if: success() || failure()
         run: |
@@ -62,17 +53,8 @@ jobs:
           webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
           color: "#990000"
           username: "GitHub Bot"
-          message: "${{ github.repository }}: ${{ github.event.workflow_run.name }} failed"
+          message: "${{ github.repository }}: ${{ github.event.workflow_run.name }} failed: droplets from this workflow (tag ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt}}) will be deleted in 5 hours"
           file: ./results/results.log
-      - name: discord server message
-        uses: appleboy/discord-action@master
-        with:
-          webhook_id: ${{ secrets.DISCORD_WEBHOOK_ID }}
-          webhook_token: ${{ secrets.DISCORD_WEBHOOK_TOKEN }}
-          color: "#990000"
-          username: "GitHub Bot"
-          message: "droplets from this workflow (tag ${{ github.event.workflow_run.id }}-{{ $github.event.workflow_run.run_number }}) will be deleted in 6 hours"
-          file: ./server/serverinfo.txt
       - name: discord error message
         uses: appleboy/discord-action@master
         with:
@@ -85,7 +67,7 @@ jobs:
       - name: delete droplets
         if: success() || failure()
         run: |
-          sleep 6h
+          sleep 5h
           curl -X GET \
             -H "Content-Type: application/json" \
             -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \

+ 1 - 1
README.md

@@ -17,7 +17,7 @@
 
 <p align="center">
   <a href="https://github.com/gravitl/netmaker/releases">
-    <img src="https://img.shields.io/badge/Version-0.20.0-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.20.1-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" />

+ 1 - 0
cli/functions/http_client.go

@@ -148,6 +148,7 @@ retry:
 	if res.StatusCode == http.StatusUnauthorized && !retried && ctx.MasterKey == "" {
 		req.Header.Set("Authorization", "Bearer "+getAuthToken(ctx, true))
 		retried = true
+		// TODO add a retry limit, drop goto
 		goto retry
 	}
 	resBodyBytes, err := io.ReadAll(res.Body)

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

@@ -3,7 +3,7 @@ version: "3.4"
 services:
   netclient:
     container_name: netclient
-    image: 'gravitl/netclient:v0.20.0'
+    image: 'gravitl/netclient:v0.20.1'
     hostname: netmaker-1
     network_mode: host
     restart: on-failure

+ 1 - 1
controllers/docs.go

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

+ 19 - 6
controllers/enrollmentkeys.go

@@ -17,7 +17,7 @@ import (
 
 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", logic.SecurityCheck(false, 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)
 }
@@ -34,24 +34,37 @@ func enrollmentKeyHandlers(r *mux.Router) {
 //			Responses:
 //				200: getEnrollmentKeysSlice
 func getEnrollmentKeys(w http.ResponseWriter, r *http.Request) {
-	currentKeys, err := logic.GetAllEnrollmentKeys()
+	keys, 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 {
+	isMasterAdmin := r.Header.Get("ismaster") == "yes"
+	// regular user flow
+	user, err := logic.GetUser(r.Header.Get("user"))
+	if err != nil && !isMasterAdmin {
+		logger.Log(0, r.Header.Get("user"), "failed to fetch user: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	// TODO drop double pointer
+	ret := []*models.EnrollmentKey{}
+	for _, key := range keys {
+		if !isMasterAdmin && !logic.UserHasNetworksAccess(key.Networks, user) {
+			continue
+		}
+		if err = logic.Tokenize(key, 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
 		}
+		ret = append(ret, key)
 	}
 	// return JSON/API formatted keys
 	logger.Log(2, r.Header.Get("user"), "fetched enrollment keys")
 	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(currentKeys)
+	json.NewEncoder(w).Encode(ret)
 }
 
 // swagger:route DELETE /api/v1/enrollment-keys/{keyID} enrollmentKeys deleteEnrollmentKey

+ 30 - 1
controllers/hosts.go

@@ -19,7 +19,7 @@ import (
 )
 
 func hostHandlers(r *mux.Router) {
-	r.HandleFunc("/api/hosts", logic.SecurityCheck(true, http.HandlerFunc(getHosts))).Methods(http.MethodGet)
+	r.HandleFunc("/api/hosts", logic.SecurityCheck(false, http.HandlerFunc(getHosts))).Methods(http.MethodGet)
 	r.HandleFunc("/api/hosts/keys", logic.SecurityCheck(true, http.HandlerFunc(updateAllKeys))).Methods(http.MethodPut)
 	r.HandleFunc("/api/hosts/{hostid}/keys", logic.SecurityCheck(true, http.HandlerFunc(updateKeys))).Methods(http.MethodPut)
 	r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(updateHost))).Methods(http.MethodPut)
@@ -52,9 +52,38 @@ func getHosts(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
+	//isMasterAdmin := r.Header.Get("ismaster") == "yes"
+	//user, err := logic.GetUser(r.Header.Get("user"))
+	//if err != nil && !isMasterAdmin {
+	//	logger.Log(0, r.Header.Get("user"), "failed to fetch user: ", err.Error())
+	//	logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+	//	return
+	//}
 	// return JSON/API formatted hosts
+	//ret := []models.ApiHost{}
 	apiHosts := logic.GetAllHostsAPI(currentHosts[:])
 	logger.Log(2, r.Header.Get("user"), "fetched all hosts")
+	//for _, host := range apiHosts {
+	//	nodes := host.Nodes
+	//	// work on the copy
+	//	host.Nodes = []string{}
+	//	for _, nid := range nodes {
+	//		node, err := logic.GetNodeByID(nid)
+	//		if err != nil {
+	//			logger.Log(0, r.Header.Get("user"), "failed to fetch node: ", err.Error())
+	//			// TODO find the reason for the DB error, skip this node for now
+	//			continue
+	//		}
+	//		if !isMasterAdmin && !logic.UserHasNetworksAccess([]string{node.Network}, user) {
+	//			continue
+	//		}
+	//		host.Nodes = append(host.Nodes, nid)
+	//	}
+	//	// add to the response only if has perms to some nodes / networks
+	//	if len(host.Nodes) > 0 {
+	//		ret = append(ret, host)
+	//	}
+	//}
 	logic.SortApiHosts(apiHosts[:])
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode(apiHosts)

+ 1 - 4
controllers/network.go

@@ -40,10 +40,7 @@ func networkHandlers(r *mux.Router) {
 //			Responses:
 //				200: getNetworksSliceResponse
 func getNetworks(w http.ResponseWriter, r *http.Request) {
-
-	headerNetworks := r.Header.Get("networks")
-	networksSlice := []string{}
-	marshalErr := json.Unmarshal([]byte(headerNetworks), &networksSlice)
+	networksSlice, marshalErr := getHeaderNetworks(r)
 	if marshalErr != nil {
 		logger.Log(0, r.Header.Get("user"), "error unmarshalling networks: ",
 			marshalErr.Error())

+ 1 - 1
controllers/node.go

@@ -157,7 +157,7 @@ func authenticate(response http.ResponseWriter, request *http.Request) {
 func authorize(hostAllowed, networkCheck bool, authNetwork string, next http.Handler) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		var errorResponse = models.ErrorResponse{
-			Code: http.StatusUnauthorized, Message: logic.Unauthorized_Msg,
+			Code: http.StatusForbidden, Message: logic.Forbidden_Msg,
 		}
 
 		var params = mux.Vars(r)

+ 1 - 1
controllers/server.go

@@ -94,7 +94,7 @@ func getStatus(w http.ResponseWriter, r *http.Request) {
 func allowUsers(next http.Handler) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		var errorResponse = models.ErrorResponse{
-			Code: http.StatusInternalServerError, Message: logic.Unauthorized_Msg,
+			Code: http.StatusUnauthorized, Message: logic.Unauthorized_Msg,
 		}
 		bearerToken := r.Header.Get("Authorization")
 		var tokenSplit = strings.Split(bearerToken, " ")

+ 21 - 4
controllers/user.go

@@ -290,16 +290,16 @@ func updateUserNetworks(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
-	var userchange models.User
+	userChange := &models.User{}
 	// we decode our body request params
-	err = json.NewDecoder(r.Body).Decode(&userchange)
+	err = json.NewDecoder(r.Body).Decode(userChange)
 	if err != nil {
 		logger.Log(0, username, "error decoding request body: ",
 			err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 	}
-	err = logic.UpdateUserNetworks(userchange.Networks, userchange.Groups, userchange.IsAdmin, &models.ReturnUser{
+	err = logic.UpdateUserNetworks(userChange.Networks, userChange.Groups, userChange.IsAdmin, &models.ReturnUser{
 		Groups:   user.Groups,
 		IsAdmin:  user.IsAdmin,
 		Networks: user.Networks,
@@ -313,7 +313,13 @@ func updateUserNetworks(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	logger.Log(1, username, "status was updated")
-	json.NewEncoder(w).Encode(user)
+	// re-read and return the new user struct
+	if userChange, err = logic.GetUser(username); err != nil {
+		logger.Log(0, username, "failed to fetch user: ", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	json.NewEncoder(w).Encode(userChange)
 }
 
 // swagger:route PUT /api/users/{username} user updateUser
@@ -485,3 +491,14 @@ func socketHandler(w http.ResponseWriter, r *http.Request) {
 	// Start handling the session
 	go auth.SessionHandler(conn)
 }
+
+// getHeaderNetworks returns a slice of networks parsed form the request header.
+func getHeaderNetworks(r *http.Request) ([]string, error) {
+	headerNetworks := r.Header.Get("networks")
+	networksSlice := []string{}
+	err := json.Unmarshal([]byte(headerNetworks), &networksSlice)
+	if err != nil {
+		return nil, err
+	}
+	return networksSlice, nil
+}

+ 5 - 5
go.mod

@@ -4,7 +4,7 @@ go 1.19
 
 require (
 	github.com/eclipse/paho.mqtt.golang v1.4.2
-	github.com/go-playground/validator/v10 v10.13.0
+	github.com/go-playground/validator/v10 v10.14.0
 	github.com/golang-jwt/jwt/v4 v4.5.0
 	github.com/google/uuid v1.3.0
 	github.com/gorilla/handlers v1.5.1
@@ -13,7 +13,7 @@ require (
 	github.com/mattn/go-sqlite3 v1.14.16
 	github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
-	github.com/stretchr/testify v1.8.2
+	github.com/stretchr/testify v1.8.3
 	github.com/txn2/txeh v1.4.0
 	golang.org/x/crypto v0.9.0
 	golang.org/x/net v0.10.0 // indirect
@@ -33,7 +33,7 @@ require (
 )
 
 require (
-	github.com/coreos/go-oidc/v3 v3.5.0
+	github.com/coreos/go-oidc/v3 v3.6.0
 	github.com/gorilla/websocket v1.5.0
 	github.com/pkg/errors v0.9.1
 	golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e
@@ -50,9 +50,9 @@ require (
 
 require (
 	cloud.google.com/go/compute/metadata v0.2.1 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
 	github.com/go-jose/go-jose/v3 v3.0.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
-	github.com/kr/pretty v0.3.1 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 )
@@ -67,7 +67,7 @@ require (
 	github.com/google/go-cmp v0.5.9 // indirect
 	github.com/hashicorp/go-version v1.6.0
 	github.com/josharian/native v1.0.0 // indirect
-	github.com/leodido/go-urn v1.2.3 // indirect
+	github.com/leodido/go-urn v1.2.4 // indirect
 	github.com/mattn/go-runewidth v0.0.13 // indirect
 	github.com/mdlayher/genetlink v1.2.0 // indirect
 	github.com/mdlayher/netlink v1.6.0 // indirect

+ 11 - 42
go.sum

@@ -1,6 +1,5 @@
 cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0=
 cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
-cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
 cloud.google.com/go/compute/metadata v0.2.1 h1:efOwf5ymceDhK6PKMnnrTHP4pppY5L22mle96M1yP48=
 cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM=
 filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
@@ -8,11 +7,10 @@ filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5E
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/c-robinson/iplib v1.0.6 h1:FfZV9BWNrah3BgLCFl5/nDXe4RbOi/C9n+DeXFOv5CQ=
 github.com/c-robinson/iplib v1.0.6/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo=
-github.com/coreos/go-oidc/v3 v3.5.0 h1:VxKtbccHZxs8juq7RdJntSqtXFtde9YpNpGn0yqgEHw=
-github.com/coreos/go-oidc/v3 v3.5.0/go.mod h1:ecXRtV4romGPeO6ieExAsUK9cb/3fp9hXNz1tlv8PIM=
+github.com/coreos/go-oidc/v3 v3.6.0 h1:AKVxfYw1Gmkn/w96z0DbT/B/xFnzTd3MkZvWLjF4n/o=
+github.com/coreos/go-oidc/v3 v3.6.0/go.mod h1:ZpHUsHBucTUj6WOkrP4E20UPynbLZzhTQ1XKCXkxyPc=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -23,6 +21,8 @@ github.com/eclipse/paho.mqtt.golang v1.4.2/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2
 github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
 github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
+github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
 github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
 github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -30,8 +30,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.13.0 h1:cFRQdfaSMCOSfGCCLB20MHvuoHb/s5G8L5pu2ppK5AQ=
-github.com/go-playground/validator/v10 v10.13.0/go.mod h1:dwu7+CG8/CtBiJFZDz4e+5Upb6OLw04gtBYw0mcG/z4=
+github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
+github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
 github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
 github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -42,7 +42,6 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
-github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
@@ -62,15 +61,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
 github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
-github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/leodido/go-urn v1.2.3 h1:6BE2vPT0lqoz3fmOesHZiaiFh7889ssCo2GMvLCfiuA=
-github.com/leodido/go-urn v1.2.3/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
+github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
 github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
@@ -91,7 +83,6 @@ github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE9
 github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
-github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -101,8 +92,6 @@ github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sA
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
-github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f h1:BSnJgAfHzEp7o8PYJ7YfwAVHhqu7BYUTggcn/LGlUWY=
 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f/go.mod h1:UW/gxgQwSePTvL1KA8QEHsXeYHP4xkoXgbDdN781p34=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -118,47 +107,37 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
 github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
+github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/txn2/txeh v1.4.0 h1:0tdvpA4HGJrj8X3kmrU6o/JFStI009nKxwDpMK5CnRU=
 github.com/txn2/txeh v1.4.0/go.mod h1:Mgq0hY184zCrDBLgvkIp+9NYGHoYbJcu4xKqUcx1shc=
 github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
 github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220208050332-20e1d8d225ab/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
 golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA=
 golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211111083644-e5c967477495/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
 golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
 golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -173,26 +152,18 @@ golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.zx2c4.com/go118/netip v0.0.0-20211111135330-a4a02eeacf9d/go.mod h1:5yyfuiqVIJ7t+3MqrpTQ+QqRkMWiESiyDvPNvKYCecg=
@@ -206,12 +177,10 @@ google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

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

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

+ 1 - 1
k8s/client/netclient.yaml

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

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

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

+ 15 - 0
logic/enrollmentkey.go

@@ -5,6 +5,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"golang.org/x/exp/slices"
 	"time"
 
 	"github.com/gravitl/netmaker/database"
@@ -68,6 +69,7 @@ func CreateEnrollmentKey(uses int, expiration time.Time, networks, tags []string
 }
 
 // GetAllEnrollmentKeys - fetches all enrollment keys from DB
+// TODO drop double pointer
 func GetAllEnrollmentKeys() ([]*models.EnrollmentKey, error) {
 	currentKeys, err := getEnrollmentKeysMap()
 	if err != nil {
@@ -222,3 +224,16 @@ func getEnrollmentKeysMap() (map[string]*models.EnrollmentKey, error) {
 	}
 	return currentKeys, nil
 }
+
+// UserHasNetworksAccess - checks if a user `u` has access to all `networks`
+func UserHasNetworksAccess(networks []string, u *models.User) bool {
+	if u.IsAdmin {
+		return true
+	}
+	for _, n := range networks {
+		if !slices.Contains(u.Networks, n) {
+			return false
+		}
+	}
+	return true
+}

+ 74 - 0
logic/enrollmentkey_test.go

@@ -204,3 +204,77 @@ func TestDeTokenize_EnrollmentKeys(t *testing.T) {
 
 	removeAllEnrollments()
 }
+
+func TestHasNetworksAccess(t *testing.T) {
+	type Case struct {
+		// network names
+		n []string
+		u models.User
+	}
+	pass := []Case{
+		{
+			n: []string{"n1", "n2"},
+			u: models.User{
+				Networks: []string{"n1", "n2"},
+				IsAdmin:  false,
+			},
+		},
+		{
+			n: []string{"n1", "n2"},
+			u: models.User{
+				Networks: []string{},
+				IsAdmin:  true,
+			},
+		},
+		{
+			n: []string{"n1", "n2"},
+			u: models.User{
+				Networks: []string{"n1", "n2", "n3"},
+				IsAdmin:  false,
+			},
+		},
+		{
+			n: []string{"n2"},
+			u: models.User{
+				Networks: []string{"n2"},
+				IsAdmin:  false,
+			},
+		},
+	}
+	deny := []Case{
+		{
+			n: []string{"n1", "n2"},
+			u: models.User{
+				Networks: []string{"n2"},
+				IsAdmin:  false,
+			},
+		},
+		{
+			n: []string{"n1", "n2"},
+			u: models.User{
+				Networks: []string{},
+				IsAdmin:  false,
+			},
+		},
+		{
+			n: []string{"n1", "n2"},
+			u: models.User{
+				Networks: []string{"n3"},
+				IsAdmin:  false,
+			},
+		},
+		{
+			n: []string{"n2"},
+			u: models.User{
+				Networks: []string{"n1"},
+				IsAdmin:  false,
+			},
+		},
+	}
+	for _, tc := range pass {
+		assert.True(t, UserHasNetworksAccess(tc.n, &tc.u))
+	}
+	for _, tc := range deny {
+		assert.False(t, UserHasNetworksAccess(tc.n, &tc.u))
+	}
+}

+ 4 - 0
logic/hosts.go

@@ -180,6 +180,10 @@ func UpdateHostFromClient(newHost, currHost *models.Host) (sendPeerUpdate bool)
 		currHost.ListenPort = newHost.ListenPort
 		sendPeerUpdate = true
 	}
+	if newHost.WgPublicListenPort != 0 && currHost.WgPublicListenPort != newHost.WgPublicListenPort {
+		currHost.WgPublicListenPort = newHost.WgPublicListenPort
+		sendPeerUpdate = true
+	}
 	if newHost.ProxyListenPort != 0 && currHost.ProxyListenPort != newHost.ProxyListenPort {
 		currHost.ProxyListenPort = newHost.ProxyListenPort
 		sendPeerUpdate = true

+ 14 - 1
logic/peers.go

@@ -220,11 +220,12 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 				}
 				peerConfig.Endpoint = &net.UDPAddr{
 					IP:   peerHost.EndpointIP,
-					Port: peerHost.ListenPort,
+					Port: getPeerWgListenPort(peerHost),
 				}
 
 				if uselocal {
 					peerConfig.Endpoint.IP = peer.LocalAddress.IP
+					peerConfig.Endpoint.Port = peerHost.ListenPort
 				}
 				allowedips := GetAllowedIPs(&node, &peer, nil)
 				if peer.IsIngressGateway {
@@ -425,9 +426,21 @@ func GetPeerUpdateForHost(ctx context.Context, network string, host *models.Host
 	return hostPeerUpdate, nil
 }
 
+// getPeerWgListenPort - fetches the wg listen port for the host
+func getPeerWgListenPort(host *models.Host) int {
+	peerPort := host.ListenPort
+	if host.WgPublicListenPort != 0 {
+		peerPort = host.WgPublicListenPort
+	}
+	return peerPort
+}
+
 // GetPeerListenPort - given a host, retrieve it's appropriate listening port
 func GetPeerListenPort(host *models.Host) int {
 	peerPort := host.ListenPort
+	if host.WgPublicListenPort != 0 {
+		peerPort = host.WgPublicListenPort
+	}
 	if host.ProxyEnabled {
 		if host.PublicListenPort != 0 {
 			peerPort = host.PublicListenPort

+ 14 - 6
logic/security.go

@@ -18,6 +18,8 @@ const (
 	ALL_NETWORK_ACCESS = "THIS_USER_HAS_ALL"
 
 	master_uname     = "masteradministrator"
+	Forbidden_Msg    = "forbidden"
+	Forbidden_Err    = models.Error(Forbidden_Msg)
 	Unauthorized_Msg = "unauthorized"
 	Unauthorized_Err = models.Error(Unauthorized_Msg)
 )
@@ -27,8 +29,9 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc {
 
 	return func(w http.ResponseWriter, r *http.Request) {
 		var errorResponse = models.ErrorResponse{
-			Code: http.StatusUnauthorized, Message: Unauthorized_Msg,
+			Code: http.StatusForbidden, Message: Forbidden_Msg,
 		}
+		r.Header.Set("ismaster", "no")
 
 		var params = mux.Vars(r)
 		bearerToken := r.Header.Get("Authorization")
@@ -51,6 +54,10 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc {
 			ReturnErrorResponse(w, r, errorResponse)
 			return
 		}
+		// detect masteradmin
+		if len(networks) > 0 && networks[0] == ALL_NETWORK_ACCESS {
+			r.Header.Set("ismaster", "yes")
+		}
 		networksJson, err := json.Marshal(&networks)
 		if err != nil {
 			ReturnErrorResponse(w, r, errorResponse)
@@ -66,7 +73,7 @@ func SecurityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc {
 func NetUserSecurityCheck(isNodes, isClients bool, next http.Handler) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		var errorResponse = models.ErrorResponse{
-			Code: http.StatusUnauthorized, Message: "unauthorized",
+			Code: http.StatusForbidden, Message: Forbidden_Msg,
 		}
 		r.Header.Set("ismaster", "no")
 
@@ -145,6 +152,7 @@ func UserPermissions(reqAdmin bool, netname string, token string) ([]string, str
 	}
 	//all endpoints here require master so not as complicated
 	if authenticateMaster(authToken) {
+		// TODO log in as an actual admin user
 		return []string{ALL_NETWORK_ACCESS}, master_uname, nil
 	}
 	username, networks, isadmin, err := VerifyUserToken(authToken)
@@ -152,7 +160,7 @@ func UserPermissions(reqAdmin bool, netname string, token string) ([]string, str
 		return nil, username, Unauthorized_Err
 	}
 	if !isadmin && reqAdmin {
-		return nil, username, Unauthorized_Err
+		return nil, username, Forbidden_Err
 	}
 	userNetworks = networks
 	if isadmin {
@@ -160,10 +168,10 @@ func UserPermissions(reqAdmin bool, netname string, token string) ([]string, str
 	}
 	// check network admin access
 	if len(netname) > 0 && (len(userNetworks) == 0 || !authenticateNetworkUser(netname, userNetworks)) {
-		return nil, username, Unauthorized_Err
+		return nil, username, Forbidden_Err
 	}
 	if isEE && len(netname) > 0 && !pro.IsUserNetAdmin(netname, username) {
-		return nil, "", Unauthorized_Err
+		return nil, "", Forbidden_Err
 	}
 	return userNetworks, username, nil
 }
@@ -193,7 +201,7 @@ func authenticateDNSToken(tokenString string) bool {
 func ContinueIfUserMatch(next http.Handler) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		var errorResponse = models.ErrorResponse{
-			Code: http.StatusUnauthorized, Message: Unauthorized_Msg,
+			Code: http.StatusForbidden, Message: Forbidden_Msg,
 		}
 		var params = mux.Vars(r)
 		var requestedUser = params["username"]

+ 1 - 0
logic/users.go

@@ -12,6 +12,7 @@ import (
 )
 
 // GetUser - gets a user
+// TODO support "masteradmin"
 func GetUser(username string) (*models.User, error) {
 
 	var user models.User

+ 1 - 1
main.go

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

+ 28 - 26
models/api_host.go

@@ -7,32 +7,33 @@ import (
 
 // ApiHost - the host struct for API usage
 type ApiHost struct {
-	ID               string   `json:"id"`
-	Verbosity        int      `json:"verbosity"`
-	FirewallInUse    string   `json:"firewallinuse"`
-	Version          string   `json:"version"`
-	Name             string   `json:"name"`
-	OS               string   `json:"os"`
-	Debug            bool     `json:"debug"`
-	IsStatic         bool     `json:"isstatic"`
-	ListenPort       int      `json:"listenport"`
-	LocalListenPort  int      `json:"locallistenport"`
-	ProxyListenPort  int      `json:"proxy_listen_port"`
-	PublicListenPort int      `json:"public_listen_port" yaml:"public_listen_port"`
-	MTU              int      `json:"mtu" yaml:"mtu"`
-	Interfaces       []Iface  `json:"interfaces" yaml:"interfaces"`
-	DefaultInterface string   `json:"defaultinterface" yaml:"defautlinterface"`
-	EndpointIP       string   `json:"endpointip" yaml:"endpointip"`
-	PublicKey        string   `json:"publickey"`
-	MacAddress       string   `json:"macaddress"`
-	InternetGateway  string   `json:"internetgateway"`
-	Nodes            []string `json:"nodes"`
-	ProxyEnabled     bool     `json:"proxy_enabled" yaml:"proxy_enabled"`
-	IsDefault        bool     `json:"isdefault" yaml:"isdefault"`
-	IsRelayed        bool     `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
-	RelayedBy        string   `json:"relayed_by" bson:"relayed_by" yaml:"relayed_by"`
-	IsRelay          bool     `json:"isrelay" bson:"isrelay" yaml:"isrelay"`
-	RelayedHosts     []string `json:"relay_hosts" bson:"relay_hosts" yaml:"relay_hosts"`
+	ID                 string   `json:"id"`
+	Verbosity          int      `json:"verbosity"`
+	FirewallInUse      string   `json:"firewallinuse"`
+	Version            string   `json:"version"`
+	Name               string   `json:"name"`
+	OS                 string   `json:"os"`
+	Debug              bool     `json:"debug"`
+	IsStatic           bool     `json:"isstatic"`
+	ListenPort         int      `json:"listenport"`
+	LocalListenPort    int      `json:"locallistenport"`
+	ProxyListenPort    int      `json:"proxy_listen_port"`
+	PublicListenPort   int      `json:"public_listen_port" yaml:"public_listen_port"`
+	WgPublicListenPort int      `json:"wg_public_listen_port" yaml:"wg_public_listen_port"`
+	MTU                int      `json:"mtu" yaml:"mtu"`
+	Interfaces         []Iface  `json:"interfaces" yaml:"interfaces"`
+	DefaultInterface   string   `json:"defaultinterface" yaml:"defautlinterface"`
+	EndpointIP         string   `json:"endpointip" yaml:"endpointip"`
+	PublicKey          string   `json:"publickey"`
+	MacAddress         string   `json:"macaddress"`
+	InternetGateway    string   `json:"internetgateway"`
+	Nodes              []string `json:"nodes"`
+	ProxyEnabled       bool     `json:"proxy_enabled" yaml:"proxy_enabled"`
+	IsDefault          bool     `json:"isdefault" yaml:"isdefault"`
+	IsRelayed          bool     `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"`
+	RelayedBy          string   `json:"relayed_by" bson:"relayed_by" yaml:"relayed_by"`
+	IsRelay            bool     `json:"isrelay" bson:"isrelay" yaml:"isrelay"`
+	RelayedHosts       []string `json:"relay_hosts" bson:"relay_hosts" yaml:"relay_hosts"`
 }
 
 // Host.ConvertNMHostToAPI - converts a Netmaker host to an API editable host
@@ -60,6 +61,7 @@ func (h *Host) ConvertNMHostToAPI() *ApiHost {
 	a.Nodes = h.Nodes
 	a.ProxyEnabled = h.ProxyEnabled
 	a.PublicListenPort = h.PublicListenPort
+	a.WgPublicListenPort = h.WgPublicListenPort
 	a.ProxyListenPort = h.ProxyListenPort
 	a.PublicKey = h.PublicKey.String()
 	a.Verbosity = h.Verbosity

+ 37 - 36
models/host.go

@@ -41,42 +41,43 @@ const WIREGUARD_INTERFACE = "netmaker"
 
 // Host - represents a host on the network
 type Host struct {
-	ID               uuid.UUID        `json:"id" yaml:"id"`
-	Verbosity        int              `json:"verbosity" yaml:"verbosity"`
-	FirewallInUse    string           `json:"firewallinuse" yaml:"firewallinuse"`
-	Version          string           `json:"version" yaml:"version"`
-	IPForwarding     bool             `json:"ipforwarding" yaml:"ipforwarding"`
-	DaemonInstalled  bool             `json:"daemoninstalled" yaml:"daemoninstalled"`
-	AutoUpdate       bool             `json:"autoupdate" yaml:"autoupdate"`
-	HostPass         string           `json:"hostpass" yaml:"hostpass"`
-	Name             string           `json:"name" yaml:"name"`
-	OS               string           `json:"os" yaml:"os"`
-	Interface        string           `json:"interface" yaml:"interface"`
-	Debug            bool             `json:"debug" yaml:"debug"`
-	ListenPort       int              `json:"listenport" yaml:"listenport"`
-	PublicListenPort int              `json:"public_listen_port" yaml:"public_listen_port"`
-	ProxyListenPort  int              `json:"proxy_listen_port" yaml:"proxy_listen_port"`
-	MTU              int              `json:"mtu" yaml:"mtu"`
-	PublicKey        wgtypes.Key      `json:"publickey" yaml:"publickey"`
-	MacAddress       net.HardwareAddr `json:"macaddress" yaml:"macaddress"`
-	TrafficKeyPublic []byte           `json:"traffickeypublic" yaml:"traffickeypublic"`
-	InternetGateway  net.UDPAddr      `json:"internetgateway" yaml:"internetgateway"`
-	Nodes            []string         `json:"nodes" yaml:"nodes"`
-	IsRelayed        bool             `json:"isrelayed" yaml:"isrelayed"`
-	RelayedBy        string           `json:"relayed_by" yaml:"relayed_by"`
-	IsRelay          bool             `json:"isrelay" yaml:"isrelay"`
-	RelayedHosts     []string         `json:"relay_hosts" yaml:"relay_hosts"`
-	Interfaces       []Iface          `json:"interfaces" yaml:"interfaces"`
-	DefaultInterface string           `json:"defaultinterface" yaml:"defaultinterface"`
-	EndpointIP       net.IP           `json:"endpointip" yaml:"endpointip"`
-	ProxyEnabled     bool             `json:"proxy_enabled" yaml:"proxy_enabled"`
-	ProxyEnabledSet  bool             `json:"proxy_enabled_updated" yaml:"proxy_enabled_updated"`
-	IsDocker         bool             `json:"isdocker" yaml:"isdocker"`
-	IsK8S            bool             `json:"isk8s" yaml:"isk8s"`
-	IsStatic         bool             `json:"isstatic" yaml:"isstatic"`
-	IsDefault        bool             `json:"isdefault" yaml:"isdefault"`
-	NatType          string           `json:"nat_type,omitempty" yaml:"nat_type,omitempty"`
-	TurnEndpoint     *netip.AddrPort  `json:"turn_endpoint,omitempty" yaml:"turn_endpoint,omitempty"`
+	ID                 uuid.UUID        `json:"id" yaml:"id"`
+	Verbosity          int              `json:"verbosity" yaml:"verbosity"`
+	FirewallInUse      string           `json:"firewallinuse" yaml:"firewallinuse"`
+	Version            string           `json:"version" yaml:"version"`
+	IPForwarding       bool             `json:"ipforwarding" yaml:"ipforwarding"`
+	DaemonInstalled    bool             `json:"daemoninstalled" yaml:"daemoninstalled"`
+	AutoUpdate         bool             `json:"autoupdate" yaml:"autoupdate"`
+	HostPass           string           `json:"hostpass" yaml:"hostpass"`
+	Name               string           `json:"name" yaml:"name"`
+	OS                 string           `json:"os" yaml:"os"`
+	Interface          string           `json:"interface" yaml:"interface"`
+	Debug              bool             `json:"debug" yaml:"debug"`
+	ListenPort         int              `json:"listenport" yaml:"listenport"`
+	PublicListenPort   int              `json:"public_listen_port" yaml:"public_listen_port"`
+	WgPublicListenPort int              `json:"wg_public_listen_port" yaml:"wg_public_listen_port"`
+	ProxyListenPort    int              `json:"proxy_listen_port" yaml:"proxy_listen_port"`
+	MTU                int              `json:"mtu" yaml:"mtu"`
+	PublicKey          wgtypes.Key      `json:"publickey" yaml:"publickey"`
+	MacAddress         net.HardwareAddr `json:"macaddress" yaml:"macaddress"`
+	TrafficKeyPublic   []byte           `json:"traffickeypublic" yaml:"traffickeypublic"`
+	InternetGateway    net.UDPAddr      `json:"internetgateway" yaml:"internetgateway"`
+	Nodes              []string         `json:"nodes" yaml:"nodes"`
+	IsRelayed          bool             `json:"isrelayed" yaml:"isrelayed"`
+	RelayedBy          string           `json:"relayed_by" yaml:"relayed_by"`
+	IsRelay            bool             `json:"isrelay" yaml:"isrelay"`
+	RelayedHosts       []string         `json:"relay_hosts" yaml:"relay_hosts"`
+	Interfaces         []Iface          `json:"interfaces" yaml:"interfaces"`
+	DefaultInterface   string           `json:"defaultinterface" yaml:"defaultinterface"`
+	EndpointIP         net.IP           `json:"endpointip" yaml:"endpointip"`
+	ProxyEnabled       bool             `json:"proxy_enabled" yaml:"proxy_enabled"`
+	ProxyEnabledSet    bool             `json:"proxy_enabled_updated" yaml:"proxy_enabled_updated"`
+	IsDocker           bool             `json:"isdocker" yaml:"isdocker"`
+	IsK8S              bool             `json:"isk8s" yaml:"isk8s"`
+	IsStatic           bool             `json:"isstatic" yaml:"isstatic"`
+	IsDefault          bool             `json:"isdefault" yaml:"isdefault"`
+	NatType            string           `json:"nat_type,omitempty" yaml:"nat_type,omitempty"`
+	TurnEndpoint       *netip.AddrPort  `json:"turn_endpoint,omitempty" yaml:"turn_endpoint,omitempty"`
 }
 
 // FormatBool converts a boolean to a [yes|no] string

+ 9 - 0
mq/handlers.go

@@ -428,6 +428,15 @@ func handleHostCheckin(h, currentHost *models.Host) bool {
 	for i := range h.Interfaces {
 		h.Interfaces[i].AddressString = h.Interfaces[i].Address.String()
 	}
+	/// version or firewall in use change does not require a peerUpdate
+	if h.Version != currentHost.Version || h.FirewallInUse != currentHost.FirewallInUse {
+		currentHost.FirewallInUse = h.FirewallInUse
+		currentHost.Version = h.Version
+		if err := logic.UpsertHost(currentHost); err != nil {
+			logger.Log(0, "failed to update host after check-in", h.Name, h.ID.String(), err.Error())
+			return false
+		}
+	}
 	ifaceDelta := len(h.Interfaces) != len(currentHost.Interfaces) ||
 		!h.EndpointIP.Equal(currentHost.EndpointIP) ||
 		(len(h.NatType) > 0 && h.NatType != currentHost.NatType) ||

+ 8 - 5
release.md

@@ -1,13 +1,16 @@
 
-# Netmaker v0.20.0
+# Netmaker v0.20.1
 
 ## whats new
-- New UI
-- revamped compose-files and install scripts
-- TURN
+- 
     
 ## whats fixed
-- Caddy does not handle netmaker exporter well for EE
+- enrollment keys for non-admins 
+- client version displayed correctly in UI
+- upd hole punching improvments
+- SSL fallback to letsencrypt
+- permission handling for non-admin users
+
 
 ## known issues
 - Migration causes a listen port of 0 for some upgraded hosts

+ 0 - 0
scripts/netmaker.env → scripts/netmaker.default.env


+ 14 - 13
scripts/nm-certs.sh

@@ -30,16 +30,16 @@ fi
 CERTBOT_PARAMS=$(cat <<EOF
 certonly --standalone \
 	--non-interactive --agree-tos \
-	-m "$NM_EMAIL" \
-	-d "stun.$NM_DOMAIN" \
-	-d "api.$NM_DOMAIN" \
-	-d "broker.$NM_DOMAIN" \
-	-d "dashboard.$NM_DOMAIN" \
-	-d "turn.$NM_DOMAIN" \
-	-d "turnapi.$NM_DOMAIN" \
-	-d "netmaker-exporter.$NM_DOMAIN" \
-	-d "grafana.$NM_DOMAIN" \
-	-d "prometheus.$NM_DOMAIN"
+	-m $NM_EMAIL \
+	-d stun.$NM_DOMAIN \
+	-d api.$NM_DOMAIN \
+	-d broker.$NM_DOMAIN \
+	-d dashboard.$NM_DOMAIN \
+	-d turn.$NM_DOMAIN \
+	-d turnapi.$NM_DOMAIN \
+	-d netmaker-exporter.$NM_DOMAIN \
+	-d grafana.$NM_DOMAIN \
+	-d prometheus.$NM_DOMAIN
 EOF
 )
 
@@ -47,6 +47,7 @@ EOF
 cat <<EOF >"$SCRIPT_DIR/certbot-entry.sh"
 #!/bin/sh
 # deps
+apk update
 apk add bash curl
 # zerossl
 wget -qO zerossl-bot.sh "https://github.com/zerossl/zerossl-bot/raw/master/zerossl-bot.sh"
@@ -54,7 +55,8 @@ chmod +x zerossl-bot.sh
 # request the certs
 ./zerossl-bot.sh "$CERTBOT_PARAMS"
 EOF
-chmod +x certbot-entry.sh
+
+chmod +x "$SCRIPT_DIR/certbot-entry.sh"
 
 # request certs
 sudo docker run -it --rm --name certbot \
@@ -73,8 +75,7 @@ if [ ! -f "$CERT_DIR"/fullchain.pem ]; then
 	sudo docker run -it --rm --name certbot \
 		-p 80:80 -p 443:443 \
 		-v "$SCRIPT_DIR/letsencrypt:/etc/letsencrypt" \
-		--entrypoint "/opt/certbot/certbot-entry.sh" \
-		certbot/certbot "$CERTBOT_PARAMS"
+		certbot/certbot $CERTBOT_PARAMS
 	if [ ! -f "$CERT_DIR"/fullchain.pem ]; then
 		echo "Missing file: $CERT_DIR/fullchain.pem"
 		echo "SSL certificates failed"

+ 51 - 35
scripts/nm-quick.sh

@@ -4,6 +4,7 @@ CONFIG_FILE=netmaker.env
 # location of nm-quick.sh (usually `/root`)
 SCRIPT_DIR=$(dirname "$(realpath "$0")")
 CONFIG_PATH="$SCRIPT_DIR/$CONFIG_FILE"
+NM_QUICK_VERSION="0.1.0"
 LATEST=$(curl -s https://api.github.com/repos/gravitl/netmaker/releases/latest | grep "tag_name" | cut -d : -f 2,3 | tr -d [:space:],\")
 
 if [ $(id -u) -ne 0 ]; then
@@ -11,12 +12,6 @@ if [ $(id -u) -ne 0 ]; then
 	exit 1
 fi
 
-# read the config file
-if [ -f "$CONFIG_PATH" ]; then
-	echo "Reading config from $CONFIG_PATH"
-	source "$CONFIG_PATH"
-fi
-
 unset INSTALL_TYPE
 unset BUILD_TYPE
 unset BUILD_TAG
@@ -25,6 +20,7 @@ unset AUTO_BUILD
 
 # usage - displays usage instructions
 usage() {
+	echo "nm-quick.sh v$NM_QUICK_VERSION"
 	echo "usage: ./nm-quick.sh [-e] [-b buildtype] [-t tag] [-a auto]"
 	echo "  -e      if specified, will install netmaker EE"
 	echo "  -b      type of build; options:"
@@ -141,6 +137,7 @@ set_buildinfo() {
 	echo "  Build Type: $BUILD_TYPE"
 	echo "   Build Tag: $BUILD_TAG"
 	echo "   Image Tag: $IMAGE_TAG"
+	echo "   Installer: v$NM_QUICK_VERSION"
 	echo "-----------------------------------------------------"
 
 }
@@ -303,7 +300,7 @@ save_config() { (
 		save_config_item SERVER_IMAGE_TAG "$IMAGE_TAG"
 	fi
 	# copy entries from the previous config
-	local toCopy=("SERVER_HOST" "MASTER_KEY" "TURN_USERNAME" "MQ_USERNAME" "MQ_PASSWORD"
+	local toCopy=("SERVER_HOST" "MASTER_KEY" "TURN_USERNAME" "TURN_PASSWORD" "MQ_USERNAME" "MQ_PASSWORD"
 		"INSTALL_TYPE" "NODE_ID" "METRICS_EXPORTER" "PROMETHEUS" "DNS_MODE" "NETCLIENT_AUTO_UPDATE" "API_PORT"
 		"CORS_ALLOWED_ORIGIN" "DISPLAY_KEYS" "DATABASE" "SERVER_BROKER_ENDPOINT" "STUN_PORT" "VERBOSITY"
 		"DEFAULT_PROXY_MODE" "TURN_PORT" "USE_TURN" "DEBUG_MODE" "TURN_API_PORT" "REST_BACKEND" "DISABLE_REMOTE_IP_CHECK"
@@ -327,13 +324,20 @@ save_config() { (
 save_config_item() { (
 	local NAME="$1"
 	local VALUE="$2"
-	# echo "NAME $NAME"
-	# echo "VALUE $VALUE"
+	#echo "$NAME=$VALUE"
+	if test -z "$VALUE"; then
+		# load the default for empty values
+		VALUE=$(awk -F'=' "/^$NAME/ { print \$2}"  "$SCRIPT_DIR/netmaker.default.env")
+		# trim quotes for docker
+		VALUE=$(echo "$VALUE" | sed -E "s|^(['\"])(.*)\1$|\2|g")
+		#echo "Default for $NAME=$VALUE"
+	fi
+	# TODO single quote passwords
 	if grep -q "^$NAME=" "$CONFIG_PATH"; then
 		# TODO escape | in the value
 		sed -i "s|$NAME=.*|$NAME=$VALUE|" "$CONFIG_PATH"
 	else
-		echo "$NAME=\"$VALUE\"" >>"$CONFIG_PATH"
+		echo "$NAME=$VALUE" >>"$CONFIG_PATH"
 	fi
 ); }
 
@@ -362,8 +366,7 @@ local_install_setup() { (
 		cp docker/Caddyfile "$SCRIPT_DIR/Caddyfile"
 	fi
 	cp scripts/nm-certs.sh "$SCRIPT_DIR/nm-certs.sh"
-	cp scripts/netmaker.env "$SCRIPT_DIR/netmaker.env"
-	ln -fs "$SCRIPT_DIR/netmaker.env" "$SCRIPT_DIR/.env"
+	cp scripts/netmaker.default.env "$SCRIPT_DIR/netmaker.default.env"
 	cp docker/mosquitto.conf "$SCRIPT_DIR/mosquitto.conf"
 	cp docker/wait.sh "$SCRIPT_DIR/wait.sh"
 	cd ../../
@@ -383,31 +386,31 @@ install_dependencies() {
 
 	OS=$(uname)
 	if [ -f /etc/debian_version ]; then
-		dependencies="git wireguard wireguard-tools dnsutils jq docker.io docker-compose"
+		dependencies="git wireguard wireguard-tools dnsutils jq docker.io docker-compose grep gawk"
 		update_cmd='apt update'
 		install_cmd='apt-get install -y'
 	elif [ -f /etc/alpine-release ]; then
-		dependencies="git wireguard jq docker.io docker-compose"
+		dependencies="git wireguard jq docker.io docker-compose grep gawk"
 		update_cmd='apk update'
 		install_cmd='apk --update add'
 	elif [ -f /etc/centos-release ]; then
-		dependencies="git wireguard jq bind-utils docker.io docker-compose"
+		dependencies="git wireguard jq bind-utils docker.io docker-compose grep gawk"
 		update_cmd='yum update'
 		install_cmd='yum install -y'
 	elif [ -f /etc/fedora-release ]; then
-		dependencies="git wireguard bind-utils jq docker.io docker-compose"
+		dependencies="git wireguard bind-utils jq docker.io docker-compose grep gawk"
 		update_cmd='dnf update'
 		install_cmd='dnf install -y'
 	elif [ -f /etc/redhat-release ]; then
-		dependencies="git wireguard jq docker.io bind-utils docker-compose"
+		dependencies="git wireguard jq docker.io bind-utils docker-compose grep gawk"
 		update_cmd='yum update'
 		install_cmd='yum install -y'
 	elif [ -f /etc/arch-release ]; then
-		dependencies="git wireguard-tools dnsutils jq docker.io docker-compose"
+		dependencies="git wireguard-tools dnsutils jq docker.io docker-compose grep gawk"
 		update_cmd='pacman -Sy'
 		install_cmd='pacman -S --noconfirm'
 	elif [ "${OS}" = "FreeBSD" ]; then
-		dependencies="git wireguard wget jq docker.io docker-compose"
+		dependencies="git wireguard wget jq docker.io docker-compose grep gawk"
 		update_cmd='pkg update'
 		install_cmd='pkg install -y'
 	else
@@ -490,10 +493,12 @@ set_install_vars() {
 
 	NETMAKER_BASE_DOMAIN=nm.$(echo $IP_ADDR | tr . -).nip.io
 	SERVER_HOST=$IP_ADDR
-	MASTER_KEY=$(
-		tr -dc A-Za-z0-9 </dev/urandom | head -c 30
-		echo ''
-	)
+	if test -z "$MASTER_KEY"; then
+		MASTER_KEY=$(
+			tr -dc A-Za-z0-9 </dev/urandom | head -c 30
+			echo ''
+		)
+	fi
 	DOMAIN_TYPE=""
 	echo "-----------------------------------------------------"
 	echo "Would you like to use your own domain for netmaker, or an auto-generated domain?"
@@ -604,13 +609,15 @@ set_install_vars() {
 		MQ_USERNAME="$GET_MQ_USERNAME"
 	fi
 
-	MQ_PASSWORD=$(
-		tr -dc A-Za-z0-9 </dev/urandom | head -c 30
-		echo ''
-	)
+	if test -z "$MQ_PASSWORD"; then
+		MQ_PASSWORD=$(
+			tr -dc A-Za-z0-9 </dev/urandom | head -c 30
+			echo ''
+		)
+	fi
 
 	if [ -z $AUTO_BUILD ]; then
-		select domain_option in "Auto Generated Password" "Input Your Own Password"; do
+		select domain_option in "Auto Generated / Config Password" "Input Your Own Password"; do
 			case $REPLY in
 			1)
 				echo "using random password for mq"
@@ -651,13 +658,15 @@ set_install_vars() {
 		TURN_USERNAME="$GET_TURN_USERNAME"
 	fi
 
-	TURN_PASSWORD=$(
-		tr -dc A-Za-z0-9 </dev/urandom | head -c 30
-		echo ''
-	)
+	if test -z "$TURN_PASSWORD"; then
+		TURN_PASSWORD=$(
+			tr -dc A-Za-z0-9 </dev/urandom | head -c 30
+			echo ''
+		)
+	fi
 
 	if [ -z $AUTO_BUILD ]; then
-		select domain_option in "Auto Generated Password" "Input Your Own Password"; do
+		select domain_option in "Auto Generated / Config Password" "Input Your Own Password"; do
 			case $REPLY in
 			1)
 				echo "using random password for turn"
@@ -734,8 +743,7 @@ install_netmaker() {
 			wget -qO "$SCRIPT_DIR"/docker-compose.override.yml $COMPOSE_OVERRIDE_URL
 		fi
 		wget -qO "$SCRIPT_DIR"/Caddyfile "$CADDY_URL"
-		wget -qO "$SCRIPT_DIR"/netmaker.env "$BASE_URL/scripts/netmaker.env"
-		ln -fs "$SCRIPT_DIR/netmaker.env" "$SCRIPT_DIR/.env"
+		wget -qO "$SCRIPT_DIR"/netmaker.default.env "$BASE_URL/scripts/netmaker.default.env"
 		wget -qO "$SCRIPT_DIR"/mosquitto.conf "$BASE_URL/docker/mosquitto.conf"
 		wget -qO "$SCRIPT_DIR"/nm-certs.sh "$BASE_URL/scripts/nm-certs.sh"
 		wget -qO "$SCRIPT_DIR"/wait.sh "$BASE_URL/docker/wait.sh"
@@ -744,6 +752,8 @@ install_netmaker() {
 	chmod +x "$SCRIPT_DIR"/wait.sh
 	mkdir -p /etc/netmaker
 
+	# link .env to the user config
+	ln -fs "$SCRIPT_DIR/netmaker.env" "$SCRIPT_DIR/.env"
 	save_config
 
 	# Fetch / update certs using certbot
@@ -860,6 +870,12 @@ cleanup() {
 # 1. print netmaker logo
 print_logo
 
+# read the config
+if [ -f "$CONFIG_PATH" ]; then
+	echo "Using config: $CONFIG_PATH"
+	source "$CONFIG_PATH"
+fi
+
 # 2. setup the build instructions
 set_buildinfo
 

+ 1 - 1
scripts/nm-upgrade-0-17-1-to-0-19-0.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-LATEST="v0.20.0"
+LATEST="v0.20.1"
 INSTALL_PATH="/root"
 
 trap restore_old_netmaker_instructions

+ 1 - 1
swagger.yaml

@@ -704,7 +704,7 @@ info:
 
         API calls must be authenticated via a header of the format -H “Authorization: Bearer <YOUR_SECRET_KEY>” There are two methods to obtain YOUR_SECRET_KEY: 1. Using the masterkey. By default, this value is “secret key,” but you should change this on your instance and keep it secure. This value can be set via env var at startup or in a config file (config/environments/< env >.yaml). See the [Netmaker](https://docs.netmaker.org/index.html) documentation for more details. 2. Using a JWT received for a node. This can be retrieved by calling the /api/nodes/<network>/authenticate endpoint, as documented below.
     title: Netmaker
-    version: 0.20.0
+    version: 0.20.1
 paths:
     /api/dns:
         get: