fix(NET-897): uniform client and node acls (#2803)

This commit is contained in:
Aceix 2024-02-13 13:25:27 +00:00 committed by GitHub
parent 2c29a70df1
commit 100b778449
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 384 additions and 2 deletions

View file

@ -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())

View file

@ -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).

View file

@ -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{}) {

View file

@ -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
}
}
}

View file

@ -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
}