sync.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  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. networks, err := logic.GetNetworks()
  226. if err != nil {
  227. return err
  228. }
  229. var aclsUpdated bool
  230. var acls []models.Acl
  231. for _, network := range networks {
  232. aclID := fmt.Sprintf("%s.%s-grp", network.NetID, models.NetworkUser)
  233. acl, err := logic.GetAcl(aclID)
  234. if err == nil {
  235. acls = append(acls, acl)
  236. }
  237. }
  238. for _, group := range idpGroups {
  239. var found bool
  240. for _, filter := range filters {
  241. if strings.HasPrefix(group.Name, filter) {
  242. found = true
  243. break
  244. }
  245. }
  246. // if there are filters but none of them match, then skip this group.
  247. if len(filters) > 0 && !found {
  248. continue
  249. }
  250. dbGroup, ok := dbGroupsMap[group.ID]
  251. if !ok {
  252. dbGroup.ExternalIdentityProviderID = group.ID
  253. dbGroup.Name = group.Name
  254. dbGroup.Default = false
  255. dbGroup.NetworkRoles = map[models.NetworkID]map[models.UserRoleID]struct{}{
  256. models.AllNetworks: {
  257. proLogic.GetDefaultGlobalUserRoleID(): {},
  258. },
  259. }
  260. err := proLogic.CreateUserGroup(&dbGroup)
  261. if err != nil {
  262. return err
  263. }
  264. for i := range acls {
  265. acls[i].Src = append(acls[i].Src, models.AclPolicyTag{
  266. ID: models.UserGroupAclID,
  267. Value: dbGroup.ID.String(),
  268. })
  269. aclsUpdated = true
  270. }
  271. } else {
  272. dbGroup.Name = group.Name
  273. err = proLogic.UpdateUserGroup(dbGroup)
  274. if err != nil {
  275. return err
  276. }
  277. }
  278. groupMembersMap := make(map[string]struct{})
  279. for _, member := range group.Members {
  280. groupMembersMap[member] = struct{}{}
  281. }
  282. for _, user := range dbUsers {
  283. // use dbGroup.Name because the group name may have been changed on idp.
  284. _, inNetmakerGroup := user.UserGroups[dbGroup.ID]
  285. _, inIDPGroup := groupMembersMap[user.ExternalIdentityProviderID]
  286. if inNetmakerGroup && !inIDPGroup {
  287. // use dbGroup.Name because the group name may have been changed on idp.
  288. delete(dbUsersMap[user.ExternalIdentityProviderID].UserGroups, dbGroup.ID)
  289. modifiedUsers[user.ExternalIdentityProviderID] = struct{}{}
  290. }
  291. if !inNetmakerGroup && inIDPGroup {
  292. // use dbGroup.Name because the group name may have been changed on idp.
  293. dbUsersMap[user.ExternalIdentityProviderID].UserGroups[dbGroup.ID] = struct{}{}
  294. modifiedUsers[user.ExternalIdentityProviderID] = struct{}{}
  295. }
  296. }
  297. }
  298. for userID := range modifiedUsers {
  299. err = logic.UpsertUser(dbUsersMap[userID])
  300. if err != nil {
  301. return err
  302. }
  303. }
  304. for _, group := range dbGroups {
  305. if group.ExternalIdentityProviderID != "" {
  306. if _, ok := idpGroupsMap[group.ExternalIdentityProviderID]; !ok {
  307. // delete the group if it has been deleted on idp
  308. // or is filtered out.
  309. err = proLogic.DeleteAndCleanUpGroup(&group)
  310. if err != nil {
  311. return err
  312. }
  313. }
  314. }
  315. }
  316. if aclsUpdated {
  317. for _, acl := range acls {
  318. err = logic.UpsertAcl(acl)
  319. if err != nil {
  320. return err
  321. }
  322. }
  323. }
  324. return nil
  325. }
  326. func GetIDPSyncStatus() models.IDPSyncStatus {
  327. if idpSyncMtx.TryLock() {
  328. defer idpSyncMtx.Unlock()
  329. if idpSyncErr == nil {
  330. return models.IDPSyncStatus{
  331. Status: "completed",
  332. }
  333. } else {
  334. return models.IDPSyncStatus{
  335. Status: "failed",
  336. Description: idpSyncErr.Error(),
  337. }
  338. }
  339. } else {
  340. return models.IDPSyncStatus{
  341. Status: "in_progress",
  342. }
  343. }
  344. }
  345. func filterUsersByGroupMembership(idpUsers []idp.User, idpGroups []idp.Group) []idp.User {
  346. usersMap := make(map[string]int)
  347. for i, user := range idpUsers {
  348. usersMap[user.ID] = i
  349. }
  350. filteredUsersMap := make(map[string]int)
  351. for _, group := range idpGroups {
  352. for _, member := range group.Members {
  353. if userIdx, ok := usersMap[member]; ok {
  354. // user at index `userIdx` is a member of at least one of the
  355. // groups in the `idpGroups` list, so we keep it.
  356. filteredUsersMap[member] = userIdx
  357. }
  358. }
  359. }
  360. i := 0
  361. filteredUsers := make([]idp.User, len(filteredUsersMap))
  362. for _, userIdx := range filteredUsersMap {
  363. filteredUsers[i] = idpUsers[userIdx]
  364. i++
  365. }
  366. return filteredUsers
  367. }
  368. func filterGroupsByMembers(idpGroups []idp.Group, idpUsers []idp.User) []idp.Group {
  369. usersMap := make(map[string]int)
  370. for i, user := range idpUsers {
  371. usersMap[user.ID] = i
  372. }
  373. filteredGroupsMap := make(map[int]bool)
  374. for i, group := range idpGroups {
  375. var members []string
  376. for _, member := range group.Members {
  377. if _, ok := usersMap[member]; ok {
  378. members = append(members, member)
  379. }
  380. }
  381. if len(members) > 0 {
  382. // the group at index `i` has members from the `idpUsers` list,
  383. // so we keep it.
  384. filteredGroupsMap[i] = true
  385. // filter out members that were not provided in the `idpUsers` list.
  386. idpGroups[i].Members = members
  387. }
  388. }
  389. i := 0
  390. filteredGroups := make([]idp.Group, len(filteredGroupsMap))
  391. for groupIdx := range filteredGroupsMap {
  392. filteredGroups[i] = idpGroups[groupIdx]
  393. i++
  394. }
  395. return filteredGroups
  396. }
  397. // TODO: deduplicate
  398. // The cyclic import between the package logic and mq requires this
  399. // function to be duplicated in multiple places.
  400. func deleteAndCleanUpUser(user *models.User) error {
  401. err := logic.DeleteUser(user.UserName)
  402. if err != nil {
  403. return err
  404. }
  405. // check and delete extclient with this ownerID
  406. go func() {
  407. extclients, err := logic.GetAllExtClients()
  408. if err != nil {
  409. return
  410. }
  411. for _, extclient := range extclients {
  412. if extclient.OwnerID == user.UserName {
  413. err = logic.DeleteExtClientAndCleanup(extclient)
  414. if err == nil {
  415. _ = mq.PublishDeletedClientPeerUpdate(&extclient)
  416. }
  417. }
  418. }
  419. go logic.DeleteUserInvite(user.UserName)
  420. go mq.PublishPeerUpdate(false)
  421. if servercfg.IsDNSMode() {
  422. go logic.SetDNS()
  423. }
  424. }()
  425. return nil
  426. }