diff --git a/compose/docker-compose.yml b/compose/docker-compose.yml index 7acff9e2..40e7dd1f 100644 --- a/compose/docker-compose.yml +++ b/compose/docker-compose.yml @@ -19,7 +19,8 @@ services: - sqldata:/root/data - mosquitto_data:/etc/netmaker environment: - SERVER_NAME: "broker.NETMAKER_BASE_DOMAIN" + BROKER_NAME: "broker.NETMAKER_BASE_DOMAIN" + SERVER_NAME: "NETMAKER_BASE_DOMAIN" SERVER_HOST: "SERVER_PUBLIC_IP" SERVER_API_CONN_STRING: "api.NETMAKER_BASE_DOMAIN:443" COREDNS_ADDR: "SERVER_PUBLIC_IP" diff --git a/config/config.go b/config/config.go index 443af912..9d13a526 100644 --- a/config/config.go +++ b/config/config.go @@ -1,7 +1,7 @@ -//Environment file for getting variables -//Currently the only thing it does is set the master password -//Should probably have it take over functions from OS such as port and mongodb connection details -//Reads from the config/environments/dev.yaml file by default +// Environment file for getting variables +// Currently the only thing it does is set the master password +// Should probably have it take over functions from OS such as port and mongodb connection details +// Reads from the config/environments/dev.yaml file by default package config import ( @@ -69,6 +69,7 @@ type ServerConfig struct { MQPort string `yaml:"mqport"` MQServerPort string `yaml:"mqserverport"` Server string `yaml:"server"` + Broker string `yam:"broker"` PublicIPService string `yaml:"publicipservice"` MQAdminPassword string `yaml:"mqadminpassword"` MetricsExporter string `yaml:"metrics_exporter"` diff --git a/controllers/node.go b/controllers/node.go index 07d5b534..4feb9f61 100644 --- a/controllers/node.go +++ b/controllers/node.go @@ -2,6 +2,7 @@ package controller import ( "encoding/json" + "errors" "fmt" "net/http" "strings" @@ -40,13 +41,13 @@ func nodeHandlers(r *mux.Router) { // // Authenticate to make further API calls related to a network. // -// Schemes: https +// Schemes: https // -// Security: -// oauth +// Security: +// oauth // -// Responses: -// 200: successResponse +// Responses: +// 200: successResponse func authenticate(response http.ResponseWriter, request *http.Request) { var authRequest models.AuthParams @@ -343,13 +344,13 @@ func authorize(nodesAllowed, networkCheck bool, authNetwork string, next http.Ha // // Gets all nodes associated with network including pending nodes. // -// Schemes: https +// Schemes: https // -// Security: -// oauth +// Security: +// oauth // -// Responses: -// 200: nodeSliceResponse +// Responses: +// 200: nodeSliceResponse func getNetworkNodes(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -382,13 +383,14 @@ func getNetworkNodes(w http.ResponseWriter, r *http.Request) { // // Get all nodes across all networks. // -// Schemes: https +// Schemes: https // -// Security: -// oauth +// Security: +// oauth +// +// Responses: +// 200: nodeSliceResponse // -// Responses: -// 200: nodeSliceResponse // Not quite sure if this is necessary. Probably necessary based on front end but may want to review after iteration 1 if it's being used or not func getAllNodes(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -439,13 +441,13 @@ func getUsersNodes(user models.User) ([]models.Node, error) { // // Get an individual node. // -// Schemes: https +// Schemes: https // -// Security: -// oauth +// Security: +// oauth // -// Responses: -// 200: nodeResponse +// Responses: +// 200: nodeResponse func getNode(w http.ResponseWriter, r *http.Request) { // set header. w.Header().Set("Content-Type", "application/json") @@ -504,13 +506,13 @@ func getNode(w http.ResponseWriter, r *http.Request) { // // Create a node on a network. // -// Schemes: https +// Schemes: https // -// Security: -// oauth +// Security: +// oauth // -// Responses: -// 200: nodeGetResponse +// Responses: +// 200: nodeGetResponse func createNode(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -547,6 +549,12 @@ func createNode(w http.ResponseWriter, r *http.Request) { return } + if !logic.IsVersionComptatible(node.Version) { + err := errors.New("incomatible netclient version") + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return + } + node.Network = networkName network, err := logic.GetNetworkByNode(&node) @@ -631,7 +639,7 @@ func createNode(w http.ResponseWriter, r *http.Request) { } if !updatedUserNode { // user was found but not updated, so delete node logger.Log(0, "failed to add node to user", keyName) - logic.DeleteNodeByID(&node, true) + logic.DeleteNode(&node, true) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } @@ -645,12 +653,12 @@ func createNode(w http.ResponseWriter, r *http.Request) { return } - // Create client for this node in Mq + // Create client for this host in Mq event := mq.MqDynsecPayload{ Commands: []mq.MqDynSecCmd{ { // delete if any client exists already Command: mq.DeleteClientCmd, - Username: node.ID, + Username: node.HostID, }, { Command: mq.CreateRoleCmd, @@ -660,7 +668,7 @@ func createNode(w http.ResponseWriter, r *http.Request) { }, { Command: mq.CreateClientCmd, - Username: node.ID, + Username: node.HostID, Password: nodePassword, Textname: node.Name, Roles: []mq.MqDynSecRole{ @@ -700,13 +708,14 @@ func createNode(w http.ResponseWriter, r *http.Request) { // // Takes a node out of pending state. // -// Schemes: https +// Schemes: https // -// Security: -// oauth +// Security: +// oauth +// +// Responses: +// 200: nodeResponse // -// Responses: -// 200: nodeResponse // Takes node out of pending state // TODO: May want to use cordon/uncordon terminology instead of "ispending". func uncordonNode(w http.ResponseWriter, r *http.Request) { @@ -733,13 +742,13 @@ func uncordonNode(w http.ResponseWriter, r *http.Request) { // // Create an egress gateway. // -// Schemes: https +// Schemes: https // -// Security: -// oauth +// Security: +// oauth // -// Responses: -// 200: nodeResponse +// Responses: +// 200: nodeResponse func createEgressGateway(w http.ResponseWriter, r *http.Request) { var gateway models.EgressGatewayRequest var params = mux.Vars(r) @@ -772,13 +781,13 @@ func createEgressGateway(w http.ResponseWriter, r *http.Request) { // // Delete an egress gateway. // -// Schemes: https +// Schemes: https // -// Security: -// oauth +// Security: +// oauth // -// Responses: -// 200: nodeResponse +// Responses: +// 200: nodeResponse func deleteEgressGateway(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var params = mux.Vars(r) @@ -806,13 +815,13 @@ func deleteEgressGateway(w http.ResponseWriter, r *http.Request) { // // Create an ingress gateway. // -// Schemes: https +// Schemes: https // -// Security: -// oauth +// Security: +// oauth // -// Responses: -// 200: nodeResponse +// Responses: +// 200: nodeResponse func createIngressGateway(w http.ResponseWriter, r *http.Request) { var params = mux.Vars(r) w.Header().Set("Content-Type", "application/json") @@ -850,13 +859,13 @@ func createIngressGateway(w http.ResponseWriter, r *http.Request) { // // Delete an ingress gateway. // -// Schemes: https +// Schemes: https // -// Security: -// oauth +// Security: +// oauth // -// Responses: -// 200: nodeResponse +// Responses: +// 200: nodeResponse func deleteIngressGateway(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var params = mux.Vars(r) @@ -888,13 +897,13 @@ func deleteIngressGateway(w http.ResponseWriter, r *http.Request) { // // Update an individual node. // -// Schemes: https +// Schemes: https // -// Security: -// oauth +// Security: +// oauth // -// Responses: -// 200: nodeResponse +// Responses: +// 200: nodeResponse func updateNode(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -998,13 +1007,13 @@ func updateNode(w http.ResponseWriter, r *http.Request) { // // Delete an individual node. // -// Schemes: https +// Schemes: https // -// Security: -// oauth +// Security: +// oauth // -// Responses: -// 200: nodeResponse +// Responses: +// 200: nodeResponse func deleteNode(w http.ResponseWriter, r *http.Request) { // Set header w.Header().Set("Content-Type", "application/json") @@ -1013,22 +1022,11 @@ func deleteNode(w http.ResponseWriter, r *http.Request) { var params = mux.Vars(r) var nodeid = params["nodeid"] fromNode := r.Header.Get("requestfrom") == "node" - var node, err = logic.GetNodeByID(nodeid) + node, err := logic.GetNodeByID(nodeid) if err != nil { - if fromNode { - node, err = logic.GetDeletedNodeByID(nodeid) - if err != nil { - logger.Log(0, r.Header.Get("user"), - fmt.Sprintf("error fetching node from deleted nodes [ %s ] info: %v", nodeid, err)) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } - } else { - logger.Log(0, r.Header.Get("user"), - fmt.Sprintf("error fetching node [ %s ] info: %v", nodeid, err)) - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) - return - } + logger.Log(0, "error retrieving node to delete", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) + return } if isServer(&node) { err := fmt.Errorf("cannot delete server node") @@ -1044,34 +1042,35 @@ func deleteNode(w http.ResponseWriter, r *http.Request) { return } } - //send update to node to be deleted before deleting on server otherwise message cannot be sent - node.Action = models.NODE_DELETE - - err = logic.DeleteNodeByID(&node, fromNode) - if err != nil { - logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + if err := logic.DeleteNode(&node, fromNode); err != nil { + logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("failed to delete node"), "internal")) return } if fromNode { - // deletes node related role and client - event := mq.MqDynsecPayload{ - Commands: []mq.MqDynSecCmd{ - { - Command: mq.DeleteClientCmd, - Username: nodeid, + //check if server should be removed from mq + found := false + // err is irrelevent + nodes, _ := logic.GetAllNodes() + for _, nodetocheck := range nodes { + if nodetocheck.HostID == node.HostID { + found = true + break + } + } + if !found { + // deletes node related role and client + event := mq.MqDynsecPayload{ + Commands: []mq.MqDynSecCmd{ + { + Command: mq.DeleteClientCmd, + Username: node.HostID, + }, }, - }, - } - - if err := mq.PublishEventToDynSecTopic(event); err != nil { - logger.Log(0, fmt.Sprintf("failed to send DynSec command [%v]: %v", - event.Commands, err.Error())) - } - } - - if servercfg.Is_EE { - if err = logic.EnterpriseResetAllPeersFailovers(node.ID, node.Network); err != nil { - logger.Log(0, "failed to reset failover lists during node delete for node", node.Name, node.Network) + } + if err := mq.PublishEventToDynSecTopic(event); err != nil { + logger.Log(0, fmt.Sprintf("failed to send DynSec command [%v]: %v", + event.Commands, err.Error())) + } } } logic.ReturnSuccessResponse(w, r, nodeid+" deleted.") diff --git a/go.mod b/go.mod index 2a754774..a473adb5 100644 --- a/go.mod +++ b/go.mod @@ -50,6 +50,8 @@ require ( gortc.io/stun v1.23.0 ) +require github.com/matryer/is v1.4.0 + require ( cloud.google.com/go/compute v1.7.0 // indirect fyne.io/systray v1.10.1-0.20220621085403-9a2652634e93 // indirect @@ -77,6 +79,7 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.8 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect + github.com/hashicorp/go-version v1.6.0 github.com/josharian/native v1.0.0 // indirect github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect github.com/kr/text v0.2.0 // indirect diff --git a/go.sum b/go.sum index 7fd5489c..b980cadd 100644 --- a/go.sum +++ b/go.sum @@ -301,6 +301,8 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -341,6 +343,8 @@ github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= diff --git a/k8s/client/netclient-daemonset.yaml b/k8s/client/netclient-daemonset.yaml index 14f9ffa5..ac4edf38 100644 --- a/k8s/client/netclient-daemonset.yaml +++ b/k8s/client/netclient-daemonset.yaml @@ -16,7 +16,11 @@ spec: hostNetwork: true containers: - name: netclient +<<<<<<< HEAD image: gravitl/netclient:v0.17.0 +======= + image: gravitl/netclient:v0.16.3 +>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4 env: - name: TOKEN value: "TOKEN_VALUE" diff --git a/k8s/client/netclient.yaml b/k8s/client/netclient.yaml index ca6bd0c4..18ae287a 100644 --- a/k8s/client/netclient.yaml +++ b/k8s/client/netclient.yaml @@ -28,7 +28,11 @@ spec: # - "" containers: - name: netclient +<<<<<<< HEAD image: gravitl/netclient:v0.17.0 +======= + image: gravitl/netclient:v0.16.3 +>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4 env: - name: TOKEN value: "TOKEN_VALUE" diff --git a/k8s/server/netmaker-server.yaml b/k8s/server/netmaker-server.yaml index 43853cd9..c82a4780 100644 --- a/k8s/server/netmaker-server.yaml +++ b/k8s/server/netmaker-server.yaml @@ -83,7 +83,11 @@ spec: value: "Kubernetes" - name: VERBOSITY value: "3" +<<<<<<< HEAD image: gravitl/netmaker:v0.17.0 +======= + image: gravitl/netmaker:v0.16.3 +>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4 imagePullPolicy: Always name: netmaker ports: diff --git a/k8s/server/netmaker-ui.yaml b/k8s/server/netmaker-ui.yaml index 4d5944e6..5d95e2c0 100644 --- a/k8s/server/netmaker-ui.yaml +++ b/k8s/server/netmaker-ui.yaml @@ -15,7 +15,11 @@ spec: spec: containers: - name: netmaker-ui +<<<<<<< HEAD image: gravitl/netmaker-ui:v0.17.0 +======= + image: gravitl/netmaker-ui:v0.16.3 +>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4 ports: - containerPort: 443 env: diff --git a/logic/networks.go b/logic/networks.go index e2676e4a..c116f7bf 100644 --- a/logic/networks.go +++ b/logic/networks.go @@ -54,7 +54,7 @@ func DeleteNetwork(network string) error { servers, err := GetSortedNetworkServerNodes(network) if err == nil { for _, s := range servers { - if err = DeleteNodeByID(&s, true); err != nil { + if err = DeleteNode(&s, true); err != nil { logger.Log(2, "could not removed server", s.Name, "before deleting network", network) } else { logger.Log(2, "removed server", s.Name, "before deleting network", network) diff --git a/logic/nodes.go b/logic/nodes.go index 6bd46fe8..0f23adf7 100644 --- a/logic/nodes.go +++ b/logic/nodes.go @@ -1,6 +1,7 @@ package logic import ( + "context" "encoding/json" "errors" "fmt" @@ -22,8 +23,14 @@ import ( "golang.org/x/crypto/bcrypt" ) -// RELAY_NODE_ERR - error to return if relay node is unfound -const RELAY_NODE_ERR = "could not find relay for node" +const ( + // RELAY_NODE_ERR - error to return if relay node is unfound + RELAY_NODE_ERR = "could not find relay for node" + // NodePurgeTime time to wait for node to response to a NODE_DELETE actions + NodePurgeTime = time.Second * 10 + // NodePurgeCheckTime is how often to check nodes for Pending Delete + NodePurgeCheckTime = time.Second * 30 +) // GetNetworkNodes - gets the nodes of a network func GetNetworkNodes(network string) ([]models.Node, error) { @@ -160,8 +167,31 @@ func UpdateNode(currentNode *models.Node, newNode *models.Node) error { return fmt.Errorf("failed to update node " + currentNode.ID + ", cannot change ID.") } -// DeleteNodeByID - deletes a node from database or moves into delete nodes table -func DeleteNodeByID(node *models.Node, exterminate bool) error { +// DeleteNode - marks node for deletion if called by UI or deletes node if called by node +func DeleteNode(node *models.Node, purge bool) error { + if !purge { + newnode := node + newnode.PendingDelete = true + newnode.Action = models.NODE_DELETE + if err := UpdateNode(node, newnode); err != nil { + return err + } + return nil + } + if err := DeleteNodeByID(node); err != nil { + return err + } + if servercfg.Is_EE { + if err := EnterpriseResetAllPeersFailovers(node.ID, node.Network); err != nil { + logger.Log(0, "failed to reset failover lists during node delete for node", node.Name, node.Network) + } + } + + return nil +} + +// DeleteNodeByID - deletes a node from database +func DeleteNodeByID(node *models.Node) error { var err error var key = node.ID //delete any ext clients as required @@ -170,27 +200,11 @@ func DeleteNodeByID(node *models.Node, exterminate bool) error { logger.Log(0, "failed to deleted ext clients", err.Error()) } } - if !exterminate { - node.Action = models.NODE_DELETE - nodedata, err := json.Marshal(&node) - if err != nil { - return err - } - err = database.Insert(key, string(nodedata), database.DELETED_NODES_TABLE_NAME) - if err != nil { - return err - } - } else { - if err := database.DeleteRecord(database.DELETED_NODES_TABLE_NAME, key); err != nil { - logger.Log(2, err.Error()) - } - } if err = database.DeleteRecord(database.NODES_TABLE_NAME, key); err != nil { if !database.IsEmptyRecord(err) { return err } } - if servercfg.IsDNSMode() { SetDNS() } @@ -200,7 +214,6 @@ func DeleteNodeByID(node *models.Node, exterminate bool) error { logger.Log(0, "failed to dissasociate", node.OwnerID, "from node", node.ID, ":", err.Error()) } } - _, err = nodeacls.RemoveNodeACL(nodeacls.NetworkID(node.Network), nodeacls.NodeID(node.ID)) if err != nil { // ignoring for now, could hit a nil pointer if delete called twice @@ -210,11 +223,9 @@ func DeleteNodeByID(node *models.Node, exterminate bool) error { if err = DeleteMetrics(node.ID); err != nil { logger.Log(1, "unable to remove metrics from DB for node", node.ID, err.Error()) } - if node.IsServer == "yes" { return removeLocalServer(node) } - return nil } @@ -324,6 +335,9 @@ func CreateNode(node *models.Node) error { } node.ID = uuid.NewString() + if node.IsServer == "yes" { + node.HostID = uuid.NewString() + } //Create a JWT for the node tokenString, _ := CreateJWT(node.ID, node.MacAddress, node.Network) @@ -770,4 +784,34 @@ func updateProNodeACLS(node *models.Node) error { return nil } +func PurgePendingNodes(ctx context.Context) { + ticker := time.NewTicker(NodePurgeCheckTime) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + nodes, err := GetAllNodes() + if err != nil { + logger.Log(0, "PurgePendingNodes failed to retrieve nodes", err.Error()) + continue + } + for _, node := range nodes { + if node.PendingDelete { + modified := time.Unix(node.LastModified, 0) + if time.Since(modified) > NodePurgeTime { + if err := DeleteNode(&node, true); err != nil { + logger.Log(0, "failed to purge node", node.ID, err.Error()) + } else { + logger.Log(0, "purged node ", node.ID) + } + + } + } + } + } + } +} + // == END PRO == diff --git a/logic/peers.go b/logic/peers.go index 83c2a31f..f7e1515c 100644 --- a/logic/peers.go +++ b/logic/peers.go @@ -257,6 +257,8 @@ func GetPeerUpdate(node *models.Node) (models.PeerUpdate, error) { if peer.LocalListenPort != 0 { peer.ListenPort = peer.LocalListenPort } + } else { + continue } } diff --git a/logic/server.go b/logic/server.go index a13cfeff..767421b6 100644 --- a/logic/server.go +++ b/logic/server.go @@ -208,7 +208,7 @@ func ServerUpdate(serverNode *models.Node, ifaceDelta bool) error { var err = ServerPull(serverNode, ifaceDelta) if isDeleteError(err) { - return DeleteNodeByID(serverNode, true) + return DeleteNode(serverNode, true) } else if err != nil && !ifaceDelta { err = ServerPull(serverNode, true) if err != nil { @@ -239,7 +239,7 @@ func checkNodeActions(node *models.Node) string { } } if node.Action == models.NODE_DELETE { - err := DeleteNodeByID(node, true) + err := DeleteNode(node, true) if err != nil { logger.Log(1, "error deleting locally:", err.Error()) } diff --git a/logic/version.go b/logic/version.go new file mode 100644 index 00000000..24576a93 --- /dev/null +++ b/logic/version.go @@ -0,0 +1,31 @@ +package logic + +import ( + "strings" + "unicode" + + "github.com/hashicorp/go-version" +) + +const MinVersion = "v0.17.0" + +// IsVersionCompatible checks that the version passed is compabtible (>=) with MinVersion +func IsVersionComptatible(ver string) bool { + // during dev, assume developers know what they are doing + if ver == "dev" { + return true + } + trimmed := strings.TrimFunc(ver, func(r rune) bool { + return !unicode.IsNumber(r) + }) + v, err := version.NewVersion(trimmed) + if err != nil { + return false + } + constraint, err := version.NewConstraint(">= " + MinVersion) + if err != nil { + return false + } + return constraint.Check(v) + +} diff --git a/logic/version_test.go b/logic/version_test.go new file mode 100644 index 00000000..c94af967 --- /dev/null +++ b/logic/version_test.go @@ -0,0 +1,35 @@ +package logic + +import ( + "testing" + + "github.com/matryer/is" +) + +func TestVersion(t *testing.T) { + t.Run("valid version", func(t *testing.T) { + is := is.New(t) + valid := IsVersionComptatible("v0.17.1-testing") + is.Equal(valid, true) + }) + t.Run("dev version", func(t *testing.T) { + is := is.New(t) + valid := IsVersionComptatible("dev") + is.Equal(valid, true) + }) + t.Run("invalid version", func(t *testing.T) { + is := is.New(t) + valid := IsVersionComptatible("v0.14.2-refactor") + is.Equal(valid, false) + }) + t.Run("no version", func(t *testing.T) { + is := is.New(t) + valid := IsVersionComptatible("testing") + is.Equal(valid, false) + }) + t.Run("incomplete version", func(t *testing.T) { + is := is.New(t) + valid := IsVersionComptatible("0.18") + is.Equal(valid, true) + }) +} diff --git a/logic/zombie.go b/logic/zombie.go index af647d1e..6ac7cc98 100644 --- a/logic/zombie.go +++ b/logic/zombie.go @@ -74,7 +74,7 @@ func ManageZombies(ctx context.Context) { continue } if time.Since(time.Unix(node.LastCheckIn, 0)) > time.Minute*ZOMBIE_DELETE_TIME { - if err := DeleteNodeByID(&node, true); err != nil { + if err := DeleteNode(&node, true); err != nil { logger.Log(1, "error deleting zombie node", zombies[i], err.Error()) continue } diff --git a/main.go b/main.go index 23f5d218..c2c26eec 100644 --- a/main.go +++ b/main.go @@ -209,6 +209,7 @@ func runMessageQueue(wg *sync.WaitGroup) { ctx, cancel := context.WithCancel(context.Background()) go mq.Keepalive(ctx) go logic.ManageZombies(ctx) + go logic.PurgePendingNodes(ctx) quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGTERM, os.Interrupt) <-quit diff --git a/models/node.go b/models/node.go index 2d084686..8c66e40c 100644 --- a/models/node.go +++ b/models/node.go @@ -43,14 +43,23 @@ var seededRand *rand.Rand = rand.New( type NodeCheckin struct { Version string Connected string + Ifaces []Iface +} + +// Iface struct for local interfaces of a node +type Iface struct { + Name string + Address net.IPNet } // Node - struct for node model type Node struct { ID string `json:"id,omitempty" bson:"id,omitempty" yaml:"id,omitempty" validate:"required,min=5,id_unique"` + HostID string `json:"hostid,omitempty" bson:"id,omitempty" yaml:"id,omitempty" validate:"required,min=5,id_unique"` Address string `json:"address" bson:"address" yaml:"address" validate:"omitempty,ipv4"` Address6 string `json:"address6" bson:"address6" yaml:"address6" validate:"omitempty,ipv6"` LocalAddress string `json:"localaddress" bson:"localaddress" yaml:"localaddress" validate:"omitempty"` + Interfaces []Iface `json:"interfaces" yaml:"interfaces"` Name string `json:"name" bson:"name" yaml:"name" validate:"omitempty,max=62,in_charset"` NetworkSettings Network `json:"networksettings" bson:"networksettings" yaml:"networksettings" validate:"-"` ListenPort int32 `json:"listenport" bson:"listenport" yaml:"listenport" validate:"omitempty,numeric,min=1024,max=65535"` @@ -102,6 +111,7 @@ type Node struct { FirewallInUse string `json:"firewallinuse" bson:"firewallinuse" yaml:"firewallinuse"` InternetGateway string `json:"internetgateway" bson:"internetgateway" yaml:"internetgateway"` Connected string `json:"connected" bson:"connected" yaml:"connected" validate:"checkyesorno"` + PendingDelete bool `json:"pendingdelete" bson:"pendingdelete" yaml:"pendingdelete"` // == 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"` diff --git a/models/structs.go b/models/structs.go index 34c53ddd..a99fa6fd 100644 --- a/models/structs.go +++ b/models/structs.go @@ -220,6 +220,7 @@ type ServerConfig struct { Version string `yaml:"version"` MQPort string `yaml:"mqport"` Server string `yaml:"server"` + Broker string `yaml:"broker"` Is_EE bool `yaml:"isee"` StunPort string `yaml:"stun_port"` } diff --git a/mq/handlers.go b/mq/handlers.go index 8659b899..a27f1806 100644 --- a/mq/handlers.go +++ b/mq/handlers.go @@ -52,6 +52,7 @@ func Ping(client mqtt.Client, msg mqtt.Message) { node.SetLastCheckIn() node.Version = checkin.Version node.Connected = checkin.Connected + node.Interfaces = checkin.Ifaces if err := logic.UpdateNode(&node, &node); err != nil { logger.Log(0, "error updating node", node.Name, node.ID, " on checkin", err.Error()) return diff --git a/netclient/netclient.exe.manifest.xml b/netclient/netclient.exe.manifest.xml index 0db144e5..5ad93770 100644 --- a/netclient/netclient.exe.manifest.xml +++ b/netclient/netclient.exe.manifest.xml @@ -1,7 +1,11 @@ >>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4 processorArchitecture="*" name="netclient.exe" type="win32" diff --git a/scripts/nm-quick-interactive.sh b/scripts/nm-quick-interactive.sh index 4f942683..05da0c43 100644 --- a/scripts/nm-quick-interactive.sh +++ b/scripts/nm-quick-interactive.sh @@ -17,11 +17,14 @@ cat << "EOF" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - EOF +<<<<<<< HEAD if [ $(id -u) -ne 0 ]; then echo "This script must be run as root" exit 1 fi +======= +>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4 if [ -z "$1" ]; then echo "-----------------------------------------------------" echo "Would you like to install Netmaker Community Edition (CE), or Netmaker Enterprise Edition (EE)?" @@ -66,12 +69,24 @@ confirm() {( read -p 'Does everything look right? [y/n]: ' yn case $yn in [Yy]* ) override="true"; break;; +<<<<<<< HEAD [Nn]* ) echo "exiting..."; exit 1;; +======= + [Nn]* ) echo "exiting..."; exit;; +>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4 * ) echo "Please answer yes or no.";; esac done )} +<<<<<<< HEAD +======= +if [ $(id -u) -ne 0 ]; then + echo "This script must be run as root" + exit 1 +fi + +>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4 echo "checking dependencies..." OS=$(uname) @@ -124,9 +139,12 @@ if [ -z "${install_cmd}" ]; then fi set -- $dependencies +<<<<<<< HEAD ${update_cmd} +======= +>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4 while [ -n "$1" ]; do if [ "${OS}" = "FreeBSD" ]; then is_installed=$(pkg check -d $1 | grep "Checking" | grep "done") @@ -189,12 +207,22 @@ COREDNS_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p') SERVER_PUBLIC_IP=$(curl -s ifconfig.me) MASTER_KEY=$(tr -dc A-Za-z0-9 >>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4 select domain_option in "Auto Generated ($NETMAKER_BASE_DOMAIN)" "Custom Domain (e.x: netmaker.example.com)"; do case $REPLY in 1) @@ -212,9 +240,15 @@ select domain_option in "Auto Generated ($NETMAKER_BASE_DOMAIN)" "Custom Domain *) echo "invalid option $REPLY";; esac done +<<<<<<< HEAD wait_seconds 2 +======= + +wait_seconds 2 + +>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4 echo "-----------------------------------------------------" echo "The following subdomains will be used:" echo " dashboard.$NETMAKER_BASE_DOMAIN" @@ -225,6 +259,7 @@ if [ "$INSTALL_TYPE" = "ee" ]; then echo " prometheus.$NETMAKER_BASE_DOMAIN" echo " netmaker-exporter.$NETMAKER_BASE_DOMAIN" echo " grafana.$NETMAKER_BASE_DOMAIN" +<<<<<<< HEAD fi echo "-----------------------------------------------------" @@ -266,6 +301,45 @@ if [ -z "$GET_EMAIL" ]; then else EMAIL="$GET_EMAIL" fi +======= +fi + +echo "-----------------------------------------------------" + +if [[ "$DOMAIN_TYPE" == "custom" ]]; then + echo "before continuing, confirm DNS is configured correctly, with records pointing to $SERVER_PUBLIC_IP" + confirm +fi + +wait_seconds 1 + +if [ "$INSTALL_TYPE" = "ee" ]; then + + echo "-----------------------------------------------------" + echo "Provide Details for EE installation:" + echo " 1. Log into https://dashboard.license.netmaker.io" + echo " 2. Copy License Key Value: https://dashboard.license.netmaker.io/license-keys" + echo " 3. Retrieve Account ID: https://dashboard.license.netmaker.io/user" + echo " 4. note email address" + echo "-----------------------------------------------------" + unset LICENSE_KEY + while [ -z "$LICENSE_KEY" ]; do + read -p "License Key: " LICENSE_KEY + done + unset ACCOUNT_ID + while [ -z ${ACCOUNT_ID} ]; do + read -p "Account ID: " ACCOUNT_ID + done + +fi + +unset EMAIL +while [ -z ${EMAIL} ]; do + read -p "Email Address (for LetsEncrypt): " EMAIL +done + +wait_seconds 2 +>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4 wait_seconds 2 @@ -295,6 +369,7 @@ wait_seconds 3 echo "Pulling config files..." COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.yml" +<<<<<<< HEAD CADDY_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/docker/Caddyfile" if [ "$INSTALL_TYPE" = "ee" ]; then COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.ee.yml" @@ -302,6 +377,13 @@ if [ "$INSTALL_TYPE" = "ee" ]; then fi wget -O /root/docker-compose.yml $COMPOSE_URL && wget -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/master/docker/mosquitto.conf && wget -O /root/Caddyfile $CADDY_URL && wget -q -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/master/docker/wait.sh && chmod +x /root/wait.sh +======= +if [ "$INSTALL_TYPE" = "ee" ]; then + COMPOSE_URL="https://raw.githubusercontent.com/gravitl/netmaker/master/compose/docker-compose.ee.yml" +fi + +wget -O docker-compose.yml $COMPOSE_URL && wget -O /root/mosquitto.conf https://raw.githubusercontent.com/gravitl/netmaker/master/docker/mosquitto.conf && wget -q -O /root/wait.sh https://raw.githubusercontent.com/gravitl/netmaker/develop/docker/wait.sh && chmod +x wait.sh +>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4 mkdir -p /etc/netmaker @@ -311,7 +393,11 @@ sed -i "s/SERVER_PUBLIC_IP/$SERVER_PUBLIC_IP/g" /root/docker-compose.yml sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/Caddyfile sed -i "s/NETMAKER_BASE_DOMAIN/$NETMAKER_BASE_DOMAIN/g" /root/docker-compose.yml sed -i "s/REPLACE_MASTER_KEY/$MASTER_KEY/g" /root/docker-compose.yml +<<<<<<< HEAD sed -i "s/YOUR_EMAIL/$EMAIL/g" /root/Caddyfile +======= +sed -i "s/YOUR_EMAIL/$EMAIL/g" /root/docker-compose.yml +>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4 sed -i "s/REPLACE_MQ_ADMIN_PASSWORD/$MQ_PASSWORD/g" /root/docker-compose.yml if [ "$INSTALL_TYPE" = "ee" ]; then sed -i "s~YOUR_LICENSE_KEY~$LICENSE_KEY~g" /root/docker-compose.yml @@ -368,6 +454,7 @@ wait_seconds 3 echo "Configuring netmaker server as ingress gateway" +<<<<<<< HEAD for i in 1 2 3 4 5 6 do echo " waiting for server node to become available" @@ -386,6 +473,16 @@ do fi done +======= + +while [ -z "$SERVER_ID" ]; do + echo "waiting for server node to become available" + wait_seconds 2 + curlresponse=$(curl -s -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/netmaker) + SERVER_ID=$(jq -r '.[0].id' <<< ${curlresponse}) +done + +>>>>>>> 407c6ed20a427153acb4901db7e61d3016823cc4 curl -o /dev/null -s -X POST -H "Authorization: Bearer $MASTER_KEY" -H 'Content-Type: application/json' https://api.${NETMAKER_BASE_DOMAIN}/api/nodes/netmaker/$SERVER_ID/createingress )} diff --git a/servercfg/serverconf.go b/servercfg/serverconf.go index 7f900e32..f7f74cc0 100644 --- a/servercfg/serverconf.go +++ b/servercfg/serverconf.go @@ -96,6 +96,8 @@ func GetServerConfig() config.ServerConfig { // GetServerConfig - gets the server config into memory from file or env func GetServerInfo() models.ServerConfig { var cfg models.ServerConfig + cfg.Server = GetServer() + cfg.Broker = GetBroker() cfg.API = GetAPIConnString() cfg.CoreDNSAddr = GetCoreDNSAddr() cfg.APIPort = GetAPIPort() @@ -105,7 +107,6 @@ func GetServerInfo() models.ServerConfig { cfg.DNSMode = "on" } cfg.Version = GetVersion() - cfg.Server = GetServer() cfg.Is_EE = Is_EE cfg.StunPort = GetStunPort() @@ -385,6 +386,17 @@ func GetServer() string { return server } +// GetBroker - gets the broker name +func GetBroker() string { + server := "" + if os.Getenv("BROKER_NAME") != "" { + server = os.Getenv("BROKER_NAME") + } else if config.Config.Server.Broker != "" { + server = config.Config.Server.Broker + } + return server +} + func GetVerbosity() int32 { var verbosity = 0 var err error