jit.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  1. package logic
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "sort"
  8. "time"
  9. "github.com/google/uuid"
  10. "github.com/gravitl/netmaker/database"
  11. "github.com/gravitl/netmaker/db"
  12. "github.com/gravitl/netmaker/logger"
  13. "github.com/gravitl/netmaker/models"
  14. "github.com/gravitl/netmaker/mq"
  15. "github.com/gravitl/netmaker/schema"
  16. "golang.org/x/exp/slog"
  17. "github.com/gravitl/netmaker/logic"
  18. )
  19. // JITStatusResponse - response for JIT status check
  20. type JITStatusResponse struct {
  21. HasAccess bool `json:"has_access"`
  22. Grant *schema.JITGrant `json:"grant,omitempty"`
  23. Request *schema.JITRequest `json:"request,omitempty"`
  24. PendingRequest bool `json:"pending_request"`
  25. }
  26. // EnableJITOnNetwork - enables JIT on a network and disconnects existing ext clients
  27. func EnableJITOnNetwork(networkID string) error {
  28. // Check if JIT feature is enabled
  29. featureFlags := GetFeatureFlags()
  30. if !featureFlags.EnableJIT {
  31. return errors.New("JIT feature is not enabled")
  32. }
  33. network, err := logic.GetNetwork(networkID)
  34. if err != nil {
  35. return fmt.Errorf("failed to get network: %w", err)
  36. }
  37. network.JITEnabled = "yes"
  38. network.SetNetworkLastModified()
  39. if err := logic.SaveNetwork(&network); err != nil {
  40. return fmt.Errorf("failed to save network: %w", err)
  41. }
  42. // Disconnect all ext clients from this network
  43. if err := DisconnectExtClientsFromNetwork(networkID); err != nil {
  44. logger.Log(0, "failed to disconnect ext clients when enabling JIT:", err.Error())
  45. // Don't fail the operation, just log
  46. }
  47. return nil
  48. }
  49. // DisableJITOnNetwork - disables JIT on a network
  50. func DisableJITOnNetwork(networkID string) error {
  51. network, err := logic.GetNetwork(networkID)
  52. if err != nil {
  53. return fmt.Errorf("failed to get network: %w", err)
  54. }
  55. network.JITEnabled = "no"
  56. network.SetNetworkLastModified()
  57. return logic.SaveNetwork(&network)
  58. }
  59. // CreateJITRequest - creates a new JIT access request
  60. func CreateJITRequest(networkID, userName, reason string) (*schema.JITRequest, error) {
  61. // Check if JIT feature is enabled
  62. featureFlags := GetFeatureFlags()
  63. if !featureFlags.EnableJIT {
  64. return nil, errors.New("JIT feature is not enabled")
  65. }
  66. ctx := db.WithContext(context.Background())
  67. // Check if network exists and has JIT enabled
  68. network, err := logic.GetNetwork(networkID)
  69. if err != nil {
  70. return nil, fmt.Errorf("network not found: %w", err)
  71. }
  72. if network.JITEnabled != "yes" {
  73. return nil, errors.New("JIT is not enabled on this network")
  74. }
  75. // Check if user already has an active grant
  76. hasAccess, _, err := CheckJITAccess(networkID, userName)
  77. if err == nil && hasAccess {
  78. return nil, errors.New("user already has active access grant")
  79. }
  80. // Check if there's already a pending request
  81. request := schema.JITRequest{
  82. NetworkID: networkID,
  83. UserID: userName,
  84. }
  85. pendingRequests, err := request.ListPendingByNetwork(ctx)
  86. if err == nil {
  87. for _, req := range pendingRequests {
  88. if req.UserID == userName {
  89. return nil, errors.New("user already has a pending request")
  90. }
  91. }
  92. }
  93. // Create new request
  94. newRequest := schema.JITRequest{
  95. ID: uuid.New().String(),
  96. NetworkID: networkID,
  97. UserID: userName,
  98. UserName: userName,
  99. Reason: reason,
  100. Status: "pending",
  101. RequestedAt: time.Now().UTC(),
  102. }
  103. if err := newRequest.Create(ctx); err != nil {
  104. return nil, fmt.Errorf("failed to create request: %w", err)
  105. }
  106. return &newRequest, nil
  107. }
  108. // ApproveJITRequest - approves a JIT request and creates a grant
  109. func ApproveJITRequest(requestID string, expiresAt time.Time, approvedBy string) (*schema.JITGrant, *schema.JITRequest, error) {
  110. ctx := db.WithContext(context.Background())
  111. // Get the request
  112. request := schema.JITRequest{ID: requestID}
  113. if err := request.Get(ctx); err != nil {
  114. return nil, nil, fmt.Errorf("request not found: %w", err)
  115. }
  116. if request.Status != "pending" {
  117. return nil, nil, errors.New("request is not pending")
  118. }
  119. // Update request status
  120. now := time.Now().UTC()
  121. // Ensure expiresAt is in UTC
  122. expiresAt = expiresAt.UTC()
  123. // Calculate duration in hours for storage
  124. durationHours := int(expiresAt.Sub(now).Hours())
  125. if durationHours < 1 {
  126. durationHours = 1 // Minimum 1 hour
  127. }
  128. request.Status = "approved"
  129. request.ApprovedAt = now
  130. request.ApprovedBy = approvedBy
  131. request.DurationHours = durationHours
  132. request.ExpiresAt = expiresAt
  133. if err := request.Update(ctx); err != nil {
  134. return nil, nil, fmt.Errorf("failed to update request: %w", err)
  135. }
  136. // Delete any existing grants for this user on this network
  137. if err := deactivateUserGrants(ctx, request.NetworkID, request.UserID); err != nil {
  138. slog.Warn("failed to delete existing grants", "error", err)
  139. }
  140. // Create new grant
  141. grant := schema.JITGrant{
  142. ID: uuid.New().String(),
  143. NetworkID: request.NetworkID,
  144. UserID: request.UserID,
  145. RequestID: request.ID,
  146. GrantedAt: now,
  147. ExpiresAt: expiresAt,
  148. }
  149. if err := grant.Create(ctx); err != nil {
  150. return nil, nil, fmt.Errorf("failed to create grant: %w", err)
  151. }
  152. return &grant, &request, nil
  153. }
  154. // DenyJITRequest - denies a JIT request and returns the updated request
  155. func DenyJITRequest(requestID string, deniedBy string) (*schema.JITRequest, error) {
  156. ctx := db.WithContext(context.Background())
  157. request := schema.JITRequest{ID: requestID}
  158. if err := request.Get(ctx); err != nil {
  159. return nil, fmt.Errorf("request not found: %w", err)
  160. }
  161. if request.Status != "pending" {
  162. return nil, errors.New("request is not pending")
  163. }
  164. now := time.Now().UTC()
  165. request.Status = "denied"
  166. request.ApprovedAt = now
  167. request.ApprovedBy = deniedBy
  168. if err := request.Update(ctx); err != nil {
  169. return nil, err
  170. }
  171. return &request, nil
  172. }
  173. // CheckJITAccess - checks if a user has active JIT access for a network
  174. func CheckJITAccess(networkID, userID string) (bool, *schema.JITGrant, error) {
  175. // Check if JIT feature is enabled
  176. featureFlags := GetFeatureFlags()
  177. if !featureFlags.EnableJIT {
  178. // Feature flag disabled, allow access (backward compatibility)
  179. return true, nil, nil
  180. }
  181. // Check if user is super admin, admin, network admin, or global network admin - skip JIT check for them
  182. user, err := logic.GetUser(userID)
  183. if err == nil {
  184. // Check platform role (super admin or admin)
  185. if user.PlatformRoleID == models.SuperAdminRole || user.PlatformRoleID == models.AdminRole {
  186. // Super admin or admin - bypass JIT check
  187. return true, nil, nil
  188. }
  189. if user.PlatformRoleID == models.PlatformUser {
  190. // Check network admin roles
  191. networkIDModel := models.NetworkID(networkID)
  192. allNetworksID := models.AllNetworks
  193. globalNetworksAdminRoleID := models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkAdmin))
  194. // Check user groups for network admin roles
  195. for groupID := range user.UserGroups {
  196. groupData, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, groupID.String())
  197. if err != nil {
  198. continue
  199. }
  200. var group models.UserGroup
  201. if err := json.Unmarshal([]byte(groupData), &group); err != nil {
  202. continue
  203. }
  204. // Check if group has network admin role for this network
  205. if roles, ok := group.NetworkRoles[networkIDModel]; ok {
  206. for roleID := range roles {
  207. if roleID == models.NetworkAdmin {
  208. // User is in group with network admin role for this network - bypass JIT check
  209. return true, nil, nil
  210. }
  211. }
  212. }
  213. // Check if group has global network admin role
  214. if roles, ok := group.NetworkRoles[allNetworksID]; ok {
  215. for roleID := range roles {
  216. if roleID == models.NetworkAdmin || roleID == globalNetworksAdminRoleID {
  217. // User is in group with global network admin role - bypass JIT check
  218. return true, nil, nil
  219. }
  220. }
  221. }
  222. }
  223. }
  224. }
  225. ctx := db.WithContext(context.Background())
  226. // Check if network has JIT enabled
  227. network, err := logic.GetNetwork(networkID)
  228. if err != nil {
  229. return false, nil, fmt.Errorf("network not found: %w", err)
  230. }
  231. if network.JITEnabled != "yes" {
  232. // JIT not enabled, allow access
  233. return true, nil, nil
  234. }
  235. // Check for active grant
  236. grant := schema.JITGrant{
  237. NetworkID: networkID,
  238. UserID: userID,
  239. }
  240. activeGrant, err := grant.GetActiveByUserAndNetwork(ctx)
  241. if err != nil {
  242. // No active grant found
  243. return false, nil, nil
  244. }
  245. // Check if grant is expired
  246. if time.Now().UTC().After(activeGrant.ExpiresAt) {
  247. // Grant expired, delete it
  248. _ = activeGrant.Delete(ctx)
  249. return false, nil, nil
  250. }
  251. return true, activeGrant, nil
  252. }
  253. // JITRequestWithGrant - JIT request with grant ID for approved requests
  254. type JITRequestWithGrant struct {
  255. schema.JITRequest
  256. GrantID string `json:"grant_id,omitempty"` // Grant ID if request is approved
  257. }
  258. // GetNetworkJITRequests - gets JIT requests for a network, optionally filtered by status
  259. // statusFilter can be: "pending", "approved", "denied", "expired", or "" for all
  260. func GetNetworkJITRequests(networkID string, statusFilter string) ([]JITRequestWithGrant, error) {
  261. ctx := db.WithContext(context.Background())
  262. requests, _, err := GetNetworkJITRequestsPaginated(ctx, networkID, statusFilter, 1, 0)
  263. return requests, err
  264. }
  265. // GetNetworkJITRequestsPaginated - gets paginated JIT requests for a network, optionally filtered by status
  266. // statusFilter can be: "pending", "approved", "denied", "expired", or "" for all
  267. // page and pageSize control pagination. db.SetPagination will apply defaults (page=1, pageSize=10) if values are invalid.
  268. // Returns: requests, total count, error
  269. func GetNetworkJITRequestsPaginated(ctx context.Context, networkID string, statusFilter string, page, pageSize int) ([]JITRequestWithGrant, int64, error) {
  270. request := schema.JITRequest{NetworkID: networkID}
  271. var requests []schema.JITRequest
  272. var total int64
  273. var err error
  274. // Always set up pagination context - db.SetPagination handles defaults (page=1, pageSize=10)
  275. paginatedCtx := db.SetPagination(ctx, page, pageSize)
  276. // Get total count for pagination metadata
  277. if statusFilter == "" || statusFilter == "all" {
  278. total, err = request.CountByNetwork(ctx)
  279. if err != nil {
  280. return nil, 0, err
  281. }
  282. requests, err = request.ListByNetwork(paginatedCtx)
  283. if err != nil {
  284. return nil, 0, err
  285. }
  286. } else if statusFilter == "expired" {
  287. // Handle expired filter (approved requests that have expired)
  288. // For expired filter, we need to get all and filter in memory, then apply pagination
  289. allRequests, err := request.ListByNetwork(ctx)
  290. if err != nil {
  291. return nil, 0, err
  292. }
  293. now := time.Now().UTC()
  294. var filteredRequests []schema.JITRequest
  295. for _, req := range allRequests {
  296. // Include requests with status "expired" or "approved" requests that have passed expiration
  297. if req.Status == "expired" ||
  298. (req.Status == "approved" && !req.ExpiresAt.IsZero() && now.After(req.ExpiresAt)) {
  299. filteredRequests = append(filteredRequests, req)
  300. }
  301. }
  302. // Sort by requested_at DESC (most recent first)
  303. sort.Slice(filteredRequests, func(i, j int) bool {
  304. return filteredRequests[i].RequestedAt.After(filteredRequests[j].RequestedAt)
  305. })
  306. total = int64(len(filteredRequests))
  307. // Apply pagination manually for expired filter
  308. if pageSize > 0 {
  309. offset := (page - 1) * pageSize
  310. end := offset + pageSize
  311. if offset >= len(filteredRequests) {
  312. requests = []schema.JITRequest{}
  313. } else {
  314. if end > len(filteredRequests) {
  315. end = len(filteredRequests)
  316. }
  317. requests = filteredRequests[offset:end]
  318. }
  319. } else {
  320. requests = filteredRequests
  321. }
  322. } else {
  323. // Filter by status: pending, approved, or denied
  324. total, err = request.CountByStatusAndNetwork(ctx, statusFilter)
  325. if err != nil {
  326. return nil, 0, err
  327. }
  328. requests, err = request.ListByStatusAndNetwork(paginatedCtx, statusFilter)
  329. if err != nil {
  330. return nil, 0, err
  331. }
  332. }
  333. // Enrich requests with grant_id for approved requests
  334. result := make([]JITRequestWithGrant, 0, len(requests))
  335. for _, req := range requests {
  336. enriched := JITRequestWithGrant{
  337. JITRequest: req,
  338. }
  339. // If request is approved or expired, get the associated grant ID
  340. if req.Status == "approved" || req.Status == "expired" {
  341. grant := schema.JITGrant{RequestID: req.ID}
  342. if grantObj, err := grant.GetByRequestID(ctx); err == nil {
  343. enriched.GrantID = grantObj.ID
  344. }
  345. }
  346. result = append(result, enriched)
  347. }
  348. return result, total, nil
  349. }
  350. // GetUserJITStatus - gets JIT status for a user on a network
  351. func GetUserJITStatus(networkID, userID string) (*JITStatusResponse, error) {
  352. ctx := db.WithContext(context.Background())
  353. response := &JITStatusResponse{}
  354. // Check for active grant
  355. hasAccess, grant, err := CheckJITAccess(networkID, userID)
  356. if err != nil {
  357. return nil, err
  358. }
  359. response.HasAccess = hasAccess
  360. response.Grant = grant
  361. // Check for pending request
  362. request := schema.JITRequest{
  363. NetworkID: networkID,
  364. UserID: userID,
  365. }
  366. pendingRequests, err := request.ListPendingByNetwork(ctx)
  367. if err == nil {
  368. for _, req := range pendingRequests {
  369. if req.UserID == userID {
  370. response.PendingRequest = true
  371. response.Request = &req
  372. break
  373. }
  374. }
  375. }
  376. return response, nil
  377. }
  378. // UserJITNetworkStatus - represents JIT status for a network from user's perspective
  379. type UserJITNetworkStatus struct {
  380. NetworkID string `json:"network_id"`
  381. NetworkName string `json:"network_name,omitempty"`
  382. JITEnabled bool `json:"jit_enabled"`
  383. HasAccess bool `json:"has_access"`
  384. Grant *schema.JITGrant `json:"grant,omitempty"`
  385. Request *schema.JITRequest `json:"request,omitempty"`
  386. PendingRequest bool `json:"pending_request"`
  387. }
  388. // isUserAdminForNetwork - checks if user is super admin, admin, network admin, or global network admin
  389. func isUserAdminForNetwork(user *models.User, networkID string) bool {
  390. // Check platform role (super admin or admin)
  391. if user.PlatformRoleID == models.SuperAdminRole || user.PlatformRoleID == models.AdminRole {
  392. return true
  393. }
  394. if user.PlatformRoleID != models.PlatformUser {
  395. return false
  396. }
  397. networkIDModel := models.NetworkID(networkID)
  398. allNetworksID := models.AllNetworks
  399. globalNetworksAdminRoleID := models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkAdmin))
  400. // Check user groups for network admin roles
  401. for groupID := range user.UserGroups {
  402. groupData, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, groupID.String())
  403. if err != nil {
  404. continue
  405. }
  406. var group models.UserGroup
  407. if err := json.Unmarshal([]byte(groupData), &group); err != nil {
  408. continue
  409. }
  410. // Check if group has network admin role for this network
  411. if roles, ok := group.NetworkRoles[networkIDModel]; ok {
  412. for roleID := range roles {
  413. if roleID == models.NetworkAdmin {
  414. return true
  415. }
  416. }
  417. }
  418. // Check if group has global network admin role
  419. if roles, ok := group.NetworkRoles[allNetworksID]; ok {
  420. for roleID := range roles {
  421. if roleID == models.NetworkAdmin || roleID == globalNetworksAdminRoleID {
  422. return true
  423. }
  424. }
  425. }
  426. }
  427. return false
  428. }
  429. // GetUserJITNetworksStatus - gets JIT status for multiple networks for a user
  430. func GetUserJITNetworksStatus(networks []models.Network, userID string) ([]UserJITNetworkStatus, error) {
  431. ctx := db.WithContext(context.Background())
  432. var result []UserJITNetworkStatus
  433. // Get user to check admin status
  434. user, err := logic.GetUser(userID)
  435. if err != nil {
  436. return nil, fmt.Errorf("failed to get user: %w", err)
  437. }
  438. for _, network := range networks {
  439. status := UserJITNetworkStatus{
  440. NetworkID: network.NetID,
  441. NetworkName: network.NetID, // Can be enhanced with network display name if available
  442. JITEnabled: network.JITEnabled == "yes",
  443. HasAccess: false,
  444. PendingRequest: false,
  445. }
  446. // Check if user is admin - if so, show JIT as disabled and has access
  447. if isUserAdminForNetwork(user, network.NetID) {
  448. status.JITEnabled = false
  449. status.HasAccess = true
  450. result = append(result, status)
  451. continue
  452. }
  453. // Only check JIT status if JIT is enabled on the network
  454. if status.JITEnabled {
  455. // Check for active grant
  456. hasAccess, grant, err := CheckJITAccess(network.NetID, userID)
  457. if err != nil {
  458. slog.Warn("failed to check JIT access", "network", network.NetID, "user", userID, "error", err)
  459. // Continue with default values
  460. } else {
  461. status.HasAccess = hasAccess
  462. status.Grant = grant
  463. }
  464. // Check for pending request
  465. request := schema.JITRequest{
  466. NetworkID: network.NetID,
  467. UserID: userID,
  468. }
  469. pendingRequests, err := request.ListPendingByNetwork(ctx)
  470. if err == nil {
  471. for _, req := range pendingRequests {
  472. if req.UserID == userID {
  473. status.PendingRequest = true
  474. status.Request = &req
  475. break
  476. }
  477. }
  478. }
  479. } else {
  480. // JIT not enabled, user has access
  481. status.HasAccess = true
  482. }
  483. result = append(result, status)
  484. }
  485. return result, nil
  486. }
  487. // ExpireJITGrants - expires grants that have passed their expiration time
  488. func ExpireJITGrants() error {
  489. ctx := db.WithContext(context.Background())
  490. grant := schema.JITGrant{}
  491. expiredGrants, err := grant.ListExpired(ctx)
  492. if err != nil {
  493. return fmt.Errorf("failed to list expired grants: %w", err)
  494. }
  495. for _, expiredGrant := range expiredGrants {
  496. var request *schema.JITRequest
  497. // Update associated request status to "expired" before deleting grant
  498. if expiredGrant.RequestID != "" {
  499. req := schema.JITRequest{ID: expiredGrant.RequestID}
  500. if err := req.Get(ctx); err == nil {
  501. request = &req
  502. // Only update if request is currently approved
  503. if request.Status == "approved" {
  504. request.Status = "expired"
  505. if err := request.Update(ctx); err != nil {
  506. slog.Warn("failed to update request status when expiring grant",
  507. "grant_id", expiredGrant.ID, "request_id", expiredGrant.RequestID, "error", err)
  508. // Don't fail the operation, just log
  509. }
  510. }
  511. }
  512. }
  513. // Disconnect user's ext clients from the network
  514. if err := disconnectUserExtClients(expiredGrant.NetworkID, expiredGrant.UserID); err != nil {
  515. slog.Error("failed to disconnect ext clients for expired grant",
  516. "grant_id", expiredGrant.ID, "user_id", expiredGrant.UserID, "error", err)
  517. }
  518. // Delete the expired grant
  519. if err := expiredGrant.Delete(ctx); err != nil {
  520. slog.Error("failed to delete expired grant", "grant_id", expiredGrant.ID, "error", err)
  521. continue
  522. }
  523. logger.Log(1, fmt.Sprintf("Expired and deleted JIT grant %s for user %s on network %s",
  524. expiredGrant.ID, expiredGrant.UserID, expiredGrant.NetworkID))
  525. }
  526. return nil
  527. }
  528. // DisconnectExtClientsFromNetwork - disconnects all ext clients from a network
  529. func DisconnectExtClientsFromNetwork(networkID string) error {
  530. extClients, err := logic.GetNetworkExtClients(networkID)
  531. if err != nil {
  532. return fmt.Errorf("failed to get ext clients: %w", err)
  533. }
  534. for _, client := range extClients {
  535. if err := logic.DeleteExtClient(client.Network, client.ClientID, false); err != nil {
  536. slog.Warn("failed to delete ext client when disabling JIT",
  537. "client_id", client.ClientID, "network", networkID, "error", err)
  538. continue
  539. }
  540. // DeleteExtClient handles MQ notifications internally
  541. }
  542. return nil
  543. }
  544. // GetNetworkAdmins - gets all network admins for a network
  545. func GetNetworkAdmins(networkID string) ([]models.User, error) {
  546. var admins []models.User
  547. users, err := logic.GetUsersDB()
  548. if err != nil {
  549. return admins, fmt.Errorf("failed to get users: %w", err)
  550. }
  551. networkIDModel := models.NetworkID(networkID)
  552. allNetworksID := models.AllNetworks
  553. for _, user := range users {
  554. isAdmin := false
  555. // Check platform role (super admin or admin)
  556. if user.PlatformRoleID == models.SuperAdminRole || user.PlatformRoleID == models.AdminRole {
  557. isAdmin = true
  558. }
  559. // Check network-specific roles
  560. if roles, ok := user.NetworkRoles[networkIDModel]; ok {
  561. for roleID := range roles {
  562. if roleID == models.NetworkAdmin {
  563. isAdmin = true
  564. break
  565. }
  566. }
  567. }
  568. // Check all-networks role
  569. globalNetworksAdminRoleID := models.UserRoleID(fmt.Sprintf("global-%s", models.NetworkAdmin))
  570. if roles, ok := user.NetworkRoles[allNetworksID]; ok {
  571. for roleID := range roles {
  572. if roleID == models.NetworkAdmin || roleID == globalNetworksAdminRoleID {
  573. isAdmin = true
  574. break
  575. }
  576. }
  577. }
  578. // Check user groups
  579. for groupID := range user.UserGroups {
  580. groupData, err := database.FetchRecord(database.USER_GROUPS_TABLE_NAME, groupID.String())
  581. if err != nil {
  582. continue
  583. }
  584. var group models.UserGroup
  585. if err := json.Unmarshal([]byte(groupData), &group); err != nil {
  586. continue
  587. }
  588. // Check if group has network admin role for this network
  589. if roles, ok := group.NetworkRoles[networkIDModel]; ok {
  590. for roleID := range roles {
  591. if roleID == models.NetworkAdmin {
  592. isAdmin = true
  593. break
  594. }
  595. }
  596. }
  597. if roles, ok := group.NetworkRoles[allNetworksID]; ok {
  598. for roleID := range roles {
  599. if roleID == models.NetworkAdmin || roleID == globalNetworksAdminRoleID {
  600. isAdmin = true
  601. break
  602. }
  603. }
  604. }
  605. }
  606. if isAdmin {
  607. admins = append(admins, user)
  608. }
  609. }
  610. return admins, nil
  611. }
  612. // Helper functions
  613. func deactivateUserGrants(ctx context.Context, networkID, userID string) error {
  614. return DeactivateUserGrantsOnNetwork(networkID, userID)
  615. }
  616. // DeactivateUserGrantsOnNetwork - deletes all active grants for a user on a network
  617. func DeactivateUserGrantsOnNetwork(networkID, userID string) error {
  618. ctx := db.WithContext(context.Background())
  619. grant := schema.JITGrant{
  620. NetworkID: networkID,
  621. UserID: userID,
  622. }
  623. grants, err := grant.ListByUserAndNetwork(ctx)
  624. if err != nil {
  625. return err
  626. }
  627. for _, g := range grants {
  628. // Only delete grants that haven't expired yet (active grants)
  629. if time.Now().UTC().Before(g.ExpiresAt) {
  630. if err := g.Delete(ctx); err != nil {
  631. return fmt.Errorf("failed to delete grant %s: %w", g.ID, err)
  632. }
  633. }
  634. }
  635. return nil
  636. }
  637. // DisconnectUserExtClientsFromNetwork - disconnects a specific user's ext clients from a network
  638. func DisconnectUserExtClientsFromNetwork(networkID, userID string) error {
  639. return disconnectUserExtClients(networkID, userID)
  640. }
  641. func disconnectUserExtClients(networkID, userID string) error {
  642. extClients, err := logic.GetNetworkExtClients(networkID)
  643. if err != nil {
  644. return err
  645. }
  646. for _, client := range extClients {
  647. // Check if this ext client belongs to the user
  648. // Ext clients have OwnerID field that should match userID
  649. if client.OwnerID == userID {
  650. // Store original client for MQ notification
  651. clientCopy := client
  652. // Disable the ext client instead of deleting it
  653. // This preserves the client record so desktop apps can see the expiry status
  654. disabledClient, err := logic.ToggleExtClientConnectivity(&client, false)
  655. if err != nil {
  656. slog.Warn("failed to disable ext client", "client_id", client.ClientID, "error", err)
  657. continue
  658. }
  659. // Set JIT expiry to now to indicate revocation/expiry
  660. // This allows desktop apps to see the revocation when they poll the API
  661. now := time.Now().UTC()
  662. disabledClient.JITExpiresAt = &now
  663. if err := logic.SaveExtClient(&disabledClient); err != nil {
  664. slog.Warn("failed to update ext client expiry", "client_id", client.ClientID, "error", err)
  665. // Continue even if update fails
  666. }
  667. // Publish MQ peer update to notify ingress gateway nodes
  668. // This ensures nodes immediately remove the peer from WireGuard config
  669. if err := mq.PublishDeletedClientPeerUpdate(&clientCopy); err != nil {
  670. slog.Warn("failed to publish deleted client peer update",
  671. "client_id", client.ClientID, "error", err)
  672. // Don't fail the operation, just log
  673. }
  674. }
  675. }
  676. return nil
  677. }