sync.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  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()
  87. if err != nil {
  88. return err
  89. }
  90. idpGroups, err = idpClient.GetGroups()
  91. if err != nil {
  92. return err
  93. }
  94. }
  95. err = syncUsers(idpUsers)
  96. if err != nil {
  97. return err
  98. }
  99. err = syncGroups(idpGroups)
  100. return err
  101. }
  102. func syncUsers(idpUsers []idp.User) error {
  103. dbUsers, err := logic.GetUsersDB()
  104. if err != nil && !database.IsEmptyRecord(err) {
  105. return err
  106. }
  107. password, err := logic.FetchPassValue("")
  108. if err != nil {
  109. return err
  110. }
  111. idpUsersMap := make(map[string]struct{})
  112. for _, user := range idpUsers {
  113. idpUsersMap[user.Username] = struct{}{}
  114. }
  115. dbUsersMap := make(map[string]models.User)
  116. for _, user := range dbUsers {
  117. dbUsersMap[user.UserName] = user
  118. }
  119. filters := logic.GetServerSettings().UserFilters
  120. for _, user := range idpUsers {
  121. if user.AccountArchived {
  122. // delete the user if it has been archived.
  123. _ = logic.DeleteUser(user.Username)
  124. continue
  125. }
  126. var found bool
  127. for _, filter := range filters {
  128. if strings.HasPrefix(user.Username, filter) {
  129. found = true
  130. break
  131. }
  132. }
  133. // if there are filters but none of them match, then skip this user.
  134. if len(filters) > 0 && !found {
  135. continue
  136. }
  137. dbUser, ok := dbUsersMap[user.Username]
  138. if !ok {
  139. // create the user only if it doesn't exist.
  140. err = logic.CreateUser(&models.User{
  141. UserName: user.Username,
  142. ExternalIdentityProviderID: user.ID,
  143. DisplayName: user.DisplayName,
  144. AccountDisabled: user.AccountDisabled,
  145. Password: password,
  146. AuthType: models.OAuth,
  147. PlatformRoleID: models.ServiceUser,
  148. })
  149. if err != nil {
  150. return err
  151. }
  152. // It's possible that a user can attempt to log in to Netmaker
  153. // after the IDP is configured but before the users are synced.
  154. // Since the user doesn't exist, a pending user will be
  155. // created. Now, since the user is created, the pending user
  156. // can be deleted.
  157. _ = logic.DeletePendingUser(user.Username)
  158. } else if dbUser.AuthType == models.OAuth {
  159. if dbUser.AccountDisabled != user.AccountDisabled ||
  160. dbUser.DisplayName != user.DisplayName ||
  161. dbUser.ExternalIdentityProviderID != user.ID {
  162. dbUser.AccountDisabled = user.AccountDisabled
  163. dbUser.DisplayName = user.DisplayName
  164. dbUser.ExternalIdentityProviderID = user.ID
  165. err = logic.UpsertUser(dbUser)
  166. if err != nil {
  167. return err
  168. }
  169. }
  170. } else {
  171. logger.Log(0, "user with username "+user.Username+" already exists, skipping creation")
  172. continue
  173. }
  174. }
  175. for _, user := range dbUsersMap {
  176. if user.ExternalIdentityProviderID == "" {
  177. continue
  178. }
  179. if _, ok := idpUsersMap[user.UserName]; !ok {
  180. // delete the user if it has been deleted on idp.
  181. err = logic.DeleteUser(user.UserName)
  182. if err != nil {
  183. return err
  184. }
  185. }
  186. }
  187. return nil
  188. }
  189. func syncGroups(idpGroups []idp.Group) error {
  190. dbGroups, err := proLogic.ListUserGroups()
  191. if err != nil && !database.IsEmptyRecord(err) {
  192. return err
  193. }
  194. dbUsers, err := logic.GetUsersDB()
  195. if err != nil && !database.IsEmptyRecord(err) {
  196. return err
  197. }
  198. idpGroupsMap := make(map[string]struct{})
  199. for _, group := range idpGroups {
  200. idpGroupsMap[group.ID] = struct{}{}
  201. }
  202. dbGroupsMap := make(map[string]models.UserGroup)
  203. for _, group := range dbGroups {
  204. if group.ExternalIdentityProviderID != "" {
  205. dbGroupsMap[group.ExternalIdentityProviderID] = group
  206. }
  207. }
  208. dbUsersMap := make(map[string]models.User)
  209. for _, user := range dbUsers {
  210. if user.ExternalIdentityProviderID != "" {
  211. dbUsersMap[user.ExternalIdentityProviderID] = user
  212. }
  213. }
  214. modifiedUsers := make(map[string]struct{})
  215. filters := logic.GetServerSettings().GroupFilters
  216. for _, group := range idpGroups {
  217. var found bool
  218. for _, filter := range filters {
  219. if strings.HasPrefix(group.Name, filter) {
  220. found = true
  221. break
  222. }
  223. }
  224. // if there are filters but none of them match, then skip this group.
  225. if len(filters) > 0 && !found {
  226. continue
  227. }
  228. dbGroup, ok := dbGroupsMap[group.ID]
  229. if !ok {
  230. dbGroup.ExternalIdentityProviderID = group.ID
  231. dbGroup.Name = group.Name
  232. dbGroup.Default = false
  233. dbGroup.NetworkRoles = make(map[models.NetworkID]map[models.UserRoleID]struct{})
  234. err := proLogic.CreateUserGroup(&dbGroup)
  235. if err != nil {
  236. return err
  237. }
  238. } else {
  239. dbGroup.Name = group.Name
  240. err = proLogic.UpdateUserGroup(dbGroup)
  241. if err != nil {
  242. return err
  243. }
  244. }
  245. groupMembersMap := make(map[string]struct{})
  246. for _, member := range group.Members {
  247. groupMembersMap[member] = struct{}{}
  248. }
  249. for _, user := range dbUsers {
  250. // use dbGroup.Name because the group name may have been changed on idp.
  251. _, inNetmakerGroup := user.UserGroups[dbGroup.ID]
  252. _, inIDPGroup := groupMembersMap[user.ExternalIdentityProviderID]
  253. if inNetmakerGroup && !inIDPGroup {
  254. // use dbGroup.Name because the group name may have been changed on idp.
  255. delete(dbUsersMap[user.ExternalIdentityProviderID].UserGroups, dbGroup.ID)
  256. modifiedUsers[user.ExternalIdentityProviderID] = struct{}{}
  257. }
  258. if !inNetmakerGroup && inIDPGroup {
  259. // use dbGroup.Name because the group name may have been changed on idp.
  260. dbUsersMap[user.ExternalIdentityProviderID].UserGroups[dbGroup.ID] = struct{}{}
  261. modifiedUsers[user.ExternalIdentityProviderID] = struct{}{}
  262. }
  263. }
  264. }
  265. for userID := range modifiedUsers {
  266. err = logic.UpsertUser(dbUsersMap[userID])
  267. if err != nil {
  268. return err
  269. }
  270. }
  271. for _, group := range dbGroups {
  272. if group.ExternalIdentityProviderID != "" {
  273. if _, ok := idpGroupsMap[group.ExternalIdentityProviderID]; !ok {
  274. // delete the group if it has been deleted on idp.
  275. err = proLogic.DeleteUserGroup(group.ID)
  276. if err != nil {
  277. return err
  278. }
  279. }
  280. }
  281. }
  282. return nil
  283. }
  284. func GetIDPSyncStatus() models.IDPSyncStatus {
  285. if idpSyncMtx.TryLock() {
  286. defer idpSyncMtx.Unlock()
  287. if idpSyncErr == nil {
  288. return models.IDPSyncStatus{
  289. Status: "completed",
  290. }
  291. } else {
  292. return models.IDPSyncStatus{
  293. Status: "failed",
  294. Description: idpSyncErr.Error(),
  295. }
  296. }
  297. } else {
  298. return models.IDPSyncStatus{
  299. Status: "in_progress",
  300. }
  301. }
  302. }