diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..70cfddea3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+netmaker
+netclient/netclient
+netclient/files/netclient
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..520b6cfc0
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,31 @@
+#first stage - builder
+
+FROM golang:1.14-stretch as builder
+
+COPY . /app
+
+WORKDIR /app
+
+ENV GO111MODULE=auto
+
+RUN CGO_ENABLED=0 GOOS=linux go build -o app main.go
+
+
+#second stage
+
+FROM alpine:latest
+
+WORKDIR /root/
+
+RUN apk add --no-cache tzdata
+
+COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
+
+COPY --from=builder /app .
+COPY --from=builder /app/config config
+
+EXPOSE 8081
+EXPOSE 50051
+
+CMD ["./app"]
+
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..d7567ef5d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,60 @@
+
+
+
+
+
+Connect any computers together over a secure, fast, private network, and manage multiple networks from a central server.
+
+
+## What is Netmaker?
+Netmaker lets you easily create secure virtual networks: Just spin up a Netmaker server and install the agent on your computers. Netmaker relies on WireGuard to create encrypted tunnels between every node in your virtual network, creating a mesh overlay. Netmaker takes the work out of manually configuring machines and updating them every time something changes in your network. The agents are self-updating and pull necessary changes from the server.
+
+Netmaker also has a handy dandy UI, which you can find in [this repo](https://github.com/falconcat-inc/WireCat-UI). We recommend deploying the UI alongside the server to make the experience easier and better.
+
+## Why Netmaker?
+ 1. Create a flat, secure network between multiple/hybrid cloud environments
+ 2. Integrate central and edge services + IoT
+ 3. Secure an office or home network while providing remote connectivity
+ 4. Manage cryptocurrency proof-of-stake machines
+ 5. Provide an additional layer of security on an existing network
+ 6. Encrypt Kubernetes inter-node communications
+ 7. Secure site-to-site connections
+
+
+
+
+
+## Docs
+**For more information, please read the docs, or check out the Quick Start below:**
+
+ - [Getting Started](docs/GETTING_STARTED.md)
+ - [API Documentation](docs/API.md)
+ - [Product Roadmap](docs/ROADMAP.md)
+ - [Contributing](docs/CONTRIBUTING.md)
+
+
+## Quick Start
+
+Setup Docker (Prereq):
+1. Create an access token on github with artifact access.
+2. On your VPS, create a file in the home dir called TOKEN.txt with the value of your token inside.
+3. `cat ~/TOKEN.txt | sudo docker login https://docker.pkg.github.com -u GITHUB_USERNAME --password-stdin`
+
+Setup Server:
+1. Clone this repo or just copy contents of "docker-compose.yml" to a machine with a public IP.
+2. In docker-compose.yml, change BACKEND_URL to the public IP ofthat machine.
+3. Run `sudo docker-compose up`
+4. Navigate to your IP and you should see the WireCat UI asking for a new admin user (if not or if it takes you straight to login screen without asking for user creation, investigate the error).
+5. Create the admin user
+6. Click "Create Group" and fill out the details (group == network)
+7. You are now ready to begin using WireCat. Create a key or "allow manual node sign up."
+
+Run on each machine in network:
+1. Get the binary: `sudo wget 52.55.6.84:8081/meshclient/files/meshclient`
+2. Make it executable: `sudo chmod +x meshclient`
+3. Run the install command: `sudo ./meshclient -c install -g -s -k `
+
+
+#### LICENSE
+
+Netmaker's source code and all artifacts in this repository are freely available. All versions are published under the Server Side Public License (SSPL), version 1, which can be found under the "licensing" directory: [LICENSE.txt](licensing/LICENSE.txt).
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 000000000..e4d556c98
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,79 @@
+//Environment file for getting variables
+//Currently the only thing it does is set the master password
+//Should probably have it take over functions from OS such as port and mongodb connection details
+//Reads from the config/environments/dev.yaml file by default
+//TODO: Add vars for mongo and remove from OS vars in mongoconn
+package config
+
+import (
+ "os"
+ "fmt"
+ "log"
+ "gopkg.in/yaml.v3"
+)
+
+//setting dev by default
+func getEnv() string {
+
+ env := os.Getenv("APP_ENV")
+
+ if len(env) == 0 {
+ return "dev"
+ }
+
+ return env
+}
+
+// Config : application config stored as global variable
+var Config *EnvironmentConfig
+
+// EnvironmentConfig :
+type EnvironmentConfig struct {
+ Server ServerConfig `yaml:"server"`
+ MongoConn MongoConnConfig `yaml:"mongoconn"`
+}
+
+// ServerConfig :
+type ServerConfig struct {
+ Host string `yaml:"host"`
+ ApiPort string `yaml:"apiport"`
+ GrpcPort string `yaml:"grpcport"`
+ MasterKey string `yaml:"masterkey"`
+ AllowedOrigin string `yaml:"allowedorigin"`
+ RestBackend bool `yaml:"restbackend"`
+ AgentBackend bool `yaml:"agentbackend"`
+}
+
+type MongoConnConfig struct {
+ User string `yaml:"user"`
+ Pass string `yaml:"pass"`
+ Host string `yaml:"host"`
+ Port string `yaml:"port"`
+ Opts string `yaml:"opts"`
+}
+
+
+//reading in the env file
+func readConfig() *EnvironmentConfig {
+ file := fmt.Sprintf("config/environments/%s.yaml", getEnv())
+ f, err := os.Open(file)
+ if err != nil {
+ log.Fatal(err)
+ os.Exit(2)
+ }
+ defer f.Close()
+
+ var cfg EnvironmentConfig
+ decoder := yaml.NewDecoder(f)
+ err = decoder.Decode(&cfg)
+ if err != nil {
+ log.Fatal(err)
+ os.Exit(2)
+ }
+ return &cfg
+
+}
+
+func init() {
+ Config = readConfig()
+}
diff --git a/config/environments/dev.yaml b/config/environments/dev.yaml
new file mode 100644
index 000000000..205f3771b
--- /dev/null
+++ b/config/environments/dev.yaml
@@ -0,0 +1,14 @@
+server:
+ host: "localhost"
+ apiport: "8081"
+ grpcport: "50051"
+ masterkey: "secretkey"
+ allowedorigin: "*"
+ restbackend: true
+ agentbackend: true
+mongoconn:
+ user: "mongoadmin"
+ pass: "mongopass"
+ host: "localhost"
+ port: "27017"
+ opts: '/?authSource=admin'
diff --git a/controllers/.nodeHttpController.go.swp b/controllers/.nodeHttpController.go.swp
new file mode 100644
index 000000000..0c42818d0
Binary files /dev/null and b/controllers/.nodeHttpController.go.swp differ
diff --git a/controllers/authGrpc.go b/controllers/authGrpc.go
new file mode 100644
index 000000000..5607c9453
--- /dev/null
+++ b/controllers/authGrpc.go
@@ -0,0 +1,154 @@
+package controller
+
+import (
+ "errors"
+ "context"
+ "golang.org/x/crypto/bcrypt"
+ "time"
+ nodepb "github.com/gravitl/netmaker/grpc"
+ "github.com/gravitl/netmaker/models"
+ "github.com/gravitl/netmaker/functions"
+ "github.com/gravitl/netmaker/mongoconn"
+ "go.mongodb.org/mongo-driver/bson"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/metadata"
+ "google.golang.org/grpc/status"
+ "google.golang.org/grpc/codes"
+
+)
+
+func AuthServerUnaryInterceptor(ctx context.Context,
+ req interface{},
+ info *grpc.UnaryServerInfo,
+ handler grpc.UnaryHandler) (interface{}, error) {
+ // Skip authorize when GetJWT is requested
+
+ if info.FullMethod != "/node.NodeService/Login" {
+ if info.FullMethod != "/node.NodeService/CreateNode" {
+
+ err := grpcAuthorize(ctx)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+
+ // Calls the handler
+ h, err := handler(ctx, req)
+
+ return h, err
+}
+func AuthServerStreamInterceptor(
+ srv interface{},
+ stream grpc.ServerStream,
+ info *grpc.StreamServerInfo,
+ handler grpc.StreamHandler,
+ ) error {
+ if info.FullMethod == "/node.NodeService/GetPeers" {
+ if err := grpcAuthorize(stream.Context()); err != nil {
+ return err
+ }
+ }
+
+
+ // Calls the handler
+ return handler(srv, stream)
+}
+
+func grpcAuthorize(ctx context.Context) error {
+
+
+ md, ok := metadata.FromIncomingContext(ctx)
+
+ if !ok {
+ return status.Errorf(codes.InvalidArgument, "Retrieving metadata is failed")
+ }
+
+ authHeader, ok := md["authorization"]
+ if !ok {
+ return status.Errorf(codes.Unauthenticated, "Authorization token is not supplied")
+ }
+
+ authToken := authHeader[0]
+
+ mac, group, err := functions.VerifyToken(authToken)
+
+ if err != nil { return err }
+
+ groupexists, err := functions.GroupExists(group)
+
+ if err != nil {
+ return err
+ }
+ emptynode := models.Node{}
+ node, err := functions.GetNodeByMacAddress(group, mac)
+ if err != nil || node == emptynode {
+ return status.Errorf(codes.Unauthenticated, "Node does not exist.")
+ }
+
+ //check that the request is for a valid group
+ //if (groupCheck && !groupexists) || err != nil {
+ if (!groupexists) {
+
+ return status.Errorf(codes.Unauthenticated, "Group does not exist.")
+
+ } else {
+ return nil
+ }
+}
+
+
+//Node authenticates using its password and retrieves a JWT for authorization.
+func (s *NodeServiceServer) Login(ctx context.Context, req *nodepb.LoginRequest) (*nodepb.LoginResponse, error) {
+
+ //out := new(LoginResponse)
+ macaddress := req.GetMacaddress()
+ password := req.GetPassword()
+
+ var result models.NodeAuth
+
+ err := errors.New("Generic server error.")
+
+
+ if macaddress == "" {
+ //TODO: Set Error response
+ err = errors.New("Missing Mac Address.")
+ return nil, err
+ } else if password == "" {
+ err = errors.New("Missing Password.")
+ return nil, err
+ } else {
+ //Search DB for node with Mac Address. Ignore pending nodes (they should not be able to authenticate with API untill approved).
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ var err = collection.FindOne(ctx, bson.M{ "macaddress": macaddress, "ispending": false }).Decode(&result)
+
+ defer cancel()
+
+ if err != nil {
+ return nil, err
+ }
+
+ //compare password from request to stored password in database
+ //might be able to have a common hash (certificates?) and compare those so that a password isn't passed in in plain text...
+ //TODO: Consider a way of hashing the password client side before sending, or using certificates
+ err = bcrypt.CompareHashAndPassword([]byte(result.Password), []byte(password))
+ if err != nil && result.Password != password {
+ return nil, err
+ } else {
+ //Create a new JWT for the node
+ tokenString, _ := functions.CreateJWT(macaddress, result.Group)
+
+ if tokenString == "" {
+ err = errors.New("Something went wrong. Could not retrieve token.")
+ return nil, err
+ }
+
+ response := &nodepb.LoginResponse{
+ Accesstoken: tokenString,
+ }
+ return response, nil
+ }
+ }
+}
diff --git a/controllers/common.go b/controllers/common.go
new file mode 100644
index 000000000..97a371f36
--- /dev/null
+++ b/controllers/common.go
@@ -0,0 +1,469 @@
+package controller
+
+import (
+// "github.com/davecgh/go-spew/spew"
+ "gopkg.in/go-playground/validator.v9"
+ "log"
+ "fmt"
+ "golang.org/x/crypto/bcrypt"
+ "github.com/gravitl/netmaker/mongoconn"
+ "github.com/gravitl/netmaker/functions"
+ "context"
+ "go.mongodb.org/mongo-driver/bson"
+ "time"
+ "net"
+ "github.com/gravitl/netmaker/models"
+ "go.mongodb.org/mongo-driver/mongo/options"
+
+)
+
+func GetPeersList(groupName string) ([]models.PeersResponse, error) {
+
+ var peers []models.PeersResponse
+
+ //Connection mongoDB with mongoconn class
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ //Get all nodes in the relevant group which are NOT in pending state
+ filter := bson.M{"group": groupName, "ispending": false}
+ cur, err := collection.Find(ctx, filter)
+
+ if err != nil {
+ return peers, err
+ }
+
+ // Close the cursor once finished and cancel if it takes too long
+ defer cancel()
+
+ for cur.Next(context.TODO()) {
+
+ var peer models.PeersResponse
+ err := cur.Decode(&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)
+ }
+
+ //Uh oh, fatal error! This needs some better error handling
+ //TODO: needs appropriate error handling so the server doesnt shut down.
+ if err := cur.Err(); err != nil {
+ log.Fatal(err)
+ }
+
+ return peers, err
+}
+
+
+func ValidateNode(operation string, groupName string, node models.Node) error {
+
+ v := validator.New()
+
+ _ = v.RegisterValidation("endpoint_check", func(fl validator.FieldLevel) bool {
+ //var isFieldUnique bool = functions.IsFieldUnique(groupName, "endpoint", node.Endpoint)
+ isIpv4 := functions.IsIpv4Net(node.Endpoint)
+ notEmptyCheck := node.Endpoint != ""
+ return (notEmptyCheck && isIpv4) || operation == "update"
+ })
+ _ = v.RegisterValidation("localaddress_check", func(fl validator.FieldLevel) bool {
+ //var isFieldUnique bool = functions.IsFieldUnique(groupName, "endpoint", node.Endpoint)
+ isIpv4 := functions.IsIpv4Net(node.LocalAddress)
+ notEmptyCheck := node.LocalAddress != ""
+ return (notEmptyCheck && isIpv4) || operation == "update"
+ })
+
+
+ _ = v.RegisterValidation("macaddress_unique", func(fl validator.FieldLevel) bool {
+ var isFieldUnique bool = functions.IsFieldUnique(groupName, "macaddress", node.MacAddress)
+ return isFieldUnique || operation == "update"
+ })
+
+ _ = 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("group_exists", func(fl validator.FieldLevel) bool {
+ _, err := node.GetGroup()
+ return err == nil
+ })
+ _ = v.RegisterValidation("pubkey_check", func(fl validator.FieldLevel) bool {
+ notEmptyCheck := node.PublicKey != ""
+ isBase64 := functions.IsBase64(node.PublicKey)
+ return (notEmptyCheck && isBase64) || operation == "update"
+ })
+ _ = v.RegisterValidation("password_check", func(fl validator.FieldLevel) bool {
+ notEmptyCheck := node.Password != ""
+ goodLength := len(node.Password) > 5
+ return (notEmptyCheck && goodLength) || operation == "update"
+ })
+
+ 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...
+ queryMac := node.MacAddress
+ notifygroup := false
+
+ if nodechange.Address != "" {
+ node.Address = nodechange.Address
+ notifygroup = true
+ }
+ if nodechange.Name != "" {
+ node.Name = nodechange.Name
+ }
+ if nodechange.LocalAddress != "" {
+ node.LocalAddress = nodechange.LocalAddress
+ }
+ if nodechange.ListenPort != 0 {
+ node.ListenPort = nodechange.ListenPort
+ }
+ if nodechange.PreUp != "" {
+ node.PreUp = nodechange.PreUp
+ }
+ if nodechange.Interface != "" {
+ node.Interface = nodechange.Interface
+ }
+ if nodechange.PostUp != "" {
+ node.PostUp = nodechange.PostUp
+ }
+ if nodechange.AccessKey != "" {
+ node.AccessKey = nodechange.AccessKey
+ }
+ if nodechange.Endpoint != "" {
+ node.Endpoint = nodechange.Endpoint
+ notifygroup = true
+}
+ if nodechange.SaveConfig != nil {
+ node.SaveConfig = nodechange.SaveConfig
+ }
+ if nodechange.PersistentKeepalive != 0 {
+ node.PersistentKeepalive = nodechange.PersistentKeepalive
+ }
+ if nodechange.Password != "" {
+ err := bcrypt.CompareHashAndPassword([]byte(nodechange.Password), []byte(node.Password))
+ if err != nil && nodechange.Password != node.Password {
+ hash, err := bcrypt.GenerateFromPassword([]byte(nodechange.Password), 5)
+ if err != nil {
+ return node, err
+ }
+ nodechange.Password = string(hash)
+ node.Password = nodechange.Password
+ }
+ }
+ if nodechange.MacAddress != "" {
+ node.MacAddress = nodechange.MacAddress
+ }
+ if nodechange.PublicKey != "" {
+ node.PublicKey = nodechange.PublicKey
+ notifygroup = true
+ }
+
+ //collection := mongoconn.ConnectDB()
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ // Create filter
+ filter := bson.M{"macaddress": queryMac}
+
+ node.SetLastModified()
+
+ // prepare update model.
+ update := bson.D{
+ {"$set", bson.D{
+ {"name", node.Name},
+ {"password", node.Password},
+ {"listenport", node.ListenPort},
+ {"publickey", node.PublicKey},
+ {"endpoint", node.Endpoint},
+ {"postup", node.PostUp},
+ {"preup", node.PreUp},
+ {"macaddress", node.MacAddress},
+ {"localaddress", node.LocalAddress},
+ {"persistentkeepalive", node.PersistentKeepalive},
+ {"saveconfig", node.SaveConfig},
+ {"accesskey", node.AccessKey},
+ {"interface", node.Interface},
+ {"lastmodified", node.LastModified},
+ }},
+ }
+ var nodeupdate models.Node
+
+ errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&nodeupdate)
+ if errN != nil {
+ return nodeupdate, errN
+ }
+
+ returnnode, errN := GetNode(node.MacAddress, node.Group)
+
+ defer cancel()
+
+ if notifygroup {
+ errN = SetGroupNodesLastModified(node.Group)
+ }
+
+ return returnnode, errN
+}
+
+func DeleteNode(macaddress string, group string) (bool, error) {
+
+ deleted := false
+
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+
+ filter := bson.M{"macaddress": macaddress, "group": group}
+
+ 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()
+
+ err = SetGroupNodesLastModified(group)
+ fmt.Println("Deleted node " + macaddress + " from group " + group)
+
+ return deleted, err
+}
+
+func GetNode(macaddress string, group string) (models.Node, error) {
+
+ var node models.Node
+
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ filter := bson.M{"macaddress": macaddress, "group": group}
+ err := collection.FindOne(ctx, filter, options.FindOne().SetProjection(bson.M{"_id": 0})).Decode(&node)
+
+ defer cancel()
+
+ return node, err
+}
+
+func CreateNode(node models.Node, groupName string) (models.Node, error) {
+
+ //encrypt that password so we never see it again
+ hash, err := bcrypt.GenerateFromPassword([]byte(node.Password), 5)
+
+ if err != nil {
+ return node, err
+ }
+ //set password to encrypted password
+ node.Password = string(hash)
+
+
+ node.Group = groupName
+
+ //node.SetDefaults()
+ //Umm, why am I doing this again?
+ //TODO: Why am I using a local function instead of the struct function? I really dont know.
+ //I think I thought it didn't work but uhhh...idk
+ //anyways, this sets some sensible variables for unset params.
+ node.SetDefaults()
+
+ //Another DB call here...Inefficient
+ //Anyways, this scrolls through all the IP Addresses in the group range and checks against nodes
+ //until one is open and then returns it
+ node.Address, err = functions.UniqueAddress(groupName)
+
+ if err != nil {/*
+ errorResponse := models.ErrorResponse{
+ Code: http.StatusInternalServerError, Message: "W1R3: Encountered an internal error! ",
+ }*/
+ //returnErrorResponse(w, r, errorResponse)
+ return node, err
+ }
+
+ //IDK why these aren't a part of "set defaults. Pretty dumb.
+ //TODO: This is dumb. Consolidate and fix.
+ node.SetLastModified()
+ node.SetDefaultName()
+ node.SetLastCheckIn()
+ node.SetLastPeerUpdate()
+
+
+ //Create a JWT for the node
+ tokenString, _ := functions.CreateJWT(node.MacAddress, groupName)
+
+ if tokenString == "" {
+ //returnErrorResponse(w, r, errorResponse)
+ return node, err
+ }
+
+ // connect db
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+
+ // insert our node to the node db.
+ result, err := collection.InsertOne(ctx, node)
+ _ = result
+
+ defer cancel()
+
+ if err != nil {
+ return node, err
+ }
+ //return response for if node is pending
+ if !node.IsPending {
+
+ functions.DecrimentKey(node.Group, node.AccessKey)
+
+ }
+
+ SetGroupNodesLastModified(node.Group)
+
+ return node, err
+}
+
+func NodeCheckIn(node models.Node, groupName string) (models.CheckInResponse, error) {
+
+
+ var response models.CheckInResponse
+
+ parentgroup, err := functions.GetParentGroup(groupName)
+ if err != nil{
+ err = fmt.Errorf("%w; Couldnt retrieve Group " + groupName + ": ", err)
+ return response, err
+ }
+
+ parentnode, err := functions.GetNodeByMacAddress(groupName, node.MacAddress)
+ if err != nil{
+ err = fmt.Errorf("%w; Couldnt Get Node " + node.MacAddress, err)
+ return response, err
+ }
+ if parentnode.IsPending {
+ response.IsPending = true
+ return response, err
+ }
+
+ grouplm := parentgroup.GroupLastModified
+ peerslm := parentgroup.NodesLastModified
+ peerlistlm := parentnode.LastPeerUpdate
+ parentnodelm := parentnode.LastModified
+ parentnodelastcheckin := parentnode.LastCheckIn
+
+ if parentnodelastcheckin < parentnodelm {
+ response.NeedConfigUpdate = true
+ }
+
+ if parentnodelm < grouplm {
+ response.NeedConfigUpdate = true
+ }
+
+ if peerlistlm < peerslm {
+ response.NeedPeerUpdate = true
+ }
+ /*
+ if postchanges {
+ parentnode, err = UpdateNode(node, parentnode)
+ if err != nil{
+ err = fmt.Errorf("%w; Couldnt Update Node: ", err)
+ return response, err
+ } else {
+ response.NodeUpdated = true
+ }
+ }
+ */
+ err = TimestampNode(parentnode, true, false, false)
+
+ if err != nil{
+ err = fmt.Errorf("%w; Couldnt Timestamp Node: ", err)
+ return response, err
+ }
+ response.Success = true
+
+ return response, err
+}
+
+func SetGroupNodesLastModified(groupName string) error {
+
+ timestamp := time.Now().Unix()
+
+ collection := mongoconn.Client.Database("wirecat").Collection("groups")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ // Create filter
+ filter := bson.M{"nameid": groupName}
+
+ // prepare update model.
+ update := bson.D{
+ {"$set", bson.D{
+ {"nodeslastmodified", timestamp},
+ }},
+ }
+
+ result := collection.FindOneAndUpdate(ctx, filter, update)
+
+ defer cancel()
+
+ if result.Err() != nil {
+ return result.Err()
+ }
+
+ return nil
+}
+
+func TimestampNode(node models.Node, updatecheckin bool, updatepeers bool, updatelm bool) error{
+ if updatelm {
+ node.SetLastModified()
+ }
+ if updatecheckin {
+ node.SetLastCheckIn()
+ }
+ if updatepeers {
+ node.SetLastPeerUpdate()
+ }
+
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ // Create filter
+ filter := bson.M{"macaddress": node.MacAddress}
+
+ // prepare update model.
+ update := bson.D{
+ {"$set", bson.D{
+ {"lastmodified", node.LastModified},
+ {"lastpeerupdate", node.LastPeerUpdate},
+ {"lastcheckin", node.LastCheckIn},
+ }},
+ }
+
+ var nodeupdate models.Node
+ err := collection.FindOneAndUpdate(ctx, filter, update).Decode(&nodeupdate)
+ defer cancel()
+
+ return err
+}
+
+
diff --git a/controllers/controller.go b/controllers/controller.go
new file mode 100644
index 000000000..bcf283789
--- /dev/null
+++ b/controllers/controller.go
@@ -0,0 +1,63 @@
+package controller
+
+import (
+ "github.com/gravitl/netmaker/mongoconn"
+ "os/signal"
+ "os"
+ "fmt"
+ "context"
+ "net/http"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/handlers"
+ "sync"
+ "github.com/gravitl/netmaker/config"
+)
+
+
+func HandleRESTRequests(wg *sync.WaitGroup) {
+ defer wg.Done()
+
+ r := mux.NewRouter()
+
+ // Currently allowed dev origin is all. Should change in prod
+ // should consider analyzing the allowed methods further
+ headersOk := handlers.AllowedHeaders([]string{"Access-Control-Allow-Origin", "X-Requested-With", "Content-Type", "authorization"})
+ originsOk := handlers.AllowedOrigins([]string{config.Config.Server.AllowedOrigin})
+ methodsOk := handlers.AllowedMethods([]string{"GET", "PUT", "POST", "DELETE"})
+
+ nodeHandlers(r)
+ userHandlers(r)
+ groupHandlers(r)
+ fileHandlers(r)
+
+ port := config.Config.Server.ApiPort
+ if os.Getenv("API_PORT") != "" {
+ port = os.Getenv("API_PORT")
+ }
+
+ srv := &http.Server{Addr: ":" + port, Handler: handlers.CORS(originsOk, headersOk, methodsOk)(r)}
+ go func(){
+ err := srv.ListenAndServe()
+ //err := http.ListenAndServe(":" + port,
+ //handlers.CORS(originsOk, headersOk, methodsOk)(r))
+ if err != nil {
+ fmt.Println(err)
+ }
+ }()
+ fmt.Println("REST Server succesfully started on port " + port + " (REST)")
+ c := make(chan os.Signal)
+
+ // Relay os.Interrupt to our channel (os.Interrupt = CTRL+C)
+ // Ignore other incoming signals
+ signal.Notify(c, os.Interrupt)
+
+ // Block main routine until a signal is received
+ // As long as user doesn't press CTRL+C a message is not passed and our main routine keeps running
+ <-c
+
+ // After receiving CTRL+C Properly stop the server
+ fmt.Println("Stopping the REST server...")
+ srv.Shutdown(context.TODO())
+ fmt.Println("REST Server closed.")
+ mongoconn.Client.Disconnect(context.TODO())
+}
diff --git a/controllers/fileHttpController.go b/controllers/fileHttpController.go
new file mode 100644
index 000000000..97fead1f2
--- /dev/null
+++ b/controllers/fileHttpController.go
@@ -0,0 +1,11 @@
+package controller
+
+import (
+ "net/http"
+ "github.com/gorilla/mux"
+)
+
+
+func fileHandlers(r *mux.Router) {
+ r.PathPrefix("/meshclient/files").Handler(http.StripPrefix("/meshclient/files", http.FileServer(http.Dir("./meshclient/files"))))
+}
diff --git a/controllers/groupHttpController.go b/controllers/groupHttpController.go
new file mode 100644
index 000000000..df85d3a49
--- /dev/null
+++ b/controllers/groupHttpController.go
@@ -0,0 +1,574 @@
+package controller
+
+import (
+ "gopkg.in/go-playground/validator.v9"
+ "github.com/gravitl/netmaker/models"
+ "github.com/gravitl/netmaker/functions"
+ "github.com/gravitl/netmaker/mongoconn"
+ "time"
+ "strings"
+ "fmt"
+ "context"
+ "encoding/json"
+ "net/http"
+ "github.com/gorilla/mux"
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/mongo/options"
+ "github.com/gravitl/netmaker/config"
+)
+
+func groupHandlers(r *mux.Router) {
+ r.HandleFunc("/api/groups", securityCheck(http.HandlerFunc(getGroups))).Methods("GET")
+ r.HandleFunc("/api/groups", securityCheck(http.HandlerFunc(createGroup))).Methods("POST")
+ r.HandleFunc("/api/groups/{groupname}", securityCheck(http.HandlerFunc(getGroup))).Methods("GET")
+ r.HandleFunc("/api/groups/{groupname}/numnodes", securityCheck(http.HandlerFunc(getGroupNodeNumber))).Methods("GET")
+ r.HandleFunc("/api/groups/{groupname}", securityCheck(http.HandlerFunc(updateGroup))).Methods("PUT")
+ r.HandleFunc("/api/groups/{groupname}", securityCheck(http.HandlerFunc(deleteGroup))).Methods("DELETE")
+ r.HandleFunc("/api/groups/{groupname}/keys", securityCheck(http.HandlerFunc(createAccessKey))).Methods("POST")
+ r.HandleFunc("/api/groups/{groupname}/keys", securityCheck(http.HandlerFunc(getAccessKeys))).Methods("GET")
+ r.HandleFunc("/api/groups/{groupname}/keys/{name}", securityCheck(http.HandlerFunc(deleteAccessKey))).Methods("DELETE")
+}
+
+//Security check is middleware for every function and just checks to make sure that its the master calling
+//Only admin should have access to all these group-level actions
+//or maybe some Users once implemented
+func securityCheck(next http.Handler) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ var errorResponse = models.ErrorResponse{
+ Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
+ }
+
+ var params = mux.Vars(r)
+ hasgroup := params["groupname"] != ""
+ groupexists, _ := functions.GroupExists(params["groupname"])
+ if hasgroup && !groupexists {
+ errorResponse = models.ErrorResponse{
+ Code: http.StatusNotFound, Message: "W1R3: This group does not exist.",
+ }
+ returnErrorResponse(w, r, errorResponse)
+ } else {
+
+ bearerToken := r.Header.Get("Authorization")
+
+ var hasBearer = true
+ var tokenSplit = strings.Split(bearerToken, " ")
+ var authToken = ""
+
+ if len(tokenSplit) < 2 {
+ hasBearer = false
+ } else {
+ authToken = tokenSplit[1]
+ }
+ //all endpoints here require master so not as complicated
+ //still might not be a good way of doing this
+ if !hasBearer || !authenticateMaster(authToken) {
+ errorResponse = models.ErrorResponse{
+ Code: http.StatusUnauthorized, Message: "W1R3: You are unauthorized to access this endpoint.",
+ }
+ returnErrorResponse(w, r, errorResponse)
+ } else {
+ next.ServeHTTP(w, r)
+ }
+ }
+ }
+}
+//Consider a more secure way of setting master key
+func authenticateMaster(tokenString string) bool {
+ if tokenString == config.Config.Server.MasterKey {
+ return true
+ }
+ return false
+}
+
+//simple get all groups function
+func getGroups(w http.ResponseWriter, r *http.Request) {
+
+ //depends on list groups function
+ //TODO: This is perhaps a more efficient way of handling ALL http handlers
+ //Take their primary logic and put in a separate function
+ //May be better since most http handler functionality is needed internally cross-method
+ //E.G. a method may need to check against all groups. But it cant call this function. That's why there's ListGroups
+ groups := functions.ListGroups()
+
+ json.NewEncoder(w).Encode(groups)
+
+}
+
+func validateGroup(operation string, group models.Group) error {
+
+ v := validator.New()
+
+ _ = v.RegisterValidation("addressrange_valid", func(fl validator.FieldLevel) bool {
+ isvalid := functions.IsIpv4CIDR(fl.Field().String())
+ return isvalid
+ })
+
+ _ = v.RegisterValidation("nameid_valid", func(fl validator.FieldLevel) bool {
+ isFieldUnique := operation == "update" || functions.IsGroupNameUnique(fl.Field().String())
+ inGroupCharSet := functions.NameInGroupCharSet(fl.Field().String())
+ return isFieldUnique && inGroupCharSet
+ })
+
+ _ = v.RegisterValidation("displayname_unique", func(fl validator.FieldLevel) bool {
+ isFieldUnique := functions.IsGroupDisplayNameUnique(fl.Field().String())
+ return isFieldUnique || operation == "update"
+ })
+
+ err := v.Struct(group)
+
+ if err != nil {
+ for _, e := range err.(validator.ValidationErrors) {
+ fmt.Println(e)
+ }
+ }
+ return err
+}
+
+//Get number of nodes associated with a group
+//May not be necessary, but I think the front end needs it? This should be reviewed after iteration 1
+func getGroupNodeNumber(w http.ResponseWriter, r *http.Request) {
+
+ var params = mux.Vars(r)
+
+ count, err := GetGroupNodeNumber(params["groupname"])
+
+ if err != nil {
+ var errorResponse = models.ErrorResponse{
+ Code: http.StatusInternalServerError, Message: "W1R3: Error retrieving nodes.",
+ }
+ returnErrorResponse(w, r, errorResponse)
+ } else {
+ json.NewEncoder(w).Encode(count)
+ }
+}
+
+//This is haphazard
+//I need a better folder structure
+//maybe a functions/ folder and then a node.go, group.go, keys.go, misc.go
+func GetGroupNodeNumber(groupName string) (int, error){
+
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ filter := bson.M{"group": groupName}
+ count, err := collection.CountDocuments(ctx, filter)
+ returncount := int(count)
+
+ //not sure if this is the right way of handling this error...
+ if err != nil {
+ return 9999, err
+ }
+
+ defer cancel()
+
+ return returncount, err
+}
+
+//Simple get group function
+func getGroup(w http.ResponseWriter, r *http.Request) {
+
+ // set header.
+ w.Header().Set("Content-Type", "application/json")
+
+ var params = mux.Vars(r)
+
+ var group models.Group
+
+ collection := mongoconn.Client.Database("wirecat").Collection("groups")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ filter := bson.M{"nameid": params["groupname"]}
+ err := collection.FindOne(ctx, filter, options.FindOne().SetProjection(bson.M{"_id": 0})).Decode(&group)
+
+ defer cancel()
+
+ if err != nil {
+ mongoconn.GetError(err, w)
+ return
+ }
+
+ json.NewEncoder(w).Encode(group)
+}
+
+//Update a group
+func updateGroup(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json")
+
+ var params = mux.Vars(r)
+
+ var group models.Group
+
+ group, err := functions.GetParentGroup(params["groupname"])
+ if err != nil {
+ return
+ }
+
+ var groupChange models.Group
+
+ haschange := false
+ hasrangeupdate := false
+
+ _ = json.NewDecoder(r.Body).Decode(&groupChange)
+
+ if groupChange.AddressRange == "" {
+ groupChange.AddressRange = group.AddressRange
+ }
+ if groupChange.NameID == "" {
+ groupChange.NameID = group.NameID
+ }
+
+
+ err = validateGroup("update", groupChange)
+ if err != nil {
+ return
+ }
+
+
+ //TODO: group.Name is not update-able
+ //group.Name acts as the ID for the group and keeps it unique and searchable by nodes
+ //should consider renaming to group.ID
+ //Too lazy for now.
+ //DisplayName is the editable version and will not be used for node searches,
+ //but will be used by front end.
+
+ if groupChange.AddressRange != "" {
+
+ group.AddressRange = groupChange.AddressRange
+
+ var isAddressOK bool = functions.IsIpv4CIDR(groupChange.AddressRange)
+ if !isAddressOK {
+ return
+ }
+ haschange = true
+ hasrangeupdate = true
+
+ }
+
+ if groupChange.DefaultListenPort != 0 {
+ group.DefaultListenPort = groupChange.DefaultListenPort
+ haschange = true
+ }
+ if groupChange.DefaultPreUp != "" {
+ group.DefaultPreUp = groupChange.DefaultPreUp
+ haschange = true
+ }
+ if groupChange.DefaultInterface != "" {
+ group.DefaultInterface = groupChange.DefaultInterface
+ haschange = true
+ }
+ if groupChange.DefaultPostUp != "" {
+ group.DefaultPostUp = groupChange.DefaultPostUp
+ haschange = true
+ }
+ if groupChange.DefaultKeepalive != 0 {
+ group.DefaultKeepalive = groupChange.DefaultKeepalive
+ haschange = true
+ }
+ if groupChange.DisplayName != "" {
+ group.DisplayName = groupChange.DisplayName
+ haschange = true
+ }
+ if groupChange.DefaultCheckInInterval != 0 {
+ group.DefaultCheckInInterval = groupChange.DefaultCheckInInterval
+ haschange = true
+ }
+
+ //TODO: Important. This doesn't work. This will create cases where we will
+ //unintentionally go from allowing manual signup to disallowing
+ //need to find a smarter way
+ //maybe make into a text field
+ if groupChange.AllowManualSignUp != group.AllowManualSignUp {
+ group.AllowManualSignUp = groupChange.AllowManualSignUp
+ haschange = true
+ }
+
+ collection := mongoconn.Client.Database("wirecat").Collection("groups")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ filter := bson.M{"nameid": params["groupname"]}
+
+ if haschange {
+ group.SetGroupLastModified()
+ }
+
+ // prepare update model.
+ update := bson.D{
+ {"$set", bson.D{
+ {"addressrange", group.AddressRange},
+ {"displayname", group.DisplayName},
+ {"defaultlistenport", group.DefaultListenPort},
+ {"defaultpostup", group.DefaultPostUp},
+ {"defaultpreup", group.DefaultPreUp},
+ {"defaultkeepalive", group.DefaultKeepalive},
+ {"defaultsaveconfig", group.DefaultSaveConfig},
+ {"defaultinterface", group.DefaultInterface},
+ {"nodeslastmodified", group.NodesLastModified},
+ {"grouplastmodified", group.GroupLastModified},
+ {"allowmanualsignup", group.AllowManualSignUp},
+ {"defaultcheckininterval", group.DefaultCheckInInterval},
+ }},
+ }
+
+ errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&group)
+
+ defer cancel()
+
+ if errN != nil {
+ mongoconn.GetError(errN, w)
+ fmt.Println(errN)
+ return
+ }
+
+ //Cycles through nodes and gives them new IP's based on the new range
+ //Pretty cool, but also pretty inefficient currently
+ if hasrangeupdate {
+ _ = functions.UpdateGroupNodeAddresses(params["groupname"])
+ //json.NewEncoder(w).Encode(errG)
+ }
+ json.NewEncoder(w).Encode(group)
+}
+
+//Delete a group
+//Will stop you if there's any nodes associated
+func deleteGroup(w http.ResponseWriter, r *http.Request) {
+ // Set header
+ w.Header().Set("Content-Type", "application/json")
+
+ var params = mux.Vars(r)
+
+ var errorResponse = models.ErrorResponse{
+ Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
+ }
+
+ nodecount, err := GetGroupNodeNumber(params["groupname"])
+
+ //we dont wanna leave nodes hanging. They need a group!
+ if nodecount > 0 || err != nil {
+ errorResponse = models.ErrorResponse{
+ Code: http.StatusForbidden, Message: "W1R3: Node check failed. All nodes must be deleted before deleting group.",
+ }
+ returnErrorResponse(w, r, errorResponse)
+ return
+ }
+
+ collection := mongoconn.Client.Database("wirecat").Collection("groups")
+
+ filter := bson.M{"nameid": params["groupname"]}
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ deleteResult, err := collection.DeleteOne(ctx, filter)
+
+ defer cancel()
+
+ if err != nil {
+ mongoconn.GetError(err, w)
+ return
+ }
+
+ json.NewEncoder(w).Encode(deleteResult)
+
+}
+
+//Create a group
+//Pretty simple
+func createGroup(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json")
+
+ //TODO:
+ //This may be needed to get error response. May be why some errors dont work
+ //analyze different error responses and see what needs to be done
+ //commenting out for now
+ /*
+ var errorResponse = models.ErrorResponse{
+ Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
+ }
+ */
+ var group models.Group
+
+ // we decode our body request params
+ _ = json.NewDecoder(r.Body).Decode(&group)
+
+ //TODO: Not really doing good validation here. Same as createNode, updateNode, and updateGroup
+ //Need to implement some better validation across the board
+ err := validateGroup("create", group)
+ if err != nil {
+ return
+ }
+
+ group.SetDefaults()
+ group.SetNodesLastModified()
+ group.SetGroupLastModified()
+
+
+ collection := mongoconn.Client.Database("wirecat").Collection("groups")
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+
+ // insert our group into the group table
+ result, err := collection.InsertOne(ctx, group)
+ _ = result
+
+ defer cancel()
+
+ if err != nil {
+ mongoconn.GetError(err, w)
+ return
+ }
+}
+
+// BEGIN KEY MANAGEMENT SECTION
+// Consider a separate file for these controllers but I think same file is fine for now
+
+
+//TODO: Very little error handling
+//accesskey is created as a json string inside the Group collection item in mongo
+func createAccessKey(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json")
+
+ var params = mux.Vars(r)
+
+ var group models.Group
+ var accesskey models.AccessKey
+
+ //start here
+ group, err := functions.GetParentGroup(params["groupname"])
+ if err != nil {
+ return
+ }
+
+ _ = json.NewDecoder(r.Body).Decode(&accesskey)
+
+ if accesskey.Name == "" {
+ accesskey.Name = functions.GenKeyName()
+ }
+ if accesskey.Value == "" {
+ accesskey.Value = functions.GenKey()
+ }
+ if accesskey.Uses == 0 {
+ accesskey.Uses = 1
+ }
+
+
+ group.AccessKeys = append(group.AccessKeys, accesskey)
+
+ collection := mongoconn.Client.Database("wirecat").Collection("groups")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ // Create filter
+ filter := bson.M{"nameid": params["groupname"]}
+
+ // Read update model from body request
+ fmt.Println("Adding key to " + 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 {
+ mongoconn.GetError(errN, w)
+ return
+ }
+ w.Write([]byte(accesskey.Value))
+}
+
+//pretty simple get
+func getAccessKeys(w http.ResponseWriter, r *http.Request) {
+
+ // set header.
+ w.Header().Set("Content-Type", "application/json")
+
+ var params = mux.Vars(r)
+
+ var group models.Group
+ var keys []models.DisplayKey
+
+ collection := mongoconn.Client.Database("wirecat").Collection("groups")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ filter := bson.M{"nameid": params["groupname"]}
+ err := collection.FindOne(ctx, filter, options.FindOne().SetProjection(bson.M{"_id": 0})).Decode(&group)
+
+ defer cancel()
+
+ if err != nil {
+ mongoconn.GetError(err, w)
+ return
+ }
+ keydata, keyerr := json.Marshal(group.AccessKeys)
+
+ if keyerr != nil {
+ return
+ }
+
+ json.Unmarshal(keydata, &keys)
+
+ //json.NewEncoder(w).Encode(group.AccessKeys)
+ json.NewEncoder(w).Encode(keys)
+}
+
+//delete key. Has to do a little funky logic since it's not a collection item
+func deleteAccessKey(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json")
+
+ var params = mux.Vars(r)
+
+ var group models.Group
+ keyname := params["name"]
+
+ //start here
+ group, err := functions.GetParentGroup(params["groupname"])
+ if err != nil {
+ return
+ }
+
+ //basically, turn the list of access keys into the list of access keys before and after the item
+ //have not done any error handling for if there's like...1 item. I think it works? need to test.
+ for i := len(group.AccessKeys) - 1; i >= 0; i-- {
+
+ currentkey:= group.AccessKeys[i]
+ if currentkey.Name == keyname {
+ group.AccessKeys = append(group.AccessKeys[:i],
+ group.AccessKeys[i+1:]...)
+ }
+ }
+
+ collection := mongoconn.Client.Database("wirecat").Collection("groups")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ // Create filter
+ filter := bson.M{"nameid": params["groupname"]}
+
+ // 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 {
+ mongoconn.GetError(errN, w)
+ return
+ }
+}
diff --git a/controllers/nodeGrpcController.go b/controllers/nodeGrpcController.go
new file mode 100644
index 000000000..025c9ccf9
--- /dev/null
+++ b/controllers/nodeGrpcController.go
@@ -0,0 +1,324 @@
+package controller
+
+import (
+// "github.com/davecgh/go-spew/spew"
+ "context"
+ "fmt"
+// "errors"
+// "time"
+// "go.mongodb.org/mongo-driver/bson"
+// "golang.org/x/crypto/bcrypt"
+ nodepb "github.com/gravitl/netmaker/grpc"
+ "github.com/gravitl/netmaker/models"
+ "github.com/gravitl/netmaker/functions"
+// "github.com/gravitl/netmaker/mongoconn"
+ "go.mongodb.org/mongo-driver/mongo"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+)
+
+type NodeServiceServer struct {
+ NodeDB *mongo.Collection
+ nodepb.UnimplementedNodeServiceServer
+
+}
+func (s *NodeServiceServer) ReadNode(ctx context.Context, req *nodepb.ReadNodeReq) (*nodepb.ReadNodeRes, error) {
+ // convert string id (from proto) to mongoDB ObjectId
+ macaddress := req.GetMacaddress()
+ groupName := req.GetGroup()
+
+ node, err := GetNode(macaddress, groupName)
+
+ if err != nil {
+ return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("Something went wrong: %v", err))
+ }
+
+ /*
+ if node == nil {
+ return nil, status.Errorf(codes.NotFound, fmt.Sprintf("Could not find node with Mac Address %s: %v", req.GetMacaddress(), err))
+ }
+ */
+ // Cast to ReadNodeRes type
+ response := &nodepb.ReadNodeRes{
+ Node: &nodepb.Node{
+ Macaddress: node.MacAddress,
+ Name: node.Name,
+ Address: node.Address,
+ Endpoint: node.Endpoint,
+ Password: node.Password,
+ Nodegroup: node.Group,
+ Interface: node.Interface,
+ Localaddress: node.LocalAddress,
+ Preup: node.PreUp,
+ Postup: node.PostUp,
+ Checkininterval: node.CheckInInterval,
+ Ispending: node.IsPending,
+ Publickey: node.PublicKey,
+ Listenport: node.ListenPort,
+ Keepalive: node.PersistentKeepalive,
+ },
+ }
+ return response, nil
+}
+
+func (s *NodeServiceServer) CreateNode(ctx context.Context, req *nodepb.CreateNodeReq) (*nodepb.CreateNodeRes, error) {
+ // Get the protobuf node type from the protobuf request type
+ // Essentially doing req.Node to access the struct with a nil check
+ data := req.GetNode()
+ // Now we have to convert this into a NodeItem type to convert into BSON
+ node := models.Node{
+ // ID: primitive.NilObjectID,
+ MacAddress: data.GetMacaddress(),
+ LocalAddress: data.GetLocaladdress(),
+ Name: data.GetName(),
+ Address: data.GetAddress(),
+ AccessKey: data.GetAccesskey(),
+ Endpoint: data.GetEndpoint(),
+ PersistentKeepalive: data.GetKeepalive(),
+ Password: data.GetPassword(),
+ Interface: data.GetInterface(),
+ Group: data.GetNodegroup(),
+ IsPending: data.GetIspending(),
+ PublicKey: data.GetPublickey(),
+ ListenPort: data.GetListenport(),
+ }
+
+ err := ValidateNode("create", node.Group, node)
+
+ if err != nil {
+ // return internal gRPC error to be handled later
+ return nil, err
+ }
+
+ //Check to see if key is valid
+ //TODO: Triple inefficient!!! This is the third call to the DB we make for groups
+ validKey := functions.IsKeyValid(node.Group, node.AccessKey)
+
+ if !validKey {
+ group, _ := functions.GetParentGroup(node.Group)
+ //Check to see if group will allow manual sign up
+ //may want to switch this up with the valid key check and avoid a DB call that way.
+ if *group.AllowManualSignUp {
+ node.IsPending = true
+ } else {
+ return nil, status.Errorf(
+ codes.Internal,
+ fmt.Sprintf("Invalid key, and group does not allow no-key signups"),
+ )
+ }
+ }
+
+ node, err = CreateNode(node, node.Group)
+
+ if err != nil {
+ // return internal gRPC error to be handled later
+ return nil, status.Errorf(
+ codes.Internal,
+ fmt.Sprintf("Internal error: %v", err),
+ )
+ }
+ // return the node in a CreateNodeRes type
+ response := &nodepb.CreateNodeRes{
+ Node: &nodepb.Node{
+ Macaddress: node.MacAddress,
+ Localaddress: node.LocalAddress,
+ Name: node.Name,
+ Address: node.Address,
+ Endpoint: node.Endpoint,
+ Password: node.Password,
+ Interface: node.Interface,
+ Nodegroup: node.Group,
+ Ispending: node.IsPending,
+ Publickey: node.PublicKey,
+ Listenport: node.ListenPort,
+ Keepalive: node.PersistentKeepalive,
+ },
+ }
+ err = SetGroupNodesLastModified(node.Group)
+ if err != nil {
+ return nil, status.Errorf(codes.NotFound, fmt.Sprintf("Could not update group last modified date: %v", err))
+ }
+
+ return response, nil
+}
+
+func (s *NodeServiceServer) CheckIn(ctx context.Context, req *nodepb.CheckInReq) (*nodepb.CheckInRes, error) {
+ // Get the protobuf node type from the protobuf request type
+ // Essentially doing req.Node to access the struct with a nil check
+ data := req.GetNode()
+ //postchanges := req.GetPostchanges()
+ // Now we have to convert this into a NodeItem type to convert into BSON
+ node := models.Node{
+ // ID: primitive.NilObjectID,
+ MacAddress: data.GetMacaddress(),
+ Address: data.GetAddress(),
+ Endpoint: data.GetEndpoint(),
+ Group: data.GetNodegroup(),
+ Password: data.GetPassword(),
+ LocalAddress: data.GetLocaladdress(),
+ ListenPort: data.GetListenport(),
+ PersistentKeepalive: data.GetKeepalive(),
+ PublicKey: data.GetPublickey(),
+ }
+
+ checkinresponse, err := NodeCheckIn(node, node.Group)
+
+ if err != nil {
+ err = fmt.Errorf("%w; Couldnt Check in", err)
+ // return internal gRPC error to be handled later
+ return nil, status.Errorf(
+ codes.Internal,
+ fmt.Sprintf("Internal error: %v", err),
+ )
+ }
+ // return the node in a CreateNodeRes type
+ response := &nodepb.CheckInRes{
+ Checkinresponse: &nodepb.CheckInResponse{
+ Success: checkinresponse.Success,
+ Needpeerupdate: checkinresponse.NeedPeerUpdate,
+ Needconfigupdate: checkinresponse.NeedConfigUpdate,
+ Nodemessage: checkinresponse.NodeMessage,
+ Ispending: checkinresponse.IsPending,
+ },
+ }
+ return response, nil
+}
+
+
+func (s *NodeServiceServer) UpdateNode(ctx context.Context, req *nodepb.UpdateNodeReq) (*nodepb.UpdateNodeRes, error) {
+ // Get the node data from the request
+ data := req.GetNode()
+ // Now we have to convert this into a NodeItem type to convert into BSON
+ nodechange := models.Node{
+ // ID: primitive.NilObjectID,
+ MacAddress: data.GetMacaddress(),
+ Name: data.GetName(),
+ Address: data.GetAddress(),
+ LocalAddress: data.GetLocaladdress(),
+ Endpoint: data.GetEndpoint(),
+ Password: data.GetPassword(),
+ PersistentKeepalive: data.GetKeepalive(),
+ Group: data.GetNodegroup(),
+ Interface: data.GetInterface(),
+ PreUp: data.GetPreup(),
+ PostUp: data.GetPostup(),
+ IsPending: data.GetIspending(),
+ PublicKey: data.GetPublickey(),
+ ListenPort: data.GetListenport(),
+ }
+
+
+ // Convert the Id string to a MongoDB ObjectId
+ macaddress := nodechange.MacAddress
+ groupName := nodechange.Group
+
+ err := ValidateNode("update", groupName, nodechange)
+ if err != nil {
+ return nil, err
+ }
+
+ node, err := functions.GetNodeByMacAddress(groupName, macaddress)
+ if err != nil {
+ return nil, status.Errorf(
+ codes.NotFound,
+ fmt.Sprintf("Could not find node with supplied Mac Address: %v", err),
+ )
+ }
+
+
+ newnode, err := UpdateNode(nodechange, node)
+
+ if err != nil {
+ return nil, status.Errorf(
+ codes.NotFound,
+ fmt.Sprintf("Could not find node with supplied Mac Address: %v", err),
+ )
+ }
+ return &nodepb.UpdateNodeRes{
+ Node: &nodepb.Node{
+ Macaddress: newnode.MacAddress,
+ Localaddress: newnode.LocalAddress,
+ Name: newnode.Name,
+ Address: newnode.Address,
+ Endpoint: newnode.Endpoint,
+ Password: newnode.Password,
+ Interface: newnode.Interface,
+ Preup: newnode.PreUp,
+ Postup: newnode.PostUp,
+ Nodegroup: newnode.Group,
+ Ispending: newnode.IsPending,
+ Publickey: newnode.PublicKey,
+ Listenport: newnode.ListenPort,
+ Keepalive: newnode.PersistentKeepalive,
+
+ },
+ }, nil
+}
+
+func (s *NodeServiceServer) DeleteNode(ctx context.Context, req *nodepb.DeleteNodeReq) (*nodepb.DeleteNodeRes, error) {
+ fmt.Println("beginning node delete")
+ macaddress := req.GetMacaddress()
+ group := req.GetGroupName()
+
+ success, err := DeleteNode(macaddress, group)
+
+ if err != nil || !success {
+ fmt.Println("Error deleting node.")
+ fmt.Println(err)
+ return nil, status.Errorf(codes.NotFound, fmt.Sprintf("Could not find/delete node with mac address %s", macaddress))
+ }
+
+ fmt.Println("updating group last modified of" + req.GetGroupName())
+ err = SetGroupNodesLastModified(req.GetGroupName())
+ if err != nil {
+ fmt.Println("Error updating Group")
+ fmt.Println(err)
+ return nil, status.Errorf(codes.NotFound, fmt.Sprintf("Could not update group last modified date: %v", err))
+ }
+
+
+ return &nodepb.DeleteNodeRes{
+ Success: true,
+ }, nil
+}
+
+func (s *NodeServiceServer) GetPeers(req *nodepb.GetPeersReq, stream nodepb.NodeService_GetPeersServer) error {
+ // Initiate a NodeItem type to write decoded data to
+ //data := &models.PeersResponse{}
+ // collection.Find returns a cursor for our (empty) query
+ //cursor, err := s.NodeDB.Find(context.Background(), bson.M{})
+ peers, err := GetPeersList(req.GetGroup())
+
+ if err != nil {
+ return status.Errorf(codes.Internal, fmt.Sprintf("Unknown internal error: %v", err))
+ }
+ // cursor.Next() returns a boolean, if false there are no more items and loop will break
+ for i := 0; i < len(peers); i++ {
+
+ // If no error is found send node over stream
+ stream.Send(&nodepb.GetPeersRes{
+ Peers: &nodepb.PeersResponse{
+ Address: peers[i].Address,
+ Endpoint: peers[i].Endpoint,
+ Publickey: peers[i].PublicKey,
+ Keepalive: peers[i].KeepAlive,
+ Listenport: peers[i].ListenPort,
+ Localaddress: peers[i].LocalAddress,
+ },
+ })
+ }
+
+ node, err := functions.GetNodeByMacAddress(req.GetGroup(), req.GetMacaddress())
+ if err != nil {
+ return status.Errorf(codes.Internal, fmt.Sprintf("Could not get node: %v", err))
+ }
+
+
+ err = TimestampNode(node, false, true, false)
+ if err != nil {
+ return status.Errorf(codes.Internal, fmt.Sprintf("Internal error occurred: %v", err))
+ }
+
+
+ return nil
+}
diff --git a/controllers/nodeHttpController.go b/controllers/nodeHttpController.go
new file mode 100644
index 000000000..0474578c5
--- /dev/null
+++ b/controllers/nodeHttpController.go
@@ -0,0 +1,657 @@
+package controller
+
+import (
+ "github.com/gravitl/netmaker/models"
+ "github.com/gravitl/netmaker/functions"
+ "github.com/gravitl/netmaker/mongoconn"
+ "golang.org/x/crypto/bcrypt"
+ "time"
+ "strings"
+ "fmt"
+ "context"
+ "encoding/json"
+ "log"
+ "net/http"
+ "github.com/gorilla/mux"
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/mongo/options"
+)
+
+
+func nodeHandlers(r *mux.Router) {
+
+ r.HandleFunc("/api/{group}/nodes", authorize(true, "group", http.HandlerFunc(getGroupNodes))).Methods("GET")
+ r.HandleFunc("/api/nodes", authorize(false, "master", http.HandlerFunc(getAllNodes))).Methods("GET")
+ r.HandleFunc("/api/{group}/peerlist", authorize(true, "group", http.HandlerFunc(getPeerList))).Methods("GET")
+ r.HandleFunc("/api/{group}/lastmodified", authorize(true, "group", http.HandlerFunc(getLastModified))).Methods("GET")
+ r.HandleFunc("/api/{group}/nodes/{macaddress}", authorize(true, "node", http.HandlerFunc(getNode))).Methods("GET")
+ r.HandleFunc("/api/{group}/nodes", createNode).Methods("POST")
+ r.HandleFunc("/api/{group}/nodes/{macaddress}", authorize(true, "node", http.HandlerFunc(updateNode))).Methods("PUT")
+ r.HandleFunc("/api/{group}/nodes/{macaddress}/checkin", authorize(true, "node", http.HandlerFunc(checkIn))).Methods("POST")
+ r.HandleFunc("/api/{group}/nodes/{macaddress}/uncordon", authorize(true, "master", http.HandlerFunc(uncordonNode))).Methods("POST")
+ r.HandleFunc("/api/{group}/nodes/{macaddress}", authorize(true, "node", http.HandlerFunc(deleteNode))).Methods("DELETE")
+ r.HandleFunc("/api/{group}/authenticate", authenticate).Methods("POST")
+
+}
+
+//Node authenticates using its password and retrieves a JWT for authorization.
+func authenticate(response http.ResponseWriter, request *http.Request) {
+
+
+ //Auth request consists of Mac Address and Password (from node that is authorizing
+ //in case of Master, auth is ignored and mac is set to "mastermac"
+ var authRequest models.AuthParams
+ var result models.Node
+ var errorResponse = models.ErrorResponse{
+ Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
+ }
+
+ //Get password fnd mac rom request
+ decoder := json.NewDecoder(request.Body)
+ decoderErr := decoder.Decode(&authRequest)
+ defer request.Body.Close()
+
+ if decoderErr != nil {
+ returnErrorResponse(response, request, errorResponse)
+ } else {
+ errorResponse.Code = http.StatusBadRequest
+ if authRequest.MacAddress == "" {
+ errorResponse.Message = "W1R3: MacAddress can't be empty"
+ returnErrorResponse(response, request, errorResponse)
+ } else if authRequest.Password == "" {
+ errorResponse.Message = "W1R3: Password can't be empty"
+ returnErrorResponse(response, request, errorResponse)
+ } else {
+
+ //Search DB for node with Mac Address. Ignore pending nodes (they should not be able to authenticate with API untill approved).
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ var err = collection.FindOne(ctx, bson.M{ "macaddress": authRequest.MacAddress, "ispending": false }).Decode(&result)
+
+ defer cancel()
+
+ if err != nil {
+ returnErrorResponse(response, request, errorResponse)
+ }
+
+ //compare password from request to stored password in database
+ //might be able to have a common hash (certificates?) and compare those so that a password isn't passed in in plain text...
+ //TODO: Consider a way of hashing the password client side before sending, or using certificates
+ err = bcrypt.CompareHashAndPassword([]byte(result.Password), []byte(authRequest.Password))
+ if err != nil {
+ returnErrorResponse(response, request, errorResponse)
+ } else {
+ //Create a new JWT for the node
+ tokenString, _ := functions.CreateJWT(authRequest.MacAddress, result.Group)
+
+ if tokenString == "" {
+ returnErrorResponse(response, request, errorResponse)
+ }
+
+ var successResponse = models.SuccessResponse{
+ Code: http.StatusOK,
+ Message: "W1R3: Device " + authRequest.MacAddress + " Authorized",
+ Response: models.SuccessfulLoginResponse{
+ AuthToken: tokenString,
+ MacAddress: authRequest.MacAddress,
+ },
+ }
+ //Send back the JWT
+ successJSONResponse, jsonError := json.Marshal(successResponse)
+
+ if jsonError != nil {
+ returnErrorResponse(response, request, errorResponse)
+ }
+ response.Header().Set("Content-Type", "application/json")
+ response.Write(successJSONResponse)
+ }
+ }
+ }
+}
+
+//The middleware for most requests to the API
+//They all pass through here first
+//This will validate the JWT (or check for master token)
+//This will also check against the authGroup and make sure the node should be accessing that endpoint,
+//even if it's technically ok
+//This is kind of a poor man's RBAC. There's probably a better/smarter way.
+//TODO: Consider better RBAC implementations
+func authorize(groupCheck bool, authGroup string, next http.Handler) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ var errorResponse = models.ErrorResponse{
+ Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
+ }
+
+ var params = mux.Vars(r)
+
+ groupexists, _ := functions.GroupExists(params["group"])
+
+ //check that the request is for a valid group
+ //if (groupCheck && !groupexists) || err != nil {
+ if (groupCheck && !groupexists) {
+ errorResponse = models.ErrorResponse{
+ Code: http.StatusNotFound, Message: "W1R3: This group does not exist. ",
+ }
+ returnErrorResponse(w, r, errorResponse)
+
+ } else {
+
+ w.Header().Set("Content-Type", "application/json")
+
+ //get the auth token
+ bearerToken := r.Header.Get("Authorization")
+
+ var tokenSplit = strings.Split(bearerToken, " ")
+
+ //I put this in in case the user doesn't put in a token at all (in which case it's empty)
+ //There's probably a smarter way of handling this.
+ var authToken = "928rt238tghgwe@TY@$Y@#WQAEGB2FC#@HG#@$Hddd"
+
+ if len(tokenSplit) > 1 {
+ authToken = tokenSplit[1]
+ }
+
+ //This checks if
+ //A: the token is the master password
+ //B: the token corresponds to a mac address, and if so, which one
+ //TODO: There's probably a better way of dealing with the "master token"/master password. Plz Halp.
+ macaddress, _, err := functions.VerifyToken(authToken)
+
+ if err != nil {
+ return
+ }
+
+ var isAuthorized = false
+
+ //The mastermac (login with masterkey from config) can do everything!! May be dangerous.
+ if macaddress == "mastermac" {
+ isAuthorized = true
+
+ //for everyone else, there's poor man's RBAC. The "cases" are defined in the routes in the handlers
+ //So each route defines which access group should be allowed to access it
+ } else {
+ switch authGroup {
+ case "all":
+ isAuthorized = true
+ case "nodes":
+ isAuthorized = (macaddress != "")
+ case "group":
+ node, err := functions.GetNodeByMacAddress(params["group"], macaddress)
+ if err != nil {
+ return
+ }
+ isAuthorized = (node.Group == params["group"])
+ case "node":
+ isAuthorized = (macaddress == params["macaddress"])
+ case "master":
+ isAuthorized = (macaddress == "mastermac")
+ default:
+ isAuthorized = false
+ }
+ }
+ if !isAuthorized {
+ errorResponse = models.ErrorResponse{
+ Code: http.StatusUnauthorized, Message: "W1R3: You are unauthorized to access this endpoint.",
+ }
+ returnErrorResponse(w, r, errorResponse)
+ } else {
+ //If authorized, this function passes along it's request and output to the appropriate route function.
+ next.ServeHTTP(w, r)
+ }
+ }
+ }
+}
+
+//Returns a list of peers in "plaintext" format, which can be piped straight to a file (peers.conf) on a local machine
+//Not sure if it would be better to do that here or to let the client handle the formatting.
+//TODO: May want to consider a different approach
+func getPeerList(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ var nodes []models.Node
+ var params = mux.Vars(r)
+
+ //Connection mongoDB with mongoconn class
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ //Get all nodes in the relevant group which are NOT in pending state
+ filter := bson.M{"group": params["group"], "ispending": false}
+ cur, err := collection.Find(ctx, filter)
+
+ if err != nil {
+ mongoconn.GetError(err, w)
+ return
+ }
+
+ // Close the cursor once finished and cancel if it takes too long
+ defer cancel()
+
+ for cur.Next(context.TODO()) {
+
+ var node models.Node
+ err := cur.Decode(&node)
+ 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...
+ nodes = append(nodes, node)
+ }
+
+ //Uh oh, fatal error! This needs some better error handling
+ //TODO: needs appropriate error handling so the server doesnt shut down.
+ if err := cur.Err(); err != nil {
+ log.Fatal(err)
+ }
+
+ //Writes output in the style familiar to WireGuard
+ //Get's piped to peers.conf locally after client request
+ for _, n := range nodes {
+ w.Write([]byte("[Peer] \n"))
+ w.Write([]byte("PublicKey = " + n.PublicKey + "\n"))
+ w.Write([]byte("AllowedIPs = " + n.Address + "/32" + "\n"))
+ w.Write([]byte("PersistentKeepalive = " + fmt.Sprint(n.PersistentKeepalive) + "\n"))
+ w.Write([]byte("Endpoint = " + n.Endpoint + ":" + fmt.Sprint(n.ListenPort) + "\n\n"))
+ }
+}
+
+//Gets all nodes associated with group, including pending nodes
+func getGroupNodes(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json")
+
+ var nodes []models.ReturnNode
+ var params = mux.Vars(r)
+
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ filter := bson.M{"group": params["group"]}
+
+ //Filtering out the ID field cuz Dillon doesn't like it. May want to filter out other fields in the future
+ cur, err := collection.Find(ctx, filter, options.Find().SetProjection(bson.M{"_id": 0}))
+
+ if err != nil {
+ mongoconn.GetError(err, w)
+ return
+ }
+
+ defer cancel()
+
+ for cur.Next(context.TODO()) {
+
+ //Using a different model for the ReturnNode (other than regular node).
+ //Either we should do this for ALL structs (so Groups and Keys)
+ //OR we should just use the original struct
+ //My preference is to make some new return structs
+ //TODO: Think about this. Not an immediate concern. Just need to get some consistency eventually
+ var node models.ReturnNode
+
+ err := cur.Decode(&node)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // add item our array of nodes
+ nodes = append(nodes, node)
+ }
+
+ //TODO: Another fatal error we should take care of.
+ if err := cur.Err(); err != nil {
+ log.Fatal(err)
+ }
+
+ //Returns all the nodes in JSON format
+ json.NewEncoder(w).Encode(nodes)
+
+}
+
+//A separate function to get all nodes, not just nodes for a particular group.
+//Not quite sure if this is necessary. Probably necessary based on front end but may want to review after iteration 1 if it's being used or not
+func getAllNodes(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json")
+
+ var nodes []models.ReturnNode
+
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ // Filter out them ID's again
+ cur, err := collection.Find(ctx, bson.M{}, options.Find().SetProjection(bson.M{"_id": 0}))
+
+ if err != nil {
+ mongoconn.GetError(err, w)
+ return
+ }
+
+ defer cancel()
+
+ for cur.Next(context.TODO()) {
+
+ var node models.ReturnNode
+ err := cur.Decode(&node)
+
+ //TODO: Fatal error
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // add node to our array
+ nodes = append(nodes, node)
+ }
+
+ //TODO: Fatal error
+ if err := cur.Err(); err != nil {
+ log.Fatal(err)
+ }
+ //Return all the nodes in JSON format
+ json.NewEncoder(w).Encode(nodes)
+
+}
+
+//This function get's called when a node "checks in" at check in interval
+//Honestly I'm not sure what all it should be doing
+//TODO: Implement the necessary stuff, including the below
+//Check the last modified of the group
+//Check the last modified of the nodes
+//Write functions for responding to these two thingies
+func checkIn(w http.ResponseWriter, r *http.Request) {
+
+ //TODO: Current thoughts:
+ //Dont bother with a grouplastmodified
+ //Instead, implement a "configupdate" boolean on nodes
+ //when there is a group update that requrires a config update, then the node will pull its new config
+
+ // set header.
+ w.Header().Set("Content-Type", "application/json")
+
+ var params = mux.Vars(r)
+
+ var node models.Node
+
+
+ //Retrieves node with DB Call which is inefficient. Let's just get the time and set it.
+ //node = functions.GetNodeByMacAddress(params["group"], params["macaddress"])
+
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ filter := bson.M{"macaddress": params["macaddress"]}
+
+ //old code was inefficient, this is all we need.
+ time := time.Now().String()
+
+ //node.SetLastCheckIn()
+
+ // prepare update model with new time
+ update := bson.D{
+ {"$set", bson.D{
+ {"lastcheckin", time},
+ }},
+ }
+
+ errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&node)
+
+ defer cancel()
+
+ if errN != nil {
+ mongoconn.GetError(errN, w)
+ return
+ }
+ //TODO: check node last modified vs group last modified
+ json.NewEncoder(w).Encode(node)
+
+}
+
+//Get an individual node. Nothin fancy here folks.
+func getNode(w http.ResponseWriter, r *http.Request) {
+ // set header.
+ w.Header().Set("Content-Type", "application/json")
+
+ var params = mux.Vars(r)
+
+ node, err := GetNode(params["macaddress"], params["group"])
+
+ if err != nil {
+ mongoconn.GetError(err, w)
+ return
+ }
+
+ json.NewEncoder(w).Encode(node)
+}
+
+//Get the time that a group of nodes was last modified.
+//TODO: This needs to be refactored
+//Potential way to do this: On UpdateNode, set a new field for "LastModified"
+//If we go with the existing way, we need to at least set group.NodesLastModified on UpdateNode
+func getLastModified(w http.ResponseWriter, r *http.Request) {
+ // set header.
+ w.Header().Set("Content-Type", "application/json")
+
+ var group models.Group
+ var params = mux.Vars(r)
+
+ collection := mongoconn.Client.Database("wirecat").Collection("groups")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ filter := bson.M{"nameid": params["group"]}
+ err := collection.FindOne(ctx, filter).Decode(&group)
+
+ defer cancel()
+
+ if err != nil {
+ fmt.Println(err)
+ //log.Fatal(err)
+ }
+
+ w.Write([]byte(string(group.NodesLastModified)))
+
+}
+
+//This one's a doozy
+//To create a node
+//Must have valid key and be unique
+func createNode(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ var params = mux.Vars(r)
+
+ var errorResponse = models.ErrorResponse{
+ Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
+ }
+
+ groupName := params["group"]
+
+ //Check if group exists first
+ //TODO: This is inefficient. Let's find a better way.
+ //Just a few rows down we grab the group anyway
+ groupexists, errgroup := functions.GroupExists(groupName)
+
+
+ if !groupexists || errgroup != nil {
+ errorResponse = models.ErrorResponse{
+ Code: http.StatusNotFound, Message: "W1R3: Group does not exist! ",
+ }
+ returnErrorResponse(w, r, errorResponse)
+ return
+ }
+
+ var node models.Node
+
+ //get node from body of request
+ _ = json.NewDecoder(r.Body).Decode(&node)
+
+ node.Group = groupName
+
+
+ group, _ := node.GetGroup()
+
+ //Check to see if key is valid
+ //TODO: Triple inefficient!!! This is the third call to the DB we make for groups
+ validKey := functions.IsKeyValid(groupName, node.AccessKey)
+
+ if !validKey {
+ //Check to see if group will allow manual sign up
+ //may want to switch this up with the valid key check and avoid a DB call that way.
+ if *group.AllowManualSignUp {
+ node.IsPending = true
+ } else {
+ errorResponse = models.ErrorResponse{
+ Code: http.StatusUnauthorized, Message: "W1R3: Key invalid, or none provided.",
+ }
+ returnErrorResponse(w, r, errorResponse)
+ return
+ }
+ }
+
+ err := ValidateNode("create", groupName, node)
+ if err != nil {
+ return
+ }
+
+ node, err = CreateNode(node, groupName)
+ if err != nil {
+ return
+ }
+
+ json.NewEncoder(w).Encode(node)
+}
+
+//Takes node out of pending state
+//TODO: May want to use cordon/uncordon terminology instead of "ispending".
+func uncordonNode(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ var params = mux.Vars(r)
+
+ var node models.Node
+
+ node, err := functions.GetNodeByMacAddress(params["group"], params["macaddress"])
+ if err != nil {
+ mongoconn.GetError(err, w)
+ return
+ }
+
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ // Create filter
+ filter := bson.M{"macaddress": params["macaddress"]}
+
+ node.SetLastModified()
+
+ fmt.Println("Uncordoning node " + node.Name)
+
+ // prepare update model.
+ update := bson.D{
+ {"$set", bson.D{
+ {"ispending", false},
+ }},
+ }
+
+ errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&node)
+
+ defer cancel()
+
+ if errN != nil {
+ mongoconn.GetError(errN, w)
+ return
+ }
+
+ json.NewEncoder(w).Encode("SUCCESS")
+}
+
+
+func updateNode(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ var params = mux.Vars(r)
+
+ //Get id from parameters
+ //id, _ := primitive.ObjectIDFromHex(params["id"])
+
+ var node models.Node
+
+ //start here
+ node, err := functions.GetNodeByMacAddress(params["group"], params["macaddress"])
+ if err != nil {
+ json.NewEncoder(w).Encode(err)
+ return
+ }
+
+
+ var nodechange models.Node
+
+ // we decode our body request params
+ _ = json.NewDecoder(r.Body).Decode(&nodechange)
+ if nodechange.Group == "" {
+ nodechange.Group = node.Group
+ }
+ if nodechange.MacAddress == "" {
+ nodechange.MacAddress = node.MacAddress
+ }
+
+ err = ValidateNode("update", params["group"], nodechange)
+
+ if err != nil {
+ json.NewEncoder(w).Encode(err)
+ return
+ }
+
+ node, err = UpdateNode(nodechange, node)
+
+ if err != nil {
+ json.NewEncoder(w).Encode(err)
+ return
+ }
+
+ json.NewEncoder(w).Encode(node)
+}
+
+//Delete a node
+//Pretty straightforward
+func deleteNode(w http.ResponseWriter, r *http.Request) {
+ // Set header
+ w.Header().Set("Content-Type", "application/json")
+
+ // get params
+ var params = mux.Vars(r)
+
+ success, err := DeleteNode(params["macaddress"], params["group"])
+
+ if err != nil || !success {
+ json.NewEncoder(w).Encode("Could not delete node " + params["macaddress"])
+ return
+ }
+
+ json.NewEncoder(w).Encode(params["macaddress"] + " deleted.")
+}
+
+//A fun lil method for handling errors with http
+//Used in some cases but not others
+//1. This should probably be an application-wide function
+//2. All the API calls should probably be using this
+//3. The mongoconn should probably use this.
+//4. Need a consistent approach to error handling generally. Very haphazard at the moment
+//TODO: This is important. All Handlers should be replying with appropriate error code.
+func returnErrorResponse(response http.ResponseWriter, request *http.Request, errorMesage models.ErrorResponse) {
+ httpResponse := &models.ErrorResponse{Code: errorMesage.Code, Message: errorMesage.Message}
+ jsonResponse, err := json.Marshal(httpResponse)
+ if err != nil {
+ panic(err)
+ }
+ response.Header().Set("Content-Type", "application/json")
+ response.WriteHeader(errorMesage.Code)
+ response.Write(jsonResponse)
+}
+
diff --git a/controllers/userHttpController.go b/controllers/userHttpController.go
new file mode 100644
index 000000000..05823768d
--- /dev/null
+++ b/controllers/userHttpController.go
@@ -0,0 +1,463 @@
+package controller
+
+import (
+ "gopkg.in/go-playground/validator.v9"
+ "github.com/gravitl/netmaker/models"
+ "github.com/gravitl/netmaker/functions"
+ "github.com/gravitl/netmaker/mongoconn"
+ "golang.org/x/crypto/bcrypt"
+ "time"
+ "strings"
+ "fmt"
+ "context"
+ "encoding/json"
+ "net/http"
+ "github.com/gorilla/mux"
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/mongo/options"
+ "go.mongodb.org/mongo-driver/mongo"
+)
+
+
+func userHandlers(r *mux.Router) {
+
+ r.HandleFunc("/users/hasadmin", hasAdmin).Methods("GET")
+ r.HandleFunc("/users/createadmin", createAdmin).Methods("POST")
+ r.HandleFunc("/users/{username}", authorizeUser(http.HandlerFunc(updateUser))).Methods("PUT")
+ r.HandleFunc("/users/{username}", authorizeUser(http.HandlerFunc(deleteUser))).Methods("DELETE")
+ r.HandleFunc("/users/{username}", authorizeUser(http.HandlerFunc(getUser))).Methods("GET")
+ r.HandleFunc("/users/authenticate", authenticateUser).Methods("POST")
+}
+
+//Node authenticates using its password and retrieves a JWT for authorization.
+func authenticateUser(response http.ResponseWriter, request *http.Request) {
+
+ //Auth request consists of Mac Address and Password (from node that is authorizing
+ //in case of Master, auth is ignored and mac is set to "mastermac"
+ var authRequest models.UserAuthParams
+ var result models.User
+ var errorResponse = models.ErrorResponse{
+ Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
+ }
+
+ decoder := json.NewDecoder(request.Body)
+ decoderErr := decoder.Decode(&authRequest)
+ defer request.Body.Close()
+
+ if decoderErr != nil {
+ returnErrorResponse(response, request, errorResponse)
+ } else {
+ errorResponse.Code = http.StatusBadRequest
+ if authRequest.UserName == "" {
+ errorResponse.Message = "W1R3: Username can't be empty"
+ returnErrorResponse(response, request, errorResponse)
+ } else if authRequest.Password == "" {
+ errorResponse.Message = "W1R3: Password can't be empty"
+ returnErrorResponse(response, request, errorResponse)
+ } else {
+
+ //Search DB for node with Mac Address. Ignore pending nodes (they should not be able to authenticate with API untill approved).
+ collection := mongoconn.Client.Database("wirecat").Collection("users")
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ var err = collection.FindOne(ctx, bson.M{ "username": authRequest.UserName }).Decode(&result)
+
+ defer cancel()
+
+ if err != nil {
+ returnErrorResponse(response, request, errorResponse)
+ }
+
+ //compare password from request to stored password in database
+ //might be able to have a common hash (certificates?) and compare those so that a password isn't passed in in plain text...
+ //TODO: Consider a way of hashing the password client side before sending, or using certificates
+ err = bcrypt.CompareHashAndPassword([]byte(result.Password), []byte(authRequest.Password))
+ if err != nil {
+ returnErrorResponse(response, request, errorResponse)
+ } else {
+ //Create a new JWT for the node
+ tokenString, _ := functions.CreateUserJWT(authRequest.UserName, result.IsAdmin)
+
+ if tokenString == "" {
+ returnErrorResponse(response, request, errorResponse)
+ }
+
+ var successResponse = models.SuccessResponse{
+ Code: http.StatusOK,
+ Message: "W1R3: Device " + authRequest.UserName + " Authorized",
+ Response: models.SuccessfulUserLoginResponse{
+ AuthToken: tokenString,
+ UserName: authRequest.UserName,
+ },
+ }
+ //Send back the JWT
+ successJSONResponse, jsonError := json.Marshal(successResponse)
+
+ if jsonError != nil {
+ returnErrorResponse(response, request, errorResponse)
+ }
+ response.Header().Set("Content-Type", "application/json")
+ response.Write(successJSONResponse)
+ }
+ }
+ }
+}
+
+//The middleware for most requests to the API
+//They all pass through here first
+//This will validate the JWT (or check for master token)
+//This will also check against the authGroup and make sure the node should be accessing that endpoint,
+//even if it's technically ok
+//This is kind of a poor man's RBAC. There's probably a better/smarter way.
+//TODO: Consider better RBAC implementations
+func authorizeUser(next http.Handler) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+
+ var errorResponse = models.ErrorResponse{
+ Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+
+ //get the auth token
+ bearerToken := r.Header.Get("Authorization")
+
+ var tokenSplit = strings.Split(bearerToken, " ")
+
+ //I put this in in case the user doesn't put in a token at all (in which case it's empty)
+ //There's probably a smarter way of handling this.
+ var authToken = "928rt238tghgwe@TY@$Y@#WQAEGB2FC#@HG#@$Hddd"
+
+ if len(tokenSplit) > 1 {
+ authToken = tokenSplit[1]
+ }
+
+ //This checks if
+ //A: the token is the master password
+ //B: the token corresponds to a mac address, and if so, which one
+ //TODO: There's probably a better way of dealing with the "master token"/master password. Plz Halp.
+ username, _, err := functions.VerifyUserToken(authToken)
+
+ if err != nil {
+ json.NewEncoder(w).Encode(err)
+ return
+ }
+
+ isAuthorized := username != ""
+
+ if !isAuthorized {
+ errorResponse = models.ErrorResponse{
+ Code: http.StatusUnauthorized, Message: "W1R3: You are unauthorized to access this endpoint.",
+ }
+ returnErrorResponse(w, r, errorResponse)
+ } else {
+ //If authorized, this function passes along it's request and output to the appropriate route function.
+ next.ServeHTTP(w, r)
+ }
+ }
+}
+
+func HasAdmin() (bool, error){
+
+ collection := mongoconn.Client.Database("wirecat").Collection("users")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ filter := bson.M{"isadmin": true}
+
+ //Filtering out the ID field cuz Dillon doesn't like it. May want to filter out other fields in the future
+ var result bson.M
+
+ err := collection.FindOne(ctx, filter).Decode(&result)
+
+ defer cancel()
+
+ if err != nil {
+ if err == mongo.ErrNoDocuments {
+ return false, err
+ }
+ fmt.Println(err)
+ }
+ return true, err
+}
+
+func hasAdmin(w http.ResponseWriter, r *http.Request) {
+
+ w.Header().Set("Content-Type", "application/json")
+
+ hasadmin, _ := HasAdmin()
+
+ //Returns all the nodes in JSON format
+
+ json.NewEncoder(w).Encode(hasadmin)
+
+}
+
+func GetUser(username string) (models.User, error) {
+
+ var user models.User
+
+ collection := mongoconn.Client.Database("wirecat").Collection("users")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ filter := bson.M{"username": username}
+ err := collection.FindOne(ctx, filter, options.FindOne().SetProjection(bson.M{"_id": 0})).Decode(&user)
+
+ defer cancel()
+
+ return user, err
+}
+
+//Get an individual node. Nothin fancy here folks.
+func getUser(w http.ResponseWriter, r *http.Request) {
+ // set header.
+ w.Header().Set("Content-Type", "application/json")
+
+ var params = mux.Vars(r)
+
+ user, err := GetUser(params["username"])
+
+ if err != nil {
+ mongoconn.GetError(err, w)
+ return
+ }
+
+ json.NewEncoder(w).Encode(user)
+}
+
+func CreateUser(user models.User) (models.User, error) {
+
+ //encrypt that password so we never see it again
+ hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 5)
+
+ if err != nil {
+ return user, err
+ }
+ //set password to encrypted password
+ user.Password = string(hash)
+
+ tokenString, _ := functions.CreateUserJWT(user.UserName, user.IsAdmin)
+
+ if tokenString == "" {
+ //returnErrorResponse(w, r, errorResponse)
+ return user, err
+ }
+
+ // connect db
+ collection := mongoconn.Client.Database("wirecat").Collection("users")
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ // insert our node to the node db.
+ result, err := collection.InsertOne(ctx, user)
+ _ = result
+
+ defer cancel()
+
+ return user, err
+}
+
+func createAdmin(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ var errorResponse = models.ErrorResponse{
+ Code: http.StatusInternalServerError, Message: "W1R3: It's not you it's me.",
+ }
+
+ hasadmin, err := HasAdmin()
+
+ if hasadmin {
+ errorResponse = models.ErrorResponse{
+ Code: http.StatusUnauthorized, Message: "W1R3: Admin already exists! ",
+ }
+ returnErrorResponse(w, r, errorResponse)
+ return
+ }
+
+ var admin models.User
+
+ //get node from body of request
+ _ = json.NewDecoder(r.Body).Decode(&admin)
+
+ admin.IsAdmin = true
+
+ err = ValidateUser("create", admin)
+ if err != nil {
+ json.NewEncoder(w).Encode(err)
+ return
+ }
+
+ admin, err = CreateUser(admin)
+ if err != nil {
+ json.NewEncoder(w).Encode(err)
+ return
+ }
+
+ json.NewEncoder(w).Encode(admin)
+}
+
+func UpdateUser(userchange models.User, user models.User) (models.User, error) {
+
+ queryUser := user.UserName
+
+ if userchange.UserName != "" {
+ user.UserName = userchange.UserName
+ }
+ if userchange.Password != "" {
+ //encrypt that password so we never see it again
+ hash, err := bcrypt.GenerateFromPassword([]byte(userchange.Password), 5)
+
+ if err != nil {
+ return userchange, err
+ }
+ //set password to encrypted password
+ userchange.Password = string(hash)
+
+ user.Password = userchange.Password
+ }
+ //collection := mongoconn.ConnectDB()
+ collection := mongoconn.Client.Database("wirecat").Collection("users")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ // Create filter
+ filter := bson.M{"username": queryUser}
+
+ fmt.Println("Updating User " + user.UserName)
+
+
+ // prepare update model.
+ update := bson.D{
+ {"$set", bson.D{
+ {"username", user.UserName},
+ {"password", user.Password},
+ {"isadmin", user.IsAdmin},
+ }},
+ }
+ var userupdate models.User
+
+ errN := collection.FindOneAndUpdate(ctx, filter, update).Decode(&userupdate)
+ if errN != nil {
+ fmt.Println("Could not update: ")
+ fmt.Println(errN)
+ } else {
+ fmt.Println("User updated successfully.")
+ }
+
+ defer cancel()
+
+ return userupdate, errN
+}
+
+func updateUser(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+
+ var params = mux.Vars(r)
+
+ var user models.User
+
+ //start here
+ user, err := GetUser(params["username"])
+ if err != nil {
+ json.NewEncoder(w).Encode(err)
+ return
+ }
+
+
+ var userchange models.User
+
+ // we decode our body request params
+ err = json.NewDecoder(r.Body).Decode(&userchange)
+ if err != nil {
+ json.NewEncoder(w).Encode(err)
+ return
+ }
+
+ userchange.IsAdmin = true
+
+ err = ValidateUser("update", userchange)
+
+ if err != nil {
+ json.NewEncoder(w).Encode(err)
+ return
+ }
+
+ user, err = UpdateUser(userchange, user)
+
+ if err != nil {
+ json.NewEncoder(w).Encode(err)
+ return
+ }
+
+ json.NewEncoder(w).Encode(user)
+}
+
+func DeleteUser(user string) (bool, error) {
+
+ deleted := false
+
+ collection := mongoconn.Client.Database("wirecat").Collection("users")
+
+ filter := bson.M{"username": user}
+
+ 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 deleteUser(w http.ResponseWriter, r *http.Request) {
+ // Set header
+ w.Header().Set("Content-Type", "application/json")
+
+ // get params
+ var params = mux.Vars(r)
+
+ success, err := DeleteUser(params["username"])
+
+ if err != nil || !success {
+ http.Error(w, err.Error(), 400)
+ json.NewEncoder(w).Encode("Could not delete user " + params["username"])
+ return
+ }
+
+ json.NewEncoder(w).Encode(params["username"] + " deleted.")
+}
+
+func ValidateUser(operation string, user models.User) error {
+
+ v := validator.New()
+
+ _ = v.RegisterValidation("username_unique", func(fl validator.FieldLevel) bool {
+ _, err := GetUser(user.UserName)
+ return err == nil || operation == "update"
+ })
+
+ _ = v.RegisterValidation("username_valid", func(fl validator.FieldLevel) bool {
+ isvalid := functions.NameInNodeCharSet(user.UserName)
+ return isvalid
+ })
+
+ _ = v.RegisterValidation("password_check", func(fl validator.FieldLevel) bool {
+ notEmptyCheck := user.Password != ""
+ goodLength := len(user.Password) > 5
+ return (notEmptyCheck && goodLength) || operation == "update"
+ })
+
+ err := v.Struct(user)
+
+ if err != nil {
+ for _, e := range err.(validator.ValidationErrors) {
+ fmt.Println(e)
+ }
+ }
+ return err
+}
diff --git a/defaultvalues.sh b/defaultvalues.sh
new file mode 100644
index 000000000..e35125078
--- /dev/null
+++ b/defaultvalues.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+#Source this file if using default mongo settings from readme
+# if i've done my work correctly, this file will be defunct
+# refer to config folder for new method
+export API_PORT=8081
+export GRPC_PORT=5051
+export MONGO_USER=mongoadmin
+export MONGO_PASS=mongopass
+export MONGO_HOST=localhost
+export MASTER_KEY=c4tsRc001
+export MONGO_PORT=27017
+export MONGO_OPTS='/?authSource=admin'
+export MASTER_TOKEN="mastertoken"
+export CREATE_KEY="newnode123"
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 000000000..f2e544ed5
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,36 @@
+version: "3.3"
+
+services:
+ mongodb:
+ image: mongo:4.2
+ ports:
+ - "27017:27017"
+ container_name: mongodb
+ volumes:
+ - mongovol:/data/db
+ restart: always
+ environment:
+ MONGO_INITDB_ROOT_USERNAME: mongoadmin
+ MONGO_INITDB_ROOT_PASSWORD: mongopass
+ wirecat:
+ container_name: wirecat
+ depends_on:
+ - mongodb
+ image: docker.pkg.github.com/gravitl/netmaker/netmaker:v0.1
+ ports:
+ - "8081:8081"
+ - "50051:50051"
+ environment:
+ MONGO_HOST: mongodb
+ restart: always
+ wirecat-ui:
+ container_name: wirecat-ui
+ depends_on:
+ - wirecat
+ image: docker.pkg.github.com/gravitl/netmaker-ui/netmaker-ui:v0.1
+ ports:
+ - "80:80"
+ environment:
+ BACKEND_URL: "http://localhost:8081"
+volumes:
+ mongovol: {}
diff --git a/docs/API.md b/docs/API.md
new file mode 100644
index 000000000..097e1d2ba
--- /dev/null
+++ b/docs/API.md
@@ -0,0 +1,68 @@
+# API Reference Doc
+
+### Nodes
+**Get Peer List:** "/api/{group}/peerlist", "GET"
+**Get List Last Modified Date:** "/api/{group}/lastmodified", "GET"
+**Get Node Details:** "/api/{group}/nodes/{macaddress}", "GET"
+**Create Node:** "/api/{group}/nodes", "POST"
+**Uncordon Node:** "/api/{group}/nodes/{macaddress}/uncordon", "POST"
+**Check In Node:** "/api/{group}/nodes/{macaddress}/checkin", "POST"
+**Update Node:** "/api/{group}/nodes/{macaddress}", "PUT"
+**Delete Node:** "/api/{group}/nodes/{macaddress}", "DELETE"
+**Get Group Nodes:** "/api/{group}/nodes", "GET"
+**Get All Nodes:** "/api/nodes", "GET"
+**Authenticate:** "/api/{group}/authenticate", "POST"
+
+
+### Groups
+**Get Groups:** "/api/groups", "GET"
+**Get Group Details:** "/api/group/{groupname}", "GET"
+**Get Number of Nodes in Group:** "/api/group/{groupname}/numnodes", "GET"
+**Create Group:** "/api/groups", "POST"
+**Update Group:** "/api/groups/{groupname}", "PUT"
+**Delete Group:** "/api/groups/{groupname}", "DELETE"
+
+**Create Access Key:** "/api/groups/{groupname}/keys", "POST"
+**Get Access Key:** "/api/groups/{groupname}/keys", "GET"
+**Delete Access Key:** "/api/groups/{groupname}/keys/{keyname}", "DELETE"
+
+### Users (only used for interface admin user at this time)
+**Create Admin User:** "/users/createadmin", "POST"
+**Check for Admin User:** "/users/hasadmin", "GET"
+**Update User:** "/users/{username}", "PUT"
+**Delete User:** "/users/{username}", "DELETE"
+**Get User:** "/users/{username}", "GET"
+**Authenticate User:** "/users/authenticate", "POST"
+
+*note: users API does not use /api/ because of a weird bug. Will fix in future release.
+**note: Only able to create Admin at this time. The "user" is only used by the [user interface](https://github.com/falconcat-inc/WireCat-UI) to authenticate the single admin user.
+
+### Files
+**Get File:** "/meshclient/files/{filename}", "GET"
+
+## Example API CALLS
+
+**Note About Token:** This is a configurable value stored under config/environments/dev.yaml and can be changed before startup. It's a hack for testing, just provides an easy way to authorize, and should be removed and changed in the future.
+
+#### Create a Group
+curl -d '{"addressrange":"10.70.0.0/16","nameid":"skynet"}' -H "Authorization: Bearer secretkey" -H 'Content-Type: application/json' localhost:8081/api/groups
+
+#### Create a Key
+curl -d '{"uses":10}' -H "Authorization: Bearer secretkey" -H 'Content-Type: application/json' localhost:8081/api/groups localhost:8081/api/groups/skynet/keys
+
+#### Create a Node
+curl -d '{ "endpoint": 100.200.100.200, "publickey": aorijqalrik3ajflaqrdajhkr,"macaddress": "8c:90:b5:06:f1:d9","password": "reallysecret","localaddress": "172.16.16.1","accesskey": "aA3bVG0rnItIRXDx","listenport": 6400}' -H 'Content-Type: application/json' -H "authorization: Bearer secretkey" localhost:8081/api/skynet/nodes
+
+#### Get Groups
+curl -H "Authorization: Bearer secretkey" -H 'Content-Type: application/json' localhost:8081/api/groups | jq
+
+#### Get Group Nodes
+curl -H "Authorization: Bearer secretkey" -H 'Content-Type: application/json' localhost:8081/api/skynet/nodes | jq
+
+#### Update Node Settings
+curl -X "PUT" -d '{"name":"my-laptop"}' -H 'Content-Type: application/json' -H "authorization: Bearer secretkey" localhost:8081/api/skynet/nodes/8c:90:b5:06:f1:d9
+
+#### Delete a Node
+curl -X "DELETE" -H "authorization: Bearer secretkey" localhost:8081/api/skynet/nodes/8c:90:b5:06:f1:d9
+
+
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md
new file mode 100644
index 000000000..35a1fede6
--- /dev/null
+++ b/docs/CONTRIBUTING.md
@@ -0,0 +1,6 @@
+## Contributing
+
+ 1. Create a feature / bugfix branch from develop
+ 2. Write your code
+ 3. Submit PR to develop
+
diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md
new file mode 100644
index 000000000..a7fc98cc0
--- /dev/null
+++ b/docs/GETTING_STARTED.md
@@ -0,0 +1,37 @@
+# Getting Started (Simple Setup)
+### Server Setup
+ 1. Get yourself a linux server and make sure it has a public IP.
+ 2. Deploy MongoDB `docker volume create mongovol && docker run -d --name mongodb -v mongovol:/data/db --network host -e MONGO_INITDB_ROOT_USERNAME=mongoadmin -e MONGO_INITDB_ROOT_PASSWORD=mongopass mongo --bind_ip 0.0.0.0 `
+ 3. Pull this repo: `git clone https://github.com/falconcat-inc/WireCat.git`
+ 4. Switch to the directory and source the default env vars `cd WireCat && source defaultvars.sh`
+ 5. Run the server: `go run ./`
+### Optional (For Testing): Create Groups and Nodes
+
+ 1. Create Group: `./test/groupcreate.sh`
+ 2. Create Key: `./test/keycreate.sh` (save the response for step 3)
+ 3. Open ./test/nodescreate.sh and replace ACCESSKEY with value from #2
+ 4. Create Nodes: `./test/nodescreate.sh`
+ 5. Check to see if nodes were created: `curl -H "authorization: Bearer secretkey" localhost:8081/api/skynet/nodes | jq`
+### UI Setup
+Please see [this repo](https://github.com/falconcat-inc/WireCat-UI) for instructions on setting up your UI.
+
+### Agent Setup
+
+On each machine you would like to add to the network, do the following:
+
+1. Confirm wireguard is installed: `sudo apt install wireguard-tools`
+2. Confirm ipv4 forwarding is enabled: `sysctl -w net.ipv4.ip_forward=1`
+3. Create a key or enable manual node signup at the group level
+4. Get the binary: `sudo wget 52.55.6.84:8081/meshclient/files/meshclient`
+5. Make it executable: `sudo chmod +x meshclient`
+6. Run the install command: `sudo ./meshclient -c install -g -s -k `
+
+This will install netclient.service and netclient.timer in systemd, which will run periodically to call the netclient binary, which will check to see if there are any updates that it needs and update WireGuard appropriately.
+
+## BUILDING
+**Protoc command for GRPC Compilation:**
+
+ protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative grpc/node.proto
+
+**Build binary:** `go build ./`
+
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
new file mode 100644
index 000000000..a7c54b8d4
--- /dev/null
+++ b/docs/ROADMAP.md
@@ -0,0 +1,96 @@
+# FEATURE ROADMAP
+
+### 0.1
+**Server:**
+ - [x] Create Groups (virtual networks)
+ - [x] Allow default settings for nodes from groups
+ - [x] Admin/Superuser key
+ - [x] Create multiuse keys for node signup
+ - [x] JWT-based auth for post-signup
+ - [x] CRUD for groups
+ - [x] CRUD for nodes
+ - [x] Track all important info about node for networking (port, endpoints, pub key, etc)
+ - [x] Timestamps for determining if nodes need updates
+
+**Agent:**
+ - [x] Self-installer
+ - [x] Determine default settings w/o user input
+ - [x] Systemd Service + timer
+ - [x] Check-in functionality to retrieve updates from server
+ - [x] Maintain list of up-to-date peers
+ - [x] Update WG interface
+ - [x] Config file for modifying node
+
+### 0.2
+- [ ] Separate out README into DOCS folder with the following:
+ - [ ] API Docs
+ - [ ] Getting Started
+ - [ ] Advanced Usage
+ - [ ] Contributing
+ - [ ] Roadmap
+ - [ ] Troubleshooting
+
+**Server:**
+ - [ ] Allow tracking multiple groups per node
+ - [ ] Configure Check-in thresholds
+ - [ ] Separate sign-up endpoint to allow VPN-only comms after joining network
+ - [ ] Swagger Docs
+ - [ ] Build Out README
+ - [ ] Encode Server, Port, and Group into Keys
+ - [ ] Switch to Unique ID for nodes instead of MacAddress
+ - [ ] Public Key refresh
+ - [ ] Enable ipv6 addresses
+ - [ ] Have a "default" group created at startup
+
+**Agent:**
+ - [ ] Test / get working on multiple linux platforms
+ - [ ] Set private DNS via etc hosts (node name + ip). Make it optional flag on agent.
+ - [ ] Decode Server, Port, and Group from Key
+ - [ ] Service ID / unit file for SystemD Service
+ - [ ] Allow multiple interfaces
+ - [ ] Use "Check in interval" from server
+ - [ ] Pre-req check on machine (wg, port forwarding)
+ - [ ] Enable ipv6 addresses
+
+### 0.3
+**Server:**
+ - [ ] Swagger Docs
+ - [ ] Group/Node labels
+ - [ ] "Read Only" mode for nodes (can't update their settings centrally, only read)
+ - [ ] "No-GUI mode:" Similar to existing, just do more e2e testing and make sure flow makes sense
+ - [ ] Let users set prefixes (node, interface)
+
+**Agent:**
+ - [ ] Do system calls instead of direct commands
+ - [ ] Add a prompt for easy setup
+
+### 0.4
+**Server:**
+ - [ ] Private DNS
+ - [ ] UDP Hole-Punching (via WGSD: https://github.com/jwhited/wgsd )
+ - [ ] "Read Only" mode for nodes (can't update their settings centrally, only read)
+
+**Agent:**
+ - [ ] Do system calls instead of direct comma[this repo](https://github.com/falconcat-inc/WireCat-UI)nds
+ - [ ] Add a prompt for easy setup
+ - [ ] Make it work as a sidecar container!!!
+
+### 0.5
+**Server:**
+ - [ ] Multi-user support
+ - [ ] Oauth
+ - [ ] public key cycling
+
+### Future Considerations
+**Server:**
+ - [ ] Switch to distributed protocol (RAFT, Kademlia) instead of central server
+ - [ ] Load balance / fault tolerant server
+ - [ ] Change DB / make more scaleable (SQL?)
+ - [ ] Redis
+ - [ ] Group/Node labels
+
+**Agent:**
+ - [ ] userspace via Docker or Golang
+ - [ ] MacOS support
+ - [ ] Windows support
+ - [ ] Certificate-based authentication
diff --git a/functions/helpers.go b/functions/helpers.go
new file mode 100644
index 000000000..379d59c53
--- /dev/null
+++ b/functions/helpers.go
@@ -0,0 +1,522 @@
+//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
+
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+ 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) {
+
+ collection := mongoconn.Client.Database("wirecat").Collection("groups")
+
+ 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
+ }
+ log.Fatal(err)
+ }
+ 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
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+
+ 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
+
+ collection := mongoconn.Client.Database("wirecat").Collection("groups")
+
+ 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
+
+ collection := mongoconn.Client.Database("wirecat").Collection("groups")
+
+ 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
+
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+ 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}
+
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+
+ 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
+}
+
+//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
+
+ collection := mongoconn.Client.Database("wirecat").Collection("nodes")
+ 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
+ }
+ }
+ }
+
+ collection := mongoconn.Client.Database("wirecat").Collection("groups")
+
+ 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:]...)
+
+ collection := mongoconn.Client.Database("wirecat").Collection("groups")
+
+ 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
+ }
+ }
+}
diff --git a/functions/jwt.go b/functions/jwt.go
new file mode 100644
index 000000000..36f2b5370
--- /dev/null
+++ b/functions/jwt.go
@@ -0,0 +1,82 @@
+package functions
+
+import (
+ "time"
+ "github.com/gravitl/netmaker/config"
+ "github.com/gravitl/netmaker/models"
+ "github.com/dgrijalva/jwt-go"
+)
+
+var jwtSecretKey = []byte("(BytesOverTheWire)")
+
+// CreateJWT func will used to create the JWT while signing in and signing out
+func CreateJWT(macaddress string, group string) (response string, err error) {
+ expirationTime := time.Now().Add(5 * time.Minute)
+ claims := &models.Claims{
+ MacAddress: macaddress,
+ Group: group,
+ StandardClaims: jwt.StandardClaims{
+ ExpiresAt: expirationTime.Unix(),
+ },
+ }
+
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ tokenString, err := token.SignedString(jwtSecretKey)
+ if err == nil {
+ return tokenString, nil
+ }
+ return "", err
+}
+
+func CreateUserJWT(username string, isadmin bool) (response string, err error) {
+ expirationTime := time.Now().Add(60 * time.Minute)
+ claims := &models.UserClaims{
+ UserName: username,
+ IsAdmin: isadmin,
+ StandardClaims: jwt.StandardClaims{
+ ExpiresAt: expirationTime.Unix(),
+ },
+ }
+
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ tokenString, err := token.SignedString(jwtSecretKey)
+ if err == nil {
+ return tokenString, nil
+ }
+ return "", err
+}
+
+// VerifyToken func will used to Verify the JWT Token while using APIS
+func VerifyUserToken(tokenString string) (username string, isadmin bool, err error) {
+ claims := &models.UserClaims{}
+
+ token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
+ return jwtSecretKey, nil
+ })
+
+ if token != nil {
+ return claims.UserName, claims.IsAdmin, nil
+ }
+ return "", false, err
+}
+
+// VerifyToken func will used to Verify the JWT Token while using APIS
+func VerifyToken(tokenString string) (macaddress string, group string, err error) {
+ claims := &models.Claims{}
+
+ //this may be a stupid way of serving up a master key
+ //TODO: look into a different method. Encryption?
+ if tokenString == config.Config.Server.MasterKey {
+ return "mastermac", "", nil
+ }
+
+ token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
+ return jwtSecretKey, nil
+ })
+
+ if token != nil {
+ return claims.MacAddress, claims.Group, nil
+ }
+ return "", "", err
+}
+
diff --git a/go.mod b/go.mod
new file mode 100644
index 000000000..736d66a8f
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,31 @@
+module github.com/gravitl/netmaker
+
+go 1.15
+
+require (
+ github.com/davecgh/go-spew v1.1.1
+ github.com/dgrijalva/jwt-go v3.2.0+incompatible
+ github.com/go-playground/universal-translator v0.17.0 // indirect
+ github.com/golang/protobuf v1.4.3
+ github.com/gorilla/handlers v1.5.1
+ github.com/gorilla/mux v1.8.0
+ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0
+ github.com/leodido/go-urn v1.2.0 // indirect
+ github.com/mitchellh/go-homedir v1.1.0
+ github.com/robfig/cron v1.2.0
+ github.com/spf13/cobra v0.0.3
+ github.com/spf13/viper v1.7.1
+ github.com/takama/daemon v1.0.0
+ github.com/vishvananda/netlink v1.1.0
+ go.mongodb.org/mongo-driver v1.4.3
+ golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72
+ golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
+ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect
+ golang.org/x/text v0.3.5 // indirect
+ golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b
+ google.golang.org/genproto v0.0.0-20210201151548-94839c025ad4 // indirect
+ google.golang.org/grpc v1.35.0
+ google.golang.org/protobuf v1.25.0 // indirect
+ gopkg.in/go-playground/validator.v9 v9.31.0
+ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 000000000..25c32e3a7
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,484 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+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/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+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/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+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=
+github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+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 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+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=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
+github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
+github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
+github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs=
+github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
+github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI=
+github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk=
+github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28=
+github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo=
+github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk=
+github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw=
+github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360=
+github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg=
+github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE=
+github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8=
+github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
+github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc=
+github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
+github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4=
+github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ=
+github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0=
+github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
+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/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+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=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
+github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
+github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M=
+github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+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.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
+github.com/magiconair/properties v1.8.1/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/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
+github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
+github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
+github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
+github.com/mdlayher/netlink v1.1.0 h1:mpdLgm+brq10nI9zM1BpX1kpDbh3NLl3RSnVq6ZSkfg=
+github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
+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=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
+github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+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/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
+github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+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=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/takama/daemon v1.0.0 h1:XS3VLnFKmqw2Z7fQ/dHRarrVjdir9G3z7BEP8osjizQ=
+github.com/takama/daemon v1.0.0/go.mod h1:gKlhcjbqtBODg5v9H1nj5dU1a2j2GemtuWSNLD5rxOE=
+github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
+github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
+github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
+github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
+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/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.mongodb.org/mongo-driver v1.4.3 h1:moga+uhicpVshTyaqY9L23E6QqwcHRUv1sqyOsoyOO8=
+go.mongodb.org/mongo-driver v1.4.3/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc=
+golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 h1:+ELyKg6m8UBf0nPFSqD0mi7zUfwPyXo23HNjMnXPz7w=
+golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+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-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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=
+golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2 h1:T5DasATyLQfmbTpfEXx/IOL9vfjzW6up+ZDkmHvIf2s=
+golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.zx2c4.com/wireguard v0.0.20200121 h1:vcswa5Q6f+sylDfjqyrVNNrjsFUUbPsgAQTBCAg/Qf8=
+golang.zx2c4.com/wireguard v0.0.20200121/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4=
+golang.zx2c4.com/wireguard v0.0.20201119 h1:WJ4IKfT3SLG+YbM9aeZlgYB+X7hKzO66GEGBmxJPhjE=
+golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b h1:l4mBVCYinjzZuR5DtxHuBD6wyd4348TGiavJ5vLrhEc=
+golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200609130330-bd2cb7843e1b/go.mod h1:UdS9frhv65KTfwxME1xE8+rHYoFpbm36gOud1GhBe9c=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20210201151548-94839c025ad4 h1:HPkKL4eEh/nemF/FRzYMrFsAh1ZPm5t8NqKBI/Ejlg0=
+google.golang.org/genproto v0.0.0-20210201151548-94839c025ad4/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8=
+google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M=
+gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
+gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/grpc/node.pb.go b/grpc/node.pb.go
new file mode 100644
index 000000000..3b25cc210
--- /dev/null
+++ b/grpc/node.pb.go
@@ -0,0 +1,1025 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: grpc/node.proto
+
+package nodepb
+
+import (
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+type LoginRequest struct {
+ Macaddress string `protobuf:"bytes,1,opt,name=macaddress,proto3" json:"macaddress,omitempty"`
+ Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *LoginRequest) Reset() { *m = LoginRequest{} }
+func (m *LoginRequest) String() string { return proto.CompactTextString(m) }
+func (*LoginRequest) ProtoMessage() {}
+func (*LoginRequest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{0}
+}
+
+func (m *LoginRequest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_LoginRequest.Unmarshal(m, b)
+}
+func (m *LoginRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_LoginRequest.Marshal(b, m, deterministic)
+}
+func (m *LoginRequest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_LoginRequest.Merge(m, src)
+}
+func (m *LoginRequest) XXX_Size() int {
+ return xxx_messageInfo_LoginRequest.Size(m)
+}
+func (m *LoginRequest) XXX_DiscardUnknown() {
+ xxx_messageInfo_LoginRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_LoginRequest proto.InternalMessageInfo
+
+func (m *LoginRequest) GetMacaddress() string {
+ if m != nil {
+ return m.Macaddress
+ }
+ return ""
+}
+
+func (m *LoginRequest) GetPassword() string {
+ if m != nil {
+ return m.Password
+ }
+ return ""
+}
+
+type LoginResponse struct {
+ Accesstoken string `protobuf:"bytes,1,opt,name=accesstoken,proto3" json:"accesstoken,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *LoginResponse) Reset() { *m = LoginResponse{} }
+func (m *LoginResponse) String() string { return proto.CompactTextString(m) }
+func (*LoginResponse) ProtoMessage() {}
+func (*LoginResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{1}
+}
+
+func (m *LoginResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_LoginResponse.Unmarshal(m, b)
+}
+func (m *LoginResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_LoginResponse.Marshal(b, m, deterministic)
+}
+func (m *LoginResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_LoginResponse.Merge(m, src)
+}
+func (m *LoginResponse) XXX_Size() int {
+ return xxx_messageInfo_LoginResponse.Size(m)
+}
+func (m *LoginResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_LoginResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_LoginResponse proto.InternalMessageInfo
+
+func (m *LoginResponse) GetAccesstoken() string {
+ if m != nil {
+ return m.Accesstoken
+ }
+ return ""
+}
+
+type Node struct {
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
+ Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"`
+ Listenport int32 `protobuf:"varint,4,opt,name=listenport,proto3" json:"listenport,omitempty"`
+ Publickey string `protobuf:"bytes,5,opt,name=publickey,proto3" json:"publickey,omitempty"`
+ Endpoint string `protobuf:"bytes,6,opt,name=endpoint,proto3" json:"endpoint,omitempty"`
+ Macaddress string `protobuf:"bytes,7,opt,name=macaddress,proto3" json:"macaddress,omitempty"`
+ Password string `protobuf:"bytes,8,opt,name=password,proto3" json:"password,omitempty"`
+ Nodegroup string `protobuf:"bytes,9,opt,name=nodegroup,proto3" json:"nodegroup,omitempty"`
+ Ispending bool `protobuf:"varint,10,opt,name=ispending,proto3" json:"ispending,omitempty"`
+ Postup string `protobuf:"bytes,11,opt,name=postup,proto3" json:"postup,omitempty"`
+ Preup string `protobuf:"bytes,12,opt,name=preup,proto3" json:"preup,omitempty"`
+ Keepalive int32 `protobuf:"varint,13,opt,name=keepalive,proto3" json:"keepalive,omitempty"`
+ Saveconfig bool `protobuf:"varint,14,opt,name=saveconfig,proto3" json:"saveconfig,omitempty"`
+ Accesskey string `protobuf:"bytes,15,opt,name=accesskey,proto3" json:"accesskey,omitempty"`
+ Interface string `protobuf:"bytes,16,opt,name=interface,proto3" json:"interface,omitempty"`
+ Lastcheckin string `protobuf:"bytes,17,opt,name=lastcheckin,proto3" json:"lastcheckin,omitempty"`
+ Lastmodified string `protobuf:"bytes,18,opt,name=lastmodified,proto3" json:"lastmodified,omitempty"`
+ Checkininterval int32 `protobuf:"varint,19,opt,name=checkininterval,proto3" json:"checkininterval,omitempty"`
+ Localaddress string `protobuf:"bytes,20,opt,name=localaddress,proto3" json:"localaddress,omitempty"`
+ Postchanges string `protobuf:"bytes,21,opt,name=postchanges,proto3" json:"postchanges,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Node) Reset() { *m = Node{} }
+func (m *Node) String() string { return proto.CompactTextString(m) }
+func (*Node) ProtoMessage() {}
+func (*Node) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{2}
+}
+
+func (m *Node) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Node.Unmarshal(m, b)
+}
+func (m *Node) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Node.Marshal(b, m, deterministic)
+}
+func (m *Node) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Node.Merge(m, src)
+}
+func (m *Node) XXX_Size() int {
+ return xxx_messageInfo_Node.Size(m)
+}
+func (m *Node) XXX_DiscardUnknown() {
+ xxx_messageInfo_Node.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Node proto.InternalMessageInfo
+
+func (m *Node) GetId() string {
+ if m != nil {
+ return m.Id
+ }
+ return ""
+}
+
+func (m *Node) GetName() string {
+ if m != nil {
+ return m.Name
+ }
+ return ""
+}
+
+func (m *Node) GetAddress() string {
+ if m != nil {
+ return m.Address
+ }
+ return ""
+}
+
+func (m *Node) GetListenport() int32 {
+ if m != nil {
+ return m.Listenport
+ }
+ return 0
+}
+
+func (m *Node) GetPublickey() string {
+ if m != nil {
+ return m.Publickey
+ }
+ return ""
+}
+
+func (m *Node) GetEndpoint() string {
+ if m != nil {
+ return m.Endpoint
+ }
+ return ""
+}
+
+func (m *Node) GetMacaddress() string {
+ if m != nil {
+ return m.Macaddress
+ }
+ return ""
+}
+
+func (m *Node) GetPassword() string {
+ if m != nil {
+ return m.Password
+ }
+ return ""
+}
+
+func (m *Node) GetNodegroup() string {
+ if m != nil {
+ return m.Nodegroup
+ }
+ return ""
+}
+
+func (m *Node) GetIspending() bool {
+ if m != nil {
+ return m.Ispending
+ }
+ return false
+}
+
+func (m *Node) GetPostup() string {
+ if m != nil {
+ return m.Postup
+ }
+ return ""
+}
+
+func (m *Node) GetPreup() string {
+ if m != nil {
+ return m.Preup
+ }
+ return ""
+}
+
+func (m *Node) GetKeepalive() int32 {
+ if m != nil {
+ return m.Keepalive
+ }
+ return 0
+}
+
+func (m *Node) GetSaveconfig() bool {
+ if m != nil {
+ return m.Saveconfig
+ }
+ return false
+}
+
+func (m *Node) GetAccesskey() string {
+ if m != nil {
+ return m.Accesskey
+ }
+ return ""
+}
+
+func (m *Node) GetInterface() string {
+ if m != nil {
+ return m.Interface
+ }
+ return ""
+}
+
+func (m *Node) GetLastcheckin() string {
+ if m != nil {
+ return m.Lastcheckin
+ }
+ return ""
+}
+
+func (m *Node) GetLastmodified() string {
+ if m != nil {
+ return m.Lastmodified
+ }
+ return ""
+}
+
+func (m *Node) GetCheckininterval() int32 {
+ if m != nil {
+ return m.Checkininterval
+ }
+ return 0
+}
+
+func (m *Node) GetLocaladdress() string {
+ if m != nil {
+ return m.Localaddress
+ }
+ return ""
+}
+
+func (m *Node) GetPostchanges() string {
+ if m != nil {
+ return m.Postchanges
+ }
+ return ""
+}
+
+type CheckInResponse struct {
+ Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
+ Needpeerupdate bool `protobuf:"varint,2,opt,name=needpeerupdate,proto3" json:"needpeerupdate,omitempty"`
+ Needconfigupdate bool `protobuf:"varint,3,opt,name=needconfigupdate,proto3" json:"needconfigupdate,omitempty"`
+ Nodemessage string `protobuf:"bytes,4,opt,name=nodemessage,proto3" json:"nodemessage,omitempty"`
+ Ispending bool `protobuf:"varint,5,opt,name=ispending,proto3" json:"ispending,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *CheckInResponse) Reset() { *m = CheckInResponse{} }
+func (m *CheckInResponse) String() string { return proto.CompactTextString(m) }
+func (*CheckInResponse) ProtoMessage() {}
+func (*CheckInResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{3}
+}
+
+func (m *CheckInResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_CheckInResponse.Unmarshal(m, b)
+}
+func (m *CheckInResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_CheckInResponse.Marshal(b, m, deterministic)
+}
+func (m *CheckInResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_CheckInResponse.Merge(m, src)
+}
+func (m *CheckInResponse) XXX_Size() int {
+ return xxx_messageInfo_CheckInResponse.Size(m)
+}
+func (m *CheckInResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_CheckInResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CheckInResponse proto.InternalMessageInfo
+
+func (m *CheckInResponse) GetSuccess() bool {
+ if m != nil {
+ return m.Success
+ }
+ return false
+}
+
+func (m *CheckInResponse) GetNeedpeerupdate() bool {
+ if m != nil {
+ return m.Needpeerupdate
+ }
+ return false
+}
+
+func (m *CheckInResponse) GetNeedconfigupdate() bool {
+ if m != nil {
+ return m.Needconfigupdate
+ }
+ return false
+}
+
+func (m *CheckInResponse) GetNodemessage() string {
+ if m != nil {
+ return m.Nodemessage
+ }
+ return ""
+}
+
+func (m *CheckInResponse) GetIspending() bool {
+ if m != nil {
+ return m.Ispending
+ }
+ return false
+}
+
+type PeersResponse struct {
+ Publickey string `protobuf:"bytes,5,opt,name=publickey,proto3" json:"publickey,omitempty"`
+ Endpoint string `protobuf:"bytes,6,opt,name=endpoint,proto3" json:"endpoint,omitempty"`
+ Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty"`
+ Listenport int32 `protobuf:"varint,4,opt,name=listenport,proto3" json:"listenport,omitempty"`
+ Localaddress string `protobuf:"bytes,7,opt,name=localaddress,proto3" json:"localaddress,omitempty"`
+ Keepalive int32 `protobuf:"varint,13,opt,name=keepalive,proto3" json:"keepalive,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *PeersResponse) Reset() { *m = PeersResponse{} }
+func (m *PeersResponse) String() string { return proto.CompactTextString(m) }
+func (*PeersResponse) ProtoMessage() {}
+func (*PeersResponse) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{4}
+}
+
+func (m *PeersResponse) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_PeersResponse.Unmarshal(m, b)
+}
+func (m *PeersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_PeersResponse.Marshal(b, m, deterministic)
+}
+func (m *PeersResponse) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_PeersResponse.Merge(m, src)
+}
+func (m *PeersResponse) XXX_Size() int {
+ return xxx_messageInfo_PeersResponse.Size(m)
+}
+func (m *PeersResponse) XXX_DiscardUnknown() {
+ xxx_messageInfo_PeersResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PeersResponse proto.InternalMessageInfo
+
+func (m *PeersResponse) GetPublickey() string {
+ if m != nil {
+ return m.Publickey
+ }
+ return ""
+}
+
+func (m *PeersResponse) GetEndpoint() string {
+ if m != nil {
+ return m.Endpoint
+ }
+ return ""
+}
+
+func (m *PeersResponse) GetAddress() string {
+ if m != nil {
+ return m.Address
+ }
+ return ""
+}
+
+func (m *PeersResponse) GetListenport() int32 {
+ if m != nil {
+ return m.Listenport
+ }
+ return 0
+}
+
+func (m *PeersResponse) GetLocaladdress() string {
+ if m != nil {
+ return m.Localaddress
+ }
+ return ""
+}
+
+func (m *PeersResponse) GetKeepalive() int32 {
+ if m != nil {
+ return m.Keepalive
+ }
+ return 0
+}
+
+type CreateNodeReq struct {
+ Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *CreateNodeReq) Reset() { *m = CreateNodeReq{} }
+func (m *CreateNodeReq) String() string { return proto.CompactTextString(m) }
+func (*CreateNodeReq) ProtoMessage() {}
+func (*CreateNodeReq) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{5}
+}
+
+func (m *CreateNodeReq) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_CreateNodeReq.Unmarshal(m, b)
+}
+func (m *CreateNodeReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_CreateNodeReq.Marshal(b, m, deterministic)
+}
+func (m *CreateNodeReq) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_CreateNodeReq.Merge(m, src)
+}
+func (m *CreateNodeReq) XXX_Size() int {
+ return xxx_messageInfo_CreateNodeReq.Size(m)
+}
+func (m *CreateNodeReq) XXX_DiscardUnknown() {
+ xxx_messageInfo_CreateNodeReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CreateNodeReq proto.InternalMessageInfo
+
+func (m *CreateNodeReq) GetNode() *Node {
+ if m != nil {
+ return m.Node
+ }
+ return nil
+}
+
+type CreateNodeRes struct {
+ Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *CreateNodeRes) Reset() { *m = CreateNodeRes{} }
+func (m *CreateNodeRes) String() string { return proto.CompactTextString(m) }
+func (*CreateNodeRes) ProtoMessage() {}
+func (*CreateNodeRes) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{6}
+}
+
+func (m *CreateNodeRes) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_CreateNodeRes.Unmarshal(m, b)
+}
+func (m *CreateNodeRes) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_CreateNodeRes.Marshal(b, m, deterministic)
+}
+func (m *CreateNodeRes) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_CreateNodeRes.Merge(m, src)
+}
+func (m *CreateNodeRes) XXX_Size() int {
+ return xxx_messageInfo_CreateNodeRes.Size(m)
+}
+func (m *CreateNodeRes) XXX_DiscardUnknown() {
+ xxx_messageInfo_CreateNodeRes.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CreateNodeRes proto.InternalMessageInfo
+
+func (m *CreateNodeRes) GetNode() *Node {
+ if m != nil {
+ return m.Node
+ }
+ return nil
+}
+
+type UpdateNodeReq struct {
+ Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *UpdateNodeReq) Reset() { *m = UpdateNodeReq{} }
+func (m *UpdateNodeReq) String() string { return proto.CompactTextString(m) }
+func (*UpdateNodeReq) ProtoMessage() {}
+func (*UpdateNodeReq) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{7}
+}
+
+func (m *UpdateNodeReq) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_UpdateNodeReq.Unmarshal(m, b)
+}
+func (m *UpdateNodeReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_UpdateNodeReq.Marshal(b, m, deterministic)
+}
+func (m *UpdateNodeReq) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_UpdateNodeReq.Merge(m, src)
+}
+func (m *UpdateNodeReq) XXX_Size() int {
+ return xxx_messageInfo_UpdateNodeReq.Size(m)
+}
+func (m *UpdateNodeReq) XXX_DiscardUnknown() {
+ xxx_messageInfo_UpdateNodeReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_UpdateNodeReq proto.InternalMessageInfo
+
+func (m *UpdateNodeReq) GetNode() *Node {
+ if m != nil {
+ return m.Node
+ }
+ return nil
+}
+
+type UpdateNodeRes struct {
+ Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *UpdateNodeRes) Reset() { *m = UpdateNodeRes{} }
+func (m *UpdateNodeRes) String() string { return proto.CompactTextString(m) }
+func (*UpdateNodeRes) ProtoMessage() {}
+func (*UpdateNodeRes) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{8}
+}
+
+func (m *UpdateNodeRes) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_UpdateNodeRes.Unmarshal(m, b)
+}
+func (m *UpdateNodeRes) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_UpdateNodeRes.Marshal(b, m, deterministic)
+}
+func (m *UpdateNodeRes) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_UpdateNodeRes.Merge(m, src)
+}
+func (m *UpdateNodeRes) XXX_Size() int {
+ return xxx_messageInfo_UpdateNodeRes.Size(m)
+}
+func (m *UpdateNodeRes) XXX_DiscardUnknown() {
+ xxx_messageInfo_UpdateNodeRes.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_UpdateNodeRes proto.InternalMessageInfo
+
+func (m *UpdateNodeRes) GetNode() *Node {
+ if m != nil {
+ return m.Node
+ }
+ return nil
+}
+
+type ReadNodeReq struct {
+ Macaddress string `protobuf:"bytes,1,opt,name=macaddress,proto3" json:"macaddress,omitempty"`
+ Group string `protobuf:"bytes,2,opt,name=group,proto3" json:"group,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ReadNodeReq) Reset() { *m = ReadNodeReq{} }
+func (m *ReadNodeReq) String() string { return proto.CompactTextString(m) }
+func (*ReadNodeReq) ProtoMessage() {}
+func (*ReadNodeReq) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{9}
+}
+
+func (m *ReadNodeReq) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ReadNodeReq.Unmarshal(m, b)
+}
+func (m *ReadNodeReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ReadNodeReq.Marshal(b, m, deterministic)
+}
+func (m *ReadNodeReq) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ReadNodeReq.Merge(m, src)
+}
+func (m *ReadNodeReq) XXX_Size() int {
+ return xxx_messageInfo_ReadNodeReq.Size(m)
+}
+func (m *ReadNodeReq) XXX_DiscardUnknown() {
+ xxx_messageInfo_ReadNodeReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ReadNodeReq proto.InternalMessageInfo
+
+func (m *ReadNodeReq) GetMacaddress() string {
+ if m != nil {
+ return m.Macaddress
+ }
+ return ""
+}
+
+func (m *ReadNodeReq) GetGroup() string {
+ if m != nil {
+ return m.Group
+ }
+ return ""
+}
+
+type ReadNodeRes struct {
+ Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *ReadNodeRes) Reset() { *m = ReadNodeRes{} }
+func (m *ReadNodeRes) String() string { return proto.CompactTextString(m) }
+func (*ReadNodeRes) ProtoMessage() {}
+func (*ReadNodeRes) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{10}
+}
+
+func (m *ReadNodeRes) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_ReadNodeRes.Unmarshal(m, b)
+}
+func (m *ReadNodeRes) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_ReadNodeRes.Marshal(b, m, deterministic)
+}
+func (m *ReadNodeRes) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_ReadNodeRes.Merge(m, src)
+}
+func (m *ReadNodeRes) XXX_Size() int {
+ return xxx_messageInfo_ReadNodeRes.Size(m)
+}
+func (m *ReadNodeRes) XXX_DiscardUnknown() {
+ xxx_messageInfo_ReadNodeRes.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ReadNodeRes proto.InternalMessageInfo
+
+func (m *ReadNodeRes) GetNode() *Node {
+ if m != nil {
+ return m.Node
+ }
+ return nil
+}
+
+type DeleteNodeReq struct {
+ Macaddress string `protobuf:"bytes,1,opt,name=macaddress,proto3" json:"macaddress,omitempty"`
+ GroupName string `protobuf:"bytes,2,opt,name=groupName,proto3" json:"groupName,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *DeleteNodeReq) Reset() { *m = DeleteNodeReq{} }
+func (m *DeleteNodeReq) String() string { return proto.CompactTextString(m) }
+func (*DeleteNodeReq) ProtoMessage() {}
+func (*DeleteNodeReq) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{11}
+}
+
+func (m *DeleteNodeReq) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_DeleteNodeReq.Unmarshal(m, b)
+}
+func (m *DeleteNodeReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_DeleteNodeReq.Marshal(b, m, deterministic)
+}
+func (m *DeleteNodeReq) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_DeleteNodeReq.Merge(m, src)
+}
+func (m *DeleteNodeReq) XXX_Size() int {
+ return xxx_messageInfo_DeleteNodeReq.Size(m)
+}
+func (m *DeleteNodeReq) XXX_DiscardUnknown() {
+ xxx_messageInfo_DeleteNodeReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_DeleteNodeReq proto.InternalMessageInfo
+
+func (m *DeleteNodeReq) GetMacaddress() string {
+ if m != nil {
+ return m.Macaddress
+ }
+ return ""
+}
+
+func (m *DeleteNodeReq) GetGroupName() string {
+ if m != nil {
+ return m.GroupName
+ }
+ return ""
+}
+
+type DeleteNodeRes struct {
+ Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *DeleteNodeRes) Reset() { *m = DeleteNodeRes{} }
+func (m *DeleteNodeRes) String() string { return proto.CompactTextString(m) }
+func (*DeleteNodeRes) ProtoMessage() {}
+func (*DeleteNodeRes) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{12}
+}
+
+func (m *DeleteNodeRes) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_DeleteNodeRes.Unmarshal(m, b)
+}
+func (m *DeleteNodeRes) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_DeleteNodeRes.Marshal(b, m, deterministic)
+}
+func (m *DeleteNodeRes) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_DeleteNodeRes.Merge(m, src)
+}
+func (m *DeleteNodeRes) XXX_Size() int {
+ return xxx_messageInfo_DeleteNodeRes.Size(m)
+}
+func (m *DeleteNodeRes) XXX_DiscardUnknown() {
+ xxx_messageInfo_DeleteNodeRes.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_DeleteNodeRes proto.InternalMessageInfo
+
+func (m *DeleteNodeRes) GetSuccess() bool {
+ if m != nil {
+ return m.Success
+ }
+ return false
+}
+
+type GetPeersReq struct {
+ Macaddress string `protobuf:"bytes,1,opt,name=macaddress,proto3" json:"macaddress,omitempty"`
+ Group string `protobuf:"bytes,2,opt,name=group,proto3" json:"group,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *GetPeersReq) Reset() { *m = GetPeersReq{} }
+func (m *GetPeersReq) String() string { return proto.CompactTextString(m) }
+func (*GetPeersReq) ProtoMessage() {}
+func (*GetPeersReq) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{13}
+}
+
+func (m *GetPeersReq) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_GetPeersReq.Unmarshal(m, b)
+}
+func (m *GetPeersReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_GetPeersReq.Marshal(b, m, deterministic)
+}
+func (m *GetPeersReq) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_GetPeersReq.Merge(m, src)
+}
+func (m *GetPeersReq) XXX_Size() int {
+ return xxx_messageInfo_GetPeersReq.Size(m)
+}
+func (m *GetPeersReq) XXX_DiscardUnknown() {
+ xxx_messageInfo_GetPeersReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GetPeersReq proto.InternalMessageInfo
+
+func (m *GetPeersReq) GetMacaddress() string {
+ if m != nil {
+ return m.Macaddress
+ }
+ return ""
+}
+
+func (m *GetPeersReq) GetGroup() string {
+ if m != nil {
+ return m.Group
+ }
+ return ""
+}
+
+type GetPeersRes struct {
+ Peers *PeersResponse `protobuf:"bytes,1,opt,name=peers,proto3" json:"peers,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *GetPeersRes) Reset() { *m = GetPeersRes{} }
+func (m *GetPeersRes) String() string { return proto.CompactTextString(m) }
+func (*GetPeersRes) ProtoMessage() {}
+func (*GetPeersRes) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{14}
+}
+
+func (m *GetPeersRes) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_GetPeersRes.Unmarshal(m, b)
+}
+func (m *GetPeersRes) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_GetPeersRes.Marshal(b, m, deterministic)
+}
+func (m *GetPeersRes) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_GetPeersRes.Merge(m, src)
+}
+func (m *GetPeersRes) XXX_Size() int {
+ return xxx_messageInfo_GetPeersRes.Size(m)
+}
+func (m *GetPeersRes) XXX_DiscardUnknown() {
+ xxx_messageInfo_GetPeersRes.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_GetPeersRes proto.InternalMessageInfo
+
+func (m *GetPeersRes) GetPeers() *PeersResponse {
+ if m != nil {
+ return m.Peers
+ }
+ return nil
+}
+
+type CheckInReq struct {
+ Node *Node `protobuf:"bytes,1,opt,name=node,proto3" json:"node,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *CheckInReq) Reset() { *m = CheckInReq{} }
+func (m *CheckInReq) String() string { return proto.CompactTextString(m) }
+func (*CheckInReq) ProtoMessage() {}
+func (*CheckInReq) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{15}
+}
+
+func (m *CheckInReq) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_CheckInReq.Unmarshal(m, b)
+}
+func (m *CheckInReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_CheckInReq.Marshal(b, m, deterministic)
+}
+func (m *CheckInReq) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_CheckInReq.Merge(m, src)
+}
+func (m *CheckInReq) XXX_Size() int {
+ return xxx_messageInfo_CheckInReq.Size(m)
+}
+func (m *CheckInReq) XXX_DiscardUnknown() {
+ xxx_messageInfo_CheckInReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CheckInReq proto.InternalMessageInfo
+
+func (m *CheckInReq) GetNode() *Node {
+ if m != nil {
+ return m.Node
+ }
+ return nil
+}
+
+type CheckInRes struct {
+ Checkinresponse *CheckInResponse `protobuf:"bytes,1,opt,name=checkinresponse,proto3" json:"checkinresponse,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *CheckInRes) Reset() { *m = CheckInRes{} }
+func (m *CheckInRes) String() string { return proto.CompactTextString(m) }
+func (*CheckInRes) ProtoMessage() {}
+func (*CheckInRes) Descriptor() ([]byte, []int) {
+ return fileDescriptor_d13bd996b67da4ef, []int{16}
+}
+
+func (m *CheckInRes) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_CheckInRes.Unmarshal(m, b)
+}
+func (m *CheckInRes) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_CheckInRes.Marshal(b, m, deterministic)
+}
+func (m *CheckInRes) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_CheckInRes.Merge(m, src)
+}
+func (m *CheckInRes) XXX_Size() int {
+ return xxx_messageInfo_CheckInRes.Size(m)
+}
+func (m *CheckInRes) XXX_DiscardUnknown() {
+ xxx_messageInfo_CheckInRes.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CheckInRes proto.InternalMessageInfo
+
+func (m *CheckInRes) GetCheckinresponse() *CheckInResponse {
+ if m != nil {
+ return m.Checkinresponse
+ }
+ return nil
+}
+
+func init() {
+ proto.RegisterType((*LoginRequest)(nil), "node.LoginRequest")
+ proto.RegisterType((*LoginResponse)(nil), "node.LoginResponse")
+ proto.RegisterType((*Node)(nil), "node.Node")
+ proto.RegisterType((*CheckInResponse)(nil), "node.CheckInResponse")
+ proto.RegisterType((*PeersResponse)(nil), "node.PeersResponse")
+ proto.RegisterType((*CreateNodeReq)(nil), "node.CreateNodeReq")
+ proto.RegisterType((*CreateNodeRes)(nil), "node.CreateNodeRes")
+ proto.RegisterType((*UpdateNodeReq)(nil), "node.UpdateNodeReq")
+ proto.RegisterType((*UpdateNodeRes)(nil), "node.UpdateNodeRes")
+ proto.RegisterType((*ReadNodeReq)(nil), "node.ReadNodeReq")
+ proto.RegisterType((*ReadNodeRes)(nil), "node.ReadNodeRes")
+ proto.RegisterType((*DeleteNodeReq)(nil), "node.DeleteNodeReq")
+ proto.RegisterType((*DeleteNodeRes)(nil), "node.DeleteNodeRes")
+ proto.RegisterType((*GetPeersReq)(nil), "node.GetPeersReq")
+ proto.RegisterType((*GetPeersRes)(nil), "node.GetPeersRes")
+ proto.RegisterType((*CheckInReq)(nil), "node.CheckInReq")
+ proto.RegisterType((*CheckInRes)(nil), "node.CheckInRes")
+}
+
+func init() { proto.RegisterFile("grpc/node.proto", fileDescriptor_d13bd996b67da4ef) }
+
+var fileDescriptor_d13bd996b67da4ef = []byte{
+ // 813 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x56, 0x4d, 0x6f, 0xf3, 0x44,
+ 0x10, 0x56, 0xf2, 0x26, 0x4d, 0x32, 0x69, 0x9a, 0xbe, 0xdb, 0x16, 0xad, 0xac, 0xaa, 0x8a, 0x7c,
+ 0x40, 0x29, 0xa2, 0x49, 0x29, 0x12, 0xe2, 0x86, 0x44, 0x91, 0x10, 0x08, 0x2a, 0x64, 0xc4, 0x85,
+ 0xdb, 0xc6, 0x9e, 0xb8, 0x56, 0x9c, 0xdd, 0x8d, 0xd7, 0x4e, 0xd5, 0x5f, 0xc7, 0x89, 0x7f, 0xc4,
+ 0x91, 0x03, 0xda, 0x5d, 0x3b, 0xfe, 0x68, 0x48, 0xfb, 0xf6, 0x96, 0x79, 0x76, 0xbe, 0xe7, 0x99,
+ 0x89, 0x61, 0x1c, 0x26, 0xd2, 0x9f, 0x73, 0x11, 0xe0, 0x4c, 0x26, 0x22, 0x15, 0xa4, 0xa3, 0x7f,
+ 0xbb, 0x3f, 0xc3, 0xf1, 0x2f, 0x22, 0x8c, 0xb8, 0x87, 0x9b, 0x0c, 0x55, 0x4a, 0xae, 0x00, 0xd6,
+ 0xcc, 0x67, 0x41, 0x90, 0xa0, 0x52, 0xb4, 0x35, 0x69, 0x4d, 0x07, 0x5e, 0x05, 0x21, 0x0e, 0xf4,
+ 0x25, 0x53, 0xea, 0x49, 0x24, 0x01, 0x6d, 0x9b, 0xd7, 0x9d, 0xec, 0x7e, 0x05, 0xa3, 0xdc, 0x97,
+ 0x92, 0x82, 0x2b, 0x24, 0x13, 0x18, 0x32, 0xdf, 0x47, 0xa5, 0x52, 0xb1, 0x42, 0x9e, 0x7b, 0xab,
+ 0x42, 0xee, 0x3f, 0x1d, 0xe8, 0x3c, 0x88, 0x00, 0xc9, 0x09, 0xb4, 0xa3, 0x20, 0xd7, 0x68, 0x47,
+ 0x01, 0x21, 0xd0, 0xe1, 0x6c, 0x8d, 0x79, 0x0c, 0xf3, 0x9b, 0x50, 0xe8, 0x15, 0x89, 0x7d, 0x30,
+ 0x70, 0x21, 0xea, 0xac, 0xe3, 0x48, 0xa5, 0xc8, 0xa5, 0x48, 0x52, 0xda, 0x99, 0xb4, 0xa6, 0x5d,
+ 0xaf, 0x82, 0x90, 0x4b, 0x18, 0xc8, 0x6c, 0x11, 0x47, 0xfe, 0x0a, 0x9f, 0x69, 0xd7, 0xd8, 0x96,
+ 0x80, 0xae, 0x09, 0x79, 0x20, 0x45, 0xc4, 0x53, 0x7a, 0x64, 0x6b, 0x2a, 0xe4, 0x46, 0x3f, 0x7a,
+ 0x07, 0xfb, 0xd1, 0xaf, 0xf7, 0x43, 0x47, 0xd5, 0x3d, 0x0e, 0x13, 0x91, 0x49, 0x3a, 0xb0, 0x51,
+ 0x77, 0x80, 0x7e, 0x8d, 0x94, 0x44, 0x1e, 0x44, 0x3c, 0xa4, 0x30, 0x69, 0x4d, 0xfb, 0x5e, 0x09,
+ 0x90, 0xcf, 0xe0, 0x48, 0x0a, 0x95, 0x66, 0x92, 0x0e, 0x8d, 0x61, 0x2e, 0x91, 0x73, 0xe8, 0xca,
+ 0x04, 0x33, 0x49, 0x8f, 0x0d, 0x6c, 0x05, 0xed, 0x6b, 0x85, 0x28, 0x59, 0x1c, 0x6d, 0x91, 0x8e,
+ 0x4c, 0xf9, 0x25, 0xa0, 0x6b, 0x50, 0x6c, 0x8b, 0xbe, 0xe0, 0xcb, 0x28, 0xa4, 0x27, 0x26, 0x54,
+ 0x05, 0xd1, 0xd6, 0x76, 0x26, 0xba, 0x3b, 0x63, 0x9b, 0xe7, 0x0e, 0x30, 0x79, 0xf2, 0x14, 0x93,
+ 0x25, 0xf3, 0x91, 0x9e, 0xda, 0xd7, 0x1d, 0xa0, 0x47, 0x1c, 0x33, 0x95, 0xfa, 0x8f, 0xe8, 0xaf,
+ 0x22, 0x4e, 0x3f, 0xda, 0x11, 0x57, 0x20, 0xe2, 0xc2, 0xb1, 0x16, 0xd7, 0x22, 0x88, 0x96, 0x11,
+ 0x06, 0x94, 0x18, 0x95, 0x1a, 0x46, 0xa6, 0x30, 0xce, 0xd5, 0x8d, 0xe7, 0x2d, 0x8b, 0xe9, 0x99,
+ 0xa9, 0xa2, 0x09, 0x1b, 0x6f, 0xc2, 0x67, 0x71, 0x31, 0x91, 0xf3, 0xdc, 0x5b, 0x05, 0xd3, 0x39,
+ 0xe9, 0x6e, 0xf9, 0x8f, 0x8c, 0x87, 0xa8, 0xe8, 0x85, 0xcd, 0xa9, 0x02, 0xb9, 0x7f, 0xb5, 0x60,
+ 0x7c, 0xaf, 0x3d, 0xff, 0x54, 0x92, 0x95, 0x42, 0x4f, 0x65, 0xa6, 0x6a, 0x43, 0xc3, 0xbe, 0x57,
+ 0x88, 0xe4, 0x73, 0x38, 0xe1, 0x88, 0x81, 0x44, 0x4c, 0x32, 0x19, 0xb0, 0xd4, 0xb2, 0xb2, 0xef,
+ 0x35, 0x50, 0xf2, 0x05, 0x9c, 0x6a, 0xc4, 0x76, 0x35, 0xd7, 0xfc, 0x60, 0x34, 0x5f, 0xe0, 0x3a,
+ 0x47, 0x4d, 0x85, 0x35, 0x2a, 0xc5, 0x42, 0x34, 0x94, 0x1d, 0x78, 0x55, 0xa8, 0xce, 0x8f, 0x6e,
+ 0x83, 0x1f, 0xee, 0xdf, 0x2d, 0x18, 0xfd, 0x86, 0x98, 0xa8, 0x5d, 0xfe, 0xef, 0xe7, 0xf8, 0xfb,
+ 0xf7, 0xaa, 0x39, 0x8d, 0xde, 0x9e, 0x69, 0x1c, 0xe4, 0xa6, 0x3b, 0x87, 0xd1, 0x7d, 0x82, 0x2c,
+ 0x45, 0x7d, 0x05, 0x3c, 0xdc, 0x90, 0x2b, 0x30, 0x87, 0xc9, 0xcc, 0x60, 0x78, 0x07, 0x33, 0x73,
+ 0xb1, 0xcc, 0xa3, 0x3d, 0x58, 0x0d, 0x03, 0xf5, 0x16, 0x83, 0x3f, 0x4c, 0xcf, 0x3f, 0x21, 0x42,
+ 0xd5, 0xe0, 0xf5, 0x08, 0xf7, 0x30, 0xf4, 0x90, 0x05, 0xa5, 0xff, 0xc3, 0x27, 0xf4, 0x1c, 0xba,
+ 0xf6, 0x24, 0xd8, 0xdb, 0x66, 0x05, 0xf7, 0xa6, 0xea, 0xe4, 0xf5, 0x98, 0xbf, 0xc2, 0xe8, 0x07,
+ 0x8c, 0xb1, 0x5a, 0xd5, 0xe1, 0xa8, 0x97, 0x30, 0x30, 0x81, 0x1e, 0xca, 0xab, 0x5a, 0x02, 0xee,
+ 0x75, 0xdd, 0x9d, 0xfa, 0xff, 0x6d, 0xd0, 0xd5, 0xfe, 0x88, 0x69, 0xce, 0xbd, 0xf7, 0x56, 0xfb,
+ 0x6d, 0xd5, 0x89, 0x22, 0xd7, 0xd0, 0xd5, 0x7b, 0xa4, 0xf2, 0x72, 0xcf, 0x6c, 0xb9, 0x35, 0x7e,
+ 0x7b, 0x56, 0xc3, 0xfd, 0x12, 0x60, 0xb7, 0xb9, 0x9b, 0x37, 0xb4, 0xa9, 0xd4, 0x56, 0xe4, 0xbb,
+ 0xdd, 0x99, 0x49, 0x72, 0xaf, 0xb9, 0xe1, 0x85, 0x35, 0x6c, 0x9c, 0x04, 0xaf, 0xa9, 0x7d, 0xf7,
+ 0x6f, 0x1b, 0x86, 0xda, 0xfb, 0xef, 0x98, 0x6c, 0x23, 0x1f, 0xc9, 0x2d, 0x74, 0xcd, 0x3f, 0x1e,
+ 0x21, 0xd6, 0x41, 0xf5, 0xaf, 0xd4, 0x39, 0xab, 0x61, 0xf9, 0x96, 0x7e, 0x03, 0x50, 0xd2, 0x97,
+ 0xe4, 0x2a, 0xb5, 0x0d, 0x70, 0xf6, 0x80, 0x8a, 0xdc, 0x42, 0xbf, 0xa0, 0x07, 0xf9, 0x68, 0x15,
+ 0x2a, 0x9c, 0x73, 0x5e, 0x40, 0x4a, 0x47, 0x2a, 0x69, 0x5c, 0x44, 0xaa, 0x6d, 0x82, 0xb3, 0x07,
+ 0x34, 0x76, 0x25, 0x15, 0x0a, 0xbb, 0x1a, 0xd7, 0x9c, 0x3d, 0xa0, 0x22, 0x77, 0xd0, 0x2f, 0x46,
+ 0x5a, 0x64, 0x58, 0xe1, 0x89, 0xf3, 0x02, 0x52, 0xb7, 0x2d, 0x72, 0x03, 0xbd, 0xbc, 0xe7, 0xe4,
+ 0xb4, 0x31, 0x82, 0x8d, 0xd3, 0x44, 0xd4, 0xf7, 0xf3, 0x3f, 0x6f, 0x42, 0x21, 0xc2, 0x18, 0x67,
+ 0xa1, 0x88, 0x19, 0x0f, 0x67, 0x22, 0x09, 0xe7, 0xe6, 0x6b, 0x66, 0x91, 0x2d, 0xe7, 0xe9, 0xb3,
+ 0x44, 0x35, 0x5f, 0x71, 0xf1, 0xc4, 0xcd, 0x77, 0x8e, 0x5c, 0x2c, 0x8e, 0xcc, 0xe3, 0xd7, 0xff,
+ 0x05, 0x00, 0x00, 0xff, 0xff, 0x04, 0x8b, 0xcd, 0xc5, 0xfd, 0x08, 0x00, 0x00,
+}
diff --git a/grpc/node.proto b/grpc/node.proto
new file mode 100644
index 000000000..b96e03a58
--- /dev/null
+++ b/grpc/node.proto
@@ -0,0 +1,114 @@
+syntax = "proto3";
+package node;
+option go_package = "google.golang.org/protobuf/types/known/nodepb";
+
+service NodeService {
+ rpc Login(LoginRequest) returns (LoginResponse);
+ rpc CreateNode(CreateNodeReq) returns (CreateNodeRes);
+ rpc ReadNode(ReadNodeReq) returns (ReadNodeRes);
+ rpc UpdateNode(UpdateNodeReq) returns (UpdateNodeRes);
+ rpc DeleteNode(DeleteNodeReq) returns (DeleteNodeRes);
+ rpc GetPeers(GetPeersReq) returns (stream GetPeersRes);
+ rpc CheckIn(CheckInReq) returns (CheckInRes);
+}
+
+message LoginRequest {
+ string macaddress = 1;
+ string password = 2;
+}
+
+message LoginResponse { string accesstoken = 1; }
+
+message Node {
+ string id = 1;
+ string name = 2;
+ string address = 3;
+ int32 listenport = 4;
+ string publickey = 5;
+ string endpoint = 6;
+ string macaddress = 7;
+ string password = 8;
+ string nodegroup = 9;
+ bool ispending = 10;
+ string postup = 11;
+ string preup = 12;
+ int32 keepalive = 13;
+ bool saveconfig = 14;
+ string accesskey = 15;
+ string interface = 16;
+ string lastcheckin = 17;
+ string lastmodified = 18;
+ int32 checkininterval = 19;
+ string localaddress = 20;
+ string postchanges = 21;
+}
+
+message CheckInResponse {
+ bool success = 1;
+ bool needpeerupdate = 2;
+ bool needconfigupdate = 3;
+ string nodemessage = 4;
+ bool ispending = 5;
+}
+
+message PeersResponse {
+ string publickey = 5;
+ string endpoint = 6;
+ string address = 3;
+ int32 listenport = 4;
+ string localaddress = 7;
+ int32 keepalive = 13;
+}
+
+message CreateNodeReq {
+ Node node = 1; // Node id blank
+}
+
+message CreateNodeRes {
+ Node node = 1; // Node id filled in
+}
+
+message UpdateNodeReq {
+ Node node = 1;
+}
+
+message UpdateNodeRes {
+ Node node = 1;
+}
+
+message ReadNodeReq {
+ string macaddress = 1;
+ string group = 2;
+}
+
+message ReadNodeRes {
+ Node node = 1;
+}
+
+message DeleteNodeReq {
+ string macaddress = 1;
+ string groupName = 2;
+}
+
+message DeleteNodeRes {
+ bool success = 1;
+}
+
+message GetPeersReq {
+ string macaddress = 1;
+ string group = 2;
+}
+
+message GetPeersRes {
+ PeersResponse peers = 1;
+}
+
+message CheckInReq {
+ Node node = 1;
+ // bool postchanges = 2;
+}
+
+message CheckInRes {
+ CheckInResponse checkinresponse = 1;
+}
+
diff --git a/grpc/node_grpc.pb.go b/grpc/node_grpc.pb.go
new file mode 100644
index 000000000..fa52ac58a
--- /dev/null
+++ b/grpc/node_grpc.pb.go
@@ -0,0 +1,345 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+
+package nodepb
+
+import (
+ context "context"
+ grpc "google.golang.org/grpc"
+ codes "google.golang.org/grpc/codes"
+ status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+// NodeServiceClient is the client API for NodeService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type NodeServiceClient interface {
+ Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error)
+ CreateNode(ctx context.Context, in *CreateNodeReq, opts ...grpc.CallOption) (*CreateNodeRes, error)
+ ReadNode(ctx context.Context, in *ReadNodeReq, opts ...grpc.CallOption) (*ReadNodeRes, error)
+ UpdateNode(ctx context.Context, in *UpdateNodeReq, opts ...grpc.CallOption) (*UpdateNodeRes, error)
+ DeleteNode(ctx context.Context, in *DeleteNodeReq, opts ...grpc.CallOption) (*DeleteNodeRes, error)
+ GetPeers(ctx context.Context, in *GetPeersReq, opts ...grpc.CallOption) (NodeService_GetPeersClient, error)
+ CheckIn(ctx context.Context, in *CheckInReq, opts ...grpc.CallOption) (*CheckInRes, error)
+}
+
+type nodeServiceClient struct {
+ cc grpc.ClientConnInterface
+}
+
+func NewNodeServiceClient(cc grpc.ClientConnInterface) NodeServiceClient {
+ return &nodeServiceClient{cc}
+}
+
+func (c *nodeServiceClient) Login(ctx context.Context, in *LoginRequest, opts ...grpc.CallOption) (*LoginResponse, error) {
+ out := new(LoginResponse)
+ err := c.cc.Invoke(ctx, "/node.NodeService/Login", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *nodeServiceClient) CreateNode(ctx context.Context, in *CreateNodeReq, opts ...grpc.CallOption) (*CreateNodeRes, error) {
+ out := new(CreateNodeRes)
+ err := c.cc.Invoke(ctx, "/node.NodeService/CreateNode", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *nodeServiceClient) ReadNode(ctx context.Context, in *ReadNodeReq, opts ...grpc.CallOption) (*ReadNodeRes, error) {
+ out := new(ReadNodeRes)
+ err := c.cc.Invoke(ctx, "/node.NodeService/ReadNode", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *nodeServiceClient) UpdateNode(ctx context.Context, in *UpdateNodeReq, opts ...grpc.CallOption) (*UpdateNodeRes, error) {
+ out := new(UpdateNodeRes)
+ err := c.cc.Invoke(ctx, "/node.NodeService/UpdateNode", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *nodeServiceClient) DeleteNode(ctx context.Context, in *DeleteNodeReq, opts ...grpc.CallOption) (*DeleteNodeRes, error) {
+ out := new(DeleteNodeRes)
+ err := c.cc.Invoke(ctx, "/node.NodeService/DeleteNode", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *nodeServiceClient) GetPeers(ctx context.Context, in *GetPeersReq, opts ...grpc.CallOption) (NodeService_GetPeersClient, error) {
+ stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[0], "/node.NodeService/GetPeers", opts...)
+ if err != nil {
+ return nil, err
+ }
+ x := &nodeServiceGetPeersClient{stream}
+ if err := x.ClientStream.SendMsg(in); err != nil {
+ return nil, err
+ }
+ if err := x.ClientStream.CloseSend(); err != nil {
+ return nil, err
+ }
+ return x, nil
+}
+
+type NodeService_GetPeersClient interface {
+ Recv() (*GetPeersRes, error)
+ grpc.ClientStream
+}
+
+type nodeServiceGetPeersClient struct {
+ grpc.ClientStream
+}
+
+func (x *nodeServiceGetPeersClient) Recv() (*GetPeersRes, error) {
+ m := new(GetPeersRes)
+ if err := x.ClientStream.RecvMsg(m); err != nil {
+ return nil, err
+ }
+ return m, nil
+}
+
+func (c *nodeServiceClient) CheckIn(ctx context.Context, in *CheckInReq, opts ...grpc.CallOption) (*CheckInRes, error) {
+ out := new(CheckInRes)
+ err := c.cc.Invoke(ctx, "/node.NodeService/CheckIn", in, out, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+// NodeServiceServer is the server API for NodeService service.
+// All implementations must embed UnimplementedNodeServiceServer
+// for forward compatibility
+type NodeServiceServer interface {
+ Login(context.Context, *LoginRequest) (*LoginResponse, error)
+ CreateNode(context.Context, *CreateNodeReq) (*CreateNodeRes, error)
+ ReadNode(context.Context, *ReadNodeReq) (*ReadNodeRes, error)
+ UpdateNode(context.Context, *UpdateNodeReq) (*UpdateNodeRes, error)
+ DeleteNode(context.Context, *DeleteNodeReq) (*DeleteNodeRes, error)
+ GetPeers(*GetPeersReq, NodeService_GetPeersServer) error
+ CheckIn(context.Context, *CheckInReq) (*CheckInRes, error)
+ mustEmbedUnimplementedNodeServiceServer()
+}
+
+// UnimplementedNodeServiceServer must be embedded to have forward compatible implementations.
+type UnimplementedNodeServiceServer struct {
+}
+
+func (UnimplementedNodeServiceServer) Login(context.Context, *LoginRequest) (*LoginResponse, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method Login not implemented")
+}
+func (UnimplementedNodeServiceServer) CreateNode(context.Context, *CreateNodeReq) (*CreateNodeRes, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method CreateNode not implemented")
+}
+func (UnimplementedNodeServiceServer) ReadNode(context.Context, *ReadNodeReq) (*ReadNodeRes, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method ReadNode not implemented")
+}
+func (UnimplementedNodeServiceServer) UpdateNode(context.Context, *UpdateNodeReq) (*UpdateNodeRes, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method UpdateNode not implemented")
+}
+func (UnimplementedNodeServiceServer) DeleteNode(context.Context, *DeleteNodeReq) (*DeleteNodeRes, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method DeleteNode not implemented")
+}
+func (UnimplementedNodeServiceServer) GetPeers(*GetPeersReq, NodeService_GetPeersServer) error {
+ return status.Errorf(codes.Unimplemented, "method GetPeers not implemented")
+}
+func (UnimplementedNodeServiceServer) CheckIn(context.Context, *CheckInReq) (*CheckInRes, error) {
+ return nil, status.Errorf(codes.Unimplemented, "method CheckIn not implemented")
+}
+func (UnimplementedNodeServiceServer) mustEmbedUnimplementedNodeServiceServer() {}
+
+// UnsafeNodeServiceServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to NodeServiceServer will
+// result in compilation errors.
+type UnsafeNodeServiceServer interface {
+ mustEmbedUnimplementedNodeServiceServer()
+}
+
+func RegisterNodeServiceServer(s grpc.ServiceRegistrar, srv NodeServiceServer) {
+ s.RegisterService(&NodeService_ServiceDesc, srv)
+}
+
+func _NodeService_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(LoginRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(NodeServiceServer).Login(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/node.NodeService/Login",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(NodeServiceServer).Login(ctx, req.(*LoginRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _NodeService_CreateNode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(CreateNodeReq)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(NodeServiceServer).CreateNode(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/node.NodeService/CreateNode",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(NodeServiceServer).CreateNode(ctx, req.(*CreateNodeReq))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _NodeService_ReadNode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(ReadNodeReq)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(NodeServiceServer).ReadNode(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/node.NodeService/ReadNode",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(NodeServiceServer).ReadNode(ctx, req.(*ReadNodeReq))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _NodeService_UpdateNode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(UpdateNodeReq)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(NodeServiceServer).UpdateNode(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/node.NodeService/UpdateNode",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(NodeServiceServer).UpdateNode(ctx, req.(*UpdateNodeReq))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _NodeService_DeleteNode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(DeleteNodeReq)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(NodeServiceServer).DeleteNode(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/node.NodeService/DeleteNode",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(NodeServiceServer).DeleteNode(ctx, req.(*DeleteNodeReq))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _NodeService_GetPeers_Handler(srv interface{}, stream grpc.ServerStream) error {
+ m := new(GetPeersReq)
+ if err := stream.RecvMsg(m); err != nil {
+ return err
+ }
+ return srv.(NodeServiceServer).GetPeers(m, &nodeServiceGetPeersServer{stream})
+}
+
+type NodeService_GetPeersServer interface {
+ Send(*GetPeersRes) error
+ grpc.ServerStream
+}
+
+type nodeServiceGetPeersServer struct {
+ grpc.ServerStream
+}
+
+func (x *nodeServiceGetPeersServer) Send(m *GetPeersRes) error {
+ return x.ServerStream.SendMsg(m)
+}
+
+func _NodeService_CheckIn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(CheckInReq)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(NodeServiceServer).CheckIn(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: "/node.NodeService/CheckIn",
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(NodeServiceServer).CheckIn(ctx, req.(*CheckInReq))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+// NodeService_ServiceDesc is the grpc.ServiceDesc for NodeService service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var NodeService_ServiceDesc = grpc.ServiceDesc{
+ ServiceName: "node.NodeService",
+ HandlerType: (*NodeServiceServer)(nil),
+ Methods: []grpc.MethodDesc{
+ {
+ MethodName: "Login",
+ Handler: _NodeService_Login_Handler,
+ },
+ {
+ MethodName: "CreateNode",
+ Handler: _NodeService_CreateNode_Handler,
+ },
+ {
+ MethodName: "ReadNode",
+ Handler: _NodeService_ReadNode_Handler,
+ },
+ {
+ MethodName: "UpdateNode",
+ Handler: _NodeService_UpdateNode_Handler,
+ },
+ {
+ MethodName: "DeleteNode",
+ Handler: _NodeService_DeleteNode_Handler,
+ },
+ {
+ MethodName: "CheckIn",
+ Handler: _NodeService_CheckIn_Handler,
+ },
+ },
+ Streams: []grpc.StreamDesc{
+ {
+ StreamName: "GetPeers",
+ Handler: _NodeService_GetPeers_Handler,
+ ServerStreams: true,
+ },
+ },
+ Metadata: "grpc/node.proto",
+}
diff --git a/licensing/LICENSE.txt b/licensing/LICENSE.txt
new file mode 100644
index 000000000..4e1383df1
--- /dev/null
+++ b/licensing/LICENSE.txt
@@ -0,0 +1,557 @@
+ Server Side Public License
+ VERSION 1, OCTOBER 16, 2018
+
+ Copyright © 2018 MongoDB, Inc.
+
+ Everyone is permitted to copy and distribute verbatim copies of this
+ license document, but changing it is not allowed.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ “This License” refers to Server Side Public License.
+
+ “Copyright” also means copyright-like laws that apply to other kinds of
+ works, such as semiconductor masks.
+
+ “The Program” refers to any copyrightable work licensed under this
+ License. Each licensee is addressed as “you”. “Licensees” and
+ “recipients” may be individuals or organizations.
+
+ To “modify” a work means to copy from or adapt all or part of the work in
+ a fashion requiring copyright permission, other than the making of an
+ exact copy. The resulting work is called a “modified version” of the
+ earlier work or a work “based on” the earlier work.
+
+ A “covered work” means either the unmodified Program or a work based on
+ the Program.
+
+ To “propagate” a work means to do anything with it that, without
+ permission, would make you directly or secondarily liable for
+ infringement under applicable copyright law, except executing it on a
+ computer or modifying a private copy. Propagation includes copying,
+ distribution (with or without modification), making available to the
+ public, and in some countries other activities as well.
+
+ To “convey” a work means any kind of propagation that enables other
+ parties to make or receive copies. Mere interaction with a user through a
+ computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays “Appropriate Legal Notices” to the
+ extent that it includes a convenient and prominently visible feature that
+ (1) displays an appropriate copyright notice, and (2) tells the user that
+ there is no warranty for the work (except to the extent that warranties
+ are provided), that licensees may convey the work under this License, and
+ how to view a copy of this License. If the interface presents a list of
+ user commands or options, such as a menu, a prominent item in the list
+ meets this criterion.
+
+ 1. Source Code.
+
+ The “source code” for a work means the preferred form of the work for
+ making modifications to it. “Object code” means any non-source form of a
+ work.
+
+ A “Standard Interface” means an interface that either is an official
+ standard defined by a recognized standards body, or, in the case of
+ interfaces specified for a particular programming language, one that is
+ widely used among developers working in that language. The “System
+ Libraries” of an executable work include anything, other than the work as
+ a whole, that (a) is included in the normal form of packaging a Major
+ Component, but which is not part of that Major Component, and (b) serves
+ only to enable use of the work with that Major Component, or to implement
+ a Standard Interface for which an implementation is available to the
+ public in source code form. A “Major Component”, in this context, means a
+ major essential component (kernel, window system, and so on) of the
+ specific operating system (if any) on which the executable work runs, or
+ a compiler used to produce the work, or an object code interpreter used
+ to run it.
+
+ The “Corresponding Source” for a work in object code form means all the
+ source code needed to generate, install, and (for an executable work) run
+ the object code and to modify the work, including scripts to control
+ those activities. However, it does not include the work's System
+ Libraries, or general-purpose tools or generally available free programs
+ which are used unmodified in performing those activities but which are
+ not part of the work. For example, Corresponding Source includes
+ interface definition files associated with source files for the work, and
+ the source code for shared libraries and dynamically linked subprograms
+ that the work is specifically designed to require, such as by intimate
+ data communication or control flow between those subprograms and other
+ parts of the work.
+
+ The Corresponding Source need not include anything that users can
+ regenerate automatically from other parts of the Corresponding Source.
+
+ The Corresponding Source for a work in source code form is that same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+ copyright on the Program, and are irrevocable provided the stated
+ conditions are met. This License explicitly affirms your unlimited
+ permission to run the unmodified Program, subject to section 13. The
+ output from running a covered work is covered by this License only if the
+ output, given its content, constitutes a covered work. This License
+ acknowledges your rights of fair use or other equivalent, as provided by
+ copyright law. Subject to section 13, you may make, run and propagate
+ covered works that you do not convey, without conditions so long as your
+ license otherwise remains in force. You may convey covered works to
+ others for the sole purpose of having them make modifications exclusively
+ for you, or provide you with facilities for running those works, provided
+ that you comply with the terms of this License in conveying all
+ material for which you do not control copyright. Those thus making or
+ running the covered works for you must do so exclusively on your
+ behalf, under your direction and control, on terms that prohibit them
+ from making any copies of your copyrighted material outside their
+ relationship with you.
+
+ Conveying under any other circumstances is permitted solely under the
+ conditions stated below. Sublicensing is not allowed; section 10 makes it
+ unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+ measure under any applicable law fulfilling obligations under article 11
+ of the WIPO copyright treaty adopted on 20 December 1996, or similar laws
+ prohibiting or restricting circumvention of such measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+ circumvention of technological measures to the extent such circumvention is
+ effected by exercising rights under this License with respect to the
+ covered work, and you disclaim any intention to limit operation or
+ modification of the work as a means of enforcing, against the work's users,
+ your or third parties' legal rights to forbid circumvention of
+ technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+ receive it, in any medium, provided that you conspicuously and
+ appropriately publish on each copy an appropriate copyright notice; keep
+ intact all notices stating that this License and any non-permissive terms
+ added in accord with section 7 apply to the code; keep intact all notices
+ of the absence of any warranty; and give all recipients a copy of this
+ License along with the Program. You may charge any price or no price for
+ each copy that you convey, and you may offer support or warranty
+ protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+ produce it from the Program, in the form of source code under the terms
+ of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified it,
+ and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is released
+ under this License and any conditions added under section 7. This
+ requirement modifies the requirement in section 4 to “keep intact all
+ notices”.
+
+ c) You must license the entire work, as a whole, under this License to
+ anyone who comes into possession of a copy. This License will therefore
+ apply, along with any applicable section 7 additional terms, to the
+ whole of the work, and all its parts, regardless of how they are
+ packaged. This License gives no permission to license the work in any
+ other way, but it does not invalidate such permission if you have
+ separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your work
+ need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+ works, which are not by their nature extensions of the covered work, and
+ which are not combined with it such as to form a larger program, in or on
+ a volume of a storage or distribution medium, is called an “aggregate” if
+ the compilation and its resulting copyright are not used to limit the
+ access or legal rights of the compilation's users beyond what the
+ individual works permit. Inclusion of a covered work in an aggregate does
+ not cause this License to apply to the other parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms of
+ sections 4 and 5, provided that you also convey the machine-readable
+ Corresponding Source under the terms of this License, in one of these
+ ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium customarily
+ used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a written
+ offer, valid for at least three years and valid for as long as you
+ offer spare parts or customer support for that product model, to give
+ anyone who possesses the object code either (1) a copy of the
+ Corresponding Source for all the software in the product that is
+ covered by this License, on a durable physical medium customarily used
+ for software interchange, for a price no more than your reasonable cost
+ of physically performing this conveying of source, or (2) access to
+ copy the Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This alternative is
+ allowed only occasionally and noncommercially, and only if you received
+ the object code with such an offer, in accord with subsection 6b.
+
+ d) Convey the object code by offering access from a designated place
+ (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to copy
+ the object code is a network server, the Corresponding Source may be on
+ a different server (operated by you or a third party) that supports
+ equivalent copying facilities, provided you maintain clear directions
+ next to the object code saying where to find the Corresponding Source.
+ Regardless of what server hosts the Corresponding Source, you remain
+ obligated to ensure that it is available for as long as needed to
+ satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided you
+ inform other peers where the object code and Corresponding Source of
+ the work are being offered to the general public at no charge under
+ subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+ from the Corresponding Source as a System Library, need not be included
+ in conveying the object code work.
+
+ A “User Product” is either (1) a “consumer product”, which means any
+ tangible personal property which is normally used for personal, family,
+ or household purposes, or (2) anything designed or sold for incorporation
+ into a dwelling. In determining whether a product is a consumer product,
+ doubtful cases shall be resolved in favor of coverage. For a particular
+ product received by a particular user, “normally used” refers to a
+ typical or common use of that class of product, regardless of the status
+ of the particular user or of the way in which the particular user
+ actually uses, or expects or is expected to use, the product. A product
+ is a consumer product regardless of whether the product has substantial
+ commercial, industrial or non-consumer uses, unless such uses represent
+ the only significant mode of use of the product.
+
+ “Installation Information” for a User Product means any methods,
+ procedures, authorization keys, or other information required to install
+ and execute modified versions of a covered work in that User Product from
+ a modified version of its Corresponding Source. The information must
+ suffice to ensure that the continued functioning of the modified object
+ code is in no case prevented or interfered with solely because
+ modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+ specifically for use in, a User Product, and the conveying occurs as part
+ of a transaction in which the right of possession and use of the User
+ Product is transferred to the recipient in perpetuity or for a fixed term
+ (regardless of how the transaction is characterized), the Corresponding
+ Source conveyed under this section must be accompanied by the
+ Installation Information. But this requirement does not apply if neither
+ you nor any third party retains the ability to install modified object
+ code on the User Product (for example, the work has been installed in
+ ROM).
+
+ The requirement to provide Installation Information does not include a
+ requirement to continue to provide support service, warranty, or updates
+ for a work that has been modified or installed by the recipient, or for
+ the User Product in which it has been modified or installed. Access
+ to a network may be denied when the modification itself materially
+ and adversely affects the operation of the network or violates the
+ rules and protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided, in
+ accord with this section must be in a format that is publicly documented
+ (and with an implementation available to the public in source code form),
+ and must require no special password or key for unpacking, reading or
+ copying.
+
+ 7. Additional Terms.
+
+ “Additional permissions” are terms that supplement the terms of this
+ License by making exceptions from one or more of its conditions.
+ Additional permissions that are applicable to the entire Program shall be
+ treated as though they were included in this License, to the extent that
+ they are valid under applicable law. If additional permissions apply only
+ to part of the Program, that part may be used separately under those
+ permissions, but the entire Program remains governed by this License
+ without regard to the additional permissions. When you convey a copy of
+ a covered work, you may at your option remove any additional permissions
+ from that copy, or from any part of it. (Additional permissions may be
+ written to require their own removal in certain cases when you modify the
+ work.) You may place additional permissions on material, added by you to
+ a covered work, for which you have or can give appropriate copyright
+ permission.
+
+ Notwithstanding any other provision of this License, for material you add
+ to a covered work, you may (if authorized by the copyright holders of
+ that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some trade
+ names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that material
+ by anyone who conveys the material (or modified versions of it) with
+ contractual assumptions of liability to the recipient, for any
+ liability that these contractual assumptions directly impose on those
+ licensors and authors.
+
+ All other non-permissive additional terms are considered “further
+ restrictions” within the meaning of section 10. If the Program as you
+ received it, or any part of it, contains a notice stating that it is
+ governed by this License along with a term that is a further restriction,
+ you may remove that term. If a license document contains a further
+ restriction but permits relicensing or conveying under this License, you
+ may add to a covered work material governed by the terms of that license
+ document, provided that the further restriction does not survive such
+ relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you must
+ place, in the relevant source files, a statement of the additional terms
+ that apply to those files, or a notice indicating where to find the
+ applicable terms. Additional terms, permissive or non-permissive, may be
+ stated in the form of a separately written license, or stated as
+ exceptions; the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+ provided under this License. Any attempt otherwise to propagate or modify
+ it is void, and will automatically terminate your rights under this
+ License (including any patent licenses granted under the third paragraph
+ of section 11).
+
+ However, if you cease all violation of this License, then your license
+ from a particular copyright holder is reinstated (a) provisionally,
+ unless and until the copyright holder explicitly and finally terminates
+ your license, and (b) permanently, if the copyright holder fails to
+ notify you of the violation by some reasonable means prior to 60 days
+ after the cessation.
+
+ Moreover, your license from a particular copyright holder is reinstated
+ permanently if the copyright holder notifies you of the violation by some
+ reasonable means, this is the first time you have received notice of
+ violation of this License (for any work) from that copyright holder, and
+ you cure the violation prior to 30 days after your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+ licenses of parties who have received copies or rights from you under
+ this License. If your rights have been terminated and not permanently
+ reinstated, you do not qualify to receive new licenses for the same
+ material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or run a
+ copy of the Program. Ancillary propagation of a covered work occurring
+ solely as a consequence of using peer-to-peer transmission to receive a
+ copy likewise does not require acceptance. However, nothing other than
+ this License grants you permission to propagate or modify any covered
+ work. These actions infringe copyright if you do not accept this License.
+ Therefore, by modifying or propagating a covered work, you indicate your
+ acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically receives
+ a license from the original licensors, to run, modify and propagate that
+ work, subject to this License. You are not responsible for enforcing
+ compliance by third parties with this License.
+
+ An “entity transaction” is a transaction transferring control of an
+ organization, or substantially all assets of one, or subdividing an
+ organization, or merging organizations. If propagation of a covered work
+ results from an entity transaction, each party to that transaction who
+ receives a copy of the work also receives whatever licenses to the work
+ the party's predecessor in interest had or could give under the previous
+ paragraph, plus a right to possession of the Corresponding Source of the
+ work from the predecessor in interest, if the predecessor has it or can
+ get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the rights
+ granted or affirmed under this License. For example, you may not impose a
+ license fee, royalty, or other charge for exercise of rights granted
+ under this License, and you may not initiate litigation (including a
+ cross-claim or counterclaim in a lawsuit) alleging that any patent claim
+ is infringed by making, using, selling, offering for sale, or importing
+ the Program or any portion of it.
+
+ 11. Patents.
+
+ A “contributor” is a copyright holder who authorizes use under this
+ License of the Program or a work on which the Program is based. The work
+ thus licensed is called the contributor's “contributor version”.
+
+ A contributor's “essential patent claims” are all patent claims owned or
+ controlled by the contributor, whether already acquired or hereafter
+ acquired, that would be infringed by some manner, permitted by this
+ License, of making, using, or selling its contributor version, but do not
+ include claims that would be infringed only as a consequence of further
+ modification of the contributor version. For purposes of this definition,
+ “control” includes the right to grant patent sublicenses in a manner
+ consistent with the requirements of this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+ patent license under the contributor's essential patent claims, to make,
+ use, sell, offer for sale, import and otherwise run, modify and propagate
+ the contents of its contributor version.
+
+ In the following three paragraphs, a “patent license” is any express
+ agreement or commitment, however denominated, not to enforce a patent
+ (such as an express permission to practice a patent or covenant not to
+ sue for patent infringement). To “grant” such a patent license to a party
+ means to make such an agreement or commitment not to enforce a patent
+ against the party.
+
+ If you convey a covered work, knowingly relying on a patent license, and
+ the Corresponding Source of the work is not available for anyone to copy,
+ free of charge and under the terms of this License, through a publicly
+ available network server or other readily accessible means, then you must
+ either (1) cause the Corresponding Source to be so available, or (2)
+ arrange to deprive yourself of the benefit of the patent license for this
+ particular work, or (3) arrange, in a manner consistent with the
+ requirements of this License, to extend the patent license to downstream
+ recipients. “Knowingly relying” means you have actual knowledge that, but
+ for the patent license, your conveying the covered work in a country, or
+ your recipient's use of the covered work in a country, would infringe
+ one or more identifiable patents in that country that you have reason
+ to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+ arrangement, you convey, or propagate by procuring conveyance of, a
+ covered work, and grant a patent license to some of the parties receiving
+ the covered work authorizing them to use, propagate, modify or convey a
+ specific copy of the covered work, then the patent license you grant is
+ automatically extended to all recipients of the covered work and works
+ based on it.
+
+ A patent license is “discriminatory” if it does not include within the
+ scope of its coverage, prohibits the exercise of, or is conditioned on
+ the non-exercise of one or more of the rights that are specifically
+ granted under this License. You may not convey a covered work if you are
+ a party to an arrangement with a third party that is in the business of
+ distributing software, under which you make payment to the third party
+ based on the extent of your activity of conveying the work, and under
+ which the third party grants, to any of the parties who would receive the
+ covered work from you, a discriminatory patent license (a) in connection
+ with copies of the covered work conveyed by you (or copies made from
+ those copies), or (b) primarily for and in connection with specific
+ products or compilations that contain the covered work, unless you
+ entered into that arrangement, or that patent license was granted, prior
+ to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting any
+ implied license or other defenses to infringement that may otherwise be
+ available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+ otherwise) that contradict the conditions of this License, they do not
+ excuse you from the conditions of this License. If you cannot use,
+ propagate or convey a covered work so as to satisfy simultaneously your
+ obligations under this License and any other pertinent obligations, then
+ as a consequence you may not use, propagate or convey it at all. For
+ example, if you agree to terms that obligate you to collect a royalty for
+ further conveying from those to whom you convey the Program, the only way
+ you could satisfy both those terms and this License would be to refrain
+ entirely from conveying the Program.
+
+ 13. Offering the Program as a Service.
+
+ If you make the functionality of the Program or a modified version
+ available to third parties as a service, you must make the Service Source
+ Code available via network download to everyone at no charge, under the
+ terms of this License. Making the functionality of the Program or
+ modified version available to third parties as a service includes,
+ without limitation, enabling third parties to interact with the
+ functionality of the Program or modified version remotely through a
+ computer network, offering a service the value of which entirely or
+ primarily derives from the value of the Program or modified version, or
+ offering a service that accomplishes for users the primary purpose of the
+ Program or modified version.
+
+ “Service Source Code” means the Corresponding Source for the Program or
+ the modified version, and the Corresponding Source for all programs that
+ you use to make the Program or modified version available as a service,
+ including, without limitation, management software, user interfaces,
+ application program interfaces, automation software, monitoring software,
+ backup software, storage software and hosting software, all such that a
+ user could run an instance of the service using the Service Source Code
+ you make available.
+
+ 14. Revised Versions of this License.
+
+ MongoDB, Inc. may publish revised and/or new versions of the Server Side
+ Public License from time to time. Such new versions will be similar in
+ spirit to the present version, but may differ in detail to address new
+ problems or concerns.
+
+ Each version is given a distinguishing version number. If the Program
+ specifies that a certain numbered version of the Server Side Public
+ License “or any later version” applies to it, you have the option of
+ following the terms and conditions either of that numbered version or of
+ any later version published by MongoDB, Inc. If the Program does not
+ specify a version number of the Server Side Public License, you may
+ choose any version ever published by MongoDB, Inc.
+
+ If the Program specifies that a proxy can decide which future versions of
+ the Server Side Public License can be used, that proxy's public statement
+ of acceptance of a version permanently authorizes you to choose that
+ version for the Program.
+
+ Later license versions may give you additional or different permissions.
+ However, no additional obligations are imposed on any author or copyright
+ holder as a result of your choosing to follow a later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING
+ ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF
+ THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO
+ LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU
+ OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided above
+ cannot be given local legal effect according to their terms, reviewing
+ courts shall apply local law that most closely approximates an absolute
+ waiver of all civil liability in connection with the Program, unless a
+ warranty or assumption of liability accompanies a copy of the Program in
+ return for a fee.
+
+ END OF TERMS AND CONDITIONS
diff --git a/main.go b/main.go
new file mode 100644
index 000000000..d96365b83
--- /dev/null
+++ b/main.go
@@ -0,0 +1,130 @@
+//TODO: er is the wrapper of the service method implementation. It is the resUpdate README with new functions
+//TODO: Harden. Add failover for every method and agent calls
+//TODO: Make configurable. Use the config file for settings
+//TODO: Figure out why mongodb keeps failing (log rotation?)
+
+package main
+
+import (
+ "log"
+ "github.com/gravitl/netmaker/controllers"
+ "github.com/gravitl/netmaker/mongoconn"
+ "github.com/gravitl/netmaker/config"
+ "fmt"
+ "os"
+ "net"
+ "context"
+ "sync"
+ "os/signal"
+ service "github.com/gravitl/netmaker/controllers"
+ nodepb "github.com/gravitl/netmaker/grpc"
+ "google.golang.org/grpc"
+)
+//Start MongoDB Connection and start API Request Handler
+func main() {
+ log.Println("Server starting...")
+ mongoconn.ConnectDatabase()
+
+ var waitgroup sync.WaitGroup
+
+ if config.Config.Server.AgentBackend {
+ waitgroup.Add(1)
+ go runGRPC(&waitgroup)
+ }
+
+ //TODO: We need to break up this package for clarity
+ //We need groupController and nodeController, and then
+ //here we will call some third package with HandleRequests
+ //which will just set up and call the other 2
+
+ if config.Config.Server.RestBackend {
+ waitgroup.Add(1)
+ controller.HandleRESTRequests(&waitgroup)
+ }
+
+ if !config.Config.Server.RestBackend && !config.Config.Server.AgentBackend {
+ fmt.Println("Oops! No Server Mode selected. Nothing being served.")
+ }
+ waitgroup.Wait()
+ fmt.Println("Exiting now.")
+}
+
+
+func runGRPC(wg *sync.WaitGroup) {
+
+
+ defer wg.Done()
+
+ // Configure 'log' package to give file name and line number on eg. log.Fatal
+ // Pipe flags to one another (log.LstdFLags = log.Ldate | log.Ltime)
+ log.SetFlags(log.LstdFlags | log.Lshortfile)
+
+ // Start our listener, 50051 is the default gRPC port
+ grpcport := ":50051"
+ if config.Config.Server.GrpcPort != "" {
+ grpcport = ":" + config.Config.Server.GrpcPort
+ }
+ if os.Getenv("GRPC_PORT") != "" {
+ grpcport = ":" + os.Getenv("GRPC_PORT")
+ }
+
+
+ listener, err := net.Listen("tcp", grpcport)
+ // Handle errors if any
+ if err != nil {
+ log.Fatalf("Unable to listen on port" + grpcport + ": %v", err)
+ }
+
+ // Set options, here we can configure things like TLS support
+ //opts := []grpc.ServerOption{}
+ // Create new gRPC server with (blank) options
+ s := grpc.NewServer(
+ authServerUnaryInterceptor(),
+ authServerStreamInterceptor(),
+ )
+ //s := grpc.NewServer(opts...)
+ // Create NodeService type
+ srv := &service.NodeServiceServer{}
+
+ // Register the service with the server
+ nodepb.RegisterNodeServiceServer(s, srv)
+
+ srv.NodeDB = mongoconn.NodeDB
+
+ // Start the server in a child routine
+ go func() {
+ if err := s.Serve(listener); err != nil {
+ log.Fatalf("Failed to serve: %v", err)
+ }
+ }()
+ fmt.Println("Agent Server succesfully started on port " + grpcport + " (gRPC)")
+
+ // Right way to stop the server using a SHUTDOWN HOOK
+ // Create a channel to receive OS signals
+ c := make(chan os.Signal)
+
+ // Relay os.Interrupt to our channel (os.Interrupt = CTRL+C)
+ // Ignore other incoming signals
+ signal.Notify(c, os.Interrupt)
+
+ // Block main routine until a signal is received
+ // As long as user doesn't press CTRL+C a message is not passed and our main routine keeps running
+ <-c
+
+ // After receiving CTRL+C Properly stop the server
+ fmt.Println("Stopping the Agent server...")
+ s.Stop()
+ listener.Close()
+ fmt.Println("Agent server closed..")
+ fmt.Println("Closing MongoDB connection")
+ mongoconn.Client.Disconnect(context.TODO())
+ fmt.Println("MongoDB connection closed.")
+}
+
+func authServerUnaryInterceptor() grpc.ServerOption {
+ return grpc.UnaryInterceptor(controller.AuthServerUnaryInterceptor)
+}
+func authServerStreamInterceptor() grpc.ServerOption {
+ return grpc.StreamInterceptor(controller.AuthServerStreamInterceptor)
+}
+
diff --git a/mesh-diagram.png b/mesh-diagram.png
new file mode 100644
index 000000000..dd63f578c
Binary files /dev/null and b/mesh-diagram.png differ
diff --git a/models/group.go b/models/group.go
new file mode 100644
index 000000000..18827021f
--- /dev/null
+++ b/models/group.go
@@ -0,0 +1,71 @@
+package models
+
+import (
+// "../mongoconn"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+ "time"
+)
+
+//Group Struct
+//At some point, need to replace all instances of Name with something else like Identifier
+type Group struct {
+ ID primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
+ AddressRange string `json:"addressrange" bson:"addressrange" validate:"required,addressrange_valid"`
+ DisplayName string `json:"displayname,omitempty" bson:"displayname,omitempty" validate:"omitempty,displayname_unique,min=1,max=100"`
+ NameID string `json:"nameid" bson:"nameid" validate:"required,nameid_valid,min=1,max=12"`
+ NodesLastModified int64 `json:"nodeslastmodified" bson:"nodeslastmodified"`
+ GroupLastModified int64 `json:"grouplastmodified" bson:"grouplastmodified"`
+ DefaultInterface string `json:"defaulinterface" bson:"defaultinterface"`
+ DefaultListenPort int32 `json:"defaultlistenport,omitempty" bson:"defaultlistenport,omitempty" validate:"omitempty,numeric,min=1024,max=65535"`
+ DefaultPostUp string `json:"defaultpostup" bson:"defaultpostup"`
+ DefaultPreUp string `json:"defaultpreup" bson:"defaultpreup"`
+ DefaultKeepalive int32 `json:"defaultkeepalive" bson:"defaultkeepalive" validate: "omitempty,numeric,max=1000"`
+ DefaultSaveConfig *bool `json:"defaultsaveconfig" bson:"defaultsaveconfig"`
+ AccessKeys []AccessKey `json:"accesskeys" bson:"accesskeys"`
+ AllowManualSignUp *bool `json:"allowmanualsignup" bson:"allowmanualsignup"`
+ DefaultCheckInInterval int32 `json:"checkininterval,omitempty" bson:"checkininterval,omitempty" validate:"omitempty,numeric,min=1,max=100000"`
+}
+
+//TODO:
+//Not sure if we need the below two functions. Got rid of one of the calls. May want to revisit
+func(group *Group) SetNodesLastModified(){
+ group.NodesLastModified = time.Now().Unix()
+}
+
+func(group *Group) SetGroupLastModified(){
+ group.GroupLastModified = time.Now().Unix()
+}
+
+func(group *Group) SetDefaults(){
+ if group.DisplayName == "" {
+ group.DisplayName = group.NameID
+ }
+ if group.DefaultInterface == "" {
+ group.DefaultInterface = "wc-" + group.NameID
+ }
+ if group.DefaultListenPort == 0 {
+ group.DefaultListenPort = 5555
+ }
+ if group.DefaultPreUp == "" {
+
+ }
+ if group.DefaultSaveConfig == nil {
+ defaultsave := true
+ group.DefaultSaveConfig = &defaultsave
+ }
+ if group.DefaultKeepalive == 0 {
+ group.DefaultKeepalive = 20
+ }
+ if group.DefaultPostUp == "" {
+ postup := "sudo wg addconf " + group.DefaultInterface + " /etc/wireguard/peers.conf"
+ group.DefaultPostUp = postup
+ }
+ //Check-In Interval for Nodes, In Seconds
+ if group.DefaultCheckInInterval == 0 {
+ group.DefaultCheckInInterval = 120
+ }
+ if group.AllowManualSignUp == nil {
+ signup := false
+ group.AllowManualSignUp = &signup
+ }
+}
diff --git a/models/node.go b/models/node.go
new file mode 100644
index 000000000..02c9036cb
--- /dev/null
+++ b/models/node.go
@@ -0,0 +1,140 @@
+package models
+
+import (
+ "go.mongodb.org/mongo-driver/bson/primitive"
+ "github.com/gravitl/netmaker/mongoconn"
+ "math/rand"
+ "time"
+ "net"
+ "context"
+ "go.mongodb.org/mongo-driver/bson"
+)
+
+const charset = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+
+var seededRand *rand.Rand = rand.New(
+ rand.NewSource(time.Now().UnixNano()))
+
+//node struct
+type Node struct {
+ ID primitive.ObjectID `json:"_id,omitempty" bson:"_id,omitempty"`
+ Address string `json:"address" bson:"address"`
+ LocalAddress string `json:"localaddress" bson:"localaddress" validate:"localaddress_check"`
+ Name string `json:"name" bson:"name" validate:"omitempty,name_valid,max=12"`
+ ListenPort int32 `json:"listenport" bson:"listenport" validate:"omitempty,numeric,min=1024,max=65535"`
+ PublicKey string `json:"publickey" bson:"publickey" validate:"pubkey_check"`
+ Endpoint string `json:"endpoint" bson:"endpoint" validate:"endpoint_check"`
+ PostUp string `json:"postup" bson:"postup"`
+ PreUp string `json:"preup" bson:"preup"`
+ PersistentKeepalive int32 `json:"persistentkeepalive" bson:"persistentkeepalive" validate: "omitempty,numeric,max=1000"`
+ SaveConfig *bool `json:"saveconfig" bson:"saveconfig"`
+ AccessKey string `json:"accesskey" bson:"accesskey"`
+ Interface string `json:"interface" bson:"interface"`
+ LastModified int64 `json:"lastmodified" bson:"lastmodified"`
+ LastPeerUpdate int64 `json:"lastpeerupdate" bson:"lastpeerupdate"`
+ LastCheckIn int64 `json:"lastcheckin" bson:"lastcheckin"`
+ MacAddress string `json:"macaddress" bson:"macaddress" validate:"required,macaddress_valid,macaddress_unique"`
+ CheckInInterval int32 `json:"checkininterval" bson:"checkininterval"`
+ Password string `json:"password" bson:"password" validate:"password_check"`
+ Group string `json:"group" bson:"group" validate:"group_exists"`
+ IsPending bool `json:"ispending" bson:"ispending"`
+ PostChanges string `json:"postchanges" bson:"postchanges"`
+}
+
+
+//TODO: Contains a fatal error return. Need to change
+//Used in contexts where it's not the Parent group.
+func(node *Node) GetGroup() (Group, error){
+
+ var group Group
+
+ collection := mongoconn.GroupDB
+ //collection := mongoconn.Client.Database("wirecat").Collection("groups")
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+
+ filter := bson.M{"nameid": node.Group}
+ err := collection.FindOne(ctx, filter).Decode(&group)
+
+ defer cancel()
+
+ if err != nil {
+ //log.Fatal(err)
+ return group, err
+ }
+
+ return group, err
+}
+
+
+//TODO:
+//Not sure if below two methods are necessary. May want to revisit
+func(node *Node) SetLastModified(){
+ node.LastModified = time.Now().Unix()
+}
+
+func(node *Node) SetLastCheckIn(){
+ node.LastCheckIn = time.Now().Unix()
+}
+
+func(node *Node) SetLastPeerUpdate(){
+ node.LastPeerUpdate = time.Now().Unix()
+}
+
+func(node *Node) SetDefaultName(){
+ if node.Name == "" {
+ nodeid := StringWithCharset(5, charset)
+ nodename := "node-" + nodeid
+ node.Name = nodename
+ }
+}
+
+//TODO: I dont know why this exists
+//This should exist on the node.go struct. I'm sure there was a reason?
+func(node *Node) SetDefaults() {
+
+ //TODO: Maybe I should make Group a part of the node struct. Then we can just query the Group object for stuff.
+ parentGroup, _ := node.GetGroup()
+
+ if node.ListenPort == 0 {
+ node.ListenPort = parentGroup.DefaultListenPort
+ }
+ if node.PreUp == "" {
+ //Empty because we dont set it
+ //may want to set it to something in the future
+ }
+ //TODO: This is dumb and doesn't work
+ //Need to change
+ if node.SaveConfig == nil {
+ defaultsave := *parentGroup.DefaultSaveConfig
+ node.SaveConfig = &defaultsave
+ }
+ if node.Interface == "" {
+ node.Interface = parentGroup.DefaultInterface
+ }
+ if node.PersistentKeepalive == 0 {
+ node.PersistentKeepalive = parentGroup.DefaultKeepalive
+ }
+ if node.PostUp == "" {
+ postup := parentGroup.DefaultPostUp
+ node.PostUp = postup
+ }
+ node.CheckInInterval = parentGroup.DefaultCheckInInterval
+
+}
+
+func StringWithCharset(length int, charset string) string {
+ b := make([]byte, length)
+ for i := range b {
+ b[i] = charset[seededRand.Intn(len(charset))]
+ }
+ return string(b)
+}
+
+//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
+}
+
diff --git a/models/returnNode.go b/models/returnNode.go
new file mode 100644
index 000000000..290f21b96
--- /dev/null
+++ b/models/returnNode.go
@@ -0,0 +1,21 @@
+//TODO: Either add a returnGroup and returnKey, or delete this
+package models
+
+type ReturnNode struct {
+ Address string `json:"address" bson:"address"`
+ Name string `json:"name" bson:"name"`
+ MacAddress string `json:"macaddress" bson:"macaddress"`
+ LastCheckIn int64 `json:"lastcheckin" bson:"lastcheckin"`
+ LastModified int64 `json:"lastmodified" bson:"lastmodified"`
+ LastPeerUpdate int64 `json:"lastpeerupdate" bson:"lastpeerupdate"`
+ ListenPort int32 `json:"listenport" bson:"listenport"`
+ PublicKey string `json:"publickey" bson:"publickey" validate:"base64"`
+ Endpoint string `json:"endpoint" bson:"endpoint" validate:"required,ipv4"`
+ PostUp string `json:"postup" bson:"postup"`
+ PreUp string `json:"preup" bson:"preup"`
+ PersistentKeepalive int32 `json:"persistentkeepalive" bson:"persistentkeepalive"`
+ SaveConfig *bool `json:"saveconfig" bson:"saveconfig"`
+ Interface string `json:"interface" bson:"interface"`
+ Group string `json:"group" bson:"group"`
+ IsPending *bool `json:"ispending" bson:"ispending"`
+}
diff --git a/models/structs.go b/models/structs.go
new file mode 100644
index 000000000..370e465b7
--- /dev/null
+++ b/models/structs.go
@@ -0,0 +1,91 @@
+package models
+
+import jwt "github.com/dgrijalva/jwt-go"
+
+type AuthParams struct {
+ MacAddress string `json:"macaddress"`
+ Password string `json:"password"`
+}
+
+type User struct {
+ UserName string `json:"username" bson:"username" validate:username_valid,username_unique,min=3`
+ Password string `json:"password" bson:"password" validate:password_check`
+ IsAdmin bool `json:"isadmin" bson:"isadmin"`
+}
+
+type UserAuthParams struct {
+ UserName string `json:"username"`
+ Password string `json:"password"`
+}
+
+type UserClaims struct {
+ IsAdmin bool
+ UserName string
+ jwt.StandardClaims
+}
+
+type SuccessfulUserLoginResponse struct {
+ UserName string
+ AuthToken string
+}
+
+// Claims is a struct that will be encoded to a JWT.
+// jwt.StandardClaims is an embedded type to provide expiry time
+type Claims struct {
+ Group string
+ MacAddress string
+ jwt.StandardClaims
+}
+
+// SuccessfulLoginResponse is struct to send the request response
+type SuccessfulLoginResponse struct {
+ MacAddress string
+ AuthToken string
+}
+
+type ErrorResponse struct {
+ Code int
+ Message string
+}
+
+type NodeAuth struct {
+ Group string
+ Password string
+ MacAddress string
+}
+
+// SuccessResponse is struct for sending error message with code.
+type SuccessResponse struct {
+ Code int
+ Message string
+ Response interface{}
+}
+
+type AccessKey struct {
+ Name string `json:"name" bson:"name"`
+ Value string `json:"value" bson:"value"`
+ Uses int `json:"uses" bson:"uses"`
+}
+
+type DisplayKey struct {
+ Name string `json:"name" bson:"name"`
+ Uses int `json:"uses" bson:"uses"`
+}
+
+type CheckInResponse struct{
+ Success bool `json:"success" bson:"success"`
+ NeedPeerUpdate bool `json:"needpeerupdate" bson:"needpeerupdate"`
+ NeedConfigUpdate bool `json:"needconfigupdate" bson:"needconfigupdate"`
+ NodeMessage string `json:"nodemessage" bson:"nodemessage"`
+ IsPending bool `json:"ispending" bson:"ispending"`
+}
+
+type PeersResponse struct {
+ PublicKey string `json:"publickey" bson:"publickey"`
+ Endpoint string `json:"endpoint" bson:"endpoint"`
+ Address string `json:"address" bson:"address"`
+ LocalAddress string `json:"localaddress" bson:"localaddress"`
+ ListenPort int32 `json:"listenport" bson:"listenport"`
+ KeepAlive int32 `json:"persistentkeepalive" bson:"persistentkeepalive"`
+}
+
diff --git a/mongoconn/mongoconn.go b/mongoconn/mongoconn.go
new file mode 100644
index 000000000..1e951d6a2
--- /dev/null
+++ b/mongoconn/mongoconn.go
@@ -0,0 +1,143 @@
+package mongoconn
+
+import (
+ "context"
+ "encoding/json"
+ "log"
+ "os"
+ "net/http"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+ "github.com/gravitl/netmaker/config"
+)
+
+var Client *mongo.Client
+var NodeDB *mongo.Collection
+var GroupDB *mongo.Collection
+var user string
+var pass string
+var host string
+var port string
+var opts string
+
+func setVars() {
+
+ //defaults
+ user = "admin"
+ pass = "password"
+ host = "localhost"
+ port = "27017"
+ opts = "/?authSource=admin"
+
+ //override with settings from config file
+ if config.Config.MongoConn.User != "" {
+ user = config.Config.MongoConn.User
+ }
+ if config.Config.MongoConn.Pass != "" {
+ pass = config.Config.MongoConn.Pass
+ }
+ if config.Config.MongoConn.Host != "" {
+ host = config.Config.MongoConn.Host
+ }
+ if config.Config.MongoConn.Port != "" {
+ port = config.Config.MongoConn.Port
+ }
+ if config.Config.MongoConn.Opts != "" {
+ opts = config.Config.MongoConn.Opts
+ }
+
+ //override with settings from env
+ if os.Getenv("MONGO_USER") != "" {
+ user = os.Getenv("MONGO_USER")
+ }
+ if os.Getenv("MONGO_PASS") != "" {
+ pass = os.Getenv("MONGO_PASS")
+ }
+ if os.Getenv("MONGO_HOST") != "" {
+ host = os.Getenv("MONGO_HOST")
+ }
+ if os.Getenv("MONGO_PORT") != "" {
+ port = os.Getenv("MONGO_PORT")
+ }
+ if os.Getenv("MONGO_OPTS") != "" {
+ opts = os.Getenv("MONGO_OPTS")
+ }
+}
+
+//TODO: are we even using this besides at startup? Is it truely necessary?
+//TODO: Use config file instead of os.Getenv
+func ConnectDatabase() {
+ log.Println("Database connecting...")
+ // Set client options
+
+ setVars()
+
+ clientOptions := options.Client().ApplyURI( "mongodb://" +
+ user + ":" +
+ pass + "@" +
+ host + ":" +
+ port +
+ opts )
+
+ // Connect to MongoDB
+ client, err := mongo.Connect(context.TODO(), clientOptions)
+ Client = client
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Check the connection
+ err = Client.Ping(context.TODO(), nil)
+
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ NodeDB = Client.Database("wirecat").Collection("nodes")
+ GroupDB = Client.Database("wirecat").Collection("groups")
+
+ log.Println("Database Connected.")
+}
+
+//TODO: IDK if we're using ConnectDB any more.... I think we're just using Client.Database
+//Review and see if this is necessary
+// ConnectDB : This is helper function to connect mongoDB
+func ConnectDB(db string, targetCollection string) *mongo.Collection {
+
+ // Set client options
+ //clientOptions := options.Client().ApplyURI("mongodb://mongoadmin:mongopassword@localhost:27017/?authSource=admin")
+ clientOptions := options.Client().ApplyURI("mongodb://" + os.Getenv("MONGO_USER") + ":" +
+ os.Getenv("MONGO_PASS") + "@" + os.Getenv("MONGO_HOST") + ":" + os.Getenv("MONGO_PORT") + os.Getenv("MONGO_OPTS") )
+
+ // Connect to MongoDB
+ client, err := mongo.Connect(context.TODO(), clientOptions)
+
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ //collection := client.Database("go_rest_api").Collection("wg")
+ collection := client.Database(db).Collection(targetCollection)
+
+ return collection
+}
+
+// ErrorResponse : This is error model.
+type ErrorResponse struct {
+ StatusCode int `json:"status"`
+ ErrorMessage string `json:"message"`
+}
+
+// GetError : This is helper function to prepare error model.
+func GetError(err error, w http.ResponseWriter) {
+
+ var response = ErrorResponse{
+ ErrorMessage: err.Error(),
+ StatusCode: http.StatusInternalServerError,
+ }
+
+ message, _ := json.Marshal(response)
+
+ w.WriteHeader(response.StatusCode)
+ w.Write(message)
+}
diff --git a/netclient/config/config.go b/netclient/config/config.go
new file mode 100644
index 000000000..e19041274
--- /dev/null
+++ b/netclient/config/config.go
@@ -0,0 +1,216 @@
+package config
+
+import (
+// "github.com/davecgh/go-spew/spew"
+ "os"
+ "fmt"
+ "log"
+ "gopkg.in/yaml.v3"
+ homedir "github.com/mitchellh/go-homedir"
+)
+
+var Config *ClientConfig
+
+// Configurations exported
+type ClientConfig struct {
+ Server ServerConfig `yaml:"server"`
+ Node NodeConfig `yaml:"node"`
+}
+type ServerConfig struct {
+ Address string `yaml:"address"`
+ AccessKey string `yaml:"accesskey"`
+}
+
+type NodeConfig struct {
+ Name string `yaml:"name"`
+ Interface string `yaml:"interface"`
+ Group string `yaml:"group"`
+ Password string `yaml:"password"`
+ MacAddress string `yaml:"macaddress"`
+ LocalAddress string `yaml:"localaddress"`
+ WGAddress string `yaml:"wgaddress"`
+ PostUp string `yaml:"postup"`
+ PreUp string `yaml:"preup"`
+ Port int32 `yaml:"port"`
+ KeepAlive int32 `yaml:"keepalive"`
+ PublicKey string `yaml:"publickey"`
+ PrivateKey string `yaml:"privatekey"`
+ Endpoint string `yaml:"endpoint"`
+ PostChanges string `yaml:"postchanges"`
+}
+
+//reading in the env file
+func Write(config *ClientConfig) error{
+ nofile := false
+ home, err := homedir.Dir()
+ if err != nil {
+ log.Fatal(err)
+ }
+ file := fmt.Sprintf(home + "/.wcconfig")
+ f, err := os.OpenFile(file, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
+ if err != nil {
+ nofile = true
+ //fmt.Println("Could not access " + home + "/.wcconfig, proceeding...")
+ }
+ defer f.Close()
+
+ if !nofile {
+ err = yaml.NewEncoder(f).Encode(config)
+ if err != nil {
+ fmt.Println("trouble writing file")
+ return err
+ }
+ } else {
+
+ newf, err := os.Create(home + "/.wcconfig")
+ err = yaml.NewEncoder(newf).Encode(config)
+ defer newf.Close()
+ if err != nil {
+ return err
+ }
+ }
+
+
+ return err
+}
+func WriteServer(server string, accesskey string) error{
+ nofile := false
+ home, err := homedir.Dir()
+ if err != nil {
+ fmt.Println("couldnt find home dir")
+ return err
+ }
+ file := fmt.Sprintf(home + "/.wcconfig")
+ //f, err := os.Open(file)
+ f, err := os.OpenFile(file, os.O_CREATE|os.O_RDWR, 0666)
+ //f, err := ioutil.ReadFile(file)
+ if err != nil {
+ fmt.Println("couldnt open wcconfig")
+ fmt.Println(err)
+ nofile = true
+ //err = nil
+ return err
+ }
+ defer f.Close()
+
+ //cfg := &ClientConfig{}
+ var cfg ClientConfig
+
+ if !nofile {
+ fmt.Println("Writing to existing config file at " + home + "/.wcconfig")
+ decoder := yaml.NewDecoder(f)
+ err = decoder.Decode(&cfg)
+ //err = yaml.Unmarshal(f, &cfg)
+ if err != nil {
+ //fmt.Println(err)
+ //return err
+ }
+ f.Close()
+ f, err = os.OpenFile(file, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0666)
+ if err != nil {
+ fmt.Println("couldnt open wcconfig")
+ fmt.Println(err)
+ nofile = true
+ //err = nil
+ return err
+ }
+ defer f.Close()
+
+ if err != nil {
+ fmt.Println("trouble opening file")
+ fmt.Println(err)
+ }
+
+ cfg.Server.Address = server
+ cfg.Server.AccessKey = accesskey
+
+ err = yaml.NewEncoder(f).Encode(cfg)
+ //_, err = yaml.Marshal(f, &cfg)
+ if err != nil {
+ fmt.Println("trouble encoding file")
+ return err
+ }
+ } else {
+ fmt.Println("Creating new config file at " + home + "/.wcconfig")
+
+ cfg.Server.Address = server
+ cfg.Server.AccessKey = accesskey
+
+ newf, err := os.Create(home + "/.wcconfig")
+ err = yaml.NewEncoder(newf).Encode(cfg)
+ defer newf.Close()
+ if err != nil {
+ return err
+ }
+ }
+
+ return err
+}
+
+
+
+func(config *ClientConfig) ReadConfig() {
+
+ nofile := false
+ home, err := homedir.Dir()
+ if err != nil {
+ log.Fatal(err)
+ }
+ file := fmt.Sprintf(home + "/.wcconfig")
+ //f, err := os.Open(file)
+ f, err := os.OpenFile(file, os.O_RDONLY, 0666)
+ if err != nil {
+ fmt.Println("trouble opening file")
+ fmt.Println(err)
+ nofile = true
+ //fmt.Println("Could not access " + home + "/.wcconfig, proceeding...")
+ }
+ defer f.Close()
+
+ //var cfg ClientConfig
+
+ if !nofile {
+ decoder := yaml.NewDecoder(f)
+ err = decoder.Decode(&config)
+ if err != nil {
+ fmt.Println("no config or invalid")
+ fmt.Println(err)
+ log.Fatal(err)
+ } else {
+ //config = cfg
+ }
+ }
+}
+
+
+func readConfig() *ClientConfig {
+ nofile := false
+ home, err := homedir.Dir()
+ if err != nil {
+ log.Fatal(err)
+ }
+ file := fmt.Sprintf(home + "/.wcconfig")
+ f, err := os.Open(file)
+ if err != nil {
+ nofile = true
+ fmt.Println("Could not access " + home + "/.wcconfig, proceeding...")
+ }
+ defer f.Close()
+
+ var cfg ClientConfig
+
+ if !nofile {
+ decoder := yaml.NewDecoder(f)
+ err = decoder.Decode(&cfg)
+ if err != nil {
+ fmt.Println("trouble decoding file")
+ log.Fatal(err)
+ }
+ }
+ return &cfg
+}
+
+func init() {
+ Config = readConfig()
+}
+
diff --git a/netclient/functions/auth.go b/netclient/functions/auth.go
new file mode 100644
index 000000000..c55ad586d
--- /dev/null
+++ b/netclient/functions/auth.go
@@ -0,0 +1,64 @@
+package functions
+
+import (
+ "github.com/gravitl/netmaker/netclient/config"
+ "fmt"
+ "os"
+ "context"
+ "io/ioutil"
+ "google.golang.org/grpc/metadata"
+ "google.golang.org/grpc/status"
+ "google.golang.org/grpc/codes"
+ nodepb "github.com/gravitl/netmaker/grpc"
+
+)
+
+// CreateJWT func will used to create the JWT while signing in and signing out
+func SetJWT(client nodepb.NodeServiceClient) (context.Context, error) {
+ home, err := os.UserHomeDir()
+ tokentext, err := ioutil.ReadFile(home + "/.wctoken")
+ if err != nil {
+ fmt.Println("Error reading token. Logging in to retrieve new token.")
+ err = AutoLogin(client)
+ if err != nil {
+ return nil, status.Errorf(codes.Unauthenticated, fmt.Sprintf("Something went wrong with Auto Login: %v", err))
+ }
+ tokentext, err = ioutil.ReadFile(home + "/.wctoken")
+ if err != nil {
+ return nil, status.Errorf(codes.Unauthenticated, fmt.Sprintf("Something went wrong: %v", err))
+ }
+ }
+ token := string(tokentext)
+
+ // Anything linked to this variable will transmit request headers.
+ md := metadata.New(map[string]string{"authorization": token})
+ ctx := context.Background()
+ ctx = metadata.NewOutgoingContext(ctx, md)
+ return ctx, nil
+}
+
+func AutoLogin(client nodepb.NodeServiceClient) error {
+ home, err := os.UserHomeDir()
+ nodecfg := config.Config.Node
+ login := &nodepb.LoginRequest{
+ Password: nodecfg.Password,
+ Macaddress: nodecfg.MacAddress,
+ }
+ // RPC call
+ res, err := client.Login(context.TODO(), login)
+ if err != nil {
+ return err
+ }
+ fmt.Printf("Token: %s\n", res.Accesstoken)
+ tokenstring := []byte(res.Accesstoken)
+ err = ioutil.WriteFile(home + "/.wctoken", tokenstring, 0644)
+ if err != nil {
+ return err
+ }
+ return err
+}
+
+type Configuration struct {
+ MacAddress string
+ Password string
+}
diff --git a/netclient/functions/common.go b/netclient/functions/common.go
new file mode 100644
index 000000000..4bfd9a1f2
--- /dev/null
+++ b/netclient/functions/common.go
@@ -0,0 +1,875 @@
+package functions
+
+import (
+ //"github.com/davecgh/go-spew/spew"
+ "fmt"
+ "time"
+ "context"
+ "net/http"
+ "io/ioutil"
+ "io"
+ "strings"
+ "log"
+ "net"
+ "os"
+ "strconv"
+ "os/exec"
+ "github.com/gravitl/netmaker/netclient/config"
+ nodepb "github.com/gravitl/netmaker/grpc"
+ "golang.zx2c4.com/wireguard/wgctrl"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/metadata"
+ "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
+ homedir "github.com/mitchellh/go-homedir"
+)
+
+var (
+ wcclient nodepb.NodeServiceClient
+)
+
+func Install(accesskey string, password string, server string, group string, noauto bool) error {
+
+ wgclient, err := wgctrl.New()
+
+ if err != nil {
+ log.Fatalf("failed to open client: %v", err)
+ }
+ defer wgclient.Close()
+
+ nodecfg := config.Config.Node
+ servercfg := config.Config.Server
+ fmt.Println("SERVER SETTINGS:")
+
+ if server == "" {
+ if servercfg.Address == "" {
+ log.Fatal("no server provided")
+ } else {
+ server = servercfg.Address
+ }
+ }
+ fmt.Println(" Server: " + server)
+
+ if accesskey == "" {
+ if servercfg.AccessKey == "" {
+ fmt.Println("no access key provided.Proceeding anyway.")
+ } else {
+ accesskey = servercfg.AccessKey
+ }
+ }
+ fmt.Println(" AccessKey: " + accesskey)
+ err = config.WriteServer(server, accesskey)
+ if err != nil {
+ fmt.Println("Error encountered while writing Server Config.")
+ return err
+ }
+
+
+ fmt.Println("NODE REQUESTING SETTINGS:")
+ if password == "" {
+ if nodecfg.Password == "" {
+ //create error here
+ log.Fatal("no password provided")
+ } else {
+ password = nodecfg.Password
+ }
+ }
+ fmt.Println(" Password: " + password)
+
+ if group == "badgroup" {
+ if nodecfg.Group == "" {
+ //create error here
+ log.Fatal("no group provided")
+ } else {
+ group = nodecfg.Group
+ }
+ }
+ fmt.Println(" Group: " + group)
+
+ var macaddress string
+ var localaddress string
+ var listenport int32
+ var keepalive int32
+ var publickey wgtypes.Key
+ var privatekey wgtypes.Key
+ var privkeystring string
+ var endpoint string
+ var name string
+ var wginterface string
+
+ if nodecfg.Endpoint == "" {
+ resp, err := http.Get("https://ifconfig.me")
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode == http.StatusOK {
+ bodyBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ endpoint = string(bodyBytes)
+ }
+ } else {
+ endpoint = nodecfg.Endpoint
+ }
+ fmt.Println(" Public Endpoint: " + endpoint)
+
+ if nodecfg.LocalAddress == "" {
+ ifaces, err := net.Interfaces()
+ if err != nil {
+ return err
+ }
+ var local string
+ found := false
+ for _, i := range ifaces {
+ if i.Flags&net.FlagUp == 0 {
+ continue // interface down
+ }
+ if i.Flags&net.FlagLoopback != 0 {
+ continue // loopback interface
+ }
+ addrs, err := i.Addrs()
+ if err != nil {
+ return err
+ }
+ for _, addr := range addrs {
+ var ip net.IP
+ switch v := addr.(type) {
+ case *net.IPNet:
+ if !found {
+ ip = v.IP
+ local = ip.String()
+ found = true
+ }
+ case *net.IPAddr:
+ if !found {
+ ip = v.IP
+ local = ip.String()
+ found = true
+ }
+ }
+ }
+ }
+ localaddress = local
+ } else {
+ localaddress = nodecfg.LocalAddress
+ }
+ fmt.Println(" Local Address: " + localaddress)
+
+ if nodecfg.Name != "" {
+ name = nodecfg.Name
+ }
+ fmt.Println(" Name: " + name)
+
+
+ if nodecfg.Interface != "" {
+ wginterface = nodecfg.Interface
+ }
+ fmt.Println(" Interface: " + wginterface)
+
+ if nodecfg.KeepAlive != 0 {
+ keepalive = nodecfg.KeepAlive
+ }
+ fmt.Println(" KeepAlive: " + wginterface)
+
+
+ if nodecfg.Port != 0 {
+ listenport = nodecfg.Port
+ }
+ fmt.Println(" Port: " + string(listenport))
+
+ if nodecfg.PrivateKey != "" {
+ privkeystring = nodecfg.PrivateKey
+ privatekey, err := wgtypes.ParseKey(nodecfg.PrivateKey)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if nodecfg.PublicKey != "" {
+ publickey, err = wgtypes.ParseKey(nodecfg.PublicKey)
+ if err != nil {
+ log.Fatal(err)
+ }
+ } else {
+ publickey = privatekey.PublicKey()
+ }
+ } else {
+ privatekey, err := wgtypes.GeneratePrivateKey()
+ if err != nil {
+ log.Fatal(err)
+ }
+ privkeystring = privatekey.String()
+ publickey = privatekey.PublicKey()
+ }
+
+ if nodecfg.MacAddress != "" {
+ macaddress = nodecfg.MacAddress
+ } else {
+ macs, err := getMacAddr()
+ if err != nil {
+ return err
+ } else if len(macs) == 0 {
+ log.Fatal()
+ } else {
+ macaddress = macs[0]
+ }
+ }
+ fmt.Println(" Mac Address: " + macaddress)
+ fmt.Println(" Private Key: " + privatekey.String())
+ fmt.Println(" Public Key: " + publickey.String())
+
+
+ var wcclient nodepb.NodeServiceClient
+ var requestOpts grpc.DialOption
+ requestOpts = grpc.WithInsecure()
+ conn, err := grpc.Dial(server, requestOpts)
+ if err != nil {
+ log.Fatalf("Unable to establish client connection to localhost:50051: %v", err)
+ }
+ wcclient = nodepb.NewNodeServiceClient(conn)
+
+ postnode := &nodepb.Node{
+ Password: password,
+ Macaddress: macaddress,
+ Accesskey: accesskey,
+ Nodegroup: group,
+ Listenport: listenport,
+ Keepalive: keepalive,
+ Localaddress: localaddress,
+ Interface: wginterface,
+ Publickey: publickey.String(),
+ Name: name,
+ Endpoint: endpoint,
+ }
+
+ fmt.Println("Writing node settings to wcconfig file.")
+ err = modConfig(postnode)
+ if err != nil {
+ return err
+ }
+
+ res, err := wcclient.CreateNode(
+ context.TODO(),
+ &nodepb.CreateNodeReq{
+ Node: postnode,
+ },
+ )
+ if err != nil {
+ return err
+ }
+ node := res.Node
+ fmt.Println("Setting local config from server response")
+ if err != nil {
+ return err
+ }
+
+ fmt.Println("NODE RECIEVED SETTINGS: ")
+ fmt.Println(" Password: " + node.Password)
+ fmt.Println(" WG Address: " + node.Address)
+ fmt.Println(" Group: " + node.Nodegroup)
+ fmt.Println(" Public Endpoint: " + node.Endpoint)
+ fmt.Println(" Local Address: " + node.Localaddress)
+ fmt.Println(" Name: " + node.Name)
+ fmt.Println(" Interface: " + node.Interface)
+ fmt.Println(" Port: " + strconv.FormatInt(int64(node.Listenport), 10))
+ fmt.Println(" KeepAlive: " + strconv.FormatInt(int64(node.Keepalive), 10))
+ fmt.Println(" Public Key: " + node.Publickey)
+ fmt.Println(" Mac Address: " + node.Macaddress)
+
+ err = modConfig(node)
+ if err != nil {
+ return err
+ }
+
+ if node.Ispending {
+ fmt.Println("Node is marked as PENDING.")
+ fmt.Println("Awaiting approval from Admin before configuring WireGuard.")
+ if !noauto {
+ fmt.Println("Configuring WireCat Service.")
+ err = ConfigureSystemD()
+ return err
+ }
+
+ }
+
+ peers, err := getPeers(node.Macaddress, node.Nodegroup, server)
+
+ if err != nil {
+ return err
+ }
+ fmt.Println("retrived peers, setting wireguard config.")
+ err = storePrivKey(privkeystring)
+ if err != nil {
+ return err
+ }
+ err = initWireguard(node, privkeystring, peers)
+ if err != nil {
+ return err
+ }
+ if !noauto {
+ err = ConfigureSystemD()
+ }
+ if err != nil {
+ return err
+ }
+
+ return err
+}
+func modConfig(node *nodepb.Node) error{
+ modconfig := config.Config
+ modconfig.ReadConfig()
+ nodecfg := modconfig.Node
+ if node.Name != ""{
+ nodecfg.Name = node.Name
+ }
+ if node.Interface != ""{
+ nodecfg.Interface = node.Interface
+ }
+ if node.Nodegroup != ""{
+ nodecfg.Group = node.Nodegroup
+ }
+ if node.Macaddress != ""{
+ nodecfg.MacAddress = node.Macaddress
+ }
+ if node.Localaddress != ""{
+ nodecfg.LocalAddress = node.Localaddress
+ }
+ if node.Listenport != 0{
+ nodecfg.Port = node.Listenport
+ }
+ if node.Keepalive != 0{
+ nodecfg.KeepAlive = node.Keepalive
+ }
+ if node.Publickey != ""{
+ nodecfg.PublicKey = node.Publickey
+ }
+ if node.Endpoint != ""{
+ nodecfg.Endpoint = node.Endpoint
+ }
+ if node.Password != ""{
+ nodecfg.Password = node.Password
+ }
+ if node.Address != ""{
+ nodecfg.WGAddress = node.Address
+ }
+ if node.Postchanges != "" {
+ nodecfg.PostChanges = node.Postchanges
+ }
+ modconfig.Node = nodecfg
+ err := config.Write(modconfig)
+ return err
+}
+
+
+func getMacAddr() ([]string, error) {
+ ifas, err := net.Interfaces()
+ if err != nil {
+ return nil, err
+ }
+ var as []string
+ for _, ifa := range ifas {
+ a := ifa.HardwareAddr.String()
+ if a != "" {
+ as = append(as, a)
+ }
+ }
+ return as, nil
+}
+/*
+func read(macaddress string, group string) error {
+ //this would be used for retrieving state as set by the server.
+}
+
+func checkLocalConfigChange() error {
+
+}
+*/
+
+func initWireguard(node *nodepb.Node, privkey string, peers []wgtypes.PeerConfig) error {
+
+ ipExec, err := exec.LookPath("ip")
+ if err != nil {
+ return err
+ }
+ key, err := wgtypes.ParseKey(privkey)
+ if err != nil {
+ return err
+ }
+
+ wgclient, err := wgctrl.New()
+ modcfg := config.Config
+ modcfg.ReadConfig()
+ nodecfg := modcfg.Node
+ fmt.Println("beginning local WG config")
+
+
+ if err != nil {
+ log.Fatalf("failed to open client: %v", err)
+ }
+ defer wgclient.Close()
+
+ fmt.Println("setting local settings")
+
+ ifacename := node.Interface
+ if nodecfg.Interface != "" {
+ ifacename = nodecfg.Interface
+ } else if node.Interface != "" {
+ ifacename = node.Interface
+ } else {
+ log.Fatal("no interface to configure")
+ }
+ if node.Address == "" {
+ log.Fatal("no address to configure")
+ }
+
+ cmdIPDevLinkAdd := &exec.Cmd {
+ Path: ipExec,
+ Args: []string{ ipExec, "link", "add", "dev", ifacename, "type", "wireguard" },
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+ cmdIPAddrAdd := &exec.Cmd {
+ Path: ipExec,
+ Args: []string{ ipExec, "address", "add", "dev", ifacename, node.Address+"/24"},
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+ err = cmdIPDevLinkAdd.Run()
+ if err != nil && !strings.Contains(err.Error(), "exists") {
+ fmt.Println("Error creating interface")
+ //fmt.Println(err.Error())
+ //return err
+ }
+ err = cmdIPAddrAdd.Run()
+ if err != nil {
+ fmt.Println("Error adding address")
+ //return err
+ }
+
+ var nodeport int
+ nodeport = int(node.Listenport)
+
+ fmt.Println("setting WG config from node and peers")
+
+ //pubkey := privkey.PublicKey()
+ conf := wgtypes.Config{
+ PrivateKey: &key,
+ ListenPort: &nodeport,
+ ReplacePeers: true,
+ Peers: peers,
+ }
+ _, err = wgclient.Device(ifacename)
+ if err != nil {
+ if os.IsNotExist(err) {
+ fmt.Println("Device does not exist: ")
+ fmt.Println(err)
+ } else {
+ log.Fatalf("Unknown config error: %v", err)
+ }
+ }
+
+ fmt.Println("configuring WG device")
+
+ err = wgclient.ConfigureDevice(ifacename, conf)
+
+ if err != nil {
+ if os.IsNotExist(err) {
+ fmt.Println("Device does not exist: ")
+ fmt.Println(err)
+ } else {
+ log.Fatalf("Unknown config error: %v", err)
+ }
+ }
+ cmdIPLinkUp := &exec.Cmd {
+ Path: ipExec,
+ Args: []string{ ipExec, "link", "set", "up", "dev", ifacename},
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+ cmdIPLinkDown := &exec.Cmd {
+ Path: ipExec,
+ Args: []string{ ipExec, "link", "set", "down", "dev", ifacename},
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+ err = cmdIPLinkDown.Run()
+ err = cmdIPLinkUp.Run()
+ if err != nil {
+ return err
+ }
+ return err
+}
+/*
+func reconfigureWireguardSelf(node nodepb.Node) error {
+
+}
+
+func reconfigureWireguardPeers(peers []nodepb.PeersResponse) error {
+
+}
+
+
+func update(node nodepb.Node) error {
+
+}
+
+func updateLocal() error {
+
+}
+*/
+
+func setWGConfig() error {
+ servercfg := config.Config.Server
+ nodecfg := config.Config.Node
+ node := getNode()
+
+ peers, err := getPeers(node.Macaddress, nodecfg.Group, servercfg.Address)
+ if err != nil {
+ return err
+ }
+ privkey, err := retrievePrivKey()
+ if err != nil {
+ return err
+ }
+
+ err = initWireguard(&node, privkey, peers)
+ if err != nil {
+ return err
+ }
+
+ return err
+}
+
+func storePrivKey(key string) error{
+ d1 := []byte(key)
+ err := ioutil.WriteFile("/root/.wckey", d1, 0644)
+ return err
+}
+
+func retrievePrivKey() (string, error) {
+ dat, err := ioutil.ReadFile("/root/.wckey")
+ return string(dat), err
+}
+
+
+func CheckIn() error {
+ node := getNode()
+ nodecfg := config.Config.Node
+ servercfg := config.Config.Server
+
+ var wcclient nodepb.NodeServiceClient
+ var requestOpts grpc.DialOption
+ requestOpts = grpc.WithInsecure()
+ conn, err := grpc.Dial(servercfg.Address, requestOpts)
+ if err != nil {
+ return err
+ log.Fatalf("Unable to establish client connection to localhost:50051: %v", err)
+ }
+ wcclient = nodepb.NewNodeServiceClient(conn)
+
+ ctx := context.Background()
+ fmt.Println("Authenticating with GRPC Server")
+ ctx, err = SetJWT(wcclient)
+ if err != nil {
+ return err
+ log.Fatalf("Failed to authenticate: %v", err)
+ }
+ fmt.Println("Authenticated")
+
+ var header metadata.MD
+
+ checkinres, err := wcclient.CheckIn(
+ ctx,
+ &nodepb.CheckInReq{
+ Node: &node,
+ },
+ grpc.Header(&header),
+ )
+ if err != nil {
+ return err
+ log.Fatalf("Unable to process Check In request: %v", err)
+ }
+ fmt.Println("Checked in.")
+ /*
+ if nodecfg.PostChanges && checkinres.Checkinresponse.Nodeupdated {
+ nodecfg.PostChanges = false
+ modConfig(readres, false)
+ }
+ */
+ if checkinres.Checkinresponse.Ispending {
+ fmt.Println("Node is in pending status. Waiting for Admin approval of node before making furtherupdates.")
+ return err
+ }
+ if checkinres.Checkinresponse.Needconfigupdate {
+ fmt.Println("Server has requested that node update config.")
+ fmt.Println("Updating config from remote server.")
+ req := &nodepb.ReadNodeReq{
+ Macaddress: node.Macaddress,
+ Group: node.Nodegroup,
+ }
+ readres, err := wcclient.ReadNode(ctx, req, grpc.Header(&header))
+ if err != nil {
+ return err
+ log.Fatalf("Error: %v", err)
+ }
+ err = modConfig(readres.Node)
+ if err != nil {
+ return err
+ log.Fatalf("Error: %v", err)
+ }
+ err = setWGConfig()
+ if err != nil {
+ return err
+ log.Fatalf("Error: %v", err)
+ }
+ } else if nodecfg.PostChanges == "true" {
+ fmt.Println("Node has requested to update remote config.")
+ fmt.Println("Posting local config to remote server.")
+ postnode := getNode()
+ req := &nodepb.UpdateNodeReq{
+ Node: &postnode,
+ }
+ res, err := wcclient.UpdateNode(ctx, req, grpc.Header(&header))
+ if err != nil {
+ return err
+ log.Fatalf("Error: %v", err)
+ }
+ res.Node.Postchanges = "false"
+ err = modConfig(res.Node)
+ if err != nil {
+ return err
+ log.Fatalf("Error: %v", err)
+ }
+ err = setWGConfig()
+ if err != nil {
+ return err
+ log.Fatalf("Error: %v", err)
+ }
+ }
+ if checkinres.Checkinresponse.Needpeerupdate {
+ fmt.Println("Server has requested that node update peer list.")
+ fmt.Println("Updating peer list from remote server.")
+ err = setWGConfig()
+ if err != nil {
+ return err
+ log.Fatalf("Unable to process Set Peers request: %v", err)
+ }
+ }
+ return nil
+}
+func getNode() nodepb.Node {
+ modcfg := config.Config
+ modcfg.ReadConfig()
+ nodecfg := modcfg.Node
+ var node nodepb.Node
+
+ node.Name = nodecfg.Name
+ node.Interface = nodecfg.Interface
+ node.Nodegroup = nodecfg.Group
+ node.Localaddress = nodecfg.LocalAddress
+ node.Address = nodecfg.WGAddress
+ node.Listenport = nodecfg.Port
+ node.Keepalive = nodecfg.KeepAlive
+ node.Postup = nodecfg.PostUp
+ node.Preup = nodecfg.PreUp
+ node.Publickey = nodecfg.PublicKey
+ node.Macaddress = nodecfg.MacAddress
+ node.Endpoint = nodecfg.Endpoint
+ node.Password = nodecfg.Password
+
+ //spew.Dump(node)
+
+ return node
+}
+
+
+
+func Remove() error {
+ //need to implement checkin on server side
+ servercfg := config.Config.Server
+ node := config.Config.Node
+ fmt.Println("Deleting remote node with MAC: " + node.MacAddress)
+
+
+ var wcclient nodepb.NodeServiceClient
+ var requestOpts grpc.DialOption
+ requestOpts = grpc.WithInsecure()
+ conn, err := grpc.Dial(servercfg.Address, requestOpts)
+ if err != nil {
+ return err
+ log.Fatalf("Unable to establish client connection to localhost:50051: %v", err)
+ }
+ wcclient = nodepb.NewNodeServiceClient(conn)
+
+ ctx := context.Background()
+ fmt.Println("Authenticating with GRPC Server")
+ ctx, err = SetJWT(wcclient)
+ if err != nil {
+ return err
+ log.Fatalf("Failed to authenticate: %v", err)
+ }
+ fmt.Println("Authenticated")
+
+ var header metadata.MD
+
+ _, err = wcclient.DeleteNode(
+ ctx,
+ &nodepb.DeleteNodeReq{
+ Macaddress: node.MacAddress,
+ GroupName: node.Group,
+ },
+ grpc.Header(&header),
+ )
+ if err != nil {
+ fmt.Println("Encountered error deleting node.")
+ fmt.Println(err)
+ //return err
+ //log.Fatalf("Unable to process Delete request: %v", err)
+ }
+ fmt.Println("Deleted node " + node.MacAddress)
+
+ err = WipeLocal()
+ if err != nil {
+ return err
+ log.Fatalf("Unable to wipe local config: %v", err)
+ }
+ err = RemoveSystemDServices()
+ if err != nil {
+ return err
+ log.Fatalf("Unable to remove systemd services: %v", err)
+ }
+
+ return nil
+}
+
+func WipeLocal() error{
+ nodecfg := config.Config.Node
+ ifacename := nodecfg.Interface
+
+ home, err := homedir.Dir()
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = os.Remove(home + "/.wcconfig")
+ if err != nil {
+ fmt.Println(err)
+ }
+ err = os.Remove(home + "/.wctoken")
+ if err != nil {
+ fmt.Println(err)
+ }
+ ipExec, err := exec.LookPath("ip")
+
+ cmdIPLinkDel := &exec.Cmd {
+ Path: ipExec,
+ Args: []string{ ipExec, "link", "del", ifacename },
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+ err = cmdIPLinkDel.Run()
+ if err != nil {
+ fmt.Println(err)
+ }
+ return err
+
+}
+
+
+func getPeers(macaddress string, group string, server string) ([]wgtypes.PeerConfig, error) {
+ //need to implement checkin on server side
+ var peers []wgtypes.PeerConfig
+ var wcclient nodepb.NodeServiceClient
+ modcfg := config.Config
+ modcfg.ReadConfig()
+ nodecfg := modcfg.Node
+ keepalive := nodecfg.KeepAlive
+ keepalivedur, err := time.ParseDuration(strconv.FormatInt(int64(keepalive), 10) + "s")
+ if err != nil {
+ log.Fatalf("Issue with format of keepalive value. Please update wcconfig: %v", err)
+ }
+
+
+ fmt.Println("Registering with GRPC Server")
+ requestOpts := grpc.WithInsecure()
+ conn, err := grpc.Dial(server, requestOpts)
+ if err != nil {
+ log.Fatalf("Unable to establish client connection to localhost:50051: %v", err)
+ }
+ // Instantiate the BlogServiceClient with our client connection to the server
+ wcclient = nodepb.NewNodeServiceClient(conn)
+
+ req := &nodepb.GetPeersReq{
+ Macaddress: macaddress,
+ Group: group,
+ }
+ ctx := context.Background()
+ fmt.Println("Authenticating with GRPC Server")
+ ctx, err = SetJWT(wcclient)
+ if err != nil {
+ fmt.Println("Failed to authenticate.")
+ return peers, err
+ }
+ var header metadata.MD
+
+ stream, err := wcclient.GetPeers(ctx, req, grpc.Header(&header))
+ if err != nil {
+ return nil, err
+ }
+ fmt.Println("Parsing peers response")
+ for {
+ // stream.Recv returns a pointer to a ListBlogRes at the current iteration
+ res, err := stream.Recv()
+ // If end of stream, break the loop
+
+ if err == io.EOF {
+ break
+ }
+ // if err, return an error
+ if err != nil {
+ if strings.Contains(err.Error(), "mongo: no documents in result") {
+ break
+ } else {
+ fmt.Println("ERROR ENCOUNTERED WITH RESPONSE")
+ fmt.Println(res)
+ return peers, err
+ }
+ }
+ pubkey, err := wgtypes.ParseKey(res.Peers.Publickey)
+ if err != nil {
+ fmt.Println("error parsing key")
+ return peers, err
+ }
+ var peer wgtypes.PeerConfig
+ if keepalive != 0 {
+ peer = wgtypes.PeerConfig{
+ PublicKey: pubkey,
+ PersistentKeepaliveInterval: &keepalivedur,
+ Endpoint: &net.UDPAddr{
+ IP: net.ParseIP(res.Peers.Endpoint),
+ Port: int(res.Peers.Listenport),
+ },
+ ReplaceAllowedIPs: true,
+ AllowedIPs: []net.IPNet{{
+ IP: net.ParseIP(res.Peers.Address),
+ Mask: net.CIDRMask(32, 32),
+ }},
+ }
+ } else {
+ peer = wgtypes.PeerConfig{
+ PublicKey: pubkey,
+ Endpoint: &net.UDPAddr{
+ IP: net.ParseIP(res.Peers.Endpoint),
+ Port: int(res.Peers.Listenport),
+ },
+ ReplaceAllowedIPs: true,
+ AllowedIPs: []net.IPNet{{
+ IP: net.ParseIP(res.Peers.Address),
+ Mask: net.CIDRMask(32, 32),
+ }},
+ }
+ }
+ peers = append(peers, peer)
+
+ }
+ fmt.Println("Finished parsing peers response")
+ return peers, err
+}
diff --git a/netclient/functions/local.go b/netclient/functions/local.go
new file mode 100644
index 000000000..9d59c7fbd
--- /dev/null
+++ b/netclient/functions/local.go
@@ -0,0 +1,229 @@
+package functions
+
+import (
+ //"github.com/davecgh/go-spew/spew"
+ "fmt"
+ "io/ioutil"
+ "io"
+ "log"
+ "os"
+ "os/exec"
+)
+
+func ConfigureSystemD() error {
+
+ path, err := os.Getwd()
+ if err != nil {
+ log.Println(err)
+ return err
+ }
+
+ binarypath := path + "/meshclient"
+
+ _, err = copy(binarypath, "/usr/local/bin/meshclient")
+ if err != nil {
+ log.Println(err)
+ return err
+ }
+
+
+ systemservice := `[Unit]
+Description=Regularly checks for updates in peers and local config
+Wants=wirecat.timer
+
+[Service]
+Type=oneshot
+ExecStart=/usr/local/bin/meshclient -c checkin
+
+[Install]
+WantedBy=multi-user.target
+`
+
+ systemtimer := `[Unit]
+Description=Calls the WireCat Mesh Client Service
+Requires=wirecat.service
+
+[Timer]
+Unit=wirecat.service
+OnCalendar=*:*:0/30
+
+[Install]
+WantedBy=timers.target
+`
+
+ servicebytes := []byte(systemservice)
+ timerbytes := []byte(systemtimer)
+
+ err = ioutil.WriteFile("/etc/systemd/system/wirecat.service", servicebytes, 0644)
+ if err != nil {
+ log.Println(err)
+ return err
+ }
+
+ err = ioutil.WriteFile("/etc/systemd/system/wirecat.timer", timerbytes, 0644)
+ if err != nil {
+ log.Println(err)
+ return err
+ }
+
+ sysExec, err := exec.LookPath("systemctl")
+
+ cmdSysEnableService := &exec.Cmd {
+ Path: sysExec,
+ Args: []string{ sysExec, "enable", "wirecat.service" },
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+ cmdSysStartService := &exec.Cmd {
+ Path: sysExec,
+ Args: []string{ sysExec, "start", "wirecat.service"},
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+ cmdSysDaemonReload := &exec.Cmd {
+ Path: sysExec,
+ Args: []string{ sysExec, "daemon-reload"},
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+ cmdSysEnableTimer := &exec.Cmd {
+ Path: sysExec,
+ Args: []string{ sysExec, "enable", "wirecat.timer" },
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+ cmdSysStartTimer := &exec.Cmd {
+ Path: sysExec,
+ Args: []string{ sysExec, "start", "wirecat.timer"},
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+
+ err = cmdSysEnableService.Run()
+ if err != nil {
+ fmt.Println("Error enabling wirecat.service. Please investigate.")
+ fmt.Println(err)
+ }
+ err = cmdSysStartService.Run()
+ if err != nil {
+ fmt.Println("Error starting wirecat.service. Please investigate.")
+ fmt.Println(err)
+ }
+ err = cmdSysDaemonReload.Run()
+ if err != nil {
+ fmt.Println("Error reloading system daemons. Please investigate.")
+ fmt.Println(err)
+ }
+ err = cmdSysEnableTimer.Run()
+ if err != nil {
+ fmt.Println("Error enabling wirecat.timer. Please investigate.")
+ fmt.Println(err)
+ }
+ err = cmdSysStartTimer.Run()
+ if err != nil {
+ fmt.Println("Error starting wirecat.timer. Please investigate.")
+ fmt.Println(err)
+ }
+ return nil
+}
+
+func RemoveSystemDServices() error {
+ sysExec, err := exec.LookPath("systemctl")
+
+ cmdSysStopService := &exec.Cmd {
+ Path: sysExec,
+ Args: []string{ sysExec, "stop", "wirecat.service" },
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+ cmdSysDisableService := &exec.Cmd {
+ Path: sysExec,
+ Args: []string{ sysExec, "disable", "wirecat.service"},
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+ cmdSysDaemonReload := &exec.Cmd {
+ Path: sysExec,
+ Args: []string{ sysExec, "daemon-reload"},
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+ cmdSysStopTimer := &exec.Cmd {
+ Path: sysExec,
+ Args: []string{ sysExec, "stop", "wirecat.timer" },
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+ cmdSysDisableTimer := &exec.Cmd {
+ Path: sysExec,
+ Args: []string{ sysExec, "disable", "wirecat.timer"},
+ Stdout: os.Stdout,
+ Stderr: os.Stdout,
+ }
+
+ err = cmdSysStopService.Run()
+ if err != nil {
+ fmt.Println("Error stopping wirecat.service. Please investigate.")
+ fmt.Println(err)
+ }
+ err = cmdSysDisableService.Run()
+ if err != nil {
+ fmt.Println("Error disabling wirecat.service. Please investigate.")
+ fmt.Println(err)
+ }
+ err = cmdSysStopTimer.Run()
+ if err != nil {
+ fmt.Println("Error stopping wirecat.timer. Please investigate.")
+ fmt.Println(err)
+ }
+ err = cmdSysDisableTimer.Run()
+ if err != nil {
+ fmt.Println("Error disabling wirecat.timer. Please investigate.")
+ fmt.Println(err)
+ }
+
+ err = os.Remove("/etc/systemd/system/wirecat.service")
+ err = os.Remove("/etc/systemd/system/wirecat.timer")
+ //err = os.Remove("/usr/local/bin/meshclient")
+ if err != nil {
+ fmt.Println("Error removing file. Please investigate.")
+ fmt.Println(err)
+ }
+ err = cmdSysDaemonReload.Run()
+ if err != nil {
+ fmt.Println("Error reloading system daemons. Please investigate.")
+ fmt.Println(err)
+ }
+
+ return err
+
+}
+
+func copy(src, dst string) (int64, error) {
+ sourceFileStat, err := os.Stat(src)
+ if err != nil {
+ return 0, err
+ }
+
+ if !sourceFileStat.Mode().IsRegular() {
+ return 0, fmt.Errorf("%s is not a regular file", src)
+ }
+
+ source, err := os.Open(src)
+ if err != nil {
+ return 0, err
+ }
+ defer source.Close()
+
+ destination, err := os.Create(dst)
+ if err != nil {
+ return 0, err
+ }
+ defer destination.Close()
+ nBytes, err := io.Copy(destination, source)
+ err = os.Chmod(dst, 0755)
+ if err != nil {
+ log.Println(err)
+ }
+ return nBytes, err
+}
diff --git a/netclient/functions/old/create.go b/netclient/functions/old/create.go
new file mode 100644
index 000000000..ded9429ab
--- /dev/null
+++ b/netclient/functions/old/create.go
@@ -0,0 +1,99 @@
+/*
+Copyright © 2021 NAME HERE
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package cmd
+
+import (
+ "fmt"
+ "context"
+ "net/http"
+ "io/ioutil"
+ nodepb "github.com/gravitl/netmaker/grpc"
+ "github.com/spf13/cobra"
+)
+
+// createCmd represents the create command
+var createCmd = &cobra.Command{
+ Use: "create",
+ Short: "Create a new node",
+ Long: `hi`,
+ SilenceUsage: true,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ // Get the data from our flags
+ nodegroup, err := cmd.Flags().GetString("nodegroup")
+ password, err := cmd.Flags().GetString("password")
+ macaddress, err := cmd.Flags().GetString("macaddress")
+ name, err := cmd.Flags().GetString("name")
+ listenport, err := cmd.Flags().GetInt32("listenport")
+ publickey, err := cmd.Flags().GetString("publickey")
+ endpoint, err := cmd.Flags().GetString("endpoint")
+ if err != nil {
+ return err
+ }
+ if endpoint == "" {
+ resp, err := http.Get("https://ifconfig.me")
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode == http.StatusOK {
+ bodyBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ endpoint = string(bodyBytes)
+ }
+ }
+ // Create a blog protobuffer message
+ node := &nodepb.Node{
+ Password: password,
+ Macaddress: macaddress,
+ Nodegroup: nodegroup,
+ Listenport: listenport,
+ Publickey: publickey,
+ Name: name,
+ Endpoint: endpoint,
+ }
+ // RPC call
+ res, err := client.CreateNode(
+ context.TODO(),
+ // wrap the blog message in a CreateBlog request message
+ &nodepb.CreateNodeReq{
+ Node: node,
+ },
+ )
+ if err != nil {
+ return err
+ }
+ fmt.Printf("Node created: %s\n", res.Node.Id)
+ return err
+ },
+}
+
+func init() {
+
+ createCmd.Flags().StringP("name", "n", "", "The node name")
+ createCmd.Flags().StringP("listenport", "l", "", "The wireguard port")
+ createCmd.Flags().StringP("endpoint", "e", "", "The public IP")
+ createCmd.Flags().StringP("macaddress", "m", "", "The local macaddress")
+ createCmd.Flags().StringP("password", "p", "", "The password")
+ createCmd.Flags().StringP("nodegroup", "g", "", "The group this will be added to")
+ createCmd.Flags().StringP("publickey", "k", "", "The wireguard public key")
+ createCmd.MarkFlagRequired("nodegroup")
+ createCmd.MarkFlagRequired("password")
+ createCmd.MarkFlagRequired("macaddress")
+ rootCmd.AddCommand(createCmd)
+
+}
diff --git a/netclient/functions/old/delete.go b/netclient/functions/old/delete.go
new file mode 100644
index 000000000..8352aa346
--- /dev/null
+++ b/netclient/functions/old/delete.go
@@ -0,0 +1,66 @@
+/*
+Copyright © 2021 NAME HERE
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package cmd
+
+import (
+ "fmt"
+ "context"
+ "github.com/gravitl/netmaker/wcagent/functions"
+ nodepb "github.com/gravitl/netmaker/grpc"
+ "github.com/spf13/cobra"
+ "google.golang.org/grpc/metadata"
+ "google.golang.org/grpc"
+)
+
+var deleteCmd = &cobra.Command{
+ Use: "delete",
+ Short: "Delete a Nodeby its MAC",
+ Long: `Delete a node post by it's macaddress in mongodb.
+
+ If no node is found with the MAC it will return a 'Not Found' error`,
+ SilenceUsage: true,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ macaddress, err := cmd.Flags().GetString("macaddress")
+ if err != nil {
+ return err
+ }
+ req := &nodepb.DeleteNodeReq{
+ Macaddress: macaddress,
+ }
+ ctx := context.Background()
+ ctx, err = functions.SetJWT(client)
+ if err != nil {
+ return err
+ }
+
+ var header metadata.MD
+
+ _, err = client.DeleteNode(ctx, req, grpc.Header(&header))
+ if err != nil {
+ return err
+ }
+ fmt.Printf("Succesfully deleted the node with macaddress %s\n", macaddress)
+ return nil
+ },
+}
+
+func init() {
+ deleteCmd.Flags().StringP("macaddress", "m", "", "The macaddress of the node")
+ deleteCmd.MarkFlagRequired("macaddress")
+ rootCmd.AddCommand(deleteCmd)
+}
+
+
diff --git a/netclient/functions/old/getpeers.go b/netclient/functions/old/getpeers.go
new file mode 100644
index 000000000..ee3edaf9a
--- /dev/null
+++ b/netclient/functions/old/getpeers.go
@@ -0,0 +1,97 @@
+/*
+Copyright © 2021 NAME HERE
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package cmd
+
+import (
+ "fmt"
+ nodepb "github.com/gravitl/netmaker/grpc"
+ "context"
+ "io"
+ "github.com/spf13/cobra"
+ "github.com/gravitl/netmaker/wcagent/functions"
+ "google.golang.org/grpc/metadata"
+ "google.golang.org/grpc"
+
+)
+
+// getpeersCmd represents the getpeers command
+var getpeersCmd = &cobra.Command{
+ Use: "getpeers",
+ Short: "A brief description of your command",
+ Long: `A longer description that spans multiple lines and likely contains examples
+and usage of using your command. For example:
+
+Cobra is a CLI library for Go that empowers applications.
+This application is a tool to generate the needed files
+to quickly create a Cobra application.`,
+ SilenceUsage: true,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ fmt.Println("read called")
+ group, err := cmd.Flags().GetString("group")
+ if err != nil {
+ return err
+ }
+ req := &nodepb.GetPeersReq{
+ Group: group,
+ }
+ ctx := context.Background()
+ ctx, err = functions.SetJWT(client)
+ if err != nil {
+ return err
+ }
+
+ var header metadata.MD
+
+ stream, err := client.GetPeers(ctx, req, grpc.Header(&header))
+ if err != nil {
+ return err
+ }
+ //fmt.Println(res)
+
+ for {
+ // stream.Recv returns a pointer to a ListBlogRes at the current iteration
+ res, err := stream.Recv()
+ // If end of stream, break the loop
+ if err == io.EOF {
+ break
+ }
+ // if err, return an error
+ if err != nil {
+ return err
+ }
+ // If everything went well use the generated getter to print the blog message
+ fmt.Println(res.Peers)
+ }
+
+ return nil
+ },
+}
+
+func init() {
+ getpeersCmd.Flags().StringP("group", "g", "", "The group of the node")
+ getpeersCmd.MarkFlagRequired("group")
+ rootCmd.AddCommand(getpeersCmd)
+
+ // Here you will define your flags and configuration settings.
+
+ // Cobra supports Persistent Flags which will work for this command
+ // and all subcommands, e.g.:
+ // getpeersCmd.PersistentFlags().String("foo", "", "A help for foo")
+
+ // Cobra supports local flags which will only run when this command
+ // is called directly, e.g.:
+ // getpeersCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}
diff --git a/netclient/functions/old/login.go b/netclient/functions/old/login.go
new file mode 100644
index 000000000..74428b2a4
--- /dev/null
+++ b/netclient/functions/old/login.go
@@ -0,0 +1,83 @@
+/*
+Copyright © 2021 NAME HERE
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package cmd
+
+import (
+ "fmt"
+ "os"
+ "encoding/json"
+ "io/ioutil"
+ nodepb "github.com/gravitl/netmaker/grpc"
+ "context"
+ "github.com/spf13/cobra"
+)
+
+// loginCmd represents the login command
+var loginCmd = &cobra.Command{
+ Use: "login",
+ Short: "Get auth token",
+ Long: `Get auth token`,
+ SilenceUsage: true,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ password, err := cmd.Flags().GetString("password")
+ macaddress, err := cmd.Flags().GetString("macaddress")
+ if err != nil {
+ return err
+ }
+ home, err := os.UserHomeDir()
+ data := SetConfiguration{
+ MacAddress: macaddress,
+ Password: password,
+ }
+ file, err := json.MarshalIndent(data, "", " ")
+ err = ioutil.WriteFile(home + "/.wcconfig", file, 0644)
+ if err != nil {
+ return err
+ }
+ login := &nodepb.LoginRequest{
+ Password: password,
+ Macaddress: macaddress,
+ }
+ // RPC call
+ res, err := client.Login(context.TODO(), login)
+ if err != nil {
+ return err
+ }
+ fmt.Printf("Token: %s\n", res.Accesstoken)
+ tokenstring := []byte(res.Accesstoken)
+ err = ioutil.WriteFile(home + "/.wctoken", tokenstring, 0644)
+ if err != nil {
+ return err
+ }
+ return err
+
+ },
+}
+
+func init() {
+
+ loginCmd.Flags().StringP("macaddress", "m", "", "The local macaddress")
+ loginCmd.Flags().StringP("password", "p", "", "The password")
+
+ loginCmd.MarkFlagRequired("password")
+ loginCmd.MarkFlagRequired("macaddress")
+ rootCmd.AddCommand(loginCmd)
+}
+
+type SetConfiguration struct {
+ MacAddress string
+ Password string
+}
diff --git a/netclient/functions/old/read.go b/netclient/functions/old/read.go
new file mode 100644
index 000000000..61371710b
--- /dev/null
+++ b/netclient/functions/old/read.go
@@ -0,0 +1,82 @@
+/*
+Copyright © 2021 NAME HERE
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package cmd
+
+import (
+ "fmt"
+ "context"
+ nodepb "github.com/gravitl/netmaker/grpc"
+ "github.com/gravitl/netmaker/wcagent/functions"
+ "github.com/spf13/cobra"
+ "google.golang.org/grpc/metadata"
+ "google.golang.org/grpc"
+)
+
+// readCmd represents the read command
+var readCmd = &cobra.Command{
+ Use: "read",
+ Short: "Find a Node by its Mac Address",
+ Long: `Find a node by it's macaddress, stored in mongoDB.
+
+ If no node is found with the corresponding MAC it will return a 'Not Found' error`,
+ SilenceUsage: true,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ fmt.Println("read called")
+ macaddress, err := cmd.Flags().GetString("macaddress")
+ group, err := cmd.Flags().GetString("group")
+ if err != nil {
+ return err
+ }
+ req := &nodepb.ReadNodeReq{
+ Macaddress: macaddress,
+ Group: group,
+ }
+ ctx := context.Background()
+ ctx, err = functions.SetJWT(client)
+ if err != nil {
+ return err
+ }
+
+ var header metadata.MD
+
+ res, err := client.ReadNode(ctx, req, grpc.Header(&header))
+ if err != nil {
+ return err
+ }
+ fmt.Println(res)
+ return nil
+ },
+}
+
+func init() {
+
+
+ readCmd.Flags().StringP("macaddress", "m", "", "The macaddress of the node")
+ readCmd.Flags().StringP("group", "g", "", "The group of the node")
+ readCmd.MarkFlagRequired("macaddress")
+ readCmd.MarkFlagRequired("group")
+ rootCmd.AddCommand(readCmd)
+
+ // Here you will define your flags and configuration settings.
+
+ // Cobra supports Persistent Flags which will work for this command
+ // and all subcommands, e.g.:
+ // readCmd.PersistentFlags().String("foo", "", "A help for foo")
+
+ // Cobra supports local flags which will only run when this command
+ // is called directly, e.g.:
+ // readCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}
diff --git a/netclient/functions/old/root.go b/netclient/functions/old/root.go
new file mode 100644
index 000000000..eeb9c0f60
--- /dev/null
+++ b/netclient/functions/old/root.go
@@ -0,0 +1,113 @@
+/*
+Copyright © 2021 NAME HERE
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package cmd
+
+import (
+ "fmt"
+ "os"
+ "github.com/spf13/cobra"
+ "context"
+ homedir "github.com/mitchellh/go-homedir"
+ "github.com/spf13/viper"
+ "time"
+ "log"
+ nodepb "github.com/gravitl/netmaker/grpc"
+ "google.golang.org/grpc"
+
+)
+
+
+var cfgFile string
+
+
+var client nodepb.NodeServiceClient
+var requestCtx context.Context
+var requestOpts grpc.DialOption
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+ Use: "grpc-test",
+ Short: "A test cli which may or may not turn into a real one",
+ Long: `huh. this isn't very long at all, is it?`,
+ // Uncomment the following line if your bare application
+ // has an action associated with it:
+ // Run: func(cmd *cobra.Command, args []string) { },
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+ if err := rootCmd.Execute(); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+}
+
+func init() {
+ cobra.OnInitialize(initConfig)
+
+ // Here you will define your flags and configuration settings.
+ // Cobra supports persistent flags, which, if defined here,
+ // will be global for your application.
+
+ rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.blogclient.yaml)")
+
+ // Cobra also supports local flags, which will only run
+ // when this action is called directly.
+ rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+
+ fmt.Println("Starting Node Service Client")
+
+ // Establish context to timeout after 10 seconds if server does not respond
+ requestCtx, _ = context.WithTimeout(context.Background(), 10*time.Second)
+ // Establish insecure grpc options (no TLS)
+ requestOpts = grpc.WithInsecure()
+ // Dial the server, returns a client connection
+ conn, err := grpc.Dial("localhost:50051", requestOpts)
+ if err != nil {
+ log.Fatalf("Unable to establish client connection to localhost:50051: %v", err)
+ }
+ // Instantiate the BlogServiceClient with our client connection to the server
+ client = nodepb.NewNodeServiceClient(conn)
+}
+
+
+// initConfig reads in config file and ENV variables if set.
+func initConfig() {
+ if cfgFile != "" {
+ // Use config file from the flag.
+ viper.SetConfigFile(cfgFile)
+ } else {
+ // Find home directory.
+ home, err := homedir.Dir()
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ // Search config in home directory with name ".grpc-test" (without extension).
+ viper.AddConfigPath(home)
+ viper.SetConfigName(".grpc-test")
+ }
+
+ viper.AutomaticEnv() // read in environment variables that match
+
+ // If a config file is found, read it in.
+ if err := viper.ReadInConfig(); err == nil {
+ fmt.Println("Using config file:", viper.ConfigFileUsed())
+ }
+}
+
diff --git a/netclient/functions/old/update.go b/netclient/functions/old/update.go
new file mode 100644
index 000000000..f17b1ab12
--- /dev/null
+++ b/netclient/functions/old/update.go
@@ -0,0 +1,88 @@
+/*
+Copyright © 2021 NAME HERE
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package cmd
+
+import (
+ "fmt"
+ "context"
+ nodepb "github.com/gravitl/netmaker/grpc"
+ "github.com/spf13/cobra"
+ "github.com/gravitl/netmaker/wcagent/functions"
+ "google.golang.org/grpc/metadata"
+ "google.golang.org/grpc"
+
+)
+
+var updateCmd = &cobra.Command{
+ Use: "update",
+ Short: "Find a Node by its Macaddress",
+ Long: `Find a node by it's mongoDB Unique macaddressentifier.
+
+ If no node is found for the Macaddress it will return a 'Not Found' error`,
+ SilenceUsage: true,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ // Get the flags from CLI
+ nodegroup, err := cmd.Flags().GetString("nodegroup")
+ password, err := cmd.Flags().GetString("password")
+ macaddress, err := cmd.Flags().GetString("macaddress")
+ name, err := cmd.Flags().GetString("name")
+ listenport, err := cmd.Flags().GetInt32("listenport")
+ publickey, err := cmd.Flags().GetString("publickey")
+ endpoint, err := cmd.Flags().GetString("endpoint")
+ // Create an UpdateNodeRequest
+ node := &nodepb.Node{
+ Password: password,
+ Macaddress: macaddress,
+ Nodegroup: nodegroup,
+ Listenport: listenport,
+ Publickey: publickey,
+ Name: name,
+ Endpoint: endpoint,
+ }
+ req := &nodepb.UpdateNodeReq{
+ Node: node,
+ }
+ ctx := context.Background()
+ ctx, err = functions.SetJWT(client)
+ if err != nil {
+ return err
+ }
+
+ var header metadata.MD
+
+ res, err := client.UpdateNode(ctx, req, grpc.Header(&header))
+ if err != nil {
+ return err
+ }
+
+ fmt.Println(res)
+ return nil
+ },
+}
+
+func init() {
+ updateCmd.Flags().StringP("name", "n", "", "The node name")
+ updateCmd.Flags().StringP("listenport", "l", "", "The wireguard port")
+ updateCmd.Flags().StringP("endpoint", "e", "", "The public IP")
+ updateCmd.Flags().StringP("macaddress", "m", "", "The local macaddress")
+ updateCmd.Flags().StringP("password", "p", "", "The password")
+ updateCmd.Flags().StringP("nodegroup", "g", "", "The group this will be added to")
+ updateCmd.Flags().StringP("publickey", "k", "", "The wireguard public key")
+ updateCmd.MarkFlagRequired("nodegroup")
+ updateCmd.MarkFlagRequired("password")
+ updateCmd.MarkFlagRequired("macaddress")
+ rootCmd.AddCommand(updateCmd)
+}
diff --git a/netclient/main.go b/netclient/main.go
new file mode 100644
index 000000000..f0bb8fa2e
--- /dev/null
+++ b/netclient/main.go
@@ -0,0 +1,91 @@
+package main
+
+import (
+ "fmt"
+ "github.com/gravitl/netmaker/netclient/functions"
+ "golang.zx2c4.com/wireguard/wgctrl"
+ nodepb "github.com/gravitl/netmaker/grpc"
+ "flag"
+ "os"
+ "log"
+)
+
+const (
+ // name of the service
+ name = "wcdaemon"
+ description = "Wirecat Daemon Service"
+)
+
+var password string
+var group string
+var server string
+var accesskey string
+
+var (
+ wgclient *wgctrl.Client
+)
+
+var (
+ wcclient nodepb.NodeServiceClient
+)
+
+func main() {
+ tpassword := flag.String("p", "changeme", "This node's password for accessing the server regularly")
+ taccesskey := flag.String("k", "badkey", "an access key generated by the server and used for one-time access (install only)")
+ tserver := flag.String("s", "localhost:50051", "The location (including port) of the remote gRPC server.")
+ tgroup := flag.String("g", "badgroup", "The node group you are attempting to join.")
+ tnoauto := flag.Bool("na", false, "No auto mode. If true, wirecat will not be installed as a system service and you will have to retrieve updates manually via checkin command.")
+ command := flag.String("c", "required", "The command to run")
+
+
+ flag.Parse()
+
+
+ switch *command {
+ case "required":
+ fmt.Println("command flag 'c' is required. Pick one of |install|checkin|update|remove|")
+ os.Exit(1)
+ log.Fatal("Exiting")
+ case "install":
+ fmt.Println("Beginning agent installation.")
+ err := functions.Install(*taccesskey, *tpassword, *tserver, *tgroup, *tnoauto)
+ if err != nil {
+ fmt.Println("Error installing: ", err)
+ fmt.Println("Removing artifacts")
+ err = functions.Remove()
+ if err != nil {
+ fmt.Println("Error removing artifacts: ", err)
+ }
+ os.Exit(1)
+ }
+ case "service-install":
+ fmt.Println("Beginning service installation.")
+ err := functions.ConfigureSystemD()
+ if err != nil {
+ fmt.Println("Error installing service: ", err)
+ os.Exit(1)
+ }
+ case "service-uninstall":
+ fmt.Println("Beginning service uninstall.")
+ err := functions.RemoveSystemDServices()
+ if err != nil {
+ fmt.Println("Error installing service: ", err)
+ os.Exit(1)
+ }
+ case "checkin":
+ fmt.Println("Beginning node check in.")
+ err := functions.CheckIn()
+ if err != nil {
+ fmt.Println("Error checking in: ", err)
+ os.Exit(1)
+ }
+ case "remove":
+ fmt.Println("Beginning node cleanup.")
+ err := functions.Remove()
+ if err != nil {
+ fmt.Println("Error deleting node: ", err)
+ os.Exit(1)
+ }
+ }
+ fmt.Println("Command " + *command + " Executed Successfully")
+}
diff --git a/netclient/test/delscript.sh b/netclient/test/delscript.sh
new file mode 100755
index 000000000..73abcafa0
--- /dev/null
+++ b/netclient/test/delscript.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+sudo ip link del wc-skynet
+
+curl -X DELETE -H "Authorization: Bearer secretkey" -H 'Content-Type: application/json' localhost:8081/api/skynet/nodes/8c:89:a5:03:f0:d7 | jq
+
+sudo cp /root/.wcconfig.bkup /root/.wcconfig
+sudo rm /root/.wctoken
+sudo go run ./main.go remove
+
+sudo wg show
diff --git a/netmaker.png b/netmaker.png
new file mode 100644
index 000000000..7495a6cb2
Binary files /dev/null and b/netmaker.png differ
diff --git a/test/admincreate.sh b/test/admincreate.sh
new file mode 100755
index 000000000..b895cce5b
--- /dev/null
+++ b/test/admincreate.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+USERNAME="nme"
+PASSWORD="testpass"
+
+generate_post_json ()
+{
+ cat <