netmaker/controllers/enrollmentkeys.go

373 lines
12 KiB
Go
Raw Normal View History

2023-02-16 05:32:16 +08:00
package controller
import (
"encoding/json"
2023-02-17 03:27:57 +08:00
"fmt"
2023-02-16 05:32:16 +08:00
"net/http"
2023-02-17 03:27:57 +08:00
"time"
2023-02-16 05:32:16 +08:00
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
2023-02-16 05:32:16 +08:00
"github.com/gorilla/mux"
2023-04-13 23:36:13 +08:00
"github.com/gravitl/netmaker/auth"
2023-02-16 05:32:16 +08:00
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
2023-02-17 03:27:57 +08:00
"github.com/gravitl/netmaker/models"
2023-02-18 00:54:25 +08:00
"github.com/gravitl/netmaker/mq"
2023-02-16 05:32:16 +08:00
"github.com/gravitl/netmaker/servercfg"
"golang.org/x/exp/slog"
2023-02-16 05:32:16 +08:00
)
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)
2023-02-16 05:32:16 +08:00
}
// swagger:route GET /api/v1/enrollment-keys enrollmentKeys getEnrollmentKeys
//
2023-02-18 01:09:18 +08:00
// Lists all EnrollmentKeys for admins.
2023-02-16 05:32:16 +08:00
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: EnrollmentKeys
2023-02-16 05:32:16 +08:00
func getEnrollmentKeys(w http.ResponseWriter, r *http.Request) {
keys, err := logic.GetAllEnrollmentKeys()
2023-02-16 05:32:16 +08:00
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 {
2023-02-16 05:32:16 +08:00
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)
2023-02-16 05:32:16 +08:00
}
// return JSON/API formatted keys
2023-02-16 05:32:16 +08:00
logger.Log(2, r.Header.Get("user"), "fetched enrollment keys")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(ret)
2023-02-16 05:32:16 +08:00
}
// swagger:route DELETE /api/v1/enrollment-keys/{keyid} enrollmentKeys deleteEnrollmentKey
2023-02-16 05:32:16 +08:00
//
2023-02-18 01:09:18 +08:00
// Deletes an EnrollmentKey from Netmaker server.
2023-02-16 05:32:16 +08:00
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: okResponse
2023-02-16 05:32:16 +08:00
func deleteEnrollmentKey(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
2023-02-16 05:32:16 +08:00
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)
}
2023-02-17 03:27:57 +08:00
// 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: EnrollmentKey
2023-02-17 03:27:57 +08:00
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
}
}
}
2023-02-17 03:27:57 +08:00
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,
)
2023-02-17 03:27:57 +08:00
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
}
2023-02-17 04:13:40 +08:00
2023-02-17 08:34:25 +08:00
if err = logic.Tokenize(newEnrollmentKey, servercfg.GetAPIHost()); err != nil {
2023-02-17 04:13:40 +08:00
logger.Log(0, r.Header.Get("user"), "failed to create enrollment key:", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
2023-02-17 03:27:57 +08:00
logger.Log(2, r.Header.Get("user"), "created enrollment key")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(newEnrollmentKey)
}
// swagger:route PUT /api/v1/enrollment-keys/{keyid} enrollmentKeys updateEnrollmentKey
//
// Updates an EnrollmentKey for hosts to use on Netmaker server. Updates only the relay to use.
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: EnrollmentKey
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)
}
2023-02-18 01:09:18 +08:00
// swagger:route POST /api/v1/enrollment-keys/{token} enrollmentKeys handleHostRegister
2023-02-16 05:32:16 +08:00
//
2023-02-18 01:09:18 +08:00
// Handles a Netclient registration with server and add nodes accordingly.
2023-02-16 05:32:16 +08:00
//
// Schemes: https
//
// Security:
// oauth
//
// Responses:
// 200: RegisterResponse
2023-02-16 05:32:16 +08:00
func handleHostRegister(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
2023-02-17 03:27:57 +08:00
token := params["token"]
2023-02-17 08:34:25 +08:00
logger.Log(0, "received registration attempt with token", token)
2023-02-17 03:27:57 +08:00
// check if token exists
enrollmentKey, err := logic.DeTokenize(token)
2023-02-16 05:32:16 +08:00
if err != nil {
2023-02-17 03:27:57 +08:00
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())
2023-02-16 05:32:16 +08:00
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
2023-02-17 03:27:57 +08:00
// 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"),
)
2023-02-17 03:27:57 +08:00
return
}
// version check
2024-05-08 14:47:23 +08:00
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")
2023-02-17 03:27:57 +08:00
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"),
)
2023-02-17 03:27:57 +08:00
return
}
if !hostExists {
newHost.PersistentKeepalive = models.DefaultPersistentKeepAlive
// register host
NET-1082: Scale Testing Fixes (#2894) * add additional mutex lock on node acls func * increase verbosity * disable acls on cloud emqx * add emqx creds creation to go routine * add debug log of mq client id * comment port check * uncomment port check * check for connection mq connection open * use username for client id * add write mutex on acl is allowed * add mq connection lost handler on server * spin off zombie init as go routine * get whole api path from config * Revert "get whole api path from config" This reverts commit 392f5f4c5f00530788f09d26a655dcbe03a06ccb. * update extclient acls async * add additional mutex lock on node acls func (cherry picked from commit 5325f0e7d7ff9411f497fdc38c980ac0c3a6847d) * increase verbosity (cherry picked from commit 705b3cf0bfbca4d7f5dccdd579875ebb00f85511) * add emqx creds creation to go routine (cherry picked from commit c8e65f4820771eb0c7c7d62b77334211c6b82adb) * add debug log of mq client id (cherry picked from commit 29c5d6cecad6fcaeb4a57bac85895d6516294e28) * comment port check (cherry picked from commit db8d6d95ead39e9f436ad4dbc4176f5ff9312863) * check for connection mq connection open (cherry picked from commit 13b11033b0795693a0a1a0a225db50b8a5c001ae) * use username for client id (cherry picked from commit e90c7386dea48b560c9060289f1db06f2a8d77c1) * add write mutex on acl is allowed (cherry picked from commit 4cae1b0bb4b4b608fdb76b3ef49c7fd7390c9ccb) * add mq connection lost handler on server (cherry picked from commit c82918ad3564098487d5ac4a223ee5d95e76ac3e) * spin off zombie init as go routine (cherry picked from commit 6d65c44c4375ff7a292c05d2becf6507e7310837) * update extclient acls async (cherry picked from commit 6557ef1ebe87ec7c74e9038a99185616a2b87e89) * additionl logs for oauth user flow (cherry picked from commit 61703038ae3227de6f706e1d1d9a9362e4c258d5) * add more debug logs (cherry picked from commit 5980beacd10e7efa1cfbc12e6c048452d9ceca82) * add more debug logs (cherry picked from commit 4d001f0d2709fcc55ae66e2591a0bd079207a61d) * add set auth secret (cherry picked from commit f41cef5da51a884e68d12ac90ae627428b02b112) * fix fetch pass (cherry picked from commit 825caf4b600133f8365f5ab71e0ecd69cc8a996b) * make sure auth secret is set only once (cherry picked from commit ba33ed02aa52237126904f4bbe17b53dd595d7cf) * make sure auth secret is set only once (cherry picked from commit 920ac4c5073fb2c8805520d37abddffb062896ae) * comment usage of emqx acls * replace read lock with write lock on acls * replace read lock with write lock on acls (cherry picked from commit 808d2135c80461edfa96b61070e09ad136fa4644) * use deadlock pkg for visibility * add additional mutex locks * remove race flag * on mq re-connecting donot exit if failed * on mq re-connecting donot exit if failed * revert mutex package change * set mq clean session * remove debug log * go mod tidy * revert on prem emqx acls del
2024-04-11 23:48:57 +08:00
//logic.CheckHostPorts(&newHost)
2023-03-03 19:31:20 +08:00
// create EMQX credentials and ACLs for host
if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
if err := mq.GetEmqxHandler().CreateEmqxUser(newHost.ID.String(), newHost.HostPass); err != nil {
2023-03-03 19:31:20 +08:00
logger.Log(0, "failed to create host credentials for EMQX: ", err.Error())
return
}
if err := mq.GetEmqxHandler().CreateHostACL(newHost.ID.String(), servercfg.GetServerInfo().Server); err != nil {
2023-03-03 19:31:20 +08:00
logger.Log(0, "failed to add host ACL rules to 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
}
2023-02-17 03:27:57 +08:00
}
// ready the response
server := servercfg.GetServerInfo()
server.TrafficKey = key
response := models.RegisterResponse{
ServerConf: server,
RequestedHost: newHost,
}
2023-02-17 08:34:25 +08:00
logger.Log(0, newHost.Name, newHost.ID.String(), "registered with Netmaker")
2023-02-16 05:32:16 +08:00
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(&response)
2023-02-17 03:27:57 +08:00
// notify host of changes, peer and node updates
go auth.CheckNetRegAndHostUpdate(enrollmentKey.Networks, &newHost, enrollmentKey.Relay)
2023-02-16 05:32:16 +08:00
}