Browse Source

create client for nodes on authenticate,generate dyn sec file on startup

Abhishek Kondur 2 years ago
parent
commit
878430bf75

+ 2 - 4
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,7 +39,7 @@ services:
       VERBOSITY: "1"
       MANAGE_IPTABLES: "on"
       PORT_FORWARD_SERVICES: "dns"
-      MQ_ADMIN_PASSWORD: "MQ_ADMIN_PASSWORD"
+      MQ_ADMIN_PASSWORD: "REPLACE_MQ_ADMIN_PASSWORD"
     ports:
       - "51821-51830:51821-51830/udp"
     expose:
@@ -115,7 +115,6 @@ services:
      NETMAKER_SERVER_HOST: "api.NETMAKER_BASE_DOMAIN"
     volumes:
       - /root/mosquitto.conf:/mosquitto/config/mosquitto.conf
-      - /root/dynamic-security.json:/mosquitto/config/dynamic-security.json
       - /root/wait.sh:/mosquitto/config/wait.sh
       - mosquitto_data:/mosquitto/data
       - mosquitto_logs:/mosquitto/log
@@ -130,7 +129,6 @@ services:
       - traefik.tcp.routers.mqtts.entrypoints=websecure
 volumes:
   traefik_certs: {}
-  shared_certs: {}
   sqldata: {}
   dnsconfig: {}
   mosquitto_data: {}

+ 84 - 65
controllers/node.go

@@ -67,75 +67,94 @@ 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
-			}
+		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
-				}
+	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
+	}
+	event := mq.DynSecAction{
+		ActionType: mq.CreateClient,
+		Payload: mq.MqDynsecPayload{
+			Commands: []mq.MqDynSecCmd{
+				{
+					Command:  mq.CreateClientCmd,
+					Username: result.ID,
+					Password: authRequest.Password,
+					Textname: result.Name,
+					Roles:    make([]mq.MqDynSecRole, 0),
+					Groups:   make([]mq.MqDynSecGroup, 0),
+				},
+			},
+		},
+	}
+	if err := mq.PublishEventToDynSecTopic(event); err != nil {
+		logger.Log(0, fmt.Sprintf("failed to send DynSec command [%s]: %v",
+			event.ActionType, err.Error()))
+		errorResponse.Code = http.StatusInternalServerError
+		errorResponse.Message = fmt.Sprintf("could not create mq client for node [%s]: %v", result.ID, err)
+		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)
-			}
-		}
+	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)

+ 0 - 68
docker/dynamic-security.json

@@ -1,68 +0,0 @@
-{
-	"clients":	[{
-			"username":	"Netmaker-Admin",
-			"textName":	"netmaker admin user",
-			"password":	"T42rorlC/mAP+i19g/YqMlWShPpfo8F/nBz2ZQNRcjAnfczrgu4rIQam9z7T/87NBIHxqR1wMlCIvRN5JApHcw==",
-			"salt":	"lHl24sEf+lJ/kFHk",
-			"iterations":	101,
-			"roles":	[{
-					"rolename":	"admin"
-				}]
-		},
-		{
-			"username":	"netmaker-exporter",
-			"textName":	"netmaker metrics exporter",
-			"password":	"yl7HZglF4CvCxgjPLLIYc73LRtjEwp2/SAEQXeW5Ta1Dl4RoLN5/gjqiv8xmue+F9LfRk8KICkNbhSYuEfJ7ww==",
-			"salt":	"veLl9eN02i+hKkyT",
-			"iterations":	101,
-			"roles":	[]
-		}],
-	"roles":	[{
-			"rolename":	"admin",
-			"acls":	[{
-					"acltype":	"publishClientSend",
-					"topic":	"$CONTROL/dynamic-security/#",
-					"allow":	true
-				}, {
-					"acltype":	"publishClientReceive",
-					"topic":	"$CONTROL/dynamic-security/#",
-					"allow":	true
-				}, {
-					"acltype":	"subscribePattern",
-					"topic":	"$CONTROL/dynamic-security/#",
-					"allow":	true
-				}, {
-					"acltype":	"publishClientReceive",
-					"topic":	"$SYS/#",
-					"allow":	true
-				}, {
-					"acltype":	"subscribePattern",
-					"topic":	"$SYS/#",
-					"allow":	true
-				}, {
-					"acltype":	"publishClientReceive",
-					"topic":	"#",
-					"allow":	true
-				}, {
-					"acltype":	"subscribePattern",
-					"topic":	"#",
-					"allow":	true
-				}, {
-					"acltype":	"unsubscribePattern",
-					"topic":	"#",
-					"allow":	true
-				},
-				{
-					"acltype":	"publishClientSend",
-					"topic":	"#",
-					"allow":	true
-				}
-			]
-		}],
-	"defaultACLAccess":	{
-		"publishClientSend":	true,
-		"publishClientReceive":	true,
-		"subscribe":	true,
-		"unsubscribe":	true
-	}
-}

+ 1 - 1
docker/mosquitto.conf

@@ -4,5 +4,5 @@ allow_anonymous false
 listener 1883
 allow_anonymous false
 plugin /usr/lib/mosquitto_dynamic_security.so
-plugin_opt_config_file /mosquitto/config/dynamic-security.json
+plugin_opt_config_file /mosquitto/data/dynamic-security.json
 

+ 11 - 5
docker/wait.sh

@@ -1,6 +1,7 @@
-#!/bin/sh
+#!/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"
@@ -8,10 +9,15 @@ wait_for_netmaker() {
   done
 }
 
-main() {
-  # wait for netmaker to startup
-  apk add curl
-  wait_for_netmaker
+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 "${@}"

+ 5 - 0
logic/accesskeys.go

@@ -222,6 +222,11 @@ func genKeyName() string {
 }
 
 func GenKey() string {
+	entropy, _ := rand.Int(rand.Reader, maxentropy)
+	return entropy.Text(16)[:16]
+}
+
+func GenPassWord() string {
 	entropy, _ := rand.Int(rand.Reader, maxentropy)
 	return entropy.Text(62)[:64]
 }

+ 99 - 28
mq/dynsec.go

@@ -9,12 +9,88 @@ import (
 	"os"
 
 	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"
 )
 
+var (
+	dynamicSecurityFile = "dynamic-security.json"
+	dynConfig           = dynJSON{
+		Clients: []client{
+			{
+				Username:   "Netmaker-Admin",
+				TextName:   "netmaker admin user",
+				Password:   "",
+				Salt:       "",
+				Iterations: 0,
+				Roles: []clientRole{
+					{
+						Rolename: "admin",
+					},
+				},
+			},
+			{
+				Username:   "Netmaker-Server",
+				TextName:   "netmaker server user",
+				Password:   "",
+				Salt:       "",
+				Iterations: 0,
+				Roles:      []clientRole{},
+			},
+			{
+				Username:   "netmaker-exporter",
+				TextName:   "netmaker metrics exporter",
+				Password:   "yl7HZglF4CvCxgjPLLIYc73LRtjEwp2/SAEQXeW5Ta1Dl4RoLN5/gjqiv8xmue+F9LfRk8KICkNbhSYuEfJ7ww==",
+				Salt:       "veLl9eN02i+hKkyT",
+				Iterations: 0,
+				Roles:      []clientRole{},
+			},
+		},
+		Roles: []role{
+			{
+				Rolename: "admin",
+				Acls: []Acl{
+					{
+						AclType: "publishClientSend",
+						Topic:   "$CONTROL/dynamic-security/#",
+						Allow:   true,
+					},
+					{
+						AclType: "publishClientReceive",
+						Topic:   "$CONTROL/dynamic-security/#",
+						Allow:   true,
+					},
+					{
+						AclType: "subscribePattern",
+						Topic:   "$CONTROL/dynamic-security/#",
+						Allow:   true,
+					},
+					{
+						AclType: "publishClientReceive",
+						Topic:   "$SYS/#",
+						Allow:   true,
+					},
+					{
+						AclType: "subscribePattern",
+						Topic:   "$SYS/#",
+						Allow:   true,
+					},
+				},
+			},
+		},
+		DefaultAcl: defaultAccessAcl{
+			PublishClientSend:    true,
+			PublishClientReceive: true,
+			Subscribe:            true,
+			Unsubscribe:          true,
+		},
+	}
+)
+
 const DynamicSecSubTopic = "$CONTROL/dynamic-security/#"
 const DynamicSecPubTopic = "$CONTROL/dynamic-security/v1"
 
@@ -37,29 +113,32 @@ var (
 	ModifyClientCmd  = "modifyClient"
 )
 
+type dynJSON struct {
+	Clients    []client         `json:"clients"`
+	Roles      []role           `json:"roles"`
+	DefaultAcl defaultAccessAcl `json:"defaultACLAccess"`
+}
+
 var (
 	mqAdminUserName          string = "Netmaker-Admin"
 	mqNetmakerServerUserName string = "Netmaker-Server"
 )
 
+type clientRole struct {
+	Rolename string `json:"rolename"`
+}
 type client struct {
-	Username   string `json:"username"`
-	TextName   string `json:"textName"`
-	Password   string `json:"password"`
-	Salt       string `json:"salt"`
-	Iterations int    `json:"iterations"`
-	Roles      []struct {
-		Rolename string `json:"rolename"`
-	} `json:"roles"`
+	Username   string       `json:"username"`
+	TextName   string       `json:"textName"`
+	Password   string       `json:"password"`
+	Salt       string       `json:"salt"`
+	Iterations int          `json:"iterations"`
+	Roles      []clientRole `json:"roles"`
 }
 
 type role struct {
 	Rolename string `json:"rolename"`
-	Acls     []struct {
-		Acltype string `json:"acltype"`
-		Topic   string `json:"topic"`
-		Allow   bool   `json:"allow"`
-	} `json:"acls"`
+	Acls     []Acl  `json:"acls"`
 }
 
 type defaultAccessAcl struct {
@@ -120,34 +199,26 @@ func encodePasswordToPBKDF2(password string, salt string, iterations int, keyLen
 }
 
 func Configure() error {
-	file := "/root/dynamic-security.json"
-	b, err := os.ReadFile(file)
-	if err != nil {
-		return err
-	}
-	c := dynCnf{}
-	err = json.Unmarshal(b, &c)
-	if err != nil {
-		return err
-	}
 	password := servercfg.GetMqAdminPassword()
 	if password == "" {
 		return errors.New("MQ admin password not provided")
 	}
-	for i, cI := range c.Clients {
+	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))
-			c.Clients[i] = cI
+			dynConfig.Clients[i] = cI
 		}
 	}
-	data, err := json.MarshalIndent(c, "", " ")
+	data, err := json.MarshalIndent(dynConfig, "", " ")
 	if err != nil {
 		return err
 	}
-	return os.WriteFile(file, data, 0755)
+	path := functions.GetNetmakerPath() + ncutils.GetSeparator() + dynamicSecurityFile
+	return os.WriteFile(path, data, 0755)
 }
 
 func PublishEventToDynSecTopic(event DynSecAction) error {
@@ -157,7 +228,7 @@ func PublishEventToDynSecTopic(event DynSecAction) error {
 		return err
 	}
 	if token := mqAdminClient.Publish(DynamicSecPubTopic, 2, false, d); token.Error() != nil {
-		return err
+		return token.Error()
 	}
 	return nil
 }

+ 1 - 1
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 {

+ 0 - 1
netclient/functions/mqpublish.go

@@ -115,7 +115,6 @@ func checkin(currentRun int) {
 			config.Write(&nodeCfg, nodeCfg.Network)
 		}
 		Hello(&nodeCfg)
-		checkCertExpiry(&nodeCfg)
 		if currentRun >= 5 && nodeCfg.Server.Is_EE {
 			logger.Log(0, "collecting metrics for node", nodeCfg.Node.Name)
 			publishMetrics(&nodeCfg)

+ 2 - 2
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 30 ; echo '')
 if [ -n "$domain" ]; then
   NETMAKER_BASE_DOMAIN=$domain
 fi
@@ -139,7 +139,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