2021-03-26 00:17:52 +08:00
|
|
|
//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 (
|
|
|
|
"fmt"
|
|
|
|
"errors"
|
|
|
|
"math/rand"
|
|
|
|
"time"
|
|
|
|
"context"
|
|
|
|
"encoding/base64"
|
|
|
|
"strings"
|
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"github.com/gravitl/netmaker/models"
|
|
|
|
"github.com/gravitl/netmaker/mongoconn"
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
|
|
)
|
|
|
|
|
|
|
|
//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 group
|
|
|
|
func IsFieldUnique(group string, field string, value string) bool {
|
|
|
|
|
|
|
|
var node models.Node
|
|
|
|
isunique := true
|
|
|
|
|
2021-04-02 02:50:18 +08:00
|
|
|
collection := mongoconn.Client.Database("netmaker").Collection("nodes")
|
2021-03-26 00:17:52 +08:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
|
|
|
|
filter := bson.M{field: value, "group": group}
|
|
|
|
|
|
|
|
err := collection.FindOne(ctx, filter).Decode(&node)
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return isunique
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node.Name != "") {
|
|
|
|
isunique = false
|
|
|
|
}
|
|
|
|
|
|
|
|
return isunique
|
|
|
|
}
|
|
|
|
|
|
|
|
func GroupExists(name string) (bool, error) {
|
|
|
|
|
2021-04-02 02:50:18 +08:00
|
|
|
collection := mongoconn.Client.Database("netmaker").Collection("groups")
|
2021-03-26 00:17:52 +08:00
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
|
|
|
|
filter := bson.M{"nameid": name}
|
|
|
|
|
|
|
|
var result bson.M
|
|
|
|
err := collection.FindOne(ctx, filter).Decode(&result)
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
if err == mongo.ErrNoDocuments {
|
|
|
|
return false, err
|
|
|
|
}
|
2021-03-26 10:29:36 +08:00
|
|
|
fmt.Println("ERROR RETRIEVING GROUP!")
|
|
|
|
fmt.Println(err)
|
2021-03-26 00:17:52 +08:00
|
|
|
}
|
|
|
|
return true, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//TODO: This is very inefficient (N-squared). Need to find a better way.
|
|
|
|
//Takes a list of nodes in a group and iterates through
|
|
|
|
//for each node, it gets a unique address. That requires checking against all other nodes once more
|
|
|
|
func UpdateGroupNodeAddresses(groupName string) error {
|
|
|
|
|
|
|
|
//Connection mongoDB with mongoconn class
|
2021-04-02 02:50:18 +08:00
|
|
|
collection := mongoconn.Client.Database("netmaker").Collection("nodes")
|
2021-03-26 00:17:52 +08:00
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
|
|
|
|
filter := bson.M{"group": groupName}
|
|
|
|
cur, err := collection.Find(ctx, filter)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
for cur.Next(context.TODO()) {
|
|
|
|
|
|
|
|
var node models.Node
|
|
|
|
|
|
|
|
err := cur.Decode(&node)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("error in node address assignment!")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ipaddr, iperr := UniqueAddress(groupName)
|
|
|
|
if iperr != nil {
|
|
|
|
fmt.Println("error in node address assignment!")
|
|
|
|
return iperr
|
|
|
|
}
|
|
|
|
|
|
|
|
filter := bson.M{"macaddress": node.MacAddress}
|
|
|
|
update := bson.D{{"$set", bson.D{{"address", ipaddr}}}}
|
|
|
|
|
|
|
|
errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&node)
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
if errN != nil {
|
|
|
|
return errN
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
//Checks to see if any other groups have the same name (id)
|
|
|
|
func IsGroupNameUnique(name string) bool {
|
|
|
|
|
|
|
|
isunique := true
|
|
|
|
|
|
|
|
dbs := ListGroups()
|
|
|
|
|
|
|
|
for i := 0; i < len(dbs); i++ {
|
|
|
|
|
|
|
|
if name == dbs[i].NameID {
|
|
|
|
isunique = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return isunique
|
|
|
|
}
|
|
|
|
|
|
|
|
func IsGroupDisplayNameUnique(name string) bool {
|
|
|
|
|
|
|
|
isunique := true
|
|
|
|
|
|
|
|
dbs := ListGroups()
|
|
|
|
|
|
|
|
for i := 0; i < len(dbs); i++ {
|
|
|
|
|
|
|
|
if name == dbs[i].DisplayName {
|
|
|
|
isunique = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return isunique
|
|
|
|
}
|
|
|
|
|
|
|
|
//Kind of a weird name. Should just be GetGroups I think. Consider changing.
|
|
|
|
//Anyway, returns all the groups
|
|
|
|
func ListGroups() []models.Group{
|
|
|
|
var groups []models.Group
|
|
|
|
|
2021-04-02 02:50:18 +08:00
|
|
|
collection := mongoconn.Client.Database("netmaker").Collection("groups")
|
2021-03-26 00:17:52 +08:00
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
|
|
|
|
cur, err := collection.Find(ctx, bson.M{}, options.Find().SetProjection(bson.M{"_id": 0}))
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return groups
|
|
|
|
}
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
for cur.Next(context.TODO()) {
|
|
|
|
|
|
|
|
var group models.Group
|
|
|
|
err := cur.Decode(&group)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// add group our array
|
|
|
|
groups = append(groups, group)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := cur.Err(); err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return groups
|
|
|
|
}
|
|
|
|
|
|
|
|
//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!!!!
|
|
|
|
func IsKeyValid(groupname string, keyvalue string) bool{
|
|
|
|
|
|
|
|
group, _ := GetParentGroup(groupname)
|
|
|
|
var key models.AccessKey
|
|
|
|
foundkey := false
|
|
|
|
isvalid := false
|
|
|
|
|
|
|
|
for i := len(group.AccessKeys) - 1; i >= 0; i-- {
|
|
|
|
currentkey:= group.AccessKeys[i]
|
|
|
|
if currentkey.Value == keyvalue {
|
|
|
|
key = currentkey
|
|
|
|
foundkey = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if foundkey {
|
|
|
|
if key.Uses > 0 {
|
|
|
|
isvalid = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return isvalid
|
|
|
|
}
|
|
|
|
//TODO: Contains a fatal error return. Need to change
|
|
|
|
//This just gets a group object from a group name
|
|
|
|
//Should probably just be GetGroup. kind of a dumb name.
|
|
|
|
//Used in contexts where it's not the Parent group.
|
|
|
|
func GetParentGroup(groupname string) (models.Group, error) {
|
|
|
|
|
|
|
|
var group models.Group
|
|
|
|
|
2021-04-02 02:50:18 +08:00
|
|
|
collection := mongoconn.Client.Database("netmaker").Collection("groups")
|
2021-03-26 00:17:52 +08:00
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
|
|
|
|
filter := bson.M{"nameid": groupname}
|
|
|
|
err := collection.FindOne(ctx, filter).Decode(&group)
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return group, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return group, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//Check for valid IPv4 address
|
|
|
|
//Note: We dont handle IPv6 AT ALL!!!!! This definitely is needed at some point
|
|
|
|
//But for iteration 1, lets just stick to IPv4. Keep it simple stupid.
|
|
|
|
func IsIpv4Net(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
|
|
|
|
func IsIpv4CIDR(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 is used to validate public keys (make sure they're base64 encoded like all public keys should be).
|
|
|
|
func IsBase64(s string) bool {
|
|
|
|
_, err := base64.StdEncoding.DecodeString(s)
|
|
|
|
return err == nil
|
|
|
|
}
|
|
|
|
|
|
|
|
//This should probably just be called GetNode
|
|
|
|
//It returns a node based on the ID of the node.
|
|
|
|
//Why do we need this?
|
|
|
|
//TODO: Check references. This seems unnecessary.
|
|
|
|
func GetNodeObj(id primitive.ObjectID) models.Node {
|
|
|
|
|
|
|
|
var node models.Node
|
|
|
|
|
2021-04-02 02:50:18 +08:00
|
|
|
collection := mongoconn.Client.Database("netmaker").Collection("nodes")
|
2021-03-26 00:17:52 +08:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
|
|
|
|
filter := bson.M{"_id": id}
|
|
|
|
err := collection.FindOne(ctx, filter).Decode(&node)
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
fmt.Println("Did not get the node...")
|
|
|
|
return node
|
|
|
|
}
|
|
|
|
fmt.Println("Got node " + node.Name)
|
|
|
|
return node
|
|
|
|
}
|
|
|
|
|
|
|
|
//This checks to make sure a group name is valid.
|
|
|
|
//Switch to REGEX?
|
|
|
|
func NameInGroupCharSet(name string) bool{
|
|
|
|
|
|
|
|
charset := "abcdefghijklmnopqrstuvwxyz1234567890-_"
|
|
|
|
|
|
|
|
for _, char := range name {
|
|
|
|
if !strings.Contains(charset, strings.ToLower(string(char))) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
func GetNodeByMacAddress(group string, macaddress string) (models.Node, error) {
|
|
|
|
|
|
|
|
var node models.Node
|
|
|
|
|
|
|
|
filter := bson.M{"macaddress": macaddress, "group": group}
|
|
|
|
|
2021-04-02 02:50:18 +08:00
|
|
|
collection := mongoconn.Client.Database("netmaker").Collection("nodes")
|
2021-03-26 00:17:52 +08:00
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
|
|
|
|
err := collection.FindOne(ctx, filter).Decode(&node)
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return node, err
|
|
|
|
}
|
|
|
|
return node, 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
|
|
|
|
func UniqueAddress(groupName string) (string, error){
|
|
|
|
|
|
|
|
var group models.Group
|
|
|
|
group, err := GetParentGroup(groupName)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("UniqueAddress encountered an error")
|
|
|
|
return "666", err
|
|
|
|
}
|
|
|
|
|
|
|
|
offset := true
|
|
|
|
ip, ipnet, err := net.ParseCIDR(group.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 IsIPUnique(groupName, ip.String()){
|
|
|
|
return ip.String(), err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//TODO
|
|
|
|
err1 := errors.New("ERROR: No unique addresses available. Check group subnet.")
|
|
|
|
return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", err1
|
|
|
|
}
|
|
|
|
|
2021-04-06 02:47:07 +08:00
|
|
|
//pretty simple get
|
|
|
|
func GetGlobalConfig() ( models.GlobalConfig, error) {
|
|
|
|
|
|
|
|
filter := bson.M{}
|
|
|
|
|
|
|
|
var globalconf models.GlobalConfig
|
|
|
|
|
|
|
|
collection := mongoconn.Client.Database("netmaker").Collection("config")
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
|
|
|
|
err := collection.FindOne(ctx, filter).Decode(&globalconf)
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
fmt.Println("Could not get global config")
|
|
|
|
return globalconf, err
|
|
|
|
}
|
|
|
|
return globalconf, err
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-03-26 00:17:52 +08:00
|
|
|
//generate an access key value
|
|
|
|
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"
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
//checks if IP is unique in the address range
|
|
|
|
//used by UniqueAddress
|
|
|
|
func IsIPUnique(group string, ip string) bool {
|
|
|
|
|
|
|
|
var node models.Node
|
|
|
|
|
|
|
|
isunique := true
|
|
|
|
|
2021-04-02 02:50:18 +08:00
|
|
|
collection := mongoconn.Client.Database("netmaker").Collection("nodes")
|
2021-03-26 00:17:52 +08:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
|
|
|
|
filter := bson.M{"address": ip, "group": group}
|
|
|
|
|
|
|
|
err := collection.FindOne(ctx, filter).Decode(&node)
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return isunique
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node.Address == ip) {
|
|
|
|
isunique = false
|
|
|
|
}
|
|
|
|
return isunique
|
|
|
|
}
|
|
|
|
|
|
|
|
//called once key has been used by createNode
|
|
|
|
//reduces value by one and deletes if necessary
|
|
|
|
func DecrimentKey(groupName string, keyvalue string) {
|
|
|
|
|
|
|
|
var group models.Group
|
|
|
|
|
|
|
|
group, err := GetParentGroup(groupName)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := len(group.AccessKeys) - 1; i >= 0; i-- {
|
|
|
|
|
|
|
|
currentkey := group.AccessKeys[i]
|
|
|
|
if currentkey.Value == keyvalue {
|
|
|
|
group.AccessKeys[i].Uses--
|
|
|
|
if group.AccessKeys[i].Uses < 1 {
|
|
|
|
//this is the part where it will call the delete
|
|
|
|
//not sure if there's edge cases I'm missing
|
|
|
|
DeleteKey(group, i)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-02 02:50:18 +08:00
|
|
|
collection := mongoconn.Client.Database("netmaker").Collection("groups")
|
2021-03-26 00:17:52 +08:00
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
|
|
|
|
filter := bson.M{"nameid": group.NameID}
|
|
|
|
|
|
|
|
update := bson.D{
|
|
|
|
{"$set", bson.D{
|
|
|
|
{"accesskeys", group.AccessKeys},
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&group)
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
if errN != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//takes the logic from controllers.deleteKey
|
|
|
|
func DeleteKey(group models.Group, i int) {
|
|
|
|
|
|
|
|
group.AccessKeys = append(group.AccessKeys[:i],
|
|
|
|
group.AccessKeys[i+1:]...)
|
|
|
|
|
2021-04-02 02:50:18 +08:00
|
|
|
collection := mongoconn.Client.Database("netmaker").Collection("groups")
|
2021-03-26 00:17:52 +08:00
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
|
|
|
|
|
|
// Create filter
|
|
|
|
filter := bson.M{"nameid": group.NameID}
|
|
|
|
|
|
|
|
// prepare update model.
|
|
|
|
update := bson.D{
|
|
|
|
{"$set", bson.D{
|
|
|
|
{"accesskeys", group.AccessKeys},
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
|
|
|
errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&group)
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
if errN != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//increments an IP over the previous
|
|
|
|
func Inc(ip net.IP) {
|
|
|
|
for j := len(ip)-1; j>=0; j-- {
|
|
|
|
ip[j]++
|
|
|
|
if ip[j] > 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|