//TODO: Consider restructuring this file/folder "github.com/gorilla/handlers" //It may make more sense to split into different files and not call it "helpers" package functions import ( "encoding/base64" "encoding/json" "errors" "fmt" "log" "math/rand" "net" "strings" "time" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/servercfg" ) // PrintUserLog - prints a log with a given username func PrintUserLog(username string, message string, loglevel int) { log.SetFlags(log.Flags() &^ (log.Llongfile | log.Lshortfile)) if int32(loglevel) <= servercfg.GetVerbose() && servercfg.GetVerbose() != 0 { log.Println("[netmaker]", username, message) } } // ParseNetwork - parses a network into a model func ParseNetwork(value string) (models.Network, error) { var network models.Network err := json.Unmarshal([]byte(value), &network) return network, err } // ParseNode - parses a node into a model func ParseNode(value string) (models.Node, error) { var node models.Node err := json.Unmarshal([]byte(value), &node) return node, err } // ParseExtClient - parses an extclient into a model func ParseExtClient(value string) (models.ExtClient, error) { var extClient models.ExtClient err := json.Unmarshal([]byte(value), &extClient) return extClient, err } // ParseIntClient - parses int client func ParseIntClient(value string) (models.IntClient, error) { var intClient models.IntClient err := json.Unmarshal([]byte(value), &intClient) return intClient, err } //Takes in an arbitrary field and value for field and checks to see if any other //node has that value for the same field within the network // GetUser - gets a user func GetUser(username string) (models.User, error) { var user models.User record, err := database.FetchRecord(database.USERS_TABLE_NAME, username) if err != nil { return user, err } if err = json.Unmarshal([]byte(record), &user); err != nil { return models.User{}, err } return user, err } // SliceContains - sees if a slice contains something func SliceContains(slice []string, item string) bool { set := make(map[string]struct{}, len(slice)) for _, s := range slice { set[s] = struct{}{} } _, ok := set[item] return ok } // CreateServerToken - creates a server token func CreateServerToken(netID string) (string, error) { var network models.Network var accesskey models.AccessKey network, err := GetParentNetwork(netID) if err != nil { return "", err } var accessToken models.AccessToken servervals := models.ServerConfig{} if servercfg.GetPlatform() == "Kubernetes" { log.Println("server on kubernetes") servervals = models.ServerConfig{ APIConnString: servercfg.GetPodIP() + ":" + servercfg.GetAPIPort(), GRPCConnString: servercfg.GetPodIP() + ":" + servercfg.GetGRPCPort(), GRPCSSL: "off", } } else { log.Println("server on linux") servervals = models.ServerConfig{ APIConnString: "127.0.0.1:" + servercfg.GetAPIPort(), GRPCConnString: "127.0.0.1:" + servercfg.GetGRPCPort(), GRPCSSL: "off", CheckinInterval: servercfg.GetCheckinInterval(), } } log.Println("APIConnString:", servervals.APIConnString) log.Println("GRPCConnString:", servervals.GRPCConnString) log.Println("GRPCSSL:", servervals.GRPCSSL) accessToken.ServerConfig = servervals accessToken.ClientConfig.Network = netID accessToken.ClientConfig.Key = GenKey() accesskey.Name = GenKeyName() accesskey.Value = accessToken.ClientConfig.Key accesskey.Uses = 1 tokenjson, err := json.Marshal(accessToken) if err != nil { return accesskey.AccessString, err } accesskey.AccessString = base64.StdEncoding.EncodeToString([]byte(tokenjson)) log.Println("accessstring:", accesskey.AccessString) network.AccessKeys = append(network.AccessKeys, accesskey) if data, err := json.Marshal(network); err != nil { return "", err } else { database.Insert(netID, string(data), database.NETWORKS_TABLE_NAME) } return accesskey.AccessString, nil } // GetPeersList - gets peers for given network func GetPeersList(networkName string) ([]models.PeersResponse, error) { var peers []models.PeersResponse collection, err := database.FetchRecords(database.NODES_TABLE_NAME) if err != nil { return peers, err } for _, value := range collection { var peer models.PeersResponse err := json.Unmarshal([]byte(value), &peer) if err != nil { continue // try the rest } peers = append(peers, peer) } return peers, err } // GetIntPeersList - get int peers list func GetIntPeersList() ([]models.PeersResponse, error) { var peers []models.PeersResponse records, err := database.FetchRecords(database.INT_CLIENTS_TABLE_NAME) if err != nil { return peers, err } // parse the peers for _, value := range records { var peer models.PeersResponse err := json.Unmarshal([]byte(value), &peer) if err != nil { log.Fatal(err) } // add the node to our node array //maybe better to just return this? But then that's just GetNodes... peers = append(peers, peer) } return peers, err } // GetServerIntClient - get server int client func GetServerIntClient() (*models.IntClient, error) { intClients, err := database.FetchRecords(database.INT_CLIENTS_TABLE_NAME) for _, value := range intClients { var intClient models.IntClient err = json.Unmarshal([]byte(value), &intClient) if err != nil { return nil, err } if intClient.IsServer == "yes" && intClient.Network == "comms" { return &intClient, nil } } return nil, err } // NetworkExists - check if network exists func NetworkExists(name string) (bool, error) { var network string var err error if network, err = database.FetchRecord(database.NETWORKS_TABLE_NAME, name); err != nil { return false, err } return len(network) > 0, nil } // GetRecordKey - get record key func GetRecordKey(id string, network string) (string, error) { if id == "" || network == "" { return "", errors.New("unable to get record key") } return id + "###" + network, nil } // UpdateNetworkNodeAddresses - updates network node addresses func UpdateNetworkNodeAddresses(networkName string) error { collections, err := database.FetchRecords(database.NODES_TABLE_NAME) if err != nil { return err } for _, value := range collections { var node models.Node err := json.Unmarshal([]byte(value), &node) if err != nil { fmt.Println("error in node address assignment!") return err } if node.Network == networkName { ipaddr, iperr := UniqueAddress(networkName) if iperr != nil { fmt.Println("error in node address assignment!") return iperr } node.Address = ipaddr node.PullChanges = "yes" data, err := json.Marshal(&node) if err != nil { return err } node.SetID() database.Insert(node.ID, string(data), database.NODES_TABLE_NAME) } } return nil } // NetworkNodesUpdateAction - updates action of network nodes func NetworkNodesUpdateAction(networkName string, action string) error { collections, err := database.FetchRecords(database.NODES_TABLE_NAME) if err != nil { if database.IsEmptyRecord(err) { return nil } return err } for _, value := range collections { var node models.Node err := json.Unmarshal([]byte(value), &node) if err != nil { fmt.Println("error in node address assignment!") return err } if action == models.NODE_UPDATE_KEY && node.IsStatic == "yes" { continue } if node.Network == networkName { node.Action = action data, err := json.Marshal(&node) if err != nil { return err } node.SetID() database.Insert(node.ID, string(data), database.NODES_TABLE_NAME) } } return nil } // NetworkNodesUpdatePullChanges - tells nodes on network to pull func NetworkNodesUpdatePullChanges(networkName string) error { collections, err := database.FetchRecords(database.NODES_TABLE_NAME) if err != nil { if database.IsEmptyRecord(err) { return nil } return err } for _, value := range collections { var node models.Node err := json.Unmarshal([]byte(value), &node) if err != nil { fmt.Println("error in node address assignment!") return err } if node.Network == networkName { node.PullChanges = "yes" data, err := json.Marshal(&node) if err != nil { return err } node.SetID() database.Insert(node.ID, string(data), database.NODES_TABLE_NAME) } } return nil } // UpdateNetworkLocalAddresses - updates network localaddresses func UpdateNetworkLocalAddresses(networkName string) error { collection, err := database.FetchRecords(database.NODES_TABLE_NAME) if err != nil { return err } for _, value := range collection { var node models.Node err := json.Unmarshal([]byte(value), &node) if err != nil { fmt.Println("error in node address assignment!") return err } if node.Network == networkName { ipaddr, iperr := UniqueAddress(networkName) if iperr != nil { fmt.Println("error in node address assignment!") return iperr } node.Address = ipaddr newNodeData, err := json.Marshal(&node) if err != nil { fmt.Println("error in node address assignment!") return err } node.SetID() database.Insert(node.ID, string(newNodeData), database.NODES_TABLE_NAME) } } return nil } // IsNetworkDisplayNameUnique - checks if network display name unique func IsNetworkDisplayNameUnique(name string) (bool, error) { isunique := true dbs, err := models.GetNetworks() if err != nil { return database.IsEmptyRecord(err), err } for i := 0; i < len(dbs); i++ { if name == dbs[i].DisplayName { isunique = false } } return isunique, nil } // IsMacAddressUnique - checks if mac is unique func IsMacAddressUnique(macaddress string, networkName string) (bool, error) { _, err := database.FetchRecord(database.NODES_TABLE_NAME, macaddress+"###"+networkName) if err != nil { return database.IsEmptyRecord(err), err } return true, nil } // GetNetworkNonServerNodeCount - get number of network non server nodes func GetNetworkNonServerNodeCount(networkName string) (int, error) { collection, err := database.FetchRecords(database.NODES_TABLE_NAME) count := 0 if err != nil && !database.IsEmptyRecord(err) { return count, err } for _, value := range collection { var node models.Node if err = json.Unmarshal([]byte(value), &node); err != nil { return count, err } else { if node.Network == networkName && node.IsServer != "yes" { count++ } } } return count, nil } //Checks to see if access key is valid //Does so by checking against all keys and seeing if any have the same value //may want to hash values before comparing...consider this //TODO: No error handling!!!! // IsKeyValid - check if key is valid func IsKeyValid(networkname string, keyvalue string) bool { network, _ := GetParentNetwork(networkname) var key models.AccessKey foundkey := false isvalid := false for i := len(network.AccessKeys) - 1; i >= 0; i-- { currentkey := network.AccessKeys[i] if currentkey.Value == keyvalue { key = currentkey foundkey = true } } if foundkey { if key.Uses > 0 { isvalid = true } } return isvalid } // IsKeyValidGlobal - checks if a key is valid globally func IsKeyValidGlobal(keyvalue string) bool { networks, _ := models.GetNetworks() var key models.AccessKey foundkey := false isvalid := false for _, network := range networks { for i := len(network.AccessKeys) - 1; i >= 0; i-- { currentkey := network.AccessKeys[i] if currentkey.Value == keyvalue { key = currentkey foundkey = true break } } if foundkey { break } } if foundkey { if key.Uses > 0 { isvalid = true } } return isvalid } //TODO: Contains a fatal error return. Need to change //This just gets a network object from a network name //Should probably just be GetNetwork. kind of a dumb name. //Used in contexts where it's not the Parent network. // GetParentNetwork - get parent network func GetParentNetwork(networkname string) (models.Network, error) { var network models.Network networkData, err := database.FetchRecord(database.NETWORKS_TABLE_NAME, networkname) if err != nil { return network, err } if err = json.Unmarshal([]byte(networkData), &network); err != nil { return models.Network{}, err } return network, nil } // IsIpNet - checks if valid ip func IsIpNet(host string) bool { return net.ParseIP(host) != nil } //Similar to above but checks if Cidr range is valid //At least this guy's got some print statements //still not good error handling // IsIpCIDR - IsIpCIDR func IsIpCIDR(host string) bool { ip, ipnet, err := net.ParseCIDR(host) if err != nil { fmt.Println(err) fmt.Println("Address Range is not valid!") return false } return ip != nil && ipnet != nil } //This checks to make sure a network name is valid. //Switch to REGEX? // NameInNetworkCharSet - see if name is in charset for networks func NameInNetworkCharSet(name string) bool { charset := "abcdefghijklmnopqrstuvwxyz1234567890-_." for _, char := range name { if !strings.Contains(charset, strings.ToLower(string(char))) { return false } } return true } // NameInDNSCharSet - name in dns char set func NameInDNSCharSet(name string) bool { charset := "abcdefghijklmnopqrstuvwxyz1234567890-." for _, char := range name { if !strings.Contains(charset, strings.ToLower(string(char))) { return false } } return true } // NameInNodeCharSet - name in node char set func NameInNodeCharSet(name string) bool { charset := "abcdefghijklmnopqrstuvwxyz1234567890-" for _, char := range name { if !strings.Contains(charset, strings.ToLower(string(char))) { return false } } return true } //This returns a node based on its mac address. //The mac address acts as the Unique ID for nodes. //Is this a dumb thing to do? I thought it was cool but maybe it's dumb. //It doesn't really provide a tangible benefit over a random ID // GetNodeByMacAddress - gets a node by mac address func GetNodeByMacAddress(network string, macaddress string) (models.Node, error) { var node models.Node key, err := GetRecordKey(macaddress, network) if err != nil { return node, err } record, err := database.FetchRecord(database.NODES_TABLE_NAME, key) if err != nil { return models.Node{}, err } if err = json.Unmarshal([]byte(record), &node); err != nil { return models.Node{}, err } node.SetDefaults() return node, nil } // GetDeletedNodeByMacAddress - get a deleted node func GetDeletedNodeByMacAddress(network string, macaddress string) (models.Node, error) { var node models.Node key, err := GetRecordKey(macaddress, network) if err != nil { return node, err } record, err := database.FetchRecord(database.DELETED_NODES_TABLE_NAME, key) if err != nil { return models.Node{}, err } if err = json.Unmarshal([]byte(record), &node); err != nil { return models.Node{}, err } node.SetDefaults() return node, nil } // RemoveDeletedNode - remove deleted node func RemoveDeletedNode(nodeid string) bool { return database.DeleteRecord(database.DELETED_NODES_TABLE_NAME, nodeid) == nil } // DeleteAllIntClients - delete all int clients func DeleteAllIntClients() error { err := database.DeleteAllRecords(database.INT_CLIENTS_TABLE_NAME) if err != nil { return err } return nil } // GetAllIntClients - get all int clients func GetAllIntClients() ([]models.IntClient, error) { var clients []models.IntClient collection, err := database.FetchRecords(database.INT_CLIENTS_TABLE_NAME) if err != nil { return clients, err } for _, value := range collection { var client models.IntClient err := json.Unmarshal([]byte(value), &client) if err != nil { return []models.IntClient{}, err } // add node to our array clients = append(clients, client) } return clients, nil } // GetAllExtClients - get all ext clients func GetAllExtClients() ([]models.ExtClient, error) { var extclients []models.ExtClient collection, err := database.FetchRecords(database.EXT_CLIENT_TABLE_NAME) if err != nil { return extclients, err } for _, value := range collection { var extclient models.ExtClient err := json.Unmarshal([]byte(value), &extclient) if err != nil { return []models.ExtClient{}, err } // add node to our array extclients = append(extclients, extclient) } return extclients, nil } //This returns a unique address for a node to use //it iterates through the list of IP's in the subnet //and checks against all nodes to see if it's taken, until it finds one. //TODO: We do not handle a case where we run out of addresses. //We will need to handle that eventually // UniqueAddress - see if address is unique func UniqueAddress(networkName string) (string, error) { var network models.Network network, err := GetParentNetwork(networkName) if err != nil { fmt.Println("UniqueAddress encountered an error") return "666", err } offset := true ip, ipnet, err := net.ParseCIDR(network.AddressRange) if err != nil { fmt.Println("UniqueAddress encountered an error") return "666", err } for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); Inc(ip) { if offset { offset = false continue } if networkName == "comms" { if IsIPUnique(networkName, ip.String(), database.INT_CLIENTS_TABLE_NAME, false) { return ip.String(), err } } else { if IsIPUnique(networkName, ip.String(), database.NODES_TABLE_NAME, false) && IsIPUnique(networkName, ip.String(), database.EXT_CLIENT_TABLE_NAME, false) { return ip.String(), err } } } //TODO err1 := errors.New("ERROR: No unique addresses available. Check network subnet.") return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", err1 } // UniqueAddress6 - see if ipv6 address is unique func UniqueAddress6(networkName string) (string, error) { var network models.Network network, err := GetParentNetwork(networkName) if err != nil { fmt.Println("Network Not Found") return "", err } if network.IsDualStack == "no" { return "", nil } offset := true ip, ipnet, err := net.ParseCIDR(network.AddressRange6) if err != nil { fmt.Println("UniqueAddress6 encountered an error") return "666", err } for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); Inc(ip) { if offset { offset = false continue } if IsIPUnique(networkName, ip.String(), database.NODES_TABLE_NAME, true) { return ip.String(), err } } //TODO err1 := errors.New("ERROR: No unique addresses available. Check network subnet.") return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", err1 } // GenKey - generates access key func GenKey() string { var seededRand *rand.Rand = rand.New( rand.NewSource(time.Now().UnixNano())) length := 16 charset := "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, length) for i := range b { b[i] = charset[seededRand.Intn(len(charset))] } return string(b) } //generate a key value //we should probably just have 1 random string generator //that can be used across all functions //have a "base string" a "length" and a "charset" // GenKeyName - generates a key name func GenKeyName() string { var seededRand *rand.Rand = rand.New( rand.NewSource(time.Now().UnixNano())) length := 5 charset := "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, length) for i := range b { b[i] = charset[seededRand.Intn(len(charset))] } return "key" + string(b) } // IsIPUnique - checks if an IP is unique func IsIPUnique(network string, ip string, tableName string, isIpv6 bool) bool { isunique := true collection, err := database.FetchRecords(tableName) if err != nil { return isunique } for _, value := range collection { // filter var node models.Node if err = json.Unmarshal([]byte(value), &node); err != nil { continue } if isIpv6 { if node.Address6 == ip && node.Network == network { return false } } else { if node.Address == ip && node.Network == network { return false } } } return isunique } //called once key has been used by createNode //reduces value by one and deletes if necessary // DecrimentKey - decriments key uses func DecrimentKey(networkName string, keyvalue string) { var network models.Network network, err := GetParentNetwork(networkName) if err != nil { return } for i := len(network.AccessKeys) - 1; i >= 0; i-- { currentkey := network.AccessKeys[i] if currentkey.Value == keyvalue { network.AccessKeys[i].Uses-- if network.AccessKeys[i].Uses < 1 { network.AccessKeys = append(network.AccessKeys[:i], network.AccessKeys[i+1:]...) break } } } if newNetworkData, err := json.Marshal(&network); err != nil { PrintUserLog(models.NODE_SERVER_NAME, "failed to decrement key", 2) return } else { database.Insert(network.NetID, string(newNetworkData), database.NETWORKS_TABLE_NAME) } } // DeleteKey - deletes a key func DeleteKey(network models.Network, i int) { network.AccessKeys = append(network.AccessKeys[:i], network.AccessKeys[i+1:]...) if networkData, err := json.Marshal(&network); err != nil { return } else { database.Insert(network.NetID, string(networkData), database.NETWORKS_TABLE_NAME) } } // Inc - increments an IP func Inc(ip net.IP) { for j := len(ip) - 1; j >= 0; j-- { ip[j]++ if ip[j] > 0 { break } } }