sync.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. package auth
  2. import (
  3. "context"
  4. "fmt"
  5. "strings"
  6. "sync"
  7. "time"
  8. "github.com/gravitl/netmaker/database"
  9. "github.com/gravitl/netmaker/logger"
  10. "github.com/gravitl/netmaker/logic"
  11. "github.com/gravitl/netmaker/models"
  12. "github.com/gravitl/netmaker/mq"
  13. "github.com/gravitl/netmaker/pro/idp"
  14. "github.com/gravitl/netmaker/pro/idp/azure"
  15. "github.com/gravitl/netmaker/pro/idp/google"
  16. "github.com/gravitl/netmaker/pro/idp/okta"
  17. proLogic "github.com/gravitl/netmaker/pro/logic"
  18. "github.com/gravitl/netmaker/servercfg"
  19. )
  20. var (
  21. cancelSyncHook context.CancelFunc
  22. hookStopWg sync.WaitGroup
  23. idpSyncMtx sync.Mutex
  24. idpSyncErr error
  25. )
  26. func ResetIDPSyncHook() {
  27. if cancelSyncHook != nil {
  28. cancelSyncHook()
  29. hookStopWg.Wait()
  30. cancelSyncHook = nil
  31. }
  32. if logic.IsSyncEnabled() {
  33. ctx, cancel := context.WithCancel(context.Background())
  34. cancelSyncHook = cancel
  35. hookStopWg.Add(1)
  36. go runIDPSyncHook(ctx)
  37. }
  38. }
  39. func runIDPSyncHook(ctx context.Context) {
  40. defer hookStopWg.Done()
  41. ticker := time.NewTicker(logic.GetIDPSyncInterval())
  42. defer ticker.Stop()
  43. for {
  44. select {
  45. case <-ctx.Done():
  46. logger.Log(0, "idp sync hook stopped")
  47. return
  48. case <-ticker.C:
  49. if err := SyncFromIDP(); err != nil {
  50. logger.Log(0, "failed to sync from idp: ", err.Error())
  51. } else {
  52. logger.Log(0, "sync from idp complete")
  53. }
  54. }
  55. }
  56. }
  57. func SyncFromIDP() error {
  58. idpSyncMtx.Lock()
  59. defer idpSyncMtx.Unlock()
  60. settings := logic.GetServerSettings()
  61. var idpClient idp.Client
  62. var idpUsers []idp.User
  63. var idpGroups []idp.Group
  64. var err error
  65. defer func() {
  66. idpSyncErr = err
  67. }()
  68. switch settings.AuthProvider {
  69. case "google":
  70. idpClient, err = google.NewGoogleWorkspaceClientFromSettings()
  71. if err != nil {
  72. return err
  73. }
  74. case "azure-ad":
  75. idpClient = azure.NewAzureEntraIDClientFromSettings()
  76. case "okta":
  77. idpClient, err = okta.NewOktaClientFromSettings()
  78. if err != nil {
  79. return err
  80. }
  81. default:
  82. if settings.AuthProvider != "" {
  83. err = fmt.Errorf("invalid auth provider: %s", settings.AuthProvider)
  84. return err
  85. }
  86. }
  87. if settings.AuthProvider != "" && idpClient != nil {
  88. idpUsers, err = idpClient.GetUsers(settings.UserFilters)
  89. if err != nil {
  90. return err
  91. }
  92. idpGroups, err = idpClient.GetGroups(settings.GroupFilters)
  93. if err != nil {
  94. return err
  95. }
  96. if len(settings.GroupFilters) > 0 {
  97. idpUsers = filterUsersByGroupMembership(idpUsers, idpGroups)
  98. }
  99. if len(settings.UserFilters) > 0 {
  100. idpGroups = filterGroupsByMembers(idpGroups, idpUsers)
  101. }
  102. }
  103. err = syncUsers(idpUsers)
  104. if err != nil {
  105. return err
  106. }
  107. err = syncGroups(idpGroups)
  108. return err
  109. }
  110. func syncUsers(idpUsers []idp.User) error {
  111. dbUsers, err := logic.GetUsersDB()
  112. if err != nil && !database.IsEmptyRecord(err) {
  113. return err
  114. }
  115. password, err := logic.FetchPassValue("")
  116. if err != nil {
  117. return err
  118. }
  119. idpUsersMap := make(map[string]struct{})
  120. for _, user := range idpUsers {
  121. idpUsersMap[user.Username] = struct{}{}
  122. }
  123. dbUsersMap := make(map[string]models.User)
  124. for _, user := range dbUsers {
  125. dbUsersMap[user.UserName] = user
  126. }
  127. filters := logic.GetServerSettings().UserFilters
  128. for _, user := range idpUsers {
  129. if user.AccountArchived {
  130. // delete the user if it has been archived.
  131. user := dbUsersMap[user.Username]
  132. _ = deleteAndCleanUpUser(&user)
  133. continue
  134. }
  135. var found bool
  136. for _, filter := range filters {
  137. if strings.HasPrefix(user.Username, filter) {
  138. found = true
  139. break
  140. }
  141. }
  142. // if there are filters but none of them match, then skip this user.
  143. if len(filters) > 0 && !found {
  144. continue
  145. }
  146. dbUser, ok := dbUsersMap[user.Username]
  147. if !ok {
  148. // create the user only if it doesn't exist.
  149. err = logic.CreateUser(&models.User{
  150. UserName: user.Username,
  151. ExternalIdentityProviderID: user.ID,
  152. DisplayName: user.DisplayName,
  153. AccountDisabled: user.AccountDisabled,
  154. Password: password,
  155. AuthType: models.OAuth,
  156. PlatformRoleID: models.ServiceUser,
  157. })
  158. if err != nil {
  159. return err
  160. }
  161. // It's possible that a user can attempt to log in to Netmaker
  162. // after the IDP is configured but before the users are synced.
  163. // Since the user doesn't exist, a pending user will be
  164. // created. Now, since the user is created, the pending user
  165. // can be deleted.
  166. _ = logic.DeletePendingUser(user.Username)
  167. } else if dbUser.AuthType == models.OAuth {
  168. if dbUser.AccountDisabled != user.AccountDisabled ||
  169. dbUser.DisplayName != user.DisplayName ||
  170. dbUser.ExternalIdentityProviderID != user.ID {
  171. dbUser.AccountDisabled = user.AccountDisabled
  172. dbUser.DisplayName = user.DisplayName
  173. dbUser.ExternalIdentityProviderID = user.ID
  174. err = logic.UpsertUser(dbUser)
  175. if err != nil {
  176. return err
  177. }
  178. }
  179. } else {
  180. logger.Log(0, "user with username "+user.Username+" already exists, skipping creation")
  181. continue
  182. }
  183. }
  184. for _, user := range dbUsersMap {
  185. if user.ExternalIdentityProviderID != "" {
  186. if _, ok := idpUsersMap[user.UserName]; !ok {
  187. // delete the user if it has been deleted on idp
  188. // or is filtered out.
  189. err = deleteAndCleanUpUser(&user)
  190. if err != nil {
  191. return err
  192. }
  193. }
  194. }
  195. }
  196. return nil
  197. }
  198. func syncGroups(idpGroups []idp.Group) error {
  199. dbGroups, err := proLogic.ListUserGroups()
  200. if err != nil && !database.IsEmptyRecord(err) {
  201. return err
  202. }
  203. dbUsers, err := logic.GetUsersDB()
  204. if err != nil && !database.IsEmptyRecord(err) {
  205. return err
  206. }
  207. idpGroupsMap := make(map[string]struct{})
  208. for _, group := range idpGroups {
  209. idpGroupsMap[group.ID] = struct{}{}
  210. }
  211. dbGroupsMap := make(map[string]models.UserGroup)
  212. for _, group := range dbGroups {
  213. if group.ExternalIdentityProviderID != "" {
  214. dbGroupsMap[group.ExternalIdentityProviderID] = group
  215. }
  216. }
  217. dbUsersMap := make(map[string]models.User)
  218. for _, user := range dbUsers {
  219. if user.ExternalIdentityProviderID != "" {
  220. dbUsersMap[user.ExternalIdentityProviderID] = user
  221. }
  222. }
  223. modifiedUsers := make(map[string]struct{})
  224. filters := logic.GetServerSettings().GroupFilters
  225. for _, group := range idpGroups {
  226. var found bool
  227. for _, filter := range filters {
  228. if strings.HasPrefix(group.Name, filter) {
  229. found = true
  230. break
  231. }
  232. }
  233. // if there are filters but none of them match, then skip this group.
  234. if len(filters) > 0 && !found {
  235. continue
  236. }
  237. dbGroup, ok := dbGroupsMap[group.ID]
  238. if !ok {
  239. dbGroup.ExternalIdentityProviderID = group.ID
  240. dbGroup.Name = group.Name
  241. dbGroup.Default = false
  242. dbGroup.NetworkRoles = map[models.NetworkID]map[models.UserRoleID]struct{}{
  243. models.AllNetworks: {
  244. proLogic.GetDefaultGlobalUserRoleID(): {},
  245. },
  246. }
  247. err := proLogic.CreateUserGroup(&dbGroup)
  248. if err != nil {
  249. return err
  250. }
  251. } else {
  252. dbGroup.Name = group.Name
  253. err = proLogic.UpdateUserGroup(dbGroup)
  254. if err != nil {
  255. return err
  256. }
  257. }
  258. groupMembersMap := make(map[string]struct{})
  259. for _, member := range group.Members {
  260. groupMembersMap[member] = struct{}{}
  261. }
  262. for _, user := range dbUsers {
  263. // use dbGroup.Name because the group name may have been changed on idp.
  264. _, inNetmakerGroup := user.UserGroups[dbGroup.ID]
  265. _, inIDPGroup := groupMembersMap[user.ExternalIdentityProviderID]
  266. if inNetmakerGroup && !inIDPGroup {
  267. // use dbGroup.Name because the group name may have been changed on idp.
  268. delete(dbUsersMap[user.ExternalIdentityProviderID].UserGroups, dbGroup.ID)
  269. modifiedUsers[user.ExternalIdentityProviderID] = struct{}{}
  270. }
  271. if !inNetmakerGroup && inIDPGroup {
  272. // use dbGroup.Name because the group name may have been changed on idp.
  273. dbUsersMap[user.ExternalIdentityProviderID].UserGroups[dbGroup.ID] = struct{}{}
  274. modifiedUsers[user.ExternalIdentityProviderID] = struct{}{}
  275. }
  276. }
  277. }
  278. for userID := range modifiedUsers {
  279. err = logic.UpsertUser(dbUsersMap[userID])
  280. if err != nil {
  281. return err
  282. }
  283. }
  284. for _, group := range dbGroups {
  285. if group.ExternalIdentityProviderID != "" {
  286. if _, ok := idpGroupsMap[group.ExternalIdentityProviderID]; !ok {
  287. // delete the group if it has been deleted on idp
  288. // or is filtered out.
  289. err = proLogic.DeleteAndCleanUpGroup(&group)
  290. if err != nil {
  291. return err
  292. }
  293. }
  294. }
  295. }
  296. return nil
  297. }
  298. func GetIDPSyncStatus() models.IDPSyncStatus {
  299. if idpSyncMtx.TryLock() {
  300. defer idpSyncMtx.Unlock()
  301. if idpSyncErr == nil {
  302. return models.IDPSyncStatus{
  303. Status: "completed",
  304. }
  305. } else {
  306. return models.IDPSyncStatus{
  307. Status: "failed",
  308. Description: idpSyncErr.Error(),
  309. }
  310. }
  311. } else {
  312. return models.IDPSyncStatus{
  313. Status: "in_progress",
  314. }
  315. }
  316. }
  317. func filterUsersByGroupMembership(idpUsers []idp.User, idpGroups []idp.Group) []idp.User {
  318. usersMap := make(map[string]int)
  319. for i, user := range idpUsers {
  320. usersMap[user.ID] = i
  321. }
  322. filteredUsersMap := make(map[string]int)
  323. for _, group := range idpGroups {
  324. for _, member := range group.Members {
  325. if userIdx, ok := usersMap[member]; ok {
  326. // user at index `userIdx` is a member of at least one of the
  327. // groups in the `idpGroups` list, so we keep it.
  328. filteredUsersMap[member] = userIdx
  329. }
  330. }
  331. }
  332. i := 0
  333. filteredUsers := make([]idp.User, len(filteredUsersMap))
  334. for _, userIdx := range filteredUsersMap {
  335. filteredUsers[i] = idpUsers[userIdx]
  336. i++
  337. }
  338. return filteredUsers
  339. }
  340. func filterGroupsByMembers(idpGroups []idp.Group, idpUsers []idp.User) []idp.Group {
  341. usersMap := make(map[string]int)
  342. for i, user := range idpUsers {
  343. usersMap[user.ID] = i
  344. }
  345. filteredGroupsMap := make(map[int]bool)
  346. for i, group := range idpGroups {
  347. var members []string
  348. for _, member := range group.Members {
  349. if _, ok := usersMap[member]; ok {
  350. members = append(members, member)
  351. }
  352. }
  353. if len(members) > 0 {
  354. // the group at index `i` has members from the `idpUsers` list,
  355. // so we keep it.
  356. filteredGroupsMap[i] = true
  357. // filter out members that were not provided in the `idpUsers` list.
  358. idpGroups[i].Members = members
  359. }
  360. }
  361. i := 0
  362. filteredGroups := make([]idp.Group, len(filteredGroupsMap))
  363. for groupIdx := range filteredGroupsMap {
  364. filteredGroups[i] = idpGroups[groupIdx]
  365. i++
  366. }
  367. return filteredGroups
  368. }
  369. // TODO: deduplicate
  370. // The cyclic import between the package logic and mq requires this
  371. // function to be duplicated in multiple places.
  372. func deleteAndCleanUpUser(user *models.User) error {
  373. err := logic.DeleteUser(user.UserName)
  374. if err != nil {
  375. return err
  376. }
  377. // check and delete extclient with this ownerID
  378. go func() {
  379. extclients, err := logic.GetAllExtClients()
  380. if err != nil {
  381. return
  382. }
  383. for _, extclient := range extclients {
  384. if extclient.OwnerID == user.UserName {
  385. err = logic.DeleteExtClientAndCleanup(extclient)
  386. if err == nil {
  387. _ = mq.PublishDeletedClientPeerUpdate(&extclient)
  388. }
  389. }
  390. }
  391. go logic.DeleteUserInvite(user.UserName)
  392. go mq.PublishPeerUpdate(false)
  393. if servercfg.IsDNSMode() {
  394. go logic.SetDNS()
  395. }
  396. }()
  397. return nil
  398. }