Browse Source

Merge branch 'develop' of https://github.com/gravitl/netmaker into NET-940-2

abhishek9686 1 năm trước cách đây
mục cha
commit
5833611883

+ 1 - 1
.github/ISSUE_TEMPLATE/bug-report.yml

@@ -31,7 +31,7 @@ body:
       label: Version
       description: What version are you running?
       options:
-        - v0.22.1
+        - v0.23.0
         - v0.22.0
         - v0.21.2
         - v0.21.1

+ 1 - 1
Dockerfile

@@ -6,7 +6,7 @@ COPY . .
 
 RUN GOOS=linux CGO_ENABLED=1 go build -ldflags="-s -w " -tags ${tags} .
 # RUN go build -tags=ee . -o netmaker main.go
-FROM alpine:3.19.0
+FROM alpine:3.19.1
 
 # add a c lib
 # set the working directory

+ 1 - 1
Dockerfile-quick

@@ -1,5 +1,5 @@
 #first stage - builder
-FROM alpine:3.19.0
+FROM alpine:3.19.1
 ARG version 
 WORKDIR /app
 COPY ./netmaker /root/netmaker

+ 1 - 1
README.md

@@ -16,7 +16,7 @@
 
 <p align="center">
   <a href="https://github.com/gravitl/netmaker/releases">
-    <img src="https://img.shields.io/badge/Version-0.22.1-informational?style=flat-square" />
+    <img src="https://img.shields.io/badge/Version-0.23.0-informational?style=flat-square" />
   </a>
   <a href="https://hub.docker.com/r/gravitl/netmaker/tags">
     <img src="https://img.shields.io/docker/pulls/gravitl/netmaker?label=downloads" />

+ 1 - 1
compose/docker-compose.netclient.yml

@@ -3,7 +3,7 @@ version: "3.4"
 services:
   netclient:
     container_name: netclient
-    image: 'gravitl/netclient:v0.22.1'
+    image: 'gravitl/netclient:v0.23.0'
     hostname: netmaker-1
     network_mode: host
     restart: on-failure

+ 1 - 1
controllers/docs.go

@@ -10,7 +10,7 @@
 //
 //	Schemes: https
 //	BasePath: /
-//	Version: 0.22.1
+//	Version: 0.23.0
 //	Host: api.demo.netmaker.io
 //
 //	Consumes:

+ 49 - 4
controllers/ext_client.go

@@ -6,12 +6,16 @@ import (
 	"fmt"
 	"net"
 	"net/http"
+	"reflect"
 	"strconv"
+	"strings"
 
+	"github.com/go-playground/validator/v10"
 	"github.com/gorilla/mux"
 	"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"
@@ -250,11 +254,28 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
 	if host.MTU != 0 {
 		defaultMTU = host.MTU
 	}
+
+	postUp := strings.Builder{}
+	if client.PostUp != "" && params["type"] != "qr" {
+		for _, loc := range strings.Split(client.PostUp, "\n") {
+			postUp.WriteString(fmt.Sprintf("PostUp = %s\n", loc))
+		}
+	}
+
+	postDown := strings.Builder{}
+	if client.PostDown != "" && params["type"] != "qr" {
+		for _, loc := range strings.Split(client.PostDown, "\n") {
+			postDown.WriteString(fmt.Sprintf("PostDown = %s\n", loc))
+		}
+	}
+
 	config := fmt.Sprintf(`[Interface]
 Address = %s
 PrivateKey = %s
 MTU = %d
 %s
+%s
+%s
 
 [Peer]
 PublicKey = %s
@@ -266,10 +287,13 @@ Endpoint = %s
 		client.PrivateKey,
 		defaultMTU,
 		defaultDNS,
+		postUp.String(),
+		postDown.String(),
 		host.PublicKey,
 		newAllowedIPs,
 		gwendpoint,
-		keepalive)
+		keepalive,
+	)
 
 	if params["type"] == "qr" {
 		bytes, err := qrcode.Encode(config, qrcode.Medium, 220)
@@ -330,7 +354,6 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 	var customExtClient models.CustomExtClient
-
 	if err := json.NewDecoder(r.Body).Decode(&customExtClient); err != nil {
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
 		return
@@ -486,7 +509,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)
 	}
@@ -499,7 +522,6 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
 	}
 	newclient := logic.UpdateExtClient(&oldExtClient, &update)
 	if err := logic.DeleteExtClient(oldExtClient.Network, oldExtClient.ClientID); err != nil {
-
 		slog.Error("failed to delete ext client", "user", r.Header.Get("user"), "id", oldExtClient.ClientID, "network", oldExtClient.Network, "error", err)
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
@@ -593,6 +615,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())
@@ -609,6 +649,11 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) {
 
 // validateCustomExtClient	Validates the extclient object
 func validateCustomExtClient(customExtClient *models.CustomExtClient, checkID bool) error {
+	v := validator.New()
+	err := v.Struct(customExtClient)
+	if err != nil {
+		return err
+	}
 	//validate clientid
 	if customExtClient.ClientID != "" {
 		if err := isValid(customExtClient.ClientID, checkID); err != nil {

+ 194 - 1
controllers/network.go

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

+ 8 - 8
go.mod

@@ -4,21 +4,21 @@ go 1.19
 
 require (
 	github.com/eclipse/paho.mqtt.golang v1.4.3
-	github.com/go-playground/validator/v10 v10.17.0
+	github.com/go-playground/validator/v10 v10.18.0
 	github.com/golang-jwt/jwt/v4 v4.5.0
 	github.com/google/uuid v1.6.0
 	github.com/gorilla/handlers v1.5.2
 	github.com/gorilla/mux v1.8.1
 	github.com/lib/pq v1.10.9
-	github.com/mattn/go-sqlite3 v1.14.19
+	github.com/mattn/go-sqlite3 v1.14.22
 	github.com/rqlite/gorqlite v0.0.0-20240122221808-a8a425b1a6aa
 	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
 	github.com/stretchr/testify v1.8.4
 	github.com/txn2/txeh v1.5.5
-	golang.org/x/crypto v0.18.0
-	golang.org/x/net v0.20.0 // indirect
-	golang.org/x/oauth2 v0.16.0
-	golang.org/x/sys v0.16.0 // indirect
+	golang.org/x/crypto v0.19.0
+	golang.org/x/net v0.21.0 // indirect
+	golang.org/x/oauth2 v0.17.0
+	golang.org/x/sys v0.17.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb
 	google.golang.org/protobuf v1.31.0 // indirect
@@ -47,7 +47,7 @@ require (
 
 require (
 	cloud.google.com/go/compute/metadata v0.2.3 // indirect
-	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
@@ -61,7 +61,7 @@ require (
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/hashicorp/go-version v1.6.0
-	github.com/leodido/go-urn v1.2.4 // indirect
+	github.com/leodido/go-urn v1.4.0 // indirect
 	github.com/mattn/go-runewidth v0.0.13 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect

+ 16 - 23
go.sum

@@ -18,8 +18,8 @@ github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQ
 github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
 github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
 github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
-github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
-github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
+github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
+github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
 github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
 github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -27,8 +27,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
-github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
-github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
+github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
 github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
 github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@@ -52,8 +52,8 @@ github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mO
 github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
-github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
 github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
@@ -62,8 +62,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
 github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
-github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
+github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -73,8 +73,6 @@ github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sA
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f h1:BSnJgAfHzEp7o8PYJ7YfwAVHhqu7BYUTggcn/LGlUWY=
-github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f/go.mod h1:UW/gxgQwSePTvL1KA8QEHsXeYHP4xkoXgbDdN781p34=
 github.com/rqlite/gorqlite v0.0.0-20240122221808-a8a425b1a6aa h1:hxMLFbj+F444JAS5nUQxTDZwUxwCRqg3WkNqhiDzXrM=
 github.com/rqlite/gorqlite v0.0.0-20240122221808-a8a425b1a6aa/go.mod h1:xF/KoXmrRyahPfo5L7Szb5cAAUl53dMWBh9cMruGEZg=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -87,12 +85,7 @@ github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyh
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/txn2/txeh v1.5.5 h1:UN4e/lCK5HGw/gGAi2GCVrNKg0GTCUWs7gs5riaZlz4=
@@ -104,8 +97,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
-golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
+golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
 golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -113,10 +106,10 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
-golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
-golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
-golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
+golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
+golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
@@ -127,8 +120,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
-golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

+ 1 - 1
k8s/client/netclient-daemonset.yaml

@@ -16,7 +16,7 @@ spec:
       hostNetwork: true
       containers:
       - name: netclient
-        image: gravitl/netclient:v0.22.1
+        image: gravitl/netclient:v0.23.0
         env:
         - name: TOKEN
           value: "TOKEN_VALUE"

+ 1 - 1
k8s/client/netclient.yaml

@@ -28,7 +28,7 @@ spec:
       #           - "<node label value>"
       containers:
       - name: netclient
-        image: gravitl/netclient:v0.22.1
+        image: gravitl/netclient:v0.23.0
         env:
         - name: TOKEN
           value: "TOKEN_VALUE"

+ 1 - 1
k8s/server/netmaker-ui.yaml

@@ -15,7 +15,7 @@ spec:
     spec:
       containers:
       - name: netmaker-ui
-        image: gravitl/netmaker-ui:v0.22.1
+        image: gravitl/netmaker-ui:v0.23.0
         ports:
         - containerPort: 443
         env:

+ 19 - 0
logic/clients.go

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

+ 4 - 0
logic/extpeers.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"net"
 	"reflect"
+	"strings"
 	"sync"
 	"time"
 
@@ -276,6 +277,9 @@ func UpdateExtClient(old *models.ExtClient, update *models.CustomExtClient) mode
 	if update.DeniedACLs != nil && !reflect.DeepEqual(old.DeniedACLs, update.DeniedACLs) {
 		new.DeniedACLs = update.DeniedACLs
 	}
+	// replace any \r\n with \n in postup and postdown from HTTP request
+	new.PostUp = strings.Replace(update.PostUp, "\r\n", "\n", -1)
+	new.PostDown = strings.Replace(update.PostDown, "\r\n", "\n", -1)
 	return new
 }
 

+ 4 - 7
logic/gateway.go

@@ -96,6 +96,9 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 	if err != nil {
 		return models.Node{}, err
 	}
+	if gateway.Ranges == nil {
+		gateway.Ranges = make([]string, 0)
+	}
 	node.IsEgressGateway = true
 	node.EgressGatewayRanges = gateway.Ranges
 	node.EgressGatewayNatEnabled = models.ParseBool(gateway.NatEnabled)
@@ -109,13 +112,7 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
 
 // ValidateEgressGateway - validates the egress gateway model
 func ValidateEgressGateway(gateway models.EgressGatewayRequest) error {
-	var err error
-
-	empty := len(gateway.Ranges) == 0
-	if empty {
-		err = errors.New("IP Ranges Cannot Be Empty")
-	}
-	return err
+	return nil
 }
 
 // DeleteEgressGateway - deletes egress from node

+ 1 - 1
main.go

@@ -28,7 +28,7 @@ import (
 	"golang.org/x/exp/slog"
 )
 
-var version = "v0.22.1"
+var version = "v0.23.0"
 
 // Start DB Connection and start API Request Handler
 func main() {

+ 123 - 0
migrate/migrate.go

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

+ 4 - 0
models/extclient.go

@@ -18,6 +18,8 @@ type ExtClient struct {
 	OwnerID                string              `json:"ownerid" bson:"ownerid"`
 	DeniedACLs             map[string]struct{} `json:"deniednodeacls" bson:"acls,omitempty"`
 	RemoteAccessClientID   string              `json:"remote_access_client_id"` // unique ID (MAC address) of RAC machine
+	PostUp                 string              `json:"postup" bson:"postup"`
+	PostDown               string              `json:"postdown" bson:"postdown"`
 }
 
 // CustomExtClient - struct for CustomExtClient params
@@ -29,4 +31,6 @@ type CustomExtClient struct {
 	Enabled              bool                `json:"enabled,omitempty"`
 	DeniedACLs           map[string]struct{} `json:"deniednodeacls" bson:"acls,omitempty"`
 	RemoteAccessClientID string              `json:"remote_access_client_id"` // unique ID (MAC address) of RAC machine
+	PostUp               string              `json:"postup" bson:"postup" validate:"max=1024"`
+	PostDown             string              `json:"postdown" bson:"postdown" validate:"max=1024"`
 }

+ 27 - 0
pro/logic/ext_acls.go

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

+ 2 - 6
pro/logic/relays.go

@@ -103,11 +103,7 @@ func SetRelayedNodes(setRelayed bool, relay string, relayed []string) []models.N
 // ValidateRelay - checks if relay is valid
 func ValidateRelay(relay models.RelayRequest) error {
 	var err error
-	// isIp := functions.IsIpCIDR(gateway.RangeString)
-	empty := len(relay.RelayedNodes) == 0
-	if empty {
-		return errors.New("IP Ranges Cannot Be Empty")
-	}
+
 	node, err := logic.GetNodeByID(relay.NodeID)
 	if err != nil {
 		return err
@@ -135,7 +131,7 @@ func updateRelayNodes(relay string, oldNodes []string, newNodes []string) []mode
 
 func RelayUpdates(currentNode, newNode *models.Node) bool {
 	relayUpdates := false
-	if servercfg.IsPro && newNode.IsRelay && len(newNode.RelayedNodes) > 0 {
+	if servercfg.IsPro && newNode.IsRelay {
 		if len(newNode.RelayedNodes) != len(currentNode.RelayedNodes) {
 			relayUpdates = true
 		} else {

+ 1 - 1
release.md

@@ -1,5 +1,5 @@
 
-# Netmaker v0.22.1
+# Netmaker v0.23.0
 
 ## Whats New
 - Revamped Internet Gateways

+ 1 - 1
swagger.yml

@@ -1149,7 +1149,7 @@ info:
 
         API calls must be authenticated via a header of the format -H “Authorization: Bearer <YOUR_SECRET_KEY>” There are two methods to obtain YOUR_SECRET_KEY: 1. Using the masterkey. By default, this value is “secret key,” but you should change this on your instance and keep it secure. This value can be set via env var at startup or in a config file (config/environments/< env >.yaml). See the [Netmaker](https://docs.netmaker.org/index.html) documentation for more details. 2. Using a JWT received for a node. This can be retrieved by calling the /api/nodes/<network>/authenticate endpoint, as documented below.
     title: Netmaker
-    version: 0.22.1
+    version: 0.23.0
 paths:
     /api/dns:
         get: