diff --git a/.github/workflows/publish-docker-latest.yml b/.github/workflows/publish-docker-latest.yml new file mode 100644 index 00000000..2aae2ce7 --- /dev/null +++ b/.github/workflows/publish-docker-latest.yml @@ -0,0 +1,33 @@ +name: Publish Docker + +on: + push: + branches: + - 'master' +jobs: + docker: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - + name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - + name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + platforms: linux/amd64, linux/arm64 + push: true + tags: gravitl/netmaker:latest diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml new file mode 100644 index 00000000..5942bf11 --- /dev/null +++ b/.github/workflows/publish-docker.yml @@ -0,0 +1,35 @@ +name: Publish Docker + +on: + push: + branches: + - 'arm-docker' + - 'develop' + - +jobs: + docker: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - + name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - + name: Build and push + uses: docker/build-push-action@v2 + with: + context: . + platforms: linux/amd64, linux/arm64 + push: true + tags: gravitl/netmaker:dev diff --git a/controllers/common.go b/controllers/common.go index d43fa7d8..237ea87b 100644 --- a/controllers/common.go +++ b/controllers/common.go @@ -117,6 +117,7 @@ func ValidateNode(operation string, groupName string, node models.Node) error { 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... diff --git a/controllers/controller.go b/controllers/controller.go index bcf28378..338f765b 100644 --- a/controllers/controller.go +++ b/controllers/controller.go @@ -29,6 +29,7 @@ func HandleRESTRequests(wg *sync.WaitGroup) { userHandlers(r) groupHandlers(r) fileHandlers(r) + serverHandlers(r) port := config.Config.Server.ApiPort if os.Getenv("API_PORT") != "" { diff --git a/controllers/groupHttpController.go b/controllers/groupHttpController.go index 44c0b980..d824aea9 100644 --- a/controllers/groupHttpController.go +++ b/controllers/groupHttpController.go @@ -551,7 +551,7 @@ func createAccessKey(w http.ResponseWriter, r *http.Request) { mongoconn.GetError(errN, w) return } - w.Write([]byte(accesskey.Value)) + w.Write([]byte(accesskey.AccessString)) } //pretty simple get diff --git a/controllers/serverHttpController.go b/controllers/serverHttpController.go new file mode 100644 index 00000000..88374534 --- /dev/null +++ b/controllers/serverHttpController.go @@ -0,0 +1,90 @@ +package controller + +import ( + "github.com/gravitl/netmaker/models" + "github.com/gravitl/netmaker/serverctl" + "github.com/gravitl/netmaker/config" + "encoding/json" + "strings" + "net/http" + "github.com/gorilla/mux" +) + +func serverHandlers(r *mux.Router) { + r.HandleFunc("/api/server/addnetwork/{network}", securityCheckServer(http.HandlerFunc(addNetwork))).Methods("POST") + r.HandleFunc("/api/server/removenetwork/{network}", securityCheckServer(http.HandlerFunc(removeNetwork))).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 securityCheckServer(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.", + } + + 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 || !authenticateMasterServer(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 authenticateMasterServer(tokenString string) bool { + if tokenString == config.Config.Server.MasterKey { + return true + } + return false +} + +func removeNetwork(w http.ResponseWriter, r *http.Request) { + // Set header + w.Header().Set("Content-Type", "application/json") + + // get params + var params = mux.Vars(r) + + success, err := serverctl.RemoveNetwork(params["network"]) + + if err != nil || !success { + json.NewEncoder(w).Encode("Could not remove server from network " + params["network"]) + return + } + + json.NewEncoder(w).Encode("Server removed from network " + params["network"]) +} + +func addNetwork(w http.ResponseWriter, r *http.Request) { + // Set header + w.Header().Set("Content-Type", "application/json") + + // get params + var params = mux.Vars(r) + + success, err := serverctl.AddNetwork(params["network"]) + + if err != nil || !success { + json.NewEncoder(w).Encode("Could not add server to network " + params["network"]) + return + } + + json.NewEncoder(w).Encode("Server added to network " + params["network"]) +} diff --git a/functions/helpers.go b/functions/helpers.go index d7a3a3fc..8a594a21 100644 --- a/functions/helpers.go +++ b/functions/helpers.go @@ -24,6 +24,58 @@ import ( //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 CreateServerToken(network string) (string, error) { + + var group models.Group + var accesskey models.AccessKey + + group, err := GetParentGroup(network) + if err != nil { + return "", err + } + + accesskey.Name = GenKeyName() + accesskey.Value = GenKey() + accesskey.Uses = 1 + gconf, errG := GetGlobalConfig() + if errG != nil { + return "", errG + } + address := "localhost" + gconf.PortGRPC + + accessstringdec := address + "." + network + "." + accesskey.Value + accesskey.AccessString = base64.StdEncoding.EncodeToString([]byte(accessstringdec)) + + group.AccessKeys = append(group.AccessKeys, accesskey) + + collection := mongoconn.Client.Database("netmaker").Collection("groups") + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + + // Create filter + filter := bson.M{"nameid": network} + + // 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 { + return "", errN + } + return accesskey.AccessString, nil +} + func IsFieldUnique(group string, field string, value string) bool { var node models.Node diff --git a/main.go b/main.go index 3211d7f9..ee873ef7 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "log" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/controllers" + "github.com/gravitl/netmaker/serverctl" "github.com/gravitl/netmaker/functions" "github.com/gravitl/netmaker/mongoconn" "github.com/gravitl/netmaker/config" @@ -33,19 +34,22 @@ var PortGRPC string func main() { log.Println("Server starting...") mongoconn.ConnectDatabase() - + installserver := false if config.Config.Server.CreateDefault { - err := createDefaultNetwork() + created, err := createDefaultNetwork() if err != nil { fmt.Printf("Error creating default network: %v", err) } + if created { + installserver = true + } } var waitgroup sync.WaitGroup if config.Config.Server.AgentBackend { waitgroup.Add(1) - go runGRPC(&waitgroup) + go runGRPC(&waitgroup, installserver) } if config.Config.Server.RestBackend { @@ -60,7 +64,7 @@ func main() { } -func runGRPC(wg *sync.WaitGroup) { +func runGRPC(wg *sync.WaitGroup, installserver bool) { defer wg.Done() @@ -126,6 +130,20 @@ func runGRPC(wg *sync.WaitGroup) { }() fmt.Println("Agent Server succesfully started on port " + grpcport + " (gRPC)") + if installserver { + fmt.Println("Adding server to default network") + success, err := serverctl.AddNetwork(config.Config.Server.DefaultNetName) + if err != nil || !success { + fmt.Printf("Error adding to default network: %v", err) + fmt.Println("Unable to add server to network. Continuing.") + } else { + fmt.Println("Server successfully added to default network.") + } + } + fmt.Println("Setup complete. You are ready to begin using netmaker.") + + + // Right way to stop the server using a SHUTDOWN HOOK // Create a channel to receive OS signals c := make(chan os.Signal) @@ -172,15 +190,15 @@ func setGlobalConfig(globalconf models.GlobalConfig) (error) { return nil } -func createDefaultNetwork() error { - +func createDefaultNetwork() (bool, error) { + iscreated := false exists, err := functions.GroupExists(config.Config.Server.DefaultNetName) if exists || err != nil { fmt.Println("Default group already exists") fmt.Println("Skipping default group create") - return err + return iscreated, err } else { var group models.Group @@ -207,7 +225,10 @@ func createDefaultNetwork() error { defer cancel() } - return err + if err == nil { + iscreated = true + } + return iscreated, err } diff --git a/netclient/functions/common.go b/netclient/functions/common.go index a8e417ba..385bba75 100644 --- a/netclient/functions/common.go +++ b/netclient/functions/common.go @@ -73,9 +73,7 @@ func GetFreePort(rangestart int32) (int32, error){ return portno, err } - -func Install(accesskey string, password string, server string, group string, noauto bool, accesstoken string) error { - +func Install(accesskey string, password string, server string, group string, noauto bool, accesstoken string, inputname string) error { tserver := "" tnetwork := "" @@ -234,6 +232,9 @@ func Install(accesskey string, password string, server string, group string, noa if nodecfg.Name != "" { name = nodecfg.Name } + if inputname != "" && inputname != "noname" { + name = inputname + } fmt.Println(" Name: " + name) diff --git a/netclient/main.go b/netclient/main.go index e64b83ac..b9568534 100644 --- a/netclient/main.go +++ b/netclient/main.go @@ -36,6 +36,7 @@ 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)") taccesstoken := flag.String("t", "badtoken", "an token generated by the server and used for one-time access (install only)") + tname := flag.String("name", "noname", "give the node a name at runtime") tserver := flag.String("s", "localhost:50051", "The location (including port) of the remote gRPC server.") tnetwork := flag.String("n", "nonetwork", "The node group you are attempting to join.") tnoauto := flag.Bool("na", false, "No auto mode. If true, netmclient will not be installed as a system service and you will have to retrieve updates manually via checkin command.") @@ -107,7 +108,7 @@ func main() { } fmt.Println("Beginning agent installation.") - err := functions.Install(*taccesskey, *tpassword, *tserver, *tnetwork, *tnoauto, *taccesstoken) + err := functions.Install(*taccesskey, *tpassword, *tserver, *tnetwork, *tnoauto, *taccesstoken, *tname) if err != nil { fmt.Println("Error installing: ", err) fmt.Println("Cleaning up (uninstall)") diff --git a/serverctl/serverctl.go b/serverctl/serverctl.go new file mode 100644 index 00000000..7ab218bc --- /dev/null +++ b/serverctl/serverctl.go @@ -0,0 +1,80 @@ +package serverctl + +import ( + "fmt" + "github.com/gravitl/netmaker/functions" + "io" + "net/http" + "os" + "os/exec" +) + + +func DownloadNetclient() error { + + // Get the data + resp, err := http.Get("https://github.com/gravitl/netmaker/releases/download/develop/netclient") + if err != nil { + return err + } + defer resp.Body.Close() + + // Create the file + out, err := os.Create("/etc/netclient/netclient") + if err != nil { + return err + } + defer out.Close() + + // Write the body to file + _, err = io.Copy(out, resp.Body) + return err +} +func RemoveNetwork(network string) (bool, error) { + _, err := os.Stat("/etc/netclient/netclient") + if err != nil { + return false, err + } + cmdoutput, err := exec.Command("/etc/netclient/netclient","-c","remove","-n",network).Output() + if err != nil { + fmt.Println(string(cmdoutput)) + return false, err + } + fmt.Println("Server removed from network " + network) + return true, err + +} + +func AddNetwork(network string) (bool, error) { + _, err := os.Stat("/etc/netclient") + if os.IsNotExist(err) { + os.Mkdir("/etc/netclient", 744) + } else if err != nil { + fmt.Println("couldnt find or create /etc/netclient") + return false, err + } + token, err := functions.CreateServerToken(network) + if err != nil { + return false, err + } + _, err = os.Stat("/etc/netclient/netclient") + if os.IsNotExist(err) { + err = DownloadNetclient() + if err != nil { + return false, err + } + } + err = os.Chmod("/etc/netclient/netclient", 0755) + if err != nil { + return false, err + } + cmdoutput, err := exec.Command("/etc/netclient/netclient","-c","install","-t",token,"-name","netmaker").Output() + if err != nil { + fmt.Println(string(cmdoutput)) + return false, err + } + fmt.Println("Server added to network " + network) + return true, err +} + +