helpers.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. //TODO: Consider restructuring this file/folder "github.com/gorilla/handlers"
  2. //It may make more sense to split into different files and not call it "helpers"
  3. package functions
  4. import (
  5. "fmt"
  6. "errors"
  7. "math/rand"
  8. "time"
  9. "context"
  10. "encoding/base64"
  11. "strings"
  12. "log"
  13. "net"
  14. "github.com/gravitl/netmaker/models"
  15. "github.com/gravitl/netmaker/mongoconn"
  16. "go.mongodb.org/mongo-driver/bson"
  17. "go.mongodb.org/mongo-driver/bson/primitive"
  18. "go.mongodb.org/mongo-driver/mongo/options"
  19. "go.mongodb.org/mongo-driver/mongo"
  20. )
  21. //Takes in an arbitrary field and value for field and checks to see if any other
  22. //node has that value for the same field within the group
  23. func CreateServerToken(network string) (string, error) {
  24. var group models.Group
  25. var accesskey models.AccessKey
  26. group, err := GetParentGroup(network)
  27. if err != nil {
  28. return "", err
  29. }
  30. accesskey.Name = GenKeyName()
  31. accesskey.Value = GenKey()
  32. accesskey.Uses = 1
  33. gconf, errG := GetGlobalConfig()
  34. if errG != nil {
  35. return "", errG
  36. }
  37. address := "localhost" + gconf.PortGRPC
  38. accessstringdec := address + "." + network + "." + accesskey.Value
  39. accesskey.AccessString = base64.StdEncoding.EncodeToString([]byte(accessstringdec))
  40. group.AccessKeys = append(group.AccessKeys, accesskey)
  41. collection := mongoconn.Client.Database("netmaker").Collection("groups")
  42. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  43. // Create filter
  44. filter := bson.M{"nameid": network}
  45. // Read update model from body request
  46. fmt.Println("Adding key to " + group.NameID)
  47. // prepare update model.
  48. update := bson.D{
  49. {"$set", bson.D{
  50. {"accesskeys", group.AccessKeys},
  51. }},
  52. }
  53. errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&group)
  54. defer cancel()
  55. if errN != nil {
  56. return "", errN
  57. }
  58. return accesskey.AccessString, nil
  59. }
  60. func IsFieldUnique(group string, field string, value string) bool {
  61. var node models.Node
  62. isunique := true
  63. collection := mongoconn.Client.Database("netmaker").Collection("nodes")
  64. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  65. filter := bson.M{field: value, "group": group}
  66. err := collection.FindOne(ctx, filter).Decode(&node)
  67. defer cancel()
  68. if err != nil {
  69. return isunique
  70. }
  71. if (node.Name != "") {
  72. isunique = false
  73. }
  74. return isunique
  75. }
  76. func GroupExists(name string) (bool, error) {
  77. collection := mongoconn.Client.Database("netmaker").Collection("groups")
  78. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  79. filter := bson.M{"nameid": name}
  80. var result bson.M
  81. err := collection.FindOne(ctx, filter).Decode(&result)
  82. defer cancel()
  83. if err != nil {
  84. if err == mongo.ErrNoDocuments {
  85. return false, nil
  86. }
  87. fmt.Println("ERROR RETRIEVING GROUP!")
  88. fmt.Println(err)
  89. }
  90. return true, err
  91. }
  92. //TODO: This is very inefficient (N-squared). Need to find a better way.
  93. //Takes a list of nodes in a group and iterates through
  94. //for each node, it gets a unique address. That requires checking against all other nodes once more
  95. func UpdateGroupNodeAddresses(groupName string) error {
  96. //Connection mongoDB with mongoconn class
  97. collection := mongoconn.Client.Database("netmaker").Collection("nodes")
  98. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  99. filter := bson.M{"group": groupName}
  100. cur, err := collection.Find(ctx, filter)
  101. if err != nil {
  102. return err
  103. }
  104. defer cancel()
  105. for cur.Next(context.TODO()) {
  106. var node models.Node
  107. err := cur.Decode(&node)
  108. if err != nil {
  109. fmt.Println("error in node address assignment!")
  110. return err
  111. }
  112. ipaddr, iperr := UniqueAddress(groupName)
  113. if iperr != nil {
  114. fmt.Println("error in node address assignment!")
  115. return iperr
  116. }
  117. filter := bson.M{"macaddress": node.MacAddress}
  118. update := bson.D{{"$set", bson.D{{"address", ipaddr}}}}
  119. errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&node)
  120. defer cancel()
  121. if errN != nil {
  122. return errN
  123. }
  124. }
  125. return err
  126. }
  127. //Checks to see if any other groups have the same name (id)
  128. func IsGroupNameUnique(name string) bool {
  129. isunique := true
  130. dbs := ListGroups()
  131. for i := 0; i < len(dbs); i++ {
  132. if name == dbs[i].NameID {
  133. isunique = false
  134. }
  135. }
  136. return isunique
  137. }
  138. func IsGroupDisplayNameUnique(name string) bool {
  139. isunique := true
  140. dbs := ListGroups()
  141. for i := 0; i < len(dbs); i++ {
  142. if name == dbs[i].DisplayName {
  143. isunique = false
  144. }
  145. }
  146. return isunique
  147. }
  148. //Kind of a weird name. Should just be GetGroups I think. Consider changing.
  149. //Anyway, returns all the groups
  150. func ListGroups() []models.Group{
  151. var groups []models.Group
  152. collection := mongoconn.Client.Database("netmaker").Collection("groups")
  153. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  154. cur, err := collection.Find(ctx, bson.M{}, options.Find().SetProjection(bson.M{"_id": 0}))
  155. if err != nil {
  156. return groups
  157. }
  158. defer cancel()
  159. for cur.Next(context.TODO()) {
  160. var group models.Group
  161. err := cur.Decode(&group)
  162. if err != nil {
  163. log.Fatal(err)
  164. }
  165. // add group our array
  166. groups = append(groups, group)
  167. }
  168. if err := cur.Err(); err != nil {
  169. log.Fatal(err)
  170. }
  171. return groups
  172. }
  173. //Checks to see if access key is valid
  174. //Does so by checking against all keys and seeing if any have the same value
  175. //may want to hash values before comparing...consider this
  176. //TODO: No error handling!!!!
  177. func IsKeyValid(groupname string, keyvalue string) bool{
  178. group, _ := GetParentGroup(groupname)
  179. var key models.AccessKey
  180. foundkey := false
  181. isvalid := false
  182. for i := len(group.AccessKeys) - 1; i >= 0; i-- {
  183. currentkey:= group.AccessKeys[i]
  184. if currentkey.Value == keyvalue {
  185. key = currentkey
  186. foundkey = true
  187. }
  188. }
  189. if foundkey {
  190. if key.Uses > 0 {
  191. isvalid = true
  192. }
  193. }
  194. return isvalid
  195. }
  196. //TODO: Contains a fatal error return. Need to change
  197. //This just gets a group object from a group name
  198. //Should probably just be GetGroup. kind of a dumb name.
  199. //Used in contexts where it's not the Parent group.
  200. func GetParentGroup(groupname string) (models.Group, error) {
  201. var group models.Group
  202. collection := mongoconn.Client.Database("netmaker").Collection("groups")
  203. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  204. filter := bson.M{"nameid": groupname}
  205. err := collection.FindOne(ctx, filter).Decode(&group)
  206. defer cancel()
  207. if err != nil {
  208. return group, err
  209. }
  210. return group, nil
  211. }
  212. //Check for valid IPv4 address
  213. //Note: We dont handle IPv6 AT ALL!!!!! This definitely is needed at some point
  214. //But for iteration 1, lets just stick to IPv4. Keep it simple stupid.
  215. func IsIpv4Net(host string) bool {
  216. return net.ParseIP(host) != nil
  217. }
  218. //Similar to above but checks if Cidr range is valid
  219. //At least this guy's got some print statements
  220. //still not good error handling
  221. func IsIpv4CIDR(host string) bool {
  222. ip, ipnet, err := net.ParseCIDR(host)
  223. if err != nil {
  224. fmt.Println(err)
  225. fmt.Println("Address Range is not valid!")
  226. return false
  227. }
  228. return ip != nil && ipnet != nil
  229. }
  230. //This is used to validate public keys (make sure they're base64 encoded like all public keys should be).
  231. func IsBase64(s string) bool {
  232. _, err := base64.StdEncoding.DecodeString(s)
  233. return err == nil
  234. }
  235. //This should probably just be called GetNode
  236. //It returns a node based on the ID of the node.
  237. //Why do we need this?
  238. //TODO: Check references. This seems unnecessary.
  239. func GetNodeObj(id primitive.ObjectID) models.Node {
  240. var node models.Node
  241. collection := mongoconn.Client.Database("netmaker").Collection("nodes")
  242. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  243. filter := bson.M{"_id": id}
  244. err := collection.FindOne(ctx, filter).Decode(&node)
  245. defer cancel()
  246. if err != nil {
  247. fmt.Println(err)
  248. fmt.Println("Did not get the node...")
  249. return node
  250. }
  251. fmt.Println("Got node " + node.Name)
  252. return node
  253. }
  254. //This checks to make sure a group name is valid.
  255. //Switch to REGEX?
  256. func NameInGroupCharSet(name string) bool{
  257. charset := "abcdefghijklmnopqrstuvwxyz1234567890-_"
  258. for _, char := range name {
  259. if !strings.Contains(charset, strings.ToLower(string(char))) {
  260. return false
  261. }
  262. }
  263. return true
  264. }
  265. func NameInNodeCharSet(name string) bool{
  266. charset := "abcdefghijklmnopqrstuvwxyz1234567890-"
  267. for _, char := range name {
  268. if !strings.Contains(charset, strings.ToLower(string(char))) {
  269. return false
  270. }
  271. }
  272. return true
  273. }
  274. //This returns a node based on its mac address.
  275. //The mac address acts as the Unique ID for nodes.
  276. //Is this a dumb thing to do? I thought it was cool but maybe it's dumb.
  277. //It doesn't really provide a tangible benefit over a random ID
  278. func GetNodeByMacAddress(group string, macaddress string) (models.Node, error) {
  279. var node models.Node
  280. filter := bson.M{"macaddress": macaddress, "group": group}
  281. collection := mongoconn.Client.Database("netmaker").Collection("nodes")
  282. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  283. err := collection.FindOne(ctx, filter).Decode(&node)
  284. defer cancel()
  285. if err != nil {
  286. return node, err
  287. }
  288. return node, nil
  289. }
  290. //This returns a unique address for a node to use
  291. //it iterates through the list of IP's in the subnet
  292. //and checks against all nodes to see if it's taken, until it finds one.
  293. //TODO: We do not handle a case where we run out of addresses.
  294. //We will need to handle that eventually
  295. func UniqueAddress(groupName string) (string, error){
  296. var group models.Group
  297. group, err := GetParentGroup(groupName)
  298. if err != nil {
  299. fmt.Println("UniqueAddress encountered an error")
  300. return "666", err
  301. }
  302. offset := true
  303. ip, ipnet, err := net.ParseCIDR(group.AddressRange)
  304. if err != nil {
  305. fmt.Println("UniqueAddress encountered an error")
  306. return "666", err
  307. }
  308. for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); Inc(ip) {
  309. if offset {
  310. offset = false
  311. continue
  312. }
  313. if IsIPUnique(groupName, ip.String()){
  314. return ip.String(), err
  315. }
  316. }
  317. //TODO
  318. err1 := errors.New("ERROR: No unique addresses available. Check group subnet.")
  319. return "W1R3: NO UNIQUE ADDRESSES AVAILABLE", err1
  320. }
  321. //pretty simple get
  322. func GetGlobalConfig() ( models.GlobalConfig, error) {
  323. filter := bson.M{}
  324. var globalconf models.GlobalConfig
  325. collection := mongoconn.Client.Database("netmaker").Collection("config")
  326. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  327. err := collection.FindOne(ctx, filter).Decode(&globalconf)
  328. defer cancel()
  329. if err != nil {
  330. fmt.Println(err)
  331. fmt.Println("Could not get global config")
  332. return globalconf, err
  333. }
  334. return globalconf, err
  335. }
  336. //generate an access key value
  337. func GenKey() string {
  338. var seededRand *rand.Rand = rand.New(
  339. rand.NewSource(time.Now().UnixNano()))
  340. length := 16
  341. charset := "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  342. b := make([]byte, length)
  343. for i := range b {
  344. b[i] = charset[seededRand.Intn(len(charset))]
  345. }
  346. return string(b)
  347. }
  348. //generate a key value
  349. //we should probably just have 1 random string generator
  350. //that can be used across all functions
  351. //have a "base string" a "length" and a "charset"
  352. func GenKeyName() string {
  353. var seededRand *rand.Rand = rand.New(
  354. rand.NewSource(time.Now().UnixNano()))
  355. length := 5
  356. charset := "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  357. b := make([]byte, length)
  358. for i := range b {
  359. b[i] = charset[seededRand.Intn(len(charset))]
  360. }
  361. return "key-" + string(b)
  362. }
  363. //checks if IP is unique in the address range
  364. //used by UniqueAddress
  365. func IsIPUnique(group string, ip string) bool {
  366. var node models.Node
  367. isunique := true
  368. collection := mongoconn.Client.Database("netmaker").Collection("nodes")
  369. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  370. filter := bson.M{"address": ip, "group": group}
  371. err := collection.FindOne(ctx, filter).Decode(&node)
  372. defer cancel()
  373. if err != nil {
  374. fmt.Println(err)
  375. return isunique
  376. }
  377. if (node.Address == ip) {
  378. isunique = false
  379. }
  380. return isunique
  381. }
  382. //called once key has been used by createNode
  383. //reduces value by one and deletes if necessary
  384. func DecrimentKey(groupName string, keyvalue string) {
  385. var group models.Group
  386. group, err := GetParentGroup(groupName)
  387. if err != nil {
  388. return
  389. }
  390. for i := len(group.AccessKeys) - 1; i >= 0; i-- {
  391. currentkey := group.AccessKeys[i]
  392. if currentkey.Value == keyvalue {
  393. group.AccessKeys[i].Uses--
  394. if group.AccessKeys[i].Uses < 1 {
  395. //this is the part where it will call the delete
  396. //not sure if there's edge cases I'm missing
  397. DeleteKey(group, i)
  398. return
  399. }
  400. }
  401. }
  402. collection := mongoconn.Client.Database("netmaker").Collection("groups")
  403. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  404. filter := bson.M{"nameid": group.NameID}
  405. update := bson.D{
  406. {"$set", bson.D{
  407. {"accesskeys", group.AccessKeys},
  408. }},
  409. }
  410. errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&group)
  411. defer cancel()
  412. if errN != nil {
  413. return
  414. }
  415. }
  416. //takes the logic from controllers.deleteKey
  417. func DeleteKey(group models.Group, i int) {
  418. group.AccessKeys = append(group.AccessKeys[:i],
  419. group.AccessKeys[i+1:]...)
  420. collection := mongoconn.Client.Database("netmaker").Collection("groups")
  421. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  422. // Create filter
  423. filter := bson.M{"nameid": group.NameID}
  424. // prepare update model.
  425. update := bson.D{
  426. {"$set", bson.D{
  427. {"accesskeys", group.AccessKeys},
  428. }},
  429. }
  430. errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&group)
  431. defer cancel()
  432. if errN != nil {
  433. return
  434. }
  435. }
  436. //increments an IP over the previous
  437. func Inc(ip net.IP) {
  438. for j := len(ip)-1; j>=0; j-- {
  439. ip[j]++
  440. if ip[j] > 0 {
  441. break
  442. }
  443. }
  444. }