diff --git a/controllers/controller.go b/controllers/controller.go index de3f3d77..aee74063 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -27,6 +27,7 @@ var HttpHandlers = []interface{}{ extClientHandlers, ipHandlers, loggerHandlers, + hostHandlers, } // HandleRESTRequests - handles the rest requests diff --git a/controllers/hosts.go b/controllers/hosts.go new file mode 100644 index 00000000..cd8f6c00 --- /dev/null +++ b/controllers/hosts.go @@ -0,0 +1,114 @@ +package controller + +import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/gravitl/netmaker/logger" + "github.com/gravitl/netmaker/logic" + "github.com/gravitl/netmaker/models" +) + +func hostHandlers(r *mux.Router) { + r.HandleFunc("/api/hosts", logic.SecurityCheck(false, http.HandlerFunc(getHosts))).Methods("GET") + r.HandleFunc("/api/hosts", logic.SecurityCheck(true, http.HandlerFunc(updateHost))).Methods("PUT") + r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(deleteHost))).Methods("DELETE") + // r.HandleFunc("/api/hosts/{hostid}/{network}", logic.SecurityCheck(false, http.HandlerFunc(getHosts))).Methods("PUT") +} + +// swagger:route GET /api/hosts hosts getHosts +// +// Lists all hosts. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: getHostsSliceResponse +func getHosts(w http.ResponseWriter, r *http.Request) { + currentHosts, err := logic.GetAllHosts() + if err != nil { + logger.Log(0, r.Header.Get("user"), "failed to fetch hosts: ", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + logger.Log(2, r.Header.Get("user"), "fetched all hosts") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(currentHosts) +} + +// swagger:route PUT /api/hosts/{hostid} hosts updateHost +// +// Updates a Netclient host on Netmaker server. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: updateHostResponse +func updateHost(w http.ResponseWriter, r *http.Request) { + var newHostData models.Host + err := json.NewDecoder(r.Body).Decode(&newHostData) + 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 + } + + // confirm host exists + currHost, err := logic.GetHost(newHostData.ID.String()) + 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 + } + + logic.UpdateHost(&newHostData, currHost) // update the in memory struct values + if err = logic.UpsertHost(&newHostData); 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 + } + + logger.Log(2, r.Header.Get("user"), "updated host", newHostData.ID.String()) + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(newHostData) +} + +// swagger:route DELETE /api/hosts/{hostid} hosts deleteHost +// +// Deletes a Netclient host from Netmaker server. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: deleteHostResponse +func deleteHost(w http.ResponseWriter, r *http.Request) { + var params = mux.Vars(r) + hostid := params["hostid"] + // confirm host exists + currHost, err := logic.GetHost(hostid) + if err != nil { + logger.Log(0, r.Header.Get("user"), "failed to delete a host:", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + + if err = logic.RemoveHost(currHost); err != nil { + logger.Log(0, r.Header.Get("user"), "failed to delete a host:", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + + logger.Log(2, r.Header.Get("user"), "removed host", currHost.Name) + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(currHost) +} diff --git a/database/database.go b/database/database.go index dea641be..156d18a2 100644 --- a/database/database.go +++ b/database/database.go @@ -14,96 +14,73 @@ import ( "golang.org/x/crypto/nacl/box" ) -// NETWORKS_TABLE_NAME - networks table -const NETWORKS_TABLE_NAME = "networks" +const ( + // == Table Names == + // NETWORKS_TABLE_NAME - networks table + NETWORKS_TABLE_NAME = "networks" + // NODES_TABLE_NAME - nodes table + NODES_TABLE_NAME = "nodes" + // DELETED_NODES_TABLE_NAME - deleted nodes table + DELETED_NODES_TABLE_NAME = "deletednodes" + // USERS_TABLE_NAME - users table + USERS_TABLE_NAME = "users" + // CERTS_TABLE_NAME - certificates table + CERTS_TABLE_NAME = "certs" + // DNS_TABLE_NAME - dns table + DNS_TABLE_NAME = "dns" + // EXT_CLIENT_TABLE_NAME - ext client table + EXT_CLIENT_TABLE_NAME = "extclients" + // PEERS_TABLE_NAME - peers table + PEERS_TABLE_NAME = "peers" + // SERVERCONF_TABLE_NAME - stores server conf + SERVERCONF_TABLE_NAME = "serverconf" + // SERVER_UUID_TABLE_NAME - stores unique netmaker server data + SERVER_UUID_TABLE_NAME = "serveruuid" + // SERVER_UUID_RECORD_KEY - telemetry thing + SERVER_UUID_RECORD_KEY = "serveruuid" + // DATABASE_FILENAME - database file name + DATABASE_FILENAME = "netmaker.db" + // GENERATED_TABLE_NAME - stores server generated k/v + GENERATED_TABLE_NAME = "generated" + // NODE_ACLS_TABLE_NAME - stores the node ACL rules + NODE_ACLS_TABLE_NAME = "nodeacls" + // SSO_STATE_CACHE - holds sso session information for OAuth2 sign-ins + SSO_STATE_CACHE = "ssostatecache" + // METRICS_TABLE_NAME - stores network metrics + METRICS_TABLE_NAME = "metrics" + // NETWORK_USER_TABLE_NAME - network user table tracks stats for a network user per network + NETWORK_USER_TABLE_NAME = "networkusers" + // USER_GROUPS_TABLE_NAME - table for storing usergroups + USER_GROUPS_TABLE_NAME = "usergroups" + // CACHE_TABLE_NAME - caching table + CACHE_TABLE_NAME = "cache" + // HOSTS_TABLE_NAME - the table name for hosts + HOSTS_TABLE_NAME = "hosts" -// NODES_TABLE_NAME - nodes table -const NODES_TABLE_NAME = "nodes" + // == ERROR CONSTS == + // NO_RECORD - no singular result found + NO_RECORD = "no result found" + // NO_RECORDS - no results found + NO_RECORDS = "could not find any records" -// DELETED_NODES_TABLE_NAME - deleted nodes table -const DELETED_NODES_TABLE_NAME = "deletednodes" - -// USERS_TABLE_NAME - users table -const USERS_TABLE_NAME = "users" - -// CERTS_TABLE_NAME - certificates table -const CERTS_TABLE_NAME = "certs" - -// DNS_TABLE_NAME - dns table -const DNS_TABLE_NAME = "dns" - -// EXT_CLIENT_TABLE_NAME - ext client table -const EXT_CLIENT_TABLE_NAME = "extclients" - -// PEERS_TABLE_NAME - peers table -const PEERS_TABLE_NAME = "peers" - -// SERVERCONF_TABLE_NAME - stores server conf -const SERVERCONF_TABLE_NAME = "serverconf" - -// SERVER_UUID_TABLE_NAME - stores unique netmaker server data -const SERVER_UUID_TABLE_NAME = "serveruuid" - -// SERVER_UUID_RECORD_KEY - telemetry thing -const SERVER_UUID_RECORD_KEY = "serveruuid" - -// DATABASE_FILENAME - database file name -const DATABASE_FILENAME = "netmaker.db" - -// GENERATED_TABLE_NAME - stores server generated k/v -const GENERATED_TABLE_NAME = "generated" - -// NODE_ACLS_TABLE_NAME - stores the node ACL rules -const NODE_ACLS_TABLE_NAME = "nodeacls" - -// SSO_STATE_CACHE - holds sso session information for OAuth2 sign-ins -const SSO_STATE_CACHE = "ssostatecache" - -// METRICS_TABLE_NAME - stores network metrics -const METRICS_TABLE_NAME = "metrics" - -// NETWORK_USER_TABLE_NAME - network user table tracks stats for a network user per network -const NETWORK_USER_TABLE_NAME = "networkusers" - -// USER_GROUPS_TABLE_NAME - table for storing usergroups -const USER_GROUPS_TABLE_NAME = "usergroups" - -// CACHE_TABLE_NAME - caching table -const CACHE_TABLE_NAME = "cache" - -// == ERROR CONSTS == - -// NO_RECORD - no singular result found -const NO_RECORD = "no result found" - -// NO_RECORDS - no results found -const NO_RECORDS = "could not find any records" - -// == Constants == - -// INIT_DB - initialize db -const INIT_DB = "init" - -// CREATE_TABLE - create table const -const CREATE_TABLE = "createtable" - -// INSERT - insert into db const -const INSERT = "insert" - -// INSERT_PEER - insert peer into db const -const INSERT_PEER = "insertpeer" - -// DELETE - delete db record const -const DELETE = "delete" - -// DELETE_ALL - delete a table const -const DELETE_ALL = "deleteall" - -// FETCH_ALL - fetch table contents const -const FETCH_ALL = "fetchall" - -// CLOSE_DB - graceful close of db const -const CLOSE_DB = "closedb" + // == DB Constants == + // INIT_DB - initialize db + INIT_DB = "init" + // CREATE_TABLE - create table const + CREATE_TABLE = "createtable" + // INSERT - insert into db const + INSERT = "insert" + // INSERT_PEER - insert peer into db const + INSERT_PEER = "insertpeer" + // DELETE - delete db record const + DELETE = "delete" + // DELETE_ALL - delete a table const + DELETE_ALL = "deleteall" + // FETCH_ALL - fetch table contents const + FETCH_ALL = "fetchall" + // CLOSE_DB - graceful close of db const + CLOSE_DB = "closedb" +) func getCurrentDB() map[string]interface{} { switch servercfg.GetDB() { @@ -155,6 +132,7 @@ func createTables() { createTable(NETWORK_USER_TABLE_NAME) createTable(USER_GROUPS_TABLE_NAME) createTable(CACHE_TABLE_NAME) + createTable(HOSTS_TABLE_NAME) } func createTable(tableName string) error { diff --git a/logic/hosts.go b/logic/hosts.go new file mode 100644 index 00000000..1627f3e6 --- /dev/null +++ b/logic/hosts.go @@ -0,0 +1,139 @@ +package logic + +import ( + "encoding/json" + "fmt" + + "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/models" +) + +// GetAllHosts - returns all hosts in flat list or error +func GetAllHosts() ([]models.Host, error) { + currHostMap, err := GetHostsMap() + if err != nil { + return nil, err + } + var currentHosts = []models.Host{} + for k := range currHostMap { + var h = *currHostMap[k] + currentHosts = append(currentHosts, h) + } + + return currentHosts, nil +} + +// GetHostsMap - gets all the current hosts on machine in a map +func GetHostsMap() (map[string]*models.Host, error) { + records, err := database.FetchRecords(database.HOSTS_TABLE_NAME) + if err != nil && !database.IsEmptyRecord(err) { + return nil, err + } + currHostMap := make(map[string]*models.Host) + for k := range records { + var h models.Host + err = json.Unmarshal([]byte(records[k]), &h) + if err != nil { + return nil, err + } + currHostMap[h.ID.String()] = &h + } + + return currHostMap, nil +} + +// GetHost - gets a host from db given id +func GetHost(hostid string) (*models.Host, error) { + record, err := database.FetchRecord(database.HOSTS_TABLE_NAME, hostid) + if err != nil { + return nil, err + } + + var h models.Host + if err = json.Unmarshal([]byte(record), &h); err != nil { + return nil, err + } + + return &h, nil +} + +// CreateHost - creates a host if not exist +func CreateHost(h *models.Host) error { + _, err := GetHost(h.ID.String()) + if (err != nil && !database.IsEmptyRecord(err)) || (err == nil) { + return fmt.Errorf("host already exists") + } + + return UpsertHost(h) +} + +// UpdateHost - updates host data by field +func UpdateHost(newHost, currentHost *models.Host) { + // unchangeable fields via API here + newHost.DaemonInstalled = currentHost.DaemonInstalled + newHost.OS = currentHost.OS + newHost.IPForwarding = currentHost.IPForwarding + newHost.HostPass = currentHost.HostPass + newHost.NodePassword = currentHost.NodePassword + newHost.MacAddress = currentHost.MacAddress + newHost.Debug = currentHost.Debug + newHost.Nodes = currentHost.Nodes + newHost.PublicKey = currentHost.PublicKey + newHost.InternetGateway = currentHost.InternetGateway + newHost.TrafficKeyPublic = currentHost.TrafficKeyPublic + + // changeable fields + if len(newHost.Version) == 0 { + newHost.Version = currentHost.Version + } + + if len(newHost.Name) == 0 { + newHost.Name = currentHost.Name + } + + if newHost.LocalAddress.String() != currentHost.LocalAddress.String() { + newHost.LocalAddress = currentHost.LocalAddress + } + + if newHost.LocalRange.String() != currentHost.LocalRange.String() { + newHost.LocalRange = currentHost.LocalRange + } + + if newHost.MTU == 0 { + newHost.MTU = currentHost.MTU + } + + if newHost.ListenPort == 0 { + newHost.ListenPort = currentHost.ListenPort + } + + if newHost.ProxyListenPort == 0 { + newHost.ProxyListenPort = currentHost.ProxyListenPort + } +} + +// UpsertHost - upserts into DB a given host model, does not check for existence* +func UpsertHost(h *models.Host) error { + data, err := json.Marshal(h) + if err != nil { + return err + } + + return database.Insert(h.ID.String(), string(data), database.HOSTS_TABLE_NAME) +} + +// RemoveHost - removes a given host from server +func RemoveHost(h *models.Host) error { + if len(h.Nodes) > 0 { + for i := range h.Nodes { + id := h.Nodes[i] + n, err := GetNodeByID(id) + if err == nil { + if err = DeleteNodeByID(&n); err != nil { + return err // must remove associated nodes before removing a host + } + } + } + } + return database.DeleteRecord(database.HOSTS_TABLE_NAME, h.ID.String()) +} diff --git a/models/host.go b/models/host.go index 10b9ae80..2cc04a19 100644 --- a/models/host.go +++ b/models/host.go @@ -30,5 +30,5 @@ type Host struct { MacAddress net.HardwareAddr `json:"macaddress" yaml:"macaddress"` TrafficKeyPublic []byte `json:"traffickeypublic" yaml:"trafficekeypublic"` InternetGateway net.UDPAddr `json:"internetgateway" yaml:"internetgateway"` - Nodes []Node `json:"nodes" yaml:"nodes"` + Nodes []string `json:"nodes" yaml:"nodes"` } diff --git a/stun-server/stun-server.go b/stun-server/stun-server.go index 7e6b199e..5cf36ed4 100644 --- a/stun-server/stun-server.go +++ b/stun-server/stun-server.go @@ -144,20 +144,20 @@ func normalize(address string) string { return address } +// Start - starts the stun server func Start(wg *sync.WaitGroup) { - defer wg.Done() ctx, cancel := context.WithCancel(context.Background()) - go func() { + go func(wg *sync.WaitGroup) { + defer wg.Done() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGTERM, os.Interrupt) <-quit cancel() - }() + }(wg) normalized := normalize(fmt.Sprintf("0.0.0.0:%d", servercfg.GetStunPort())) logger.Log(0, "netmaker-stun listening on", normalized, "via udp") err := listenUDPAndServe(ctx, "udp", normalized) if err != nil { logger.Log(0, "failed to start stun server: ", err.Error()) } - }