From 3d765f9cf1375845794ea8b8059200b6cccf466d Mon Sep 17 00:00:00 2001 From: Abhishek K Date: Tue, 18 Mar 2025 13:25:55 +0400 Subject: [PATCH] NET-1910: Acl controls for Egress Traffic (#3377) * add support for egress ranges on acl policy * add egress ranges to acl rules * add egress ranges to acl policies * Add egress ranges to acl rules * add egress ranges to fw update * fetch acl rules for egress networks * apply egress policies for devices * configure user policies for egresss routes * fix gw tag name migration * fix egress acl rules for static nodes * add egress ranges for static nodes on ingress gw * fileter acl IPs to be unique * cleanup IOT logic from peer update * make acl Rule Dst List * cleanup egress ranges from acl policies * create user group default acl policy for gateways * remove remote access name ids * rm egress ranges removal from acl policies * simplify user permissions on nodes * add additional nameservers to extclient dns * remove debug logs * fix static checks --- Dockerfile | 2 +- controllers/acls.go | 1 + controllers/ext_client.go | 13 +- controllers/hosts.go | 58 +--- controllers/network.go | 28 ++ logic/acls.go | 600 ++++++++++++++++++++++++++++++-------- logic/extpeers.go | 238 +++++++-------- logic/nodes.go | 6 + logic/peers.go | 73 +---- logic/tags.go | 4 +- migrate/migrate.go | 4 +- models/acl.go | 2 + models/extclient.go | 1 + models/mqtt.go | 2 +- pro/auth/error.go | 2 +- pro/controllers/users.go | 40 +++ pro/email/invite.go | 5 +- pro/logic/user_mgmt.go | 94 +----- 18 files changed, 737 insertions(+), 436 deletions(-) diff --git a/Dockerfile b/Dockerfile index 179d9a6c..552b0455 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ ARG tags WORKDIR /app COPY . . -RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -w " -tags ${tags} . +RUN GOOS=linux CGO_ENABLED=1 go build -race -ldflags="-s -w " -tags ${tags} . # RUN go build -tags=ee . -o netmaker main.go FROM alpine:3.21.2 diff --git a/controllers/acls.go b/controllers/acls.go index b834164a..9f958605 100644 --- a/controllers/acls.go +++ b/controllers/acls.go @@ -51,6 +51,7 @@ func aclPolicyTypes(w http.ResponseWriter, r *http.Request) { DstGroupTypes: []models.AclGroupType{ models.NodeTagID, models.NodeID, + models.EgressRange, // models.NetmakerIPAclID, // models.NetmakerSubNetRangeAClID, }, diff --git a/controllers/ext_client.go b/controllers/ext_client.go index 789892bd..f182d957 100644 --- a/controllers/ext_client.go +++ b/controllers/ext_client.go @@ -475,7 +475,18 @@ func getExtClientHAConf(w http.ResponseWriter, r *http.Request) { // models.RemoteAccessTagName))] = struct{}{} // set extclient dns to ingressdns if extclient dns is not explicitly set if (extclient.DNS == "") && (gwnode.IngressDNS != "") { - extclient.DNS = gwnode.IngressDNS + network, _ := logic.GetNetwork(gwnode.Network) + dns := gwnode.IngressDNS + if len(network.NameServers) > 0 { + if dns == "" { + dns = strings.Join(network.NameServers, ",") + } else { + dns += "," + strings.Join(network.NameServers, ",") + } + + } + extclient.DNS = dns + } listenPort := logic.GetPeerListenPort(host) diff --git a/controllers/hosts.go b/controllers/hosts.go index a9cacd23..e389a364 100644 --- a/controllers/hosts.go +++ b/controllers/hosts.go @@ -140,60 +140,12 @@ func upgradeHost(w http.ResponseWriter, r *http.Request) { // @Failure 500 {object} models.ErrorResponse func getHosts(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - currentHosts := []models.Host{} - var err error - if r.Header.Get("ismaster") == "yes" { - currentHosts, err = logic.GetAllHosts() - if err != nil { - logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - } else { - username := r.Header.Get("user") - user, err := logic.GetUser(username) - if err != nil { - return - } - userPlatformRole, err := logic.GetRole(user.PlatformRoleID) - if err != nil { - return - } - respHostsMap := make(map[string]struct{}) - if !userPlatformRole.FullAccess { - nodes, err := logic.GetAllNodes() - if err != nil { - logger.Log(0, "error fetching all nodes info: ", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - filteredNodes := logic.GetFilteredNodesByUserAccess(*user, nodes) - if len(filteredNodes) > 0 { - currentHostsMap, err := logic.GetHostsMap() - if err != nil { - logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - for _, node := range filteredNodes { - if _, ok := respHostsMap[node.HostID.String()]; ok { - continue - } - if host, ok := currentHostsMap[node.HostID.String()]; ok { - currentHosts = append(currentHosts, host) - respHostsMap[host.ID.String()] = struct{}{} - } - } - } - } else { - currentHosts, err = logic.GetAllHosts() - if err != nil { - logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error()) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return - } - } + currentHosts, err := logic.GetAllHosts() + if err != nil { + logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return } apiHosts := logic.GetAllHostsAPI(currentHosts[:]) diff --git a/controllers/network.go b/controllers/network.go index 695d5b72..5f3dfd29 100644 --- a/controllers/network.go +++ b/controllers/network.go @@ -41,6 +41,7 @@ func networkHandlers(r *mux.Router) { Methods(http.MethodPut) r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(getNetworkACL))). Methods(http.MethodGet) + r.HandleFunc("/api/networks/{networkname}/egress_routes", logic.SecurityCheck(true, http.HandlerFunc(getNetworkEgressRoutes))) } // @Summary Lists all networks @@ -429,6 +430,33 @@ func getNetworkACL(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(networkACL) } +// @Summary Get a network Egress routes +// @Router /api/networks/{networkname}/egress_routes [get] +// @Tags Networks +// @Security oauth +// @Param networkname path string true "Network name" +// @Produce json +// @Success 200 {object} acls.SuccessResponse +// @Failure 500 {object} models.ErrorResponse +func getNetworkEgressRoutes(w http.ResponseWriter, r *http.Request) { + var params = mux.Vars(r) + netname := params["networkname"] + // check if network exists + _, err := logic.GetNetwork(netname) + if err != nil { + logger.Log(0, r.Header.Get("user"), + fmt.Sprintf("failed to fetch ACLs for network [%s]: %v", netname, err)) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + nodeEgressRoutes, _, err := logic.GetEgressRanges(models.NetworkID(netname)) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + logic.ReturnSuccessResponseWithJson(w, r, nodeEgressRoutes, "fetched network egress routes") +} + // @Summary Delete a network // @Router /api/networks/{networkname} [delete] // @Tags Networks diff --git a/logic/acls.go b/logic/acls.go index c1167627..8ccee241 100644 --- a/logic/acls.go +++ b/logic/acls.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "maps" + "net" "sort" "sync" "time" @@ -222,6 +223,104 @@ func IsAclExists(aclID string) bool { _, err := GetAcl(aclID) return err == nil } +func GetEgressRanges(netID models.NetworkID) (map[string][]string, map[string]struct{}, error) { + + resultMap := make(map[string]struct{}) + nodeEgressMap := make(map[string][]string) + networkNodes, err := GetNetworkNodes(netID.String()) + if err != nil { + return nil, nil, err + } + for _, currentNode := range networkNodes { + if currentNode.Network != netID.String() { + continue + } + if currentNode.IsEgressGateway { // add the egress gateway range(s) to the result + if len(currentNode.EgressGatewayRanges) > 0 { + nodeEgressMap[currentNode.ID.String()] = currentNode.EgressGatewayRanges + for _, egressRangeI := range currentNode.EgressGatewayRanges { + resultMap[egressRangeI] = struct{}{} + } + } + } + } + extclients, _ := GetNetworkExtClients(netID.String()) + for _, extclient := range extclients { + if len(extclient.ExtraAllowedIPs) > 0 { + nodeEgressMap[extclient.ClientID] = extclient.ExtraAllowedIPs + for _, extraAllowedIP := range extclient.ExtraAllowedIPs { + resultMap[extraAllowedIP] = struct{}{} + } + } + } + return nodeEgressMap, resultMap, nil +} + +func checkIfAclTagisValid(t models.AclPolicyTag, netID models.NetworkID, policyType models.AclPolicyType, isSrc bool) bool { + switch t.ID { + case models.NodeTagID: + if policyType == models.UserPolicy && isSrc { + return false + } + // check if tag is valid + _, err := GetTag(models.TagID(t.Value)) + if err != nil { + return false + } + case models.NodeID: + if policyType == models.UserPolicy && isSrc { + return false + } + _, nodeErr := GetNodeByID(t.Value) + if nodeErr != nil { + _, staticNodeErr := GetExtClient(t.Value, netID.String()) + if staticNodeErr != nil { + return false + } + } + case models.EgressRange: + if isSrc { + return false + } + // _, rangesMap, err := GetEgressRanges(netID) + // if err != nil { + // return false + // } + // if _, ok := rangesMap[t.Value]; !ok { + // return false + // } + case models.UserAclID: + if policyType == models.DevicePolicy { + return false + } + if !isSrc { + return false + } + _, err := GetUser(t.Value) + if err != nil { + return false + } + case models.UserGroupAclID: + if policyType == models.DevicePolicy { + return false + } + if !isSrc { + return false + } + err := IsGroupValid(models.UserGroupID(t.Value)) + if err != nil { + return false + } + // check if group belongs to this network + netGrps := GetUserGroupsInNetwork(netID) + if _, ok := netGrps[models.UserGroupID(t.Value)]; !ok { + return false + } + default: + return false + } + return true +} // IsAclPolicyValid - validates if acl policy is valid func IsAclPolicyValid(acl models.Acl) bool { @@ -235,115 +334,43 @@ func IsAclPolicyValid(acl models.Acl) bool { // src list should only contain users for _, srcI := range acl.Src { - if srcI.ID == "" || srcI.Value == "" { - return false - } if srcI.Value == "*" { continue } - if srcI.ID != models.UserAclID && srcI.ID != models.UserGroupAclID { + // check if user group is valid + if !checkIfAclTagisValid(srcI, acl.NetworkID, acl.RuleType, true) { return false } - // check if user group is valid - if srcI.ID == models.UserAclID { - _, err := GetUser(srcI.Value) - if err != nil { - return false - } - - } else if srcI.ID == models.UserGroupAclID { - err := IsGroupValid(models.UserGroupID(srcI.Value)) - if err != nil { - return false - } - // check if group belongs to this network - netGrps := GetUserGroupsInNetwork(acl.NetworkID) - if _, ok := netGrps[models.UserGroupID(srcI.Value)]; !ok { - return false - } - } - } for _, dstI := range acl.Dst { - if dstI.ID == "" || dstI.Value == "" { - return false - } - if dstI.ID != models.NodeTagID && dstI.ID != models.NodeID { - return false - } if dstI.Value == "*" { continue } - if dstI.ID == models.NodeTagID { - // check if tag is valid - _, err := GetTag(models.TagID(dstI.Value)) - if err != nil { - return false - } - } else { - _, nodeErr := GetNodeByID(dstI.Value) - if nodeErr != nil { - _, staticNodeErr := GetExtClient(dstI.Value, acl.NetworkID.String()) - if staticNodeErr != nil { - return false - } - } + + // check if user group is valid + if !checkIfAclTagisValid(dstI, acl.NetworkID, acl.RuleType, false) { + return false } } case models.DevicePolicy: for _, srcI := range acl.Src { - if srcI.ID == "" || srcI.Value == "" { - return false - } - if srcI.ID != models.NodeTagID && srcI.ID != models.NodeID { - return false - } if srcI.Value == "*" { continue } - if srcI.ID == models.NodeTagID { - // check if tag is valid - _, err := GetTag(models.TagID(srcI.Value)) - if err != nil { - return false - } - } else { - _, nodeErr := GetNodeByID(srcI.Value) - if nodeErr != nil { - _, staticNodeErr := GetExtClient(srcI.Value, acl.NetworkID.String()) - if staticNodeErr != nil { - return false - } - } + // check if user group is valid + if !checkIfAclTagisValid(srcI, acl.NetworkID, acl.RuleType, true) { + return false } - } for _, dstI := range acl.Dst { - if dstI.ID == "" || dstI.Value == "" { - return false - } - if dstI.ID != models.NodeTagID && dstI.ID != models.NodeID { - return false - } if dstI.Value == "*" { continue } - if dstI.ID == models.NodeTagID { - // check if tag is valid - _, err := GetTag(models.TagID(dstI.Value)) - if err != nil { - return false - } - } else { - _, nodeErr := GetNodeByID(dstI.Value) - if nodeErr != nil { - _, staticNodeErr := GetExtClient(dstI.Value, acl.NetworkID.String()) - if staticNodeErr != nil { - return false - } - } + // check if user group is valid + if !checkIfAclTagisValid(dstI, acl.NetworkID, acl.RuleType, false) { + return false } } } @@ -688,8 +715,8 @@ func RemoveUserFromAclPolicy(userName string) { delete := false update := false if acl.RuleType == models.UserPolicy { - for i, srcI := range acl.Src { - if srcI.ID == models.UserAclID && srcI.Value == userName { + for i := len(acl.Src) - 1; i >= 0; i-- { + if acl.Src[i].ID == models.UserAclID && acl.Src[i].Value == userName { if len(acl.Src) == 1 { // delete policy delete = true @@ -723,8 +750,8 @@ func RemoveNodeFromAclPolicy(node models.Node) { delete := false update := false if acl.RuleType == models.DevicePolicy { - for i, srcI := range acl.Src { - if srcI.ID == models.NodeID && srcI.Value == nodeID { + for i := len(acl.Src) - 1; i >= 0; i-- { + if acl.Src[i].ID == models.NodeID && acl.Src[i].Value == nodeID { if len(acl.Src) == 1 { // delete policy delete = true @@ -739,8 +766,8 @@ func RemoveNodeFromAclPolicy(node models.Node) { DeleteAcl(acl) continue } - for i, dstI := range acl.Dst { - if dstI.ID == models.NodeID && dstI.Value == nodeID { + for i := len(acl.Dst) - 1; i >= 0; i-- { + if acl.Dst[i].ID == models.NodeID && acl.Dst[i].Value == nodeID { if len(acl.Dst) == 1 { // delete policy delete = true @@ -761,8 +788,8 @@ func RemoveNodeFromAclPolicy(node models.Node) { } if acl.RuleType == models.UserPolicy { - for i, dstI := range acl.Dst { - if dstI.ID == models.NodeID && dstI.Value == nodeID { + for i := len(acl.Dst) - 1; i >= 0; i-- { + if acl.Dst[i].ID == models.NodeID && acl.Dst[i].Value == nodeID { if len(acl.Dst) == 1 { // delete policy delete = true @@ -1239,17 +1266,17 @@ func RemoveDeviceTagFromAclPolicies(tagID models.TagID, netID models.NetworkID) acls := listDevicePolicies(netID) update := false for _, acl := range acls { - for i, srcTagI := range acl.Src { - if srcTagI.ID == models.NodeTagID { - if tagID.String() == srcTagI.Value { + for i := len(acl.Src) - 1; i >= 0; i-- { + if acl.Src[i].ID == models.NodeTagID { + if tagID.String() == acl.Src[i].Value { acl.Src = append(acl.Src[:i], acl.Src[i+1:]...) update = true } } } - for i, dstTagI := range acl.Dst { - if dstTagI.ID == models.NodeTagID { - if tagID.String() == dstTagI.Value { + for i := len(acl.Dst) - 1; i >= 0; i-- { + if acl.Dst[i].ID == models.NodeTagID { + if tagID.String() == acl.Dst[i].Value { acl.Dst = append(acl.Dst[:i], acl.Dst[i+1:]...) update = true } @@ -1262,19 +1289,16 @@ func RemoveDeviceTagFromAclPolicies(tagID models.TagID, netID models.NetworkID) return nil } -func getUserAclRulesForNode(targetnode *models.Node, +func getEgressUserRulesForNode(targetnode *models.Node, rules map[string]models.AclRule) map[string]models.AclRule { userNodes := GetStaticUserNodesByNetwork(models.NetworkID(targetnode.Network)) userGrpMap := GetUserGrpMap() allowedUsers := make(map[string][]models.Acl) acls := listUserPolicies(models.NetworkID(targetnode.Network)) var targetNodeTags = make(map[models.TagID]struct{}) - if targetnode.Mutex != nil { - targetnode.Mutex.Lock() - targetNodeTags = maps.Clone(targetnode.Tags) - targetnode.Mutex.Unlock() - } else { - targetNodeTags = maps.Clone(targetnode.Tags) + targetNodeTags["*"] = struct{}{} + for _, rangeI := range targetnode.EgressGatewayRanges { + targetNodeTags[models.TagID(rangeI)] = struct{}{} } for _, acl := range acls { if !acl.Enabled { @@ -1285,10 +1309,9 @@ func getUserAclRulesForNode(targetnode *models.Node, addUsers := false if !all { for nodeTag := range targetNodeTags { - if _, ok := dstTags[nodeTag.String()]; !ok { - if _, ok = dstTags[targetnode.ID.String()]; !ok { - break - } + if _, ok := dstTags[nodeTag.String()]; ok { + addUsers = true + break } } } else { @@ -1326,7 +1349,110 @@ func getUserAclRulesForNode(targetnode *models.Node, if !acl.Enabled { continue } + r := models.AclRule{ + ID: acl.ID, + AllowedProtocol: acl.Proto, + AllowedPorts: acl.Port, + Direction: acl.AllowedDirection, + Allowed: true, + } + // Get peers in the tags and add allowed rules + if userNode.StaticNode.Address != "" { + r.IPList = append(r.IPList, userNode.StaticNode.AddressIPNet4()) + } + if userNode.StaticNode.Address6 != "" { + r.IP6List = append(r.IP6List, userNode.StaticNode.AddressIPNet6()) + } + for _, dstI := range acl.Dst { + if dstI.ID == models.EgressRange { + ip, cidr, err := net.ParseCIDR(dstI.Value) + if err == nil { + if ip.To4() != nil { + r.Dst = append(r.Dst, *cidr) + } else { + r.Dst6 = append(r.Dst6, *cidr) + } + } + } + + } + if aclRule, ok := rules[acl.ID]; ok { + aclRule.IPList = append(aclRule.IPList, r.IPList...) + aclRule.IP6List = append(aclRule.IP6List, r.IP6List...) + rules[acl.ID] = aclRule + } else { + rules[acl.ID] = r + } + } + } + return rules +} + +func getUserAclRulesForNode(targetnode *models.Node, + rules map[string]models.AclRule) map[string]models.AclRule { + userNodes := GetStaticUserNodesByNetwork(models.NetworkID(targetnode.Network)) + userGrpMap := GetUserGrpMap() + allowedUsers := make(map[string][]models.Acl) + acls := listUserPolicies(models.NetworkID(targetnode.Network)) + var targetNodeTags = make(map[models.TagID]struct{}) + if targetnode.Mutex != nil { + targetnode.Mutex.Lock() + targetNodeTags = maps.Clone(targetnode.Tags) + targetnode.Mutex.Unlock() + } else { + targetNodeTags = maps.Clone(targetnode.Tags) + } + targetNodeTags[models.TagID(targetnode.ID.String())] = struct{}{} + for _, acl := range acls { + if !acl.Enabled { + continue + } + dstTags := convAclTagToValueMap(acl.Dst) + _, all := dstTags["*"] + addUsers := false + if !all { + for nodeTag := range targetNodeTags { + if _, ok := dstTags[nodeTag.String()]; ok { + addUsers = true + break + } + } + } else { + addUsers = true + } + + if addUsers { + // get all src tags + for _, srcAcl := range acl.Src { + if srcAcl.ID == models.UserAclID { + allowedUsers[srcAcl.Value] = append(allowedUsers[srcAcl.Value], acl) + } else if srcAcl.ID == models.UserGroupAclID { + // fetch all users in the group + if usersMap, ok := userGrpMap[models.UserGroupID(srcAcl.Value)]; ok { + for userName := range usersMap { + allowedUsers[userName] = append(allowedUsers[userName], acl) + } + } + } + } + } + + } + + for _, userNode := range userNodes { + if !userNode.StaticNode.Enabled { + continue + } + acls, ok := allowedUsers[userNode.StaticNode.OwnerID] + if !ok { + continue + } + for _, acl := range acls { + + if !acl.Enabled { + continue + } r := models.AclRule{ ID: acl.ID, AllowedProtocol: acl.Proto, @@ -1354,7 +1480,16 @@ func getUserAclRulesForNode(targetnode *models.Node, } func checkIfAnyPolicyisUniDirectional(targetNode models.Node) bool { - targetNode.Tags[models.TagID(targetNode.ID.String())] = struct{}{} + var targetNodeTags = make(map[models.TagID]struct{}) + if targetNode.Mutex != nil { + targetNode.Mutex.Lock() + targetNodeTags = maps.Clone(targetNode.Tags) + targetNode.Mutex.Unlock() + } else { + targetNodeTags = maps.Clone(targetNode.Tags) + } + targetNodeTags[models.TagID(targetNode.ID.String())] = struct{}{} + targetNodeTags["*"] = struct{}{} acls := listDevicePolicies(models.NetworkID(targetNode.Network)) for _, acl := range acls { if !acl.Enabled { @@ -1363,9 +1498,12 @@ func checkIfAnyPolicyisUniDirectional(targetNode models.Node) bool { if acl.AllowedDirection == models.TrafficDirectionBi { continue } + if acl.Proto != models.ALL || acl.ServiceType != models.Any { + return true + } srcTags := convAclTagToValueMap(acl.Src) dstTags := convAclTagToValueMap(acl.Dst) - for nodeTag := range targetNode.Tags { + for nodeTag := range targetNodeTags { if _, ok := srcTags[nodeTag.String()]; ok { return true } @@ -1385,12 +1523,10 @@ func checkIfAnyPolicyisUniDirectional(targetNode models.Node) bool { func GetAclRulesForNode(targetnodeI *models.Node) (rules map[string]models.AclRule) { targetnode := *targetnodeI - targetnode.Tags[models.TagID(targetnode.ID.String())] = struct{}{} defer func() { if !targetnode.IsIngressGateway { rules = getUserAclRulesForNode(&targetnode, rules) } - }() rules = make(map[string]models.AclRule) var taggedNodes map[models.TagID][]models.Node @@ -1409,8 +1545,8 @@ func GetAclRulesForNode(targetnodeI *models.Node) (rules map[string]models.AclRu } else { targetNodeTags = maps.Clone(targetnode.Tags) } + targetNodeTags[models.TagID(targetnode.ID.String())] = struct{}{} targetNodeTags["*"] = struct{}{} - for _, acl := range acls { if !acl.Enabled { continue @@ -1593,9 +1729,245 @@ func GetAclRulesForNode(targetnodeI *models.Node) (rules map[string]models.AclRu } } + if len(aclRule.IPList) > 0 || len(aclRule.IP6List) > 0 { + aclRule.IPList = UniqueIPNetList(aclRule.IPList) + aclRule.IP6List = UniqueIPNetList(aclRule.IP6List) rules[acl.ID] = aclRule } } return rules } +func UniqueIPNetList(ipnets []net.IPNet) []net.IPNet { + uniqueMap := make(map[string]net.IPNet) + + for _, ipnet := range ipnets { + key := ipnet.String() // Uses CIDR notation as a unique key + if _, exists := uniqueMap[key]; !exists { + uniqueMap[key] = ipnet + } + } + + // Convert map back to slice + uniqueList := make([]net.IPNet, 0, len(uniqueMap)) + for _, ipnet := range uniqueMap { + uniqueList = append(uniqueList, ipnet) + } + + return uniqueList +} + +func GetEgressRulesForNode(targetnode models.Node) (rules map[string]models.AclRule) { + rules = make(map[string]models.AclRule) + defer func() { + rules = getEgressUserRulesForNode(&targetnode, rules) + }() + taggedNodes := GetTagMapWithNodesByNetwork(models.NetworkID(targetnode.Network), true) + + acls := listDevicePolicies(models.NetworkID(targetnode.Network)) + var targetNodeTags = make(map[models.TagID]struct{}) + targetNodeTags["*"] = struct{}{} + + /* + if target node is egress gateway + if acl policy has egress route and it is present in target node egress ranges + fetches all the nodes in that policy and add rules + */ + + for _, rangeI := range targetnode.EgressGatewayRanges { + targetNodeTags[models.TagID(rangeI)] = struct{}{} + } + for _, acl := range acls { + if !acl.Enabled { + continue + } + srcTags := convAclTagToValueMap(acl.Src) + dstTags := convAclTagToValueMap(acl.Dst) + _, srcAll := srcTags["*"] + _, dstAll := dstTags["*"] + for nodeTag := range targetNodeTags { + aclRule := models.AclRule{ + ID: acl.ID, + AllowedProtocol: acl.Proto, + AllowedPorts: acl.Port, + Direction: acl.AllowedDirection, + Allowed: true, + } + if nodeTag != "*" { + ip, cidr, err := net.ParseCIDR(nodeTag.String()) + if err != nil { + continue + } + if ip.To4() != nil { + aclRule.Dst = append(aclRule.Dst, *cidr) + } else { + aclRule.Dst6 = append(aclRule.Dst6, *cidr) + } + + } else { + aclRule.Dst = append(aclRule.Dst, net.IPNet{ + IP: net.IPv4zero, // 0.0.0.0 + Mask: net.CIDRMask(0, 32), // /0 means match all IPv4 + }) + aclRule.Dst6 = append(aclRule.Dst6, net.IPNet{ + IP: net.IPv6zero, // :: + Mask: net.CIDRMask(0, 128), // /0 means match all IPv6 + }) + } + if acl.AllowedDirection == models.TrafficDirectionBi { + var existsInSrcTag bool + var existsInDstTag bool + + if _, ok := srcTags[nodeTag.String()]; ok || srcAll { + existsInSrcTag = true + } + if _, ok := dstTags[nodeTag.String()]; ok || dstAll { + existsInDstTag = true + } + + if existsInSrcTag && !existsInDstTag { + // get all dst tags + for dst := range dstTags { + if dst == nodeTag.String() { + continue + } + // Get peers in the tags and add allowed rules + nodes := taggedNodes[models.TagID(dst)] + if dst != targetnode.ID.String() { + node, err := GetNodeByID(dst) + if err == nil { + nodes = append(nodes, node) + } + } + + for _, node := range nodes { + if node.ID == targetnode.ID { + continue + } + if node.Address.IP != nil { + aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4()) + } + if node.Address6.IP != nil { + aclRule.IP6List = append(aclRule.IP6List, node.AddressIPNet6()) + } + if node.IsStatic && node.StaticNode.Address != "" { + aclRule.IPList = append(aclRule.IPList, node.StaticNode.AddressIPNet4()) + } + if node.IsStatic && node.StaticNode.Address6 != "" { + aclRule.IP6List = append(aclRule.IP6List, node.StaticNode.AddressIPNet6()) + } + } + } + } + if existsInDstTag && !existsInSrcTag { + // get all src tags + for src := range srcTags { + if src == nodeTag.String() { + continue + } + // Get peers in the tags and add allowed rules + nodes := taggedNodes[models.TagID(src)] + if src != targetnode.ID.String() { + node, err := GetNodeByID(src) + if err == nil { + nodes = append(nodes, node) + } + } + for _, node := range nodes { + if node.ID == targetnode.ID { + continue + } + if node.Address.IP != nil { + aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4()) + } + if node.Address6.IP != nil { + aclRule.IP6List = append(aclRule.IP6List, node.AddressIPNet6()) + } + if node.IsStatic && node.StaticNode.Address != "" { + aclRule.IPList = append(aclRule.IPList, node.StaticNode.AddressIPNet4()) + } + if node.IsStatic && node.StaticNode.Address6 != "" { + aclRule.IP6List = append(aclRule.IP6List, node.StaticNode.AddressIPNet6()) + } + } + } + } + if existsInDstTag && existsInSrcTag { + nodes := taggedNodes[nodeTag] + for srcID := range srcTags { + if srcID == targetnode.ID.String() { + continue + } + node, err := GetNodeByID(srcID) + if err == nil { + nodes = append(nodes, node) + } + } + for dstID := range dstTags { + if dstID == targetnode.ID.String() { + continue + } + node, err := GetNodeByID(dstID) + if err == nil { + nodes = append(nodes, node) + } + } + for _, node := range nodes { + if node.ID == targetnode.ID { + continue + } + if node.Address.IP != nil { + aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4()) + } + if node.Address6.IP != nil { + aclRule.IP6List = append(aclRule.IP6List, node.AddressIPNet6()) + } + if node.IsStatic && node.StaticNode.Address != "" { + aclRule.IPList = append(aclRule.IPList, node.StaticNode.AddressIPNet4()) + } + if node.IsStatic && node.StaticNode.Address6 != "" { + aclRule.IP6List = append(aclRule.IP6List, node.StaticNode.AddressIPNet6()) + } + } + } + } else { + _, all := dstTags["*"] + if _, ok := dstTags[nodeTag.String()]; ok || all { + // get all src tags + for src := range srcTags { + if src == nodeTag.String() { + continue + } + // Get peers in the tags and add allowed rules + nodes := taggedNodes[models.TagID(src)] + for _, node := range nodes { + if node.ID == targetnode.ID { + continue + } + if node.Address.IP != nil { + aclRule.IPList = append(aclRule.IPList, node.AddressIPNet4()) + } + if node.Address6.IP != nil { + aclRule.IP6List = append(aclRule.IP6List, node.AddressIPNet6()) + } + if node.IsStatic && node.StaticNode.Address != "" { + aclRule.IPList = append(aclRule.IPList, node.StaticNode.AddressIPNet4()) + } + if node.IsStatic && node.StaticNode.Address6 != "" { + aclRule.IP6List = append(aclRule.IP6List, node.StaticNode.AddressIPNet6()) + } + } + } + } + } + if len(aclRule.IPList) > 0 || len(aclRule.IP6List) > 0 { + aclRule.IPList = UniqueIPNetList(aclRule.IPList) + aclRule.IP6List = UniqueIPNetList(aclRule.IP6List) + rules[acl.ID] = aclRule + } + + } + + } + return +} diff --git a/logic/extpeers.go b/logic/extpeers.go index df8ebd48..e32595e8 100644 --- a/logic/extpeers.go +++ b/logic/extpeers.go @@ -591,132 +591,130 @@ func getFwRulesForNodeAndPeerOnGw(node, peer models.Node, allowedPolicies []mode } } + + // add egress range rules + for _, dstI := range policy.Dst { + if dstI.ID == models.EgressRange { + ip, cidr, err := net.ParseCIDR(dstI.Value) + if err == nil { + if ip.To4() != nil { + if node.Address.IP != nil { + rules = append(rules, models.FwRule{ + SrcIP: net.IPNet{ + IP: node.Address.IP, + Mask: net.CIDRMask(32, 32), + }, + DstIP: *cidr, + AllowedProtocol: policy.Proto, + AllowedPorts: policy.Port, + Allow: true, + }) + } + } else { + if node.Address6.IP != nil { + rules = append(rules, models.FwRule{ + SrcIP: net.IPNet{ + IP: node.Address6.IP, + Mask: net.CIDRMask(128, 128), + }, + DstIP: *cidr, + AllowedProtocol: policy.Proto, + AllowedPorts: policy.Port, + Allow: true, + }) + } + } + + } + } + } } return } -func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) { - // fetch user access to static clients via policies +func getFwRulesForUserNodesOnGw(node models.Node, nodes []models.Node) (rules []models.FwRule) { defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy) - defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy) - nodes, _ := GetNetworkNodes(node.Network) - nodes = append(nodes, GetStaticNodesByNetwork(models.NetworkID(node.Network), true)...) userNodes := GetStaticUserNodesByNetwork(models.NetworkID(node.Network)) for _, userNodeI := range userNodes { for _, peer := range nodes { if peer.IsUserNode { continue } + if ok, allowedPolicies := IsUserAllowedToCommunicate(userNodeI.StaticNode.OwnerID, peer); ok { if peer.IsStatic { - if userNodeI.StaticNode.Address != "" { - if !defaultUserPolicy.Enabled { - for _, policy := range allowedPolicies { - rules = append(rules, models.FwRule{ - SrcIP: userNodeI.StaticNode.AddressIPNet4(), - DstIP: peer.StaticNode.AddressIPNet4(), - AllowedProtocol: policy.Proto, - AllowedPorts: policy.Port, - Allow: true, - }) - rules = append(rules, models.FwRule{ - SrcIP: peer.StaticNode.AddressIPNet4(), - DstIP: userNodeI.StaticNode.AddressIPNet4(), - AllowedProtocol: policy.Proto, - AllowedPorts: policy.Port, - Allow: true, - }) + peer = peer.StaticNode.ConvertToStaticNode() + } + if !defaultUserPolicy.Enabled { + for _, policy := range allowedPolicies { + if userNodeI.StaticNode.Address != "" { + rules = append(rules, models.FwRule{ + SrcIP: userNodeI.StaticNode.AddressIPNet4(), + DstIP: net.IPNet{ + IP: peer.Address.IP, + Mask: net.CIDRMask(32, 32), + }, + AllowedProtocol: policy.Proto, + AllowedPorts: policy.Port, + Allow: true, + }) + } + if userNodeI.StaticNode.Address6 != "" { + rules = append(rules, models.FwRule{ + SrcIP: userNodeI.StaticNode.AddressIPNet6(), + DstIP: net.IPNet{ + IP: peer.Address6.IP, + Mask: net.CIDRMask(128, 128), + }, + AllowedProtocol: policy.Proto, + AllowedPorts: policy.Port, + Allow: true, + }) + } + + // add egress ranges + for _, dstI := range policy.Dst { + if dstI.ID == models.EgressRange { + ip, cidr, err := net.ParseCIDR(dstI.Value) + if err == nil { + if ip.To4() != nil && userNodeI.StaticNode.Address != "" { + rules = append(rules, models.FwRule{ + SrcIP: userNodeI.StaticNode.AddressIPNet4(), + DstIP: *cidr, + AllowedProtocol: policy.Proto, + AllowedPorts: policy.Port, + Allow: true, + }) + } else if ip.To16() != nil && userNodeI.StaticNode.Address6 != "" { + rules = append(rules, models.FwRule{ + SrcIP: userNodeI.StaticNode.AddressIPNet6(), + DstIP: *cidr, + AllowedProtocol: policy.Proto, + AllowedPorts: policy.Port, + Allow: true, + }) + } + } } } } - if userNodeI.StaticNode.Address6 != "" { - if !defaultUserPolicy.Enabled { - for _, policy := range allowedPolicies { - rules = append(rules, models.FwRule{ - SrcIP: userNodeI.StaticNode.AddressIPNet6(), - DstIP: peer.StaticNode.AddressIPNet6(), - Allow: true, - AllowedProtocol: policy.Proto, - AllowedPorts: policy.Port, - }) - rules = append(rules, models.FwRule{ - SrcIP: peer.StaticNode.AddressIPNet6(), - DstIP: userNodeI.StaticNode.AddressIPNet6(), - AllowedProtocol: policy.Proto, - AllowedPorts: policy.Port, - Allow: true, - }) - - } - } - - } - if len(peer.StaticNode.ExtraAllowedIPs) > 0 { - for _, additionalAllowedIPNet := range peer.StaticNode.ExtraAllowedIPs { - _, ipNet, err := net.ParseCIDR(additionalAllowedIPNet) - if err != nil { - continue - } - if ipNet.IP.To4() != nil { - rules = append(rules, models.FwRule{ - SrcIP: userNodeI.StaticNode.AddressIPNet4(), - DstIP: *ipNet, - Allow: true, - }) - } else { - rules = append(rules, models.FwRule{ - SrcIP: userNodeI.StaticNode.AddressIPNet6(), - DstIP: *ipNet, - Allow: true, - }) - } - - } - - } - } else { - - if userNodeI.StaticNode.Address != "" { - if !defaultUserPolicy.Enabled { - for _, policy := range allowedPolicies { - rules = append(rules, models.FwRule{ - SrcIP: userNodeI.StaticNode.AddressIPNet4(), - DstIP: net.IPNet{ - IP: peer.Address.IP, - Mask: net.CIDRMask(32, 32), - }, - AllowedProtocol: policy.Proto, - AllowedPorts: policy.Port, - Allow: true, - }) - } - - } - } - - if userNodeI.StaticNode.Address6 != "" { - if !defaultUserPolicy.Enabled { - for _, policy := range allowedPolicies { - rules = append(rules, models.FwRule{ - SrcIP: userNodeI.StaticNode.AddressIPNet6(), - DstIP: net.IPNet{ - IP: peer.Address6.IP, - Mask: net.CIDRMask(128, 128), - }, - AllowedProtocol: policy.Proto, - AllowedPorts: policy.Port, - Allow: true, - }) - } - } - } } + } } } + return +} +func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) { + // fetch user access to static clients via policies + + defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy) + nodes, _ := GetNetworkNodes(node.Network) + nodes = append(nodes, GetStaticNodesByNetwork(models.NetworkID(node.Network), true)...) + rules = getFwRulesForUserNodesOnGw(node, nodes) if defaultDevicePolicy.Enabled { return } @@ -739,17 +737,34 @@ func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) { if peer.IsStatic { peer = peer.StaticNode.ConvertToStaticNode() } - if ok, allowedPolicies := IsNodeAllowedToCommunicateV1(nodeI.StaticNode.ConvertToStaticNode(), peer, true); ok { - rules = append(rules, getFwRulesForNodeAndPeerOnGw(nodeI.StaticNode.ConvertToStaticNode(), peer, allowedPolicies)...) + var allowedPolicies1 []models.Acl + var ok bool + if ok, allowedPolicies1 = IsNodeAllowedToCommunicateV1(nodeI.StaticNode.ConvertToStaticNode(), peer, true); ok { + rules = append(rules, getFwRulesForNodeAndPeerOnGw(nodeI.StaticNode.ConvertToStaticNode(), peer, allowedPolicies1)...) } - if ok, allowedPolicies := IsNodeAllowedToCommunicateV1(peer, nodeI.StaticNode.ConvertToStaticNode(), true); ok { - rules = append(rules, getFwRulesForNodeAndPeerOnGw(peer, nodeI.StaticNode.ConvertToStaticNode(), allowedPolicies)...) + if ok, allowedPolicies2 := IsNodeAllowedToCommunicateV1(peer, nodeI.StaticNode.ConvertToStaticNode(), true); ok { + rules = append(rules, + getFwRulesForNodeAndPeerOnGw(peer, nodeI.StaticNode.ConvertToStaticNode(), + GetUniquePolicies(allowedPolicies1, allowedPolicies2))...) } } } return } +func GetUniquePolicies(policies1, policies2 []models.Acl) []models.Acl { + policies1Map := make(map[string]struct{}) + for _, policy1I := range policies1 { + policies1Map[policy1I.ID] = struct{}{} + } + for i := len(policies2) - 1; i >= 0; i-- { + if _, ok := policies1Map[policies2[i].ID]; ok { + policies2 = append(policies2[:i], policies2[i+1:]...) + } + } + return policies2 +} + func GetExtPeers(node, peer *models.Node) ([]wgtypes.PeerConfig, []models.IDandAddr, []models.EgressNetworkRoutes, error) { var peers []wgtypes.PeerConfig var idsAndAddr []models.IDandAddr @@ -764,13 +779,11 @@ func GetExtPeers(node, peer *models.Node) ([]wgtypes.PeerConfig, []models.IDandA } for _, extPeer := range extPeers { extPeer := extPeer - fmt.Println("=====> checking EXT peer: ", extPeer.ClientID) if !IsClientNodeAllowed(&extPeer, peer.ID.String()) { continue } if extPeer.RemoteAccessClientID == "" { if ok := IsPeerAllowed(extPeer.ConvertToStaticNode(), *peer, true); !ok { - fmt.Println("=====>1 checking EXT peer: ", extPeer.ClientID) continue } } else { @@ -934,16 +947,11 @@ func GetStaticUserNodesByNetwork(network models.NetworkID) (staticNode []models. for _, extI := range extClients { if extI.Network == network.String() { if extI.RemoteAccessClientID != "" { - n := models.Node{ - IsStatic: true, - StaticNode: extI, - IsUserNode: extI.RemoteAccessClientID != "", - } + n := extI.ConvertToStaticNode() staticNode = append(staticNode, n) } } } - return } diff --git a/logic/nodes.go b/logic/nodes.go index 09ccfc89..fabb592f 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -866,6 +866,9 @@ func GetTagMapWithNodesByNetwork(netID models.NetworkID, withStaticNodes bool) ( nodeI.Mutex.Lock() } for nodeTagID := range nodeI.Tags { + if nodeTagID == models.TagID(nodeI.ID.String()) { + continue + } tagNodesMap[nodeTagID] = append(tagNodesMap[nodeTagID], nodeI) } if nodeI.Mutex != nil { @@ -903,6 +906,9 @@ func AddTagMapWithStaticNodes(netID models.NetworkID, extclient.Mutex.Lock() } for tagID := range extclient.Tags { + if tagID == models.TagID(extclient.ClientID) { + continue + } tagNodesMap[tagID] = append(tagNodesMap[tagID], extclient.ConvertToStaticNode()) tagNodesMap["*"] = append(tagNodesMap["*"], extclient.ConvertToStaticNode()) } diff --git a/logic/peers.go b/logic/peers.go index 17f0e1ec..bb412151 100644 --- a/logic/peers.go +++ b/logic/peers.go @@ -181,6 +181,7 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N slog.Debug("peer update for host", "hostId", host.ID.String()) peerIndexMap := make(map[string]int) for _, nodeID := range host.Nodes { + networkAllowAll := true nodeID := nodeID node, err := GetNodeByID(nodeID) if err != nil { @@ -190,60 +191,6 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N if !node.Connected || node.PendingDelete || node.Action == models.NODE_DELETE { continue } - if host.OS == models.OS_Types.IoT { - hostPeerUpdate.NodeAddrs = append(hostPeerUpdate.NodeAddrs, node.PrimaryAddressIPNet()) - if node.IsRelayed { - relayNode, err := GetNodeByID(node.RelayedBy) - if err != nil { - continue - } - relayHost, err := GetHost(relayNode.HostID.String()) - if err != nil { - continue - } - relayPeer := wgtypes.PeerConfig{ - PublicKey: relayHost.PublicKey, - PersistentKeepaliveInterval: &relayHost.PersistentKeepalive, - ReplaceAllowedIPs: true, - AllowedIPs: GetAllowedIPs(&node, &relayNode, nil), - } - uselocal := false - if host.EndpointIP.String() == relayHost.EndpointIP.String() { - // peer is on same network - // set to localaddress - uselocal = true - if node.LocalAddress.IP == nil { - // use public endpint - uselocal = false - } - if node.LocalAddress.String() == relayNode.LocalAddress.String() { - uselocal = false - } - } - relayPeer.Endpoint = &net.UDPAddr{ - IP: relayHost.EndpointIP, - Port: GetPeerListenPort(relayHost), - } - - if uselocal { - relayPeer.Endpoint.IP = relayNode.LocalAddress.IP - relayPeer.Endpoint.Port = relayHost.ListenPort - } - - hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, relayPeer) - } else if deletedNode != nil && deletedNode.IsRelay { - relayHost, err := GetHost(deletedNode.HostID.String()) - if err != nil { - continue - } - relayPeer := wgtypes.PeerConfig{ - PublicKey: relayHost.PublicKey, - Remove: true, - } - hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, relayPeer) - } - continue - } hostPeerUpdate = SetDefaultGw(node, hostPeerUpdate) if !hostPeerUpdate.IsInternetGw { hostPeerUpdate.IsInternetGw = IsInternetGw(node) @@ -259,6 +206,7 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N hostPeerUpdate.FwUpdate.AllowedNetworks = append(hostPeerUpdate.FwUpdate.AllowedNetworks, node.NetworkRange6) } } else { + networkAllowAll = false hostPeerUpdate.FwUpdate.AllowAll = false rules := GetAclRulesForNode(&node) if len(hostPeerUpdate.FwUpdate.AclRules) == 0 { @@ -370,7 +318,6 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N } if uselocal { - peerConfig.Endpoint.IP = peer.LocalAddress.IP peerConfig.Endpoint.Port = peerHost.ListenPort } var allowedToComm bool @@ -438,7 +385,6 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N IngressID: node.ID.String(), Network: node.NetworkRange, Network6: node.NetworkRange6, - AllowAll: defaultDevicePolicy.Enabled && defaultUserPolicy.Default, StaticNodeIps: GetStaticNodeIps(node), Rules: GetFwRulesOnIngressGateway(node), } @@ -472,10 +418,23 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N IP: node.Address6.IP, Mask: getCIDRMaskFromAddr(node.Address6.IP.String()), }, - EgressGWCfg: node.EgressGatewayRequest, + EgressGWCfg: node.EgressGatewayRequest, + EgressFwRules: make(map[string]models.AclRule), } } + if node.IsEgressGateway { + if !networkAllowAll { + egressInfo := hostPeerUpdate.FwUpdate.EgressInfo[node.ID.String()] + if egressInfo.EgressFwRules == nil { + egressInfo.EgressFwRules = make(map[string]models.AclRule) + } + egressInfo.EgressFwRules = GetEgressRulesForNode(node) + hostPeerUpdate.FwUpdate.EgressInfo[node.ID.String()] = egressInfo + } + + } + if IsInternetGw(node) { hostPeerUpdate.FwUpdate.IsEgressGw = true egressrange := []string{"0.0.0.0/0"} diff --git a/logic/tags.go b/logic/tags.go index 69daace2..db9b7a9c 100644 --- a/logic/tags.go +++ b/logic/tags.go @@ -278,7 +278,7 @@ func CheckIDSyntax(id string) error { } func CreateDefaultTags(netID models.NetworkID) { - // create tag for remote access gws in the network + // create tag for gws in the network tag := models.Tag{ ID: models.TagID(fmt.Sprintf("%s.%s", netID.String(), models.GwTagName)), TagName: models.GwTagName, @@ -292,7 +292,7 @@ func CreateDefaultTags(netID models.NetworkID) { } err = InsertTag(tag) if err != nil { - slog.Error("failed to create remote access gw tag", "error", err.Error()) + slog.Error("failed to create gw tag", "error", err.Error()) return } } diff --git a/migrate/migrate.go b/migrate/migrate.go index 30f7a5f7..221d6a97 100644 --- a/migrate/migrate.go +++ b/migrate/migrate.go @@ -468,14 +468,14 @@ func migrateToGws() { upsert := false for i, srcI := range acl.Src { if srcI.ID == models.NodeTagID && srcI.Value == fmt.Sprintf("%s.%s", acl.NetworkID.String(), models.OldRemoteAccessTagName) { - srcI.Value = models.GwTagName + srcI.Value = fmt.Sprintf("%s.%s", acl.NetworkID.String(), models.GwTagName) acl.Src[i] = srcI upsert = true } } for i, dstI := range acl.Dst { if dstI.ID == models.NodeTagID && dstI.Value == fmt.Sprintf("%s.%s", acl.NetworkID.String(), models.OldRemoteAccessTagName) { - dstI.Value = models.GwTagName + dstI.Value = fmt.Sprintf("%s.%s", acl.NetworkID.String(), models.GwTagName) acl.Dst[i] = dstI upsert = true } diff --git a/models/acl.go b/models/acl.go index 2fd0aed2..18d7a3d0 100644 --- a/models/acl.go +++ b/models/acl.go @@ -117,5 +117,7 @@ type AclRule struct { AllowedProtocol Protocol `json:"allowed_protocols"` // tcp, udp, etc. AllowedPorts []string `json:"allowed_ports"` Direction AllowedTrafficDirection `json:"direction"` // single or two-way + Dst []net.IPNet `json:"dst"` + Dst6 []net.IPNet `json:"dst6"` Allowed bool } diff --git a/models/extclient.go b/models/extclient.go index f7762a2e..01cec21d 100644 --- a/models/extclient.go +++ b/models/extclient.go @@ -60,6 +60,7 @@ func (ext *ExtClient) ConvertToStaticNode() Node { Tags: ext.Tags, IsStatic: true, StaticNode: *ext, + IsUserNode: ext.RemoteAccessClientID != "", Mutex: ext.Mutex, } } diff --git a/models/mqtt.go b/models/mqtt.go index 2db7c51b..d3131610 100644 --- a/models/mqtt.go +++ b/models/mqtt.go @@ -53,7 +53,6 @@ type IngressInfo struct { Network6 net.IPNet `json:"network6"` StaticNodeIps []net.IP `json:"static_node_ips"` Rules []FwRule `json:"rules"` - AllowAll bool `json:"allow_all"` EgressRanges []net.IPNet `json:"egress_ranges"` EgressRanges6 []net.IPNet `json:"egress_ranges6"` } @@ -66,6 +65,7 @@ type EgressInfo struct { Network6 net.IPNet `json:"network6" yaml:"network6"` EgressGwAddr6 net.IPNet `json:"egress_gw_addr6" yaml:"egress_gw_addr6"` EgressGWCfg EgressGatewayRequest `json:"egress_gateway_cfg" yaml:"egress_gateway_cfg"` + EgressFwRules map[string]AclRule `json:"egress_fw_rules"` } // EgressNetworkRoutes - struct for egress network routes for adding routes to peer's interface diff --git a/pro/auth/error.go b/pro/auth/error.go index 8906caaa..d9beecb6 100644 --- a/pro/auth/error.go +++ b/pro/auth/error.go @@ -98,7 +98,7 @@ var oauthNotConfigured = fmt.Sprintf(htmlBaseTemplate, `

Your Netmaker server var oauthStateInvalid = fmt.Sprintf(htmlBaseTemplate, `

Invalid OAuth Session. Please re-try again.

`) var userNotAllowed = fmt.Sprintf(htmlBaseTemplate, `

Your account does not have access to the dashboard. Please contact your administrator for more information about your account.

-

Non-Admins can access the netmaker networks using our Remote Access Client.

`) +

Non-Admins can access the netmaker networks using our Netmaker Desktop App.

`) var userFirstTimeSignUp = fmt.Sprintf(htmlBaseTemplate, `

Thank you for signing up. Please contact your administrator for access.

`) diff --git a/pro/controllers/users.go b/pro/controllers/users.go index f87fe020..efffa56b 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -8,7 +8,9 @@ import ( "net/http" "net/url" "strings" + "time" + "github.com/google/uuid" "github.com/gorilla/mux" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" @@ -411,6 +413,44 @@ func createUserGroup(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } + networks, err := logic.GetNetworks() + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + for _, network := range networks { + acl := models.Acl{ + ID: uuid.New().String(), + Name: fmt.Sprintf("%s group", userGroupReq.Group.Name), + MetaData: "This Policy allows user group to communicate with all gateways", + Default: true, + ServiceType: models.Any, + NetworkID: models.NetworkID(network.NetID), + Proto: models.ALL, + RuleType: models.UserPolicy, + Src: []models.AclPolicyTag{ + { + ID: models.UserGroupAclID, + Value: userGroupReq.Group.ID.String(), + }, + }, + Dst: []models.AclPolicyTag{ + { + ID: models.NodeTagID, + Value: fmt.Sprintf("%s.%s", models.NetworkID(network.NetID), models.GwTagName), + }}, + AllowedDirection: models.TrafficDirectionUni, + Enabled: true, + CreatedBy: "auto", + CreatedAt: time.Now().UTC(), + } + err = logic.InsertAcl(acl) + if err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + } + for _, userID := range userGroupReq.Members { user, err := logic.GetUser(userID) if err != nil { diff --git a/pro/email/invite.go b/pro/email/invite.go index 721b3387..dd4f4767 100644 --- a/pro/email/invite.go +++ b/pro/email/invite.go @@ -2,6 +2,7 @@ package email import ( "fmt" + "github.com/gravitl/netmaker/models" proLogic "github.com/gravitl/netmaker/pro/logic" "github.com/gravitl/netmaker/servercfg" @@ -31,11 +32,11 @@ func (invite UserInvitedMail) GetBody(info Notification) string { content := invite.BodyBuilder. WithParagraph("Hi,"). - WithParagraph("You've been invited to access a secure network via Netmaker's Remote Access Client (RAC). Follow these simple steps to get connected:"). + WithParagraph("You've been invited to access a secure network via Netmaker Desktop App. Follow these simple steps to get connected:"). WithHtml("
    "). WithHtml(fmt.Sprintf("
  1. Click here to accept your invitation and setup your account.
  2. ", invite.InviteURL)). WithHtml("
    "). - WithHtml(fmt.Sprintf("
  3. Download the Remote Access Client (RAC).
  4. ", downloadLink)) + WithHtml(fmt.Sprintf("
  5. Download the Netmaker Desktop App.
  6. ", downloadLink)) if invite.PlatformRoleID == models.AdminRole.String() || invite.PlatformRoleID == models.PlatformUser.String() { content = content. diff --git a/pro/logic/user_mgmt.go b/pro/logic/user_mgmt.go index 994ca6bc..8a4ed7fb 100644 --- a/pro/logic/user_mgmt.go +++ b/pro/logic/user_mgmt.go @@ -40,7 +40,7 @@ var NetworkAdminAllPermissionTemplate = models.UserRolePermissionTemplate{ var NetworkUserAllPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkUser)), Name: "Network Users", - MetaData: "Can connect to nodes in your networks via Remote Access Client.", + MetaData: "Can connect to nodes in your networks via Netmaker Desktop App.", Default: true, FullAccess: false, NetworkID: models.AllNetworks, @@ -131,7 +131,7 @@ func UserGroupsInit() { models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkUser)): {}, }, }, - MetaData: "Provides read-only dashboard access to platform users and allows connection to network nodes via the Remote Access Client.", + MetaData: "Provides read-only dashboard access to platform users and allows connection to network nodes via the Netmaker Desktop App.", } d, _ := json.Marshal(NetworkGlobalAdminGroup) database.Insert(NetworkGlobalAdminGroup.ID.String(), string(d), database.USER_GROUPS_TABLE_NAME) @@ -156,7 +156,7 @@ func CreateDefaultNetworkRolesAndGroups(netID models.NetworkID) { var NetworkUserPermissionTemplate = models.UserRolePermissionTemplate{ ID: models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkUser)), Name: fmt.Sprintf("%s User", netID), - MetaData: fmt.Sprintf("Can connect to nodes in your network `%s` via Remote Access Client.", netID), + MetaData: fmt.Sprintf("Can connect to nodes in your network `%s` via Netmaker Desktop App.", netID), Default: true, FullAccess: false, NetworkID: netID, @@ -235,7 +235,7 @@ func CreateDefaultNetworkRolesAndGroups(netID models.NetworkID) { models.UserRoleID(fmt.Sprintf("%s-%s", netID, models.NetworkUser)): {}, }, }, - MetaData: fmt.Sprintf("Can connect to nodes in your network `%s` via Remote Access Client. Platform users will have read-only access to the the dashboard.", netID), + MetaData: fmt.Sprintf("Can connect to nodes in your network `%s` via Netmaker Desktop App. Platform users will have read-only access to the the dashboard.", netID), } d, _ = json.Marshal(NetworkAdminGroup) database.Insert(NetworkAdminGroup.ID.String(), string(d), database.USER_GROUPS_TABLE_NAME) @@ -810,87 +810,7 @@ func GetUserNetworkRolesWithRemoteVPNAccess(user models.User) (gwAccess map[mode } func GetFilteredNodesByUserAccess(user models.User, nodes []models.Node) (filteredNodes []models.Node) { - - nodesMap := make(map[string]struct{}) - allNetworkRoles := make(map[models.UserRoleID]struct{}) - defer func() { - filteredNodes = logic.AddStaticNodestoList(filteredNodes) - }() - if len(user.NetworkRoles) > 0 { - for _, netRoles := range user.NetworkRoles { - for netRoleI := range netRoles { - allNetworkRoles[netRoleI] = struct{}{} - } - } - } - if _, ok := user.NetworkRoles[models.AllNetworks]; ok { - filteredNodes = nodes - return - } - if len(user.UserGroups) > 0 { - for userGID := range user.UserGroups { - userG, err := GetUserGroup(userGID) - if err == nil { - if len(userG.NetworkRoles) > 0 { - if _, ok := userG.NetworkRoles[models.AllNetworks]; ok { - filteredNodes = nodes - return - } - for _, netRoles := range userG.NetworkRoles { - for netRoleI := range netRoles { - allNetworkRoles[netRoleI] = struct{}{} - } - } - } - } - } - } - for networkRoleID := range allNetworkRoles { - userPermTemplate, err := logic.GetRole(networkRoleID) - if err != nil { - continue - } - networkNodes := logic.GetNetworkNodesMemory(nodes, userPermTemplate.NetworkID.String()) - if userPermTemplate.FullAccess { - for _, node := range networkNodes { - if _, ok := nodesMap[node.ID.String()]; ok { - continue - } - nodesMap[node.ID.String()] = struct{}{} - filteredNodes = append(filteredNodes, node) - } - - continue - } - if rsrcPerms, ok := userPermTemplate.NetworkLevelAccess[models.RemoteAccessGwRsrc]; ok { - if _, ok := rsrcPerms[models.AllRemoteAccessGwRsrcID]; ok { - for _, node := range networkNodes { - if _, ok := nodesMap[node.ID.String()]; ok { - continue - } - if node.IsIngressGateway { - nodesMap[node.ID.String()] = struct{}{} - filteredNodes = append(filteredNodes, node) - } - } - } else { - for gwID, scope := range rsrcPerms { - if _, ok := nodesMap[gwID.String()]; ok { - continue - } - if scope.Read { - gwNode, err := logic.GetNodeByID(gwID.String()) - if err == nil && gwNode.IsIngressGateway { - nodesMap[gwNode.ID.String()] = struct{}{} - filteredNodes = append(filteredNodes, gwNode) - } - } - } - } - } - - } - return + return filteredNodes } func FilterNetworksByRole(allnetworks []models.Network, user models.User) []models.Network { @@ -1211,7 +1131,7 @@ func CreateDefaultUserPolicies(netID models.NetworkID) { defaultUserAcl := models.Acl{ ID: fmt.Sprintf("%s.%s-grp", netID, models.NetworkAdmin), Name: "Network Admin", - MetaData: "This Policy allows all network admins to communicate with all remote access gateways", + MetaData: "This Policy allows all network admins to communicate with all gateways", Default: true, ServiceType: models.Any, NetworkID: netID, @@ -1244,7 +1164,7 @@ func CreateDefaultUserPolicies(netID models.NetworkID) { defaultUserAcl := models.Acl{ ID: fmt.Sprintf("%s.%s-grp", netID, models.NetworkUser), Name: "Network User", - MetaData: "This Policy allows all network users to communicate with all remote access gateways", + MetaData: "This Policy allows all network users to communicate with all gateways", Default: true, ServiceType: models.Any, NetworkID: netID,