Skip to content

Commit

Permalink
fix(NET-897): uniform client and node acls (#2803)
Browse files Browse the repository at this point in the history
  • Loading branch information
Aceix authored Feb 13, 2024
1 parent 2c29a70 commit 100b778
Show file tree
Hide file tree
Showing 5 changed files with 384 additions and 2 deletions.
22 changes: 21 additions & 1 deletion controllers/ext_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"net"
"net/http"
"reflect"
"strconv"
"strings"

Expand All @@ -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"
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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())
Expand Down
195 changes: 194 additions & 1 deletion controllers/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"strings"

"github.com/google/uuid"
"github.com/gorilla/mux"
"golang.org/x/exp/slog"

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

Expand Down Expand Up @@ -129,14 +132,204 @@ 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)
}
}()

w.WriteHeader(http.StatusOK)
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).
Expand Down
19 changes: 19 additions & 0 deletions logic/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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{}) {
Expand Down
Loading

0 comments on commit 100b778

Please sign in to comment.