mirror of
https://github.com/gravitl/netmaker.git
synced 2025-09-27 05:08:11 +08:00

* New Docs CSS update and Dockerfile to include docs folder flash of unrendered text fix markdown docs ignore docs/docs.go improving the docs generation github actions for docs generation go runner version fix updated docs.yml update repo action updated updated actions and dns docs dns complete More docs update Complete docs and updated workflow Update documentation Tue Aug 6 11:17:42 UTC 2024 Update documentation Thu Aug 8 12:26:57 UTC 2024 clean up clean up Dockerfile clean up Updated workflow Updated workflow Update docs.yml Update docs.yml * requested changes * changed ingress gateway to remote access gateway
360 lines
12 KiB
Go
360 lines
12 KiB
Go
package controller
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/go-playground/validator/v10"
|
|
"github.com/google/uuid"
|
|
"github.com/gorilla/mux"
|
|
|
|
"github.com/gravitl/netmaker/auth"
|
|
"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"
|
|
"golang.org/x/exp/slog"
|
|
)
|
|
|
|
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/{token}", http.HandlerFunc(handleHostRegister)).
|
|
Methods(http.MethodPost)
|
|
r.HandleFunc("/api/v1/enrollment-keys/{keyID}", logic.SecurityCheck(true, http.HandlerFunc(updateEnrollmentKey))).
|
|
Methods(http.MethodPut)
|
|
}
|
|
|
|
// @Summary Lists all EnrollmentKeys for admins
|
|
// @Router /api/v1/enrollment-keys [get]
|
|
// @Tags EnrollmentKeys
|
|
// @Security oauth
|
|
// @Success 200 {array} models.EnrollmentKey
|
|
// @Failure 500 {object} models.ErrorResponse
|
|
func getEnrollmentKeys(w http.ResponseWriter, r *http.Request) {
|
|
keys, err := logic.GetAllEnrollmentKeys()
|
|
if err != nil {
|
|
logger.Log(0, r.Header.Get("user"), "failed to fetch enrollment keys: ", err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
|
|
ret := []*models.EnrollmentKey{}
|
|
for _, key := range keys {
|
|
key := key
|
|
if err = logic.Tokenize(key, servercfg.GetAPIHost()); err != nil {
|
|
logger.Log(0, r.Header.Get("user"), "failed to get token values for keys:", err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
ret = append(ret, key)
|
|
}
|
|
// return JSON/API formatted keys
|
|
logger.Log(2, r.Header.Get("user"), "fetched enrollment keys")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(ret)
|
|
}
|
|
|
|
// @Summary Deletes an EnrollmentKey from Netmaker server
|
|
// @Router /api/v1/enrollment-keys/{keyid} [delete]
|
|
// @Tags EnrollmentKeys
|
|
// @Security oauth
|
|
// @Param keyid path string true "Enrollment Key ID"
|
|
// @Success 200
|
|
// @Failure 500 {object} models.ErrorResponse
|
|
func deleteEnrollmentKey(w http.ResponseWriter, r *http.Request) {
|
|
params := mux.Vars(r)
|
|
keyID := params["keyID"]
|
|
err := logic.DeleteEnrollmentKey(keyID)
|
|
if err != nil {
|
|
logger.Log(0, r.Header.Get("user"), "failed to remove enrollment key: ", err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
logger.Log(2, r.Header.Get("user"), "deleted enrollment key", keyID)
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
// @Summary Creates an EnrollmentKey for hosts to register with server and join networks
|
|
// @Router /api/v1/enrollment-keys [post]
|
|
// @Tags EnrollmentKeys
|
|
// @Security oauth
|
|
// @Param body body models.APIEnrollmentKey true "Enrollment Key parameters"
|
|
// @Success 200 {object} models.EnrollmentKey
|
|
// @Failure 400 {object} models.ErrorResponse
|
|
// @Failure 500 {object} models.ErrorResponse
|
|
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)
|
|
}
|
|
v := validator.New()
|
|
err = v.Struct(enrollmentKeyBody)
|
|
if err != nil {
|
|
logger.Log(0, r.Header.Get("user"), "error validating request body: ",
|
|
err.Error())
|
|
logic.ReturnErrorResponse(
|
|
w,
|
|
r,
|
|
logic.FormatError(
|
|
fmt.Errorf("validation error: name length must be between 3 and 32: %w", err),
|
|
"badrequest",
|
|
),
|
|
)
|
|
return
|
|
}
|
|
|
|
if existingKeys, err := logic.GetAllEnrollmentKeys(); err != nil {
|
|
logger.Log(0, r.Header.Get("user"), "error validating request body: ",
|
|
err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
} else {
|
|
// check if any tags are duplicate
|
|
existingTags := make(map[string]struct{})
|
|
for _, existingKey := range existingKeys {
|
|
for _, t := range existingKey.Tags {
|
|
existingTags[t] = struct{}{}
|
|
}
|
|
}
|
|
for _, t := range enrollmentKeyBody.Tags {
|
|
if _, ok := existingTags[t]; ok {
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("key names must be unique"), "badrequest"))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
relayId := uuid.Nil
|
|
if enrollmentKeyBody.Relay != "" {
|
|
relayId, err = uuid.Parse(enrollmentKeyBody.Relay)
|
|
if err != nil {
|
|
logger.Log(0, r.Header.Get("user"), "error parsing relay id: ", err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
}
|
|
|
|
newEnrollmentKey, err := logic.CreateEnrollmentKey(
|
|
enrollmentKeyBody.UsesRemaining,
|
|
newTime,
|
|
enrollmentKeyBody.Networks,
|
|
enrollmentKeyBody.Tags,
|
|
enrollmentKeyBody.Unlimited,
|
|
relayId,
|
|
)
|
|
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
|
|
}
|
|
|
|
if err = logic.Tokenize(newEnrollmentKey, servercfg.GetAPIHost()); 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)
|
|
}
|
|
|
|
// @Summary Updates an EnrollmentKey. Updates are only limited to the relay to use
|
|
// @Router /api/v1/enrollment-keys/{keyid} [put]
|
|
// @Tags EnrollmentKeys
|
|
// @Security oauth
|
|
// @Param keyid path string true "Enrollment Key ID"
|
|
// @Param body body models.APIEnrollmentKey true "Enrollment Key parameters"
|
|
// @Success 200 {object} models.EnrollmentKey
|
|
// @Failure 400 {object} models.ErrorResponse
|
|
// @Failure 500 {object} models.ErrorResponse
|
|
func updateEnrollmentKey(w http.ResponseWriter, r *http.Request) {
|
|
var enrollmentKeyBody models.APIEnrollmentKey
|
|
params := mux.Vars(r)
|
|
keyId := params["keyID"]
|
|
|
|
err := json.NewDecoder(r.Body).Decode(&enrollmentKeyBody)
|
|
if err != nil {
|
|
slog.Error("error decoding request body", "error", err)
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
|
|
relayId := uuid.Nil
|
|
if enrollmentKeyBody.Relay != "" {
|
|
relayId, err = uuid.Parse(enrollmentKeyBody.Relay)
|
|
if err != nil {
|
|
slog.Error("error parsing relay id", "error", err)
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
}
|
|
|
|
newEnrollmentKey, err := logic.UpdateEnrollmentKey(keyId, relayId)
|
|
if err != nil {
|
|
slog.Error("failed to update enrollment key", "error", err)
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
|
|
if err = logic.Tokenize(newEnrollmentKey, servercfg.GetAPIHost()); err != nil {
|
|
slog.Error("failed to update enrollment key", "error", err)
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
|
|
slog.Info("updated enrollment key", "id", keyId)
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(newEnrollmentKey)
|
|
}
|
|
|
|
// @Summary Handles a Netclient registration with server and add nodes accordingly
|
|
// @Router /api/v1/host/register/{token} [post]
|
|
// @Tags EnrollmentKeys
|
|
// @Security oauth
|
|
// @Param token path string true "Enrollment Key Token"
|
|
// @Param body body models.Host true "Host registration parameters"
|
|
// @Success 200 {object} models.RegisterResponse
|
|
// @Failure 400 {object} models.ErrorResponse
|
|
// @Failure 500 {object} models.ErrorResponse
|
|
func handleHostRegister(w http.ResponseWriter, r *http.Request) {
|
|
params := mux.Vars(r)
|
|
token := params["token"]
|
|
logger.Log(0, "received registration attempt with token", token)
|
|
// check if token exists
|
|
enrollmentKey, err := logic.DeTokenize(token)
|
|
if err != nil {
|
|
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
|
|
}
|
|
// check if host already exists
|
|
hostExists := false
|
|
if hostExists = logic.HostExists(&newHost); hostExists && len(enrollmentKey.Networks) == 0 {
|
|
logger.Log(
|
|
0,
|
|
"host",
|
|
newHost.ID.String(),
|
|
newHost.Name,
|
|
"attempted to re-register with no networks",
|
|
)
|
|
logic.ReturnErrorResponse(
|
|
w,
|
|
r,
|
|
logic.FormatError(fmt.Errorf("host already exists"), "badrequest"),
|
|
)
|
|
return
|
|
}
|
|
// version check
|
|
if !logic.IsVersionCompatible(newHost.Version) {
|
|
err := fmt.Errorf("bad client version on register: %s", newHost.Version)
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
|
|
return
|
|
}
|
|
if newHost.TrafficKeyPublic == nil && newHost.OS != models.OS_Types.IoT {
|
|
err := fmt.Errorf("missing traffic key")
|
|
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
|
|
}
|
|
if !hostExists {
|
|
newHost.PersistentKeepalive = models.DefaultPersistentKeepAlive
|
|
// register host
|
|
//logic.CheckHostPorts(&newHost)
|
|
// create EMQX credentials and ACLs for host
|
|
if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
|
|
if err := mq.GetEmqxHandler().CreateEmqxUser(newHost.ID.String(), newHost.HostPass); err != nil {
|
|
logger.Log(0, "failed to create host credentials for EMQX: ", err.Error())
|
|
return
|
|
}
|
|
}
|
|
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
|
|
}
|
|
} else {
|
|
// need to revise the list of networks from key
|
|
// based on the ones host currently has
|
|
networksToAdd := []string{}
|
|
currentNets := logic.GetHostNetworks(newHost.ID.String())
|
|
for _, newNet := range enrollmentKey.Networks {
|
|
if !logic.StringSliceContains(currentNets, newNet) {
|
|
networksToAdd = append(networksToAdd, newNet)
|
|
}
|
|
}
|
|
enrollmentKey.Networks = networksToAdd
|
|
currHost, err := logic.GetHost(newHost.ID.String())
|
|
if err != nil {
|
|
slog.Error("failed registration", "hostID", newHost.ID.String(), "hostName", newHost.Name, "error", err.Error())
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
logic.UpdateHostFromClient(&newHost, currHost)
|
|
err = logic.UpsertHost(currHost)
|
|
if err != nil {
|
|
slog.Error("failed to update host", "id", currHost.ID, "error", err)
|
|
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
|
|
return
|
|
}
|
|
}
|
|
// ready the response
|
|
server := servercfg.GetServerInfo()
|
|
server.TrafficKey = key
|
|
response := models.RegisterResponse{
|
|
ServerConf: server,
|
|
RequestedHost: newHost,
|
|
}
|
|
logger.Log(0, newHost.Name, newHost.ID.String(), "registered with Netmaker")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(&response)
|
|
// notify host of changes, peer and node updates
|
|
go auth.CheckNetRegAndHostUpdate(enrollmentKey.Networks, &newHost, enrollmentKey.Relay)
|
|
}
|