Browse Source

Merge pull request #1622 from gravitl/feature_mq_dynsec

MQ Dynamic Security
Alex Feiszli 2 years ago
parent
commit
6890ca70b9

+ 12 - 10
compose/docker-compose.ee.yml

@@ -17,7 +17,7 @@ services:
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
-      - shared_certs:/etc/netmaker
+      - mosquitto_data:/etc/netmaker
     environment:
       SERVER_NAME: "broker.NETMAKER_BASE_DOMAIN"
       SERVER_HOST: "SERVER_PUBLIC_IP"
@@ -42,6 +42,7 @@ services:
       METRICS_EXPORTER: "on"
       LICENSE_KEY: "YOUR_LICENSE_KEY"
       NETMAKER_ACCOUNT_ID: "YOUR_ACCOUNT_ID"
+      MQ_ADMIN_PASSWORD: "REPLACE_MQ_ADMIN_PASSWORD"
     ports:
       - "51821-51830:51821-51830/udp"
     expose:
@@ -112,21 +113,22 @@ services:
     depends_on:
       - netmaker
     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/mosquitto.passwords:/etc/mosquitto.passwords
+      - /root/wait.sh:/mosquitto/config/wait.sh
       - mosquitto_data:/mosquitto/data
       - mosquitto_logs:/mosquitto/log
-      - shared_certs:/mosquitto/certs
     expose:
       - "8883"
     labels:
       - traefik.enable=true
-      - traefik.tcp.routers.mqtts.rule=HostSNI(`broker.NETMAKER_BASE_DOMAIN`)
-      - traefik.tcp.routers.mqtts.tls.passthrough=true
-      - traefik.tcp.services.mqtts-svc.loadbalancer.server.port=8883
-      - traefik.tcp.routers.mqtts.service=mqtts-svc
-      - traefik.tcp.routers.mqtts.entrypoints=websecure
+      - traefik.tcp.routers.mqtt.rule=HostSNI(`broker.NETMAKER_BASE_DOMAIN`)
+      - traefik.tcp.routers.mqtt.tls.certresolver=http
+      - traefik.tcp.services.mqtt.loadbalancer.server.port=8883
+      - traefik.tcp.routers.mqtt.entrypoints=websecure
   prometheus:
     container_name: prometheus
     image: gravitl/netmaker-prometheus:latest
@@ -180,16 +182,16 @@ services:
     environment:
       MQ_HOST: "mq"
       MQ_PORT: "443"
-      MQ_SERVER_PORT: "1884"
+      MQ_SERVER_PORT: "1883"
       PROMETHEUS: "on"
       VERBOSITY: "1"
       API_PORT: "8085"
+      LICENSE_KEY: "YOUR_LICENSE_KEY"
       PROMETHEUS_HOST: https://prometheus.NETMAKER_BASE_DOMAIN
     expose:
       - "8085"
 volumes:
   traefik_certs: {}
-  shared_certs: {}
   sqldata: {}
   dnsconfig: {}
   mosquitto_data: {}

+ 11 - 9
compose/docker-compose.yml

@@ -17,7 +17,7 @@ services:
     volumes:
       - dnsconfig:/root/config/dnsconfig
       - sqldata:/root/data
-      - shared_certs:/etc/netmaker
+      - mosquitto_data:/etc/netmaker
     environment:
       SERVER_NAME: "broker.NETMAKER_BASE_DOMAIN"
       SERVER_HOST: "SERVER_PUBLIC_IP"
@@ -39,6 +39,7 @@ services:
       VERBOSITY: "1"
       MANAGE_IPTABLES: "on"
       PORT_FORWARD_SERVICES: "dns"
+      MQ_ADMIN_PASSWORD: "REPLACE_MQ_ADMIN_PASSWORD"
     ports:
       - "51821-51830:51821-51830/udp"
     expose:
@@ -109,24 +110,25 @@ services:
     depends_on:
       - netmaker
     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
-      - shared_certs:/mosquitto/certs
     expose:
       - "8883"
     labels:
       - traefik.enable=true
-      - traefik.tcp.routers.mqtts.rule=HostSNI(`broker.NETMAKER_BASE_DOMAIN`)
-      - traefik.tcp.routers.mqtts.tls.passthrough=true
-      - traefik.tcp.services.mqtts-svc.loadbalancer.server.port=8883
-      - traefik.tcp.routers.mqtts.service=mqtts-svc
-      - traefik.tcp.routers.mqtts.entrypoints=websecure
+      - traefik.tcp.routers.mqtt.rule=HostSNI(`broker.NETMAKER_BASE_DOMAIN`)
+      - traefik.tcp.routers.mqtt.tls.certresolver=http
+      - traefik.tcp.services.mqtt.loadbalancer.server.port=8883
+      - traefik.tcp.routers.mqtt.entrypoints=websecure
 volumes:
   traefik_certs: {}
-  shared_certs: {}
   sqldata: {}
   dnsconfig: {}
   mosquitto_data: {}
-  mosquitto_logs: {}
+  mosquitto_logs: {}

+ 1 - 0
config/config.go

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

+ 30 - 0
controllers/network.go

@@ -442,6 +442,20 @@ func deleteNetwork(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, errtype))
 		return
 	}
+	// Deletes the network role from MQ
+	event := mq.MqDynsecPayload{
+		Commands: []mq.MqDynSecCmd{
+			{
+				Command:  mq.DeleteRoleCmd,
+				RoleName: network,
+			},
+		},
+	}
+
+	if err := mq.PublishEventToDynSecTopic(event); err != nil {
+		logger.Log(0, fmt.Sprintf("failed to send DynSec command [%v]: %v",
+			event.Commands, err.Error()))
+	}
 	logger.Log(1, r.Header.Get("user"), "deleted network", network)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode("success")
@@ -488,6 +502,22 @@ func createNetwork(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
 	}
+	// Create Role with acls for the network
+	event := mq.MqDynsecPayload{
+		Commands: []mq.MqDynSecCmd{
+			{
+				Command:  mq.CreateRoleCmd,
+				RoleName: network.NetID,
+				Textname: "Network wide role with Acls for nodes",
+				Acls:     mq.FetchNetworkAcls(network.NetID),
+			},
+		},
+	}
+
+	if err := mq.PublishEventToDynSecTopic(event); err != nil {
+		logger.Log(0, fmt.Sprintf("failed to send DynSec command [%v]: %v",
+			event.Commands, err.Error()))
+	}
 
 	if servercfg.IsClientMode() != "off" {
 		_, err := logic.ServerJoin(&network)

+ 156 - 67
controllers/node.go

@@ -67,75 +67,109 @@ func authenticate(response http.ResponseWriter, request *http.Request) {
 			decoderErr.Error())
 		logic.ReturnErrorResponse(response, request, errorResponse)
 		return
-	} else {
+	}
+	errorResponse.Code = http.StatusBadRequest
+	if authRequest.ID == "" {
+		errorResponse.Message = "W1R3: ID can't be empty"
+		logger.Log(0, request.Header.Get("user"), errorResponse.Message)
+		logic.ReturnErrorResponse(response, request, errorResponse)
+		return
+	} else if authRequest.Password == "" {
+		errorResponse.Message = "W1R3: Password can't be empty"
+		logger.Log(0, request.Header.Get("user"), errorResponse.Message)
+		logic.ReturnErrorResponse(response, request, errorResponse)
+		return
+	}
+	var err error
+	result, err = logic.GetNodeByID(authRequest.ID)
+	if err != nil {
 		errorResponse.Code = http.StatusBadRequest
-		if authRequest.ID == "" {
-			errorResponse.Message = "W1R3: ID can't be empty"
-			logger.Log(0, request.Header.Get("user"), errorResponse.Message)
-			logic.ReturnErrorResponse(response, request, errorResponse)
-			return
-		} else if authRequest.Password == "" {
-			errorResponse.Message = "W1R3: Password can't be empty"
-			logger.Log(0, request.Header.Get("user"), errorResponse.Message)
-			logic.ReturnErrorResponse(response, request, errorResponse)
-			return
-		} else {
-			var err error
-			result, err = logic.GetNodeByID(authRequest.ID)
-
-			if err != nil {
-				errorResponse.Code = http.StatusBadRequest
-				errorResponse.Message = err.Error()
-				logger.Log(0, request.Header.Get("user"),
-					fmt.Sprintf("failed to get node info [%s]: %v", authRequest.ID, err))
-				logic.ReturnErrorResponse(response, request, errorResponse)
-				return
-			}
-
-			err = bcrypt.CompareHashAndPassword([]byte(result.Password), []byte(authRequest.Password))
-			if err != nil {
-				errorResponse.Code = http.StatusBadRequest
-				errorResponse.Message = err.Error()
-				logger.Log(0, request.Header.Get("user"),
-					"error validating user password: ", err.Error())
-				logic.ReturnErrorResponse(response, request, errorResponse)
-				return
-			} else {
-				tokenString, err := logic.CreateJWT(authRequest.ID, authRequest.MacAddress, result.Network)
-
-				if tokenString == "" {
-					errorResponse.Code = http.StatusBadRequest
-					errorResponse.Message = "Could not create Token"
-					logger.Log(0, request.Header.Get("user"),
-						fmt.Sprintf("%s: %v", errorResponse.Message, err))
-					logic.ReturnErrorResponse(response, request, errorResponse)
-					return
-				}
+		errorResponse.Message = err.Error()
+		logger.Log(0, request.Header.Get("user"),
+			fmt.Sprintf("failed to get node info [%s]: %v", authRequest.ID, err))
+		logic.ReturnErrorResponse(response, request, errorResponse)
+		return
+	}
 
-				var successResponse = models.SuccessResponse{
-					Code:    http.StatusOK,
-					Message: "W1R3: Device " + authRequest.ID + " Authorized",
-					Response: models.SuccessfulLoginResponse{
-						AuthToken: tokenString,
-						ID:        authRequest.ID,
+	err = bcrypt.CompareHashAndPassword([]byte(result.Password), []byte(authRequest.Password))
+	if err != nil {
+		errorResponse.Code = http.StatusBadRequest
+		errorResponse.Message = err.Error()
+		logger.Log(0, request.Header.Get("user"),
+			"error validating user password: ", err.Error())
+		logic.ReturnErrorResponse(response, request, errorResponse)
+		return
+	}
+	// creates network role,node client (added here to resolve any missing configuration in MQ)
+	event := mq.MqDynsecPayload{
+		Commands: []mq.MqDynSecCmd{
+
+			{
+				Command:  mq.CreateRoleCmd,
+				RoleName: result.Network,
+				Textname: "Network wide role with Acls for nodes",
+				Acls:     mq.FetchNetworkAcls(result.Network),
+			},
+			{
+				Command:  mq.CreateClientCmd,
+				Username: result.ID,
+				Password: authRequest.Password,
+				Textname: result.Name,
+				Roles: []mq.MqDynSecRole{
+					{
+						Rolename: mq.NodeRole,
+						Priority: -1,
 					},
-				}
-				successJSONResponse, jsonError := json.Marshal(successResponse)
-
-				if jsonError != nil {
-					errorResponse.Code = http.StatusBadRequest
-					errorResponse.Message = err.Error()
-					logger.Log(0, request.Header.Get("user"),
-						"error marshalling resp: ", err.Error())
-					logic.ReturnErrorResponse(response, request, errorResponse)
-					return
-				}
-				response.WriteHeader(http.StatusOK)
-				response.Header().Set("Content-Type", "application/json")
-				response.Write(successJSONResponse)
-			}
-		}
+					{
+						Rolename: result.Network,
+						Priority: -1,
+					},
+				},
+				Groups: make([]mq.MqDynSecGroup, 0),
+			},
+		},
+	}
+
+	if err := mq.PublishEventToDynSecTopic(event); err != nil {
+		logger.Log(0, fmt.Sprintf("failed to send DynSec command [%v]: %v",
+			event.Commands, err.Error()))
+		errorResponse.Code = http.StatusInternalServerError
+		errorResponse.Message = fmt.Sprintf("could not create mq client for node [%s]: %v", result.ID, err)
+		return
 	}
+
+	tokenString, err := logic.CreateJWT(authRequest.ID, authRequest.MacAddress, result.Network)
+	if tokenString == "" {
+		errorResponse.Code = http.StatusBadRequest
+		errorResponse.Message = "Could not create Token"
+		logger.Log(0, request.Header.Get("user"),
+			fmt.Sprintf("%s: %v", errorResponse.Message, err))
+		logic.ReturnErrorResponse(response, request, errorResponse)
+		return
+	}
+
+	var successResponse = models.SuccessResponse{
+		Code:    http.StatusOK,
+		Message: "W1R3: Device " + authRequest.ID + " Authorized",
+		Response: models.SuccessfulLoginResponse{
+			AuthToken: tokenString,
+			ID:        authRequest.ID,
+		},
+	}
+	successJSONResponse, jsonError := json.Marshal(successResponse)
+
+	if jsonError != nil {
+		errorResponse.Code = http.StatusBadRequest
+		errorResponse.Message = err.Error()
+		logger.Log(0, request.Header.Get("user"),
+			"error marshalling resp: ", err.Error())
+		logic.ReturnErrorResponse(response, request, errorResponse)
+		return
+	}
+	response.WriteHeader(http.StatusOK)
+	response.Header().Set("Content-Type", "application/json")
+	response.Write(successJSONResponse)
+
 }
 
 // auth middleware for api calls from nodes where node is has not yet joined the server (register, join)
@@ -597,7 +631,8 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 		Mine:   node.TrafficKeys.Mine,
 		Server: key,
 	}
-
+	// consume password before hashing for mq client creation
+	nodePassword := node.Password
 	err = logic.CreateNode(&node)
 	if err != nil {
 		logger.Log(0, r.Header.Get("user"),
@@ -633,6 +668,44 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	// Create client for this node in Mq
+	event := mq.MqDynsecPayload{
+		Commands: []mq.MqDynSecCmd{
+			{ // delete if any client exists already
+				Command:  mq.DeleteClientCmd,
+				Username: node.ID,
+			},
+			{
+				Command:  mq.CreateRoleCmd,
+				RoleName: node.Network,
+				Textname: "Network wide role with Acls for nodes",
+				Acls:     mq.FetchNetworkAcls(node.Network),
+			},
+			{
+				Command:  mq.CreateClientCmd,
+				Username: node.ID,
+				Password: nodePassword,
+				Textname: node.Name,
+				Roles: []mq.MqDynSecRole{
+					{
+						Rolename: mq.NodeRole,
+						Priority: -1,
+					},
+					{
+						Rolename: node.Network,
+						Priority: -1,
+					},
+				},
+				Groups: make([]mq.MqDynSecGroup, 0),
+			},
+		},
+	}
+
+	if err := mq.PublishEventToDynSecTopic(event); err != nil {
+		logger.Log(0, fmt.Sprintf("failed to send DynSec command [%v]: %v",
+			event.Commands, err.Error()))
+	}
+
 	response := models.NodeGet{
 		Node:         node,
 		Peers:        peerUpdate.Peers,
@@ -992,7 +1065,24 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
+	// deletes node related role and client
+	event := mq.MqDynsecPayload{
+		Commands: []mq.MqDynSecCmd{
+			{
+				Command:  mq.DeleteRoleCmd,
+				RoleName: fmt.Sprintf("%s-%s", "Node", nodeid),
+			},
+			{
+				Command:  mq.DeleteClientCmd,
+				Username: nodeid,
+			},
+		},
+	}
 
+	if err := mq.PublishEventToDynSecTopic(event); err != nil {
+		logger.Log(0, fmt.Sprintf("failed to send DynSec command [%v]: %v",
+			event.Commands, err.Error()))
+	}
 	if servercfg.Is_EE {
 		if err = logic.EnterpriseResetAllPeersFailovers(node.ID, node.Network); err != nil {
 			logger.Log(0, "failed to reset failover lists during node delete for node", node.Name, node.Network)
@@ -1000,7 +1090,6 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 	}
 
 	logic.ReturnSuccessResponse(w, r, nodeid+" deleted.")
-
 	logger.Log(1, r.Header.Get("user"), "Deleted node", nodeid, "from network", params["network"])
 	runUpdates(&node, false)
 	runForceServerUpdate(&node, false)

+ 4 - 83
controllers/server.go

@@ -1,28 +1,23 @@
 package controller
 
 import (
-	"crypto/ed25519"
-	"crypto/x509"
-	"crypto/x509/pkix"
 	"encoding/json"
-	"fmt"
 	"net/http"
 	"strings"
 
 	"github.com/gorilla/mux"
-	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
-	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/servercfg"
-	"github.com/gravitl/netmaker/serverctl"
-	"github.com/gravitl/netmaker/tls"
 )
 
 func serverHandlers(r *mux.Router) {
 	// r.HandleFunc("/api/server/addnetwork/{network}", securityCheckServer(true, http.HandlerFunc(addNetwork))).Methods("POST")
+	r.HandleFunc("/api/server/health", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
+		resp.WriteHeader(http.StatusOK)
+		resp.Write([]byte("Server is up and running!!"))
+	}))
 	r.HandleFunc("/api/server/getconfig", allowUsers(http.HandlerFunc(getConfig))).Methods("GET")
-	r.HandleFunc("/api/server/register", authorize(true, false, "node", http.HandlerFunc(register))).Methods("POST")
 	r.HandleFunc("/api/server/getserverinfo", authorize(true, false, "node", http.HandlerFunc(getServerInfo))).Methods("GET")
 }
 
@@ -96,77 +91,3 @@ func getConfig(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(scfg)
 	//w.WriteHeader(http.StatusOK)
 }
-
-// swagger:route POST /api/server/register server register
-//
-// Registers a client with the server and return the Certificate Authority and certificate.
-//
-//			Schemes: https
-//
-//			Security:
-//	  		oauth
-//
-//			Responses:
-//				200: registerResponse
-func register(w http.ResponseWriter, r *http.Request) {
-	logger.Log(2, "processing registration request")
-	w.Header().Set("Content-Type", "application/json")
-	//decode body
-	var request config.RegisterRequest
-	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
-		logger.Log(0, "error decoding request", err.Error())
-		errorResponse := models.ErrorResponse{
-			Code: http.StatusBadRequest, Message: err.Error(),
-		}
-		logic.ReturnErrorResponse(w, r, errorResponse)
-		return
-	}
-	cert, ca, err := genCerts(&request.Key, &request.CommonName)
-	if err != nil {
-		logger.Log(0, "failed to generater certs ", err.Error())
-		errorResponse := models.ErrorResponse{
-			Code: http.StatusNotFound, Message: err.Error(),
-		}
-		logic.ReturnErrorResponse(w, r, errorResponse)
-		return
-	}
-	//x509.Certificate.PublicKey is an interface therefore json encoding/decoding result in a string value rather than a []byte
-	//include the actual public key so the certificate can be properly reassembled on the other end.
-	response := config.RegisterResponse{
-		CA:         *ca,
-		CAPubKey:   (ca.PublicKey).(ed25519.PublicKey),
-		Cert:       *cert,
-		CertPubKey: (cert.PublicKey).(ed25519.PublicKey),
-		Broker:     servercfg.GetServer(),
-		Port:       servercfg.GetMQPort(),
-	}
-	logger.Log(2, r.Header.Get("user"),
-		fmt.Sprintf("registered client [%+v] with server", request))
-	w.WriteHeader(http.StatusOK)
-	json.NewEncoder(w).Encode(response)
-}
-
-// genCerts generates a client certificate and returns the certificate and root CA
-func genCerts(clientKey *ed25519.PrivateKey, name *pkix.Name) (*x509.Certificate, *x509.Certificate, error) {
-	ca, err := serverctl.ReadCertFromDB(tls.ROOT_PEM_NAME)
-	if err != nil {
-		logger.Log(2, "root ca not found ", err.Error())
-		return nil, nil, fmt.Errorf("root ca not found %w", err)
-	}
-	key, err := serverctl.ReadKeyFromDB(tls.ROOT_KEY_NAME)
-	if err != nil {
-		logger.Log(2, "root key not found ", err.Error())
-		return nil, nil, fmt.Errorf("root key not found %w", err)
-	}
-	csr, err := tls.NewCSR(*clientKey, *name)
-	if err != nil {
-		logger.Log(2, "failed to generate client certificate requests", err.Error())
-		return nil, nil, fmt.Errorf("client certification request generation failed %w", err)
-	}
-	cert, err := tls.NewEndEntityCert(*key, csr, ca, tls.CERTIFICATE_VALIDITY)
-	if err != nil {
-		logger.Log(2, "unable to generate client certificate", err.Error())
-		return nil, nil, fmt.Errorf("client certification generation failed %w", err)
-	}
-	return cert, ca, nil
-}

+ 0 - 16
docker/mosquitto-ee.conf

@@ -1,16 +0,0 @@
-per_listener_settings true
-
-listener 8883
-allow_anonymous false
-require_certificate true
-use_identity_as_username true
-cafile /mosquitto/certs/root.pem
-certfile /mosquitto/certs/server.pem
-keyfile /mosquitto/certs/server.key
-
-listener 1883
-allow_anonymous true
-
-listener 1884
-allow_anonymous false
-password_file /etc/mosquitto.passwords

+ 5 - 8
docker/mosquitto.conf

@@ -1,12 +1,9 @@
-per_listener_settings true
-
+per_listener_settings false
 listener 8883
 allow_anonymous false
-require_certificate true
-use_identity_as_username true
-cafile /mosquitto/certs/root.pem
-certfile /mosquitto/certs/server.pem
-keyfile /mosquitto/certs/server.key
 
 listener 1883
-allow_anonymous true
+allow_anonymous false
+
+plugin /usr/lib/mosquitto_dynamic_security.so
+plugin_opt_config_file /mosquitto/data/dynamic-security.json

+ 0 - 1
docker/mosquitto.passwords

@@ -1 +0,0 @@
-netmaker-exporter:$7$101$9kcXwXP+nUMh06gm$MND2YjtRSvcZTXjMn7xYKoqUFQxG6NOgqWmXIcxxxZksM9cA8732URQWOsPHqpGEvVF9mSVagM1MBEMIKwZm2A==

+ 23 - 0
docker/wait.sh

@@ -0,0 +1,23 @@
+#!/bin/ash
+
+wait_for_netmaker() {
+  echo "SERVER: ${NETMAKER_SERVER_HOST}"
+  until curl --output /dev/null --silent --fail --head \
+    --location "${NETMAKER_SERVER_HOST}/api/server/health"; do
+    echo "Waiting for netmaker server to startup"
+    sleep 1
+  done
+}
+
+main(){
+ # wait for netmaker to startup
+ apk add curl
+ wait_for_netmaker
+ echo "Starting MQ..."
+ # Run the main container command.
+ /docker-entrypoint.sh
+ /usr/sbin/mosquitto -c /mosquitto/config/mosquitto.conf
+
+}
+
+main "${@}"

+ 8 - 1
logic/accesskeys.go

@@ -9,7 +9,7 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/go-playground/validator/v10"
+	validator "github.com/go-playground/validator/v10"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
@@ -221,7 +221,14 @@ func genKeyName() string {
 	return strings.Join([]string{"key", entropy.Text(16)[:16]}, "-")
 }
 
+// GenKey - generates random key of length 16
 func GenKey() string {
 	entropy, _ := rand.Int(rand.Reader, maxentropy)
 	return entropy.Text(16)[:16]
 }
+
+// GenPassWord - generates random password of length 64
+func GenPassWord() string {
+	entropy, _ := rand.Int(rand.Reader, maxentropy)
+	return entropy.Text(62)[:64]
+}

+ 1 - 1
logic/auth.go

@@ -6,7 +6,7 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/go-playground/validator/v10"
+	validator "github.com/go-playground/validator/v10"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic/pro"

+ 1 - 1
logic/dns.go

@@ -4,7 +4,7 @@ import (
 	"encoding/json"
 	"os"
 
-	"github.com/go-playground/validator/v10"
+	validator "github.com/go-playground/validator/v10"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"

+ 10 - 0
logic/metrics/metrics.go

@@ -1,12 +1,14 @@
 package metrics
 
 import (
+	"runtime"
 	"time"
 
 	"github.com/go-ping/ping"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/netclient/wireguard"
 	"golang.zx2c4.com/wireguard/wgctrl"
 )
 
@@ -20,6 +22,14 @@ func Collect(iface string, peerMap models.PeerMap) (*models.Metrics, error) {
 		return &metrics, err
 	}
 	defer wgclient.Close()
+
+	if runtime.GOOS == "darwin" {
+		iface, err = wireguard.GetRealIface(iface)
+		if err != nil {
+			fillUnconnectedData(&metrics, peerMap)
+			return &metrics, err
+		}
+	}
 	device, err := wgclient.Device(iface)
 	if err != nil {
 		fillUnconnectedData(&metrics, peerMap)

+ 1 - 1
logic/networks.go

@@ -9,7 +9,7 @@ import (
 	"strings"
 
 	"github.com/c-robinson/iplib"
-	"github.com/go-playground/validator/v10"
+	validator "github.com/go-playground/validator/v10"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic/acls/nodeacls"

+ 1 - 1
logic/nodes.go

@@ -7,7 +7,7 @@ import (
 	"sort"
 	"time"
 
-	"github.com/go-playground/validator/v10"
+	validator "github.com/go-playground/validator/v10"
 	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"

+ 8 - 0
logic/util.go

@@ -218,3 +218,11 @@ func StringDifference(a, b []string) []string {
 	}
 	return diff
 }
+
+// CheckIfFileExists - checks if file exists or not in the given path
+func CheckIfFileExists(filePath string) bool {
+	if _, err := os.Stat(filePath); os.IsNotExist(err) {
+		return false
+	}
+	return true
+}

+ 7 - 149
main.go

@@ -3,9 +3,6 @@ package main
 
 import (
 	"context"
-	"crypto/ed25519"
-	"crypto/rand"
-	"errors"
 	"flag"
 	"fmt"
 	"os"
@@ -14,7 +11,6 @@ import (
 	"strconv"
 	"sync"
 	"syscall"
-	"time"
 
 	"github.com/gravitl/netmaker/auth"
 	"github.com/gravitl/netmaker/config"
@@ -29,7 +25,6 @@ import (
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/serverctl"
-	"github.com/gravitl/netmaker/tls"
 )
 
 var version = "dev"
@@ -134,10 +129,6 @@ func initialize() { // Client Mode Prereq Check
 		}
 	}
 
-	if err = genCerts(); err != nil {
-		logger.Log(0, "something went wrong when generating broker certs", err.Error())
-	}
-
 	if servercfg.IsMessageQueueBackend() {
 		if err = mq.ServerStartNotify(); err != nil {
 			logger.Log(0, "error occurred when notifying nodes of startup", err.Error())
@@ -154,6 +145,12 @@ func startControllers() {
 			logger.Log(0, "error occurred initializing DNS: ", err.Error())
 		}
 	}
+	if servercfg.IsMessageQueueBackend() {
+		if err := mq.Configure(); err != nil {
+			logger.FatalLog("failed to configure MQ: ", err.Error())
+		}
+	}
+
 	//Run Rest Server
 	if servercfg.IsRestBackend() {
 		if !servercfg.DisableRemoteIPCheck() && servercfg.GetAPIHost() == "127.0.0.1" {
@@ -165,7 +162,6 @@ func startControllers() {
 		waitnetwork.Add(1)
 		go controller.HandleRESTRequests(&waitnetwork)
 	}
-
 	//Run MessageQueue
 	if servercfg.IsMessageQueueBackend() {
 		waitnetwork.Add(1)
@@ -184,6 +180,7 @@ func runMessageQueue(wg *sync.WaitGroup) {
 	defer wg.Done()
 	brokerHost, secure := servercfg.GetMessageQueueEndpoint()
 	logger.Log(0, "connecting to mq broker at", brokerHost, "with TLS?", fmt.Sprintf("%v", secure))
+	mq.SetUpAdminClient()
 	mq.SetupMQTT()
 	ctx, cancel := context.WithCancel(context.Background())
 	go mq.Keepalive(ctx)
@@ -206,142 +203,3 @@ func setGarbageCollection() {
 		debug.SetGCPercent(ncutils.DEFAULT_GC_PERCENT)
 	}
 }
-
-func genCerts() error {
-	logger.Log(0, "checking keys and certificates")
-	var private *ed25519.PrivateKey
-	var err error
-
-	// == ROOT key handling ==
-
-	private, err = serverctl.ReadKeyFromDB(tls.ROOT_KEY_NAME)
-	if errors.Is(err, os.ErrNotExist) || database.IsEmptyRecord(err) {
-		logger.Log(0, "generating new root key")
-		_, newKey, err := ed25519.GenerateKey(rand.Reader)
-		if err != nil {
-			return err
-		}
-		private = &newKey
-	} else if err != nil {
-		return err
-	}
-	logger.Log(2, "saving root.key")
-	if err := serverctl.SaveKey(functions.GetNetmakerPath()+ncutils.GetSeparator(), tls.ROOT_KEY_NAME, *private); err != nil {
-		return err
-	}
-
-	// == ROOT cert handling ==
-
-	ca, err := serverctl.ReadCertFromDB(tls.ROOT_PEM_NAME)
-	//if cert doesn't exist or will expire within 10 days --- but can't do this as clients won't be able to connect
-	//if errors.Is(err, os.ErrNotExist) || cert.NotAfter.Before(time.Now().Add(time.Hour*24*10)) {
-	if errors.Is(err, os.ErrNotExist) || database.IsEmptyRecord(err) || ca.NotAfter.Before(time.Now().Add(time.Hour*24*10)) {
-		logger.Log(0, "generating new root CA")
-		caName := tls.NewName("CA Root", "US", "Gravitl")
-		csr, err := tls.NewCSR(*private, caName)
-		if err != nil {
-			return err
-		}
-		rootCA, err := tls.SelfSignedCA(*private, csr, tls.CERTIFICATE_VALIDITY)
-		if err != nil {
-			return err
-		}
-		ca = rootCA
-	} else if err != nil {
-		return err
-	}
-	logger.Log(2, "saving root.pem")
-	if err := serverctl.SaveCert(functions.GetNetmakerPath()+ncutils.GetSeparator(), tls.ROOT_PEM_NAME, ca); err != nil {
-		return err
-	}
-
-	// == SERVER cert handling ==
-
-	cert, err := serverctl.ReadCertFromDB(tls.SERVER_PEM_NAME)
-	if errors.Is(err, os.ErrNotExist) || database.IsEmptyRecord(err) || cert.NotAfter.Before(time.Now().Add(time.Hour*24*10)) {
-		//gen new key
-		logger.Log(0, "generating new server key/certificate")
-		_, key, err := ed25519.GenerateKey(rand.Reader)
-		if err != nil {
-			return err
-		}
-		serverName := tls.NewCName(servercfg.GetServer())
-		csr, err := tls.NewCSR(key, serverName)
-		if err != nil {
-			return err
-		}
-		newCert, err := tls.NewEndEntityCert(*private, csr, ca, tls.CERTIFICATE_VALIDITY)
-		if err != nil {
-			return err
-		}
-		if err := serverctl.SaveKey(functions.GetNetmakerPath()+ncutils.GetSeparator(), tls.SERVER_KEY_NAME, key); err != nil {
-			return err
-		}
-		cert = newCert
-	} else if err != nil {
-		return err
-	} else if err == nil {
-		if serverKey, err := serverctl.ReadKeyFromDB(tls.SERVER_KEY_NAME); err == nil {
-			logger.Log(2, "saving server.key")
-			if err := serverctl.SaveKey(functions.GetNetmakerPath()+ncutils.GetSeparator(), tls.SERVER_KEY_NAME, *serverKey); err != nil {
-				return err
-			}
-		} else {
-			return err
-		}
-	}
-	logger.Log(2, "saving server.pem")
-	if err := serverctl.SaveCert(functions.GetNetmakerPath()+ncutils.GetSeparator(), tls.SERVER_PEM_NAME, cert); err != nil {
-		return err
-	}
-
-	// == SERVER-CLIENT connection cert handling ==
-
-	serverClientCert, err := serverctl.ReadCertFromDB(tls.SERVER_CLIENT_PEM)
-	if errors.Is(err, os.ErrNotExist) || database.IsEmptyRecord(err) || serverClientCert.NotAfter.Before(time.Now().Add(time.Hour*24*10)) {
-		//gen new key
-		logger.Log(0, "generating new server client key/certificate")
-		_, key, err := ed25519.GenerateKey(rand.Reader)
-		if err != nil {
-			return err
-		}
-		serverName := tls.NewCName(tls.SERVER_CLIENT_ENTRY)
-		csr, err := tls.NewCSR(key, serverName)
-		if err != nil {
-			return err
-		}
-		newServerClientCert, err := tls.NewEndEntityCert(*private, csr, ca, tls.CERTIFICATE_VALIDITY)
-		if err != nil {
-			return err
-		}
-
-		if err := serverctl.SaveKey(functions.GetNetmakerPath()+ncutils.GetSeparator(), tls.SERVER_CLIENT_KEY, key); err != nil {
-			return err
-		}
-		serverClientCert = newServerClientCert
-	} else if err != nil {
-		return err
-	} else if err == nil {
-		logger.Log(2, "saving serverclient.key")
-		if serverClientKey, err := serverctl.ReadKeyFromDB(tls.SERVER_CLIENT_KEY); err == nil {
-			if err := serverctl.SaveKey(functions.GetNetmakerPath()+ncutils.GetSeparator(), tls.SERVER_CLIENT_KEY, *serverClientKey); err != nil {
-				return err
-			}
-		} else {
-			return err
-		}
-	}
-
-	logger.Log(2, "saving serverclient.pem")
-	if err := serverctl.SaveCert(functions.GetNetmakerPath()+ncutils.GetSeparator(), tls.SERVER_CLIENT_PEM, serverClientCert); err != nil {
-		return err
-	}
-
-	logger.Log(1, "ensure the root.pem, root.key, server.pem, and server.key files are updated on your broker")
-
-	return serverctl.SetClientTLSConf(
-		functions.GetNetmakerPath()+ncutils.GetSeparator()+tls.SERVER_CLIENT_PEM,
-		functions.GetNetmakerPath()+ncutils.GetSeparator()+tls.SERVER_CLIENT_KEY,
-		ca,
-	)
-}

+ 196 - 0
mq/dynsec.go

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

+ 384 - 0
mq/dynsec_helper.go

@@ -0,0 +1,384 @@
+package mq
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"time"
+
+	mqtt "github.com/eclipse/paho.mqtt.golang"
+	"github.com/gravitl/netmaker/servercfg"
+)
+
+const (
+	// constant for admin role
+	adminRole = "admin"
+	// constant for server role
+	serverRole = "server"
+	// constant for exporter role
+	exporterRole = "exporter"
+	// constant for node role
+	NodeRole = "node"
+
+	// const for dynamic security file
+	dynamicSecurityFile = "dynamic-security.json"
+)
+
+var (
+	// default configuration of dynamic security
+	dynConfig = dynJSON{
+		Clients: []client{
+			{
+				Username:   mqAdminUserName,
+				TextName:   "netmaker admin user",
+				Password:   "",
+				Salt:       "",
+				Iterations: 0,
+				Roles: []clientRole{
+					{
+						Rolename: adminRole,
+					},
+				},
+			},
+			{
+				Username:   mqNetmakerServerUserName,
+				TextName:   "netmaker server user",
+				Password:   "",
+				Salt:       "",
+				Iterations: 0,
+				Roles: []clientRole{
+					{
+						Rolename: serverRole,
+					},
+				},
+			},
+		},
+		Roles: []role{
+			{
+				Rolename: adminRole,
+				Acls:     fetchAdminAcls(),
+			},
+			{
+				Rolename: serverRole,
+				Acls:     fetchServerAcls(),
+			},
+			{
+				Rolename: NodeRole,
+				Acls:     fetchNodeAcls(),
+			},
+		},
+		DefaultAcl: defaultAccessAcl{
+			PublishClientSend:    false,
+			PublishClientReceive: true,
+			Subscribe:            false,
+			Unsubscribe:          true,
+		},
+	}
+
+	exporterMQClient = client{
+		Username:   mqExporterUserName,
+		TextName:   "netmaker metrics exporter",
+		Password:   "",
+		Salt:       "",
+		Iterations: 101,
+		Roles: []clientRole{
+			{
+				Rolename: exporterRole,
+			},
+		},
+	}
+	exporterMQRole = role{
+		Rolename: exporterRole,
+		Acls:     fetchExporterAcls(),
+	}
+)
+
+// DynListCLientsCmdResp - struct for list clients response from MQ
+type DynListCLientsCmdResp struct {
+	Responses []struct {
+		Command string          `json:"command"`
+		Error   string          `json:"error"`
+		Data    ListClientsData `json:"data"`
+	} `json:"responses"`
+}
+
+// ListClientsData - struct for list clients data
+type ListClientsData struct {
+	Clients    []string `json:"clients"`
+	TotalCount int      `json:"totalCount"`
+}
+
+// GetAdminClient - fetches admin client of the MQ
+func GetAdminClient() (mqtt.Client, error) {
+	opts := mqtt.NewClientOptions()
+	setMqOptions(mqAdminUserName, servercfg.GetMqAdminPassword(), opts)
+	mqclient := mqtt.NewClient(opts)
+	var connecterr error
+	if token := mqclient.Connect(); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
+		if token.Error() == nil {
+			connecterr = errors.New("connect timeout")
+		} else {
+			connecterr = token.Error()
+		}
+	}
+	return mqclient, connecterr
+}
+
+// ListClients -  to list all clients in the MQ
+func ListClients(client mqtt.Client) (ListClientsData, error) {
+	respChan := make(chan mqtt.Message, 10)
+	defer close(respChan)
+	command := "listClients"
+	resp := ListClientsData{}
+	msg := MqDynsecPayload{
+		Commands: []MqDynSecCmd{
+			{
+				Command: command,
+			},
+		},
+	}
+	client.Subscribe("$CONTROL/dynamic-security/v1/response", 2, mqtt.MessageHandler(func(c mqtt.Client, m mqtt.Message) {
+		respChan <- m
+	}))
+	defer client.Unsubscribe()
+	d, _ := json.Marshal(msg)
+	token := client.Publish("$CONTROL/dynamic-security/v1", 2, true, d)
+	if !token.WaitTimeout(30) || token.Error() != nil {
+		var err error
+		if token.Error() == nil {
+			err = errors.New("connection timeout")
+		} else {
+			err = token.Error()
+		}
+		return resp, err
+	}
+
+	for m := range respChan {
+		msg := DynListCLientsCmdResp{}
+		json.Unmarshal(m.Payload(), &msg)
+		for _, mI := range msg.Responses {
+			if mI.Command == command {
+				return mI.Data, nil
+			}
+		}
+	}
+	return resp, errors.New("resp not found")
+}
+
+// FetchNetworkAcls - fetches network acls
+func FetchNetworkAcls(network string) []Acl {
+	return []Acl{
+		{
+			AclType:  "publishClientReceive",
+			Topic:    fmt.Sprintf("update/%s/#", network),
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientReceive",
+			Topic:    fmt.Sprintf("peers/%s/#", network),
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "subscribePattern",
+			Topic:    "#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "unsubscribePattern",
+			Topic:    "#",
+			Priority: -1,
+			Allow:    true,
+		},
+	}
+}
+
+// serverAcls - fetches server role related acls
+func fetchServerAcls() []Acl {
+	return []Acl{
+		{
+			AclType:  "publishClientSend",
+			Topic:    "peers/#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientSend",
+			Topic:    "update/#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientSend",
+			Topic:    "metrics_exporter",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientReceive",
+			Topic:    "ping/#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientReceive",
+			Topic:    "update/#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientReceive",
+			Topic:    "signal/#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientReceive",
+			Topic:    "metrics/#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "subscribePattern",
+			Topic:    "#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "unsubscribePattern",
+			Topic:    "#",
+			Priority: -1,
+			Allow:    true,
+		},
+	}
+}
+
+// fetchNodeAcls - fetches node related acls
+func fetchNodeAcls() []Acl {
+	// keeping node acls generic as of now.
+	return []Acl{
+
+		{
+			AclType:  "publishClientSend",
+			Topic:    "signal/#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientSend",
+			Topic:    "update/#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientSend",
+			Topic:    "ping/#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientSend",
+			Topic:    "metrics/#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "subscribePattern",
+			Topic:    "#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "unsubscribePattern",
+			Topic:    "#",
+			Priority: -1,
+			Allow:    true,
+		},
+	}
+}
+
+// fetchExporterAcls - fetch exporter role related acls
+func fetchExporterAcls() []Acl {
+	return []Acl{
+		{
+			AclType:  "publishClientReceive",
+			Topic:    "metrics_exporter",
+			Allow:    true,
+			Priority: -1,
+		},
+		{
+			AclType:  "subscribePattern",
+			Topic:    "#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "unsubscribePattern",
+			Topic:    "#",
+			Priority: -1,
+			Allow:    true,
+		},
+	}
+}
+
+// fetchAdminAcls - fetches admin role related acls
+func fetchAdminAcls() []Acl {
+	return []Acl{
+		{
+			AclType:  "publishClientSend",
+			Topic:    "$CONTROL/dynamic-security/#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientReceive",
+			Topic:    "$CONTROL/dynamic-security/#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "subscribePattern",
+			Topic:    "$CONTROL/dynamic-security/#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientReceive",
+			Topic:    "$SYS/#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "subscribePattern",
+			Topic:    "$SYS/#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientReceive",
+			Topic:    "#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "subscribePattern",
+			Topic:    "#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "unsubscribePattern",
+			Topic:    "#",
+			Priority: -1,
+			Allow:    true,
+		},
+		{
+			AclType:  "publishClientSend",
+			Topic:    "#",
+			Priority: -1,
+			Allow:    true,
+		},
+	}
+}

+ 5 - 1
mq/handlers.go

@@ -251,7 +251,11 @@ func updateNodeMetrics(currentNode *models.Node, newMetrics *models.Metrics) boo
 		oldMetric := oldMetrics.Connectivity[k]
 		currMetric.TotalTime += oldMetric.TotalTime
 		currMetric.Uptime += oldMetric.Uptime // get the total uptime for this connection
-		currMetric.PercentUp = 100.0 * (float64(currMetric.Uptime) / float64(currMetric.TotalTime))
+		if currMetric.Uptime == 0 || currMetric.TotalTime == 0 {
+			currMetric.PercentUp = 0
+		} else {
+			currMetric.PercentUp = 100.0 * (float64(currMetric.Uptime) / float64(currMetric.TotalTime))
+		}
 		totalUpMinutes := currMetric.Uptime * ncutils.CheckInInterval
 		currMetric.ActualUptime = time.Duration(totalUpMinutes) * time.Minute
 		delete(oldMetrics.Connectivity, k) // remove from old data

+ 43 - 7
mq/mq.go

@@ -2,13 +2,13 @@ package mq
 
 import (
 	"context"
+	"fmt"
 	"time"
 
 	mqtt "github.com/eclipse/paho.mqtt.golang"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
-	"github.com/gravitl/netmaker/serverctl"
 )
 
 // KEEPALIVE_TIMEOUT - time in seconds for timeout
@@ -23,21 +23,57 @@ var peer_force_send = 0
 
 var mqclient mqtt.Client
 
-// SetupMQTT creates a connection to broker and return client
-func SetupMQTT() {
+// SetUpAdminClient - sets up admin client for the MQ
+func SetUpAdminClient() {
 	opts := mqtt.NewClientOptions()
-	broker, secure := servercfg.GetMessageQueueEndpoint()
+	setMqOptions(mqAdminUserName, servercfg.GetMqAdminPassword(), opts)
+	mqAdminClient = mqtt.NewClient(opts)
+	opts.SetOnConnectHandler(func(client mqtt.Client) {
+		if token := client.Subscribe(dynamicSecSubTopic, 2, mqtt.MessageHandler(watchDynSecTopic)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
+			client.Disconnect(240)
+			logger.Log(0, fmt.Sprintf("Dynamic security client subscription failed: %v ", token.Error()))
+		}
+
+		opts.SetOrderMatters(true)
+		opts.SetResumeSubs(true)
+	})
+	tperiod := time.Now().Add(10 * time.Second)
+	for {
+		if token := mqAdminClient.Connect(); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
+			logger.Log(2, "Admin: unable to connect to broker, retrying ...")
+			if time.Now().After(tperiod) {
+				if token.Error() == nil {
+					logger.FatalLog("Admin: could not connect to broker, token timeout, exiting ...")
+				} else {
+					logger.FatalLog("Admin: could not connect to broker, exiting ...", token.Error().Error())
+				}
+			}
+		} else {
+			break
+		}
+		time.Sleep(2 * time.Second)
+	}
+
+}
+
+func setMqOptions(user, password string, opts *mqtt.ClientOptions) {
+	broker, _ := servercfg.GetMessageQueueEndpoint()
 	opts.AddBroker(broker)
 	id := ncutils.MakeRandomString(23)
 	opts.ClientID = id
-	if secure {
-		opts.SetTLSConfig(&serverctl.TlsConfig)
-	}
+	opts.SetUsername(user)
+	opts.SetPassword(password)
 	opts.SetAutoReconnect(true)
 	opts.SetConnectRetry(true)
 	opts.SetConnectRetryInterval(time.Second << 2)
 	opts.SetKeepAlive(time.Minute)
 	opts.SetWriteTimeout(time.Minute)
+}
+
+// SetupMQTT creates a connection to broker and return client
+func SetupMQTT() {
+	opts := mqtt.NewClientOptions()
+	setMqOptions(mqNetmakerServerUserName, servercfg.GetMqAdminPassword(), opts)
 	opts.SetOnConnectHandler(func(client mqtt.Client) {
 		if token := client.Subscribe("ping/#", 2, mqtt.MessageHandler(Ping)); token.WaitTimeout(MQ_TIMEOUT*time.Second) && token.Error() != nil {
 			client.Disconnect(240)

+ 1 - 1
mq/publishers.go

@@ -233,7 +233,7 @@ func pushMetricsToExporter(metrics models.Metrics) error {
 	if err != nil {
 		return errors.New("failed to marshal metrics: " + err.Error())
 	}
-	if token := mqclient.Publish("metrics_exporter", 0, true, data); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
+	if token := mqclient.Publish("metrics_exporter", 2, true, data); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
 		var err error
 		if token.Error() == nil {
 			err = errors.New("connection timeout")

+ 1 - 25
netclient/command/commands.go

@@ -1,8 +1,6 @@
 package command
 
 import (
-	"crypto/ed25519"
-	"crypto/rand"
 	"errors"
 	"fmt"
 	"strings"
@@ -12,7 +10,6 @@ import (
 	"github.com/gravitl/netmaker/netclient/daemon"
 	"github.com/gravitl/netmaker/netclient/functions"
 	"github.com/gravitl/netmaker/netclient/ncutils"
-	"github.com/gravitl/netmaker/tls"
 )
 
 // Join - join command to run from cli
@@ -115,29 +112,8 @@ func Pull(cfg *config.ClientConfig) error {
 
 		currentServers[currCfg.Server.Server] = *currCfg
 	}
-	//generate new client key if one doesn' exist
-	var private *ed25519.PrivateKey
-	private, err = tls.ReadKeyFromFile(ncutils.GetNetclientPath() + ncutils.GetSeparator() + "client.key")
-	if err != nil {
-		_, newKey, err := ed25519.GenerateKey(rand.Reader)
-		if err != nil {
-			return err
-		}
-		if err := tls.SaveKeyToFile(ncutils.GetNetclientPath(), ncutils.GetSeparator()+"client.key", newKey); err != nil {
-			return err
-		}
-		private = &newKey
-	}
-	// re-register with server -- get new certs for broker
-	for _, clientCfg := range currentServers {
-		if err = functions.RegisterWithServer(private, &clientCfg); err != nil {
-			logger.Log(0, "registration error", err.Error())
-		} else {
-			daemon.Restart()
-		}
-	}
+	daemon.Restart()
 	logger.Log(1, "reset network", cfg.Network, "and peer configs")
-
 	return err
 }
 

+ 13 - 34
netclient/functions/daemon.go

@@ -2,13 +2,10 @@ package functions
 
 import (
 	"context"
-	"crypto/ed25519"
-	"crypto/rand"
 	"crypto/tls"
 	"crypto/x509"
 	"errors"
 	"fmt"
-	"log"
 	"os"
 	"os/signal"
 	"strings"
@@ -21,12 +18,10 @@ import (
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/config"
-	"github.com/gravitl/netmaker/netclient/daemon"
 	"github.com/gravitl/netmaker/netclient/global_settings"
 	"github.com/gravitl/netmaker/netclient/local"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/wireguard"
-	ssl "github.com/gravitl/netmaker/tls"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 )
 
@@ -237,14 +232,14 @@ func setupMQTTSingleton(cfg *config.ClientConfig) error {
 	opts := mqtt.NewClientOptions()
 	server := cfg.Server.Server
 	port := cfg.Server.MQPort
-	opts.AddBroker("ssl://" + server + ":" + port)
-	tlsConfig, err := NewTLSConfig(server)
+	pass, err := os.ReadFile(ncutils.GetNetclientPathSpecific() + "secret-" + cfg.Network)
 	if err != nil {
-		logger.Log(0, "failed to get TLS config for", server, err.Error())
-		return err
+		return fmt.Errorf("could not read secrets file %w", err)
 	}
-	opts.SetTLSConfig(tlsConfig)
-	mqclient = mqtt.NewClient(opts)
+	opts.AddBroker("mqtts://" + server + ":" + port)
+	opts.SetUsername(cfg.Node.ID)
+	opts.SetPassword(string(pass))
+	mqclient := mqtt.NewClient(opts)
 	var connecterr error
 	opts.SetClientID(ncutils.MakeRandomString(23))
 	if token := mqclient.Connect(); !token.WaitTimeout(30*time.Second) || token.Error() != nil {
@@ -264,13 +259,13 @@ func setupMQTT(cfg *config.ClientConfig) error {
 	opts := mqtt.NewClientOptions()
 	server := cfg.Server.Server
 	port := cfg.Server.MQPort
-	opts.AddBroker("ssl://" + server + ":" + port)
-	tlsConfig, err := NewTLSConfig(server)
+	pass, err := os.ReadFile(ncutils.GetNetclientPathSpecific() + "secret-" + cfg.Network)
 	if err != nil {
-		logger.Log(0, "failed to get TLS config for", server, err.Error())
-		return err
+		return fmt.Errorf("could not read secrets file %w", err)
 	}
-	opts.SetTLSConfig(tlsConfig)
+	opts.AddBroker(fmt.Sprintf("mqtts://%s:%s", server, port))
+	opts.SetUsername(cfg.Node.ID)
+	opts.SetPassword(string(pass))
 	opts.SetClientID(ncutils.MakeRandomString(23))
 	opts.SetDefaultPublishHandler(All)
 	opts.SetAutoReconnect(true)
@@ -313,29 +308,13 @@ func setupMQTT(cfg *config.ClientConfig) error {
 		}
 	}
 	if connecterr != nil {
-		reRegisterWithServer(cfg)
-		//try after re-registering
-		if token := mqclient.Connect(); !token.WaitTimeout(30*time.Second) || token.Error() != nil {
-			return errors.New("unable to connect to broker")
-		}
+		logger.Log(0, "failed to establish connection to broker: ", connecterr.Error())
+		return connecterr
 	}
 
 	return nil
 }
 
-func reRegisterWithServer(cfg *config.ClientConfig) {
-	logger.Log(0, "connection issue detected.. attempt connection with new certs and broker information")
-	key, err := ssl.ReadKeyFromFile(ncutils.GetNetclientPath() + ncutils.GetSeparator() + "client.key")
-	if err != nil {
-		_, *key, err = ed25519.GenerateKey(rand.Reader)
-		if err != nil {
-			log.Fatal("could not generate new key")
-		}
-	}
-	RegisterWithServer(key, cfg)
-	daemon.Restart()
-}
-
 // publishes a message to server to update peers on this peer's behalf
 func publishSignal(nodeCfg *config.ClientConfig, signal byte) error {
 	if err := publish(nodeCfg, fmt.Sprintf("signal/%s", nodeCfg.Node.ID), []byte{signal}, 1); err != nil {

+ 1 - 5
netclient/functions/join.go

@@ -199,7 +199,7 @@ func JoinNetwork(cfg *config.ClientConfig, privateKey string) error {
 		return err
 	}
 	if cfg.Node.Password == "" {
-		cfg.Node.Password = logic.GenKey()
+		cfg.Node.Password = logic.GenPassWord()
 	}
 	//check if ListenPort was set on command line
 	if cfg.Node.ListenPort != 0 {
@@ -362,10 +362,6 @@ func JoinNetwork(cfg *config.ClientConfig, privateKey string) error {
 
 	local.SetNetmakerDomainRoute(cfg.Server.API)
 	cfg.Node = node
-	if err := Register(cfg); err != nil {
-		return err
-	}
-
 	logger.Log(0, "starting wireguard")
 	err = wireguard.InitWireguard(&node, privateKey, nodeGET.Peers[:])
 	if err != nil {

+ 0 - 19
netclient/functions/mqpublish.go

@@ -8,7 +8,6 @@ import (
 	"io"
 	"net"
 	"net/http"
-	"os"
 	"strconv"
 	"sync"
 	"time"
@@ -20,7 +19,6 @@ import (
 	"github.com/gravitl/netmaker/netclient/auth"
 	"github.com/gravitl/netmaker/netclient/config"
 	"github.com/gravitl/netmaker/netclient/ncutils"
-	"github.com/gravitl/netmaker/tls"
 )
 
 var metricsCache = new(sync.Map)
@@ -109,7 +107,6 @@ func checkin() {
 			config.Write(&nodeCfg, nodeCfg.Network)
 		}
 		Hello(&nodeCfg)
-		checkCertExpiry(&nodeCfg)
 		if nodeCfg.Server.Is_EE {
 			logger.Log(0, "collecting metrics for node", nodeCfg.Node.Name)
 			publishMetrics(&nodeCfg)
@@ -262,22 +259,6 @@ func publish(nodeCfg *config.ClientConfig, dest string, msg []byte, qos byte) er
 	return nil
 }
 
-func checkCertExpiry(cfg *config.ClientConfig) error {
-	cert, err := tls.ReadCertFromFile(ncutils.GetNetclientServerPath(cfg.Server.Server) + ncutils.GetSeparator() + "client.pem")
-	//if cert doesn't exist or will expire within 10 days
-	if errors.Is(err, os.ErrNotExist) || cert.NotAfter.Before(time.Now().Add(time.Hour*24*10)) {
-		key, err := tls.ReadKeyFromFile(ncutils.GetNetclientPath() + ncutils.GetSeparator() + "client.key")
-		if err != nil {
-			return err
-		}
-		return RegisterWithServer(key, cfg)
-	}
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
 func checkBroker(broker string, port string) error {
 	if broker == "" {
 		return errors.New("error: broker address is blank")

+ 0 - 100
netclient/functions/register.go

@@ -1,100 +0,0 @@
-package functions
-
-import (
-	"crypto/ed25519"
-	"crypto/rand"
-	"encoding/json"
-	"errors"
-	"net/http"
-	"os"
-
-	"github.com/gravitl/netmaker/logger"
-	"github.com/gravitl/netmaker/netclient/config"
-	"github.com/gravitl/netmaker/netclient/ncutils"
-	"github.com/gravitl/netmaker/tls"
-)
-
-// Register - the function responsible for registering with the server and acquiring certs
-func Register(cfg *config.ClientConfig) error {
-
-	//generate new key if one doesn' exist
-	var private *ed25519.PrivateKey
-	var err error
-	private, err = tls.ReadKeyFromFile(ncutils.GetNetclientPath() + ncutils.GetSeparator() + "client.key")
-	if err != nil {
-		_, newKey, err := ed25519.GenerateKey(rand.Reader)
-		if err != nil {
-			return err
-		}
-		if err := tls.SaveKeyToFile(ncutils.GetNetclientPath(), ncutils.GetSeparator()+"client.key", newKey); err != nil {
-			return err
-		}
-		private = &newKey
-	}
-	//check if cert exists
-	_, err = tls.ReadCertFromFile(ncutils.GetNetclientServerPath(cfg.Server.Server) + ncutils.GetSeparator() + "client.pem")
-	if errors.Is(err, os.ErrNotExist) {
-		if err := RegisterWithServer(private, cfg); err != nil {
-			return err
-		}
-	} else if err != nil {
-		return err
-	}
-	return nil
-}
-
-// RegisterWithServer calls the register endpoint with privatekey and commonname - api returns ca and client certificate
-func RegisterWithServer(private *ed25519.PrivateKey, cfg *config.ClientConfig) error {
-	data := config.RegisterRequest{
-		Key:        *private,
-		CommonName: tls.NewCName(cfg.Node.Name),
-	}
-	url := "https://" + cfg.Server.API + "/api/server/register"
-	logger.Log(1, "register at "+url)
-
-	token, err := Authenticate(cfg)
-	if err != nil {
-		return err
-	}
-	response, err := API(data, http.MethodPost, url, token)
-	if err != nil {
-		return err
-	}
-	if response.StatusCode != http.StatusOK {
-		return errors.New(response.Status)
-	}
-	var resp config.RegisterResponse
-	if err := json.NewDecoder(response.Body).Decode(&resp); err != nil {
-		return errors.New("unmarshal cert error " + err.Error())
-	}
-
-	// set broker information on register
-	var modServer bool
-	if resp.Broker != "" && resp.Broker != cfg.Server.Server {
-		cfg.Server.Server = resp.Broker
-		modServer = true
-	}
-	if resp.Port != "" && resp.Port != cfg.Server.MQPort {
-		cfg.Server.MQPort = resp.Port
-		modServer = true
-	}
-	if modServer {
-		if err = config.ModServerConfig(&cfg.Server, cfg.Node.Network); err != nil {
-			logger.Log(0, "network:", cfg.Node.Network, "error overwriting config with broker information: "+err.Error())
-		}
-	}
-
-	//x509.Certificate.PublicKey is an interface so json encoding/decoding results in a string rather that []byte
-	//the pubkeys are included in the response so the values in the certificate can be updated appropriately
-	resp.CA.PublicKey = resp.CAPubKey
-	resp.Cert.PublicKey = resp.CertPubKey
-	if err := tls.SaveCertToFile(ncutils.GetNetclientServerPath(cfg.Server.Server)+ncutils.GetSeparator(), tls.ROOT_PEM_NAME, &resp.CA); err != nil {
-		return err
-	}
-	if err := tls.SaveCertToFile(ncutils.GetNetclientServerPath(cfg.Server.Server)+ncutils.GetSeparator(), "client.pem", &resp.Cert); err != nil {
-		return err
-	}
-	logger.Log(0, "network:", cfg.Network, "certificates/key saved ")
-	//join the network defined in the token
-	return nil
-}

+ 5 - 5
netclient/wireguard/mac.go

@@ -26,7 +26,7 @@ func WgQuickDownMac(node *models.Node, iface string) error {
 
 // RemoveConfMac - bring down mac interface and remove routes
 func RemoveConfMac(iface string) error {
-	realIface, err := getRealIface(iface)
+	realIface, err := GetRealIface(iface)
 	if realIface != "" {
 		err = deleteInterface(iface, realIface)
 	}
@@ -37,7 +37,7 @@ func RemoveConfMac(iface string) error {
 func WgQuickUpMac(node *models.Node, iface string, confPath string) error {
 	var err error
 	var realIface string
-	realIface, err = getRealIface(iface)
+	realIface, err = GetRealIface(iface)
 	if realIface != "" && err == nil {
 		deleteInterface(iface, realIface)
 		deleteRoutes(realIface)
@@ -101,8 +101,8 @@ func addInterface(iface string) (string, error) {
 	return realIface, err
 }
 
-// getRealIface - retrieves tun iface based on reference iface name from config file
-func getRealIface(iface string) (string, error) {
+// GetRealIface - retrieves tun iface based on reference iface name from config file
+func GetRealIface(iface string) (string, error) {
 	ncutils.RunCmd("wg show interfaces", false)
 	ifacePath := "/var/run/wireguard/" + iface + ".name"
 	if !(ncutils.FileExists(ifacePath)) {
@@ -120,7 +120,7 @@ func getRealIface(iface string) (string, error) {
 
 // deleteRoutes - deletes network routes associated with interface
 func deleteRoutes(iface string) error {
-	realIface, err := getRealIface(iface)
+	realIface, err := GetRealIface(iface)
 	if err != nil {
 		return err
 	}

+ 4 - 3
scripts/nm-quick.sh

@@ -80,7 +80,7 @@ COREDNS_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p')
 SERVER_PUBLIC_IP=$(curl -s ifconfig.me)
 MASTER_KEY=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 30 ; echo '')
 EMAIL="$(echo $RANDOM | md5sum  | head -c 32)@email.com"
-
+MQ_ADMIN_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 64 ; echo '')
 if [ -n "$domain" ]; then
   NETMAKER_BASE_DOMAIN=$domain
 fi
@@ -128,7 +128,8 @@ sleep 5
 echo "setting mosquitto.conf..."
 
 wget -q -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/master/docker/mosquitto.conf
-
+wget -q -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/master/docker/wait.sh
+chmod +x /root/wait.sh
 echo "setting docker-compose..."
 
 mkdir -p /etc/netmaker
@@ -139,7 +140,7 @@ sed -i "s/SERVER_PUBLIC_IP/$SERVER_PUBLIC_IP/g" /root/docker-compose.yml
 sed -i "s/COREDNS_IP/$COREDNS_IP/g" /root/docker-compose.yml
 sed -i "s/REPLACE_MASTER_KEY/$MASTER_KEY/g" /root/docker-compose.yml
 sed -i "s/YOUR_EMAIL/$EMAIL/g" /root/docker-compose.yml
-
+sed -i "s/REPLACE_MQ_ADMIN_PASSWORD/$MQ_ADMIN_PASSWORD/g" /root/docker-compose.yml
 echo "starting containers..."
 
 docker-compose -f /root/docker-compose.yml up -d

+ 11 - 0
servercfg/serverconf.go

@@ -622,6 +622,17 @@ func GetMQServerPort() string {
 	return port
 }
 
+// GetMqAdminPassword - fetches the MQ Admin password
+func GetMqAdminPassword() string {
+	password := ""
+	if os.Getenv("MQ_ADMIN_PASSWORD") != "" {
+		password = os.Getenv("MQ_ADMIN_PASSWORD")
+	} else if config.Config.Server.MQAdminPassword != "" {
+		password = config.Config.Server.MQAdminPassword
+	}
+	return password
+}
+
 // IsBasicAuthEnabled - checks if basic auth has been configured to be turned off
 func IsBasicAuthEnabled() bool {
 	var enabled = true //default

+ 1 - 1
validation/validation.go

@@ -3,7 +3,7 @@ package validation
 import (
 	"regexp"
 
-	"github.com/go-playground/validator/v10"
+	validator "github.com/go-playground/validator/v10"
 )
 
 // CheckYesOrNo - checks if a field on a struct is yes or no