Browse Source

added roles,acls for clients

Abhishek Kondur 2 years ago
parent
commit
a95468111d

+ 9 - 7
compose/docker-compose.ee.yml

@@ -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: "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

+ 32 - 0
controllers/network.go

@@ -442,6 +442,21 @@ 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.DynSecAction{
+		Payload: 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.Payload.Commands, err.Error()))
+	}
 	logger.Log(1, r.Header.Get("user"), "deleted network", network)
 	w.WriteHeader(http.StatusOK)
 	json.NewEncoder(w).Encode("success")
@@ -488,6 +503,23 @@ 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.DynSecAction{
+		Payload: 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.Payload.Commands, err.Error()))
+	}
 
 	if servercfg.IsClientMode() != "off" {
 		_, err := logic.ServerJoin(&network)

+ 54 - 16
controllers/node.go

@@ -101,23 +101,45 @@ func authenticate(response http.ResponseWriter, request *http.Request) {
 		return
 	}
 	event := mq.DynSecAction{
-		ActionType: mq.CreateClient,
 		Payload: 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.CreateRoleCmd,
+					RoleName: fmt.Sprintf("%s-%s", "Node", result.ID),
+					Acls:     mq.FetchNodeAcls(result.ID),
+					Textname: "Role for node " + result.Name,
+				},
 				{
 					Command:  mq.CreateClientCmd,
 					Username: result.ID,
 					Password: authRequest.Password,
 					Textname: result.Name,
-					Roles:    make([]mq.MqDynSecRole, 0),
-					Groups:   make([]mq.MqDynSecGroup, 0),
+					Roles: []mq.MqDynSecRole{
+						{
+							Rolename: fmt.Sprintf("%s-%s", "Node", result.ID),
+							Priority: -1,
+						},
+						{
+							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 [%s]: %v",
-			event.ActionType, err.Error()))
+		logger.Log(0, fmt.Sprintf("failed to send DynSec command [%v]: %v",
+			event.Payload.Commands, err.Error()))
 		errorResponse.Code = http.StatusInternalServerError
 		errorResponse.Message = fmt.Sprintf("could not create mq client for node [%s]: %v", result.ID, err)
 		return
@@ -641,7 +663,6 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 	}
 	// Delete Any Existing Client with this ID.
 	event := mq.DynSecAction{
-		ActionType: mq.DeleteClient,
 		Payload: mq.MqDynsecPayload{
 			Commands: []mq.MqDynSecCmd{
 				{
@@ -652,28 +673,42 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 		},
 	}
 	if err := mq.PublishEventToDynSecTopic(event); err != nil {
-		logger.Log(0, fmt.Sprintf("failed to send DynSec command [%s]: %v",
-			event.ActionType, err.Error()))
+		logger.Log(0, fmt.Sprintf("failed to send DynSec command [%v]: %v",
+			event.Payload.Commands, err.Error()))
 	}
 	// Create client for this node in Mq
 	event = mq.DynSecAction{
-		ActionType: mq.CreateClient,
 		Payload: mq.MqDynsecPayload{
 			Commands: []mq.MqDynSecCmd{
+				{
+					Command:  mq.CreateRoleCmd,
+					RoleName: fmt.Sprintf("%s-%s", "Node", node.ID),
+					Acls:     mq.FetchNodeAcls(node.ID),
+					Textname: "Role for node " + node.Name,
+				},
 				{
 					Command:  mq.CreateClientCmd,
 					Username: node.ID,
 					Password: nodePassword,
 					Textname: node.Name,
-					Roles:    make([]mq.MqDynSecRole, 0),
-					Groups:   make([]mq.MqDynSecGroup, 0),
+					Roles: []mq.MqDynSecRole{
+						{
+							Rolename: fmt.Sprintf("%s-%s", "Node", node.ID),
+							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 [%s]: %v",
-			event.ActionType, err.Error()))
+		logger.Log(0, fmt.Sprintf("failed to send DynSec command [%v]: %v",
+			event.Payload.Commands, err.Error()))
 	}
 
 	response := models.NodeGet{
@@ -1013,9 +1048,12 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 	}
 
 	event := mq.DynSecAction{
-		ActionType: mq.DeleteClient,
 		Payload: mq.MqDynsecPayload{
 			Commands: []mq.MqDynSecCmd{
+				{
+					Command:  mq.DeleteRoleCmd,
+					RoleName: fmt.Sprintf("%s-%s", "Node", nodeid),
+				},
 				{
 					Command:  mq.DeleteClientCmd,
 					Username: nodeid,
@@ -1024,8 +1062,8 @@ func deleteNode(w http.ResponseWriter, r *http.Request) {
 		},
 	}
 	if err := mq.PublishEventToDynSecTopic(event); err != nil {
-		logger.Log(0, fmt.Sprintf("failed to send DynSec command [%s]: %v",
-			event.ActionType, err.Error()))
+		logger.Log(0, fmt.Sprintf("failed to send DynSec command [%v]: %v",
+			event.Payload.Commands, err.Error()))
 	}
 	logic.ReturnSuccessResponse(w, r, nodeid+" deleted.")
 	logger.Log(1, r.Header.Get("user"), "Deleted node", nodeid, "from network", params["network"])

+ 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

+ 0 - 1
docker/mosquitto.conf

@@ -7,4 +7,3 @@ 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==

+ 119 - 46
mq/dynsec.go

@@ -7,6 +7,7 @@ import (
 	"errors"
 	"fmt"
 	"os"
+	"time"
 
 	mqtt "github.com/eclipse/paho.mqtt.golang"
 	"github.com/gravitl/netmaker/functions"
@@ -39,7 +40,11 @@ var (
 				Password:   "",
 				Salt:       "",
 				Iterations: 0,
-				Roles:      []clientRole{},
+				Roles: []clientRole{
+					{
+						Rolename: "server",
+					},
+				},
 			},
 			{
 				Username:   "netmaker-exporter",
@@ -47,7 +52,11 @@ var (
 				Password:   "yl7HZglF4CvCxgjPLLIYc73LRtjEwp2/SAEQXeW5Ta1Dl4RoLN5/gjqiv8xmue+F9LfRk8KICkNbhSYuEfJ7ww==",
 				Salt:       "veLl9eN02i+hKkyT",
 				Iterations: 101,
-				Roles:      []clientRole{},
+				Roles: []clientRole{
+					{
+						Rolename: "exporter",
+					},
+				},
 			},
 		},
 		Roles: []role{
@@ -55,57 +64,123 @@ var (
 				Rolename: "admin",
 				Acls: []Acl{
 					{
-						AclType: "publishClientSend",
-						Topic:   "$CONTROL/dynamic-security/#",
-						Allow:   true,
+						AclType:  "publishClientSend",
+						Topic:    "$CONTROL/dynamic-security/#",
+						Priority: -1,
+						Allow:    true,
 					},
 					{
-						AclType: "publishClientReceive",
-						Topic:   "$CONTROL/dynamic-security/#",
-						Allow:   true,
+						AclType:  "publishClientReceive",
+						Topic:    "$CONTROL/dynamic-security/#",
+						Priority: -1,
+						Allow:    true,
 					},
 					{
-						AclType: "subscribePattern",
-						Topic:   "$CONTROL/dynamic-security/#",
-						Allow:   true,
+						AclType:  "subscribePattern",
+						Topic:    "$CONTROL/dynamic-security/#",
+						Priority: -1,
+						Allow:    true,
 					},
 					{
-						AclType: "publishClientReceive",
-						Topic:   "$SYS/#",
-						Allow:   true,
+						AclType:  "publishClientReceive",
+						Topic:    "$SYS/#",
+						Priority: -1,
+						Allow:    true,
 					},
 					{
-						AclType: "subscribePattern",
-						Topic:   "$SYS/#",
-						Allow:   true,
+						AclType:  "subscribePattern",
+						Topic:    "$SYS/#",
+						Priority: -1,
+						Allow:    true,
 					},
 					{
-						AclType: "publishClientReceive",
-						Topic:   "#",
-						Allow:   true,
+						AclType:  "publishClientReceive",
+						Topic:    "#",
+						Priority: -1,
+						Allow:    true,
 					},
 					{
-						AclType: "subscribePattern",
-						Topic:   "#",
-						Allow:   true,
+						AclType:  "subscribePattern",
+						Topic:    "#",
+						Priority: -1,
+						Allow:    true,
 					},
 					{
-						AclType: "unsubscribePattern",
-						Topic:   "#",
-						Allow:   true,
+						AclType:  "unsubscribePattern",
+						Topic:    "#",
+						Priority: -1,
+						Allow:    true,
+					},
+					{
+						AclType:  "publishClientSend",
+						Topic:    "#",
+						Priority: -1,
+						Allow:    true,
+					},
+				},
+			},
+			{
+				Rolename: "server",
+				Acls: []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,
 					},
+				},
+			},
+			{
+				Rolename: "exporter",
+				Acls: []Acl{
 					{
-						AclType: "publishClientSend",
-						Topic:   "#",
+						AclType: "publishClientReceive",
+						Topic:   "metrics_exporter",
 						Allow:   true,
 					},
 				},
 			},
 		},
 		DefaultAcl: defaultAccessAcl{
-			PublishClientSend:    true,
+			PublishClientSend:    false,
 			PublishClientReceive: true,
-			Subscribe:            true,
+			Subscribe:            false,
 			Unsubscribe:          true,
 		},
 	}
@@ -114,18 +189,8 @@ var (
 const DynamicSecSubTopic = "$CONTROL/dynamic-security/#"
 const DynamicSecPubTopic = "$CONTROL/dynamic-security/v1"
 
-type DynSecActionType string
-
 var mqAdminClient mqtt.Client
 
-var (
-	CreateClient  DynSecActionType = "CREATE_CLIENT"
-	DisableClient DynSecActionType = "DISABLE_CLIENT"
-	EnableClient  DynSecActionType = "ENABLE_CLIENT"
-	DeleteClient  DynSecActionType = "DELETE_CLIENT"
-	ModifyClient  DynSecActionType = "MODIFY_CLIENT"
-)
-
 var (
 	CreateClientCmd  = "createClient"
 	DisableClientCmd = "disableClient"
@@ -133,6 +198,11 @@ var (
 	ModifyClientCmd  = "modifyClient"
 )
 
+var (
+	CreateRoleCmd = "createRole"
+	DeleteRoleCmd = "deleteRole"
+)
+
 type dynJSON struct {
 	Clients    []client         `json:"clients"`
 	Roles      []role           `json:"roles"`
@@ -205,8 +275,7 @@ type MqDynSecCmd struct {
 }
 
 type DynSecAction struct {
-	ActionType DynSecActionType
-	Payload    MqDynsecPayload
+	Payload MqDynsecPayload
 }
 
 type MqDynsecPayload struct {
@@ -223,7 +292,6 @@ func Configure() error {
 	if password == "" {
 		return errors.New("MQ admin password not provided")
 	}
-	fmt.Println("-----> PASSWORD: ", password)
 	for i, cI := range dynConfig.Clients {
 		if cI.Username == mqAdminUserName || cI.Username == mqNetmakerServerUserName {
 			salt := logic.RandomString(12)
@@ -248,10 +316,15 @@ func PublishEventToDynSecTopic(event DynSecAction) error {
 	if err != nil {
 		return err
 	}
-	if token := mqAdminClient.Publish(DynamicSecPubTopic, 2, false, d); token.Error() != nil {
-		return token.Error()
+	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 nil
+	return connecterr
 }
 
 func watchDynSecTopic(client mqtt.Client, msg mqtt.Message) {

+ 120 - 0
mq/dynsec_helper.go

@@ -0,0 +1,120 @@
+package mq
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"time"
+
+	mqtt "github.com/eclipse/paho.mqtt.golang"
+	"github.com/gravitl/netmaker/servercfg"
+)
+
+type DynListCLientsCmdResp struct {
+	Responses []struct {
+		Command string          `json:"command"`
+		Error   string          `json:"error"`
+		Data    ListClientsData `json:"data"`
+	} `json:"responses"`
+}
+
+type ListClientsData struct {
+	Clients    []string `json:"clients"`
+	TotalCount int      `json:"totalCount"`
+}
+
+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
+}
+
+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")
+}
+
+func FetchNetworkAcls(network string) []Acl {
+	return []Acl{
+		{
+			AclType: "publishClientReceive",
+			Topic:   fmt.Sprintf("update/%s/#", network),
+			Allow:   true,
+		},
+		{
+			AclType: "publishClientReceive",
+			Topic:   fmt.Sprintf("peers/%s/#", network),
+			Allow:   true,
+		},
+	}
+}
+
+func FetchNodeAcls(nodeID string) []Acl {
+	return []Acl{
+
+		{
+			AclType: "publishClientSend",
+			Topic:   fmt.Sprintf("signal/%s", nodeID),
+			Allow:   true,
+		},
+		{
+			AclType: "publishClientSend",
+			Topic:   fmt.Sprintf("update/%s", nodeID),
+			Allow:   true,
+		},
+		{
+			AclType: "publishClientSend",
+			Topic:   fmt.Sprintf("ping/%s", nodeID),
+			Allow:   true,
+		},
+		{
+			AclType: "publishClientSend",
+			Topic:   fmt.Sprintf("metrics/%s", nodeID),
+			Allow:   true,
+		},
+	}
+}