sync.go 9.8 KB

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