Files
netmaker/controllers/enrollmentkeys.go
Sayan Mallick c551c487ca New Docs (#3034)
* 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
2024-08-15 11:55:01 +05:30

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