mirror of
https://github.com/gravitl/netmaker.git
synced 2025-09-05 20:54:18 +08:00
fix(NET-897): uniform client and node acls (#2803)
This commit is contained in:
parent
2c29a70df1
commit
100b778449
5 changed files with 384 additions and 2 deletions
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -14,6 +15,7 @@ import (
|
|||
"github.com/gravitl/netmaker/database"
|
||||
"github.com/gravitl/netmaker/logger"
|
||||
"github.com/gravitl/netmaker/logic"
|
||||
"github.com/gravitl/netmaker/logic/acls"
|
||||
"github.com/gravitl/netmaker/servercfg"
|
||||
|
||||
"github.com/gravitl/netmaker/models"
|
||||
|
@ -503,7 +505,7 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
var changedID = update.ClientID != oldExtClient.ClientID
|
||||
|
||||
if len(update.DeniedACLs) != len(oldExtClient.DeniedACLs) {
|
||||
if !reflect.DeepEqual(update.DeniedACLs, oldExtClient.DeniedACLs) {
|
||||
sendPeerUpdate = true
|
||||
logic.SetClientACLs(&oldExtClient, update.DeniedACLs)
|
||||
}
|
||||
|
@ -609,6 +611,24 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
// delete client acls
|
||||
var networkAcls acls.ACLContainer
|
||||
networkAcls, err = networkAcls.Get(acls.ContainerID(network))
|
||||
if err != nil {
|
||||
slog.Error("failed to get network acls", "err", err)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
for objId := range networkAcls {
|
||||
delete(networkAcls[objId], acls.AclID(clientid))
|
||||
}
|
||||
delete(networkAcls, acls.AclID(clientid))
|
||||
if _, err = networkAcls.Save(acls.ContainerID(network)); err != nil {
|
||||
slog.Error("failed to update network acls", "err", err)
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := mq.PublishDeletedClientPeerUpdate(&extclient); err != nil {
|
||||
logger.Log(1, "error setting ext peers on "+ingressnode.ID.String()+": "+err.Error())
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/exp/slog"
|
||||
|
||||
|
@ -17,6 +18,7 @@ import (
|
|||
"github.com/gravitl/netmaker/logic/acls"
|
||||
"github.com/gravitl/netmaker/models"
|
||||
"github.com/gravitl/netmaker/mq"
|
||||
"github.com/gravitl/netmaker/servercfg"
|
||||
)
|
||||
|
||||
func networkHandlers(r *mux.Router) {
|
||||
|
@ -27,6 +29,7 @@ func networkHandlers(r *mux.Router) {
|
|||
r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(updateNetwork))).Methods(http.MethodPut)
|
||||
// ACLs
|
||||
r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(updateNetworkACL))).Methods(http.MethodPut)
|
||||
r.HandleFunc("/api/networks/{networkname}/acls/v2", logic.SecurityCheck(true, http.HandlerFunc(updateNetworkACLv2))).Methods(http.MethodPut)
|
||||
r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(getNetworkACL))).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
|
@ -129,7 +132,7 @@ func updateNetworkACL(w http.ResponseWriter, r *http.Request) {
|
|||
// send peer updates
|
||||
go func() {
|
||||
if err = mq.PublishPeerUpdate(false); err != nil {
|
||||
logger.Log(0, "failed to publish peer update after ACL update on", netname)
|
||||
logger.Log(0, "failed to publish peer update after ACL update on network:", netname)
|
||||
}
|
||||
}()
|
||||
|
||||
|
@ -137,6 +140,196 @@ func updateNetworkACL(w http.ResponseWriter, r *http.Request) {
|
|||
json.NewEncoder(w).Encode(newNetACL)
|
||||
}
|
||||
|
||||
// swagger:route PUT /api/networks/{networkname}/acls/v2 networks updateNetworkACL
|
||||
//
|
||||
// Update a network ACL (Access Control List).
|
||||
//
|
||||
// Schemes: https
|
||||
//
|
||||
// Security:
|
||||
// oauth
|
||||
//
|
||||
// Responses:
|
||||
// 200: aclContainerResponse
|
||||
func updateNetworkACLv2(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 {
|
||||
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, "internal"))
|
||||
return
|
||||
}
|
||||
err = json.NewDecoder(r.Body).Decode(&networkACLChange)
|
||||
if err != nil {
|
||||
logger.Log(0, r.Header.Get("user"), "error decoding request body: ",
|
||||
err.Error())
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
||||
return
|
||||
}
|
||||
|
||||
// clone req body to use as return data successful update
|
||||
retData := make(acls.ACLContainer)
|
||||
data, err := json.Marshal(networkACLChange)
|
||||
if err != nil {
|
||||
slog.Error("failed to marshal networkACLChange whiles cloning", "error", err.Error())
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(data, &retData)
|
||||
if err != nil {
|
||||
slog.Error("failed to unmarshal networkACLChange whiles cloning", "error", err.Error())
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
|
||||
allNodes, err := logic.GetAllNodes()
|
||||
if err != nil {
|
||||
slog.Error("failed to fetch all nodes", "error", err.Error())
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
networkNodes := make([]models.Node, 0)
|
||||
for _, node := range allNodes {
|
||||
if node.Network == netname {
|
||||
networkNodes = append(networkNodes, node)
|
||||
}
|
||||
}
|
||||
networkNodesIdMap := make(map[string]models.Node)
|
||||
for _, node := range networkNodes {
|
||||
networkNodesIdMap[node.ID.String()] = node
|
||||
}
|
||||
networkClients, err := logic.GetNetworkExtClients(netname)
|
||||
if err != nil {
|
||||
slog.Error("failed to fetch network clients", "error", err.Error())
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
networkClientsMap := make(map[string]models.ExtClient)
|
||||
for _, client := range networkClients {
|
||||
networkClientsMap[client.ClientID] = client
|
||||
}
|
||||
|
||||
// keep track of ingress gateways to disconnect from their clients
|
||||
// this is required because PublishPeerUpdate only somehow does not stop communication
|
||||
// between blocked clients and their ingress
|
||||
assocClientsToDisconnectPerHost := make(map[uuid.UUID][]models.ExtClient)
|
||||
|
||||
// update client acls and then, remove client acls from req data to pass to existing functions
|
||||
for id, acl := range networkACLChange {
|
||||
// for node acls
|
||||
if _, ok := networkNodesIdMap[string(id)]; ok {
|
||||
nodeId := string(id)
|
||||
// check acl update, then remove client entries
|
||||
for id2 := range acl {
|
||||
if _, ok := networkNodesIdMap[string(id2)]; !ok {
|
||||
// update client acl
|
||||
clientId := string(id2)
|
||||
if client, ok := networkClientsMap[clientId]; ok {
|
||||
if client.DeniedACLs == nil {
|
||||
client.DeniedACLs = make(map[string]struct{})
|
||||
}
|
||||
if acl[acls.AclID(clientId)] == acls.NotAllowed {
|
||||
client.DeniedACLs[nodeId] = struct{}{}
|
||||
} else {
|
||||
delete(client.DeniedACLs, string(nodeId))
|
||||
}
|
||||
networkClientsMap[clientId] = client
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// for client acls
|
||||
clientId := string(id)
|
||||
for id2 := range acl {
|
||||
if _, ok := networkNodesIdMap[string(id2)]; !ok {
|
||||
// update client acl
|
||||
clientId2 := string(id2)
|
||||
if client, ok := networkClientsMap[clientId]; ok {
|
||||
if client.DeniedACLs == nil {
|
||||
client.DeniedACLs = make(map[string]struct{})
|
||||
}
|
||||
{
|
||||
// TODO: review this when client-to-client acls are supported
|
||||
// if acl[acls.AclID(clientId2)] == acls.NotAllowed {
|
||||
// client.DeniedACLs[clientId2] = struct{}{}
|
||||
// } else {
|
||||
// delete(client.DeniedACLs, clientId2)
|
||||
// }
|
||||
delete(client.DeniedACLs, clientId2)
|
||||
}
|
||||
networkClientsMap[clientId] = client
|
||||
}
|
||||
} else {
|
||||
nodeId2 := string(id2)
|
||||
if networkClientsMap[clientId].IngressGatewayID == nodeId2 && acl[acls.AclID(nodeId2)] == acls.NotAllowed {
|
||||
assocClientsToDisconnectPerHost[networkNodesIdMap[nodeId2].HostID] = append(assocClientsToDisconnectPerHost[networkNodesIdMap[nodeId2].HostID], networkClientsMap[clientId])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update each client in db for pro servers
|
||||
if servercfg.IsPro {
|
||||
for _, client := range networkClientsMap {
|
||||
client := client
|
||||
err := logic.DeleteExtClient(client.Network, client.ClientID)
|
||||
if err != nil {
|
||||
slog.Error("failed to delete client during update", "client", client.ClientID, "error", err.Error())
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
err = logic.SaveExtClient(&client)
|
||||
if err != nil {
|
||||
slog.Error("failed to save client during update", "client", client.ClientID, "error", err.Error())
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = networkACLChange.Save(acls.ContainerID(netname))
|
||||
if err != nil {
|
||||
logger.Log(0, r.Header.Get("user"),
|
||||
fmt.Sprintf("failed to update ACLs for network [%s]: %v", netname, err))
|
||||
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
||||
return
|
||||
}
|
||||
logger.Log(1, r.Header.Get("user"), "updated ACLs for network", netname)
|
||||
|
||||
// send peer updates
|
||||
go func() {
|
||||
if err = mq.PublishPeerUpdate(false); err != nil {
|
||||
logger.Log(0, "failed to publish peer update after ACL update on network:", netname)
|
||||
}
|
||||
|
||||
// update ingress gateways of associated clients
|
||||
hosts, err := logic.GetAllHosts()
|
||||
if err != nil {
|
||||
slog.Error("failed to fetch hosts after network ACL update. skipping publish extclients ACL", "network", netname)
|
||||
return
|
||||
}
|
||||
hostsMap := make(map[uuid.UUID]models.Host)
|
||||
for _, host := range hosts {
|
||||
hostsMap[host.ID] = host
|
||||
}
|
||||
for hostId, clients := range assocClientsToDisconnectPerHost {
|
||||
if host, ok := hostsMap[hostId]; ok {
|
||||
if err = mq.PublishSingleHostPeerUpdate(&host, allNodes, nil, clients, false); err != nil {
|
||||
slog.Error("failed to publish peer update to ingress after ACL update on network", "network", netname, "host", hostId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(networkACLChange)
|
||||
}
|
||||
|
||||
// swagger:route GET /api/networks/{networkname}/acls networks getNetworkACL
|
||||
//
|
||||
// Get a network ACL (Access Control List).
|
||||
|
|
|
@ -4,7 +4,9 @@ import (
|
|||
"errors"
|
||||
"sort"
|
||||
|
||||
"github.com/gravitl/netmaker/logic/acls"
|
||||
"github.com/gravitl/netmaker/models"
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
// functions defined here, handle client ACLs, should be set on ee
|
||||
|
@ -23,6 +25,23 @@ var (
|
|||
return true
|
||||
}
|
||||
SetClientDefaultACLs = func(ec *models.ExtClient) error {
|
||||
// allow all on CE
|
||||
networkAcls := acls.ACLContainer{}
|
||||
networkAcls, err := networkAcls.Get(acls.ContainerID(ec.Network))
|
||||
if err != nil {
|
||||
slog.Error("failed to get network acls", "error", err)
|
||||
return err
|
||||
}
|
||||
networkAcls[acls.AclID(ec.ClientID)] = acls.ACL{}
|
||||
for objId := range networkAcls {
|
||||
networkAcls[objId][acls.AclID(ec.ClientID)] = acls.Allowed
|
||||
networkAcls[acls.AclID(ec.ClientID)][objId] = acls.Allowed
|
||||
}
|
||||
delete(networkAcls[acls.AclID(ec.ClientID)], acls.AclID(ec.ClientID))
|
||||
if _, err = networkAcls.Save(acls.ContainerID(ec.Network)); err != nil {
|
||||
slog.Error("failed to update network acls", "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
SetClientACLs = func(ec *models.ExtClient, newACLs map[string]struct{}) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package migrate
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"golang.org/x/exp/slog"
|
||||
|
@ -9,6 +10,7 @@ import (
|
|||
"github.com/gravitl/netmaker/database"
|
||||
"github.com/gravitl/netmaker/logger"
|
||||
"github.com/gravitl/netmaker/logic"
|
||||
"github.com/gravitl/netmaker/logic/acls"
|
||||
"github.com/gravitl/netmaker/models"
|
||||
"github.com/gravitl/netmaker/servercfg"
|
||||
)
|
||||
|
@ -19,6 +21,7 @@ func Run() {
|
|||
assignSuperAdmin()
|
||||
updateHosts()
|
||||
updateNodes()
|
||||
updateAcls()
|
||||
}
|
||||
|
||||
func assignSuperAdmin() {
|
||||
|
@ -167,3 +170,123 @@ func removeInterGw(egressRanges []string) ([]string, bool) {
|
|||
}
|
||||
return egressRanges, update
|
||||
}
|
||||
|
||||
func updateAcls() {
|
||||
// get all networks
|
||||
networks, err := logic.GetNetworks()
|
||||
if err != nil {
|
||||
slog.Error("acls migration failed. error getting networks", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
// get current acls per network
|
||||
for _, network := range networks {
|
||||
var networkAcl acls.ACLContainer
|
||||
networkAcl, err := networkAcl.Get(acls.ContainerID(network.NetID))
|
||||
if err != nil {
|
||||
if database.IsEmptyRecord(err) {
|
||||
continue
|
||||
}
|
||||
slog.Error(fmt.Sprintf("error during acls migration. error getting acls for network: %s", network.NetID), "error", err)
|
||||
continue
|
||||
}
|
||||
// convert old acls to new acls with clients
|
||||
// TODO: optimise O(n^2) operation
|
||||
clients, err := logic.GetNetworkExtClients(network.NetID)
|
||||
if err != nil {
|
||||
slog.Error(fmt.Sprintf("error during acls migration. error getting clients for network: %s", network.NetID), "error", err)
|
||||
continue
|
||||
}
|
||||
clientsIdMap := make(map[string]struct{})
|
||||
for _, client := range clients {
|
||||
clientsIdMap[client.ClientID] = struct{}{}
|
||||
}
|
||||
nodeIdsMap := make(map[string]struct{})
|
||||
for nodeId := range networkAcl {
|
||||
nodeIdsMap[string(nodeId)] = struct{}{}
|
||||
}
|
||||
/*
|
||||
initially, networkACL has only node acls so we add client acls to it
|
||||
final shape:
|
||||
{
|
||||
"node1": {
|
||||
"node2": 2,
|
||||
"client1": 2,
|
||||
"client2": 1,
|
||||
},
|
||||
"node2": {
|
||||
"node1": 2,
|
||||
"client1": 2,
|
||||
"client2": 1,
|
||||
},
|
||||
"client1": {
|
||||
"node1": 2,
|
||||
"node2": 2,
|
||||
"client2": 1,
|
||||
},
|
||||
"client2": {
|
||||
"node1": 1,
|
||||
"node2": 1,
|
||||
"client1": 1,
|
||||
},
|
||||
}
|
||||
*/
|
||||
for _, client := range clients {
|
||||
networkAcl[acls.AclID(client.ClientID)] = acls.ACL{}
|
||||
// add client values to node acls and create client acls with node values
|
||||
for id, nodeAcl := range networkAcl {
|
||||
// skip if not a node
|
||||
if _, ok := nodeIdsMap[string(id)]; !ok {
|
||||
continue
|
||||
}
|
||||
if nodeAcl == nil {
|
||||
slog.Warn("acls migration bad data: nil node acl", "node", id, "network", network.NetID)
|
||||
continue
|
||||
}
|
||||
nodeAcl[acls.AclID(client.ClientID)] = acls.Allowed
|
||||
networkAcl[acls.AclID(client.ClientID)][id] = acls.Allowed
|
||||
if client.DeniedACLs == nil {
|
||||
continue
|
||||
} else if _, ok := client.DeniedACLs[string(id)]; ok {
|
||||
nodeAcl[acls.AclID(client.ClientID)] = acls.NotAllowed
|
||||
networkAcl[acls.AclID(client.ClientID)][id] = acls.NotAllowed
|
||||
}
|
||||
}
|
||||
// add clients to client acls response
|
||||
for _, c := range clients {
|
||||
if c.ClientID == client.ClientID {
|
||||
continue
|
||||
}
|
||||
networkAcl[acls.AclID(client.ClientID)][acls.AclID(c.ClientID)] = acls.Allowed
|
||||
if client.DeniedACLs == nil {
|
||||
continue
|
||||
} else if _, ok := client.DeniedACLs[c.ClientID]; ok {
|
||||
networkAcl[acls.AclID(client.ClientID)][acls.AclID(c.ClientID)] = acls.NotAllowed
|
||||
}
|
||||
}
|
||||
// delete oneself from its own acl
|
||||
delete(networkAcl[acls.AclID(client.ClientID)], acls.AclID(client.ClientID))
|
||||
}
|
||||
|
||||
// remove non-existent client and node acls
|
||||
for objId := range networkAcl {
|
||||
if _, ok := nodeIdsMap[string(objId)]; ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := clientsIdMap[string(objId)]; ok {
|
||||
continue
|
||||
}
|
||||
// remove all occurances of objId from all acls
|
||||
for objId2 := range networkAcl {
|
||||
delete(networkAcl[objId2], objId)
|
||||
}
|
||||
delete(networkAcl, objId)
|
||||
}
|
||||
|
||||
// save new acls
|
||||
if _, err := networkAcl.Save(acls.ContainerID(network.NetID)); err != nil {
|
||||
slog.Error(fmt.Sprintf("error during acls migration. error saving new acls for network: %s", network.NetID), "error", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"github.com/gravitl/netmaker/logic/acls"
|
||||
"github.com/gravitl/netmaker/logic/acls/nodeacls"
|
||||
"github.com/gravitl/netmaker/models"
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
// DenyClientNode - add a denied node to an ext client's list
|
||||
|
@ -55,14 +56,40 @@ func SetClientDefaultACLs(ec *models.ExtClient) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var networkAcls acls.ACLContainer
|
||||
networkAcls, err = networkAcls.Get(acls.ContainerID(ec.Network))
|
||||
if err != nil {
|
||||
slog.Error("failed to get network acls", "error", err)
|
||||
return err
|
||||
}
|
||||
networkAcls[acls.AclID(ec.ClientID)] = acls.ACL{}
|
||||
for i := range networkNodes {
|
||||
currNode := networkNodes[i]
|
||||
if network.DefaultACL == "no" || currNode.DefaultACL == "no" {
|
||||
DenyClientNode(ec, currNode.ID.String())
|
||||
networkAcls[acls.AclID(ec.ClientID)][acls.AclID(currNode.ID.String())] = acls.NotAllowed
|
||||
networkAcls[acls.AclID(currNode.ID.String())][acls.AclID(ec.ClientID)] = acls.NotAllowed
|
||||
} else {
|
||||
RemoveDeniedNodeFromClient(ec, currNode.ID.String())
|
||||
networkAcls[acls.AclID(ec.ClientID)][acls.AclID(currNode.ID.String())] = acls.Allowed
|
||||
networkAcls[acls.AclID(currNode.ID.String())][acls.AclID(ec.ClientID)] = acls.Allowed
|
||||
}
|
||||
}
|
||||
networkClients, err := logic.GetNetworkExtClients(ec.Network)
|
||||
if err != nil {
|
||||
slog.Error("failed to get network clients", "error", err)
|
||||
return err
|
||||
}
|
||||
for _, client := range networkClients {
|
||||
// TODO: revisit when client-client acls are supported
|
||||
networkAcls[acls.AclID(ec.ClientID)][acls.AclID(client.ClientID)] = acls.Allowed
|
||||
networkAcls[acls.AclID(client.ClientID)][acls.AclID(ec.ClientID)] = acls.Allowed
|
||||
}
|
||||
delete(networkAcls[acls.AclID(ec.ClientID)], acls.AclID(ec.ClientID)) // remove oneself
|
||||
if _, err = networkAcls.Save(acls.ContainerID(ec.Network)); err != nil {
|
||||
slog.Error("failed to update network acls", "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue