added host registration endpoint

This commit is contained in:
0xdcarns 2023-02-16 14:27:57 -05:00
parent b8e01b4cda
commit 607198d563
4 changed files with 151 additions and 14 deletions

View file

@ -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())
}
}
}

View file

@ -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
}

View file

@ -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) {

View file

@ -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 {