@@ -51,18 +51,20 @@
If you're looking for a managed service, you can get started with just few clicks, visit [netmaker.io](https://account.netmaker.io) to create your netmaker server.
-# Self-Hosted Quick Start
+# Self-Hosted Open Source Quick Start
These are the instructions for deploying a Netmaker server on your own cloud VM as quickly as possible. For more detailed instructions, visit the [Install Docs](https://docs.netmaker.io/docs/server-installation/quick-install#quick-install-script).
1. Get a cloud VM with Ubuntu 22.04 and a public IP.
2. Open ports 443, 80, 3479, 8089 and 51821-51830/udp on the VM firewall and in cloud security settings.
3. (recommended) Prepare DNS - Set a wildcard subdomain in your DNS settings for Netmaker, e.g. *.netmaker.example.com, which points to your VM's public IP.
-4. Run the script:
+4. Run the script to setup open source version of Netmaker:
+
+`sudo wget -qO /root/nm-quick.sh https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/nm-quick.sh && sudo chmod +x /root/nm-quick.sh && sudo /root/nm-quick.sh`
+
+**To Install Self-Hosted PRO Version - https://docs.netmaker.io/docs/server-installation/netmaker-professional-setup
**
-`sudo wget -qO /root/nm-quick.sh https://raw.githubusercontent.com/gravitl/netmaker/master/scripts/nm-quick.sh && sudo chmod +x /root/nm-quick.sh && sudo /root/nm-quick.sh`
-This script by default installs PRO version with 14-day trial, check out these instructions for post trial period https://docs.netmaker.io/docs/server-installation/quick-install#after-the-trial-period-ends. It also gives you the option to use your own domain (recommended) or an auto-generated domain.
diff --git a/auth/host_session.go b/auth/host_session.go
index 62e9d4387..7a3929240 100644
--- a/auth/host_session.go
+++ b/auth/host_session.go
@@ -222,7 +222,7 @@ func SessionHandler(conn *websocket.Conn) {
if err = conn.WriteMessage(messageType, reponseData); err != nil {
logger.Log(0, "error during message writing:", err.Error())
}
- go CheckNetRegAndHostUpdate(netsToAdd[:], &result.Host, uuid.Nil)
+ go CheckNetRegAndHostUpdate(netsToAdd[:], &result.Host, uuid.Nil, []models.TagID{})
case <-timeout: // the read from req.answerCh has timed out
logger.Log(0, "timeout signal recv,exiting oauth socket conn")
break
@@ -236,7 +236,7 @@ func SessionHandler(conn *websocket.Conn) {
}
// CheckNetRegAndHostUpdate - run through networks and send a host update
-func CheckNetRegAndHostUpdate(networks []string, h *models.Host, relayNodeId uuid.UUID) {
+func CheckNetRegAndHostUpdate(networks []string, h *models.Host, relayNodeId uuid.UUID, tags []models.TagID) {
// publish host update through MQ
for i := range networks {
network := networks[i]
@@ -246,6 +246,14 @@ func CheckNetRegAndHostUpdate(networks []string, h *models.Host, relayNodeId uui
logger.Log(0, "failed to add host to network:", h.ID.String(), h.Name, network, err.Error())
continue
}
+ if len(tags) > 0 {
+ newNode.Tags = make(map[models.TagID]struct{})
+ for _, tagI := range tags {
+ newNode.Tags[tagI] = struct{}{}
+ }
+ logic.UpsertNode(newNode)
+ }
+
if relayNodeId != uuid.Nil && !newNode.IsRelayed {
// check if relay node exists and acting as relay
relaynode, err := logic.GetNodeByID(relayNodeId.String())
diff --git a/cli/cmd/user/groups.go b/cli/cmd/user/groups.go
index 0406083e1..0cb8db0a2 100644
--- a/cli/cmd/user/groups.go
+++ b/cli/cmd/user/groups.go
@@ -56,7 +56,7 @@ var userGroupCreateCmd = &cobra.Command{
Short: "create user group",
Long: `create user group`,
Run: func(cmd *cobra.Command, args []string) {
- fmt.Println("CLI doesn't support creation of groups currently. Visit the dashboard to create one or refer to our api documentation https://docs.v2.netmaker.io/reference")
+ fmt.Println("CLI doesn't support creation of groups currently. Visit the dashboard to create one or refer to our api documentation https://docs.netmaker.io/api")
},
}
diff --git a/cli/cmd/user/roles.go b/cli/cmd/user/roles.go
index 2bb880ef5..fbb8a5920 100644
--- a/cli/cmd/user/roles.go
+++ b/cli/cmd/user/roles.go
@@ -58,7 +58,7 @@ var userRoleCreateCmd = &cobra.Command{
Short: "create user role",
Long: `create user role`,
Run: func(cmd *cobra.Command, args []string) {
- fmt.Println("CLI doesn't support creation of roles currently. Visit the dashboard to create one or refer to our api documentation https://docs.v2.netmaker.io/reference")
+ fmt.Println("CLI doesn't support creation of roles currently. Visit the dashboard to create one or refer to our api documentation https://docs.netmaker.io/api")
},
}
diff --git a/compose/docker-compose.netclient.yml b/compose/docker-compose.netclient.yml
index a488fa006..da6fc14f4 100644
--- a/compose/docker-compose.netclient.yml
+++ b/compose/docker-compose.netclient.yml
@@ -3,7 +3,7 @@ version: "3.4"
services:
netclient:
container_name: netclient
- image: 'gravitl/netclient:v0.25.0'
+ image: 'gravitl/netclient:v0.26.0'
hostname: netmaker-1
network_mode: host
restart: on-failure
diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml
index 3111f1f5f..e32481393 100644
--- a/compose/docker-compose.yml
+++ b/compose/docker-compose.yml
@@ -41,7 +41,7 @@ services:
restart: always
caddy:
- image: caddy:2.6.2
+ image: caddy:2.8.4
container_name: caddy
env_file: ./netmaker.env
restart: unless-stopped
diff --git a/config/config.go b/config/config.go
index 061109f6d..f9acaf980 100644
--- a/config/config.go
+++ b/config/config.go
@@ -100,6 +100,8 @@ type ServerConfig struct {
SmtpHost string `json:"smtp_host"`
SmtpPort int `json:"smtp_port"`
MetricInterval string `yaml:"metric_interval"`
+ ManageDNS bool `yaml:"manage_dns"`
+ DefaultDomain string `yaml:"default_domain"`
}
// SQLConfig - Generic SQL Config
diff --git a/controllers/acls.go b/controllers/acls.go
new file mode 100644
index 000000000..727811fb5
--- /dev/null
+++ b/controllers/acls.go
@@ -0,0 +1,230 @@
+package controller
+
+import (
+ "encoding/json"
+ "errors"
+ "net/http"
+ "net/url"
+ "time"
+
+ "github.com/google/uuid"
+ "github.com/gorilla/mux"
+ "github.com/gravitl/netmaker/logger"
+ "github.com/gravitl/netmaker/logic"
+ "github.com/gravitl/netmaker/models"
+ "github.com/gravitl/netmaker/mq"
+)
+
+func aclHandlers(r *mux.Router) {
+ r.HandleFunc("/api/v1/acls", logic.SecurityCheck(true, http.HandlerFunc(getAcls))).
+ Methods(http.MethodGet)
+ r.HandleFunc("/api/v1/acls/policy_types", logic.SecurityCheck(true, http.HandlerFunc(aclPolicyTypes))).
+ Methods(http.MethodGet)
+ r.HandleFunc("/api/v1/acls", logic.SecurityCheck(true, http.HandlerFunc(createAcl))).
+ Methods(http.MethodPost)
+ r.HandleFunc("/api/v1/acls", logic.SecurityCheck(true, http.HandlerFunc(updateAcl))).
+ Methods(http.MethodPut)
+ r.HandleFunc("/api/v1/acls", logic.SecurityCheck(true, http.HandlerFunc(deleteAcl))).
+ Methods(http.MethodDelete)
+ r.HandleFunc("/api/v1/acls/debug", logic.SecurityCheck(true, http.HandlerFunc(aclDebug))).
+ Methods(http.MethodGet)
+}
+
+// @Summary List Acl Policy types
+// @Router /api/v1/acls/policy_types [get]
+// @Tags ACL
+// @Accept json
+// @Success 200 {array} models.SuccessResponse
+// @Failure 500 {object} models.ErrorResponse
+func aclPolicyTypes(w http.ResponseWriter, r *http.Request) {
+ resp := models.AclPolicyTypes{
+ RuleTypes: []models.AclPolicyType{
+ models.DevicePolicy,
+ models.UserPolicy,
+ },
+ SrcGroupTypes: []models.AclGroupType{
+ models.UserAclID,
+ models.UserGroupAclID,
+ models.DeviceAclID,
+ },
+ DstGroupTypes: []models.AclGroupType{
+ models.DeviceAclID,
+ // models.NetmakerIPAclID,
+ // models.NetmakerSubNetRangeAClID,
+ },
+ }
+ logic.ReturnSuccessResponseWithJson(w, r, resp, "fetched acls types")
+}
+
+func aclDebug(w http.ResponseWriter, r *http.Request) {
+ nodeID, _ := url.QueryUnescape(r.URL.Query().Get("node"))
+ peerID, _ := url.QueryUnescape(r.URL.Query().Get("peer"))
+ node, err := logic.GetNodeByID(nodeID)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ peer, err := logic.GetNodeByID(peerID)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ allowed := logic.IsNodeAllowedToCommunicate(node, peer)
+ logic.ReturnSuccessResponseWithJson(w, r, allowed, "fetched all acls in the network ")
+}
+
+// @Summary List Acls in a network
+// @Router /api/v1/acls [get]
+// @Tags ACL
+// @Accept json
+// @Success 200 {array} models.SuccessResponse
+// @Failure 500 {object} models.ErrorResponse
+func getAcls(w http.ResponseWriter, r *http.Request) {
+ netID := r.URL.Query().Get("network")
+ if netID == "" {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("network id param is missing"), "badrequest"))
+ return
+ }
+ // check if network exists
+ _, err := logic.GetNetwork(netID)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ acls, err := logic.ListAcls(models.NetworkID(netID))
+ if err != nil {
+ logger.Log(0, r.Header.Get("user"), "failed to get all network acl entries: ", err.Error())
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+ return
+ }
+ logic.SortAclEntrys(acls[:])
+ logic.ReturnSuccessResponseWithJson(w, r, acls, "fetched all acls in the network "+netID)
+}
+
+// @Summary Create Acl
+// @Router /api/v1/acls [post]
+// @Tags ACL
+// @Accept json
+// @Success 200 {array} models.SuccessResponse
+// @Failure 500 {object} models.ErrorResponse
+func createAcl(w http.ResponseWriter, r *http.Request) {
+ var req models.Acl
+ err := json.NewDecoder(r.Body).Decode(&req)
+ if err != nil {
+ logger.Log(0, "error decoding request body: ",
+ err.Error())
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ user, err := logic.GetUser(r.Header.Get("user"))
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ err = logic.ValidateCreateAclReq(req)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+
+ acl := req
+ acl.ID = uuid.New().String()
+ acl.CreatedBy = user.UserName
+ acl.CreatedAt = time.Now().UTC()
+ acl.Default = false
+ if acl.RuleType == models.DevicePolicy {
+ acl.AllowedDirection = models.TrafficDirectionBi
+ } else {
+ acl.AllowedDirection = models.TrafficDirectionUni
+ }
+ // validate create acl policy
+ if !logic.IsAclPolicyValid(acl) {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("invalid policy"), "badrequest"))
+ return
+ }
+ err = logic.InsertAcl(acl)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+ return
+ }
+ acl, err = logic.GetAcl(acl.ID)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+ return
+ }
+ go mq.PublishPeerUpdate(false)
+ logic.ReturnSuccessResponseWithJson(w, r, acl, "created acl successfully")
+}
+
+// @Summary Update Acl
+// @Router /api/v1/acls [put]
+// @Tags ACL
+// @Accept json
+// @Success 200 {array} models.SuccessResponse
+// @Failure 500 {object} models.ErrorResponse
+func updateAcl(w http.ResponseWriter, r *http.Request) {
+ var updateAcl models.UpdateAclRequest
+ err := json.NewDecoder(r.Body).Decode(&updateAcl)
+ if err != nil {
+ logger.Log(0, "error decoding request body: ",
+ err.Error())
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+
+ acl, err := logic.GetAcl(updateAcl.ID)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ if !logic.IsAclPolicyValid(updateAcl.Acl) {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("invalid policy"), "badrequest"))
+ return
+ }
+ if updateAcl.Acl.NetworkID != acl.NetworkID {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("invalid policy, network id mismatch"), "badrequest"))
+ return
+ }
+ if !acl.Default && updateAcl.NewName != "" {
+ //check if policy exists with same name
+ updateAcl.Acl.Name = updateAcl.NewName
+ }
+ err = logic.UpdateAcl(updateAcl.Acl, acl)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ go mq.PublishPeerUpdate(false)
+ logic.ReturnSuccessResponse(w, r, "updated acl "+acl.Name)
+}
+
+// @Summary Delete Acl
+// @Router /api/v1/acls [delete]
+// @Tags ACL
+// @Accept json
+// @Success 200 {array} models.SuccessResponse
+// @Failure 500 {object} models.ErrorResponse
+func deleteAcl(w http.ResponseWriter, r *http.Request) {
+ aclID, _ := url.QueryUnescape(r.URL.Query().Get("acl_id"))
+ if aclID == "" {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("acl id is required"), "badrequest"))
+ return
+ }
+ acl, err := logic.GetAcl(aclID)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ if acl.Default {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("cannot delete default policy"), "badrequest"))
+ return
+ }
+ err = logic.DeleteAcl(acl)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r,
+ logic.FormatError(errors.New("cannot delete default policy"), "internal"))
+ return
+ }
+ go mq.PublishPeerUpdate(false)
+ logic.ReturnSuccessResponse(w, r, "deleted acl "+acl.Name)
+}
diff --git a/controllers/controller.go b/controllers/controller.go
index 75423b6e1..317536dd8 100644
--- a/controllers/controller.go
+++ b/controllers/controller.go
@@ -34,6 +34,8 @@ var HttpHandlers = []interface{}{
loggerHandlers,
hostHandlers,
enrollmentKeyHandlers,
+ tagHandlers,
+ aclHandlers,
legacyHandlers,
}
diff --git a/controllers/dns.go b/controllers/dns.go
index b1fc71fc3..cc1d70abf 100644
--- a/controllers/dns.go
+++ b/controllers/dns.go
@@ -11,6 +11,7 @@ import (
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
+ "github.com/gravitl/netmaker/mq"
"github.com/gravitl/netmaker/servercfg"
)
@@ -24,6 +25,8 @@ func dnsHandlers(r *mux.Router) {
Methods(http.MethodGet)
r.HandleFunc("/api/dns/adm/{network}", logic.SecurityCheck(true, http.HandlerFunc(getDNS))).
Methods(http.MethodGet)
+ r.HandleFunc("/api/dns/adm/{network}/sync", logic.SecurityCheck(true, http.HandlerFunc(syncDNS))).
+ Methods(http.MethodPost)
r.HandleFunc("/api/dns/{network}", logic.SecurityCheck(true, http.HandlerFunc(createDNS))).
Methods(http.MethodPost)
r.HandleFunc("/api/dns/adm/pushdns", logic.SecurityCheck(true, http.HandlerFunc(pushDNS))).
@@ -147,6 +150,7 @@ func createDNS(w http.ResponseWriter, r *http.Request) {
var entry models.DNSEntry
var params = mux.Vars(r)
+ netID := params["network"]
_ = json.NewDecoder(r.Body).Decode(&entry)
entry.Network = params["network"]
@@ -176,6 +180,10 @@ func createDNS(w http.ResponseWriter, r *http.Request) {
}
}
+ if servercfg.GetManageDNS() {
+ mq.SendDNSSyncByNetwork(netID)
+ }
+
logger.Log(1, "new DNS record added:", entry.Name)
logger.Log(2, r.Header.Get("user"),
fmt.Sprintf("DNS entry is set: %+v", entry))
@@ -197,6 +205,7 @@ func deleteDNS(w http.ResponseWriter, r *http.Request) {
// get params
var params = mux.Vars(r)
+ netID := params["network"]
entrytext := params["domain"] + "." + params["network"]
err := logic.DeleteDNS(params["domain"], params["network"])
@@ -216,6 +225,10 @@ func deleteDNS(w http.ResponseWriter, r *http.Request) {
}
}
+ if servercfg.GetManageDNS() {
+ mq.SendDNSSyncByNetwork(netID)
+ }
+
json.NewEncoder(w).Encode(entrytext + " deleted.")
}
@@ -264,3 +277,38 @@ func pushDNS(w http.ResponseWriter, r *http.Request) {
logger.Log(1, r.Header.Get("user"), "pushed DNS updates to nameserver")
json.NewEncoder(w).Encode("DNS Pushed to CoreDNS")
}
+
+// @Summary Sync DNS entries for a given network
+// @Router /api/dns/adm/{network}/sync [post]
+// @Tags DNS
+// @Accept json
+// @Success 200 {string} string "DNS Sync completed successfully"
+// @Failure 400 {object} models.ErrorResponse
+// @Failure 500 {object} models.ErrorResponse
+func syncDNS(w http.ResponseWriter, r *http.Request) {
+ // Set header
+ w.Header().Set("Content-Type", "application/json")
+ if !servercfg.GetManageDNS() {
+ logic.ReturnErrorResponse(
+ w,
+ r,
+ logic.FormatError(errors.New("manage DNS is set to false"), "badrequest"),
+ )
+ return
+ }
+ var params = mux.Vars(r)
+ netID := params["network"]
+ k, err := logic.GetDNS(netID)
+ if err == nil && len(k) > 0 {
+ err = mq.PushSyncDNS(k)
+ }
+
+ if err != nil {
+ logger.Log(0, r.Header.Get("user"),
+ fmt.Sprintf("Failed to Sync DNS entries to network %s: %v", netID, err))
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+ return
+ }
+ logger.Log(1, r.Header.Get("user"), "DNS Sync complelted successfully")
+ json.NewEncoder(w).Encode("DNS Sync completed successfully")
+}
diff --git a/controllers/enrollmentkeys.go b/controllers/enrollmentkeys.go
index dc6669bd6..1ab9498ea 100644
--- a/controllers/enrollmentkeys.go
+++ b/controllers/enrollmentkeys.go
@@ -72,7 +72,7 @@ func getEnrollmentKeys(w http.ResponseWriter, r *http.Request) {
func deleteEnrollmentKey(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
keyID := params["keyID"]
- err := logic.DeleteEnrollmentKey(keyID)
+ err := logic.DeleteEnrollmentKey(keyID, false)
if err != nil {
logger.Log(0, r.Header.Get("user"), "failed to remove enrollment key: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
@@ -156,8 +156,10 @@ func createEnrollmentKey(w http.ResponseWriter, r *http.Request) {
newTime,
enrollmentKeyBody.Networks,
enrollmentKeyBody.Tags,
+ enrollmentKeyBody.Groups,
enrollmentKeyBody.Unlimited,
relayId,
+ false,
)
if err != nil {
logger.Log(0, r.Header.Get("user"), "failed to create enrollment key:", err.Error())
@@ -206,7 +208,7 @@ func updateEnrollmentKey(w http.ResponseWriter, r *http.Request) {
}
}
- newEnrollmentKey, err := logic.UpdateEnrollmentKey(keyId, relayId)
+ newEnrollmentKey, err := logic.UpdateEnrollmentKey(keyId, relayId, enrollmentKeyBody.Groups)
if err != nil {
slog.Error("failed to update enrollment key", "error", err)
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
@@ -307,6 +309,7 @@ func handleHostRegister(w http.ResponseWriter, r *http.Request) {
return
}
}
+
if err = logic.CreateHost(&newHost); err != nil {
logger.Log(
0,
@@ -355,5 +358,5 @@ func handleHostRegister(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(&response)
// notify host of changes, peer and node updates
- go auth.CheckNetRegAndHostUpdate(enrollmentKey.Networks, &newHost, enrollmentKey.Relay)
+ go auth.CheckNetRegAndHostUpdate(enrollmentKey.Networks, &newHost, enrollmentKey.Relay, enrollmentKey.Groups)
}
diff --git a/controllers/ext_client.go b/controllers/ext_client.go
index b98256927..aabb5103e 100644
--- a/controllers/ext_client.go
+++ b/controllers/ext_client.go
@@ -244,6 +244,9 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
if network.DefaultKeepalive != 0 {
keepalive = "PersistentKeepalive = " + strconv.Itoa(int(network.DefaultKeepalive))
}
+ if gwnode.IngressPersistentKeepalive != 0 {
+ keepalive = "PersistentKeepalive = " + strconv.Itoa(int(gwnode.IngressPersistentKeepalive))
+ }
gwendpoint := ""
if preferredIp == "" {
@@ -284,11 +287,30 @@ func getExtClientConf(w http.ResponseWriter, r *http.Request) {
} else if gwnode.IngressDNS != "" {
defaultDNS = "DNS = " + gwnode.IngressDNS
}
+ // if servercfg.GetManageDNS() {
+ // if gwnode.Address6.IP != nil {
+ // if defaultDNS == "" {
+ // defaultDNS = "DNS = " + gwnode.Address6.IP.String()
+ // } else {
+ // defaultDNS = defaultDNS + ", " + gwnode.Address6.IP.String()
+ // }
+ // }
+ // if gwnode.Address.IP != nil {
+ // if defaultDNS == "" {
+ // defaultDNS = "DNS = " + gwnode.Address.IP.String()
+ // } else {
+ // defaultDNS = defaultDNS + ", " + gwnode.Address.IP.String()
+ // }
+ // }
+ // }
defaultMTU := 1420
if host.MTU != 0 {
defaultMTU = host.MTU
}
+ if gwnode.IngressMTU != 0 {
+ defaultMTU = int(gwnode.IngressMTU)
+ }
postUp := strings.Builder{}
if client.PostUp != "" && params["type"] != "qr" {
@@ -446,13 +468,14 @@ func createExtClient(w http.ResponseWriter, r *http.Request) {
extclient.OwnerID = userName
extclient.RemoteAccessClientID = customExtClient.RemoteAccessClientID
extclient.IngressGatewayID = nodeid
-
+ extclient.Network = node.Network
+ extclient.Tags = make(map[models.TagID]struct{})
+ extclient.Tags[models.TagID(fmt.Sprintf("%s.%s", extclient.Network,
+ models.RemoteAccessTagName))] = struct{}{}
// set extclient dns to ingressdns if extclient dns is not explicitly set
if (extclient.DNS == "") && (node.IngressDNS != "") {
extclient.DNS = node.IngressDNS
}
-
- extclient.Network = node.Network
host, err := logic.GetHost(node.HostID.String())
if err != nil {
logger.Log(0, r.Header.Get("user"),
@@ -531,6 +554,7 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
var update models.CustomExtClient
//var oldExtClient models.ExtClient
var sendPeerUpdate bool
+ var replacePeers bool
err := json.NewDecoder(r.Body).Decode(&update)
if err != nil {
logger.Log(0, r.Header.Get("user"), "error decoding request body: ",
@@ -588,6 +612,11 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
if update.Enabled != oldExtClient.Enabled {
sendPeerUpdate = true
}
+ if update.PublicKey != oldExtClient.PublicKey {
+ //remove old peer entry
+ sendPeerUpdate = true
+ replacePeers = true
+ }
newclient := logic.UpdateExtClient(&oldExtClient, &update)
if err := logic.DeleteExtClient(oldExtClient.Network, oldExtClient.ClientID); err != nil {
slog.Error(
@@ -627,6 +656,11 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) {
if changedID && servercfg.IsDNSMode() {
logic.SetDNS()
}
+ if replacePeers {
+ if err := mq.PublishDeletedClientPeerUpdate(&oldExtClient); err != nil {
+ slog.Error("error deleting old ext peers", "error", err.Error())
+ }
+ }
if sendPeerUpdate { // need to send a peer update to the ingress node as enablement of one of it's clients has changed
ingressNode, err := logic.GetNodeByID(newclient.IngressGatewayID)
if err == nil {
@@ -735,7 +769,7 @@ func validateCustomExtClient(customExtClient *models.CustomExtClient, checkID bo
//validate clientid
if customExtClient.ClientID != "" {
if err := isValid(customExtClient.ClientID, checkID); err != nil {
- return fmt.Errorf("client validatation: %v", err)
+ return fmt.Errorf("client validation: %v", err)
}
}
//extclient.ClientID = customExtClient.ClientID
diff --git a/controllers/hosts.go b/controllers/hosts.go
index 349894331..a1015cf95 100644
--- a/controllers/hosts.go
+++ b/controllers/hosts.go
@@ -167,6 +167,8 @@ func pull(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
+
+ sendPeerUpdate := false
for _, nodeID := range host.Nodes {
node, err := logic.GetNodeByID(nodeID)
if err != nil {
@@ -174,7 +176,13 @@ func pull(w http.ResponseWriter, r *http.Request) {
continue
}
if node.FailedOverBy != uuid.Nil {
- go logic.ResetFailedOverPeer(&node)
+ logic.ResetFailedOverPeer(&node)
+ sendPeerUpdate = true
+ }
+ }
+ if sendPeerUpdate {
+ if err := mq.PublishPeerUpdate(true); err != nil {
+ logger.Log(0, "fail to publish peer update: ", err.Error())
}
}
allNodes, err := logic.GetAllNodes()
@@ -245,19 +253,6 @@ func updateHost(w http.ResponseWriter, r *http.Request) {
newHost := newHostData.ConvertAPIHostToNMHost(currHost)
- if newHost.Name != currHost.Name {
- // update any rag role ids
- for _, nodeID := range newHost.Nodes {
- node, err := logic.GetNodeByID(nodeID)
- if err == nil && node.IsIngressGateway {
- role, err := logic.GetRole(models.GetRAGRoleID(node.Network, currHost.ID.String()))
- if err == nil {
- role.UiName = models.GetRAGRoleName(node.Network, newHost.Name)
- logic.UpdateRole(role)
- }
- }
- }
- }
logic.UpdateHost(newHost, currHost) // update the in memory struct values
if err = logic.UpsertHost(newHost); err != nil {
logger.Log(0, r.Header.Get("user"), "failed to update a host:", err.Error())
diff --git a/controllers/middleware.go b/controllers/middleware.go
index fb2bef683..98cb85716 100644
--- a/controllers/middleware.go
+++ b/controllers/middleware.go
@@ -27,13 +27,20 @@ func userMiddleWare(handler http.Handler) http.Handler {
r.Header.Set("TARGET_RSRC", "")
r.Header.Set("RSRC_TYPE", "")
r.Header.Set("TARGET_RSRC_ID", "")
+ r.Header.Set("RAC", "")
r.Header.Set("NET_ID", params["network"])
+ if r.URL.Query().Get("network") != "" {
+ r.Header.Set("NET_ID", r.URL.Query().Get("network"))
+ }
if strings.Contains(route, "hosts") || strings.Contains(route, "nodes") {
r.Header.Set("TARGET_RSRC", models.HostRsrc.String())
}
if strings.Contains(route, "dns") {
r.Header.Set("TARGET_RSRC", models.DnsRsrc.String())
}
+ if strings.Contains(route, "rac") {
+ r.Header.Set("RAC", "true")
+ }
if strings.Contains(route, "users") {
r.Header.Set("TARGET_RSRC", models.UserRsrc.String())
}
@@ -53,6 +60,9 @@ func userMiddleWare(handler http.Handler) http.Handler {
if strings.Contains(route, "acls") {
r.Header.Set("TARGET_RSRC", models.AclRsrc.String())
}
+ if strings.Contains(route, "tags") {
+ r.Header.Set("TARGET_RSRC", models.TagRsrc.String())
+ }
if strings.Contains(route, "extclients") {
r.Header.Set("TARGET_RSRC", models.ExtClientsRsrc.String())
}
@@ -101,7 +111,6 @@ func userMiddleWare(handler http.Handler) http.Handler {
r.Header.Get("TARGET_RSRC") == models.UserRsrc.String()) {
r.Header.Set("IS_GLOBAL_ACCESS", "yes")
}
-
r.Header.Set("RSRC_TYPE", r.Header.Get("TARGET_RSRC"))
handler.ServeHTTP(w, r)
})
diff --git a/controllers/network.go b/controllers/network.go
index acb479ec9..86a6e3403 100644
--- a/controllers/network.go
+++ b/controllers/network.go
@@ -24,6 +24,8 @@ import (
func networkHandlers(r *mux.Router) {
r.HandleFunc("/api/networks", logic.SecurityCheck(true, http.HandlerFunc(getNetworks))).
Methods(http.MethodGet)
+ r.HandleFunc("/api/v1/networks/stats", logic.SecurityCheck(true, http.HandlerFunc(getNetworksStats))).
+ Methods(http.MethodGet)
r.HandleFunc("/api/networks", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceNetworks, http.HandlerFunc(createNetwork)))).
Methods(http.MethodPost)
r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(getNetwork))).
@@ -74,6 +76,48 @@ func getNetworks(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(allnetworks)
}
+// @Summary Lists all networks with stats
+// @Router /api/v1/networks/stats [get]
+// @Tags Networks
+// @Security oauth
+// @Produce json
+// @Success 200 {object} models.SuccessResponse
+// @Failure 500 {object} models.ErrorResponse
+func getNetworksStats(w http.ResponseWriter, r *http.Request) {
+
+ var err error
+ allnetworks, err := logic.GetNetworks()
+ if err != nil && !database.IsEmptyRecord(err) {
+ slog.Error("failed to fetch networks", "error", err.Error())
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+ return
+ }
+ if r.Header.Get("ismaster") != "yes" {
+ username := r.Header.Get("user")
+ user, err := logic.GetUser(username)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+ return
+ }
+ allnetworks = logic.FilterNetworksByRole(allnetworks, *user)
+ }
+ allNodes, err := logic.GetAllNodes()
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+ return
+ }
+ netstats := []models.NetworkStatResp{}
+ logic.SortNetworks(allnetworks[:])
+ for _, network := range allnetworks {
+ netstats = append(netstats, models.NetworkStatResp{
+ Network: network,
+ Hosts: len(logic.GetNetworkNodesMemory(allNodes, network.NetID)),
+ })
+ }
+ logger.Log(2, r.Header.Get("user"), "fetched networks.")
+ logic.ReturnSuccessResponseWithJson(w, r, netstats, "fetched networks with stats")
+}
+
// @Summary Get a network
// @Router /api/networks/{networkname} [get]
// @Tags Networks
@@ -412,6 +456,7 @@ func deleteNetwork(w http.ResponseWriter, r *http.Request) {
return
}
go logic.DeleteNetworkRoles(network)
+ go logic.DeleteDefaultNetworkPolicies(models.NetworkID(network))
//delete network from allocated ip map
go logic.RemoveNetworkFromAllocatedIpMap(network)
@@ -487,7 +532,8 @@ func createNetwork(w http.ResponseWriter, r *http.Request) {
return
}
logic.CreateDefaultNetworkRolesAndGroups(models.NetworkID(network.NetID))
-
+ logic.CreateDefaultAclNetworkPolicies(models.NetworkID(network.NetID))
+ logic.CreateDefaultTags(models.NetworkID(network.NetID))
//add new network to allocated ip map
go logic.AddNetworkToAllocatedIpMap(network.NetID)
diff --git a/controllers/node.go b/controllers/node.go
index fd6f5d902..d7f2e2569 100644
--- a/controllers/node.go
+++ b/controllers/node.go
@@ -326,6 +326,7 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) {
if len(filteredNodes) > 0 {
nodes = filteredNodes
}
+ nodes = logic.AddStaticNodestoList(nodes)
// returns all the nodes in JSON/API format
apiNodes := logic.GetAllNodesAPI(nodes[:])
@@ -363,7 +364,9 @@ func getAllNodes(w http.ResponseWriter, r *http.Request) {
if !userPlatformRole.FullAccess {
nodes = logic.GetFilteredNodesByUserAccess(*user, nodes)
}
+
}
+ nodes = logic.AddStaticNodestoList(nodes)
// return all the nodes in JSON/API format
apiNodes := logic.GetAllNodesAPI(nodes[:])
logger.Log(3, r.Header.Get("user"), "fetched all nodes they have access to")
@@ -587,6 +590,7 @@ func createIngressGateway(w http.ResponseWriter, r *http.Request) {
if err := mq.NodeUpdate(&node); err != nil {
slog.Error("error publishing node update to node", "node", node.ID, "error", err)
}
+ mq.PublishPeerUpdate(false)
}()
}
@@ -631,6 +635,7 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) {
if err := mq.PublishSingleHostPeerUpdate(host, allNodes, nil, removedClients[:], false, nil); err != nil {
slog.Error("publishSingleHostUpdate", "host", host.Name, "error", err)
}
+ mq.PublishPeerUpdate(false)
if err := mq.NodeUpdate(&node); err != nil {
slog.Error(
"error publishing node update to node",
@@ -746,6 +751,7 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
logger.Log(0, "error during node ACL update for node", newNode.ID.String())
}
}
+ mq.PublishPeerUpdate(false)
if servercfg.IsDNSMode() {
logic.SetDNS()
}
diff --git a/controllers/regex.go b/controllers/regex.go
index 2c1f0c51e..488e7dbc6 100644
--- a/controllers/regex.go
+++ b/controllers/regex.go
@@ -6,10 +6,10 @@ import (
)
var (
- errInvalidExtClientPubKey = errors.New("incorrect ext client public key")
- errInvalidExtClientID = errors.New("ext client ID must be alphanumderic and/or dashes and less that 15 chars")
- errInvalidExtClientExtraIP = errors.New("ext client extra ip must be a valid cidr")
- errInvalidExtClientDNS = errors.New("ext client dns must be a valid ip address")
+ errInvalidExtClientPubKey = errors.New("incorrect client public key")
+ errInvalidExtClientID = errors.New("node name must be alphanumderic and/or dashes and less that 15 chars")
+ errInvalidExtClientExtraIP = errors.New("client extra ip must be a valid cidr")
+ errInvalidExtClientDNS = errors.New("client dns must be a valid ip address")
errDuplicateExtClientName = errors.New("duplicate client name")
)
diff --git a/controllers/server.go b/controllers/server.go
index 84f732b95..10f548280 100644
--- a/controllers/server.go
+++ b/controllers/server.go
@@ -3,6 +3,7 @@ package controller
import (
"encoding/json"
"net/http"
+ "os"
"strings"
"syscall"
"time"
@@ -17,6 +18,8 @@ import (
"github.com/gravitl/netmaker/servercfg"
)
+var cpuProfileLog *os.File
+
func serverHandlers(r *mux.Router) {
// r.HandleFunc("/api/server/addnetwork/{network}", securityCheckServer(true, http.HandlerFunc(addNetwork))).Methods(http.MethodPost)
r.HandleFunc(
@@ -43,6 +46,21 @@ func serverHandlers(r *mux.Router) {
r.HandleFunc("/api/server/status", getStatus).Methods(http.MethodGet)
r.HandleFunc("/api/server/usage", logic.SecurityCheck(false, http.HandlerFunc(getUsage))).
Methods(http.MethodGet)
+ r.HandleFunc("/api/server/cpu_profile", logic.SecurityCheck(false, http.HandlerFunc(cpuProfile))).
+ Methods(http.MethodPost)
+}
+
+func cpuProfile(w http.ResponseWriter, r *http.Request) {
+ start := r.URL.Query().Get("action") == "start"
+ if start {
+ os.Remove("/root/data/cpu.prof")
+ cpuProfileLog = logic.StartCPUProfiling()
+ } else {
+ if cpuProfileLog != nil {
+ logic.StopCPUProfiling(cpuProfileLog)
+ cpuProfileLog = nil
+ }
+ }
}
func getUsage(w http.ResponseWriter, _ *http.Request) {
diff --git a/controllers/tags.go b/controllers/tags.go
new file mode 100644
index 000000000..633dab968
--- /dev/null
+++ b/controllers/tags.go
@@ -0,0 +1,231 @@
+package controller
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/gorilla/mux"
+ "github.com/gravitl/netmaker/logger"
+ "github.com/gravitl/netmaker/logic"
+ "github.com/gravitl/netmaker/models"
+ "github.com/gravitl/netmaker/mq"
+)
+
+func tagHandlers(r *mux.Router) {
+ r.HandleFunc("/api/v1/tags", logic.SecurityCheck(true, http.HandlerFunc(getTags))).
+ Methods(http.MethodGet)
+ r.HandleFunc("/api/v1/tags", logic.SecurityCheck(true, http.HandlerFunc(createTag))).
+ Methods(http.MethodPost)
+ r.HandleFunc("/api/v1/tags", logic.SecurityCheck(true, http.HandlerFunc(updateTag))).
+ Methods(http.MethodPut)
+ r.HandleFunc("/api/v1/tags", logic.SecurityCheck(true, http.HandlerFunc(deleteTag))).
+ Methods(http.MethodDelete)
+
+}
+
+// @Summary List Tags in a network
+// @Router /api/v1/tags [get]
+// @Tags TAG
+// @Accept json
+// @Success 200 {array} models.SuccessResponse
+// @Failure 500 {object} models.ErrorResponse
+func getTags(w http.ResponseWriter, r *http.Request) {
+ netID, _ := url.QueryUnescape(r.URL.Query().Get("network"))
+ if netID == "" {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("network id param is missing"), "badrequest"))
+ return
+ }
+ // check if network exists
+ _, err := logic.GetNetwork(netID)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ tags, err := logic.ListTagsWithNodes(models.NetworkID(netID))
+ if err != nil {
+ logger.Log(0, r.Header.Get("user"), "failed to get all network tag entries: ", err.Error())
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+ return
+ }
+ logic.SortTagEntrys(tags[:])
+ logic.ReturnSuccessResponseWithJson(w, r, tags, "fetched all tags in the network "+netID)
+}
+
+// @Summary Create Tag
+// @Router /api/v1/tags [post]
+// @Tags TAG
+// @Accept json
+// @Success 200 {array} models.SuccessResponse
+// @Failure 500 {object} models.ErrorResponse
+func createTag(w http.ResponseWriter, r *http.Request) {
+ var req models.CreateTagReq
+ err := json.NewDecoder(r.Body).Decode(&req)
+ if err != nil {
+ logger.Log(0, "error decoding request body: ",
+ err.Error())
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ user, err := logic.GetUser(r.Header.Get("user"))
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ // check if tag network exists
+ _, err = logic.GetNetwork(req.Network.String())
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("failed to get network details for "+req.Network.String()), "badrequest"))
+ return
+ }
+ // check if tag exists
+ tag := models.Tag{
+ ID: models.TagID(fmt.Sprintf("%s.%s", req.Network, req.TagName)),
+ TagName: req.TagName,
+ Network: req.Network,
+ CreatedBy: user.UserName,
+ CreatedAt: time.Now(),
+ }
+ _, err = logic.GetTag(tag.ID)
+ if err == nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("tag with id %s exists already", tag.TagName), "badrequest"))
+ return
+ }
+ // validate name
+ err = logic.CheckIDSyntax(tag.TagName)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ err = logic.InsertTag(tag)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ go func() {
+ for _, node := range req.TaggedNodes {
+ if node.IsStatic {
+ extclient, err := logic.GetExtClient(node.StaticNode.ClientID, node.StaticNode.Network)
+ if err == nil && extclient.RemoteAccessClientID == "" {
+ if extclient.Tags == nil {
+ extclient.Tags = make(map[models.TagID]struct{})
+ }
+ extclient.Tags[tag.ID] = struct{}{}
+ logic.SaveExtClient(&extclient)
+ }
+ continue
+ }
+ node, err := logic.GetNodeByID(node.ID)
+ if err != nil {
+ continue
+ }
+ if node.Tags == nil {
+ node.Tags = make(map[models.TagID]struct{})
+ }
+ node.Tags[tag.ID] = struct{}{}
+ logic.UpsertNode(&node)
+ }
+ }()
+ go mq.PublishPeerUpdate(false)
+
+ var res models.TagListRespNodes = models.TagListRespNodes{
+ Tag: tag,
+ UsedByCnt: len(req.TaggedNodes),
+ TaggedNodes: req.TaggedNodes,
+ }
+
+ logic.ReturnSuccessResponseWithJson(w, r, res, "created tag successfully")
+}
+
+// @Summary Update Tag
+// @Router /api/v1/tags [put]
+// @Tags TAG
+// @Accept json
+// @Success 200 {array} models.SuccessResponse
+// @Failure 500 {object} models.ErrorResponse
+func updateTag(w http.ResponseWriter, r *http.Request) {
+ var updateTag models.UpdateTagReq
+ err := json.NewDecoder(r.Body).Decode(&updateTag)
+ if err != nil {
+ logger.Log(0, "error decoding request body: ",
+ err.Error())
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+
+ tag, err := logic.GetTag(updateTag.ID)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ updateTag.NewName = strings.TrimSpace(updateTag.NewName)
+ var newID models.TagID
+ if updateTag.NewName != "" {
+ // validate name
+ err = logic.CheckIDSyntax(updateTag.NewName)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ newID = models.TagID(fmt.Sprintf("%s.%s", tag.Network, updateTag.NewName))
+ tag.ID = newID
+ tag.TagName = updateTag.NewName
+ err = logic.InsertTag(tag)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ // delete old Tag entry
+ logic.DeleteTag(updateTag.ID, false)
+ }
+ go func() {
+ logic.UpdateTag(updateTag, newID)
+ if updateTag.NewName != "" {
+ logic.UpdateDeviceTag(updateTag.ID, newID, tag.Network)
+ }
+ mq.PublishPeerUpdate(false)
+ }()
+
+ var res models.TagListRespNodes = models.TagListRespNodes{
+ Tag: tag,
+ UsedByCnt: len(updateTag.TaggedNodes),
+ TaggedNodes: updateTag.TaggedNodes,
+ }
+
+ logic.ReturnSuccessResponseWithJson(w, r, res, "updated tags")
+}
+
+// @Summary Delete Tag
+// @Router /api/v1/tags [delete]
+// @Tags TAG
+// @Accept json
+// @Success 200 {array} models.SuccessResponse
+// @Failure 500 {object} models.ErrorResponse
+func deleteTag(w http.ResponseWriter, r *http.Request) {
+ tagID, _ := url.QueryUnescape(r.URL.Query().Get("tag_id"))
+ if tagID == "" {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("role is required"), "badrequest"))
+ return
+ }
+ tag, err := logic.GetTag(models.TagID(tagID))
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+ return
+ }
+ err = logic.DeleteTag(models.TagID(tagID), true)
+ if err != nil {
+ logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+ return
+ }
+
+ go func() {
+ logic.RemoveDeviceTagFromAclPolicies(tag.ID, tag.Network)
+ logic.RemoveTagFromEnrollmentKeys(tag.ID)
+ mq.PublishPeerUpdate(false)
+ }()
+ logic.ReturnSuccessResponse(w, r, "deleted tag "+tagID)
+}
diff --git a/controllers/user.go b/controllers/user.go
index c48a1b1da..8f3375cc8 100644
--- a/controllers/user.go
+++ b/controllers/user.go
@@ -451,6 +451,7 @@ func createUser(w http.ResponseWriter, r *http.Request) {
}
logic.DeleteUserInvite(user.UserName)
logic.DeletePendingUser(user.UserName)
+ go mq.PublishPeerUpdate(false)
slog.Info("user was created", "username", user.UserName)
json.NewEncoder(w).Encode(logic.ToReturnUser(user))
}
@@ -590,6 +591,7 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
+ go mq.PublishPeerUpdate(false)
logger.Log(1, username, "was updated")
json.NewEncoder(w).Encode(logic.ToReturnUser(*user))
}
@@ -692,6 +694,7 @@ func deleteUser(w http.ResponseWriter, r *http.Request) {
}
}
}
+ mq.PublishPeerUpdate(false)
if servercfg.IsDNSMode() {
logic.SetDNS()
}
diff --git a/database/database.go b/database/database.go
index f8508b3f0..6590e0063 100644
--- a/database/database.go
+++ b/database/database.go
@@ -47,6 +47,8 @@ const (
GENERATED_TABLE_NAME = "generated"
// NODE_ACLS_TABLE_NAME - stores the node ACL rules
NODE_ACLS_TABLE_NAME = "nodeacls"
+ // ACLS_TABLE_NAME - table for acls v2
+ ACLS_TABLE_NAME = "acls"
// SSO_STATE_CACHE - holds sso session information for OAuth2 sign-ins
SSO_STATE_CACHE = "ssostatecache"
// METRICS_TABLE_NAME - stores network metrics
@@ -67,6 +69,8 @@ const (
PENDING_USERS_TABLE_NAME = "pending_users"
// USER_INVITES - table for user invites
USER_INVITES_TABLE_NAME = "user_invites"
+ // TAG_TABLE_NAME - table for tags
+ TAG_TABLE_NAME = "tags"
// == ERROR CONSTS ==
// NO_RECORD - no singular result found
NO_RECORD = "no result found"
@@ -152,6 +156,8 @@ func createTables() {
CreateTable(PENDING_USERS_TABLE_NAME)
CreateTable(USER_PERMISSIONS_TABLE_NAME)
CreateTable(USER_INVITES_TABLE_NAME)
+ CreateTable(TAG_TABLE_NAME)
+ CreateTable(ACLS_TABLE_NAME)
}
func CreateTable(tableName string) error {
diff --git a/docker/Caddyfile b/docker/Caddyfile
index 1226b72b3..9f5b35652 100644
--- a/docker/Caddyfile
+++ b/docker/Caddyfile
@@ -25,6 +25,10 @@ https://api.{$NM_DOMAIN} {
}
# MQ
-wss://broker.{$NM_DOMAIN} {
- reverse_proxy ws://mq:8883 # For EMQX websockets use `reverse_proxy ws://mq:8083`
+broker.{$NM_DOMAIN} {
+ @ws {
+ header Connection *Upgrade*
+ header Upgrade websocket
+ }
+ reverse_proxy @ws mq:8883 # For EMQX websockets use `reverse_proxy @ws mq:8083`
}
diff --git a/docker/Caddyfile-pro b/docker/Caddyfile-pro
index e986a8be5..226f61709 100644
--- a/docker/Caddyfile-pro
+++ b/docker/Caddyfile-pro
@@ -40,6 +40,10 @@ https://api.{$NM_DOMAIN} {
}
# MQ
-wss://broker.{$NM_DOMAIN} {
- reverse_proxy ws://mq:8883
+broker.{$NM_DOMAIN} {
+ @ws {
+ header Connection *Upgrade*
+ header Upgrade websocket
+ }
+ reverse_proxy @ws mq:8883
}
diff --git a/docker/Dockerfile-go-builder b/docker/Dockerfile-go-builder
index 0a19535fe..982f1e6ff 100644
--- a/docker/Dockerfile-go-builder
+++ b/docker/Dockerfile-go-builder
@@ -1,4 +1,4 @@
-FROM golang:1.20.13-alpine3.19
+FROM golang:1.23.0-alpine3.20
ARG version
RUN apk add build-base
WORKDIR /app
diff --git a/docs/Authentication.md b/docs/Authentication.md
index b1a7a4445..bd084a97c 100644
--- a/docs/Authentication.md
+++ b/docs/Authentication.md
@@ -7,4 +7,4 @@ Call the api/users/adm/authenticate endpoint (see documentation below for detail
Note: While a MasterKey exists (configurable via env var or config file), it should be considered a backup option, used only when server access is lost. By default, this key is "secret key," but it's crucial to change this and keep it secure in your instance.
-For more information on configuration and security best practices, refer to the [Netmaker documentation](https://docs.netmaker.org/index.html).
+For more information on configuration and security best practices, refer to the [Netmaker documentation](https://docs.netmaker.io/).
diff --git a/go.mod b/go.mod
index b5bcfe909..b337d784a 100644
--- a/go.mod
+++ b/go.mod
@@ -1,26 +1,26 @@
module github.com/gravitl/netmaker
-go 1.19
+go 1.23
require (
github.com/eclipse/paho.mqtt.golang v1.4.3
- github.com/go-playground/validator/v10 v10.22.0
+ github.com/go-playground/validator/v10 v10.22.1
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.22
+ github.com/mattn/go-sqlite3 v1.14.24
github.com/rqlite/gorqlite v0.0.0-20240122221808-a8a425b1a6aa
- github.com/seancfoley/ipaddress-go v1.6.0
+ github.com/seancfoley/ipaddress-go v1.7.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.9.0
github.com/txn2/txeh v1.5.5
- golang.org/x/crypto v0.23.0
- golang.org/x/net v0.23.0 // indirect
- golang.org/x/oauth2 v0.21.0
- golang.org/x/sys v0.21.0 // indirect
- golang.org/x/text v0.16.0 // indirect
+ golang.org/x/crypto v0.28.0
+ golang.org/x/net v0.27.0 // indirect
+ golang.org/x/oauth2 v0.23.0
+ golang.org/x/sys v0.26.0 // indirect
+ golang.org/x/text v0.19.0 // indirect
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20221104135756-97bc4ad4a1cb
gopkg.in/yaml.v3 v3.0.1
)
@@ -28,7 +28,7 @@ require (
require (
filippo.io/edwards25519 v1.1.0
github.com/c-robinson/iplib v1.0.8
- github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0
+ github.com/posthog/posthog-go v1.2.24
)
require (
@@ -38,6 +38,7 @@ require (
)
require (
+ github.com/goombaio/namegenerator v0.0.0-20181006234301-989e774b106e
github.com/guumaster/tablewriter v0.0.10
github.com/matryer/is v1.4.1
github.com/olekukonko/tablewriter v0.0.5
@@ -53,7 +54,6 @@ require (
github.com/rivo/uniseg v0.2.0 // indirect
github.com/seancfoley/bintree v1.3.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
- github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
)
@@ -66,5 +66,5 @@ require (
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
- golang.org/x/sync v0.7.0 // indirect
+ golang.org/x/sync v0.8.0 // indirect
)
diff --git a/go.sum b/go.sum
index 33d5e376a..b59242e92 100644
--- a/go.sum
+++ b/go.sum
@@ -2,12 +2,10 @@ cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2Qx
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/c-robinson/iplib v1.0.8 h1:exDRViDyL9UBLcfmlxxkY5odWX5092nPsQIykHXhIn4=
github.com/c-robinson/iplib v1.0.8/go.mod h1:i3LuuFL1hRT5gFpBRnEydzw8R6yhGkF4szNDIbF8pgo=
github.com/coreos/go-oidc/v3 v3.9.0 h1:0J/ogVOd4y8P0f0xUh8l9t07xRP/d8tccvjHl2dcsSo=
github.com/coreos/go-oidc/v3 v3.9.0/go.mod h1:rTKz2PYwftcrtoCzV5g5kvfJoWcm0Mk8AF8y1iAQro4=
-github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -21,18 +19,21 @@ github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcP
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
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.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
-github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
+github.com/go-playground/validator/v10 v10.22.1/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/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/goombaio/namegenerator v0.0.0-20181006234301-989e774b106e h1:XmA6L9IPRdUr28a+SK/oMchGgQy159wvzXA5tJ7l+40=
+github.com/goombaio/namegenerator v0.0.0-20181006234301-989e774b106e/go.mod h1:AFIo+02s+12CEg8Gzz9kzhCbmbq6JcKNrhHffCGA9z4=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
@@ -55,26 +56,24 @@ 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.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
-github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
+github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
+github.com/mattn/go-sqlite3 v1.14.24/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=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0 h1:Y2hUrkfuM0on62KZOci/VLijlkdF/yeWU262BQgvcjE=
-github.com/posthog/posthog-go v0.0.0-20211028072449-93c17c49e2b0/go.mod h1:oa2sAs9tGai3VldabTV0eWejt/O4/OOD7azP8GaikqU=
+github.com/posthog/posthog-go v1.2.24 h1:A+iG4saBJemo++VDlcWovbYf8KFFNUfrCoJtsc40RPA=
+github.com/posthog/posthog-go v1.2.24/go.mod h1:uYC2l1Yktc8E+9FAHJ9QZG4vQf/NHJPD800Hsm7DzoM=
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-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=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/seancfoley/bintree v1.3.1 h1:cqmmQK7Jm4aw8gna0bP+huu5leVOgHGSJBEpUx3EXGI=
github.com/seancfoley/bintree v1.3.1/go.mod h1:hIUabL8OFYyFVTQ6azeajbopogQc2l5C/hiXMcemWNU=
-github.com/seancfoley/ipaddress-go v1.6.0 h1:9z7yGmOnV4P2ML/dlR/kCJiv5tp8iHOOetJvxJh/R5w=
-github.com/seancfoley/ipaddress-go v1.6.0/go.mod h1:TQRZgv+9jdvzHmKoPGBMxyiaVmoI0rYpfEk8Q/sL/Iw=
-github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/seancfoley/ipaddress-go v1.7.0 h1:vWp3SR3k+HkV3aKiNO2vEe6xbVxS0x/Ixw6hgyP238s=
+github.com/seancfoley/ipaddress-go v1.7.0/go.mod h1:TQRZgv+9jdvzHmKoPGBMxyiaVmoI0rYpfEk8Q/sL/Iw=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
@@ -87,15 +86,12 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/txn2/txeh v1.5.5 h1:UN4e/lCK5HGw/gGAi2GCVrNKg0GTCUWs7gs5riaZlz4=
github.com/txn2/txeh v1.5.5/go.mod h1:qYzGG9kCzeVEI12geK4IlanHWY8X4uy/I3NcW7mk8g4=
-github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
-github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
-golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
-golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
+golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
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=
@@ -105,15 +101,15 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
-golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
-golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
-golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
-golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
+golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
+golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
+golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
-golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -122,8 +118,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
-golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
+golang.org/x/sys v0.26.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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -135,8 +131,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
-golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
+golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -150,7 +146,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/k8s/client/netclient-daemonset.yaml b/k8s/client/netclient-daemonset.yaml
index f1a3f407d..cc7b744a0 100644
--- a/k8s/client/netclient-daemonset.yaml
+++ b/k8s/client/netclient-daemonset.yaml
@@ -16,7 +16,7 @@ spec:
hostNetwork: true
containers:
- name: netclient
- image: gravitl/netclient:v0.25.0
+ image: gravitl/netclient:v0.26.0
env:
- name: TOKEN
value: "TOKEN_VALUE"
diff --git a/k8s/client/netclient.yaml b/k8s/client/netclient.yaml
index 8ab9ec97b..9aa3f396d 100644
--- a/k8s/client/netclient.yaml
+++ b/k8s/client/netclient.yaml
@@ -28,7 +28,7 @@ spec:
# - ""
containers:
- name: netclient
- image: gravitl/netclient:v0.25.0
+ image: gravitl/netclient:v0.26.0
env:
- name: TOKEN
value: "TOKEN_VALUE"
diff --git a/k8s/server/netmaker-ui.yaml b/k8s/server/netmaker-ui.yaml
index 5600bea41..3212ec10a 100644
--- a/k8s/server/netmaker-ui.yaml
+++ b/k8s/server/netmaker-ui.yaml
@@ -15,7 +15,7 @@ spec:
spec:
containers:
- name: netmaker-ui
- image: gravitl/netmaker-ui:v0.25.0
+ image: gravitl/netmaker-ui:v0.26.0
ports:
- containerPort: 443
env:
diff --git a/logic/acls.go b/logic/acls.go
new file mode 100644
index 000000000..334e6f160
--- /dev/null
+++ b/logic/acls.go
@@ -0,0 +1,650 @@
+package logic
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "sort"
+ "sync"
+ "time"
+
+ "github.com/gravitl/netmaker/database"
+ "github.com/gravitl/netmaker/models"
+ "github.com/gravitl/netmaker/servercfg"
+)
+
+var (
+ aclCacheMutex = &sync.RWMutex{}
+ aclCacheMap = make(map[string]models.Acl)
+)
+
+// CreateDefaultAclNetworkPolicies - create default acl network policies
+func CreateDefaultAclNetworkPolicies(netID models.NetworkID) {
+ if netID.String() == "" {
+ return
+ }
+ if !IsAclExists(fmt.Sprintf("%s.%s", netID, "all-nodes")) {
+ defaultDeviceAcl := models.Acl{
+ ID: fmt.Sprintf("%s.%s", netID, "all-nodes"),
+ Name: "All Nodes",
+ MetaData: "This Policy allows all nodes in the network to communicate with each other",
+ Default: true,
+ NetworkID: netID,
+ RuleType: models.DevicePolicy,
+ Src: []models.AclPolicyTag{
+ {
+ ID: models.DeviceAclID,
+ Value: "*",
+ }},
+ Dst: []models.AclPolicyTag{
+ {
+ ID: models.DeviceAclID,
+ Value: "*",
+ }},
+ AllowedDirection: models.TrafficDirectionBi,
+ Enabled: true,
+ CreatedBy: "auto",
+ CreatedAt: time.Now().UTC(),
+ }
+ InsertAcl(defaultDeviceAcl)
+ }
+ if !IsAclExists(fmt.Sprintf("%s.%s", netID, "all-users")) {
+ defaultUserAcl := models.Acl{
+ ID: fmt.Sprintf("%s.%s", netID, "all-users"),
+ Default: true,
+ Name: "All Users",
+ MetaData: "This policy gives access to everything in the network for an user",
+ NetworkID: netID,
+ RuleType: models.UserPolicy,
+ Src: []models.AclPolicyTag{
+ {
+ ID: models.UserAclID,
+ Value: "*",
+ },
+ },
+ Dst: []models.AclPolicyTag{{
+ ID: models.DeviceAclID,
+ Value: "*",
+ }},
+ AllowedDirection: models.TrafficDirectionUni,
+ Enabled: true,
+ CreatedBy: "auto",
+ CreatedAt: time.Now().UTC(),
+ }
+ InsertAcl(defaultUserAcl)
+ }
+
+ if !IsAclExists(fmt.Sprintf("%s.%s", netID, "all-remote-access-gws")) {
+ defaultUserAcl := models.Acl{
+ ID: fmt.Sprintf("%s.%s", netID, "all-remote-access-gws"),
+ Default: true,
+ Name: "All Remote Access Gateways",
+ NetworkID: netID,
+ RuleType: models.DevicePolicy,
+ Src: []models.AclPolicyTag{
+ {
+ ID: models.DeviceAclID,
+ Value: fmt.Sprintf("%s.%s", netID, models.RemoteAccessTagName),
+ },
+ },
+ Dst: []models.AclPolicyTag{
+ {
+ ID: models.DeviceAclID,
+ Value: "*",
+ },
+ },
+ AllowedDirection: models.TrafficDirectionBi,
+ Enabled: true,
+ CreatedBy: "auto",
+ CreatedAt: time.Now().UTC(),
+ }
+ InsertAcl(defaultUserAcl)
+ }
+ CreateDefaultUserPolicies(netID)
+}
+
+// DeleteDefaultNetworkPolicies - deletes all default network acl policies
+func DeleteDefaultNetworkPolicies(netId models.NetworkID) {
+ acls, _ := ListAcls(netId)
+ for _, acl := range acls {
+ if acl.NetworkID == netId && acl.Default {
+ DeleteAcl(acl)
+ }
+ }
+}
+
+// ValidateCreateAclReq - validates create req for acl
+func ValidateCreateAclReq(req models.Acl) error {
+ // check if acl network exists
+ _, err := GetNetwork(req.NetworkID.String())
+ if err != nil {
+ return errors.New("failed to get network details for " + req.NetworkID.String())
+ }
+ // err = CheckIDSyntax(req.Name)
+ // if err != nil {
+ // return err
+ // }
+ return nil
+}
+
+func listAclFromCache() (acls []models.Acl) {
+ aclCacheMutex.RLock()
+ defer aclCacheMutex.RUnlock()
+ for _, acl := range aclCacheMap {
+ acls = append(acls, acl)
+ }
+ return
+}
+
+func storeAclInCache(a models.Acl) {
+ aclCacheMutex.Lock()
+ defer aclCacheMutex.Unlock()
+ aclCacheMap[a.ID] = a
+}
+
+func removeAclFromCache(a models.Acl) {
+ aclCacheMutex.Lock()
+ defer aclCacheMutex.Unlock()
+ delete(aclCacheMap, a.ID)
+}
+
+func getAclFromCache(aID string) (a models.Acl, ok bool) {
+ aclCacheMutex.RLock()
+ defer aclCacheMutex.RUnlock()
+ a, ok = aclCacheMap[aID]
+ return
+}
+
+// InsertAcl - creates acl policy
+func InsertAcl(a models.Acl) error {
+ d, err := json.Marshal(a)
+ if err != nil {
+ return err
+ }
+ err = database.Insert(a.ID, string(d), database.ACLS_TABLE_NAME)
+ if err == nil && servercfg.CacheEnabled() {
+ storeAclInCache(a)
+ }
+ return err
+}
+
+// GetAcl - gets acl info by id
+func GetAcl(aID string) (models.Acl, error) {
+ a := models.Acl{}
+ if servercfg.CacheEnabled() {
+ var ok bool
+ a, ok = getAclFromCache(aID)
+ if ok {
+ return a, nil
+ }
+ }
+ d, err := database.FetchRecord(database.ACLS_TABLE_NAME, aID)
+ if err != nil {
+ return a, err
+ }
+ err = json.Unmarshal([]byte(d), &a)
+ if err != nil {
+ return a, err
+ }
+ if servercfg.CacheEnabled() {
+ storeAclInCache(a)
+ }
+ return a, nil
+}
+
+// IsAclExists - checks if acl exists
+func IsAclExists(aclID string) bool {
+ _, err := GetAcl(aclID)
+ return err == nil
+}
+
+// IsAclPolicyValid - validates if acl policy is valid
+func IsAclPolicyValid(acl models.Acl) bool {
+ //check if src and dst are valid
+
+ switch acl.RuleType {
+ case models.UserPolicy:
+ // 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 {
+ 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.DeviceAclID {
+ return false
+ }
+ if dstI.Value == "*" {
+ continue
+ }
+ // check if tag is valid
+ _, err := GetTag(models.TagID(dstI.Value))
+ if err != nil {
+ return false
+ }
+ }
+ case models.DevicePolicy:
+ for _, srcI := range acl.Src {
+ if srcI.ID == "" || srcI.Value == "" {
+ return false
+ }
+ if srcI.ID != models.DeviceAclID {
+ return false
+ }
+ if srcI.Value == "*" {
+ continue
+ }
+ // check if tag is valid
+ _, err := GetTag(models.TagID(srcI.Value))
+ if err != nil {
+ return false
+ }
+ }
+ for _, dstI := range acl.Dst {
+
+ if dstI.ID == "" || dstI.Value == "" {
+ return false
+ }
+ if dstI.ID != models.DeviceAclID {
+ return false
+ }
+ if dstI.Value == "*" {
+ continue
+ }
+ // check if tag is valid
+ _, err := GetTag(models.TagID(dstI.Value))
+ if err != nil {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+// UpdateAcl - updates allowed fields on acls and commits to DB
+func UpdateAcl(newAcl, acl models.Acl) error {
+ if !acl.Default {
+ acl.Name = newAcl.Name
+ acl.Src = newAcl.Src
+ acl.Dst = newAcl.Dst
+ }
+ acl.Enabled = newAcl.Enabled
+ d, err := json.Marshal(acl)
+ if err != nil {
+ return err
+ }
+ err = database.Insert(acl.ID, string(d), database.ACLS_TABLE_NAME)
+ if err == nil && servercfg.CacheEnabled() {
+ storeAclInCache(acl)
+ }
+ return err
+}
+
+// UpsertAcl - upserts acl
+func UpsertAcl(acl models.Acl) error {
+ d, err := json.Marshal(acl)
+ if err != nil {
+ return err
+ }
+ err = database.Insert(acl.ID, string(d), database.ACLS_TABLE_NAME)
+ if err == nil && servercfg.CacheEnabled() {
+ storeAclInCache(acl)
+ }
+ return err
+}
+
+// DeleteAcl - deletes acl policy
+func DeleteAcl(a models.Acl) error {
+ err := database.DeleteRecord(database.ACLS_TABLE_NAME, a.ID)
+ if err == nil && servercfg.CacheEnabled() {
+ removeAclFromCache(a)
+ }
+ return err
+}
+
+// GetDefaultPolicy - fetches default policy in the network by ruleType
+func GetDefaultPolicy(netID models.NetworkID, ruleType models.AclPolicyType) (models.Acl, error) {
+ aclID := "all-users"
+ if ruleType == models.DevicePolicy {
+ aclID = "all-nodes"
+ }
+ acl, err := GetAcl(fmt.Sprintf("%s.%s", netID, aclID))
+ if err != nil {
+ return models.Acl{}, errors.New("default rule not found")
+ }
+ if acl.Enabled {
+ return acl, nil
+ }
+ // check if there are any custom all policies
+ policies, _ := ListAcls(netID)
+ for _, policy := range policies {
+ if !policy.Enabled {
+ continue
+ }
+ if policy.RuleType == ruleType {
+ dstMap := convAclTagToValueMap(policy.Dst)
+ srcMap := convAclTagToValueMap(policy.Src)
+ if _, ok := srcMap["*"]; ok {
+ if _, ok := dstMap["*"]; ok {
+ return policy, nil
+ }
+ }
+ }
+
+ }
+
+ return acl, nil
+}
+
+func listAcls() (acls []models.Acl) {
+ if servercfg.CacheEnabled() && len(aclCacheMap) > 0 {
+ return listAclFromCache()
+ }
+
+ data, err := database.FetchRecords(database.ACLS_TABLE_NAME)
+ if err != nil && !database.IsEmptyRecord(err) {
+ return []models.Acl{}
+ }
+
+ for _, dataI := range data {
+ acl := models.Acl{}
+ err := json.Unmarshal([]byte(dataI), &acl)
+ if err != nil {
+ continue
+ }
+ acls = append(acls, acl)
+ if servercfg.CacheEnabled() {
+ storeAclInCache(acl)
+ }
+ }
+ return
+}
+
+// ListUserPolicies - lists all acl policies enforced on an user
+func ListUserPolicies(u models.User) []models.Acl {
+ allAcls := listAcls()
+ userAcls := []models.Acl{}
+ for _, acl := range allAcls {
+
+ if acl.RuleType == models.UserPolicy {
+ srcMap := convAclTagToValueMap(acl.Src)
+ if _, ok := srcMap[u.UserName]; ok {
+ userAcls = append(userAcls, acl)
+ } else {
+ // check for user groups
+ for gID := range u.UserGroups {
+ if _, ok := srcMap[gID.String()]; ok {
+ userAcls = append(userAcls, acl)
+ break
+ }
+ }
+ }
+
+ }
+ }
+ return userAcls
+}
+
+// listPoliciesOfUser - lists all user acl policies applied to user in an network
+func listPoliciesOfUser(user models.User, netID models.NetworkID) []models.Acl {
+ allAcls := listAcls()
+ userAcls := []models.Acl{}
+ for _, acl := range allAcls {
+ if acl.NetworkID == netID && acl.RuleType == models.UserPolicy {
+ srcMap := convAclTagToValueMap(acl.Src)
+ if _, ok := srcMap[user.UserName]; ok {
+ userAcls = append(userAcls, acl)
+ continue
+ }
+ for netRole := range user.NetworkRoles {
+ if _, ok := srcMap[netRole.String()]; ok {
+ userAcls = append(userAcls, acl)
+ continue
+ }
+ }
+ for userG := range user.UserGroups {
+ if _, ok := srcMap[userG.String()]; ok {
+ userAcls = append(userAcls, acl)
+ continue
+ }
+ }
+
+ }
+ }
+ return userAcls
+}
+
+// listDevicePolicies - lists all device policies in a network
+func listDevicePolicies(netID models.NetworkID) []models.Acl {
+ allAcls := listAcls()
+ deviceAcls := []models.Acl{}
+ for _, acl := range allAcls {
+ if acl.NetworkID == netID && acl.RuleType == models.DevicePolicy {
+ deviceAcls = append(deviceAcls, acl)
+ }
+ }
+ return deviceAcls
+}
+
+// ListAcls - lists all acl policies
+func ListAcls(netID models.NetworkID) ([]models.Acl, error) {
+
+ allAcls := listAcls()
+ netAcls := []models.Acl{}
+ for _, acl := range allAcls {
+ if acl.NetworkID == netID {
+ netAcls = append(netAcls, acl)
+ }
+ }
+ return netAcls, nil
+}
+
+func convAclTagToValueMap(acltags []models.AclPolicyTag) map[string]struct{} {
+ aclValueMap := make(map[string]struct{})
+ for _, aclTagI := range acltags {
+ aclValueMap[aclTagI.Value] = struct{}{}
+ }
+ return aclValueMap
+}
+
+// IsUserAllowedToCommunicate - check if user is allowed to communicate with peer
+func IsUserAllowedToCommunicate(userName string, peer models.Node) bool {
+ if peer.IsStatic {
+ peer = peer.StaticNode.ConvertToStaticNode()
+ }
+ acl, _ := GetDefaultPolicy(models.NetworkID(peer.Network), models.UserPolicy)
+ if acl.Enabled {
+ return true
+ }
+ user, err := GetUser(userName)
+ if err != nil {
+ return false
+ }
+
+ policies := listPoliciesOfUser(*user, models.NetworkID(peer.Network))
+ for _, policy := range policies {
+ if !policy.Enabled {
+ continue
+ }
+ dstMap := convAclTagToValueMap(policy.Dst)
+ if _, ok := dstMap["*"]; ok {
+ return true
+ }
+ for tagID := range peer.Tags {
+ if _, ok := dstMap[tagID.String()]; ok {
+ return true
+ }
+ }
+
+ }
+ return false
+}
+
+// IsNodeAllowedToCommunicate - check node is allowed to communicate with the peer
+func IsNodeAllowedToCommunicate(node, peer models.Node) bool {
+ if node.IsStatic {
+ node = node.StaticNode.ConvertToStaticNode()
+ }
+ if peer.IsStatic {
+ peer = peer.StaticNode.ConvertToStaticNode()
+ }
+ // check default policy if all allowed return true
+ defaultPolicy, err := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
+ if err == nil {
+ if defaultPolicy.Enabled {
+ return true
+ }
+ }
+
+ // list device policies
+ policies := listDevicePolicies(models.NetworkID(peer.Network))
+ for _, policy := range policies {
+ if !policy.Enabled {
+ continue
+ }
+ srcMap := convAclTagToValueMap(policy.Src)
+ dstMap := convAclTagToValueMap(policy.Dst)
+ // fmt.Printf("\n======> SRCMAP: %+v\n", srcMap)
+ // fmt.Printf("\n======> DSTMAP: %+v\n", dstMap)
+ // fmt.Printf("\n======> node Tags: %+v\n", node.Tags)
+ // fmt.Printf("\n======> peer Tags: %+v\n", peer.Tags)
+ for tagID := range node.Tags {
+ if _, ok := dstMap[tagID.String()]; ok {
+ if _, ok := srcMap["*"]; ok {
+ return true
+ }
+ for tagID := range peer.Tags {
+ if _, ok := srcMap[tagID.String()]; ok {
+ return true
+ }
+ }
+ }
+ if _, ok := srcMap[tagID.String()]; ok {
+ if _, ok := dstMap["*"]; ok {
+ return true
+ }
+ for tagID := range peer.Tags {
+ if _, ok := dstMap[tagID.String()]; ok {
+ return true
+ }
+ }
+ }
+ }
+ for tagID := range peer.Tags {
+ if _, ok := dstMap[tagID.String()]; ok {
+ if _, ok := srcMap["*"]; ok {
+ return true
+ }
+ for tagID := range node.Tags {
+
+ if _, ok := srcMap[tagID.String()]; ok {
+ return true
+ }
+ }
+ }
+ if _, ok := srcMap[tagID.String()]; ok {
+ if _, ok := dstMap["*"]; ok {
+ return true
+ }
+ for tagID := range node.Tags {
+ if _, ok := dstMap[tagID.String()]; ok {
+ return true
+ }
+ }
+ }
+ }
+ }
+ return false
+}
+
+// SortTagEntrys - Sorts slice of Tag entries by their id
+func SortAclEntrys(acls []models.Acl) {
+ sort.Slice(acls, func(i, j int) bool {
+ return acls[i].Name < acls[j].Name
+ })
+}
+
+// UpdateDeviceTag - updates device tag on acl policies
+func UpdateDeviceTag(OldID, newID models.TagID, netID models.NetworkID) {
+ acls := listDevicePolicies(netID)
+ update := false
+ for _, acl := range acls {
+ for i, srcTagI := range acl.Src {
+ if srcTagI.ID == models.DeviceAclID {
+ if OldID.String() == srcTagI.Value {
+ acl.Src[i].Value = newID.String()
+ update = true
+ }
+ }
+ }
+ for i, dstTagI := range acl.Dst {
+ if dstTagI.ID == models.DeviceAclID {
+ if OldID.String() == dstTagI.Value {
+ acl.Dst[i].Value = newID.String()
+ update = true
+ }
+ }
+ }
+ if update {
+ UpsertAcl(acl)
+ }
+ }
+}
+
+// RemoveDeviceTagFromAclPolicies - remove device tag from acl policies
+func RemoveDeviceTagFromAclPolicies(tagID models.TagID, netID models.NetworkID) error {
+ acls := listDevicePolicies(netID)
+ update := false
+ for _, acl := range acls {
+ for i, srcTagI := range acl.Src {
+ if srcTagI.ID == models.DeviceAclID {
+ if tagID.String() == srcTagI.Value {
+ acl.Src = append(acl.Src[:i], acl.Src[i+1:]...)
+ update = true
+ }
+ }
+ }
+ for i, dstTagI := range acl.Dst {
+ if dstTagI.ID == models.DeviceAclID {
+ if tagID.String() == dstTagI.Value {
+ acl.Dst = append(acl.Dst[:i], acl.Dst[i+1:]...)
+ update = true
+ }
+ }
+ }
+ if update {
+ UpsertAcl(acl)
+ }
+ }
+ return nil
+}
diff --git a/logic/acls/common.go b/logic/acls/common.go
index 9296e3b80..bb35123a3 100644
--- a/logic/acls/common.go
+++ b/logic/acls/common.go
@@ -2,6 +2,7 @@ package acls
import (
"encoding/json"
+ "maps"
"sync"
"github.com/gravitl/netmaker/database"
@@ -133,7 +134,7 @@ func fetchACLContainer(containerID ContainerID) (ACLContainer, error) {
defer AclMutex.RUnlock()
if servercfg.CacheEnabled() {
if aclContainer, ok := fetchAclContainerFromCache(containerID); ok {
- return aclContainer, nil
+ return maps.Clone(aclContainer), nil
}
}
aclJson, err := fetchACLContainerJson(ContainerID(containerID))
@@ -147,7 +148,7 @@ func fetchACLContainer(containerID ContainerID) (ACLContainer, error) {
if servercfg.CacheEnabled() {
storeAclContainerInCache(containerID, currentNetworkACL)
}
- return currentNetworkACL, nil
+ return maps.Clone(currentNetworkACL), nil
}
// fetchACLContainerJson - fetch the current ACL of given container except in json string
diff --git a/logic/acls/nodeacls/retrieve.go b/logic/acls/nodeacls/retrieve.go
index d5fa68c40..4411c5b22 100644
--- a/logic/acls/nodeacls/retrieve.go
+++ b/logic/acls/nodeacls/retrieve.go
@@ -3,6 +3,7 @@ package nodeacls
import (
"encoding/json"
"fmt"
+ "maps"
"sync"
"github.com/gravitl/netmaker/logic/acls"
@@ -67,5 +68,5 @@ func FetchAllACLs(networkID NetworkID) (acls.ACLContainer, error) {
if err != nil {
return nil, err
}
- return currentNetworkACL, nil
+ return maps.Clone(currentNetworkACL), nil
}
diff --git a/logic/auth.go b/logic/auth.go
index 3da9f605d..031e5fd14 100644
--- a/logic/auth.go
+++ b/logic/auth.go
@@ -186,7 +186,7 @@ func CreateUser(user *models.User) error {
logger.Log(0, "failed to insert user", err.Error())
return err
}
-
+ AddGlobalNetRolesToAdmins(*user)
return nil
}
@@ -305,6 +305,7 @@ func UpdateUser(userchange, user *models.User) (*models.User, error) {
}
user.UserGroups = userchange.UserGroups
user.NetworkRoles = userchange.NetworkRoles
+ AddGlobalNetRolesToAdmins(*user)
err := ValidateUser(user)
if err != nil {
return &models.User{}, err
diff --git a/logic/clients.go b/logic/clients.go
index 7331ee615..d9eca8c9d 100644
--- a/logic/clients.go
+++ b/logic/clients.go
@@ -32,7 +32,7 @@ var (
slog.Error("failed to get network acls", "error", err)
return err
}
- networkAcls[acls.AclID(ec.ClientID)] = acls.ACL{}
+ networkAcls[acls.AclID(ec.ClientID)] = make(acls.ACL)
for objId := range networkAcls {
networkAcls[objId][acls.AclID(ec.ClientID)] = acls.Allowed
networkAcls[acls.AclID(ec.ClientID)][objId] = acls.Allowed
diff --git a/logic/enrollmentkey.go b/logic/enrollmentkey.go
index d3c48a011..25cf0d6d3 100644
--- a/logic/enrollmentkey.go
+++ b/logic/enrollmentkey.go
@@ -37,7 +37,7 @@ var (
)
// CreateEnrollmentKey - creates a new enrollment key in db
-func CreateEnrollmentKey(uses int, expiration time.Time, networks, tags []string, unlimited bool, relay uuid.UUID) (*models.EnrollmentKey, error) {
+func CreateEnrollmentKey(uses int, expiration time.Time, networks, tags []string, groups []models.TagID, unlimited bool, relay uuid.UUID, defaultKey bool) (*models.EnrollmentKey, error) {
newKeyID, err := getUniqueEnrollmentID()
if err != nil {
return nil, err
@@ -51,6 +51,8 @@ func CreateEnrollmentKey(uses int, expiration time.Time, networks, tags []string
Tags: []string{},
Type: models.Undefined,
Relay: relay,
+ Groups: groups,
+ Default: defaultKey,
}
if uses > 0 {
k.UsesRemaining = uses
@@ -89,7 +91,7 @@ func CreateEnrollmentKey(uses int, expiration time.Time, networks, tags []string
}
// UpdateEnrollmentKey - updates an existing enrollment key's associated relay
-func UpdateEnrollmentKey(keyId string, relayId uuid.UUID) (*models.EnrollmentKey, error) {
+func UpdateEnrollmentKey(keyId string, relayId uuid.UUID, groups []models.TagID) (*models.EnrollmentKey, error) {
key, err := GetEnrollmentKey(keyId)
if err != nil {
return nil, err
@@ -109,7 +111,7 @@ func UpdateEnrollmentKey(keyId string, relayId uuid.UUID) (*models.EnrollmentKey
}
key.Relay = relayId
-
+ key.Groups = groups
if err = upsertEnrollmentKey(&key); err != nil {
return nil, err
}
@@ -151,11 +153,14 @@ func deleteEnrollmentkeyFromCache(key string) {
}
// DeleteEnrollmentKey - delete's a given enrollment key by value
-func DeleteEnrollmentKey(value string) error {
- _, err := GetEnrollmentKey(value)
+func DeleteEnrollmentKey(value string, force bool) error {
+ key, err := GetEnrollmentKey(value)
if err != nil {
return err
}
+ if key.Default && !force {
+ return errors.New("cannot delete default network key")
+ }
err = database.DeleteRecord(database.ENROLLMENT_KEYS_TABLE_NAME, value)
if err == nil {
if servercfg.CacheEnabled() {
@@ -310,3 +315,23 @@ func getEnrollmentKeysMap() (map[string]models.EnrollmentKey, error) {
}
return currentKeys, nil
}
+
+func RemoveTagFromEnrollmentKeys(deletedTagID models.TagID) {
+ keys, _ := GetAllEnrollmentKeys()
+ for _, key := range keys {
+ newTags := []models.TagID{}
+ update := false
+ for _, tagID := range key.Groups {
+ if tagID == deletedTagID {
+ update = true
+ continue
+ }
+ newTags = append(newTags, tagID)
+ }
+ if update {
+ key.Groups = newTags
+ upsertEnrollmentKey(&key)
+ }
+
+ }
+}
diff --git a/logic/enrollmentkey_test.go b/logic/enrollmentkey_test.go
index 677c47141..92b4c5e28 100644
--- a/logic/enrollmentkey_test.go
+++ b/logic/enrollmentkey_test.go
@@ -14,35 +14,35 @@ func TestCreateEnrollmentKey(t *testing.T) {
database.InitializeDatabase()
defer database.CloseDB()
t.Run("Can_Not_Create_Key", func(t *testing.T) {
- newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, false, uuid.Nil)
+ newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, nil, false, uuid.Nil, false)
assert.Nil(t, newKey)
assert.NotNil(t, err)
assert.ErrorIs(t, err, models.ErrInvalidEnrollmentKey)
})
t.Run("Can_Create_Key_Uses", func(t *testing.T) {
- newKey, err := CreateEnrollmentKey(1, time.Time{}, nil, nil, false, uuid.Nil)
+ newKey, err := CreateEnrollmentKey(1, time.Time{}, nil, nil, nil, false, uuid.Nil, false)
assert.Nil(t, err)
assert.Equal(t, 1, newKey.UsesRemaining)
assert.True(t, newKey.IsValid())
})
t.Run("Can_Create_Key_Time", func(t *testing.T) {
- newKey, err := CreateEnrollmentKey(0, time.Now().Add(time.Minute), nil, nil, false, uuid.Nil)
+ newKey, err := CreateEnrollmentKey(0, time.Now().Add(time.Minute), nil, nil, nil, false, uuid.Nil, false)
assert.Nil(t, err)
assert.True(t, newKey.IsValid())
})
t.Run("Can_Create_Key_Unlimited", func(t *testing.T) {
- newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, true, uuid.Nil)
+ newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, nil, true, uuid.Nil, false)
assert.Nil(t, err)
assert.True(t, newKey.IsValid())
})
t.Run("Can_Create_Key_WithNetworks", func(t *testing.T) {
- newKey, err := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, true, uuid.Nil)
+ newKey, err := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false)
assert.Nil(t, err)
assert.True(t, newKey.IsValid())
assert.True(t, len(newKey.Networks) == 2)
})
t.Run("Can_Create_Key_WithTags", func(t *testing.T) {
- newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, []string{"tag1", "tag2"}, true, uuid.Nil)
+ newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, []string{"tag1", "tag2"}, nil, true, uuid.Nil, false)
assert.Nil(t, err)
assert.True(t, newKey.IsValid())
assert.True(t, len(newKey.Tags) == 2)
@@ -62,10 +62,10 @@ func TestCreateEnrollmentKey(t *testing.T) {
func TestDelete_EnrollmentKey(t *testing.T) {
database.InitializeDatabase()
defer database.CloseDB()
- newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, true, uuid.Nil)
+ newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false)
t.Run("Can_Delete_Key", func(t *testing.T) {
assert.True(t, newKey.IsValid())
- err := DeleteEnrollmentKey(newKey.Value)
+ err := DeleteEnrollmentKey(newKey.Value, false)
assert.Nil(t, err)
oldKey, err := GetEnrollmentKey(newKey.Value)
assert.Equal(t, oldKey, models.EnrollmentKey{})
@@ -73,7 +73,7 @@ func TestDelete_EnrollmentKey(t *testing.T) {
assert.Equal(t, err, EnrollmentErrors.NoKeyFound)
})
t.Run("Can_Not_Delete_Invalid_Key", func(t *testing.T) {
- err := DeleteEnrollmentKey("notakey")
+ err := DeleteEnrollmentKey("notakey", false)
assert.NotNil(t, err)
assert.Equal(t, err, EnrollmentErrors.NoKeyFound)
})
@@ -83,7 +83,7 @@ func TestDelete_EnrollmentKey(t *testing.T) {
func TestDecrement_EnrollmentKey(t *testing.T) {
database.InitializeDatabase()
defer database.CloseDB()
- newKey, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, false, uuid.Nil)
+ newKey, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, nil, false, uuid.Nil, false)
t.Run("Check_initial_uses", func(t *testing.T) {
assert.True(t, newKey.IsValid())
assert.Equal(t, newKey.UsesRemaining, 1)
@@ -107,9 +107,9 @@ func TestDecrement_EnrollmentKey(t *testing.T) {
func TestUsability_EnrollmentKey(t *testing.T) {
database.InitializeDatabase()
defer database.CloseDB()
- key1, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, false, uuid.Nil)
- key2, _ := CreateEnrollmentKey(0, time.Now().Add(time.Minute<<4), nil, nil, false, uuid.Nil)
- key3, _ := CreateEnrollmentKey(0, time.Time{}, nil, nil, true, uuid.Nil)
+ key1, _ := CreateEnrollmentKey(1, time.Time{}, nil, nil, nil, false, uuid.Nil, false)
+ key2, _ := CreateEnrollmentKey(0, time.Now().Add(time.Minute<<4), nil, nil, nil, false, uuid.Nil, false)
+ key3, _ := CreateEnrollmentKey(0, time.Time{}, nil, nil, nil, true, uuid.Nil, false)
t.Run("Check if valid use key can be used", func(t *testing.T) {
assert.Equal(t, key1.UsesRemaining, 1)
ok := TryToUseEnrollmentKey(key1)
@@ -145,7 +145,7 @@ func removeAllEnrollments() {
func TestTokenize_EnrollmentKeys(t *testing.T) {
database.InitializeDatabase()
defer database.CloseDB()
- newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, true, uuid.Nil)
+ newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false)
const defaultValue = "MwE5MwE5MwE5MwE5MwE5MwE5MwE5MwE5"
const b64value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0U1TXdFNU13RTVNd0U1TXdFNU13RTVNd0U1TXdFNSJ9"
const serverAddr = "api.myserver.com"
@@ -178,7 +178,7 @@ func TestTokenize_EnrollmentKeys(t *testing.T) {
func TestDeTokenize_EnrollmentKeys(t *testing.T) {
database.InitializeDatabase()
defer database.CloseDB()
- newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, true, uuid.Nil)
+ newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, nil, true, uuid.Nil, false)
const b64Value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0U1TXdFNU13RTVNd0U1TXdFNU13RTVNd0U1TXdFNSJ9"
const serverAddr = "api.myserver.com"
diff --git a/logic/extpeers.go b/logic/extpeers.go
index c619dde94..fcb422243 100644
--- a/logic/extpeers.go
+++ b/logic/extpeers.go
@@ -69,7 +69,7 @@ func GetEgressRangesOnNetwork(client *models.ExtClient) ([]string, error) {
}
}
}
- extclients := GetGwExtclients(client.IngressGatewayID, client.Network)
+ extclients, _ := GetNetworkExtClients(client.Network)
for _, extclient := range extclients {
if extclient.ClientID == client.ClientID {
continue
@@ -136,6 +136,12 @@ func DeleteExtClientAndCleanup(extClient models.ExtClient) error {
return nil
}
+//TODO - enforce extclient-to-extclient on ingress gw
+/* 1. fetch all non-user static nodes
+a. check against each user node, if allowed add rule
+
+*/
+
// GetNetworkExtClients - gets the ext clients of given network
func GetNetworkExtClients(network string) ([]models.ExtClient, error) {
var extclients []models.ExtClient
@@ -329,6 +335,7 @@ func UpdateExtClient(old *models.ExtClient, update *models.CustomExtClient) mode
// 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)
+ new.Tags = update.Tags
return new
}
@@ -395,6 +402,206 @@ func ToggleExtClientConnectivity(client *models.ExtClient, enable bool) (models.
return newClient, nil
}
+func GetStaticNodeIps(node models.Node) (ips []net.IP) {
+ defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
+ defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
+
+ extclients := GetStaticNodesByNetwork(models.NetworkID(node.Network), false)
+ for _, extclient := range extclients {
+ if extclient.IsUserNode && defaultUserPolicy.Enabled {
+ continue
+ }
+ if !extclient.IsUserNode && defaultDevicePolicy.Enabled {
+ continue
+ }
+ if extclient.StaticNode.Address != "" {
+ ips = append(ips, extclient.StaticNode.AddressIPNet4().IP)
+ }
+ if extclient.StaticNode.Address6 != "" {
+ ips = append(ips, extclient.StaticNode.AddressIPNet6().IP)
+ }
+ }
+ return
+}
+
+func GetFwRulesOnIngressGateway(node models.Node) (rules []models.FwRule) {
+ // fetch user access to static clients via policies
+ 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)...)
+ //fmt.Printf("=====> NODES: %+v \n\n", nodes)
+ userNodes := GetStaticUserNodesByNetwork(models.NetworkID(node.Network))
+ //fmt.Printf("=====> USER NODES %+v \n\n", userNodes)
+ for _, userNodeI := range userNodes {
+ for _, peer := range nodes {
+ if peer.IsUserNode {
+ continue
+ }
+ if IsUserAllowedToCommunicate(userNodeI.StaticNode.OwnerID, peer) {
+ if peer.IsStatic {
+ if userNodeI.StaticNode.Address != "" {
+ if !defaultUserPolicy.Enabled {
+ rules = append(rules, models.FwRule{
+ SrcIP: userNodeI.StaticNode.AddressIPNet4(),
+ DstIP: peer.StaticNode.AddressIPNet4(),
+ Allow: true,
+ })
+ }
+ rules = append(rules, models.FwRule{
+ SrcIP: peer.StaticNode.AddressIPNet4(),
+ DstIP: userNodeI.StaticNode.AddressIPNet4(),
+ Allow: true,
+ })
+ }
+ if userNodeI.StaticNode.Address6 != "" {
+ if !defaultUserPolicy.Enabled {
+ rules = append(rules, models.FwRule{
+ SrcIP: userNodeI.StaticNode.AddressIPNet6(),
+ DstIP: peer.StaticNode.AddressIPNet6(),
+ Allow: true,
+ })
+ }
+
+ rules = append(rules, models.FwRule{
+ SrcIP: peer.StaticNode.AddressIPNet6(),
+ DstIP: userNodeI.StaticNode.AddressIPNet6(),
+ 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 {
+ rules = append(rules, models.FwRule{
+ SrcIP: userNodeI.StaticNode.AddressIPNet4(),
+ DstIP: net.IPNet{
+ IP: peer.Address.IP,
+ Mask: net.CIDRMask(32, 32),
+ },
+ 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),
+ },
+ Allow: true,
+ })
+ }
+ }
+
+ }
+ }
+ }
+
+ if defaultDevicePolicy.Enabled {
+ return
+ }
+ for _, nodeI := range nodes {
+ if !nodeI.IsStatic || nodeI.IsUserNode {
+ continue
+ }
+ for _, peer := range nodes {
+ if peer.StaticNode.ClientID == nodeI.StaticNode.ClientID || peer.IsUserNode {
+ continue
+ }
+ if IsNodeAllowedToCommunicate(nodeI, peer) {
+ if peer.IsStatic {
+ if nodeI.StaticNode.Address != "" {
+ rules = append(rules, models.FwRule{
+ SrcIP: nodeI.StaticNode.AddressIPNet4(),
+ DstIP: peer.StaticNode.AddressIPNet4(),
+ Allow: true,
+ })
+ }
+ if nodeI.StaticNode.Address6 != "" {
+ rules = append(rules, models.FwRule{
+ SrcIP: nodeI.StaticNode.AddressIPNet6(),
+ DstIP: peer.StaticNode.AddressIPNet6(),
+ 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: nodeI.StaticNode.AddressIPNet4(),
+ DstIP: *ipNet,
+ Allow: true,
+ })
+ } else {
+ rules = append(rules, models.FwRule{
+ SrcIP: nodeI.StaticNode.AddressIPNet6(),
+ DstIP: *ipNet,
+ Allow: true,
+ })
+ }
+
+ }
+
+ }
+ } else {
+ if nodeI.StaticNode.Address != "" {
+ rules = append(rules, models.FwRule{
+ SrcIP: nodeI.StaticNode.AddressIPNet4(),
+ DstIP: net.IPNet{
+ IP: peer.Address.IP,
+ Mask: net.CIDRMask(32, 32),
+ },
+ Allow: true,
+ })
+ }
+ if nodeI.StaticNode.Address6 != "" {
+ rules = append(rules, models.FwRule{
+ SrcIP: nodeI.StaticNode.AddressIPNet6(),
+ DstIP: net.IPNet{
+ IP: peer.Address6.IP,
+ Mask: net.CIDRMask(128, 128),
+ },
+ Allow: true,
+ })
+ }
+ }
+
+ }
+ }
+ }
+ return
+}
+
func GetExtPeers(node, peer *models.Node) ([]wgtypes.PeerConfig, []models.IDandAddr, []models.EgressNetworkRoutes, error) {
var peers []wgtypes.PeerConfig
var idsAndAddr []models.IDandAddr
@@ -412,6 +619,16 @@ func GetExtPeers(node, peer *models.Node) ([]wgtypes.PeerConfig, []models.IDandA
if !IsClientNodeAllowed(&extPeer, peer.ID.String()) {
continue
}
+ if extPeer.RemoteAccessClientID == "" {
+ if !IsNodeAllowedToCommunicate(extPeer.ConvertToStaticNode(), *peer) {
+ continue
+ }
+ } else {
+ if !IsUserAllowedToCommunicate(extPeer.OwnerID, *peer) {
+ continue
+ }
+ }
+
pubkey, err := wgtypes.ParseKey(extPeer.PublicKey)
if err != nil {
logger.Log(1, "error parsing ext pub key:", err.Error())
@@ -483,8 +700,8 @@ func getExtPeerEgressRoute(node models.Node, extPeer models.ExtClient) (egressRo
return
}
-func getExtpeersExtraRoutes(node models.Node, network string) (egressRoutes []models.EgressNetworkRoutes) {
- extPeers, err := GetNetworkExtClients(network)
+func getExtpeerEgressRanges(node models.Node) (ranges, ranges6 []net.IPNet) {
+ extPeers, err := GetNetworkExtClients(node.Network)
if err != nil {
return
}
@@ -492,6 +709,36 @@ func getExtpeersExtraRoutes(node models.Node, network string) (egressRoutes []mo
if len(extPeer.ExtraAllowedIPs) == 0 {
continue
}
+ if !IsNodeAllowedToCommunicate(extPeer.ConvertToStaticNode(), node) {
+ continue
+ }
+ for _, allowedRange := range extPeer.ExtraAllowedIPs {
+ _, ipnet, err := net.ParseCIDR(allowedRange)
+ if err == nil {
+ if ipnet.IP.To4() != nil {
+ ranges = append(ranges, *ipnet)
+ } else {
+ ranges6 = append(ranges6, *ipnet)
+ }
+
+ }
+ }
+ }
+ return
+}
+
+func getExtpeersExtraRoutes(node models.Node) (egressRoutes []models.EgressNetworkRoutes) {
+ extPeers, err := GetNetworkExtClients(node.Network)
+ if err != nil {
+ return
+ }
+ for _, extPeer := range extPeers {
+ if len(extPeer.ExtraAllowedIPs) == 0 {
+ continue
+ }
+ if !IsNodeAllowedToCommunicate(extPeer.ConvertToStaticNode(), node) {
+ continue
+ }
egressRoutes = append(egressRoutes, getExtPeerEgressRoute(node, extPeer)...)
}
return
@@ -528,3 +775,64 @@ func GetExtclientAllowedIPs(client models.ExtClient) (allowedIPs []string) {
}
return
}
+
+func GetStaticUserNodesByNetwork(network models.NetworkID) (staticNode []models.Node) {
+ extClients, err := GetAllExtClients()
+ if err != nil {
+ return
+ }
+ for _, extI := range extClients {
+ if extI.Network == network.String() {
+ if extI.RemoteAccessClientID != "" {
+ n := models.Node{
+ IsStatic: true,
+ StaticNode: extI,
+ IsUserNode: extI.RemoteAccessClientID != "",
+ }
+ staticNode = append(staticNode, n)
+ }
+ }
+ }
+
+ return
+}
+
+func GetStaticNodesByNetwork(network models.NetworkID, onlyWg bool) (staticNode []models.Node) {
+ extClients, err := GetAllExtClients()
+ if err != nil {
+ return
+ }
+ for _, extI := range extClients {
+ if extI.Network == network.String() {
+ if onlyWg && extI.RemoteAccessClientID != "" {
+ continue
+ }
+ n := models.Node{
+ IsStatic: true,
+ StaticNode: extI,
+ IsUserNode: extI.RemoteAccessClientID != "",
+ }
+ staticNode = append(staticNode, n)
+ }
+ }
+
+ return
+}
+
+func GetStaticNodesByGw(gwNode models.Node) (staticNode []models.Node) {
+ extClients, err := GetAllExtClients()
+ if err != nil {
+ return
+ }
+ for _, extI := range extClients {
+ if extI.IngressGatewayID == gwNode.ID.String() {
+ n := models.Node{
+ IsStatic: true,
+ StaticNode: extI,
+ IsUserNode: extI.RemoteAccessClientID != "",
+ }
+ staticNode = append(staticNode, n)
+ }
+ }
+ return
+}
diff --git a/logic/gateway.go b/logic/gateway.go
index 87a41105b..a92682c5e 100644
--- a/logic/gateway.go
+++ b/logic/gateway.go
@@ -2,6 +2,7 @@ package logic
import (
"errors"
+ "fmt"
"time"
"github.com/gravitl/netmaker/database"
@@ -74,7 +75,7 @@ func CreateEgressGateway(gateway models.EgressGatewayRequest) (models.Node, erro
return models.Node{}, errors.New(host.OS + " is unsupported for egress gateways")
}
if host.FirewallInUse == models.FIREWALL_NONE {
- return models.Node{}, errors.New("firewall is not supported for egress gateways. please install iptables or nftables on the device in order to use this feature")
+ return models.Node{}, errors.New("please install iptables or nftables on the device")
}
for i := len(gateway.Ranges) - 1; i >= 0; i-- {
// check if internet gateway IPv4
@@ -149,9 +150,6 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
if host.OS != "linux" {
return models.Node{}, errors.New("ingress can only be created on linux based node")
}
- if host.FirewallInUse == models.FIREWALL_NONE {
- return models.Node{}, errors.New("firewall is not supported for ingress gateways")
- }
network, err := GetParentNetwork(netid)
if err != nil {
@@ -164,6 +162,14 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
node.IngressGatewayRange = network.AddressRange
node.IngressGatewayRange6 = network.AddressRange6
node.IngressDNS = ingress.ExtclientDNS
+ node.IngressPersistentKeepalive = 20
+ if ingress.PersistentKeepalive != 0 {
+ node.IngressPersistentKeepalive = ingress.PersistentKeepalive
+ }
+ node.IngressMTU = 1420
+ if ingress.MTU != 0 {
+ node.IngressMTU = ingress.MTU
+ }
if servercfg.IsPro {
if _, exists := FailOverExists(node.Network); exists {
ResetFailedOverPeer(&node)
@@ -174,34 +180,14 @@ func CreateIngressGateway(netid string, nodeid string, ingress models.IngressReq
if node.Metadata == "" {
node.Metadata = "This host can be used for remote access"
}
+ if node.Tags == nil {
+ node.Tags = make(map[models.TagID]struct{})
+ }
+ node.Tags[models.TagID(fmt.Sprintf("%s.%s", netid, models.RemoteAccessTagName))] = struct{}{}
err = UpsertNode(&node)
if err != nil {
return models.Node{}, err
}
- // create network role for this gateway
- CreateRole(models.UserRolePermissionTemplate{
- ID: models.GetRAGRoleID(node.Network, host.ID.String()),
- UiName: models.GetRAGRoleName(node.Network, host.Name),
- NetworkID: models.NetworkID(node.Network),
- Default: true,
- NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{
- models.RemoteAccessGwRsrc: {
- models.RsrcID(node.ID.String()): models.RsrcPermissionScope{
- Read: true,
- VPNaccess: true,
- },
- },
- models.ExtClientsRsrc: {
- models.AllExtClientsRsrcID: models.RsrcPermissionScope{
- Read: true,
- Create: true,
- Update: true,
- Delete: true,
- SelfOnly: true,
- },
- },
- },
- })
err = SetNetworkNodesLastModified(netid)
return node, err
}
@@ -249,17 +235,14 @@ func DeleteIngressGateway(nodeid string) (models.Node, []models.ExtClient, error
if !servercfg.IsPro {
node.IsInternetGateway = false
}
+ delete(node.Tags, models.TagID(fmt.Sprintf("%s.%s", node.Network, models.RemoteAccessTagName)))
node.IngressGatewayRange = ""
node.Metadata = ""
err = UpsertNode(&node)
if err != nil {
return models.Node{}, removedClients, err
}
- host, err := GetHost(node.HostID.String())
- if err != nil {
- return models.Node{}, removedClients, err
- }
- go DeleteRole(models.GetRAGRoleID(node.Network, host.ID.String()), true)
+
err = SetNetworkNodesLastModified(node.Network)
return node, removedClients, err
}
diff --git a/logic/hosts.go b/logic/hosts.go
index 0fa8887e9..8fd125c52 100644
--- a/logic/hosts.go
+++ b/logic/hosts.go
@@ -10,6 +10,7 @@ import (
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
+ "golang.org/x/exp/slog"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
@@ -254,14 +255,31 @@ func UpdateHostFromClient(newHost, currHost *models.Host) (sendPeerUpdate bool)
currHost.WgPublicListenPort = newHost.WgPublicListenPort
sendPeerUpdate = true
}
+ isEndpointChanged := false
if currHost.EndpointIP.String() != newHost.EndpointIP.String() {
currHost.EndpointIP = newHost.EndpointIP
sendPeerUpdate = true
+ isEndpointChanged = true
}
if currHost.EndpointIPv6.String() != newHost.EndpointIPv6.String() {
currHost.EndpointIPv6 = newHost.EndpointIPv6
sendPeerUpdate = true
+ isEndpointChanged = true
}
+
+ if isEndpointChanged {
+ for _, nodeID := range currHost.Nodes {
+ node, err := GetNodeByID(nodeID)
+ if err != nil {
+ slog.Error("failed to get node:", "id", node.ID, "error", err)
+ continue
+ }
+ if node.FailedOverBy != uuid.Nil {
+ ResetFailedOverPeer(&node)
+ }
+ }
+ }
+
currHost.DaemonInstalled = newHost.DaemonInstalled
currHost.Debug = newHost.Debug
currHost.Verbosity = newHost.Verbosity
@@ -269,19 +287,7 @@ func UpdateHostFromClient(newHost, currHost *models.Host) (sendPeerUpdate bool)
currHost.IsStaticPort = newHost.IsStaticPort
currHost.IsStatic = newHost.IsStatic
currHost.MTU = newHost.MTU
- if newHost.Name != currHost.Name {
- // update any rag role ids
- for _, nodeID := range newHost.Nodes {
- node, err := GetNodeByID(nodeID)
- if err == nil && node.IsIngressGateway {
- role, err := GetRole(models.GetRAGRoleID(node.Network, currHost.ID.String()))
- if err == nil {
- role.UiName = models.GetRAGRoleName(node.Network, newHost.Name)
- UpdateRole(role)
- }
- }
- }
- }
+
currHost.Name = newHost.Name
if len(newHost.NatType) > 0 && newHost.NatType != currHost.NatType {
currHost.NatType = newHost.NatType
diff --git a/logic/jwts.go b/logic/jwts.go
index 41181fcd6..a70bbbd2d 100644
--- a/logic/jwts.go
+++ b/logic/jwts.go
@@ -56,8 +56,9 @@ func CreateJWT(uuid string, macAddress string, network string) (response string,
func CreateUserJWT(username string, role models.UserRoleID) (response string, err error) {
expirationTime := time.Now().Add(servercfg.GetServerConfig().JwtValidityDuration)
claims := &models.UserClaims{
- UserName: username,
- Role: role,
+ UserName: username,
+ Role: role,
+ RacAutoDisable: servercfg.GetRacAutoDisable() && (role != models.SuperAdminRole && role != models.AdminRole),
RegisteredClaims: jwt.RegisteredClaims{
Issuer: "Netmaker",
Subject: fmt.Sprintf("user|%s", username),
diff --git a/logic/networks.go b/logic/networks.go
index 14ad794e5..1617889d5 100644
--- a/logic/networks.go
+++ b/logic/networks.go
@@ -8,9 +8,11 @@ import (
"sort"
"strings"
"sync"
+ "time"
"github.com/c-robinson/iplib"
validator "github.com/go-playground/validator/v10"
+ "github.com/google/uuid"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic/acls/nodeacls"
@@ -175,6 +177,17 @@ func DeleteNetwork(network string) error {
if err != nil {
logger.Log(1, "failed to remove the node acls during network delete for network,", network)
}
+ // Delete default network enrollment key
+ keys, _ := GetAllEnrollmentKeys()
+ for _, key := range keys {
+ if key.Tags[0] == network {
+ if key.Default {
+ DeleteEnrollmentKey(key.Value, true)
+ break
+ }
+
+ }
+ }
nodeCount, err := GetNetworkNonServerNodeCount(network)
if nodeCount == 0 || database.IsEmptyRecord(err) {
// delete server nodes first then db records
@@ -233,6 +246,17 @@ func CreateNetwork(network models.Network) (models.Network, error) {
storeNetworkInCache(network.NetID, network)
}
+ _, _ = CreateEnrollmentKey(
+ 0,
+ time.Time{},
+ []string{network.NetID},
+ []string{network.NetID},
+ []models.TagID{},
+ true,
+ uuid.Nil,
+ true,
+ )
+
return network, nil
}
diff --git a/logic/nodes.go b/logic/nodes.go
index 75ad16a82..34eebe2e4 100644
--- a/logic/nodes.go
+++ b/logic/nodes.go
@@ -196,10 +196,6 @@ func DeleteNode(node *models.Node, purge bool) error {
if err := DeleteGatewayExtClients(node.ID.String(), node.Network); err != nil {
slog.Error("failed to delete ext clients", "nodeid", node.ID.String(), "error", err.Error())
}
- host, err := GetHost(node.HostID.String())
- if err == nil {
- go DeleteRole(models.GetRAGRoleID(node.Network, host.ID.String()), true)
- }
}
if node.IsRelayed {
// cleanup node from relayednodes on relay node
@@ -378,6 +374,20 @@ func GetAllNodes() ([]models.Node, error) {
return nodes, nil
}
+func AddStaticNodestoList(nodes []models.Node) []models.Node {
+ netMap := make(map[string]struct{})
+ for _, node := range nodes {
+ if _, ok := netMap[node.Network]; ok {
+ continue
+ }
+ if node.IsIngressGateway {
+ nodes = append(nodes, GetStaticNodesByNetwork(models.NetworkID(node.Network), false)...)
+ netMap[node.Network] = struct{}{}
+ }
+ }
+ return nodes
+}
+
// GetNetworkByNode - gets the network model from a node
func GetNetworkByNode(node *models.Node) (models.Network, error) {
@@ -393,7 +403,7 @@ func GetNetworkByNode(node *models.Node) (models.Network, error) {
}
// SetNodeDefaults - sets the defaults of a node to avoid empty fields
-func SetNodeDefaults(node *models.Node) {
+func SetNodeDefaults(node *models.Node, resetConnected bool) {
parentNetwork, _ := GetNetworkByNode(node)
_, cidr, err := net.ParseCIDR(parentNetwork.AddressRange)
@@ -414,8 +424,14 @@ func SetNodeDefaults(node *models.Node) {
node.SetLastModified()
node.SetLastCheckIn()
- node.SetDefaultConnected()
+
+ if resetConnected {
+ node.SetDefaultConnected()
+ }
node.SetExpirationDateTime()
+ if node.Tags == nil {
+ node.Tags = make(map[models.TagID]struct{})
+ }
}
// GetRecordKey - get record key
@@ -461,7 +477,7 @@ func GetDeletedNodeByID(uuid string) (models.Node, error) {
return models.Node{}, err
}
- SetNodeDefaults(&node)
+ SetNodeDefaults(&node, true)
return node, nil
}
@@ -531,7 +547,7 @@ func createNode(node *models.Node) error {
}
}
- SetNodeDefaults(node)
+ SetNodeDefaults(node, true)
defaultACLVal := acls.Allowed
parentNetwork, err := GetNetwork(node.Network)
@@ -694,3 +710,109 @@ func GetAllFailOvers() ([]models.Node, error) {
}
return igs, nil
}
+
+func GetTagMapWithNodes() (tagNodesMap map[models.TagID][]models.Node) {
+ tagNodesMap = make(map[models.TagID][]models.Node)
+ nodes, _ := GetAllNodes()
+ for _, nodeI := range nodes {
+ if nodeI.Tags == nil {
+ continue
+ }
+ for nodeTagID := range nodeI.Tags {
+ tagNodesMap[nodeTagID] = append(tagNodesMap[nodeTagID], nodeI)
+ }
+ }
+ return
+}
+
+func GetTagMapWithNodesByNetwork(netID models.NetworkID) (tagNodesMap map[models.TagID][]models.Node) {
+ tagNodesMap = make(map[models.TagID][]models.Node)
+ nodes, _ := GetNetworkNodes(netID.String())
+ for _, nodeI := range nodes {
+ if nodeI.Tags == nil {
+ continue
+ }
+ for nodeTagID := range nodeI.Tags {
+ tagNodesMap[nodeTagID] = append(tagNodesMap[nodeTagID], nodeI)
+ }
+ }
+ return AddTagMapWithStaticNodes(netID, tagNodesMap)
+}
+
+func AddTagMapWithStaticNodes(netID models.NetworkID,
+ tagNodesMap map[models.TagID][]models.Node) map[models.TagID][]models.Node {
+ extclients, err := GetNetworkExtClients(netID.String())
+ if err != nil {
+ return tagNodesMap
+ }
+ for _, extclient := range extclients {
+ if extclient.Tags == nil || extclient.RemoteAccessClientID != "" {
+ continue
+ }
+ for tagID := range extclient.Tags {
+ tagNodesMap[tagID] = append(tagNodesMap[tagID], models.Node{
+ IsStatic: true,
+ StaticNode: extclient,
+ })
+ }
+
+ }
+ return tagNodesMap
+}
+
+func GetNodesWithTag(tagID models.TagID) map[string]models.Node {
+ nMap := make(map[string]models.Node)
+ tag, err := GetTag(tagID)
+ if err != nil {
+ return nMap
+ }
+ nodes, _ := GetNetworkNodes(tag.Network.String())
+ for _, nodeI := range nodes {
+ if nodeI.Tags == nil {
+ continue
+ }
+ if _, ok := nodeI.Tags[tagID]; ok {
+ nMap[nodeI.ID.String()] = nodeI
+ }
+ }
+ return AddStaticNodesWithTag(tag, nMap)
+}
+
+func AddStaticNodesWithTag(tag models.Tag, nMap map[string]models.Node) map[string]models.Node {
+ extclients, err := GetNetworkExtClients(tag.Network.String())
+ if err != nil {
+ return nMap
+ }
+ for _, extclient := range extclients {
+ if extclient.RemoteAccessClientID != "" {
+ continue
+ }
+ if _, ok := extclient.Tags[tag.ID]; ok {
+ nMap[extclient.ClientID] = models.Node{
+ IsStatic: true,
+ StaticNode: extclient,
+ }
+ }
+
+ }
+ return nMap
+}
+
+func GetStaticNodeWithTag(tagID models.TagID) map[string]models.Node {
+ nMap := make(map[string]models.Node)
+ tag, err := GetTag(tagID)
+ if err != nil {
+ return nMap
+ }
+ extclients, err := GetNetworkExtClients(tag.Network.String())
+ if err != nil {
+ return nMap
+ }
+ for _, extclient := range extclients {
+ nMap[extclient.ClientID] = models.Node{
+ IsStatic: true,
+ StaticNode: extclient,
+ }
+ }
+ return nMap
+}
diff --git a/logic/peers.go b/logic/peers.go
index 77e76a2a9..e88f48630 100644
--- a/logic/peers.go
+++ b/logic/peers.go
@@ -74,7 +74,8 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
ServerVersion: servercfg.GetVersion(),
ServerAddrs: []models.ServerAddr{},
FwUpdate: models.FwUpdate{
- EgressInfo: make(map[string]models.EgressInfo),
+ EgressInfo: make(map[string]models.EgressInfo),
+ IngressInfo: make(map[string]models.IngressInfo),
},
PeerIDs: make(models.PeerMap, 0),
Peers: []wgtypes.PeerConfig{},
@@ -182,7 +183,7 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
})
}
if peer.IsIngressGateway {
- hostPeerUpdate.EgressRoutes = append(hostPeerUpdate.EgressRoutes, getExtpeersExtraRoutes(node, peer.Network)...)
+ hostPeerUpdate.EgressRoutes = append(hostPeerUpdate.EgressRoutes, getExtpeersExtraRoutes(node)...)
}
_, isFailOverPeer := node.FailOverPeers[peer.ID.String()]
if servercfg.IsPro {
@@ -241,6 +242,7 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
!peer.PendingDelete &&
peer.Connected &&
nodeacls.AreNodesAllowed(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID.String()), nodeacls.NodeID(peer.ID.String())) &&
+ IsNodeAllowedToCommunicate(node, peer) &&
(deletedNode == nil || (deletedNode != nil && peer.ID.String() != deletedNode.ID.String())) {
peerConfig.AllowedIPs = allowedips // only append allowed IPs if valid connection
}
@@ -287,8 +289,23 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
var extPeerIDAndAddrs []models.IDandAddr
var egressRoutes []models.EgressNetworkRoutes
if node.IsIngressGateway {
+ hostPeerUpdate.FwUpdate.IsIngressGw = true
extPeers, extPeerIDAndAddrs, egressRoutes, err = GetExtPeers(&node, &node)
if err == nil {
+ defaultUserPolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.UserPolicy)
+ defaultDevicePolicy, _ := GetDefaultPolicy(models.NetworkID(node.Network), models.DevicePolicy)
+ if !defaultDevicePolicy.Enabled || !defaultUserPolicy.Enabled {
+ ingFwUpdate := models.IngressInfo{
+ IngressID: node.ID.String(),
+ Network: node.NetworkRange,
+ Network6: node.NetworkRange6,
+ AllowAll: defaultDevicePolicy.Enabled && defaultUserPolicy.Default,
+ StaticNodeIps: GetStaticNodeIps(node),
+ Rules: GetFwRulesOnIngressGateway(node),
+ }
+ ingFwUpdate.EgressRanges, ingFwUpdate.EgressRanges6 = getExtpeerEgressRanges(node)
+ hostPeerUpdate.FwUpdate.IngressInfo[node.ID.String()] = ingFwUpdate
+ }
hostPeerUpdate.EgressRoutes = append(hostPeerUpdate.EgressRoutes, egressRoutes...)
hostPeerUpdate.Peers = append(hostPeerUpdate.Peers, extPeers...)
for _, extPeerIdAndAddr := range extPeerIDAndAddrs {
@@ -391,6 +408,7 @@ func GetPeerUpdateForHost(network string, host *models.Host, allNodes []models.N
}
}
+ hostPeerUpdate.ManageDNS = servercfg.GetManageDNS()
return hostPeerUpdate, nil
}
@@ -425,6 +443,7 @@ func GetAllowedIPs(node, peer *models.Node, metrics *models.Metrics) []net.IPNet
logger.Log(2, "could not retrieve ext peers for ", peer.ID.String(), err.Error())
}
for _, extPeer := range extPeers {
+
allowedips = append(allowedips, extPeer.AllowedIPs...)
}
}
diff --git a/logic/proc.go b/logic/proc.go
new file mode 100644
index 000000000..fec258d75
--- /dev/null
+++ b/logic/proc.go
@@ -0,0 +1,24 @@
+package logic
+
+import (
+ "os"
+ "runtime/pprof"
+
+ "github.com/gravitl/netmaker/logger"
+)
+
+func StartCPUProfiling() *os.File {
+ f, err := os.OpenFile("/root/data/cpu.prof", os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0755)
+ if err != nil {
+ logger.Log(0, "could not create CPU profile: ", err.Error())
+ }
+ if err := pprof.StartCPUProfile(f); err != nil {
+ logger.Log(0, "could not start CPU profile: ", err.Error())
+ }
+ return f
+}
+
+func StopCPUProfiling(f *os.File) {
+ pprof.StopCPUProfile()
+ f.Close()
+}
diff --git a/logic/tags.go b/logic/tags.go
new file mode 100644
index 000000000..7cdf0f324
--- /dev/null
+++ b/logic/tags.go
@@ -0,0 +1,290 @@
+package logic
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "regexp"
+ "sort"
+ "sync"
+ "time"
+
+ "github.com/gravitl/netmaker/database"
+ "github.com/gravitl/netmaker/models"
+ "golang.org/x/exp/slog"
+)
+
+var tagMutex = &sync.RWMutex{}
+
+// GetTag - fetches tag info
+func GetTag(tagID models.TagID) (models.Tag, error) {
+ data, err := database.FetchRecord(database.TAG_TABLE_NAME, tagID.String())
+ if err != nil {
+ return models.Tag{}, err
+ }
+ tag := models.Tag{}
+ err = json.Unmarshal([]byte(data), &tag)
+ if err != nil {
+ return tag, err
+ }
+ return tag, nil
+}
+
+// InsertTag - creates new tag
+func InsertTag(tag models.Tag) error {
+ tagMutex.Lock()
+ defer tagMutex.Unlock()
+ _, err := database.FetchRecord(database.TAG_TABLE_NAME, tag.ID.String())
+ if err == nil {
+ return fmt.Errorf("tag `%s` exists already", tag.ID)
+ }
+ d, err := json.Marshal(tag)
+ if err != nil {
+ return err
+ }
+ return database.Insert(tag.ID.String(), string(d), database.TAG_TABLE_NAME)
+}
+
+// DeleteTag - delete tag, will also untag hosts
+func DeleteTag(tagID models.TagID, removeFromPolicy bool) error {
+ tagMutex.Lock()
+ defer tagMutex.Unlock()
+ // cleanUp tags on hosts
+ tag, err := GetTag(tagID)
+ if err != nil {
+ return err
+ }
+ nodes, err := GetNetworkNodes(tag.Network.String())
+ if err != nil {
+ return err
+ }
+ for _, nodeI := range nodes {
+ nodeI := nodeI
+ if _, ok := nodeI.Tags[tagID]; ok {
+ delete(nodeI.Tags, tagID)
+ UpsertNode(&nodeI)
+ }
+ }
+ if removeFromPolicy {
+ // remove tag used on acl policy
+ go RemoveDeviceTagFromAclPolicies(tagID, tag.Network)
+ }
+ extclients, _ := GetNetworkExtClients(tag.Network.String())
+ for _, extclient := range extclients {
+ if _, ok := extclient.Tags[tagID]; ok {
+ delete(extclient.Tags, tagID)
+ SaveExtClient(&extclient)
+ }
+ }
+ return database.DeleteRecord(database.TAG_TABLE_NAME, tagID.String())
+}
+
+// ListTagsWithHosts - lists all tags with tagged hosts
+func ListTagsWithNodes(netID models.NetworkID) ([]models.TagListResp, error) {
+ tags, err := ListNetworkTags(netID)
+ if err != nil {
+ return []models.TagListResp{}, err
+ }
+ tagsNodeMap := GetTagMapWithNodesByNetwork(netID)
+ resp := []models.TagListResp{}
+ for _, tagI := range tags {
+ tagRespI := models.TagListResp{
+ Tag: tagI,
+ UsedByCnt: len(tagsNodeMap[tagI.ID]),
+ TaggedNodes: GetAllNodesAPI(tagsNodeMap[tagI.ID]),
+ }
+ resp = append(resp, tagRespI)
+ }
+ return resp, nil
+}
+
+// ListTags - lists all tags from DB
+func ListTags() ([]models.Tag, error) {
+ tagMutex.RLock()
+ defer tagMutex.RUnlock()
+ data, err := database.FetchRecords(database.TAG_TABLE_NAME)
+ if err != nil && !database.IsEmptyRecord(err) {
+ return []models.Tag{}, err
+ }
+ tags := []models.Tag{}
+ for _, dataI := range data {
+ tag := models.Tag{}
+ err := json.Unmarshal([]byte(dataI), &tag)
+ if err != nil {
+ continue
+ }
+ tags = append(tags, tag)
+ }
+ return tags, nil
+}
+
+// ListTags - lists all tags from DB
+func ListNetworkTags(netID models.NetworkID) ([]models.Tag, error) {
+ tagMutex.RLock()
+ defer tagMutex.RUnlock()
+ data, err := database.FetchRecords(database.TAG_TABLE_NAME)
+ if err != nil && !database.IsEmptyRecord(err) {
+ return []models.Tag{}, err
+ }
+ tags := []models.Tag{}
+ for _, dataI := range data {
+ tag := models.Tag{}
+ err := json.Unmarshal([]byte(dataI), &tag)
+ if err != nil {
+ continue
+ }
+ if tag.Network == netID {
+ tags = append(tags, tag)
+ }
+
+ }
+ return tags, nil
+}
+
+// UpdateTag - updates and syncs hosts with tag update
+func UpdateTag(req models.UpdateTagReq, newID models.TagID) {
+ tagMutex.Lock()
+ defer tagMutex.Unlock()
+ var err error
+ tagNodesMap := GetNodesWithTag(req.ID)
+ for _, apiNode := range req.TaggedNodes {
+ node := models.Node{}
+ var nodeID string
+ if apiNode.IsStatic {
+ if apiNode.StaticNode.RemoteAccessClientID != "" {
+ continue
+ }
+ extclient, err := GetExtClient(apiNode.StaticNode.ClientID, apiNode.StaticNode.Network)
+ if err != nil {
+ continue
+ }
+ node.IsStatic = true
+ nodeID = extclient.ClientID
+ node.StaticNode = extclient
+ } else {
+ node, err = GetNodeByID(apiNode.ID)
+ if err != nil {
+ continue
+ }
+ nodeID = node.ID.String()
+ }
+
+ if _, ok := tagNodesMap[nodeID]; !ok {
+ if node.StaticNode.Tags == nil {
+ node.StaticNode.Tags = make(map[models.TagID]struct{})
+ }
+ if node.Tags == nil {
+ node.Tags = make(map[models.TagID]struct{})
+ }
+ if newID != "" {
+ if node.IsStatic {
+ node.StaticNode.Tags[newID] = struct{}{}
+ SaveExtClient(&node.StaticNode)
+ } else {
+ node.Tags[newID] = struct{}{}
+ UpsertNode(&node)
+ }
+
+ } else {
+ if node.IsStatic {
+ node.StaticNode.Tags[req.ID] = struct{}{}
+ SaveExtClient(&node.StaticNode)
+ } else {
+ node.Tags[req.ID] = struct{}{}
+ UpsertNode(&node)
+ }
+ }
+ } else {
+ if newID != "" {
+ delete(node.Tags, req.ID)
+ delete(node.StaticNode.Tags, req.ID)
+ if node.IsStatic {
+ node.StaticNode.Tags[newID] = struct{}{}
+ SaveExtClient(&node.StaticNode)
+ } else {
+ node.Tags[newID] = struct{}{}
+ UpsertNode(&node)
+ }
+ }
+ delete(tagNodesMap, nodeID)
+ }
+
+ }
+ for _, deletedTaggedNode := range tagNodesMap {
+ delete(deletedTaggedNode.Tags, req.ID)
+ delete(deletedTaggedNode.StaticNode.Tags, req.ID)
+ if deletedTaggedNode.IsStatic {
+ SaveExtClient(&deletedTaggedNode.StaticNode)
+ } else {
+ UpsertNode(&deletedTaggedNode)
+ }
+ }
+ go func(req models.UpdateTagReq) {
+ if newID != "" {
+ tagNodesMap = GetNodesWithTag(req.ID)
+ for _, nodeI := range tagNodesMap {
+ nodeI := nodeI
+ if nodeI.StaticNode.Tags == nil {
+ nodeI.StaticNode.Tags = make(map[models.TagID]struct{})
+ }
+ if nodeI.Tags == nil {
+ nodeI.Tags = make(map[models.TagID]struct{})
+ }
+ delete(nodeI.Tags, req.ID)
+ delete(nodeI.StaticNode.Tags, req.ID)
+ nodeI.Tags[newID] = struct{}{}
+ nodeI.StaticNode.Tags[newID] = struct{}{}
+ if nodeI.IsStatic {
+ SaveExtClient(&nodeI.StaticNode)
+ } else {
+ UpsertNode(&nodeI)
+ }
+ }
+ }
+ }(req)
+
+}
+
+// SortTagEntrys - Sorts slice of Tag entries by their id
+func SortTagEntrys(tags []models.TagListResp) {
+ sort.Slice(tags, func(i, j int) bool {
+ return tags[i].ID < tags[j].ID
+ })
+}
+
+func CheckIDSyntax(id string) error {
+ if id == "" {
+ return errors.New("name is required")
+ }
+ if len(id) < 3 {
+ return errors.New("name should have min 3 characters")
+ }
+ reg, err := regexp.Compile("^[a-zA-Z-]+$")
+ if err != nil {
+ return err
+ }
+ if !reg.MatchString(id) {
+ return errors.New("invalid name. allowed characters are [a-zA-Z-]")
+ }
+ return nil
+}
+
+func CreateDefaultTags(netID models.NetworkID) {
+ // create tag for remote access gws in the network
+ tag := models.Tag{
+ ID: models.TagID(fmt.Sprintf("%s.%s", netID.String(), models.RemoteAccessTagName)),
+ TagName: models.RemoteAccessTagName,
+ Network: netID,
+ CreatedBy: "auto",
+ CreatedAt: time.Now(),
+ }
+ _, err := GetTag(tag.ID)
+ if err == nil {
+ return
+ }
+ err = InsertTag(tag)
+ if err != nil {
+ slog.Error("failed to create remote access gw tag", "error", err.Error())
+ return
+ }
+}
diff --git a/logic/user_mgmt.go b/logic/user_mgmt.go
index 8727cb57a..56395c78c 100644
--- a/logic/user_mgmt.go
+++ b/logic/user_mgmt.go
@@ -39,17 +39,28 @@ var FilterNetworksByRole = func(allnetworks []models.Network, user models.User)
var IsGroupsValid = func(groups map[models.UserGroupID]struct{}) error {
return nil
}
+var IsGroupValid = func(groupID models.UserGroupID) error {
+ return nil
+}
var IsNetworkRolesValid = func(networkRoles map[models.NetworkID]map[models.UserRoleID]struct{}) error {
return nil
}
+var MigrateUserRoleAndGroups = func(u models.User) {
+
+}
+
var UpdateUserGwAccess = func(currentUser, changeUser models.User) {}
var UpdateRole = func(r models.UserRolePermissionTemplate) error { return nil }
var InitialiseRoles = userRolesInit
+var IntialiseGroups = func() {}
var DeleteNetworkRoles = func(netID string) {}
var CreateDefaultNetworkRolesAndGroups = func(netID models.NetworkID) {}
+var CreateDefaultUserPolicies = func(netID models.NetworkID) {}
+var GetUserGroupsInNetwork = func(netID models.NetworkID) (networkGrps map[models.UserGroupID]models.UserGroup) { return }
+var AddGlobalNetRolesToAdmins = func(u models.User) {}
// GetRole - fetches role template by id
func GetRole(roleID models.UserRoleID) (models.UserRolePermissionTemplate, error) {
diff --git a/main.go b/main.go
index 027b51c72..10bb52b8b 100644
--- a/main.go
+++ b/main.go
@@ -27,10 +27,10 @@ import (
"golang.org/x/exp/slog"
)
-var version = "v0.25.0"
+var version = "v0.26.0"
// @title NetMaker
-// @version 0.24.3
+// @version 0.26.0
// @description NetMaker API Docs
// @tag.name APIUsage
// @tag.description.markdown
@@ -103,6 +103,7 @@ func initialize() { // Client Mode Prereq Check
logic.SetJWTSecret()
logic.InitialiseRoles()
+ logic.IntialiseGroups()
err = serverctl.SetDefaults()
if err != nil {
logger.FatalLog("error setting defaults: ", err.Error())
diff --git a/migrate/migrate.go b/migrate/migrate.go
index 6612d4ddc..51e74ab4c 100644
--- a/migrate/migrate.go
+++ b/migrate/migrate.go
@@ -8,6 +8,7 @@ import (
"golang.org/x/exp/slog"
+ "github.com/google/uuid"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
@@ -21,6 +22,7 @@ import (
func Run() {
updateEnrollmentKeys()
assignSuperAdmin()
+ createDefaultTagsAndPolicies()
removeOldUserGrps()
syncUsers()
updateHosts()
@@ -122,6 +124,35 @@ func updateEnrollmentKeys() {
}
}
+
+ existingKeys, err := logic.GetAllEnrollmentKeys()
+ if err != nil {
+ return
+ }
+ // check if any tags are duplicate
+ existingTags := make(map[string]struct{})
+ for _, existingKey := range existingKeys {
+ for _, t := range existingKey.Tags {
+ existingTags[t] = struct{}{}
+ }
+ }
+ networks, _ := logic.GetNetworks()
+ for _, network := range networks {
+ if _, ok := existingTags[network.NetID]; ok {
+ continue
+ }
+ _, _ = logic.CreateEnrollmentKey(
+ 0,
+ time.Time{},
+ []string{network.NetID},
+ []string{network.NetID},
+ []models.TagID{},
+ true,
+ uuid.Nil,
+ true,
+ )
+
+ }
}
func removeOldUserGrps() {
@@ -166,6 +197,26 @@ func updateNodes() {
return
}
for _, node := range nodes {
+ node := node
+ if node.Tags == nil {
+ node.Tags = make(map[models.TagID]struct{})
+ logic.UpsertNode(&node)
+ }
+ if node.IsIngressGateway {
+ tagID := models.TagID(fmt.Sprintf("%s.%s", node.Network,
+ models.RemoteAccessTagName))
+ if node.Tags == nil {
+ node.Tags = make(map[models.TagID]struct{})
+ }
+ if _, ok := node.Tags[tagID]; !ok {
+ node.Tags[tagID] = struct{}{}
+ logic.UpsertNode(&node)
+ }
+ host, err := logic.GetHost(node.HostID.String())
+ if err == nil {
+ go logic.DeleteRole(models.GetRAGRoleID(node.Network, host.ID.String()), true)
+ }
+ }
if node.IsEgressGateway {
egressRanges, update := removeInterGw(node.EgressGatewayRanges)
if update {
@@ -175,6 +226,18 @@ func updateNodes() {
}
}
}
+ extclients, _ := logic.GetAllExtClients()
+ for _, extclient := range extclients {
+ tagID := models.TagID(fmt.Sprintf("%s.%s", extclient.Network,
+ models.RemoteAccessTagName))
+ if extclient.Tags == nil {
+ extclient.Tags = make(map[models.TagID]struct{})
+ }
+ if _, ok := extclient.Tags[tagID]; !ok {
+ extclient.Tags[tagID] = struct{}{}
+ logic.SaveExtClient(&extclient)
+ }
+ }
}
func removeInterGw(egressRanges []string) ([]string, bool) {
@@ -330,42 +393,8 @@ func syncUsers() {
// create default network user roles for existing networks
if servercfg.IsPro {
networks, _ := logic.GetNetworks()
- nodes, err := logic.GetAllNodes()
- if err == nil {
- for _, netI := range networks {
- logic.CreateDefaultNetworkRolesAndGroups(models.NetworkID(netI.NetID))
- networkNodes := logic.GetNetworkNodesMemory(nodes, netI.NetID)
- for _, networkNodeI := range networkNodes {
- if networkNodeI.IsIngressGateway {
- h, err := logic.GetHost(networkNodeI.HostID.String())
- if err == nil {
- logic.CreateRole(models.UserRolePermissionTemplate{
- ID: models.GetRAGRoleID(networkNodeI.Network, h.ID.String()),
- UiName: models.GetRAGRoleName(networkNodeI.Network, h.Name),
- NetworkID: models.NetworkID(netI.NetID),
- NetworkLevelAccess: map[models.RsrcType]map[models.RsrcID]models.RsrcPermissionScope{
- models.RemoteAccessGwRsrc: {
- models.RsrcID(networkNodeI.ID.String()): models.RsrcPermissionScope{
- Read: true,
- VPNaccess: true,
- },
- },
- models.ExtClientsRsrc: {
- models.AllExtClientsRsrcID: models.RsrcPermissionScope{
- Read: true,
- Create: true,
- Update: true,
- Delete: true,
- SelfOnly: true,
- },
- },
- },
- })
- }
-
- }
- }
- }
+ for _, netI := range networks {
+ logic.CreateDefaultNetworkRolesAndGroups(models.NetworkID(netI.NetID))
}
}
@@ -382,6 +411,8 @@ func syncUsers() {
logic.UpsertUser(user)
}
if user.PlatformRoleID.String() != "" {
+ logic.MigrateUserRoleAndGroups(user)
+ logic.AddGlobalNetRolesToAdmins(user)
continue
}
user.AuthType = models.BasicAuth
@@ -403,32 +434,20 @@ func syncUsers() {
user.PlatformRoleID = models.ServiceUser
}
logic.UpsertUser(user)
- if len(user.RemoteGwIDs) > 0 {
- // define user roles for network
- // assign relevant network role to user
- for remoteGwID := range user.RemoteGwIDs {
- gwNode, err := logic.GetNodeByID(remoteGwID)
- if err != nil {
- continue
- }
- h, err := logic.GetHost(gwNode.HostID.String())
- if err != nil {
- continue
- }
- r, err := logic.GetRole(models.GetRAGRoleID(gwNode.Network, h.ID.String()))
- if err != nil {
- continue
- }
- if netRoles, ok := user.NetworkRoles[models.NetworkID(gwNode.Network)]; ok {
- netRoles[r.ID] = struct{}{}
- } else {
- user.NetworkRoles[models.NetworkID(gwNode.Network)] = map[models.UserRoleID]struct{}{
- r.ID: {},
- }
- }
- }
- logic.UpsertUser(user)
- }
+ logic.AddGlobalNetRolesToAdmins(user)
+ logic.MigrateUserRoleAndGroups(user)
}
}
+
+}
+
+func createDefaultTagsAndPolicies() {
+ networks, err := logic.GetNetworks()
+ if err != nil {
+ return
+ }
+ for _, network := range networks {
+ logic.CreateDefaultTags(models.NetworkID(network.NetID))
+ logic.CreateDefaultAclNetworkPolicies(models.NetworkID(network.NetID))
+ }
}
diff --git a/models/acl.go b/models/acl.go
new file mode 100644
index 000000000..d8c302ca9
--- /dev/null
+++ b/models/acl.go
@@ -0,0 +1,72 @@
+package models
+
+import (
+ "time"
+)
+
+// AllowedTrafficDirection - allowed direction of traffic
+type AllowedTrafficDirection int
+
+const (
+ // TrafficDirectionUni implies traffic is only allowed in one direction (src --> dst)
+ TrafficDirectionUni AllowedTrafficDirection = iota
+ // TrafficDirectionBi implies traffic is allowed both direction (src <--> dst )
+ TrafficDirectionBi
+)
+
+type AclPolicyType string
+
+const (
+ UserPolicy AclPolicyType = "user-policy"
+ DevicePolicy AclPolicyType = "device-policy"
+)
+
+type AclPolicyTag struct {
+ ID AclGroupType `json:"id"`
+ Value string `json:"value"`
+}
+
+type AclGroupType string
+
+const (
+ UserAclID AclGroupType = "user"
+ UserGroupAclID AclGroupType = "user-group"
+ DeviceAclID AclGroupType = "tag"
+ NetmakerIPAclID AclGroupType = "ip"
+ NetmakerSubNetRangeAClID AclGroupType = "ipset"
+)
+
+func (g AclGroupType) String() string {
+ return string(g)
+}
+
+type UpdateAclRequest struct {
+ Acl
+ NewName string `json:"new_name"`
+}
+
+type AclPolicy struct {
+ TypeID AclPolicyType
+ PrefixTagUser AclGroupType
+}
+
+type Acl struct {
+ ID string `json:"id"`
+ Default bool `json:"default"`
+ MetaData string `json:"meta_data"`
+ Name string `json:"name"`
+ NetworkID NetworkID `json:"network_id"`
+ RuleType AclPolicyType `json:"policy_type"`
+ Src []AclPolicyTag `json:"src_type"`
+ Dst []AclPolicyTag `json:"dst_type"`
+ AllowedDirection AllowedTrafficDirection `json:"allowed_traffic_direction"`
+ Enabled bool `json:"enabled"`
+ CreatedBy string `json:"created_by"`
+ CreatedAt time.Time `json:"created_at"`
+}
+
+type AclPolicyTypes struct {
+ RuleTypes []AclPolicyType `json:"policy_types"`
+ SrcGroupTypes []AclGroupType `json:"src_grp_types"`
+ DstGroupTypes []AclGroupType `json:"dst_grp_types"`
+}
diff --git a/models/api_node.go b/models/api_node.go
index e7005f327..30e08c639 100644
--- a/models/api_node.go
+++ b/models/api_node.go
@@ -10,33 +10,35 @@ import (
// ApiNode is a stripped down Node DTO that exposes only required fields to external systems
type ApiNode struct {
- ID string `json:"id,omitempty" validate:"required,min=5,id_unique"`
- HostID string `json:"hostid,omitempty" validate:"required,min=5,id_unique"`
- Address string `json:"address" validate:"omitempty,cidrv4"`
- Address6 string `json:"address6" validate:"omitempty,cidrv6"`
- LocalAddress string `json:"localaddress" validate:"omitempty,cidr"`
- AllowedIPs []string `json:"allowedips"`
- LastModified int64 `json:"lastmodified"`
- ExpirationDateTime int64 `json:"expdatetime"`
- LastCheckIn int64 `json:"lastcheckin"`
- LastPeerUpdate int64 `json:"lastpeerupdate"`
- Network string `json:"network"`
- NetworkRange string `json:"networkrange"`
- NetworkRange6 string `json:"networkrange6"`
- IsRelayed bool `json:"isrelayed"`
- IsRelay bool `json:"isrelay"`
- RelayedBy string `json:"relayedby" bson:"relayedby" yaml:"relayedby"`
- RelayedNodes []string `json:"relaynodes" yaml:"relayedNodes"`
- IsEgressGateway bool `json:"isegressgateway"`
- IsIngressGateway bool `json:"isingressgateway"`
- EgressGatewayRanges []string `json:"egressgatewayranges"`
- EgressGatewayNatEnabled bool `json:"egressgatewaynatenabled"`
- DNSOn bool `json:"dnson"`
- IngressDns string `json:"ingressdns"`
- Server string `json:"server"`
- Connected bool `json:"connected"`
- PendingDelete bool `json:"pendingdelete"`
- Metadata string `json:"metadata"`
+ ID string `json:"id,omitempty" validate:"required,min=5,id_unique"`
+ HostID string `json:"hostid,omitempty" validate:"required,min=5,id_unique"`
+ Address string `json:"address" validate:"omitempty,cidrv4"`
+ Address6 string `json:"address6" validate:"omitempty,cidrv6"`
+ LocalAddress string `json:"localaddress" validate:"omitempty,cidr"`
+ AllowedIPs []string `json:"allowedips"`
+ LastModified int64 `json:"lastmodified"`
+ ExpirationDateTime int64 `json:"expdatetime"`
+ LastCheckIn int64 `json:"lastcheckin"`
+ LastPeerUpdate int64 `json:"lastpeerupdate"`
+ Network string `json:"network"`
+ NetworkRange string `json:"networkrange"`
+ NetworkRange6 string `json:"networkrange6"`
+ IsRelayed bool `json:"isrelayed"`
+ IsRelay bool `json:"isrelay"`
+ RelayedBy string `json:"relayedby" bson:"relayedby" yaml:"relayedby"`
+ RelayedNodes []string `json:"relaynodes" yaml:"relayedNodes"`
+ IsEgressGateway bool `json:"isegressgateway"`
+ IsIngressGateway bool `json:"isingressgateway"`
+ EgressGatewayRanges []string `json:"egressgatewayranges"`
+ EgressGatewayNatEnabled bool `json:"egressgatewaynatenabled"`
+ DNSOn bool `json:"dnson"`
+ IngressDns string `json:"ingressdns"`
+ IngressPersistentKeepalive int32 `json:"ingresspersistentkeepalive"`
+ IngressMTU int32 `json:"ingressmtu"`
+ Server string `json:"server"`
+ Connected bool `json:"connected"`
+ PendingDelete bool `json:"pendingdelete"`
+ Metadata string `json:"metadata"`
// == PRO ==
DefaultACL string `json:"defaultacl,omitempty" validate:"checkyesornoorunset"`
IsFailOver bool `json:"is_fail_over"`
@@ -46,6 +48,10 @@ type ApiNode struct {
InetNodeReq InetNodeReq `json:"inet_node_req" yaml:"inet_node_req"`
InternetGwID string `json:"internetgw_node_id" yaml:"internetgw_node_id"`
AdditionalRagIps []string `json:"additional_rag_ips" yaml:"additional_rag_ips"`
+ Tags map[TagID]struct{} `json:"tags" yaml:"tags"`
+ IsStatic bool `json:"is_static"`
+ IsUserNode bool `json:"is_user_node"`
+ StaticNode ExtClient `json:"static_node"`
}
// ApiNode.ConvertToServerNode - converts an api node to a server node
@@ -72,6 +78,8 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node {
convertedNode.IngressGatewayRange6 = currentNode.IngressGatewayRange6
convertedNode.DNSOn = a.DNSOn
convertedNode.IngressDNS = a.IngressDns
+ convertedNode.IngressPersistentKeepalive = a.IngressPersistentKeepalive
+ convertedNode.IngressMTU = a.IngressMTU
convertedNode.IsInternetGateway = a.IsInternetGateway
convertedNode.EgressGatewayRequest = currentNode.EgressGatewayRequest
convertedNode.EgressGatewayNatEnabled = currentNode.EgressGatewayNatEnabled
@@ -119,6 +127,7 @@ func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node {
}
convertedNode.AdditionalRagIps = append(convertedNode.AdditionalRagIps, ragIp)
}
+ convertedNode.Tags = a.Tags
return &convertedNode
}
@@ -162,6 +171,8 @@ func (nm *Node) ConvertToAPINode() *ApiNode {
apiNode.EgressGatewayNatEnabled = nm.EgressGatewayNatEnabled
apiNode.DNSOn = nm.DNSOn
apiNode.IngressDns = nm.IngressDNS
+ apiNode.IngressPersistentKeepalive = nm.IngressPersistentKeepalive
+ apiNode.IngressMTU = nm.IngressMTU
apiNode.Server = nm.Server
apiNode.Connected = nm.Connected
apiNode.PendingDelete = nm.PendingDelete
@@ -174,9 +185,13 @@ func (nm *Node) ConvertToAPINode() *ApiNode {
apiNode.FailedOverBy = nm.FailedOverBy
apiNode.Metadata = nm.Metadata
apiNode.AdditionalRagIps = []string{}
+ apiNode.Tags = nm.Tags
for _, ip := range nm.AdditionalRagIps {
apiNode.AdditionalRagIps = append(apiNode.AdditionalRagIps, ip.String())
}
+ apiNode.IsStatic = nm.IsStatic
+ apiNode.IsUserNode = nm.IsUserNode
+ apiNode.StaticNode = nm.StaticNode
return &apiNode
}
diff --git a/models/dnsEntry.go b/models/dnsEntry.go
index 11e9dd6b4..596d92258 100644
--- a/models/dnsEntry.go
+++ b/models/dnsEntry.go
@@ -42,8 +42,8 @@ type DNSUpdate struct {
// DNSEntry - a DNS entry represented as struct
type DNSEntry struct {
- Address string `json:"address" validate:"ip"`
- Address6 string `json:"address6"`
+ Address string `json:"address" validate:"omitempty,ip"`
+ Address6 string `json:"address6" validate:"omitempty,ip"`
Name string `json:"name" validate:"required,name_unique,min=1,max=192,whitespace"`
Network string `json:"network" validate:"network_exists"`
}
diff --git a/models/enrollment_key.go b/models/enrollment_key.go
index e775344df..f133d7558 100644
--- a/models/enrollment_key.go
+++ b/models/enrollment_key.go
@@ -52,6 +52,8 @@ type EnrollmentKey struct {
Token string `json:"token,omitempty"` // B64 value of EnrollmentToken
Type KeyType `json:"type"`
Relay uuid.UUID `json:"relay"`
+ Groups []TagID `json:"groups"`
+ Default bool `json:"default"`
}
// APIEnrollmentKey - used to create enrollment keys via API
@@ -63,6 +65,7 @@ type APIEnrollmentKey struct {
Tags []string `json:"tags" validate:"required,dive,min=3,max=32"`
Type KeyType `json:"type"`
Relay string `json:"relay"`
+ Groups []TagID `json:"groups"`
}
// RegisterResponse - the response to a successful enrollment register
diff --git a/models/extclient.go b/models/extclient.go
index 9d67207d3..a6214c34a 100644
--- a/models/extclient.go
+++ b/models/extclient.go
@@ -20,6 +20,7 @@ type ExtClient struct {
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"`
+ Tags map[TagID]struct{} `json:"tags"`
}
// CustomExtClient - struct for CustomExtClient params
@@ -33,4 +34,17 @@ type CustomExtClient struct {
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"`
+ Tags map[TagID]struct{} `json:"tags"`
+}
+
+func (ext *ExtClient) ConvertToStaticNode() Node {
+
+ return Node{
+ CommonNode: CommonNode{
+ Network: ext.Network,
+ },
+ Tags: ext.Tags,
+ IsStatic: true,
+ StaticNode: *ext,
+ }
}
diff --git a/models/metrics.go b/models/metrics.go
index 686e3e969..459c7f17c 100644
--- a/models/metrics.go
+++ b/models/metrics.go
@@ -14,15 +14,17 @@ type Metrics struct {
// Metric - holds a metric for data between nodes
type Metric struct {
- NodeName string `json:"node_name" bson:"node_name" yaml:"node_name"`
- Uptime int64 `json:"uptime" bson:"uptime" yaml:"uptime"`
- TotalTime int64 `json:"totaltime" bson:"totaltime" yaml:"totaltime"`
- Latency int64 `json:"latency" bson:"latency" yaml:"latency"`
- TotalReceived int64 `json:"totalreceived" bson:"totalreceived" yaml:"totalreceived"`
- TotalSent int64 `json:"totalsent" bson:"totalsent" yaml:"totalsent"`
- ActualUptime time.Duration `json:"actualuptime" bson:"actualuptime" yaml:"actualuptime"`
- PercentUp float64 `json:"percentup" bson:"percentup" yaml:"percentup"`
- Connected bool `json:"connected" bson:"connected" yaml:"connected"`
+ NodeName string `json:"node_name" bson:"node_name" yaml:"node_name"`
+ Uptime int64 `json:"uptime" bson:"uptime" yaml:"uptime"`
+ TotalTime int64 `json:"totaltime" bson:"totaltime" yaml:"totaltime"`
+ Latency int64 `json:"latency" bson:"latency" yaml:"latency"`
+ TotalReceived int64 `json:"totalreceived" bson:"totalreceived" yaml:"totalreceived"`
+ LastTotalReceived int64 `json:"lasttotalreceived" bson:"lasttotalreceived" yaml:"lasttotalreceived"`
+ TotalSent int64 `json:"totalsent" bson:"totalsent" yaml:"totalsent"`
+ LastTotalSent int64 `json:"lasttotalsent" bson:"lasttotalsent" yaml:"lasttotalsent"`
+ ActualUptime time.Duration `json:"actualuptime" bson:"actualuptime" yaml:"actualuptime"`
+ PercentUp float64 `json:"percentup" bson:"percentup" yaml:"percentup"`
+ Connected bool `json:"connected" bson:"connected" yaml:"connected"`
}
// IDandAddr - struct to hold ID and primary Address
diff --git a/models/mqtt.go b/models/mqtt.go
index c0d52d9c2..f0048a014 100644
--- a/models/mqtt.go
+++ b/models/mqtt.go
@@ -24,12 +24,25 @@ type HostPeerUpdate struct {
FwUpdate FwUpdate `json:"fw_update"`
ReplacePeers bool `json:"replace_peers"`
EndpointDetection bool `json:"endpoint_detection"`
+ ManageDNS bool `yaml:"manage_dns"`
+}
+
+type FwRule struct {
+ SrcIP net.IPNet
+ DstIP net.IPNet
+ Allow bool
}
// IngressInfo - struct for ingress info
type IngressInfo struct {
- ExtPeers map[string]ExtClientInfo `json:"ext_peers" yaml:"ext_peers"`
- EgressRanges []string `json:"egress_ranges" yaml:"egress_ranges"`
+ IngressID string `json:"ingress_id"`
+ Network net.IPNet `json:"network"`
+ 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"`
}
// EgressInfo - struct for egress info
@@ -77,8 +90,10 @@ type KeyUpdate struct {
// FwUpdate - struct for firewall updates
type FwUpdate struct {
- IsEgressGw bool `json:"is_egress_gw"`
- EgressInfo map[string]EgressInfo `json:"egress_info"`
+ IsEgressGw bool `json:"is_egress_gw"`
+ IsIngressGw bool `json:"is_ingress_gw"`
+ EgressInfo map[string]EgressInfo `json:"egress_info"`
+ IngressInfo map[string]IngressInfo `json:"ingress_info"`
}
// FailOverMeReq - struct for failover req
diff --git a/models/names.go b/models/names.go
index f5c1301fe..75f6a16c6 100644
--- a/models/names.go
+++ b/models/names.go
@@ -1,242 +1,18 @@
package models
import (
- "math/rand"
"time"
-)
-
-// NAMES - list of names 4-7 chars in length
-var NAMES = []string{
- "logic",
- "warrant",
- "iconic",
- "threat",
- "strike",
- "boy",
- "vital",
- "unity",
- "audio",
- "schemer",
- "depth",
- "gravitl",
- "mystic",
- "donkey",
- "atomic",
- "turtle",
- "monkey",
- "rabbit",
- "static",
- "mosaic",
- "elite",
- "stonks",
- "doggy",
- "python",
- "mohawk",
- "arctic",
- "rival",
- "vibes",
- "delay",
- "bridge",
- "weeble",
- "combat",
- "animal",
- "wobble",
- "rubble",
- "bucket",
- "proof",
- "worker",
- "beetle",
- "racket",
- "equal",
- "panda",
- "antics",
- "strong",
- "forum",
- "koala",
- "anchor",
- "ornery",
- "indigo",
- "schism",
- "dragon",
- "knight",
- "bishop",
- "laser",
- "rhino",
- "clutch",
- "shark",
- "leader",
- "young",
- "robot",
- "squish",
- "chimp",
- "rocket",
- "space",
- "queen",
- "royalty",
- "flush",
- "earth",
- "planet",
- "heart",
- "droplet",
- "dillon",
- "saturn",
- "pluto",
- "school",
- "alien",
- "matte",
- "dingo",
- "meercat",
- "cookie",
- "snack",
- "goose",
- "pepper",
- "melissa",
- "alex",
- "elon",
- "yeet",
- "meh",
- "walrus",
- "avatar",
- "chicken",
- "proton",
- "mohawk",
- "tattoo",
- "zebra",
- "star",
- "butter",
- "tango",
- "homie",
- "rambo",
- "cosmo",
- "bubbles",
- "hulk",
- "pluto",
- "scooby",
- "thanos",
- "yoda",
- "draco",
- "goofy",
- "ditto",
- "puff",
- "duck",
- "mouse",
- "akita",
- "water",
- "hound",
- "baby",
- "spider",
- "squid",
- "roach",
- "crab",
- "cougar",
- "cyborg",
- "android",
- "being",
- "ninja",
- "unicorn",
- "zombie",
- "warrior",
- "zamboni",
- "life",
- "marine",
- "node",
- "mother",
- "father",
- "tesla",
-}
-// SMALL_NAMES - list of small (4 char or less) names
-var SMALL_NAMES = []string{
- "ace",
- "odd",
- "hot",
- "ill",
- "root",
- "sudo",
- "moon",
- "beef",
- "bro",
- "dank",
- "red",
- "gold",
- "big",
- "old",
- "og",
- "best",
- "blue",
- "lil",
- "mom",
- "bot",
- "evil",
- "good",
- "holy",
- "rad",
- "bad",
- "sad",
- "mad",
- "chad",
- "pre",
- "post",
- "foot",
- "soft",
- "hard",
- "lite",
- "dark",
- "true",
- "toy",
- "soy",
- "rude",
- "nice",
- "fun",
- "fat",
- "pro",
- "sly",
- "tan",
- "pet",
- "fine",
- "main",
- "last",
- "wide",
- "free",
- "open",
- "poor",
- "rich",
- "next",
- "real",
- "long",
- "huge",
- "wild",
- "sick",
- "weak",
- "firm",
- "pink",
- "okay",
- "dull",
- "loud",
- "lazy",
- "dumb",
- "tidy",
- "idle",
- "bony",
- "cute",
- "oily",
- "lame",
- "mega",
- "limp",
- "wavy",
- "edgy",
- "nosy",
- "zany",
- "base",
- "cold",
-}
+ "github.com/goombaio/namegenerator"
+)
var logoString = retrieveLogo()
// GenerateNodeName - generates a random node name
func GenerateNodeName() string {
- rand.Seed(time.Now().UnixNano())
- return SMALL_NAMES[rand.Intn(len(SMALL_NAMES))] + "-" + NAMES[seededRand.Intn(len(NAMES))]
+ seed := time.Now().UTC().UnixNano()
+ nameGenerator := namegenerator.NewNameGenerator(seed)
+ return nameGenerator.Generate()
}
// RetrieveLogo - retrieves the ascii art logo for Netmaker
diff --git a/models/network.go b/models/network.go
index c29b45d74..32d95f865 100644
--- a/models/network.go
+++ b/models/network.go
@@ -97,3 +97,8 @@ func (network *Network) GetNetworkNetworkCIDR6() *net.IPNet {
_, netCidr, _ := net.ParseCIDR(network.AddressRange6)
return netCidr
}
+
+type NetworkStatResp struct {
+ Network
+ Hosts int `json:"hosts"`
+}
diff --git a/models/node.go b/models/node.go
index a15bc6c92..0ca699dbb 100644
--- a/models/node.go
+++ b/models/node.go
@@ -77,16 +77,18 @@ type CommonNode struct {
// Node - a model of a network node
type Node struct {
CommonNode
- PendingDelete bool `json:"pendingdelete" bson:"pendingdelete" yaml:"pendingdelete"`
- LastModified time.Time `json:"lastmodified" bson:"lastmodified" yaml:"lastmodified"`
- LastCheckIn time.Time `json:"lastcheckin" bson:"lastcheckin" yaml:"lastcheckin"`
- LastPeerUpdate time.Time `json:"lastpeerupdate" bson:"lastpeerupdate" yaml:"lastpeerupdate"`
- ExpirationDateTime time.Time `json:"expdatetime" bson:"expdatetime" yaml:"expdatetime"`
- EgressGatewayNatEnabled bool `json:"egressgatewaynatenabled" bson:"egressgatewaynatenabled" yaml:"egressgatewaynatenabled"`
- EgressGatewayRequest EgressGatewayRequest `json:"egressgatewayrequest" bson:"egressgatewayrequest" yaml:"egressgatewayrequest"`
- IngressGatewayRange string `json:"ingressgatewayrange" bson:"ingressgatewayrange" yaml:"ingressgatewayrange"`
- IngressGatewayRange6 string `json:"ingressgatewayrange6" bson:"ingressgatewayrange6" yaml:"ingressgatewayrange6"`
- Metadata string `json:"metadata"`
+ PendingDelete bool `json:"pendingdelete" bson:"pendingdelete" yaml:"pendingdelete"`
+ LastModified time.Time `json:"lastmodified" bson:"lastmodified" yaml:"lastmodified"`
+ LastCheckIn time.Time `json:"lastcheckin" bson:"lastcheckin" yaml:"lastcheckin"`
+ LastPeerUpdate time.Time `json:"lastpeerupdate" bson:"lastpeerupdate" yaml:"lastpeerupdate"`
+ ExpirationDateTime time.Time `json:"expdatetime" bson:"expdatetime" yaml:"expdatetime"`
+ EgressGatewayNatEnabled bool `json:"egressgatewaynatenabled" bson:"egressgatewaynatenabled" yaml:"egressgatewaynatenabled"`
+ EgressGatewayRequest EgressGatewayRequest `json:"egressgatewayrequest" bson:"egressgatewayrequest" yaml:"egressgatewayrequest"`
+ IngressGatewayRange string `json:"ingressgatewayrange" bson:"ingressgatewayrange" yaml:"ingressgatewayrange"`
+ IngressGatewayRange6 string `json:"ingressgatewayrange6" bson:"ingressgatewayrange6" yaml:"ingressgatewayrange6"`
+ IngressPersistentKeepalive int32 `json:"ingresspersistentkeepalive" bson:"ingresspersistentkeepalive" yaml:"ingresspersistentkeepalive"`
+ IngressMTU int32 `json:"ingressmtu" bson:"ingressmtu" yaml:"ingressmtu"`
+ Metadata string `json:"metadata"`
// == PRO ==
DefaultACL string `json:"defaultacl,omitempty" bson:"defaultacl,omitempty" yaml:"defaultacl,omitempty" validate:"checkyesornoorunset"`
OwnerID string `json:"ownerid,omitempty" bson:"ownerid,omitempty" yaml:"ownerid,omitempty"`
@@ -97,6 +99,10 @@ type Node struct {
InetNodeReq InetNodeReq `json:"inet_node_req" yaml:"inet_node_req"`
InternetGwID string `json:"internetgw_node_id" yaml:"internetgw_node_id"`
AdditionalRagIps []net.IP `json:"additional_rag_ips" yaml:"additional_rag_ips" swaggertype:"array,number"`
+ Tags map[TagID]struct{} `json:"tags" yaml:"tags"`
+ IsStatic bool `json:"is_static"`
+ IsUserNode bool `json:"is_user_node"`
+ StaticNode ExtClient `json:"static_node"`
}
// LegacyNode - legacy struct for node model
diff --git a/models/structs.go b/models/structs.go
index decb65572..2a723adf9 100644
--- a/models/structs.go
+++ b/models/structs.go
@@ -45,6 +45,16 @@ type UserRemoteGws struct {
NetworkAddresses []string `json:"network_addresses"`
}
+// UserRAGs - struct for user access gws
+type UserRAGs struct {
+ GwID string `json:"remote_access_gw_id"`
+ GWName string `json:"gw_name"`
+ Network string `json:"network"`
+ Connected bool `json:"connected"`
+ IsInternetGateway bool `json:"is_internet_gateway"`
+ Metadata string `json:"metadata"`
+}
+
// UserRemoteGwsReq - struct to hold user remote acccess gws req
type UserRemoteGwsReq struct {
RemoteAccessClientID string `json:"remote_access_clientid"`
@@ -163,9 +173,11 @@ type HostRelayRequest struct {
// IngressRequest - ingress request struct
type IngressRequest struct {
- ExtclientDNS string `json:"extclientdns"`
- IsInternetGateway bool `json:"is_internet_gw"`
- Metadata string `json:"metadata"`
+ ExtclientDNS string `json:"extclientdns"`
+ IsInternetGateway bool `json:"is_internet_gw"`
+ Metadata string `json:"metadata"`
+ PersistentKeepalive int32 `json:"persistentkeepalive"`
+ MTU int32 `json:"mtu"`
}
// InetNodeReq - exit node request struct
@@ -254,6 +266,8 @@ type ServerConfig struct {
IsPro bool `yaml:"isee" json:"Is_EE"`
TrafficKey []byte `yaml:"traffickey"`
MetricInterval string `yaml:"metric_interval"`
+ ManageDNS bool `yaml:"manage_dns"`
+ DefaultDomain string `yaml:"default_domain"`
}
// User.NameInCharset - returns if name is in charset below or not
diff --git a/models/tags.go b/models/tags.go
new file mode 100644
index 000000000..9fcb449da
--- /dev/null
+++ b/models/tags.go
@@ -0,0 +1,52 @@
+package models
+
+import (
+ "fmt"
+ "time"
+)
+
+type TagID string
+
+const (
+ RemoteAccessTagName = "remote-access-gws"
+)
+
+func (id TagID) String() string {
+ return string(id)
+}
+
+func (t Tag) GetIDFromName() string {
+ return fmt.Sprintf("%s.%s", t.Network, t.TagName)
+}
+
+type Tag struct {
+ ID TagID `json:"id"`
+ TagName string `json:"tag_name"`
+ Network NetworkID `json:"network"`
+ CreatedBy string `json:"created_by"`
+ CreatedAt time.Time `json:"created_at"`
+}
+
+type CreateTagReq struct {
+ TagName string `json:"tag_name"`
+ Network NetworkID `json:"network"`
+ TaggedNodes []ApiNode `json:"tagged_nodes"`
+}
+
+type TagListResp struct {
+ Tag
+ UsedByCnt int `json:"used_by_count"`
+ TaggedNodes []ApiNode `json:"tagged_nodes"`
+}
+
+type TagListRespNodes struct {
+ Tag
+ UsedByCnt int `json:"used_by_count"`
+ TaggedNodes []ApiNode `json:"tagged_nodes"`
+}
+
+type UpdateTagReq struct {
+ Tag
+ NewName string `json:"new_name"`
+ TaggedNodes []ApiNode `json:"tagged_nodes"`
+}
diff --git a/models/user_mgmt.go b/models/user_mgmt.go
index a87a0f4b8..7debd6e22 100644
--- a/models/user_mgmt.go
+++ b/models/user_mgmt.go
@@ -62,6 +62,7 @@ const (
EnrollmentKeysRsrc RsrcType = "enrollment_key"
UserRsrc RsrcType = "users"
AclRsrc RsrcType = "acl"
+ TagRsrc RsrcType = "tag"
DnsRsrc RsrcType = "dns"
FailOverRsrc RsrcType = "fail_over"
MetricRsrc RsrcType = "metrics"
@@ -116,8 +117,9 @@ type RsrcPermissionScope struct {
type UserRolePermissionTemplate struct {
ID UserRoleID `json:"id"`
- UiName string `json:"ui_name"`
+ Name string `json:"name"`
Default bool `json:"default"`
+ MetaData string `json:"meta_data"`
DenyDashboardAccess bool `json:"deny_dashboard_access"`
FullAccess bool `json:"full_access"`
NetworkID NetworkID `json:"network_id"`
@@ -132,6 +134,8 @@ type CreateGroupReq struct {
type UserGroup struct {
ID UserGroupID `json:"id"`
+ Default bool `json:"default"`
+ Name string `json:"name"`
NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"`
MetaData string `json:"meta_data"`
}
@@ -177,8 +181,9 @@ type UserAuthParams struct {
// UserClaims - user claims struct
type UserClaims struct {
- Role UserRoleID
- UserName string
+ Role UserRoleID
+ UserName string
+ RacAutoDisable bool
jwt.RegisteredClaims
}
diff --git a/mq/publishers.go b/mq/publishers.go
index 099eb4a05..3b47390a8 100644
--- a/mq/publishers.go
+++ b/mq/publishers.go
@@ -23,6 +23,10 @@ func PublishPeerUpdate(replacePeers bool) error {
return nil
}
+ if servercfg.GetManageDNS() {
+ sendDNSSync()
+ }
+
hosts, err := logic.GetAllHosts()
if err != nil {
logger.Log(1, "err getting all hosts", err.Error())
@@ -249,3 +253,55 @@ func sendPeers() {
}
}
}
+
+func SendDNSSyncByNetwork(network string) error {
+
+ k, err := logic.GetDNS(network)
+ if err == nil && len(k) > 0 {
+ err = PushSyncDNS(k)
+ if err != nil {
+ slog.Warn("error publishing dns entry data for network ", network, err.Error())
+ }
+ }
+
+ return err
+}
+
+func sendDNSSync() error {
+
+ networks, err := logic.GetNetworks()
+ if err == nil && len(networks) > 0 {
+ for _, v := range networks {
+ k, err := logic.GetDNS(v.NetID)
+ if err == nil && len(k) > 0 {
+ err = PushSyncDNS(k)
+ if err != nil {
+ slog.Warn("error publishing dns entry data for network ", v.NetID, err.Error())
+ }
+ }
+ }
+ return nil
+ }
+ return err
+}
+
+func PushSyncDNS(dnsEntries []models.DNSEntry) error {
+ logger.Log(2, "----> Pushing Sync DNS")
+ data, err := json.Marshal(dnsEntries)
+ if err != nil {
+ return errors.New("failed to marshal DNS entries: " + err.Error())
+ }
+ if mqclient == nil || !mqclient.IsConnectionOpen() {
+ return errors.New("cannot publish ... mqclient not connected")
+ }
+ if token := mqclient.Publish(fmt.Sprintf("host/dns/sync/%s", dnsEntries[0].Network), 0, true, data); !token.WaitTimeout(MQ_TIMEOUT*time.Second) || token.Error() != nil {
+ var err error
+ if token.Error() == nil {
+ err = errors.New("connection timeout")
+ } else {
+ err = token.Error()
+ }
+ return err
+ }
+ return nil
+}
diff --git a/pro/auth/error.go b/pro/auth/error.go
index eb49b8769..9fb532d95 100644
--- a/pro/auth/error.go
+++ b/pro/auth/error.go
@@ -93,12 +93,12 @@ var htmlBaseTemplate = `