Browse Source

Merge pull request #137 from gravitl/feature_v0.3_coredns

Feature v0.3 coredns
Alex 4 years ago
parent
commit
a12c92a2e4

+ 67 - 8
controllers/common.go

@@ -58,14 +58,13 @@ func GetPeersList(networkName string) ([]models.PeersResponse, error) {
 	return peers, err
 }
 
-func ValidateNode(operation string, networkName string, node models.Node) error {
+func ValidateNodeCreate(networkName string, node models.Node) error {
 
 	v := validator.New()
-
 	_ = v.RegisterValidation("address_check", func(fl validator.FieldLevel) bool {
 		isIpv4 := functions.IsIpv4Net(node.Address)
-		notEmptyCheck := node.Address != ""
-		return (notEmptyCheck && isIpv4)
+		empty := node.Address == ""
+		return (empty || isIpv4)
 	})
 	_ = v.RegisterValidation("endpoint_check", func(fl validator.FieldLevel) bool {
 		//var isFieldUnique bool = functions.IsFieldUnique(networkName, "endpoint", node.Endpoint)
@@ -76,13 +75,13 @@ func ValidateNode(operation string, networkName string, node models.Node) error
 	_ = v.RegisterValidation("localaddress_check", func(fl validator.FieldLevel) bool {
 		//var isFieldUnique bool = functions.IsFieldUnique(networkName, "endpoint", node.Endpoint)
 		isIpv4 := functions.IsIpv4Net(node.LocalAddress)
-		notEmptyCheck := node.LocalAddress != ""
-		return (notEmptyCheck && isIpv4)
+		empty := node.LocalAddress == ""
+		return (empty || isIpv4)
 	})
 
 	_ = v.RegisterValidation("macaddress_unique", func(fl validator.FieldLevel) bool {
 		var isFieldUnique bool = functions.IsFieldUnique(networkName, "macaddress", node.MacAddress)
-		return isFieldUnique || operation == "update"
+		return isFieldUnique
 	})
 
 	_ = v.RegisterValidation("macaddress_valid", func(fl validator.FieldLevel) bool {
@@ -120,6 +119,66 @@ func ValidateNode(operation string, networkName string, node models.Node) error
 	return err
 }
 
+func ValidateNodeUpdate(networkName string, node models.Node) error {
+
+        v := validator.New()
+        _ = v.RegisterValidation("address_check", func(fl validator.FieldLevel) bool {
+                isIpv4 := functions.IsIpv4Net(node.Address)
+                empty := node.Address == ""
+                return (empty || isIpv4)
+        })
+        _ = v.RegisterValidation("endpoint_check", func(fl validator.FieldLevel) bool {
+                //var isFieldUnique bool = functions.IsFieldUnique(networkName, "endpoint", node.Endpoint)
+                isIpv4 := functions.IsIpv4Net(node.Endpoint)
+                empty := node.Endpoint == ""
+                return (empty || isIpv4)
+        })
+        _ = v.RegisterValidation("localaddress_check", func(fl validator.FieldLevel) bool {
+                //var isFieldUnique bool = functions.IsFieldUnique(networkName, "endpoint", node.Endpoint)
+                isIpv4 := functions.IsIpv4Net(node.LocalAddress)
+                empty := node.LocalAddress == ""
+                return (empty || isIpv4)
+        })
+
+        _ = v.RegisterValidation("macaddress_unique", func(fl validator.FieldLevel) bool {
+                return true
+        })
+
+        _ = v.RegisterValidation("macaddress_valid", func(fl validator.FieldLevel) bool {
+                _, err := net.ParseMAC(node.MacAddress)
+                return err == nil
+        })
+
+        _ = v.RegisterValidation("name_valid", func(fl validator.FieldLevel) bool {
+                isvalid := functions.NameInNodeCharSet(node.Name)
+                return isvalid
+        })
+
+        _ = v.RegisterValidation("network_exists", func(fl validator.FieldLevel) bool {
+                _, err := node.GetNetwork()
+                return err == nil
+        })
+        _ = v.RegisterValidation("pubkey_check", func(fl validator.FieldLevel) bool {
+                empty := node.PublicKey == ""
+                isBase64 := functions.IsBase64(node.PublicKey)
+                return (empty || isBase64)
+        })
+        _ = v.RegisterValidation("password_check", func(fl validator.FieldLevel) bool {
+                empty := node.Password == ""
+                goodLength := len(node.Password) > 5
+                return (empty || goodLength)
+        })
+
+        err := v.Struct(node)
+
+        if err != nil {
+                for _, e := range err.(validator.ValidationErrors) {
+                        fmt.Println(e)
+                }
+        }
+        return err
+}
+
 func UpdateNode(nodechange models.Node, node models.Node) (models.Node, error) {
 	//Question: Is there a better way  of doing  this than a bunch of "if" statements? probably...
 	//Eventually, lets have a better way to check if any of the fields are filled out...
@@ -303,7 +362,7 @@ func CreateNode(node models.Node, networkName string) (models.Node, error) {
 	//Anyways, this scrolls through all the IP Addresses in the network range and checks against nodes
 	//until one is open and then returns it
 	node.Address, err = functions.UniqueAddress(networkName)
-
+	fmt.Println("Setting node address: " + node.Address)
 	if err != nil {
 		return node, err
 	}

+ 1 - 0
controllers/controller.go

@@ -28,6 +28,7 @@ func HandleRESTRequests(wg *sync.WaitGroup) {
     nodeHandlers(r)
     userHandlers(r)
     networkHandlers(r)
+    dnsHandlers(r)
     fileHandlers(r)
     serverHandlers(r)
 

+ 525 - 0
controllers/dnsHttpController.go

@@ -0,0 +1,525 @@
+package controller
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"time"
+	"github.com/gorilla/mux"
+	"github.com/txn2/txeh"
+	"github.com/gravitl/netmaker/functions"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/mongoconn"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/mongo/options"
+	"gopkg.in/go-playground/validator.v9"
+)
+
+func dnsHandlers(r *mux.Router) {
+
+	r.HandleFunc("/api/dns", securityCheck(http.HandlerFunc(getAllDNS))).Methods("GET")
+	r.HandleFunc("/api/dns/adm/{network}/nodes", securityCheck(http.HandlerFunc(getNodeDNS))).Methods("GET")
+	r.HandleFunc("/api/dns/adm/{network}/custom", securityCheck(http.HandlerFunc(getCustomDNS))).Methods("GET")
+	r.HandleFunc("/api/dns/adm/{network}", securityCheck(http.HandlerFunc(getDNS))).Methods("GET")
+	r.HandleFunc("/api/dns/{network}", securityCheck(http.HandlerFunc(createDNS))).Methods("POST")
+	r.HandleFunc("/api/dns/adm/pushdns", securityCheck(http.HandlerFunc(pushDNS))).Methods("POST")
+	r.HandleFunc("/api/dns/{network}/{domain}", securityCheck(http.HandlerFunc(deleteDNS))).Methods("DELETE")
+	r.HandleFunc("/api/dns/{network}/{domain}", securityCheck(http.HandlerFunc(updateDNS))).Methods("PUT")
+}
+
+//Gets all nodes associated with network, including pending nodes
+func getNodeDNS(w http.ResponseWriter, r *http.Request) {
+
+        w.Header().Set("Content-Type", "application/json")
+
+        var dns []models.DNSEntry
+        var params = mux.Vars(r)
+
+	dns, err := GetNodeDNS(params["network"])
+        if err != nil {
+                returnErrorResponse(w, r, formatError(err, "internal"))
+                return
+        }
+
+        //Returns all the nodes in JSON format
+        w.WriteHeader(http.StatusOK)
+        json.NewEncoder(w).Encode(dns)
+}
+
+//Gets all nodes associated with network, including pending nodes
+func getAllDNS(w http.ResponseWriter, r *http.Request) {
+
+        w.Header().Set("Content-Type", "application/json")
+
+        var dns []models.DNSEntry
+
+	networks, err := functions.ListNetworks()
+        if err != nil {
+                returnErrorResponse(w, r, formatError(err, "internal"))
+                return
+        }
+
+        for _, net := range networks {
+                netdns, err := GetDNS(net.NetID)
+                if err != nil {
+			returnErrorResponse(w, r, formatError(err, "internal"))
+                        return
+                }
+	        dns = append(dns, netdns...)
+        }
+
+        //Returns all the nodes in JSON format
+        w.WriteHeader(http.StatusOK)
+        json.NewEncoder(w).Encode(dns)
+}
+
+
+func GetNodeDNS(network string) ([]models.DNSEntry, error){
+
+        var dns []models.DNSEntry
+
+        collection := mongoconn.Client.Database("netmaker").Collection("nodes")
+
+        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+        filter := bson.M{"network": network}
+
+        cur, err := collection.Find(ctx, filter, options.Find().SetProjection(bson.M{"_id": 0}))
+
+        if err != nil {
+                return dns, err
+        }
+
+        defer cancel()
+
+        for cur.Next(context.TODO()) {
+
+                var entry models.DNSEntry
+
+                err := cur.Decode(&entry)
+                if err != nil {
+                        return dns, err
+                }
+
+                // add item our array of nodes
+                dns = append(dns, entry)
+        }
+
+        //TODO: Another fatal error we should take care of.
+        if err := cur.Err(); err != nil {
+                return dns, err
+        }
+
+	return dns, err
+}
+
+//Gets all nodes associated with network, including pending nodes
+func getCustomDNS(w http.ResponseWriter, r *http.Request) {
+
+        w.Header().Set("Content-Type", "application/json")
+
+        var dns []models.DNSEntry
+        var params = mux.Vars(r)
+
+        dns, err := GetCustomDNS(params["network"])
+        if err != nil {
+                returnErrorResponse(w, r, formatError(err, "internal"))
+                return
+        }
+
+        //Returns all the nodes in JSON format
+        w.WriteHeader(http.StatusOK)
+        json.NewEncoder(w).Encode(dns)
+}
+
+func GetCustomDNS(network string) ([]models.DNSEntry, error){
+
+        var dns []models.DNSEntry
+
+        collection := mongoconn.Client.Database("netmaker").Collection("dns")
+
+        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+        filter := bson.M{"network": network}
+
+        cur, err := collection.Find(ctx, filter, options.Find().SetProjection(bson.M{"_id": 0}))
+
+        if err != nil {
+                return dns, err
+        }
+
+        defer cancel()
+
+        for cur.Next(context.TODO()) {
+
+                var entry models.DNSEntry
+
+                err := cur.Decode(&entry)
+                if err != nil {
+                        return dns, err
+                }
+
+                // add item our array of nodes
+                dns = append(dns, entry)
+        }
+
+        //TODO: Another fatal error we should take care of.
+        if err := cur.Err(); err != nil {
+                return dns, err
+        }
+
+        return dns, err
+}
+
+func GetDNSEntryNum(domain string, network string) (int, error){
+
+        num := 0
+
+        entries, err := GetDNS(network)
+        if err != nil {
+                return 0, err
+        }
+
+        for i := 0; i < len(entries); i++ {
+
+                if domain == entries[i].Name {
+                        num++
+                }
+        }
+
+        return num, nil
+}
+
+//Gets all nodes associated with network, including pending nodes
+func getDNS(w http.ResponseWriter, r *http.Request) {
+
+        w.Header().Set("Content-Type", "application/json")
+
+        var dns []models.DNSEntry
+        var params = mux.Vars(r)
+
+	dns, err := GetDNS(params["network"])
+        if err != nil {
+                returnErrorResponse(w, r, formatError(err, "internal"))
+                return
+        }
+        w.WriteHeader(http.StatusOK)
+        json.NewEncoder(w).Encode(dns)
+}
+
+func GetDNS(network string) ([]models.DNSEntry, error) {
+
+        var dns []models.DNSEntry
+        dns, err := GetNodeDNS(network)
+        if err != nil {
+                return dns, err
+        }
+        customdns, err := GetCustomDNS(network)
+        if err != nil {
+                return dns, err
+        }
+
+        dns = append(dns, customdns...)
+	return dns, err
+}
+
+func createDNS(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+
+	var entry models.DNSEntry
+        var params = mux.Vars(r)
+
+	//get node from body of request
+	_ = json.NewDecoder(r.Body).Decode(&entry)
+	entry.Network = params["network"]
+
+	err := ValidateDNSCreate(entry)
+	if err != nil {
+		returnErrorResponse(w, r, formatError(err, "badrequest"))
+		return
+	}
+
+	entry, err = CreateDNS(entry)
+	if err != nil {
+                returnErrorResponse(w, r, formatError(err, "internal"))
+		return
+	}
+        w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(entry)
+}
+
+func updateDNS(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+
+	var params = mux.Vars(r)
+
+	var entry models.DNSEntry
+
+	//start here
+	entry, err := GetDNSEntry(params["domain"],params["network"])
+	if err != nil {
+                returnErrorResponse(w, r, formatError(err, "badrequest"))
+		return
+	}
+
+	var dnschange models.DNSEntry
+
+	// we decode our body request params
+	err = json.NewDecoder(r.Body).Decode(&dnschange)
+	if err != nil {
+                returnErrorResponse(w, r, formatError(err, "badrequest"))
+		return
+	}
+
+	err = ValidateDNSUpdate(dnschange, entry)
+
+	if err != nil {
+                returnErrorResponse(w, r, formatError(err, "badrequest"))
+		return
+	}
+
+	entry, err = UpdateDNS(dnschange, entry)
+
+	if err != nil {
+                returnErrorResponse(w, r, formatError(err, "badrequest"))
+		return
+	}
+
+	json.NewEncoder(w).Encode(entry)
+}
+
+func deleteDNS(w http.ResponseWriter, r *http.Request) {
+        // Set header
+        w.Header().Set("Content-Type", "application/json")
+
+        // get params
+        var params = mux.Vars(r)
+
+        success, err := DeleteDNS(params["domain"], params["network"])
+
+        if err != nil {
+                returnErrorResponse(w, r, formatError(err, "internal"))
+                return
+        } else if !success {
+                returnErrorResponse(w, r, formatError(errors.New("Delete unsuccessful."), "badrequest"))
+                return
+        }
+
+        json.NewEncoder(w).Encode(params["domain"] + " deleted.")
+}
+
+func CreateDNS(entry models.DNSEntry) (models.DNSEntry, error) {
+
+        // connect db
+        collection := mongoconn.Client.Database("netmaker").Collection("dns")
+        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+        // insert our node to the node db.
+        _, err := collection.InsertOne(ctx, entry)
+
+        defer cancel()
+
+        return entry, err
+}
+
+func GetDNSEntry(domain string, network string) (models.DNSEntry, error) {
+        var entry models.DNSEntry
+
+        collection := mongoconn.Client.Database("netmaker").Collection("dns")
+
+        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+        filter := bson.M{"name": domain, "network": network}
+        err := collection.FindOne(ctx, filter, options.FindOne().SetProjection(bson.M{"_id": 0})).Decode(&entry)
+
+        defer cancel()
+
+        return entry, err
+}
+
+func UpdateDNS(dnschange models.DNSEntry, entry models.DNSEntry) (models.DNSEntry, error) {
+
+        queryDNS := entry.Name
+
+        if dnschange.Name != "" {
+                entry.Name = dnschange.Name
+        }
+        if dnschange.Address != "" {
+                entry.Address = dnschange.Address
+        }
+        //collection := mongoconn.ConnectDB()
+        collection := mongoconn.Client.Database("netmaker").Collection("dns")
+
+        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+        // Create filter
+        filter := bson.M{"name": queryDNS}
+
+        // prepare update model.
+        update := bson.D{
+                {"$set", bson.D{
+                        {"name", entry.Name},
+                        {"address", entry.Address},
+                }},
+        }
+        var dnsupdate models.DNSEntry
+
+        errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&dnsupdate)
+        if errN != nil {
+                fmt.Println("Could not update: ")
+                fmt.Println(errN)
+        } else {
+                fmt.Println("DNS Entry updated successfully.")
+        }
+
+        defer cancel()
+
+        return dnsupdate, errN
+}
+
+func DeleteDNS(domain string, network string) (bool, error) {
+
+	deleted := false
+
+	collection := mongoconn.Client.Database("netmaker").Collection("dns")
+
+	filter := bson.M{"name": domain,  "network": network}
+
+	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+	result, err := collection.DeleteOne(ctx, filter)
+
+	deletecount := result.DeletedCount
+
+	if deletecount > 0 {
+		deleted = true
+	}
+
+	defer cancel()
+
+	return deleted, err
+}
+
+func pushDNS(w http.ResponseWriter, r *http.Request) {
+        // Set header
+        w.Header().Set("Content-Type", "application/json")
+
+        err := WriteHosts()
+
+        if err != nil {
+                returnErrorResponse(w, r, formatError(err, "internal"))
+                return
+	}
+        json.NewEncoder(w).Encode("DNS Pushed to CoreDNS")
+}
+
+
+func WriteHosts() error {
+	//hostfile, err := txeh.NewHostsDefault()
+	hostfile := txeh.Hosts{}
+	/*
+	if err != nil {
+                return err
+        }
+	*/
+	networks, err := functions.ListNetworks()
+        if err != nil {
+                return err
+        }
+
+	for _, net := range networks {
+		dns, err := GetDNS(net.NetID)
+		if err != nil {
+			return err
+		}
+		for _, entry := range dns {
+			hostfile.AddHost(entry.Address, entry.Name+"."+entry.Network)
+			if err != nil {
+				return err
+	                }
+		}
+	}
+	err = hostfile.SaveAs("./config/netmaker.hosts")
+	return err
+}
+
+func ValidateDNSCreate(entry models.DNSEntry) error {
+
+	v := validator.New()
+        fmt.Println("Validating DNS: " + entry.Name)
+        fmt.Println("       Address: " + entry.Address)
+        fmt.Println("       Network: " + entry.Network)
+
+	_ = v.RegisterValidation("name_unique", func(fl validator.FieldLevel) bool {
+		num, err := GetDNSEntryNum(entry.Name, entry.Network)
+		return err == nil && num == 0
+	})
+
+	_ = v.RegisterValidation("name_valid", func(fl validator.FieldLevel) bool {
+		isvalid := functions.NameInDNSCharSet(entry.Name)
+                notEmptyCheck := len(entry.Name) > 0
+		return isvalid && notEmptyCheck
+	})
+
+	_ = v.RegisterValidation("address_valid", func(fl validator.FieldLevel) bool {
+		notEmptyCheck := len(entry.Address) > 0
+                isIpv4 := functions.IsIpv4Net(entry.Address)
+		return notEmptyCheck && isIpv4
+	})
+        _ = v.RegisterValidation("network_exists", func(fl validator.FieldLevel) bool {
+                _, err := functions.GetParentNetwork(entry.Network)
+                return err == nil
+        })
+
+	err := v.Struct(entry)
+
+	if err != nil {
+		for _, e := range err.(validator.ValidationErrors) {
+			fmt.Println(e)
+		}
+	}
+	return err
+}
+
+func ValidateDNSUpdate(change models.DNSEntry, entry models.DNSEntry) error {
+
+        v := validator.New()
+
+        _ = v.RegisterValidation("name_unique", func(fl validator.FieldLevel) bool {
+		goodNum := false
+                num, err := GetDNSEntryNum(entry.Name, entry.Network)
+		if change.Name != entry.Name {
+			goodNum = num == 0
+		} else {
+                        goodNum = num == 1
+		}
+		return err == nil && goodNum
+        })
+
+        _ = v.RegisterValidation("name_valid", func(fl validator.FieldLevel) bool {
+                isvalid := functions.NameInDNSCharSet(entry.Name)
+                notEmptyCheck := entry.Name != ""
+                return isvalid && notEmptyCheck
+        })
+
+        _ = v.RegisterValidation("address_valid", func(fl validator.FieldLevel) bool {
+		isValid := true
+		if entry.Address != "" {
+			isValid = functions.IsIpv4Net(entry.Address)
+		}
+		return isValid
+        })
+
+        err := v.Struct(entry)
+
+        if err != nil {
+                for _, e := range err.(validator.ValidationErrors) {
+                        fmt.Println(e)
+                }
+        }
+        return err
+}
+
+

+ 2 - 2
controllers/nodeGrpcController.go

@@ -82,7 +82,7 @@ func (s *NodeServiceServer) CreateNode(ctx context.Context, req *nodepb.CreateNo
                         ListenPort:  data.GetListenport(),
 	}
 
-        err := ValidateNode("create", node.Network, node)
+        err := ValidateNodeCreate(node.Network, node)
 
         if err != nil {
                 // return internal gRPC error to be handled later
@@ -227,7 +227,7 @@ func (s *NodeServiceServer) UpdateNode(ctx context.Context, req *nodepb.UpdateNo
         network, _ := functions.GetParentNetwork(networkName)
 
 
-	err := ValidateNode("update", networkName, nodechange)
+	err := ValidateNodeUpdate(networkName, nodechange)
         if err != nil {
                 return nil, err
         }

+ 2 - 2
controllers/nodeHttpController.go

@@ -509,7 +509,7 @@ func createNode(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	err = ValidateNode("create", networkName, node)
+	err = ValidateNodeCreate(networkName, node)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
 		return
@@ -765,7 +765,7 @@ func updateNode(w http.ResponseWriter, r *http.Request) {
 	if nodechange.MacAddress == "" {
 		nodechange.MacAddress = node.MacAddress
 	}
-	err = ValidateNode("update", params["network"], nodechange)
+	err = ValidateNodeUpdate(params["network"], nodechange)
 	if err != nil {
 		returnErrorResponse(w, r, formatError(err, "badrequest"))
 		return

+ 13 - 0
functions/helpers.go

@@ -446,6 +446,19 @@ func NameInNetworkCharSet(name string) bool {
 	return true
 }
 
+func NameInDNSCharSet(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-"

+ 1 - 0
go.mod

@@ -11,6 +11,7 @@ require (
 	github.com/gorilla/mux v1.8.0
 	github.com/leodido/go-urn v1.2.0 // indirect
 	github.com/stretchr/testify v1.6.1
+	github.com/txn2/txeh v1.3.0 // indirect
 	go.mongodb.org/mongo-driver v1.4.3
 	golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
 	golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect

+ 23 - 0
go.sum

@@ -1,10 +1,15 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/aws/aws-sdk-go v1.34.28 h1:sscPpn/Ns3i0F4HPEWAVcwdIRaZZCuL7llJ2/60yPIk=
 github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -16,6 +21,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
 github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
 github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
 github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
@@ -74,6 +80,7 @@ github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH
 github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
@@ -96,6 +103,7 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
 github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
 github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
@@ -106,7 +114,10 @@ github.com/mdlayher/netlink v1.1.0 h1:mpdLgm+brq10nI9zM1BpX1kpDbh3NLl3RSnVq6ZSkf
 github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
 github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws=
 github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -118,11 +129,17 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
 github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -133,13 +150,18 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/txn2/txeh v1.3.0 h1:vnbv63htVMZCaQgLqVBxKvj2+HHHFUzNW7I183zjg3E=
+github.com/txn2/txeh v1.3.0/go.mod h1:O7M6gUTPeMF+vsa4c4Ipx3JDkOYrruB1Wry8QRsMcw8=
+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
 github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
 github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo=
 github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 go.mongodb.org/mongo-driver v1.4.3 h1:moga+uhicpVshTyaqY9L23E6QqwcHRUv1sqyOsoyOO8=
 go.mongodb.org/mongo-driver v1.4.3/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
 golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -173,6 +195,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

+ 8 - 0
models/dnsEntry.go

@@ -0,0 +1,8 @@
+//TODO:  Either add a returnNetwork and returnKey, or delete this
+package models
+
+type DNSEntry struct {
+	Address	string `json:"address" bson:"address" validate:"address_valid"`
+	Name	string `json:"name" bson:"name" validate:"name_valid,name_unique,max=120"`
+	Network	string `json:"network" bson:"network" validate:"network_exists"`
+}

+ 7 - 0
netmaker-install-clientmode.sh

@@ -31,6 +31,12 @@ mongoconn:
   opts: '/?authSource=admin'
 EOL
 
+cat >/etc/netmaker/config/Corefile<<EOL
+. {
+    hosts /root/netmaker.hosts
+}
+EOL
+
 cat >/etc/systemd/system/netmaker.service<<EOL
 [Unit]
 Description=Netmaker Server
@@ -51,3 +57,4 @@ systemctl start netmaker.service
 
 
 docker run -d --name netmaker-ui -p 80:80 -e BACKEND_URL="http://$SERVER_DOMAIN:8081" gravitl/netmaker-ui:v0.2
+docker run -d --name coredns --restart=always --volume=/etc/netmaker/config/:/root/ -p 52:53/udp coredns/coredns -conf /root/Corefile

+ 0 - 12
test/node_test.go

@@ -213,18 +213,6 @@ func TestUpdateNode(t *testing.T) {
 		assert.Equal(t, http.StatusBadRequest, message.Code)
 		assert.Contains(t, message.Message, "Field validation for 'Password' failed")
 	})
-	t.Run("EmptyPassword", func(t *testing.T) {
-		data.Password = ""
-		response, err := api(t, data, http.MethodPut, baseURL+"/api/nodes/skynet/01:02:03:04:05:05", "secretkey")
-		assert.Nil(t, err, err)
-		assert.Equal(t, http.StatusBadRequest, response.StatusCode)
-		var message models.ErrorResponse
-		defer response.Body.Close()
-		err = json.NewDecoder(response.Body).Decode(&message)
-		assert.Nil(t, err, err)
-		assert.Equal(t, http.StatusBadRequest, message.Code)
-		assert.Contains(t, message.Message, "Field validation for 'Password' failed")
-	})
 }
 
 func TestDeleteNode(t *testing.T) {