posture_check.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. package controllers
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "net/http"
  7. "time"
  8. "github.com/google/uuid"
  9. "github.com/gorilla/mux"
  10. "github.com/gravitl/netmaker/db"
  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. proLogic "github.com/gravitl/netmaker/pro/logic"
  16. "github.com/gravitl/netmaker/schema"
  17. )
  18. func PostureCheckHandlers(r *mux.Router) {
  19. r.HandleFunc("/api/v1/posture_check", logic.SecurityCheck(true, http.HandlerFunc(createPostureCheck))).Methods(http.MethodPost)
  20. r.HandleFunc("/api/v1/posture_check", logic.SecurityCheck(true, http.HandlerFunc(listPostureChecks))).Methods(http.MethodGet)
  21. r.HandleFunc("/api/v1/posture_check", logic.SecurityCheck(true, http.HandlerFunc(updatePostureCheck))).Methods(http.MethodPut)
  22. r.HandleFunc("/api/v1/posture_check", logic.SecurityCheck(true, http.HandlerFunc(deletePostureCheck))).Methods(http.MethodDelete)
  23. r.HandleFunc("/api/v1/posture_check/attrs", logic.SecurityCheck(true, http.HandlerFunc(listPostureChecksAttrs))).Methods(http.MethodGet)
  24. r.HandleFunc("/api/v1/posture_check/violations", logic.SecurityCheck(true, http.HandlerFunc(listPostureCheckViolatedNodes))).Methods(http.MethodGet)
  25. }
  26. // @Summary List Posture Checks Available Attributes
  27. // @Router /api/v1/posture_check/attrs [get]
  28. // @Tags Posture Check
  29. // @Security oauth
  30. // @Produce json
  31. // @Success 200 {object} models.SuccessResponse
  32. // @Failure 400 {object} models.ErrorResponse
  33. // @Failure 401 {object} models.ErrorResponse
  34. // @Failure 500 {object} models.ErrorResponse
  35. func listPostureChecksAttrs(w http.ResponseWriter, r *http.Request) {
  36. logic.ReturnSuccessResponseWithJson(w, r, schema.PostureCheckAttrValues, "fetched posture checks")
  37. }
  38. // @Summary Create Posture Check
  39. // @Router /api/v1/posture_check [post]
  40. // @Tags Posture Check
  41. // @Security oauth
  42. // @Accept json
  43. // @Produce json
  44. // @Param body body schema.PostureCheck true "Posture Check payload"
  45. // @Success 200 {object} schema.PostureCheck
  46. // @Failure 400 {object} models.ErrorResponse
  47. // @Failure 401 {object} models.ErrorResponse
  48. // @Failure 500 {object} models.ErrorResponse
  49. func createPostureCheck(w http.ResponseWriter, r *http.Request) {
  50. var req schema.PostureCheck
  51. err := json.NewDecoder(r.Body).Decode(&req)
  52. if err != nil {
  53. logger.Log(0, "error decoding request body: ",
  54. err.Error())
  55. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  56. return
  57. }
  58. if err := proLogic.ValidatePostureCheck(&req); err != nil {
  59. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  60. return
  61. }
  62. pc := schema.PostureCheck{
  63. ID: uuid.New().String(),
  64. Name: req.Name,
  65. NetworkID: req.NetworkID,
  66. Description: req.Description,
  67. Tags: req.Tags,
  68. UserGroups: req.UserGroups,
  69. Attribute: req.Attribute,
  70. Values: req.Values,
  71. Severity: req.Severity,
  72. Status: true,
  73. CreatedBy: r.Header.Get("user"),
  74. CreatedAt: time.Now().UTC(),
  75. }
  76. err = pc.Create(db.WithContext(r.Context()))
  77. if err != nil {
  78. logic.ReturnErrorResponse(
  79. w,
  80. r,
  81. logic.FormatError(errors.New("error creating posture check "+err.Error()), logic.Internal),
  82. )
  83. return
  84. }
  85. logic.LogEvent(&models.Event{
  86. Action: models.Create,
  87. Source: models.Subject{
  88. ID: r.Header.Get("user"),
  89. Name: r.Header.Get("user"),
  90. Type: models.UserSub,
  91. },
  92. TriggeredBy: r.Header.Get("user"),
  93. Target: models.Subject{
  94. ID: pc.ID,
  95. Name: pc.Name,
  96. Type: models.PostureCheckSub,
  97. },
  98. NetworkID: models.NetworkID(pc.NetworkID),
  99. Origin: models.Dashboard,
  100. })
  101. go mq.PublishPeerUpdate(false)
  102. go proLogic.RunPostureChecks()
  103. logic.ReturnSuccessResponseWithJson(w, r, pc, "created posture check")
  104. }
  105. // @Summary List Posture Checks
  106. // @Router /api/v1/posture_check [get]
  107. // @Tags Posture Check
  108. // @Security oauth
  109. // @Produce json
  110. // @Param network query string true "Network ID"
  111. // @Param id query string false "Posture Check ID to fetch a specific check"
  112. // @Success 200 {array} schema.PostureCheck
  113. // @Failure 400 {object} models.ErrorResponse
  114. // @Failure 401 {object} models.ErrorResponse
  115. // @Failure 500 {object} models.ErrorResponse
  116. func listPostureChecks(w http.ResponseWriter, r *http.Request) {
  117. network := r.URL.Query().Get("network")
  118. if network == "" {
  119. logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("network is required"), logic.BadReq))
  120. return
  121. }
  122. _, err := logic.GetNetwork(network)
  123. if err != nil {
  124. logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("network not found"), logic.BadReq))
  125. return
  126. }
  127. id := r.URL.Query().Get("id")
  128. if id != "" {
  129. pc := schema.PostureCheck{ID: id}
  130. err := pc.Get(db.WithContext(r.Context()))
  131. if err != nil {
  132. logic.ReturnErrorResponse(
  133. w,
  134. r,
  135. logic.FormatError(errors.New("error listing posture checks "+err.Error()), "internal"),
  136. )
  137. return
  138. }
  139. logic.ReturnSuccessResponseWithJson(w, r, pc, "fetched posture check")
  140. return
  141. }
  142. pc := schema.PostureCheck{NetworkID: models.NetworkID(network)}
  143. list, err := pc.ListByNetwork(db.WithContext(r.Context()))
  144. if err != nil {
  145. logic.ReturnErrorResponse(
  146. w,
  147. r,
  148. logic.FormatError(errors.New("error listing posture checks "+err.Error()), "internal"),
  149. )
  150. return
  151. }
  152. logic.ReturnSuccessResponseWithJson(w, r, list, "fetched posture checks")
  153. }
  154. // @Summary Update Posture Check
  155. // @Router /api/v1/posture_check [put]
  156. // @Tags Posture Check
  157. // @Security oauth
  158. // @Accept json
  159. // @Produce json
  160. // @Param body body schema.PostureCheck true "Posture Check payload"
  161. // @Success 200 {object} schema.PostureCheck
  162. // @Failure 400 {object} models.ErrorResponse
  163. // @Failure 401 {object} models.ErrorResponse
  164. // @Failure 500 {object} models.ErrorResponse
  165. func updatePostureCheck(w http.ResponseWriter, r *http.Request) {
  166. var updatePc schema.PostureCheck
  167. err := json.NewDecoder(r.Body).Decode(&updatePc)
  168. if err != nil {
  169. logger.Log(0, "error decoding request body: ",
  170. err.Error())
  171. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  172. return
  173. }
  174. if err := proLogic.ValidatePostureCheck(&updatePc); err != nil {
  175. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  176. return
  177. }
  178. pc := schema.PostureCheck{ID: updatePc.ID}
  179. err = pc.Get(db.WithContext(r.Context()))
  180. if err != nil {
  181. logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
  182. return
  183. }
  184. var updateStatus bool
  185. if updatePc.Status != pc.Status {
  186. updateStatus = true
  187. }
  188. event := &models.Event{
  189. Action: models.Update,
  190. Source: models.Subject{
  191. ID: r.Header.Get("user"),
  192. Name: r.Header.Get("user"),
  193. Type: models.UserSub,
  194. },
  195. TriggeredBy: r.Header.Get("user"),
  196. Target: models.Subject{
  197. ID: pc.ID,
  198. Name: updatePc.Name,
  199. Type: models.PostureCheckSub,
  200. },
  201. Diff: models.Diff{
  202. Old: pc,
  203. New: updatePc,
  204. },
  205. NetworkID: models.NetworkID(pc.NetworkID),
  206. Origin: models.Dashboard,
  207. }
  208. pc.Tags = updatePc.Tags
  209. pc.UserGroups = updatePc.UserGroups
  210. pc.Attribute = updatePc.Attribute
  211. pc.Values = updatePc.Values
  212. pc.Description = updatePc.Description
  213. pc.Name = updatePc.Name
  214. pc.Severity = updatePc.Severity
  215. pc.Status = updatePc.Status
  216. pc.UpdatedAt = time.Now().UTC()
  217. err = pc.Update(db.WithContext(context.TODO()))
  218. if err != nil {
  219. logic.ReturnErrorResponse(
  220. w,
  221. r,
  222. logic.FormatError(errors.New("error updating posture check "+err.Error()), "internal"),
  223. )
  224. return
  225. }
  226. if updateStatus {
  227. pc.UpdateStatus(db.WithContext(context.TODO()))
  228. }
  229. logic.LogEvent(event)
  230. go mq.PublishPeerUpdate(false)
  231. go proLogic.RunPostureChecks()
  232. logic.ReturnSuccessResponseWithJson(w, r, pc, "updated posture check")
  233. }
  234. // @Summary Delete Posture Check
  235. // @Router /api/v1/posture_check [delete]
  236. // @Tags Posture Check
  237. // @Security oauth
  238. // @Produce json
  239. // @Param id query string true "Posture Check ID"
  240. // @Success 200 {object} schema.PostureCheck
  241. // @Failure 400 {object} models.ErrorResponse
  242. // @Failure 401 {object} models.ErrorResponse
  243. // @Failure 500 {object} models.ErrorResponse
  244. func deletePostureCheck(w http.ResponseWriter, r *http.Request) {
  245. id := r.URL.Query().Get("id")
  246. if id == "" {
  247. logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("id is required"), "badrequest"))
  248. return
  249. }
  250. pc := schema.PostureCheck{ID: id}
  251. err := pc.Get(db.WithContext(r.Context()))
  252. if err != nil {
  253. logic.ReturnErrorResponse(w, r, logic.FormatError(err, logic.BadReq))
  254. return
  255. }
  256. err = pc.Delete(db.WithContext(r.Context()))
  257. if err != nil {
  258. logic.ReturnErrorResponse(w, r, logic.FormatError(err, logic.Internal))
  259. return
  260. }
  261. logic.LogEvent(&models.Event{
  262. Action: models.Delete,
  263. Source: models.Subject{
  264. ID: r.Header.Get("user"),
  265. Name: r.Header.Get("user"),
  266. Type: models.UserSub,
  267. },
  268. TriggeredBy: r.Header.Get("user"),
  269. Target: models.Subject{
  270. ID: pc.ID,
  271. Name: pc.Name,
  272. Type: models.PostureCheckSub,
  273. },
  274. NetworkID: models.NetworkID(pc.NetworkID),
  275. Origin: models.Dashboard,
  276. Diff: models.Diff{
  277. Old: pc,
  278. New: nil,
  279. },
  280. })
  281. go mq.PublishPeerUpdate(false)
  282. logic.ReturnSuccessResponseWithJson(w, r, pc, "deleted posture check")
  283. }
  284. // @Summary List Posture Check violated Nodes
  285. // @Router /api/v1/posture_check/violations [get]
  286. // @Tags Posture Check
  287. // @Security oauth
  288. // @Produce json
  289. // @Param network query string true "Network ID"
  290. // @Param users query string false "If 'true', list violated users instead of nodes"
  291. // @Success 200 {array} models.ApiNode
  292. // @Failure 400 {object} models.ErrorResponse
  293. // @Failure 401 {object} models.ErrorResponse
  294. // @Failure 500 {object} models.ErrorResponse
  295. func listPostureCheckViolatedNodes(w http.ResponseWriter, r *http.Request) {
  296. network := r.URL.Query().Get("network")
  297. if network == "" {
  298. logic.ReturnErrorResponse(w, r, logic.FormatError(errors.New("network is required"), logic.BadReq))
  299. return
  300. }
  301. listViolatedusers := r.URL.Query().Get("users") == "true"
  302. violatedNodes := []models.Node{}
  303. if listViolatedusers {
  304. extclients, err := logic.GetNetworkExtClients(network)
  305. if err != nil {
  306. logic.ReturnErrorResponse(w, r, logic.FormatError(err, logic.BadReq))
  307. return
  308. }
  309. for _, extclient := range extclients {
  310. if extclient.DeviceID != "" {
  311. if len(extclient.PostureChecksViolations) > 0 {
  312. violatedNodes = append(violatedNodes, extclient.ConvertToStaticNode())
  313. }
  314. }
  315. }
  316. } else {
  317. nodes, err := logic.GetNetworkNodes(network)
  318. if err != nil {
  319. logic.ReturnErrorResponse(w, r, logic.FormatError(err, logic.BadReq))
  320. return
  321. }
  322. for _, node := range nodes {
  323. if len(node.PostureChecksViolations) > 0 {
  324. violatedNodes = append(violatedNodes, node)
  325. }
  326. }
  327. }
  328. apiNodes := logic.GetAllNodesAPI(violatedNodes)
  329. logic.SortApiNodes(apiNodes[:])
  330. logic.ReturnSuccessResponseWithJson(w, r, apiNodes, "fetched posture checks violated nodes")
  331. }