ldap.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. package ldap
  2. import (
  3. "crypto/tls"
  4. "crypto/x509"
  5. "errors"
  6. "fmt"
  7. "io/ioutil"
  8. "math"
  9. "strings"
  10. "github.com/davecgh/go-spew/spew"
  11. "gopkg.in/ldap.v3"
  12. "github.com/grafana/grafana/pkg/bus"
  13. "github.com/grafana/grafana/pkg/infra/log"
  14. "github.com/grafana/grafana/pkg/models"
  15. )
  16. // IConnection is interface for LDAP connection manipulation
  17. type IConnection interface {
  18. Bind(username, password string) error
  19. UnauthenticatedBind(username string) error
  20. Add(*ldap.AddRequest) error
  21. Del(*ldap.DelRequest) error
  22. Search(*ldap.SearchRequest) (*ldap.SearchResult, error)
  23. StartTLS(*tls.Config) error
  24. Close()
  25. }
  26. // IServer is interface for LDAP authorization
  27. type IServer interface {
  28. Login(*models.LoginUserQuery) (*models.ExternalUserInfo, error)
  29. Users([]string) ([]*models.ExternalUserInfo, error)
  30. Auth(string, string) error
  31. Dial() error
  32. Close()
  33. }
  34. // Server is basic struct of LDAP authorization
  35. type Server struct {
  36. Config *ServerConfig
  37. Connection IConnection
  38. log log.Logger
  39. }
  40. // UsersMaxRequest is a max amount of users we can request via Users().
  41. // Since many LDAP servers has limitations
  42. // on how much items can we return in one request
  43. const UsersMaxRequest = 500
  44. var (
  45. // ErrInvalidCredentials is returned if username and password do not match
  46. ErrInvalidCredentials = errors.New("Invalid Username or Password")
  47. )
  48. // New creates the new LDAP auth
  49. func New(config *ServerConfig) IServer {
  50. return &Server{
  51. Config: config,
  52. log: log.New("ldap"),
  53. }
  54. }
  55. // Dial dials in the LDAP
  56. func (server *Server) Dial() error {
  57. var err error
  58. var certPool *x509.CertPool
  59. if server.Config.RootCACert != "" {
  60. certPool = x509.NewCertPool()
  61. for _, caCertFile := range strings.Split(server.Config.RootCACert, " ") {
  62. pem, err := ioutil.ReadFile(caCertFile)
  63. if err != nil {
  64. return err
  65. }
  66. if !certPool.AppendCertsFromPEM(pem) {
  67. return errors.New("Failed to append CA certificate " + caCertFile)
  68. }
  69. }
  70. }
  71. var clientCert tls.Certificate
  72. if server.Config.ClientCert != "" && server.Config.ClientKey != "" {
  73. clientCert, err = tls.LoadX509KeyPair(server.Config.ClientCert, server.Config.ClientKey)
  74. if err != nil {
  75. return err
  76. }
  77. }
  78. for _, host := range strings.Split(server.Config.Host, " ") {
  79. address := fmt.Sprintf("%s:%d", host, server.Config.Port)
  80. if server.Config.UseSSL {
  81. tlsCfg := &tls.Config{
  82. InsecureSkipVerify: server.Config.SkipVerifySSL,
  83. ServerName: host,
  84. RootCAs: certPool,
  85. }
  86. if len(clientCert.Certificate) > 0 {
  87. tlsCfg.Certificates = append(tlsCfg.Certificates, clientCert)
  88. }
  89. if server.Config.StartTLS {
  90. server.Connection, err = ldap.Dial("tcp", address)
  91. if err == nil {
  92. if err = server.Connection.StartTLS(tlsCfg); err == nil {
  93. return nil
  94. }
  95. }
  96. } else {
  97. server.Connection, err = ldap.DialTLS("tcp", address, tlsCfg)
  98. }
  99. } else {
  100. server.Connection, err = ldap.Dial("tcp", address)
  101. }
  102. if err == nil {
  103. return nil
  104. }
  105. }
  106. return err
  107. }
  108. // Close closes the LDAP connection
  109. func (server *Server) Close() {
  110. server.Connection.Close()
  111. }
  112. // Login user by searching and serializing it
  113. func (server *Server) Login(query *models.LoginUserQuery) (
  114. *models.ExternalUserInfo, error,
  115. ) {
  116. // Authentication
  117. err := server.Auth(query.Username, query.Password)
  118. if err != nil {
  119. return nil, err
  120. }
  121. // Find user entry & attributes
  122. users, err := server.Users([]string{query.Username})
  123. if err != nil {
  124. return nil, err
  125. }
  126. // If we couldn't find the user -
  127. // we should show incorrect credentials err
  128. if len(users) == 0 {
  129. server.disableExternalUser(query.Username)
  130. return nil, ErrInvalidCredentials
  131. }
  132. user := users[0]
  133. if err := server.validateGrafanaUser(user); err != nil {
  134. return nil, err
  135. }
  136. return user, nil
  137. }
  138. // getUsersIteration is a helper function for Users() method.
  139. // It divides the users by equal parts for the anticipated requests
  140. func getUsersIteration(logins []string, fn func(int, int) error) error {
  141. lenLogins := len(logins)
  142. iterations := int(
  143. math.Ceil(
  144. float64(lenLogins) / float64(UsersMaxRequest),
  145. ),
  146. )
  147. for i := 1; i < iterations+1; i++ {
  148. previous := float64(UsersMaxRequest * (i - 1))
  149. current := math.Min(float64(i*UsersMaxRequest), float64(lenLogins))
  150. err := fn(int(previous), int(current))
  151. if err != nil {
  152. return err
  153. }
  154. }
  155. return nil
  156. }
  157. // Users gets LDAP users
  158. func (server *Server) Users(logins []string) (
  159. []*models.ExternalUserInfo,
  160. error,
  161. ) {
  162. var users []*ldap.Entry
  163. err := getUsersIteration(logins, func(previous, current int) error {
  164. entries, err := server.users(logins[previous:current])
  165. if err != nil {
  166. return err
  167. }
  168. users = append(users, entries...)
  169. return nil
  170. })
  171. if err != nil {
  172. return nil, err
  173. }
  174. if len(users) == 0 {
  175. return []*models.ExternalUserInfo{}, nil
  176. }
  177. serializedUsers, err := server.serializeUsers(users)
  178. if err != nil {
  179. return nil, err
  180. }
  181. server.log.Debug("LDAP users found", "users", spew.Sdump(serializedUsers))
  182. return serializedUsers, nil
  183. }
  184. // users is helper method for the Users()
  185. func (server *Server) users(logins []string) (
  186. []*ldap.Entry,
  187. error,
  188. ) {
  189. var result *ldap.SearchResult
  190. var Config = server.Config
  191. var err error
  192. for _, base := range Config.SearchBaseDNs {
  193. result, err = server.Connection.Search(
  194. server.getSearchRequest(base, logins),
  195. )
  196. if err != nil {
  197. return nil, err
  198. }
  199. if len(result.Entries) > 0 {
  200. break
  201. }
  202. }
  203. return result.Entries, nil
  204. }
  205. // validateGrafanaUser validates user access.
  206. // If there are no ldap group mappings access is true
  207. // otherwise a single group must match
  208. func (server *Server) validateGrafanaUser(user *models.ExternalUserInfo) error {
  209. if len(server.Config.Groups) > 0 && len(user.OrgRoles) < 1 {
  210. server.log.Error(
  211. "user does not belong in any of the specified LDAP groups",
  212. "username", user.Login,
  213. "groups", user.Groups,
  214. )
  215. return ErrInvalidCredentials
  216. }
  217. return nil
  218. }
  219. // disableExternalUser marks external user as disabled in Grafana db
  220. func (server *Server) disableExternalUser(username string) error {
  221. // Check if external user exist in Grafana
  222. userQuery := &models.GetExternalUserInfoByLoginQuery{
  223. LoginOrEmail: username,
  224. }
  225. if err := bus.Dispatch(userQuery); err != nil {
  226. return err
  227. }
  228. userInfo := userQuery.Result
  229. if !userInfo.IsDisabled {
  230. server.log.Debug("Disabling external user", "user", userQuery.Result.Login)
  231. // Mark user as disabled in grafana db
  232. disableUserCmd := &models.DisableUserCommand{
  233. UserId: userQuery.Result.UserId,
  234. IsDisabled: true,
  235. }
  236. if err := bus.Dispatch(disableUserCmd); err != nil {
  237. server.log.Debug("Error disabling external user", "user", userQuery.Result.Login, "message", err.Error())
  238. return err
  239. }
  240. }
  241. return nil
  242. }
  243. // getSearchRequest returns LDAP search request for users
  244. func (server *Server) getSearchRequest(
  245. base string,
  246. logins []string,
  247. ) *ldap.SearchRequest {
  248. attributes := []string{}
  249. inputs := server.Config.Attr
  250. attributes = appendIfNotEmpty(
  251. attributes,
  252. inputs.Username,
  253. inputs.Surname,
  254. inputs.Email,
  255. inputs.Name,
  256. inputs.MemberOf,
  257. )
  258. search := ""
  259. for _, login := range logins {
  260. query := strings.Replace(
  261. server.Config.SearchFilter,
  262. "%s", ldap.EscapeFilter(login),
  263. -1,
  264. )
  265. search = search + query
  266. }
  267. filter := fmt.Sprintf("(|%s)", search)
  268. return &ldap.SearchRequest{
  269. BaseDN: base,
  270. Scope: ldap.ScopeWholeSubtree,
  271. DerefAliases: ldap.NeverDerefAliases,
  272. Attributes: attributes,
  273. Filter: filter,
  274. }
  275. }
  276. // buildGrafanaUser extracts info from UserInfo model to ExternalUserInfo
  277. func (server *Server) buildGrafanaUser(user *ldap.Entry) (*models.ExternalUserInfo, error) {
  278. memberOf, err := server.getMemberOf(user)
  279. if err != nil {
  280. return nil, err
  281. }
  282. attrs := server.Config.Attr
  283. extUser := &models.ExternalUserInfo{
  284. AuthModule: models.AuthModuleLDAP,
  285. AuthId: user.DN,
  286. Name: strings.TrimSpace(
  287. fmt.Sprintf(
  288. "%s %s",
  289. getAttribute(attrs.Name, user),
  290. getAttribute(attrs.Surname, user),
  291. ),
  292. ),
  293. Login: getAttribute(attrs.Username, user),
  294. Email: getAttribute(attrs.Email, user),
  295. Groups: memberOf,
  296. OrgRoles: map[int64]models.RoleType{},
  297. }
  298. for _, group := range server.Config.Groups {
  299. // only use the first match for each org
  300. if extUser.OrgRoles[group.OrgID] != "" {
  301. continue
  302. }
  303. if isMemberOf(memberOf, group.GroupDN) {
  304. extUser.OrgRoles[group.OrgID] = group.OrgRole
  305. if extUser.IsGrafanaAdmin == nil || !*extUser.IsGrafanaAdmin {
  306. extUser.IsGrafanaAdmin = group.IsGrafanaAdmin
  307. }
  308. }
  309. }
  310. return extUser, nil
  311. }
  312. // shouldBindAdmin checks if we should use
  313. // admin username & password for LDAP bind
  314. func (server *Server) shouldBindAdmin() bool {
  315. return server.Config.BindPassword != ""
  316. }
  317. // Auth authentificates user in LDAP.
  318. // It might not use passed password and username,
  319. // since they can be overwritten with admin config values -
  320. // see "bind_dn" and "bind_password" options in LDAP config
  321. func (server *Server) Auth(username, password string) error {
  322. path := server.Config.BindDN
  323. if server.shouldBindAdmin() {
  324. password = server.Config.BindPassword
  325. } else {
  326. path = fmt.Sprintf(path, username)
  327. }
  328. bindFn := func() error {
  329. return server.Connection.Bind(path, password)
  330. }
  331. if err := bindFn(); err != nil {
  332. server.log.Error("Cannot authentificate in LDAP", "err", err)
  333. if ldapErr, ok := err.(*ldap.Error); ok {
  334. if ldapErr.ResultCode == 49 {
  335. return ErrInvalidCredentials
  336. }
  337. }
  338. return err
  339. }
  340. return nil
  341. }
  342. // requestMemberOf use this function when POSIX LDAP schema does not support memberOf, so it manually search the groups
  343. func (server *Server) requestMemberOf(entry *ldap.Entry) ([]string, error) {
  344. var memberOf []string
  345. var config = server.Config
  346. for _, groupSearchBase := range config.GroupSearchBaseDNs {
  347. var filterReplace string
  348. if config.GroupSearchFilterUserAttribute == "" {
  349. filterReplace = getAttribute(config.Attr.Username, entry)
  350. } else {
  351. filterReplace = getAttribute(
  352. config.GroupSearchFilterUserAttribute,
  353. entry,
  354. )
  355. }
  356. filter := strings.Replace(
  357. config.GroupSearchFilter, "%s",
  358. ldap.EscapeFilter(filterReplace),
  359. -1,
  360. )
  361. server.log.Info("Searching for user's groups", "filter", filter)
  362. // support old way of reading settings
  363. groupIDAttribute := config.Attr.MemberOf
  364. // but prefer dn attribute if default settings are used
  365. if groupIDAttribute == "" || groupIDAttribute == "memberOf" {
  366. groupIDAttribute = "dn"
  367. }
  368. groupSearchReq := ldap.SearchRequest{
  369. BaseDN: groupSearchBase,
  370. Scope: ldap.ScopeWholeSubtree,
  371. DerefAliases: ldap.NeverDerefAliases,
  372. Attributes: []string{groupIDAttribute},
  373. Filter: filter,
  374. }
  375. groupSearchResult, err := server.Connection.Search(&groupSearchReq)
  376. if err != nil {
  377. return nil, err
  378. }
  379. if len(groupSearchResult.Entries) > 0 {
  380. for _, group := range groupSearchResult.Entries {
  381. memberOf = append(
  382. memberOf,
  383. getAttribute(groupIDAttribute, group),
  384. )
  385. }
  386. break
  387. }
  388. }
  389. return memberOf, nil
  390. }
  391. // serializeUsers serializes the users
  392. // from LDAP result to ExternalInfo struct
  393. func (server *Server) serializeUsers(
  394. entries []*ldap.Entry,
  395. ) ([]*models.ExternalUserInfo, error) {
  396. var serialized []*models.ExternalUserInfo
  397. for _, user := range entries {
  398. extUser, err := server.buildGrafanaUser(user)
  399. if err != nil {
  400. return nil, err
  401. }
  402. serialized = append(serialized, extUser)
  403. }
  404. return serialized, nil
  405. }
  406. // getMemberOf finds memberOf property or request it
  407. func (server *Server) getMemberOf(result *ldap.Entry) (
  408. []string, error,
  409. ) {
  410. if server.Config.GroupSearchFilter == "" {
  411. memberOf := getArrayAttribute(server.Config.Attr.MemberOf, result)
  412. return memberOf, nil
  413. }
  414. memberOf, err := server.requestMemberOf(result)
  415. if err != nil {
  416. return nil, err
  417. }
  418. return memberOf, nil
  419. }