Browse Source

Merge pull request #887 from gravitl/feature_v0.12.0_node_acls

Feature v0.12.0 node acls
dcarns 3 years ago
parent
commit
098af74974

+ 2 - 0
.gitignore

@@ -14,6 +14,8 @@ netclient/netclient-32
 netclient/netclient32
 netclient/netclient32
 netclient/netclient.exe
 netclient/netclient.exe
 config/dnsconfig/
 config/dnsconfig/
+controllers/config/dnsconfig/
+controllers/data/
 data/
 data/
 .vscode/
 .vscode/
 .idea/
 .idea/

+ 1 - 2
controllers/config/dnsconfig/netmaker.hosts

@@ -1,2 +1 @@
-10.0.0.1         testnode.skynet
-10.0.0.2         myhost.skynet
+10.0.0.2         testnode.skynet myhost.skynet

+ 56 - 0
controllers/network.go

@@ -11,6 +11,7 @@ import (
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/logic/acls"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
@@ -34,6 +35,9 @@ func networkHandlers(r *mux.Router) {
 	r.HandleFunc("/api/networks/{networkname}/keys", securityCheck(false, http.HandlerFunc(createAccessKey))).Methods("POST")
 	r.HandleFunc("/api/networks/{networkname}/keys", securityCheck(false, http.HandlerFunc(createAccessKey))).Methods("POST")
 	r.HandleFunc("/api/networks/{networkname}/keys", securityCheck(false, http.HandlerFunc(getAccessKeys))).Methods("GET")
 	r.HandleFunc("/api/networks/{networkname}/keys", securityCheck(false, http.HandlerFunc(getAccessKeys))).Methods("GET")
 	r.HandleFunc("/api/networks/{networkname}/keys/{name}", securityCheck(false, http.HandlerFunc(deleteAccessKey))).Methods("DELETE")
 	r.HandleFunc("/api/networks/{networkname}/keys/{name}", securityCheck(false, http.HandlerFunc(deleteAccessKey))).Methods("DELETE")
+	// ACLs
+	r.HandleFunc("/api/networks/{networkname}/acls", securityCheck(true, http.HandlerFunc(updateNetworkACL))).Methods("PUT")
+	r.HandleFunc("/api/networks/{networkname}/acls", securityCheck(true, http.HandlerFunc(getNetworkACL))).Methods("GET")
 }
 }
 
 
 //simple get all networks function
 //simple get all networks function
@@ -231,6 +235,58 @@ func updateNetworkNodeLimit(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(network)
 	json.NewEncoder(w).Encode(network)
 }
 }
 
 
+func updateNetworkACL(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+	var params = mux.Vars(r)
+	netname := params["networkname"]
+	var networkACLChange acls.ACLContainer
+	networkACLChange, err := networkACLChange.Get(acls.ContainerID(netname))
+	if err != nil {
+		returnErrorResponse(w, r, formatError(err, "internal"))
+		return
+	}
+	_ = json.NewDecoder(r.Body).Decode(&networkACLChange)
+	newNetACL, err := networkACLChange.Save(acls.ContainerID(netname))
+	if err != nil {
+		returnErrorResponse(w, r, formatError(err, "badrequest"))
+		return
+	}
+	logger.Log(1, r.Header.Get("user"), "updated ACLs for network", netname)
+
+	// send peer updates
+	if servercfg.IsMessageQueueBackend() {
+		serverNode, err := logic.GetNetworkServerLocal(netname)
+		if err != nil {
+			logger.Log(1, "failed to find server node after ACL update on", netname)
+		} else {
+			if err = logic.ServerUpdate(&serverNode, false); err != nil {
+				logger.Log(1, "failed to update server node after ACL update on", netname)
+			}
+			if err = mq.PublishPeerUpdate(&serverNode); err != nil {
+				logger.Log(0, "failed to publish peer update after ACL update on", netname)
+			}
+		}
+	}
+
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(newNetACL)
+}
+
+func getNetworkACL(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+	var params = mux.Vars(r)
+	netname := params["networkname"]
+	var networkACL acls.ACLContainer
+	networkACL, err := networkACL.Get(acls.ContainerID(netname))
+	if err != nil {
+		returnErrorResponse(w, r, formatError(err, "internal"))
+		return
+	}
+	logger.Log(2, r.Header.Get("user"), "fetched acl for network", netname)
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(networkACL)
+}
+
 // Delete a network
 // Delete a network
 // Will stop you if  there's any nodes associated
 // Will stop you if  there's any nodes associated
 func deleteNetwork(w http.ResponseWriter, r *http.Request) {
 func deleteNetwork(w http.ResponseWriter, r *http.Request) {

+ 0 - 1
controllers/node.go

@@ -34,7 +34,6 @@ func nodeHandlers(r *mux.Router) {
 	r.HandleFunc("/api/nodes/{network}", createNode).Methods("POST")
 	r.HandleFunc("/api/nodes/{network}", createNode).Methods("POST")
 	r.HandleFunc("/api/nodes/adm/{network}/lastmodified", authorize(true, "network", http.HandlerFunc(getLastModified))).Methods("GET")
 	r.HandleFunc("/api/nodes/adm/{network}/lastmodified", authorize(true, "network", http.HandlerFunc(getLastModified))).Methods("GET")
 	r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods("POST")
 	r.HandleFunc("/api/nodes/adm/{network}/authenticate", authenticate).Methods("POST")
-
 }
 }
 
 
 func authenticate(response http.ResponseWriter, request *http.Request) {
 func authenticate(response http.ResponseWriter, request *http.Request) {

+ 68 - 0
controllers/node_test.go

@@ -5,6 +5,8 @@ import (
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logic"
 	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/logic/acls"
+	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/assert"
 )
 )
@@ -143,6 +145,72 @@ func TestValidateEgressGateway(t *testing.T) {
 	})
 	})
 }
 }
 
 
+func TestNodeACLs(t *testing.T) {
+	deleteAllNodes()
+	node1 := models.Node{PublicKey: "DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=", Name: "testnode", Endpoint: "10.0.0.50", MacAddress: "01:02:03:04:05:06", Password: "password", Network: "skynet", OS: "linux"}
+	node2 := models.Node{PublicKey: "DM5qhLAE20FG7BbfBCger+Ac9D2NDOwCtY1rbYDXf14=", Name: "testnode", Endpoint: "10.0.0.100", MacAddress: "01:02:03:04:05:07", Password: "password", Network: "skynet", OS: "linux"}
+	logic.CreateNode(&node1)
+	logic.CreateNode(&node2)
+	t.Run("acls not present", func(t *testing.T) {
+		currentACL, err := nodeacls.FetchAllACLs(nodeacls.NetworkID(node1.Network))
+		assert.Nil(t, err)
+		assert.NotNil(t, currentACL)
+		node1ACL, err := nodeacls.FetchNodeACL(nodeacls.NetworkID(node1.Network), nodeacls.NodeID(node1.ID))
+		assert.Nil(t, err)
+		assert.NotNil(t, node1ACL)
+		assert.Equal(t, acls.Allowed, node1ACL[acls.AclID(node2.ID)])
+	})
+	t.Run("node acls exists after creates", func(t *testing.T) {
+		node1ACL, err := nodeacls.FetchNodeACL(nodeacls.NetworkID(node1.Network), nodeacls.NodeID(node1.ID))
+		assert.Nil(t, err)
+		assert.NotNil(t, node1ACL)
+		node2ACL, err := nodeacls.FetchNodeACL(nodeacls.NetworkID(node2.Network), nodeacls.NodeID(node2.ID))
+		assert.Nil(t, err)
+		assert.NotNil(t, node2ACL)
+		assert.Equal(t, acls.Allowed, node2ACL[acls.AclID(node1.ID)])
+	})
+	t.Run("node acls correct after fetch", func(t *testing.T) {
+		node1ACL, err := nodeacls.FetchNodeACL(nodeacls.NetworkID(node1.Network), nodeacls.NodeID(node1.ID))
+		assert.Nil(t, err)
+		assert.Equal(t, acls.Allowed, node1ACL[acls.AclID(node2.ID)])
+	})
+	t.Run("node acls correct after modify", func(t *testing.T) {
+		node1ACL, err := nodeacls.FetchNodeACL(nodeacls.NetworkID(node1.Network), nodeacls.NodeID(node1.ID))
+		assert.Nil(t, err)
+		assert.NotNil(t, node1ACL)
+		node2ACL, err := nodeacls.FetchNodeACL(nodeacls.NetworkID(node2.Network), nodeacls.NodeID(node2.ID))
+		assert.Nil(t, err)
+		assert.NotNil(t, node2ACL)
+		currentACL, err := nodeacls.DisallowNodes(nodeacls.NetworkID(node1.Network), nodeacls.NodeID(node1.ID), nodeacls.NodeID(node2.ID))
+		assert.Nil(t, err)
+		assert.Equal(t, acls.NotAllowed, currentACL[acls.AclID(node1.ID)][acls.AclID(node2.ID)])
+		assert.Equal(t, acls.NotAllowed, currentACL[acls.AclID(node2.ID)][acls.AclID(node1.ID)])
+		currentACL.Save(acls.ContainerID(node1.Network))
+	})
+	t.Run("node acls correct after add new node not allowed", func(t *testing.T) {
+		node3 := models.Node{PublicKey: "DM5qhLAE20FG7BbfBCger+Ac9D2NDOwCtY1rbYDXv24=", Name: "testnode3", Endpoint: "10.0.0.100", MacAddress: "01:02:03:04:05:07", Password: "password", Network: "skynet", OS: "linux"}
+		logic.CreateNode(&node3)
+		var currentACL, err = nodeacls.FetchAllACLs(nodeacls.NetworkID(node3.Network))
+		assert.Nil(t, err)
+		assert.NotNil(t, currentACL)
+		assert.Equal(t, acls.NotPresent, currentACL[acls.AclID(node1.ID)][acls.AclID(node3.ID)])
+		nodeACL, err := nodeacls.CreateNodeACL(nodeacls.NetworkID(node3.Network), nodeacls.NodeID(node3.ID), acls.NotAllowed)
+		assert.Nil(t, err)
+		nodeACL.Save(acls.ContainerID(node3.Network), acls.AclID(node3.ID))
+		currentACL, err = nodeacls.FetchAllACLs(nodeacls.NetworkID(node3.Network))
+		assert.Nil(t, err)
+		assert.Equal(t, acls.NotAllowed, currentACL[acls.AclID(node1.ID)][acls.AclID(node3.ID)])
+		assert.Equal(t, acls.NotAllowed, currentACL[acls.AclID(node2.ID)][acls.AclID(node3.ID)])
+	})
+	t.Run("node acls removed", func(t *testing.T) {
+		retNetworkACL, err := nodeacls.RemoveNodeACL(nodeacls.NetworkID(node1.Network), nodeacls.NodeID(node1.ID))
+		assert.Nil(t, err)
+		assert.NotNil(t, retNetworkACL)
+		assert.Equal(t, acls.NotPresent, retNetworkACL[acls.AclID(node2.ID)][acls.AclID(node1.ID)])
+	})
+	deleteAllNodes()
+}
+
 func deleteAllNodes() {
 func deleteAllNodes() {
 	database.DeleteAllRecords(database.NODES_TABLE_NAME)
 	database.DeleteAllRecords(database.NODES_TABLE_NAME)
 }
 }

+ 4 - 0
database/database.go

@@ -50,6 +50,9 @@ const DATABASE_FILENAME = "netmaker.db"
 // GENERATED_TABLE_NAME - stores server generated k/v
 // GENERATED_TABLE_NAME - stores server generated k/v
 const GENERATED_TABLE_NAME = "generated"
 const GENERATED_TABLE_NAME = "generated"
 
 
+// NODE_ACLS_TABLE_NAME - stores the node ACL rules
+const NODE_ACLS_TABLE_NAME = "nodeacls"
+
 // == ERROR CONSTS ==
 // == ERROR CONSTS ==
 
 
 // NO_RECORD - no singular result found
 // NO_RECORD - no singular result found
@@ -127,6 +130,7 @@ func createTables() {
 	createTable(SERVERCONF_TABLE_NAME)
 	createTable(SERVERCONF_TABLE_NAME)
 	createTable(SERVER_UUID_TABLE_NAME)
 	createTable(SERVER_UUID_TABLE_NAME)
 	createTable(GENERATED_TABLE_NAME)
 	createTable(GENERATED_TABLE_NAME)
+	createTable(NODE_ACLS_TABLE_NAME)
 }
 }
 
 
 func createTable(tableName string) error {
 func createTable(tableName string) error {

+ 124 - 0
logic/acls/common.go

@@ -0,0 +1,124 @@
+package acls
+
+import (
+	"encoding/json"
+
+	"github.com/gravitl/netmaker/database"
+)
+
+// == type functions ==
+
+// ACL.Allow - allows access by ID in memory
+func (acl ACL) Allow(ID AclID) {
+	acl[ID] = Allowed
+}
+
+// ACL.DisallowNode - disallows access by ID in memory
+func (acl ACL) Disallow(ID AclID) {
+	acl[ID] = NotAllowed
+}
+
+// ACL.Remove - removes a node from a ACL in memory
+func (acl ACL) Remove(ID AclID) {
+	delete(acl, ID)
+}
+
+// ACL.Update - updates a ACL in DB
+func (acl ACL) Save(containerID ContainerID, ID AclID) (ACL, error) {
+	return upsertACL(containerID, ID, acl)
+}
+
+// ACL.IsAllowed - sees if ID is allowed in referring ACL
+func (acl ACL) IsAllowed(ID AclID) bool {
+	return acl[ID] == Allowed
+}
+
+// ACLContainer.IsAllowed - returns if the current ACL container contains allowed ACLs between two IDs
+func (aclContainer ACLContainer) IsAllowed(ID1, ID2 AclID) bool {
+	return aclContainer[ID1].IsAllowed(ID2) && aclContainer[ID2].IsAllowed(ID1)
+}
+
+// ACLContainer.UpdateACL - saves the state of a ACL in the ACLContainer in memory
+func (aclContainer ACLContainer) UpdateACL(ID AclID, acl ACL) ACLContainer {
+	aclContainer[ID] = acl
+	return aclContainer
+}
+
+// ACLContainer.RemoveACL - removes the state of a ACL in the ACLContainer in memory
+func (aclContainer ACLContainer) RemoveACL(ID AclID) ACLContainer {
+	delete(aclContainer, ID)
+	return aclContainer
+}
+
+// ACLContainer.ChangeAccess - changes the relationship between two nodes in memory
+func (networkACL ACLContainer) ChangeAccess(ID1, ID2 AclID, value byte) {
+	networkACL[ID1][ID2] = value
+	networkACL[ID2][ID1] = value
+}
+
+// ACLContainer.Save - saves the state of a ACLContainer to the db
+func (aclContainer ACLContainer) Save(containerID ContainerID) (ACLContainer, error) {
+	return upsertACLContainer(containerID, aclContainer)
+}
+
+// ACLContainer.New - saves the state of a ACLContainer to the db
+func (aclContainer ACLContainer) New(containerID ContainerID) (ACLContainer, error) {
+	return upsertACLContainer(containerID, nil)
+}
+
+// ACLContainer.Get - saves the state of a ACLContainer to the db
+func (aclContainer ACLContainer) Get(containerID ContainerID) (ACLContainer, error) {
+	return fetchACLContainer(containerID)
+}
+
+// == private ==
+
+// fetchACLContainer - fetches all current rules in given ACL container
+func fetchACLContainer(containerID ContainerID) (ACLContainer, error) {
+	aclJson, err := fetchACLContainerJson(ContainerID(containerID))
+	if err != nil {
+		return nil, err
+	}
+	var currentNetworkACL ACLContainer
+	if err := json.Unmarshal([]byte(aclJson), &currentNetworkACL); err != nil {
+		return nil, err
+	}
+	return currentNetworkACL, nil
+}
+
+// fetchACLContainerJson - fetch the current ACL of given container except in json string
+func fetchACLContainerJson(containerID ContainerID) (ACLJson, error) {
+	currentACLs, err := database.FetchRecord(database.NODE_ACLS_TABLE_NAME, string(containerID))
+	if err != nil {
+		return ACLJson(""), err
+	}
+	return ACLJson(currentACLs), nil
+}
+
+// upsertACL - applies a ACL to the db, overwrites or creates
+func upsertACL(containerID ContainerID, ID AclID, acl ACL) (ACL, error) {
+	currentNetACL, err := fetchACLContainer(containerID)
+	if err != nil {
+		return acl, err
+	}
+	currentNetACL[ID] = acl
+	_, err = upsertACLContainer(containerID, currentNetACL)
+	return acl, err
+}
+
+// upsertACLContainer - Inserts or updates a network ACL given the json string of the ACL and the container ID
+// if nil, create it
+func upsertACLContainer(containerID ContainerID, aclContainer ACLContainer) (ACLContainer, error) {
+	if aclContainer == nil {
+		aclContainer = make(ACLContainer)
+	}
+	return aclContainer, database.Insert(string(containerID), string(convertNetworkACLtoACLJson(aclContainer)), database.NODE_ACLS_TABLE_NAME)
+}
+
+func convertNetworkACLtoACLJson(networkACL ACLContainer) ACLJson {
+	data, err := json.Marshal(networkACL)
+	if err != nil {
+		return ""
+	}
+	return ACLJson(data)
+}

+ 87 - 0
logic/acls/nodeacls/modify.go

@@ -0,0 +1,87 @@
+package nodeacls
+
+import (
+	"github.com/gravitl/netmaker/database"
+	"github.com/gravitl/netmaker/logic/acls"
+)
+
+// CreateNodeACL - inserts or updates a node ACL on given network and adds to state
+func CreateNodeACL(networkID NetworkID, nodeID NodeID, defaultVal byte) (acls.ACL, error) {
+	if defaultVal != acls.NotAllowed && defaultVal != acls.Allowed {
+		defaultVal = acls.NotAllowed
+	}
+	var currentNetworkACL, err = FetchAllACLs(networkID)
+	if err != nil {
+		if database.IsEmptyRecord(err) {
+			currentNetworkACL, err = currentNetworkACL.New(acls.ContainerID(networkID))
+			if err != nil {
+				return nil, err
+			}
+		} else {
+			return nil, err
+		}
+	}
+	var newNodeACL = make(acls.ACL)
+	for existingNodeID := range currentNetworkACL {
+		currentNetworkACL[existingNodeID][acls.AclID(nodeID)] = defaultVal // set the old nodes to default value for new node
+		newNodeACL[existingNodeID] = defaultVal                            // set the old nodes in new node ACL to default value
+	}
+	currentNetworkACL[acls.AclID(nodeID)] = newNodeACL                        // append the new node's ACL
+	retNetworkACL, err := currentNetworkACL.Save(acls.ContainerID(networkID)) // insert into db
+	if err != nil {
+		return nil, err
+	}
+	return retNetworkACL[acls.AclID(nodeID)], nil
+}
+
+// AllowNode - allow access between two nodes in memory
+func AllowNodes(networkID NetworkID, node1, node2 NodeID) (acls.ACLContainer, error) {
+	container, err := FetchAllACLs(networkID)
+	if err != nil {
+		return nil, err
+	}
+	container[acls.AclID(node1)].Allow(acls.AclID(node2))
+	container[acls.AclID(node2)].Allow(acls.AclID(node1))
+	return container, nil
+}
+
+// DisallowNodes - deny access between two nodes
+func DisallowNodes(networkID NetworkID, node1, node2 NodeID) (acls.ACLContainer, error) {
+	container, err := FetchAllACLs(networkID)
+	if err != nil {
+		return nil, err
+	}
+	container[acls.AclID(node1)].Disallow(acls.AclID(node2))
+	container[acls.AclID(node2)].Disallow(acls.AclID(node1))
+	return container, nil
+}
+
+// UpdateNodeACL - updates a node's ACL in state
+func UpdateNodeACL(networkID NetworkID, nodeID NodeID, acl acls.ACL) (acls.ACL, error) {
+	var currentNetworkACL, err = FetchAllACLs(networkID)
+	if err != nil {
+		return nil, err
+	}
+	currentNetworkACL[acls.AclID(nodeID)] = acl
+	return currentNetworkACL[acls.AclID(nodeID)].Save(acls.ContainerID(networkID), acls.AclID(nodeID))
+}
+
+// RemoveNodeACL - removes a specific Node's ACL, returns the NetworkACL and error
+func RemoveNodeACL(networkID NetworkID, nodeID NodeID) (acls.ACLContainer, error) {
+	var currentNetworkACL, err = FetchAllACLs(networkID)
+	if err != nil {
+		return nil, err
+	}
+	for currentNodeID := range currentNetworkACL {
+		if NodeID(currentNodeID) != nodeID {
+			currentNetworkACL[currentNodeID].Remove(acls.AclID(nodeID))
+		}
+	}
+	delete(currentNetworkACL, acls.AclID(nodeID))
+	return currentNetworkACL.Save(acls.ContainerID(networkID))
+}
+
+// DeleteACLContainer - removes an ACLContainer state from db
+func DeleteACLContainer(network NetworkID) error {
+	return database.DeleteRecord(database.NODE_ACLS_TABLE_NAME, string(network))
+}

+ 53 - 0
logic/acls/nodeacls/retrieve.go

@@ -0,0 +1,53 @@
+package nodeacls
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/gravitl/netmaker/logic/acls"
+)
+
+// AreNodesAllowed - checks if nodes are allowed to communicate in their network ACL
+func AreNodesAllowed(networkID NetworkID, node1, node2 NodeID) bool {
+	var currentNetworkACL, err = FetchAllACLs(networkID)
+	if err != nil {
+		return false
+	}
+	return currentNetworkACL[acls.AclID(node1)].IsAllowed(acls.AclID(node2)) && currentNetworkACL[acls.AclID(node2)].IsAllowed(acls.AclID(node1))
+}
+
+// FetchNodeACL - fetches a specific node's ACL in a given network
+func FetchNodeACL(networkID NetworkID, nodeID NodeID) (acls.ACL, error) {
+	var currentNetworkACL, err = FetchAllACLs(networkID)
+	if err != nil {
+		return nil, err
+	}
+	if currentNetworkACL[acls.AclID(nodeID)] == nil {
+		return nil, fmt.Errorf("no node ACL present for node %s", nodeID)
+	}
+	return currentNetworkACL[acls.AclID(nodeID)], nil
+}
+
+// FetchNodeACLJson - fetches a node's acl in given network except returns the json string
+func FetchNodeACLJson(networkID NetworkID, nodeID NodeID) (acls.ACLJson, error) {
+	currentNodeACL, err := FetchNodeACL(networkID, nodeID)
+	if err != nil {
+		return "", err
+	}
+	jsonData, err := json.Marshal(&currentNodeACL)
+	if err != nil {
+		return "", err
+	}
+	return acls.ACLJson(jsonData), nil
+}
+
+// FetchAllACLs - fetchs all node
+func FetchAllACLs(networkID NetworkID) (acls.ACLContainer, error) {
+	var err error
+	var currentNetworkACL acls.ACLContainer
+	currentNetworkACL, err = currentNetworkACL.Get(acls.ContainerID(networkID))
+	if err != nil {
+		return nil, err
+	}
+	return currentNetworkACL, nil
+}

+ 14 - 0
logic/acls/nodeacls/types.go

@@ -0,0 +1,14 @@
+package nodeacls
+
+import (
+	"github.com/gravitl/netmaker/logic/acls"
+)
+
+type (
+	// NodeACL - interface for NodeACLs
+	NodeACL acls.ACL
+	// NodeID - node ID for ACLs
+	NodeID acls.AclID
+	// NetworkID - ACL container based on network ID for nodes
+	NetworkID acls.ContainerID
+)

+ 27 - 0
logic/acls/types.go

@@ -0,0 +1,27 @@
+package acls
+
+var (
+	// NotPresent - 0 - not present (default)
+	NotPresent = byte(0)
+	// NotAllowed - 1 - not allowed access
+	NotAllowed = byte(1) // 1 - not allowed
+	// Allowed - 2 - allowed access
+	Allowed = byte(2)
+)
+
+type (
+	// AclID - the node id of a given node
+	AclID string
+
+	// ACL - the ACL of other nodes in a NetworkACL for a single unique node
+	ACL map[AclID]byte
+
+	// ACLJson - the string representation in JSON of an ACL Node or Network
+	ACLJson string
+
+	// ContainerID - the networkID of a given network
+	ContainerID string
+
+	// ACLContainer - the total list of all node's ACL in a given network
+	ACLContainer map[AclID]ACL
+)

+ 6 - 0
logic/networks.go

@@ -12,6 +12,7 @@ import (
 	"github.com/go-playground/validator/v10"
 	"github.com/go-playground/validator/v10"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/validation"
 	"github.com/gravitl/netmaker/validation"
@@ -41,6 +42,11 @@ func GetNetworks() ([]models.Network, error) {
 
 
 // DeleteNetwork - deletes a network
 // DeleteNetwork - deletes a network
 func DeleteNetwork(network string) error {
 func DeleteNetwork(network string) error {
+	// remove ACL for network
+	err := nodeacls.DeleteACLContainer(nodeacls.NetworkID(network))
+	if err != nil {
+		logger.Log(1, "failed to remove the node acls during network delete for network,", network)
+	}
 	nodeCount, err := GetNetworkNonServerNodeCount(network)
 	nodeCount, err := GetNetworkNonServerNodeCount(network)
 	if nodeCount == 0 || database.IsEmptyRecord(err) {
 	if nodeCount == 0 || database.IsEmptyRecord(err) {
 		// delete server nodes first then db records
 		// delete server nodes first then db records

+ 44 - 0
logic/nodes.go

@@ -12,6 +12,8 @@ import (
 	"github.com/google/uuid"
 	"github.com/google/uuid"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/logic/acls"
+	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
@@ -174,6 +176,41 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error {
 	return fmt.Errorf("failed to update node " + currentNode.ID + ", cannot change ID.")
 	return fmt.Errorf("failed to update node " + currentNode.ID + ", cannot change ID.")
 }
 }
 
 
+// DeleteNodeByID - deletes a node from database or moves into delete nodes table
+func DeleteNodeByID(node *models.Node, exterminate bool) error {
+	var err error
+	var key = node.ID
+	if !exterminate {
+		node.Action = models.NODE_DELETE
+		nodedata, err := json.Marshal(&node)
+		if err != nil {
+			return err
+		}
+		err = database.Insert(key, string(nodedata), database.DELETED_NODES_TABLE_NAME)
+		if err != nil {
+			return err
+		}
+	} else {
+		if err := database.DeleteRecord(database.DELETED_NODES_TABLE_NAME, key); err != nil {
+			logger.Log(2, err.Error())
+		}
+	}
+	if err = database.DeleteRecord(database.NODES_TABLE_NAME, key); err != nil {
+		return err
+	}
+	if servercfg.IsDNSMode() {
+		SetDNS()
+	}
+
+	_, err = nodeacls.RemoveNodeACL(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID))
+	if err != nil {
+		// ignoring for now, could hit a nil pointer if delete called twice
+		logger.Log(2, "attempted to remove node ACL for node", node.Name, node.ID)
+	}
+
+	return removeLocalServer(node)
+}
+
 // IsNodeIDUnique - checks if node id is unique
 // IsNodeIDUnique - checks if node id is unique
 func IsNodeIDUnique(node *models.Node) (bool, error) {
 func IsNodeIDUnique(node *models.Node) (bool, error) {
 	_, err := database.FetchRecord(database.NODES_TABLE_NAME, node.ID)
 	_, err := database.FetchRecord(database.NODES_TABLE_NAME, node.ID)
@@ -274,6 +311,13 @@ func CreateNode(node *models.Node) error {
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
+	// TODO get template logic to decide initial ACL value
+	_, err = nodeacls.CreateNodeACL(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID), acls.Allowed)
+	if err != nil {
+		logger.Log(1, "failed to create node ACL for node,", node.ID, "err:", err.Error())
+		return err
+	}
+
 	if node.IsPending != "yes" {
 	if node.IsPending != "yes" {
 		DecrimentKey(node.Network, node.AccessKey)
 		DecrimentKey(node.Network, node.AccessKey)
 	}
 	}

+ 11 - 4
logic/peers.go

@@ -10,6 +10,8 @@ import (
 
 
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/database"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/logic/acls"
+	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
 	"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
@@ -33,7 +35,7 @@ func GetHubPeer(networkName string) []models.Node {
 */
 */
 
 
 // GetNodePeers - fetches peers for a given node
 // GetNodePeers - fetches peers for a given node
-func GetNodePeers(networkName string, excludeRelayed bool, isP2S bool) ([]models.Node, error) {
+func GetNodePeers(networkName, nodeid string, excludeRelayed bool, isP2S bool) ([]models.Node, error) {
 	var peers []models.Node
 	var peers []models.Node
 	var networkNodes, egressNetworkNodes, err = getNetworkEgressAndNodes(networkName)
 	var networkNodes, egressNetworkNodes, err = getNetworkEgressAndNodes(networkName)
 	if err != nil {
 	if err != nil {
@@ -45,6 +47,11 @@ func GetNodePeers(networkName string, excludeRelayed bool, isP2S bool) ([]models
 		logger.Log(2, errN.Error())
 		logger.Log(2, errN.Error())
 	}
 	}
 
 
+	currentNetworkACLs, aclErr := nodeacls.FetchAllACLs(nodeacls.NetworkID(networkName))
+	if aclErr != nil {
+		return peers, aclErr
+	}
+
 	for _, node := range networkNodes {
 	for _, node := range networkNodes {
 		var peer = models.Node{}
 		var peer = models.Node{}
 		if node.IsEgressGateway == "yes" { // handle egress stuff
 		if node.IsEgressGateway == "yes" { // handle egress stuff
@@ -79,7 +86,7 @@ func GetNodePeers(networkName string, excludeRelayed bool, isP2S bool) ([]models
 					}
 					}
 				}
 				}
 			}
 			}
-			if !isP2S || peer.IsHub == "yes" {
+			if (!isP2S || peer.IsHub == "yes") && currentNetworkACLs.IsAllowed(acls.AclID(nodeid), acls.AclID(node.ID)) {
 				peers = append(peers, peer)
 				peers = append(peers, peer)
 			}
 			}
 		}
 		}
@@ -107,7 +114,7 @@ func GetPeersList(refnode *models.Node) ([]models.Node, error) {
 		isP2S = true
 		isP2S = true
 	}
 	}
 	if relayedNodeAddr == "" {
 	if relayedNodeAddr == "" {
-		peers, err = GetNodePeers(networkName, excludeRelayed, isP2S)
+		peers, err = GetNodePeers(networkName, refnode.ID, excludeRelayed, isP2S)
 	} else {
 	} else {
 		var relayNode models.Node
 		var relayNode models.Node
 		relayNode, err = GetNodeRelay(networkName, relayedNodeAddr)
 		relayNode, err = GetNodeRelay(networkName, relayedNodeAddr)
@@ -127,7 +134,7 @@ func GetPeersList(refnode *models.Node) ([]models.Node, error) {
 			} else {
 			} else {
 				peerNode.AllowedIPs = append(peerNode.AllowedIPs, peerNode.RelayAddrs...)
 				peerNode.AllowedIPs = append(peerNode.AllowedIPs, peerNode.RelayAddrs...)
 			}
 			}
-			nodepeers, err := GetNodePeers(networkName, false, isP2S)
+			nodepeers, err := GetNodePeers(networkName, refnode.ID, false, isP2S)
 			if err == nil && peerNode.UDPHolePunch == "yes" {
 			if err == nil && peerNode.UDPHolePunch == "yes" {
 				for _, nodepeer := range nodepeers {
 				for _, nodepeer := range nodepeers {
 					if nodepeer.Address == peerNode.Address {
 					if nodepeer.Address == peerNode.Address {

+ 11 - 0
logic/server.go

@@ -11,6 +11,8 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
+	"github.com/gravitl/netmaker/logic/acls"
+	"github.com/gravitl/netmaker/logic/acls/nodeacls"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/servercfg"
 	"github.com/gravitl/netmaker/servercfg"
@@ -208,6 +210,11 @@ func GetServerPeers(serverNode *models.Node) ([]wgtypes.PeerConfig, bool, []stri
 		return nil, hasGateway, gateways, err
 		return nil, hasGateway, gateways, err
 	}
 	}
 
 
+	currentNetworkACL, err := nodeacls.FetchAllACLs(nodeacls.NetworkID(serverNode.Network))
+	if err != nil {
+		logger.Log(1, "could not fetch current ACL list, proceeding with all peers")
+	}
+
 	for _, node := range nodes {
 	for _, node := range nodes {
 		pubkey, err := wgtypes.ParseKey(node.PublicKey)
 		pubkey, err := wgtypes.ParseKey(node.PublicKey)
 		if err != nil {
 		if err != nil {
@@ -225,6 +232,9 @@ func GetServerPeers(serverNode *models.Node) ([]wgtypes.PeerConfig, bool, []stri
 				continue
 				continue
 			}
 			}
 		}
 		}
+		if currentNetworkACL != nil && currentNetworkACL.IsAllowed(acls.AclID(serverNode.ID), acls.AclID(node.ID)) {
+			continue
+		}
 
 
 		var peer wgtypes.PeerConfig
 		var peer wgtypes.PeerConfig
 		var peeraddr = net.IPNet{
 		var peeraddr = net.IPNet{
@@ -290,6 +300,7 @@ func GetServerPeers(serverNode *models.Node) ([]wgtypes.PeerConfig, bool, []stri
 			ReplaceAllowedIPs:           true,
 			ReplaceAllowedIPs:           true,
 			AllowedIPs:                  allowedips,
 			AllowedIPs:                  allowedips,
 		}
 		}
+
 		peers = append(peers, peer)
 		peers = append(peers, peer)
 	}
 	}
 	if serverNode.IsIngressGateway == "yes" {
 	if serverNode.IsIngressGateway == "yes" {

+ 0 - 29
logic/util.go

@@ -17,7 +17,6 @@ import (
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/models"
 	"github.com/gravitl/netmaker/netclient/ncutils"
 	"github.com/gravitl/netmaker/netclient/ncutils"
-	"github.com/gravitl/netmaker/servercfg"
 )
 )
 
 
 // IsBase64 - checks if a string is in base64 format
 // IsBase64 - checks if a string is in base64 format
@@ -101,34 +100,6 @@ func GenerateCryptoString(n int) (string, error) {
 	return string(ret), nil
 	return string(ret), nil
 }
 }
 
 
-// DeleteNodeByID - deletes a node from database or moves into delete nodes table
-func DeleteNodeByID(node *models.Node, exterminate bool) error {
-	var err error
-	var key = node.ID
-	if !exterminate {
-		node.Action = models.NODE_DELETE
-		nodedata, err := json.Marshal(&node)
-		if err != nil {
-			return err
-		}
-		err = database.Insert(key, string(nodedata), database.DELETED_NODES_TABLE_NAME)
-		if err != nil {
-			return err
-		}
-	} else {
-		if err := database.DeleteRecord(database.DELETED_NODES_TABLE_NAME, key); err != nil {
-			logger.Log(2, err.Error())
-		}
-	}
-	if err = database.DeleteRecord(database.NODES_TABLE_NAME, key); err != nil {
-		return err
-	}
-	if servercfg.IsDNSMode() {
-		SetDNS()
-	}
-	return removeLocalServer(node)
-}
-
 // RandomString - returns a random string in a charset
 // RandomString - returns a random string in a charset
 func RandomString(length int) string {
 func RandomString(length int) string {
 	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
 	const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

+ 1 - 1
netclient/functions/mqhandlers.go

@@ -196,11 +196,11 @@ func UpdatePeers(client mqtt.Client, msg mqtt.Message) {
 		ncutils.Log("error syncing wg after peer update: " + err.Error())
 		ncutils.Log("error syncing wg after peer update: " + err.Error())
 		return
 		return
 	}
 	}
+	ncutils.Log("received peer update for node " + cfg.Node.Name + " " + cfg.Node.Network)
 	//skip dns updates if this is a peer update for comms network
 	//skip dns updates if this is a peer update for comms network
 	if cfg.Node.NetworkSettings.IsComms == "yes" {
 	if cfg.Node.NetworkSettings.IsComms == "yes" {
 		return
 		return
 	}
 	}
-	ncutils.Log("DNS updating /etc/hosts")
 	if cfg.Node.DNSOn == "yes" {
 	if cfg.Node.DNSOn == "yes" {
 		if err := setHostDNS(peerUpdate.DNS, ncutils.IsWindows()); err != nil {
 		if err := setHostDNS(peerUpdate.DNS, ncutils.IsWindows()); err != nil {
 			ncutils.Log("error updating /etc/hosts " + err.Error())
 			ncutils.Log("error updating /etc/hosts " + err.Error())