Browse Source

added host registration endpoint

0xdcarns 2 years ago
parent
commit
607198d563
4 changed files with 151 additions and 14 deletions
  1. 135 6
      controllers/enrollmentkeys.go
  2. 2 2
      logic/enrollmentkey.go
  3. 3 4
      logic/enrollmentkey_test.go
  4. 11 2
      models/enrollment_key.go

+ 135 - 6
controllers/enrollmentkeys.go

@@ -2,18 +2,23 @@ package controller
 
 import (
 	"encoding/json"
+	"fmt"
 	"net/http"
+	"time"
 
 	"github.com/gorilla/mux"
 	"github.com/gravitl/netmaker/logger"
 	"github.com/gravitl/netmaker/logic"
+	"github.com/gravitl/netmaker/models"
+	"github.com/gravitl/netmaker/mq"
 	"github.com/gravitl/netmaker/servercfg"
 )
 
 func enrollmentKeyHandlers(r *mux.Router) {
+	r.HandleFunc("/api/v1/enrollment-keys", logic.SecurityCheck(true, http.HandlerFunc(createEnrollmentKey))).Methods(http.MethodPost)
 	r.HandleFunc("/api/v1/enrollment-keys", logic.SecurityCheck(true, http.HandlerFunc(getEnrollmentKeys))).Methods(http.MethodGet)
 	r.HandleFunc("/api/v1/enrollment-keys/{keyID}", logic.SecurityCheck(true, http.HandlerFunc(deleteEnrollmentKey))).Methods(http.MethodDelete)
-	r.HandleFunc("/api/v1/host/register", logic.SecurityCheck(true, http.HandlerFunc(handleHostRegister))).Methods(http.MethodPost)
+	r.HandleFunc("/api/v1/host/register/{token}", logic.SecurityCheck(true, http.HandlerFunc(handleHostRegister))).Methods(http.MethodPost)
 }
 
 // swagger:route GET /api/v1/enrollment-keys enrollmentKeys getEnrollmentKeys
@@ -71,7 +76,45 @@ func deleteEnrollmentKey(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 }
 
-// swagger:route DELETE /api/v1/enrollment-keys/{keyID} enrollmentKeys deleteEnrollmentKey
+// swagger:route POST /api/v1/enrollment-keys enrollmentKeys createEnrollmentKey
+//
+// Creates an EnrollmentKey for hosts to use on Netmaker server.
+//
+//			Schemes: https
+//
+//			Security:
+//	  		oauth
+//
+//			Responses:
+//				200: createEnrollmentKeyResponse
+func createEnrollmentKey(w http.ResponseWriter, r *http.Request) {
+
+	var enrollmentKeyBody models.APIEnrollmentKey
+
+	err := json.NewDecoder(r.Body).Decode(&enrollmentKeyBody)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "error decoding request body: ",
+			err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	var newTime time.Time
+	if enrollmentKeyBody.Expiration > 0 {
+		newTime = time.Unix(enrollmentKeyBody.Expiration, 0)
+	}
+
+	newEnrollmentKey, err := logic.CreateEnrollmentKey(enrollmentKeyBody.UsesRemaining, newTime, enrollmentKeyBody.Networks, enrollmentKeyBody.Tags, enrollmentKeyBody.Unlimited)
+	if err != nil {
+		logger.Log(0, r.Header.Get("user"), "failed to create enrollment key:", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	logger.Log(2, r.Header.Get("user"), "created enrollment key")
+	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(newEnrollmentKey)
+}
+
+// swagger:route POST /api/v1/enrollment-keys/{token} enrollmentKeys deleteEnrollmentKey
 //
 // Deletes a Netclient host from Netmaker server.
 //
@@ -84,13 +127,99 @@ func deleteEnrollmentKey(w http.ResponseWriter, r *http.Request) {
 //				200: hostRegisterResponse
 func handleHostRegister(w http.ResponseWriter, r *http.Request) {
 	var params = mux.Vars(r)
-	keyID := params["keyID"]
-	err := logic.DeleteEnrollmentKey(keyID)
+	token := params["token"]
+	// check if token exists
+	enrollmentKey, err := logic.DeTokenize(token)
 	if err != nil {
-		logger.Log(0, r.Header.Get("user"), "failed to remove enrollment key: ", err.Error())
+		logger.Log(0, "invalid enrollment key used", token, err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	// get the host
+	var newHost models.Host
+	if err = json.NewDecoder(r.Body).Decode(&newHost); err != nil {
+		logger.Log(0, r.Header.Get("user"), "error decoding request body: ",
+			err.Error())
 		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
 		return
 	}
-	logger.Log(2, r.Header.Get("user"), "deleted enrollment key", keyID)
+	// check if host already exists
+	if ok := logic.HostExists(&newHost); ok {
+		logger.Log(0, "host", newHost.ID.String(), newHost.Name, "attempted to re-register")
+		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("host already exists"), "badrequest"))
+		return
+	}
+	// version check
+	if !logic.IsVersionComptatible(newHost.Version) || newHost.TrafficKeyPublic == nil {
+		err := fmt.Errorf("incompatible netclient")
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
+		return
+	}
+	key, keyErr := logic.RetrievePublicTrafficKey()
+	if keyErr != nil {
+		logger.Log(0, "error retrieving key:", keyErr.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+	// use the token
+	if ok := logic.TryToUseEnrollmentKey(enrollmentKey); !ok {
+		logger.Log(0, "host", newHost.ID.String(), newHost.Name, "failed registration")
+		logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("invalid enrollment key"), "badrequest"))
+		return
+	}
+	// register host
+	logic.CheckHostPorts(&newHost)
+	if err = logic.CreateHost(&newHost); err != nil {
+		logger.Log(0, "host", newHost.ID.String(), newHost.Name, "failed registration -", err.Error())
+		logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
+		return
+	}
+
+	// ready the response
+	server := servercfg.GetServerInfo()
+	server.TrafficKey = key
+	logger.Log(2, r.Header.Get("user"), "deleted enrollment key", token)
 	w.WriteHeader(http.StatusOK)
+	json.NewEncoder(w).Encode(&server)
+	// notify host of changes, peer and node updates
+	go checkNetRegAndHostUpdate(enrollmentKey.Networks, &newHost)
+}
+
+// run through networks and send a host update
+func checkNetRegAndHostUpdate(networks []string, h *models.Host) {
+	// publish host update through MQ
+	if servercfg.IsMessageQueueBackend() {
+		if err := mq.HostUpdate(&models.HostUpdate{
+			Action: models.UpdateHost,
+			Host:   *h,
+		}); err != nil {
+			logger.Log(0, "failed to send host update after registration:", h.ID.String(), err.Error())
+		}
+	}
+
+	for i := range networks {
+		if ok, _ := logic.NetworkExists(networks[i]); ok {
+			newNode, err := logic.UpdateHostNetwork(h, networks[i], true)
+			if err != nil {
+				logger.Log(0, "failed to add host to network:", h.ID.String(), h.Name, networks[i], err.Error())
+				continue
+			}
+			logger.Log(1, "added new node", newNode.ID.String(), "to host", h.Name)
+			if servercfg.IsMessageQueueBackend() {
+				if err = mq.HostUpdate(&models.HostUpdate{
+					Action: models.JoinHostToNetwork,
+					Host:   *h,
+					Node:   *newNode,
+				}); err != nil {
+					logger.Log(0, "failed to send host update to", h.ID.String(), networks[i], err.Error())
+				}
+			}
+		}
+	}
+
+	if servercfg.IsMessageQueueBackend() {
+		if err := mq.PublishPeerUpdate(); err != nil {
+			logger.Log(0, "failed to publish peer update after host registration -", err.Error())
+		}
+	}
 }

+ 2 - 2
logic/enrollmentkey.go

@@ -191,9 +191,9 @@ func getUniqueEnrollmentID() (string, error) {
 	if err != nil {
 		return "", err
 	}
-	newID := ncutils.MakeRandomString(32)
+	newID := ncutils.MakeRandomString(models.EnrollmentKeyLength)
 	for _, ok := currentKeys[newID]; ok; {
-		newID = ncutils.MakeRandomString(32)
+		newID = ncutils.MakeRandomString(models.EnrollmentKeyLength)
 	}
 	return newID, nil
 }

+ 3 - 4
logic/enrollmentkey_test.go

@@ -135,8 +135,8 @@ func TestTokenize_EnrollmentKeys(t *testing.T) {
 	database.InitializeDatabase()
 	defer database.CloseDB()
 	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, true)
-	const defaultValue = "MwEtpqTSrGd4HTO3ahYDTExKAehh6udJ"
-	const b64value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0V0cHFUU3JHZDRIVE8zYWhZRFRFeEtBZWhoNnVkSiJ9"
+	const defaultValue = "MwE5MwE5MwE5MwE5MwE5MwE5MwE5MwE5"
+	const b64value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0U1TXdFNU13RTVNd0U1TXdFNU13RTVNd0U1TXdFNSJ9"
 	const serverAddr = "api.myserver.com"
 	t.Run("Can_Not_Tokenize_Nil_Key", func(t *testing.T) {
 		err := Tokenize(nil, "ServerAddress")
@@ -168,8 +168,7 @@ func TestDeTokenize_EnrollmentKeys(t *testing.T) {
 	database.InitializeDatabase()
 	defer database.CloseDB()
 	newKey, _ := CreateEnrollmentKey(0, time.Time{}, []string{"mynet", "skynet"}, nil, true)
-	//const defaultValue = "MwEtpqTSrGd4HTO3ahYDTExKAehh6udJ"
-	const b64Value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0V0cHFUU3JHZDRIVE8zYWhZRFRFeEtBZWhoNnVkSiJ9"
+	const b64Value = "eyJzZXJ2ZXIiOiJhcGkubXlzZXJ2ZXIuY29tIiwidmFsdWUiOiJNd0U1TXdFNU13RTVNd0U1TXdFNU13RTVNd0U1TXdFNSJ9"
 	const serverAddr = "api.myserver.com"
 
 	t.Run("Can_Not_DeTokenize", func(t *testing.T) {

+ 11 - 2
models/enrollment_key.go

@@ -11,10 +11,10 @@ type EnrollmentToken struct {
 	Value  string `json:"value"`
 }
 
-// EnrollmentKeyLength - the length of an enrollment key
+// EnrollmentKeyLength - the length of an enrollment key - 62^16 unique possibilities
 const EnrollmentKeyLength = 32
 
-// EnrollmentKey - the
+// EnrollmentKey - the key used to register hosts and join them to specific networks
 type EnrollmentKey struct {
 	Expiration    time.Time `json:"expiration"`
 	UsesRemaining int       `json:"uses_remaining"`
@@ -25,6 +25,15 @@ type EnrollmentKey struct {
 	Token         string    `json:"token,omitempty"` // B64 value of EnrollmentToken
 }
 
+// APIEnrollmentKey - used to create enrollment keys via API
+type APIEnrollmentKey struct {
+	Expiration    int64    `json:"expiration"`
+	UsesRemaining int      `json:"uses_remaining"`
+	Networks      []string `json:"networks"`
+	Unlimited     bool     `json:"unlimited"`
+	Tags          []string `json:"tags"`
+}
+
 // EnrollmentKey.IsValid - checks if the key is still valid to use
 func (k *EnrollmentKey) IsValid() bool {
 	if k == nil {