enrollmentkeys.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. package controller
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "time"
  7. "github.com/go-playground/validator/v10"
  8. "github.com/google/uuid"
  9. "github.com/gorilla/mux"
  10. "github.com/gravitl/netmaker/auth"
  11. "github.com/gravitl/netmaker/logger"
  12. "github.com/gravitl/netmaker/logic"
  13. "github.com/gravitl/netmaker/models"
  14. "github.com/gravitl/netmaker/mq"
  15. "github.com/gravitl/netmaker/servercfg"
  16. "golang.org/x/exp/slog"
  17. )
  18. func enrollmentKeyHandlers(r *mux.Router) {
  19. r.HandleFunc("/api/v1/enrollment-keys", logic.SecurityCheck(true, http.HandlerFunc(createEnrollmentKey))).
  20. Methods(http.MethodPost)
  21. r.HandleFunc("/api/v1/enrollment-keys", logic.SecurityCheck(true, http.HandlerFunc(getEnrollmentKeys))).
  22. Methods(http.MethodGet)
  23. r.HandleFunc("/api/v1/enrollment-keys/{keyID}", logic.SecurityCheck(true, http.HandlerFunc(deleteEnrollmentKey))).
  24. Methods(http.MethodDelete)
  25. r.HandleFunc("/api/v1/host/register/{token}", http.HandlerFunc(handleHostRegister)).
  26. Methods(http.MethodPost)
  27. r.HandleFunc("/api/v1/enrollment-keys/{keyID}", logic.SecurityCheck(true, http.HandlerFunc(updateEnrollmentKey))).
  28. Methods(http.MethodPut)
  29. }
  30. // swagger:route GET /api/v1/enrollment-keys enrollmentKeys getEnrollmentKeys
  31. //
  32. // Lists all EnrollmentKeys for admins.
  33. //
  34. // Schemes: https
  35. //
  36. // Security:
  37. // oauth
  38. //
  39. // Responses:
  40. // 200: EnrollmentKeys
  41. func getEnrollmentKeys(w http.ResponseWriter, r *http.Request) {
  42. keys, err := logic.GetAllEnrollmentKeys()
  43. if err != nil {
  44. logger.Log(0, r.Header.Get("user"), "failed to fetch enrollment keys: ", err.Error())
  45. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  46. return
  47. }
  48. ret := []*models.EnrollmentKey{}
  49. for _, key := range keys {
  50. key := key
  51. if err = logic.Tokenize(key, servercfg.GetAPIHost()); err != nil {
  52. logger.Log(0, r.Header.Get("user"), "failed to get token values for keys:", err.Error())
  53. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  54. return
  55. }
  56. ret = append(ret, key)
  57. }
  58. // return JSON/API formatted keys
  59. logger.Log(2, r.Header.Get("user"), "fetched enrollment keys")
  60. w.WriteHeader(http.StatusOK)
  61. json.NewEncoder(w).Encode(ret)
  62. }
  63. // swagger:route DELETE /api/v1/enrollment-keys/{keyid} enrollmentKeys deleteEnrollmentKey
  64. //
  65. // Deletes an EnrollmentKey from Netmaker server.
  66. //
  67. // Schemes: https
  68. //
  69. // Security:
  70. // oauth
  71. //
  72. // Responses:
  73. // 200: okResponse
  74. func deleteEnrollmentKey(w http.ResponseWriter, r *http.Request) {
  75. params := mux.Vars(r)
  76. keyID := params["keyID"]
  77. err := logic.DeleteEnrollmentKey(keyID)
  78. if err != nil {
  79. logger.Log(0, r.Header.Get("user"), "failed to remove enrollment key: ", err.Error())
  80. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  81. return
  82. }
  83. logger.Log(2, r.Header.Get("user"), "deleted enrollment key", keyID)
  84. w.WriteHeader(http.StatusOK)
  85. }
  86. // swagger:route POST /api/v1/enrollment-keys enrollmentKeys createEnrollmentKey
  87. //
  88. // Creates an EnrollmentKey for hosts to use on Netmaker server.
  89. //
  90. // Schemes: https
  91. //
  92. // Security:
  93. // oauth
  94. //
  95. // Responses:
  96. // 200: EnrollmentKey
  97. func createEnrollmentKey(w http.ResponseWriter, r *http.Request) {
  98. var enrollmentKeyBody models.APIEnrollmentKey
  99. err := json.NewDecoder(r.Body).Decode(&enrollmentKeyBody)
  100. if err != nil {
  101. logger.Log(0, r.Header.Get("user"), "error decoding request body: ",
  102. err.Error())
  103. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  104. return
  105. }
  106. var newTime time.Time
  107. if enrollmentKeyBody.Expiration > 0 {
  108. newTime = time.Unix(enrollmentKeyBody.Expiration, 0)
  109. }
  110. v := validator.New()
  111. err = v.Struct(enrollmentKeyBody)
  112. if err != nil {
  113. logger.Log(0, r.Header.Get("user"), "error validating request body: ",
  114. err.Error())
  115. logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("validation error: name length must be between 3 and 32: %w", err), "badrequest"))
  116. return
  117. }
  118. if existingKeys, err := logic.GetAllEnrollmentKeys(); err != nil {
  119. logger.Log(0, r.Header.Get("user"), "error validating request body: ",
  120. err.Error())
  121. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  122. return
  123. } else {
  124. // check if any tags are duplicate
  125. existingTags := make(map[string]struct{})
  126. for _, existingKey := range existingKeys {
  127. for _, t := range existingKey.Tags {
  128. existingTags[t] = struct{}{}
  129. }
  130. }
  131. for _, t := range enrollmentKeyBody.Tags {
  132. if _, ok := existingTags[t]; ok {
  133. logic.ReturnErrorResponse(w, r, logic.FormatError(fmt.Errorf("key names must be unique"), "badrequest"))
  134. return
  135. }
  136. }
  137. }
  138. relayId := uuid.Nil
  139. if enrollmentKeyBody.Relay != "" {
  140. relayId, err = uuid.Parse(enrollmentKeyBody.Relay)
  141. if err != nil {
  142. logger.Log(0, r.Header.Get("user"), "error parsing relay id: ", err.Error())
  143. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  144. return
  145. }
  146. }
  147. newEnrollmentKey, err := logic.CreateEnrollmentKey(
  148. enrollmentKeyBody.UsesRemaining,
  149. newTime,
  150. enrollmentKeyBody.Networks,
  151. enrollmentKeyBody.Tags,
  152. enrollmentKeyBody.Unlimited,
  153. relayId,
  154. )
  155. if err != nil {
  156. logger.Log(0, r.Header.Get("user"), "failed to create enrollment key:", err.Error())
  157. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  158. return
  159. }
  160. if err = logic.Tokenize(newEnrollmentKey, servercfg.GetAPIHost()); err != nil {
  161. logger.Log(0, r.Header.Get("user"), "failed to create enrollment key:", err.Error())
  162. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  163. return
  164. }
  165. logger.Log(2, r.Header.Get("user"), "created enrollment key")
  166. w.WriteHeader(http.StatusOK)
  167. json.NewEncoder(w).Encode(newEnrollmentKey)
  168. }
  169. // swagger:route PUT /api/v1/enrollment-keys/:id enrollmentKeys updateEnrollmentKey
  170. //
  171. // Updates an EnrollmentKey for hosts to use on Netmaker server. Updates only the relay to use.
  172. //
  173. // Schemes: https
  174. //
  175. // Security:
  176. // oauth
  177. //
  178. // Responses:
  179. // 200: EnrollmentKey
  180. func updateEnrollmentKey(w http.ResponseWriter, r *http.Request) {
  181. var enrollmentKeyBody models.APIEnrollmentKey
  182. params := mux.Vars(r)
  183. keyId := params["keyID"]
  184. err := json.NewDecoder(r.Body).Decode(&enrollmentKeyBody)
  185. if err != nil {
  186. slog.Error("error decoding request body", "error", err)
  187. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  188. return
  189. }
  190. relayId := uuid.Nil
  191. if enrollmentKeyBody.Relay != "" {
  192. relayId, err = uuid.Parse(enrollmentKeyBody.Relay)
  193. if err != nil {
  194. slog.Error("error parsing relay id", "error", err)
  195. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  196. return
  197. }
  198. }
  199. newEnrollmentKey, err := logic.UpdateEnrollmentKey(keyId, relayId)
  200. if err != nil {
  201. slog.Error("failed to update enrollment key", "error", err)
  202. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  203. return
  204. }
  205. if err = logic.Tokenize(newEnrollmentKey, servercfg.GetAPIHost()); err != nil {
  206. slog.Error("failed to update enrollment key", "error", err)
  207. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  208. return
  209. }
  210. slog.Info("updated enrollment key", "id", keyId)
  211. w.WriteHeader(http.StatusOK)
  212. json.NewEncoder(w).Encode(newEnrollmentKey)
  213. }
  214. // swagger:route POST /api/v1/enrollment-keys/{token} enrollmentKeys handleHostRegister
  215. //
  216. // Handles a Netclient registration with server and add nodes accordingly.
  217. //
  218. // Schemes: https
  219. //
  220. // Security:
  221. // oauth
  222. //
  223. // Responses:
  224. // 200: RegisterResponse
  225. func handleHostRegister(w http.ResponseWriter, r *http.Request) {
  226. params := mux.Vars(r)
  227. token := params["token"]
  228. logger.Log(0, "received registration attempt with token", token)
  229. // check if token exists
  230. enrollmentKey, err := logic.DeTokenize(token)
  231. if err != nil {
  232. logger.Log(0, "invalid enrollment key used", token, err.Error())
  233. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  234. return
  235. }
  236. // get the host
  237. var newHost models.Host
  238. if err = json.NewDecoder(r.Body).Decode(&newHost); err != nil {
  239. logger.Log(0, r.Header.Get("user"), "error decoding request body: ",
  240. err.Error())
  241. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  242. return
  243. }
  244. // check if host already exists
  245. hostExists := false
  246. if hostExists = logic.HostExists(&newHost); hostExists && len(enrollmentKey.Networks) == 0 {
  247. logger.Log(
  248. 0,
  249. "host",
  250. newHost.ID.String(),
  251. newHost.Name,
  252. "attempted to re-register with no networks",
  253. )
  254. logic.ReturnErrorResponse(
  255. w,
  256. r,
  257. logic.FormatError(fmt.Errorf("host already exists"), "badrequest"),
  258. )
  259. return
  260. }
  261. // version check
  262. if !logic.IsVersionComptatible(newHost.Version) {
  263. err := fmt.Errorf("bad client version on register: %s", newHost.Version)
  264. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  265. return
  266. }
  267. if newHost.TrafficKeyPublic == nil && newHost.OS != models.OS_Types.IoT {
  268. err := fmt.Errorf("missing traffic key")
  269. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  270. return
  271. }
  272. key, keyErr := logic.RetrievePublicTrafficKey()
  273. if keyErr != nil {
  274. logger.Log(0, "error retrieving key:", keyErr.Error())
  275. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  276. return
  277. }
  278. // use the token
  279. if ok := logic.TryToUseEnrollmentKey(enrollmentKey); !ok {
  280. logger.Log(0, "host", newHost.ID.String(), newHost.Name, "failed registration")
  281. logic.ReturnErrorResponse(
  282. w,
  283. r,
  284. logic.FormatError(fmt.Errorf("invalid enrollment key"), "badrequest"),
  285. )
  286. return
  287. }
  288. hostPass := newHost.HostPass
  289. if !hostExists {
  290. newHost.PersistentKeepalive = models.DefaultPersistentKeepAlive
  291. // register host
  292. logic.CheckHostPorts(&newHost)
  293. // create EMQX credentials and ACLs for host
  294. if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
  295. if err := mq.CreateEmqxUser(newHost.ID.String(), newHost.HostPass, false); err != nil {
  296. logger.Log(0, "failed to create host credentials for EMQX: ", err.Error())
  297. return
  298. }
  299. if err := mq.CreateHostACL(newHost.ID.String(), servercfg.GetServerInfo().Server); err != nil {
  300. logger.Log(0, "failed to add host ACL rules to EMQX: ", err.Error())
  301. return
  302. }
  303. }
  304. if err = logic.CreateHost(&newHost); err != nil {
  305. logger.Log(
  306. 0,
  307. "host",
  308. newHost.ID.String(),
  309. newHost.Name,
  310. "failed registration -",
  311. err.Error(),
  312. )
  313. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  314. return
  315. }
  316. } else {
  317. // need to revise the list of networks from key
  318. // based on the ones host currently has
  319. networksToAdd := []string{}
  320. currentNets := logic.GetHostNetworks(newHost.ID.String())
  321. for _, newNet := range enrollmentKey.Networks {
  322. if !logic.StringSliceContains(currentNets, newNet) {
  323. networksToAdd = append(networksToAdd, newNet)
  324. }
  325. }
  326. enrollmentKey.Networks = networksToAdd
  327. currHost, err := logic.GetHost(newHost.ID.String())
  328. if err != nil {
  329. slog.Error("failed registration", "hostID", newHost.ID.String(), "hostName", newHost.Name, "error", err.Error())
  330. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  331. return
  332. }
  333. logic.UpdateHostFromClient(&newHost, currHost)
  334. err = logic.UpsertHost(currHost)
  335. if err != nil {
  336. slog.Error("failed to update host", "id", currHost.ID, "error", err)
  337. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
  338. return
  339. }
  340. }
  341. // ready the response
  342. server := servercfg.GetServerInfo()
  343. server.TrafficKey = key
  344. if servercfg.GetBrokerType() == servercfg.EmqxBrokerType {
  345. // set MQ username and password for EMQX clients
  346. server.MQUserName = newHost.ID.String()
  347. server.MQPassword = hostPass
  348. }
  349. response := models.RegisterResponse{
  350. ServerConf: server,
  351. RequestedHost: newHost,
  352. }
  353. logger.Log(0, newHost.Name, newHost.ID.String(), "registered with Netmaker")
  354. w.WriteHeader(http.StatusOK)
  355. json.NewEncoder(w).Encode(&response)
  356. // notify host of changes, peer and node updates
  357. go auth.CheckNetRegAndHostUpdate(enrollmentKey.Networks, &newHost, enrollmentKey.Relay)
  358. }