diff --git a/.github/workflows/deletedroplets.yml b/.github/workflows/deletedroplets.yml index 5e1917929..d2fc1253b 100644 --- a/.github/workflows/deletedroplets.yml +++ b/.github/workflows/deletedroplets.yml @@ -12,7 +12,7 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - name: get logs - uses: dawidd6/action-download-artifact@v2 + uses: dawidd6/action-download-artifact@v3 with: run_id: ${{ github.event.workflow_run.id}} if_no_artifact_found: warn @@ -60,7 +60,7 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'failure' }} steps: - name: get logs - uses: dawidd6/action-download-artifact@v2 + uses: dawidd6/action-download-artifact@v3 with: run_id: ${{ github.event.workflow_run.id}} if_no_artifact_found: warn diff --git a/auth/azure-ad.go b/auth/azure-ad.go index 76b861167..f5bdf4c5b 100644 --- a/auth/azure-ad.go +++ b/auth/azure-ad.go @@ -66,6 +66,15 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { return } } + user, err := logic.GetUser(content.Email) + if err != nil { + handleOauthUserNotFound(w) + return + } + if !(user.IsSuperAdmin || user.IsAdmin) { + handleOauthUserNotAllowed(w) + return + } var newPass, fetchErr = fetchPassValue("") if fetchErr != nil { return diff --git a/auth/error.go b/auth/error.go index 002eed058..b982bc980 100644 --- a/auth/error.go +++ b/auth/error.go @@ -10,6 +10,31 @@ const oauthNotConfigured = ` ` +const userNotAllowed = ` + +

Only Admins are allowed to access Dashboard.

+

Non-Admins can access the netmaker networks using RemoteAccessClient.

+ + +` +const userNotFound = ` + +

User Not Found.

+ +` + +func handleOauthUserNotFound(response http.ResponseWriter) { + response.Header().Set("Content-Type", "text/html; charset=utf-8") + response.WriteHeader(http.StatusNotFound) + response.Write([]byte(userNotFound)) +} + +func handleOauthUserNotAllowed(response http.ResponseWriter) { + response.Header().Set("Content-Type", "text/html; charset=utf-8") + response.WriteHeader(http.StatusForbidden) + response.Write([]byte(userNotAllowed)) +} + // handleOauthNotConfigured - returns an appropriate html page when oauth is not configured on netmaker server but an oauth login was attempted func handleOauthNotConfigured(response http.ResponseWriter) { response.Header().Set("Content-Type", "text/html; charset=utf-8") diff --git a/auth/github.go b/auth/github.go index bdbbabc80..37c66085a 100644 --- a/auth/github.go +++ b/auth/github.go @@ -66,6 +66,15 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { return } } + user, err := logic.GetUser(content.Email) + if err != nil { + handleOauthUserNotFound(w) + return + } + if !(user.IsSuperAdmin || user.IsAdmin) { + handleOauthUserNotAllowed(w) + return + } var newPass, fetchErr = fetchPassValue("") if fetchErr != nil { return diff --git a/auth/google.go b/auth/google.go index de144a3cc..e61ab4c7c 100644 --- a/auth/google.go +++ b/auth/google.go @@ -68,6 +68,15 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { return } } + user, err := logic.GetUser(content.Email) + if err != nil { + handleOauthUserNotFound(w) + return + } + if !(user.IsSuperAdmin || user.IsAdmin) { + handleOauthUserNotAllowed(w) + return + } var newPass, fetchErr = fetchPassValue("") if fetchErr != nil { return diff --git a/auth/oidc.go b/auth/oidc.go index 86530bc91..d38ddeea0 100644 --- a/auth/oidc.go +++ b/auth/oidc.go @@ -79,6 +79,15 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { return } } + user, err := logic.GetUser(content.Email) + if err != nil { + handleOauthUserNotFound(w) + return + } + if !(user.IsSuperAdmin || user.IsAdmin) { + handleOauthUserNotAllowed(w) + return + } var newPass, fetchErr = fetchPassValue("") if fetchErr != nil { return diff --git a/cli/cmd/acl/allow.go b/cli/cmd/acl/allow.go index 5809c263e..14bef7e46 100644 --- a/cli/cmd/acl/allow.go +++ b/cli/cmd/acl/allow.go @@ -2,6 +2,7 @@ package acl import ( "fmt" + "log" "github.com/gravitl/netmaker/cli/functions" "github.com/gravitl/netmaker/logic/acls" @@ -14,17 +15,34 @@ var aclAllowCmd = &cobra.Command{ Short: "Allow access from one node to another", Long: `Allow access from one node to another`, Run: func(cmd *cobra.Command, args []string) { + network := args[0] fromNodeID := args[1] toNodeID := args[2] - payload := acls.ACLContainer(map[acls.AclID]acls.ACL{ - acls.AclID(fromNodeID): map[acls.AclID]byte{ - acls.AclID(toNodeID): acls.Allowed, - }, - acls.AclID(toNodeID): map[acls.AclID]byte{ - acls.AclID(fromNodeID): acls.Allowed, - }, - }) - functions.UpdateACL(args[0], &payload) + + if fromNodeID == toNodeID { + log.Fatal("Cannot allow access from a node to itself") + } + + // get current acls + res := functions.GetACL(network) + if res == nil { + log.Fatalf("Could not load network ACLs") + } + + payload := *res + + if _, ok := payload[acls.AclID(fromNodeID)]; !ok { + log.Fatalf("Node %s does not exist", fromNodeID) + } + if _, ok := payload[acls.AclID(toNodeID)]; !ok { + log.Fatalf("Node %s does not exist", toNodeID) + } + + // update acls + payload[acls.AclID(fromNodeID)][acls.AclID(toNodeID)] = acls.Allowed + payload[acls.AclID(toNodeID)][acls.AclID(fromNodeID)] = acls.Allowed + + functions.UpdateACL(network, &payload) fmt.Println("Success") }, } diff --git a/cli/cmd/acl/deny.go b/cli/cmd/acl/deny.go index 83523f62c..587e42dc9 100644 --- a/cli/cmd/acl/deny.go +++ b/cli/cmd/acl/deny.go @@ -2,6 +2,7 @@ package acl import ( "fmt" + "log" "github.com/gravitl/netmaker/cli/functions" "github.com/gravitl/netmaker/logic/acls" @@ -14,17 +15,34 @@ var aclDenyCmd = &cobra.Command{ Short: "Deny access from one node to another", Long: `Deny access from one node to another`, Run: func(cmd *cobra.Command, args []string) { + network := args[0] fromNodeID := args[1] toNodeID := args[2] - payload := acls.ACLContainer(map[acls.AclID]acls.ACL{ - acls.AclID(fromNodeID): map[acls.AclID]byte{ - acls.AclID(toNodeID): acls.NotAllowed, - }, - acls.AclID(toNodeID): map[acls.AclID]byte{ - acls.AclID(fromNodeID): acls.NotAllowed, - }, - }) - functions.UpdateACL(args[0], &payload) + + if fromNodeID == toNodeID { + log.Fatal("Cannot deny access to self") + } + + // get current acls + res := functions.GetACL(network) + if res == nil { + log.Fatalf("Could not load network ACLs") + } + + payload := *res + + if _, ok := payload[acls.AclID(fromNodeID)]; !ok { + log.Fatalf("Node [%s] does not exist", fromNodeID) + } + if _, ok := payload[acls.AclID(toNodeID)]; !ok { + log.Fatalf("Node [%s] does not exist", toNodeID) + } + + // update acls + payload[acls.AclID(fromNodeID)][acls.AclID(toNodeID)] = acls.NotAllowed + payload[acls.AclID(toNodeID)][acls.AclID(fromNodeID)] = acls.NotAllowed + + functions.UpdateACL(network, &payload) fmt.Println("Success") }, } diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml index 34873b744..2db9439ca 100644 --- a/compose/docker-compose.yml +++ b/compose/docker-compose.yml @@ -56,6 +56,7 @@ services: - "443:443" coredns: + #network_mode: host container_name: coredns image: coredns/coredns:1.10.1 command: -conf /root/dnsconfig/Corefile diff --git a/controllers/controller.go b/controllers/controller.go index 8e54d38b1..d80093beb 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -41,7 +41,7 @@ func HandleRESTRequests(wg *sync.WaitGroup, ctx context.Context) { // Currently allowed dev origin is all. Should change in prod // should consider analyzing the allowed methods further - headersOk := handlers.AllowedHeaders([]string{"Access-Control-Allow-Origin", "X-Requested-With", "Content-Type", "authorization"}) + headersOk := handlers.AllowedHeaders([]string{"Access-Control-Allow-Origin", "X-Requested-With", "Content-Type", "authorization", "From-Ui"}) originsOk := handlers.AllowedOrigins(strings.Split(servercfg.GetAllowedOrigin(), ",")) methodsOk := handlers.AllowedMethods([]string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete}) diff --git a/controllers/dns.go b/controllers/dns.go index 8a987a4e8..1c0157c09 100644 --- a/controllers/dns.go +++ b/controllers/dns.go @@ -2,6 +2,7 @@ package controller import ( "encoding/json" + "errors" "fmt" "net/http" @@ -10,7 +11,6 @@ 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" ) @@ -170,24 +170,17 @@ func createDNS(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - err = logic.SetDNS() - if err != nil { - logger.Log(0, r.Header.Get("user"), - fmt.Sprintf("Failed to set DNS entries on file: %v", err)) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return + if servercfg.IsDNSMode() { + err = logic.SetDNS() + if err != nil { + logger.Log(0, r.Header.Get("user"), + fmt.Sprintf("Failed to set DNS entries on file: %v", err)) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } } + logger.Log(1, "new DNS record added:", entry.Name) - if servercfg.IsMessageQueueBackend() { - go func() { - if err = mq.PublishPeerUpdate(); err != nil { - logger.Log(0, "failed to publish peer update after ACL update on", entry.Network) - } - if err := mq.PublishCustomDNS(&entry); err != nil { - logger.Log(0, "error publishing custom dns", err.Error()) - } - }() - } logger.Log(2, r.Header.Get("user"), fmt.Sprintf("DNS entry is set: %+v", entry)) w.WriteHeader(http.StatusOK) @@ -221,23 +214,17 @@ func deleteDNS(w http.ResponseWriter, r *http.Request) { return } logger.Log(1, "deleted dns entry: ", entrytext) - err = logic.SetDNS() - if err != nil { - logger.Log(0, r.Header.Get("user"), - fmt.Sprintf("Failed to set DNS entries on file: %v", err)) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) - return + if servercfg.IsDNSMode() { + err = logic.SetDNS() + if err != nil { + logger.Log(0, r.Header.Get("user"), + fmt.Sprintf("Failed to set DNS entries on file: %v", err)) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } } + json.NewEncoder(w).Encode(entrytext + " deleted.") - go func() { - dns := models.DNSUpdate{ - Action: models.DNSDeleteByName, - Name: entrytext, - } - if err := mq.PublishDNSUpdate(params["network"], dns); err != nil { - logger.Log(0, "failed to publish dns update", err.Error()) - } - }() } @@ -271,7 +258,10 @@ func GetDNSEntry(domain string, network string) (models.DNSEntry, error) { func pushDNS(w http.ResponseWriter, r *http.Request) { // Set header w.Header().Set("Content-Type", "application/json") - + if !servercfg.IsDNSMode() { + logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("DNS Mode is set to off"), "badrequest")) + return + } err := logic.SetDNS() if err != nil { diff --git a/controllers/enrollmentkeys.go b/controllers/enrollmentkeys.go index 1e5c3ad1f..b75d7b04e 100644 --- a/controllers/enrollmentkeys.go +++ b/controllers/enrollmentkeys.go @@ -6,6 +6,7 @@ import ( "net/http" "time" + "github.com/go-playground/validator/v10" "github.com/google/uuid" "github.com/gorilla/mux" @@ -115,6 +116,35 @@ func createEnrollmentKey(w http.ResponseWriter, r *http.Request) { if enrollmentKeyBody.Expiration > 0 { newTime = time.Unix(enrollmentKeyBody.Expiration, 0) } + v := validator.New() + err = v.Struct(enrollmentKeyBody) + if err != nil { + logger.Log(0, r.Header.Get("user"), "error validating request body: ", + err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("validation error: name length must be between 3 and 32: %w", err), "badrequest")) + return + } + + if existingKeys, err := logic.GetAllEnrollmentKeys(); err != nil { + logger.Log(0, r.Header.Get("user"), "error validating request body: ", + err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } else { + // check if any tags are duplicate + existingTags := make(map[string]struct{}) + for _, existingKey := range existingKeys { + for _, t := range existingKey.Tags { + existingTags[t] = struct{}{} + } + } + for _, t := range enrollmentKeyBody.Tags { + if _, ok := existingTags[t]; ok { + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("key names must be unique"), "badrequest")) + return + } + } + } relayId := uuid.Nil if enrollmentKeyBody.Relay != "" { diff --git a/controllers/ext_client.go b/controllers/ext_client.go index f0b36ae41..ab5b6d3bb 100644 --- a/controllers/ext_client.go +++ b/controllers/ext_client.go @@ -12,6 +12,7 @@ import ( "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/servercfg" "github.com/gravitl/netmaker/models" @@ -355,30 +356,28 @@ func createExtClient(w http.ResponseWriter, r *http.Request) { return } userName = caller.UserName - if !caller.IsAdmin && !caller.IsSuperAdmin { - if _, ok := caller.RemoteGwIDs[nodeid]; !ok { - err = errors.New("permission denied") - slog.Error("failed to create extclient", "error", err) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) - return - } - // check if user has a config already for remote access client - extclients, err := logic.GetNetworkExtClients(node.Network) - if err != nil { - slog.Error("failed to get extclients", "error", err) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + if _, ok := caller.RemoteGwIDs[nodeid]; (!caller.IsAdmin && !caller.IsSuperAdmin) && !ok { + err = errors.New("permission denied") + slog.Error("failed to create extclient", "error", err) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "forbidden")) + return + } + // check if user has a config already for remote access client + extclients, err := logic.GetNetworkExtClients(node.Network) + if err != nil { + slog.Error("failed to get extclients", "error", err) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + for _, extclient := range extclients { + if extclient.RemoteAccessClientID != "" && + extclient.RemoteAccessClientID == customExtClient.RemoteAccessClientID && nodeid == extclient.IngressGatewayID { + // extclient on the gw already exists for the remote access client + err = errors.New("remote client config already exists on the gateway. it may have been created by another user with this same remote client machine") + slog.Error("failed to create extclient", "user", userName, "error", err) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } - for _, extclient := range extclients { - if extclient.RemoteAccessClientID != "" && - extclient.RemoteAccessClientID == customExtClient.RemoteAccessClientID && nodeid == extclient.IngressGatewayID { - // extclient on the gw already exists for the remote access client - err = errors.New("remote client config already exists on the gateway") - slog.Error("failed to create extclient", "user", userName, "error", err) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - } } } @@ -426,8 +425,8 @@ func createExtClient(w http.ResponseWriter, r *http.Request) { if err := mq.PublishPeerUpdate(); err != nil { logger.Log(1, "error setting ext peers on "+nodeid+": "+err.Error()) } - if err := mq.PublishExtClientDNS(&extclient); err != nil { - logger.Log(1, "error publishing extclient dns", err.Error()) + if servercfg.IsDNSMode() { + logic.SetDNS() } }() } @@ -522,8 +521,8 @@ func updateExtClient(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(newclient) if changedID { go func() { - if err := mq.PublishExtClientDNSUpdate(oldExtClient, newclient, oldExtClient.Network); err != nil { - logger.Log(1, "error pubishing dns update for extcient update", err.Error()) + if servercfg.IsDNSMode() { + logic.SetDNS() } }() } @@ -583,8 +582,8 @@ func deleteExtClient(w http.ResponseWriter, r *http.Request) { if err := mq.PublishDeletedClientPeerUpdate(&extclient); err != nil { logger.Log(1, "error setting ext peers on "+ingressnode.ID.String()+": "+err.Error()) } - if err = mq.PublishDeleteExtClientDNS(&extclient); err != nil { - logger.Log(1, "error publishing dns update for extclient deletion", err.Error()) + if servercfg.IsDNSMode() { + logic.SetDNS() } }() diff --git a/controllers/hosts.go b/controllers/hosts.go index f4a5464e3..5ae3cb8a8 100644 --- a/controllers/hosts.go +++ b/controllers/hosts.go @@ -31,6 +31,7 @@ func hostHandlers(r *mux.Router) { r.HandleFunc("/api/hosts/adm/authenticate", authenticateHost).Methods(http.MethodPost) r.HandleFunc("/api/v1/host", Authorize(true, false, "host", http.HandlerFunc(pull))).Methods(http.MethodGet) r.HandleFunc("/api/v1/host/{hostid}/signalpeer", Authorize(true, false, "host", http.HandlerFunc(signalPeer))).Methods(http.MethodPost) + r.HandleFunc("/api/v1/fallback/host/{hostid}", Authorize(true, false, "host", http.HandlerFunc(hostUpdateFallback))).Methods(http.MethodPut) r.HandleFunc("/api/v1/auth-register/host", socketHandler) } @@ -141,6 +142,8 @@ func pull(w http.ResponseWriter, r *http.Request) { Peers: hPU.Peers, PeerIDs: hPU.PeerIDs, HostNetworkInfo: hPU.HostNetworkInfo, + EgressRoutes: hPU.EgressRoutes, + FwUpdate: hPU.FwUpdate, } logger.Log(1, hostID, "completed a pull") @@ -196,16 +199,8 @@ func updateHost(w http.ResponseWriter, r *http.Request) { logger.Log(0, "fail to publish peer update: ", err.Error()) } if newHost.Name != currHost.Name { - networks := logic.GetHostNetworks(currHost.ID.String()) - if err := mq.PublishHostDNSUpdate(currHost, newHost, networks); err != nil { - var dnsError *models.DNSError - if errors.Is(err, dnsError) { - for _, message := range err.(models.DNSError).ErrorStrings { - logger.Log(0, message) - } - } else { - logger.Log(0, err.Error()) - } + if servercfg.IsDNSMode() { + logic.SetDNS() } } }() @@ -216,6 +211,51 @@ func updateHost(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(apiHostData) } +// swagger:route PUT /api/v1/fallback/host/{hostid} hosts hostUpdateFallback +// +// Updates a Netclient host on Netmaker server. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: apiHostResponse +func hostUpdateFallback(w http.ResponseWriter, r *http.Request) { + var params = mux.Vars(r) + hostid := params["hostid"] + currentHost, err := logic.GetHost(hostid) + if err != nil { + slog.Error("error getting host", "id", hostid, "error", err) + return + } + + var hostUpdate models.HostUpdate + err = json.NewDecoder(r.Body).Decode(&hostUpdate) + if err != nil { + logger.Log(0, r.Header.Get("user"), "failed to update a host:", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + slog.Info("recieved host update", "name", hostUpdate.Host.Name, "id", hostUpdate.Host.ID) + switch hostUpdate.Action { + case models.CheckIn: + _ = mq.HandleHostCheckin(&hostUpdate.Host, currentHost) + + case models.UpdateHost: + + _ = logic.UpdateHostFromClient(&hostUpdate.Host, currentHost) + err := logic.UpsertHost(currentHost) + if err != nil { + slog.Error("failed to update host", "id", currentHost.ID, "error", err) + return + } + + } + +} + // swagger:route DELETE /api/hosts/{hostid} hosts deleteHost // // Deletes a Netclient host from Netmaker server. @@ -252,6 +292,12 @@ func deleteHost(w http.ResponseWriter, r *http.Request) { go mq.PublishMqUpdatesForDeletedNode(node, false, gwClients) } + if servercfg.GetBrokerType() == servercfg.EmqxBrokerType { + // delete EMQX credentials for host + if err := mq.DeleteEmqxUser(currHost.ID.String()); err != nil { + slog.Error("failed to remove host credentials from EMQX", "id", currHost.ID, "error", err) + } + } if err = logic.RemoveHost(currHost, forceDelete); err != nil { logger.Log(0, r.Header.Get("user"), "failed to delete a host:", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) @@ -311,7 +357,9 @@ func addHostToNetwork(w http.ResponseWriter, r *http.Request) { Node: *newNode, }) mq.PublishPeerUpdate() - mq.HandleNewNodeDNS(currHost, newNode) + if servercfg.IsDNSMode() { + logic.SetDNS() + } }() logger.Log(2, r.Header.Get("user"), fmt.Sprintf("added host %s to network %s", currHost.Name, network)) w.WriteHeader(http.StatusOK) @@ -396,7 +444,12 @@ func deleteHostFromNetwork(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal")) return } - go mq.PublishMqUpdatesForDeletedNode(*node, true, gwClients) + go func() { + mq.PublishMqUpdatesForDeletedNode(*node, true, gwClients) + if servercfg.IsDNSMode() { + logic.SetDNS() + } + }() logger.Log(2, r.Header.Get("user"), fmt.Sprintf("removed host %s from network %s", currHost.Name, network)) w.WriteHeader(http.StatusOK) } @@ -492,7 +545,7 @@ func authenticateHost(response http.ResponseWriter, request *http.Request) { // Create EMQX creds and ACLs if not found if servercfg.GetBrokerType() == servercfg.EmqxBrokerType { - if err := mq.CreateEmqxUser(host.ID.String(), host.HostPass, false); err != nil { + if err := mq.CreateEmqxUser(host.ID.String(), authRequest.Password, false); err != nil { slog.Error("failed to create host credentials for EMQX: ", err.Error()) } else { if err := mq.CreateHostACL(host.ID.String(), servercfg.GetServerInfo().Server); err != nil { diff --git a/controllers/node.go b/controllers/node.go index 52441c774..5a6b065eb 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -596,7 +596,9 @@ func deleteIngressGateway(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.PublishDeleteAllExtclientsDNS(node.Network, removedClients) + if servercfg.IsDNSMode() { + logic.SetDNS() + } }() } } @@ -635,7 +637,7 @@ func updateNode(w http.ResponseWriter, r *http.Request) { } newNode := newData.ConvertToServerNode(¤tNode) relayUpdate := logic.RelayUpdates(¤tNode, newNode) - host, err := logic.GetHost(newNode.HostID.String()) + _, err = logic.GetHost(newNode.HostID.String()) if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to get host for node [ %s ] info: %v", nodeid, err)) @@ -655,9 +657,6 @@ func updateNode(w http.ResponseWriter, r *http.Request) { if relayUpdate { logic.UpdateRelayed(¤tNode, newNode) } - if servercfg.IsDNSMode() { - logic.SetDNS() - } apiNode := newNode.ConvertToAPINode() logger.Log(1, r.Header.Get("user"), "updated node", currentNode.ID.String(), "on network", currentNode.Network) @@ -672,8 +671,8 @@ func updateNode(w http.ResponseWriter, r *http.Request) { logger.Log(0, "error during node ACL update for node", newNode.ID.String()) } } - if err := mq.PublishReplaceDNS(¤tNode, newNode, host); err != nil { - logger.Log(1, "failed to publish dns update", err.Error()) + if servercfg.IsDNSMode() { + logic.SetDNS() } }(aclUpdate, relayUpdate, newNode) } diff --git a/controllers/user.go b/controllers/user.go index 64dd187a2..4896b12fe 100644 --- a/controllers/user.go +++ b/controllers/user.go @@ -71,6 +71,20 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) { logic.ReturnErrorResponse(response, request, errorResponse) return } + if val := request.Header.Get("From-Ui"); val == "true" { + // request came from UI, if normal user block Login + user, err := logic.GetUser(authRequest.UserName) + if err != nil { + logger.Log(0, authRequest.UserName, "user validation failed: ", + err.Error()) + logic.ReturnErrorResponse(response, request, logic.FormatError(err, "unauthorized")) + return + } + if !(user.IsAdmin || user.IsSuperAdmin) { + logic.ReturnErrorResponse(response, request, logic.FormatError(errors.New("only admins can access dashboard"), "unauthorized")) + return + } + } username := authRequest.UserName jwt, err := logic.VerifyAuthRequest(authRequest) if err != nil { @@ -119,7 +133,7 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) { if client.OwnerID == username && !client.Enabled { slog.Info(fmt.Sprintf("enabling ext client %s for user %s due to RAC autodisabling feature", client.ClientID, client.OwnerID)) if newClient, err := logic.ToggleExtClientConnectivity(&client, true); err != nil { - slog.Error("error disabling ext client in RAC autodisable hook", "error", err) + slog.Error("error enabling ext client in RAC autodisable hook", "error", err) continue // dont return but try for other clients } else { // publish peer update to ingress gateway @@ -545,6 +559,9 @@ func deleteUser(w http.ResponseWriter, r *http.Request) { } } } + if servercfg.IsDNSMode() { + logic.SetDNS() + } }() logger.Log(1, username, "was deleted") json.NewEncoder(w).Encode(params["username"] + " deleted.") diff --git a/go.mod b/go.mod index 81e6de734..8a8a194ef 100644 --- a/go.mod +++ b/go.mod @@ -6,16 +6,16 @@ require ( github.com/eclipse/paho.mqtt.golang v1.4.3 github.com/go-playground/validator/v10 v10.16.0 github.com/golang-jwt/jwt/v4 v4.5.0 - github.com/google/uuid v1.4.0 + github.com/google/uuid v1.5.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.18 + github.com/mattn/go-sqlite3 v1.14.19 github.com/rqlite/gorqlite v0.0.0-20210514125552-08ff1e76b22f github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/stretchr/testify v1.8.4 github.com/txn2/txeh v1.5.5 - golang.org/x/crypto v0.16.0 + golang.org/x/crypto v0.17.0 golang.org/x/net v0.19.0 // indirect golang.org/x/oauth2 v0.15.0 golang.org/x/sys v0.15.0 // indirect @@ -38,7 +38,6 @@ require ( ) require ( - github.com/devilcove/httpclient v0.6.0 github.com/go-jose/go-jose/v3 v3.0.1 github.com/guumaster/tablewriter v0.0.10 github.com/matryer/is v1.4.1 diff --git a/go.sum b/go.sum index 44b3c681f..38cbdc8a1 100644 --- a/go.sum +++ b/go.sum @@ -14,8 +14,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t 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= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/devilcove/httpclient v0.6.0 h1:M5YAfHeNbu+0QxCiOCo/fKN+Hf0BtF/6aovu3NNgcKk= -github.com/devilcove/httpclient v0.6.0/go.mod h1:ctrAO2gRgTT+GxtRdWBp2SMQ+vacuxXlbhmlM4oWhs8= github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik= github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= @@ -40,8 +38,8 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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= @@ -64,8 +62,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI= -github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= +github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/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= @@ -104,8 +102,8 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 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= diff --git a/logic/dns.go b/logic/dns.go index 54c97f393..314cf7705 100644 --- a/logic/dns.go +++ b/logic/dns.go @@ -11,6 +11,7 @@ import ( "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/servercfg" "github.com/txn2/txeh" ) @@ -36,6 +37,10 @@ func SetDNS() error { hostfile.AddHost(entry.Address, entry.Name) } } + dns := GetExtclientDNS() + for _, entry := range dns { + hostfile.AddHost(entry.Address, entry.Name) + } if corefilestring == "" { corefilestring = "example.com" } @@ -69,6 +74,28 @@ func GetDNS(network string) ([]models.DNSEntry, error) { return dns, nil } +// GetExtclientDNS - gets all extclients dns entries +func GetExtclientDNS() []models.DNSEntry { + extclients, err := GetAllExtClients() + if err != nil { + return []models.DNSEntry{} + } + var dns []models.DNSEntry + for _, extclient := range extclients { + var entry = models.DNSEntry{} + entry.Name = fmt.Sprintf("%s.%s", extclient.ClientID, extclient.Network) + entry.Network = extclient.Network + if extclient.Address != "" { + entry.Address = extclient.Address + } + if extclient.Address6 != "" { + entry.Address6 = extclient.Address6 + } + dns = append(dns, entry) + } + return dns +} + // GetNodeDNS - gets the DNS of a network node func GetNodeDNS(network string) ([]models.DNSEntry, error) { @@ -142,6 +169,7 @@ func SetCorefile(domains string) error { } corefile := domains + ` { + bind %s reload 15s hosts /root/dnsconfig/netmaker.hosts { fallthrough @@ -150,8 +178,7 @@ func SetCorefile(domains string) error { log } ` - corebytes := []byte(corefile) - + corebytes := []byte(fmt.Sprintf(corefile, servercfg.GetCoreDNSAddr())) err = os.WriteFile(dir+"/config/dnsconfig/Corefile", corebytes, 0644) if err != nil { return err diff --git a/logic/enrollmentkey.go b/logic/enrollmentkey.go index 5605bdac9..ae5d01d5f 100644 --- a/logic/enrollmentkey.go +++ b/logic/enrollmentkey.go @@ -22,7 +22,7 @@ var EnrollmentErrors = struct { FailedToTokenize error FailedToDeTokenize error }{ - InvalidCreate: fmt.Errorf("invalid enrollment key created"), + InvalidCreate: fmt.Errorf("failed to create enrollment key. paramters invalid"), NoKeyFound: fmt.Errorf("no enrollmentkey found"), InvalidKey: fmt.Errorf("invalid key provided"), NoUsesRemaining: fmt.Errorf("no uses remaining"), @@ -61,8 +61,8 @@ func CreateEnrollmentKey(uses int, expiration time.Time, networks, tags []string if len(tags) > 0 { k.Tags = tags } - if ok := k.Validate(); !ok { - return nil, EnrollmentErrors.InvalidCreate + if err := k.Validate(); err != nil { + return nil, err } if relay != uuid.Nil { relayNode, err := GetNodeByID(relay.String()) diff --git a/logic/enrollmentkey_test.go b/logic/enrollmentkey_test.go index 3812b08a2..f91469ad0 100644 --- a/logic/enrollmentkey_test.go +++ b/logic/enrollmentkey_test.go @@ -17,7 +17,7 @@ func TestCreateEnrollmentKey(t *testing.T) { newKey, err := CreateEnrollmentKey(0, time.Time{}, nil, nil, false, uuid.Nil) assert.Nil(t, newKey) assert.NotNil(t, err) - assert.Equal(t, err, EnrollmentErrors.InvalidCreate) + 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) diff --git a/logic/hosts.go b/logic/hosts.go index 854eac3b1..d3dd48bb9 100644 --- a/logic/hosts.go +++ b/logic/hosts.go @@ -309,6 +309,11 @@ func RemoveHost(h *models.Host, forceDelete bool) error { if servercfg.CacheEnabled() { deleteHostFromCache(h.ID.String()) } + go func() { + if servercfg.IsDNSMode() { + SetDNS() + } + }() return nil } diff --git a/models/enrollment_key.go b/models/enrollment_key.go index 982c5463b..e775344df 100644 --- a/models/enrollment_key.go +++ b/models/enrollment_key.go @@ -1,6 +1,8 @@ package models import ( + "errors" + "fmt" "time" "github.com/google/uuid" @@ -13,6 +15,14 @@ const ( Unlimited ) +var ( + ErrNilEnrollmentKey = errors.New("enrollment key is nil") + ErrNilNetworksEnrollmentKey = errors.New("enrollment key networks is nil") + ErrNilTagsEnrollmentKey = errors.New("enrollment key tags is nil") + ErrInvalidEnrollmentKey = errors.New("enrollment key is not valid") + ErrInvalidEnrollmentKeyValue = errors.New("enrollment key value is not valid") +) + // KeyType - the type of enrollment key type KeyType int @@ -50,7 +60,7 @@ type APIEnrollmentKey struct { UsesRemaining int `json:"uses_remaining"` Networks []string `json:"networks"` Unlimited bool `json:"unlimited"` - Tags []string `json:"tags"` + Tags []string `json:"tags" validate:"required,dive,min=3,max=32"` Type KeyType `json:"type"` Relay string `json:"relay"` } @@ -81,9 +91,18 @@ func (k *EnrollmentKey) IsValid() bool { // EnrollmentKey.Validate - validate's an EnrollmentKey // should be used during creation -func (k *EnrollmentKey) Validate() bool { - return k.Networks != nil && - k.Tags != nil && - len(k.Value) == EnrollmentKeyLength && - k.IsValid() +func (k *EnrollmentKey) Validate() error { + if k == nil { + return ErrNilEnrollmentKey + } + if k.Tags == nil { + return ErrNilTagsEnrollmentKey + } + if len(k.Value) != EnrollmentKeyLength { + return fmt.Errorf("%w: length not %d characters", ErrInvalidEnrollmentKeyValue, EnrollmentKeyLength) + } + if !k.IsValid() { + return fmt.Errorf("%w: uses remaining: %d, expiration: %s, unlimited: %t", ErrInvalidEnrollmentKey, k.UsesRemaining, k.Expiration, k.Unlimited) + } + return nil } diff --git a/models/extclient.go b/models/extclient.go index a5d1dea49..569775639 100644 --- a/models/extclient.go +++ b/models/extclient.go @@ -17,7 +17,7 @@ type ExtClient struct { Enabled bool `json:"enabled" bson:"enabled"` OwnerID string `json:"ownerid" bson:"ownerid"` DeniedACLs map[string]struct{} `json:"deniednodeacls" bson:"acls,omitempty"` - RemoteAccessClientID string `json:"remote_access_client_id"` + RemoteAccessClientID string `json:"remote_access_client_id"` // unique ID (MAC address) of RAC machine } // CustomExtClient - struct for CustomExtClient params @@ -28,5 +28,5 @@ type CustomExtClient struct { ExtraAllowedIPs []string `json:"extraallowedips,omitempty"` Enabled bool `json:"enabled,omitempty"` DeniedACLs map[string]struct{} `json:"deniednodeacls" bson:"acls,omitempty"` - RemoteAccessClientID string `json:"remote_access_client_id"` + RemoteAccessClientID string `json:"remote_access_client_id"` // unique ID (MAC address) of RAC machine } diff --git a/models/structs.go b/models/structs.go index 5b21f9211..fc03cc957 100644 --- a/models/structs.go +++ b/models/structs.go @@ -225,12 +225,14 @@ type TrafficKeys struct { // HostPull - response of a host's pull type HostPull struct { - Host Host `json:"host" yaml:"host"` - Nodes []Node `json:"nodes" yaml:"nodes"` - Peers []wgtypes.PeerConfig `json:"peers" yaml:"peers"` - ServerConfig ServerConfig `json:"server_config" yaml:"server_config"` - PeerIDs PeerMap `json:"peer_ids,omitempty" yaml:"peer_ids,omitempty"` - HostNetworkInfo HostInfoMap `json:"host_network_info,omitempty" yaml:"host_network_info,omitempty"` + Host Host `json:"host" yaml:"host"` + Nodes []Node `json:"nodes" yaml:"nodes"` + Peers []wgtypes.PeerConfig `json:"peers" yaml:"peers"` + ServerConfig ServerConfig `json:"server_config" yaml:"server_config"` + PeerIDs PeerMap `json:"peer_ids,omitempty" yaml:"peer_ids,omitempty"` + HostNetworkInfo HostInfoMap `json:"host_network_info,omitempty" yaml:"host_network_info,omitempty"` + EgressRoutes []EgressNetworkRoutes `json:"egress_network_routes"` + FwUpdate FwUpdate `json:"fw_update"` } // NodeGet - struct for a single node get response @@ -261,6 +263,7 @@ type ServerConfig struct { MQPort string `yaml:"mqport"` MQUserName string `yaml:"mq_username"` MQPassword string `yaml:"mq_password"` + BrokerType string `yaml:"broker_type"` Server string `yaml:"server"` Broker string `yaml:"broker"` IsPro bool `yaml:"isee" json:"Is_EE"` diff --git a/mq/emqx.go b/mq/emqx.go index 27776fe3c..9727fae2f 100644 --- a/mq/emqx.go +++ b/mq/emqx.go @@ -286,16 +286,6 @@ func CreateHostACL(hostID, serverName string) error { Permission: "allow", Action: "all", }, - { - Topic: fmt.Sprintf("dns/all/%s/%s", hostID, serverName), - Permission: "allow", - Action: "all", - }, - { - Topic: fmt.Sprintf("dns/update/%s/%s", hostID, serverName), - Permission: "allow", - Action: "all", - }, { Topic: fmt.Sprintf("host/serverupdate/%s/%s", serverName, hostID), Permission: "allow", diff --git a/mq/handlers.go b/mq/handlers.go index a6b67f3f8..066b6f825 100644 --- a/mq/handlers.go +++ b/mq/handlers.go @@ -104,7 +104,7 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) { var sendPeerUpdate bool switch hostUpdate.Action { case models.CheckIn: - sendPeerUpdate = handleHostCheckin(&hostUpdate.Host, currentHost) + sendPeerUpdate = HandleHostCheckin(&hostUpdate.Host, currentHost) case models.Acknowledgement: hu := hostactions.GetAction(currentHost.ID.String()) if hu != nil { @@ -126,10 +126,6 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) { slog.Error("failed peers publish after join acknowledged", "name", hostUpdate.Host.Name, "id", currentHost.ID, "error", err) return } - if err = HandleNewNodeDNS(&hu.Host, &hu.Node); err != nil { - slog.Error("failed to send dns update after node added to host", "name", hostUpdate.Host.Name, "id", currentHost.ID, "error", err) - return - } } } case models.UpdateHost: @@ -166,7 +162,6 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) { // delete EMQX credentials for host if err := DeleteEmqxUser(currentHost.ID.String()); err != nil { slog.Error("failed to remove host credentials from EMQX", "id", currentHost.ID, "error", err) - return } } @@ -193,6 +188,9 @@ func UpdateHost(client mqtt.Client, msg mqtt.Message) { slog.Error("failed to delete host", "id", currentHost.ID, "error", err) return } + if servercfg.IsDNSMode() { + logic.SetDNS() + } sendPeerUpdate = true case models.SignalHost: signalPeer(hostUpdate.Signal) @@ -260,29 +258,7 @@ func ClientPeerUpdate(client mqtt.Client, msg mqtt.Message) { slog.Info("sent peer updates after signal received from", "id", id) } -func HandleNewNodeDNS(host *models.Host, node *models.Node) error { - dns := models.DNSUpdate{ - Action: models.DNSInsert, - Name: host.Name + "." + node.Network, - } - if node.Address.IP != nil { - dns.Address = node.Address.IP.String() - if err := PublishDNSUpdate(node.Network, dns); err != nil { - return err - } - } else if node.Address6.IP != nil { - dns.Address = node.Address6.IP.String() - if err := PublishDNSUpdate(node.Network, dns); err != nil { - return err - } - } - if err := PublishAllDNS(node); err != nil { - return err - } - return nil -} - -func handleHostCheckin(h, currentHost *models.Host) bool { +func HandleHostCheckin(h, currentHost *models.Host) bool { if h == nil { return false } diff --git a/mq/publishers.go b/mq/publishers.go index 5ffef67be..f75de8ad7 100644 --- a/mq/publishers.go +++ b/mq/publishers.go @@ -166,73 +166,6 @@ func ServerStartNotify() error { return nil } -// PublishDNSUpdatev1 - published dns updates to all nodes passed -func PublishDNSUpdatev1(network string, dns models.DNSUpdate, nodes []models.Node) error { - for _, node := range nodes { - host, err := logic.GetHost(node.HostID.String()) - if err != nil { - logger.Log(0, "error retrieving host for dns update", node.HostID.String(), err.Error()) - continue - } - data, err := json.Marshal(dns) - if err != nil { - logger.Log(0, "failed to encode dns data for node", node.ID.String(), err.Error()) - } - if err := publish(host, "dns/update/"+host.ID.String()+"/"+servercfg.GetServer(), data); err != nil { - logger.Log(0, "error publishing dns update to host", host.ID.String(), err.Error()) - continue - } - logger.Log(3, "published dns update to host", host.ID.String()) - } - return nil -} - -// PublishDNSUpdate publishes a dns update to all nodes on a network -func PublishDNSUpdate(network string, dns models.DNSUpdate) error { - nodes, err := logic.GetNetworkNodes(network) - if err != nil { - return err - } - for _, node := range nodes { - host, err := logic.GetHost(node.HostID.String()) - if err != nil { - logger.Log(0, "error retrieving host for dns update", node.HostID.String(), err.Error()) - continue - } - data, err := json.Marshal(dns) - if err != nil { - logger.Log(0, "failed to encode dns data for node", node.ID.String(), err.Error()) - } - if err := publish(host, "dns/update/"+host.ID.String()+"/"+servercfg.GetServer(), data); err != nil { - logger.Log(0, "error publishing dns update to host", host.ID.String(), err.Error()) - continue - } - logger.Log(3, "published dns update to host", host.ID.String()) - } - return nil -} - -// PublishAllDNS publishes an array of dns updates (ip / host.network) for each peer to a node joining a network -func PublishAllDNS(newnode *models.Node) error { - alldns := []models.DNSUpdate{} - newnodeHost, err := logic.GetHost(newnode.HostID.String()) - if err != nil { - return fmt.Errorf("error retrieving host for dns update %w", err) - } - alldns = append(alldns, getNodeDNS(newnode.Network)...) - alldns = append(alldns, getExtClientDNS(newnode.Network)...) - alldns = append(alldns, getCustomDNS(newnode.Network)...) - data, err := json.Marshal(alldns) - if err != nil { - return fmt.Errorf("error encoding dns data %w", err) - } - if err := publish(newnodeHost, "dns/all/"+newnodeHost.ID.String()+"/"+servercfg.GetServer(), data); err != nil { - return fmt.Errorf("error publishing full dns update to %s, %w", newnodeHost.ID.String(), err) - } - logger.Log(3, "published full dns update to %s", newnodeHost.ID.String()) - return nil -} - // PublishMqUpdatesForDeletedNode - published all the required updates for deleted node func PublishMqUpdatesForDeletedNode(node models.Node, sendNodeUpdate bool, gwClients []models.ExtClient) { // notify of peer change @@ -246,162 +179,10 @@ func PublishMqUpdatesForDeletedNode(node models.Node, sendNodeUpdate bool, gwCli if err := PublishDeletedNodePeerUpdate(&node); err != nil { logger.Log(1, "error publishing peer update ", err.Error()) } - host, err := logic.GetHost(node.HostID.String()) - if err != nil { - logger.Log(1, "failed to retrieve host for node", node.ID.String(), err.Error()) + if servercfg.IsDNSMode() { + logic.SetDNS() } - if err := PublishDNSDelete(&node, host); err != nil { - logger.Log(1, "error publishing dns update", err.Error()) - } - if err := PublishDeleteAllExtclientsDNS(node.Network, gwClients); err != nil { - logger.Log(1, "error publishing ext dns update", err.Error()) - } - -} -// PublishDNSDelete publish a dns update deleting a node to all hosts on a network -func PublishDNSDelete(node *models.Node, host *models.Host) error { - dns := models.DNSUpdate{ - Action: models.DNSDeleteByIP, - Name: host.Name + "." + node.Network, - } - if node.Address.IP != nil { - dns.Address = node.Address.IP.String() - if err := PublishDNSUpdate(node.Network, dns); err != nil { - return fmt.Errorf("dns update node deletion %w", err) - } - } - if node.Address6.IP != nil { - dns.Address = node.Address6.IP.String() - if err := PublishDNSUpdate(node.Network, dns); err != nil { - return fmt.Errorf("dns update node deletion %w", err) - } - } - return nil -} - -// PublishReplaceDNS publish a dns update to replace a dns entry on all hosts in network -func PublishReplaceDNS(oldNode, newNode *models.Node, host *models.Host) error { - dns := models.DNSUpdate{ - Action: models.DNSReplaceIP, - Name: host.Name + "." + oldNode.Network, - } - if !oldNode.Address.IP.Equal(newNode.Address.IP) { - dns.Address = oldNode.Address.IP.String() - dns.NewAddress = newNode.Address.IP.String() - if err := PublishDNSUpdate(oldNode.Network, dns); err != nil { - return err - } - } - if !oldNode.Address6.IP.Equal(newNode.Address6.IP) { - dns.Address = oldNode.Address6.IP.String() - dns.NewAddress = newNode.Address6.IP.String() - if err := PublishDNSUpdate(oldNode.Network, dns); err != nil { - return err - } - } - return nil -} - -// PublishExtClientDNS publish dns update for new extclient -func PublishExtClientDNS(client *models.ExtClient) error { - errMsgs := models.DNSError{} - dns := models.DNSUpdate{ - Action: models.DNSInsert, - Name: client.ClientID + "." + client.Network, - Address: client.Address, - } - if client.Address != "" { - dns.Address = client.Address - if err := PublishDNSUpdate(client.Network, dns); err != nil { - errMsgs.ErrorStrings = append(errMsgs.ErrorStrings, err.Error()) - } - - } - if client.Address6 != "" { - dns.Address = client.Address6 - if err := PublishDNSUpdate(client.Network, dns); err != nil { - errMsgs.ErrorStrings = append(errMsgs.ErrorStrings, err.Error()) - } - } - if len(errMsgs.ErrorStrings) > 0 { - return errMsgs - } - return nil -} - -// PublishExtClientDNSUpdate update for extclient name change -func PublishExtClientDNSUpdate(old, new models.ExtClient, network string) error { - dns := models.DNSUpdate{ - Action: models.DNSReplaceName, - Name: old.ClientID + "." + network, - NewName: new.ClientID + "." + network, - } - if err := PublishDNSUpdate(network, dns); err != nil { - return err - } - return nil -} - -// PublishDeleteAllExtclientsDNS - publish to delete all passed ext clients dns entries -func PublishDeleteAllExtclientsDNS(network string, clients []models.ExtClient) error { - nodes, err := logic.GetNetworkNodes(network) - if err != nil { - return err - } - for _, client := range clients { - dns := models.DNSUpdate{ - Action: models.DNSDeleteByName, - Name: client.ClientID + "." + client.Network, - } - go PublishDNSUpdatev1(client.Network, dns, nodes) - } - return nil -} - -// PublishDeleteExtClientDNS publish dns update to delete extclient entry -func PublishDeleteExtClientDNS(client *models.ExtClient) error { - dns := models.DNSUpdate{ - Action: models.DNSDeleteByName, - Name: client.ClientID + "." + client.Network, - } - if err := PublishDNSUpdate(client.Network, dns); err != nil { - return err - } - return nil -} - -// PublishCustomDNS publish dns update for new custom dns entry -func PublishCustomDNS(entry *models.DNSEntry) error { - dns := models.DNSUpdate{ - Action: models.DNSInsert, - Name: entry.Name, - //entry.Address6 is never used - Address: entry.Address, - } - if err := PublishDNSUpdate(entry.Network, dns); err != nil { - return err - } - return nil -} - -// PublishHostDNSUpdate publishes dns update on host name change -func PublishHostDNSUpdate(old, new *models.Host, networks []string) error { - errMsgs := models.DNSError{} - for _, network := range networks { - dns := models.DNSUpdate{ - Action: models.DNSReplaceName, - Name: old.Name + "." + network, - NewName: new.Name + "." + network, - } - if err := PublishDNSUpdate(network, dns); err != nil { - errMsgs.ErrorStrings = append(errMsgs.ErrorStrings, err.Error()) - } - } - if len(errMsgs.ErrorStrings) > 0 { - return errMsgs - } - return nil } func PushMetricsToExporter(metrics models.Metrics) error { @@ -422,71 +203,6 @@ func PushMetricsToExporter(metrics models.Metrics) error { return nil } -func getNodeDNS(network string) []models.DNSUpdate { - alldns := []models.DNSUpdate{} - dns := models.DNSUpdate{} - nodes, err := logic.GetNetworkNodes(network) - if err != nil { - logger.Log(0, "error retreiving network nodes for network", network, err.Error()) - } - for _, node := range nodes { - host, err := logic.GetHost(node.HostID.String()) - if err != nil { - logger.Log(0, "error retrieving host for dns update", node.HostID.String(), err.Error()) - continue - } - dns.Action = models.DNSInsert - dns.Name = host.Name + "." + node.Network - if node.Address.IP != nil { - dns.Address = node.Address.IP.String() - alldns = append(alldns, dns) - } - if node.Address6.IP != nil { - dns.Address = node.Address6.IP.String() - alldns = append(alldns, dns) - } - } - return alldns -} - -func getExtClientDNS(network string) []models.DNSUpdate { - alldns := []models.DNSUpdate{} - dns := models.DNSUpdate{} - clients, err := logic.GetNetworkExtClients(network) - if err != nil { - logger.Log(0, "error retrieving extclients", err.Error()) - } - for _, client := range clients { - dns.Action = models.DNSInsert - dns.Name = client.ClientID + "." + client.Network - if client.Address != "" { - dns.Address = client.Address - alldns = append(alldns, dns) - } - if client.Address6 != "" { - dns.Address = client.Address - alldns = append(alldns, dns) - } - } - return alldns -} - -func getCustomDNS(network string) []models.DNSUpdate { - alldns := []models.DNSUpdate{} - dns := models.DNSUpdate{} - customdns, err := logic.GetCustomDNS(network) - if err != nil { - logger.Log(0, "error retrieving custom dns entries", err.Error()) - } - for _, custom := range customdns { - dns.Action = models.DNSInsert - dns.Address = custom.Address - dns.Name = custom.Name + "." + custom.Network - alldns = append(alldns, dns) - } - return alldns -} - // sendPeers - retrieve networks, send peer ports to all peers func sendPeers() { diff --git a/mq/util.go b/mq/util.go index 6ecee1414..d99891098 100644 --- a/mq/util.go +++ b/mq/util.go @@ -78,7 +78,7 @@ func publish(host *models.Host, dest string, msg []byte) error { if encryptErr != nil { return encryptErr } - if mqclient == nil { + if mqclient == nil || !mqclient.IsConnectionOpen() { return errors.New("cannot publish ... mqclient not connected") } diff --git a/pro/controllers/users.go b/pro/controllers/users.go index 2323ee0c5..7b82fa5f6 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -10,6 +10,7 @@ import ( "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/servercfg" "golang.org/x/exp/slog" ) @@ -116,6 +117,9 @@ func removeUserFromRemoteAccessGW(w http.ResponseWriter, r *http.Request) { logic.DeleteExtClient(extclient.Network, extclient.ClientID) } } + if servercfg.IsDNSMode() { + logic.SetDNS() + } }(*user, remoteGwID) err = logic.UpsertUser(*user) @@ -148,17 +152,23 @@ func getUserRemoteAccessGws(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("required params username"), "badrequest")) return } + remoteAccessClientID := r.URL.Query().Get("remote_access_clientid") var req models.UserRemoteGwsReq - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - slog.Error("error decoding request body: ", "error", err) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return + if remoteAccessClientID == "" { + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + slog.Error("error decoding request body: ", "error", err) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } } - if req.RemoteAccessClientID == "" { + if req.RemoteAccessClientID == "" && remoteAccessClientID == "" { logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("remote access client id cannot be empty"), "badrequest")) return } + if req.RemoteAccessClientID == "" { + req.RemoteAccessClientID = remoteAccessClientID + } userGws := make(map[string][]models.UserRemoteGws) user, err := logic.GetUser(username) if err != nil { @@ -166,16 +176,13 @@ func getUserRemoteAccessGws(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to fetch user %s, error: %v", username, err), "badrequest")) return } - if user.IsAdmin || user.IsSuperAdmin { - logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("admins can visit dashboard to create remote clients"), "badrequest")) - return - } allextClients, err := logic.GetAllExtClients() if err != nil { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } + processedAdminNodeIds := make(map[string]struct{}) for _, extClient := range allextClients { if extClient.RemoteAccessClientID == req.RemoteAccessClientID && extClient.OwnerID == username { node, err := logic.GetNodeByID(extClient.IngressGatewayID) @@ -193,7 +200,7 @@ func getUserRemoteAccessGws(w http.ResponseWriter, r *http.Request) { continue } - if _, ok := user.RemoteGwIDs[node.ID.String()]; ok { + if _, ok := user.RemoteGwIDs[node.ID.String()]; (!user.IsAdmin && !user.IsSuperAdmin) && ok { gws := userGws[node.Network] extClient.AllowedIPs = logic.GetExtclientAllowedIPs(extClient) gws = append(gws, models.UserRemoteGws{ @@ -207,40 +214,81 @@ func getUserRemoteAccessGws(w http.ResponseWriter, r *http.Request) { }) userGws[node.Network] = gws delete(user.RemoteGwIDs, node.ID.String()) - + } else { + gws := userGws[node.Network] + extClient.AllowedIPs = logic.GetExtclientAllowedIPs(extClient) + gws = append(gws, models.UserRemoteGws{ + GwID: node.ID.String(), + GWName: host.Name, + Network: node.Network, + GwClient: extClient, + Connected: true, + IsInternetGateway: node.IsInternetGateway, + GwPeerPublicKey: host.PublicKey.String(), + }) + userGws[node.Network] = gws + processedAdminNodeIds[node.ID.String()] = struct{}{} } } - } // add remaining gw nodes to resp - for gwID := range user.RemoteGwIDs { - node, err := logic.GetNodeByID(gwID) - if err != nil { - continue - } - if !node.IsIngressGateway { - continue - } - if node.PendingDelete { - continue + if !user.IsAdmin && !user.IsSuperAdmin { + for gwID := range user.RemoteGwIDs { + node, err := logic.GetNodeByID(gwID) + if err != nil { + continue + } + if !node.IsIngressGateway { + continue + } + if node.PendingDelete { + continue + } + host, err := logic.GetHost(node.HostID.String()) + if err != nil { + continue + } + gws := userGws[node.Network] + + gws = append(gws, models.UserRemoteGws{ + GwID: node.ID.String(), + GWName: host.Name, + Network: node.Network, + IsInternetGateway: node.IsInternetGateway, + GwPeerPublicKey: host.PublicKey.String(), + }) + userGws[node.Network] = gws } - host, err := logic.GetHost(node.HostID.String()) + } else { + allNodes, err := logic.GetAllNodes() if err != nil { - continue + slog.Error("failed to fetch all nodes", "error", err) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return } - gws := userGws[node.Network] + for _, node := range allNodes { + _, ok := processedAdminNodeIds[node.ID.String()] + if node.IsIngressGateway && !node.PendingDelete && !ok { + host, err := logic.GetHost(node.HostID.String()) + if err != nil { + slog.Error("failed to fetch host", "error", err) + continue + } + gws := userGws[node.Network] - gws = append(gws, models.UserRemoteGws{ - GwID: node.ID.String(), - GWName: host.Name, - Network: node.Network, - IsInternetGateway: node.IsInternetGateway, - GwPeerPublicKey: host.PublicKey.String(), - }) - userGws[node.Network] = gws + gws = append(gws, models.UserRemoteGws{ + GwID: node.ID.String(), + GWName: host.Name, + Network: node.Network, + IsInternetGateway: node.IsInternetGateway, + GwPeerPublicKey: host.PublicKey.String(), + }) + userGws[node.Network] = gws + } + } } - + slog.Debug("returned user gws", "user", username, "gws", userGws) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(userGws) } diff --git a/servercfg/serverconf.go b/servercfg/serverconf.go index 46b4178f5..dcf4fd8c4 100644 --- a/servercfg/serverconf.go +++ b/servercfg/serverconf.go @@ -119,6 +119,7 @@ func GetServerInfo() models.ServerConfig { cfg.APIPort = GetAPIPort() cfg.DNSMode = "off" cfg.Broker = GetPublicBrokerEndpoint() + cfg.BrokerType = GetBrokerType() if IsDNSMode() { cfg.DNSMode = "on" }