ldap.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  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. "github.com/grafana/grafana/pkg/infra/log"
  12. "github.com/grafana/grafana/pkg/models"
  13. "gopkg.in/ldap.v3"
  14. )
  15. // IConnection is interface for LDAP connection manipulation
  16. type IConnection interface {
  17. Bind(username, password string) error
  18. UnauthenticatedBind(username string) error
  19. Add(*ldap.AddRequest) error
  20. Del(*ldap.DelRequest) error
  21. Search(*ldap.SearchRequest) (*ldap.SearchResult, error)
  22. StartTLS(*tls.Config) error
  23. Close()
  24. }
  25. // IServer is interface for LDAP authorization
  26. type IServer interface {
  27. Login(*models.LoginUserQuery) (*models.ExternalUserInfo, error)
  28. Users([]string) ([]*models.ExternalUserInfo, error)
  29. Bind() error
  30. UserBind(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. // Bind authenticates the connection with the LDAP server
  41. // - with the username and password setup in the config
  42. // - or, anonymously
  43. //
  44. // Dial() sets the connection with the server for this Struct. Therefore, we require a
  45. // call to Dial() before being able to execute this function.
  46. func (server *Server) Bind() error {
  47. if server.shouldAdminBind() {
  48. if err := server.AdminBind(); err != nil {
  49. return err
  50. }
  51. } else {
  52. err := server.Connection.UnauthenticatedBind(server.Config.BindDN)
  53. if err != nil {
  54. return err
  55. }
  56. }
  57. return nil
  58. }
  59. // UsersMaxRequest is a max amount of users we can request via Users().
  60. // Since many LDAP servers has limitations
  61. // on how much items can we return in one request
  62. const UsersMaxRequest = 500
  63. var (
  64. // ErrInvalidCredentials is returned if username and password do not match
  65. ErrInvalidCredentials = errors.New("Invalid Username or Password")
  66. // ErrCouldNotFindUser is returned when username hasn't been found (not username+password)
  67. ErrCouldNotFindUser = errors.New("Can't find user in LDAP")
  68. )
  69. // New creates the new LDAP connection
  70. func New(config *ServerConfig) IServer {
  71. return &Server{
  72. Config: config,
  73. log: log.New("ldap"),
  74. }
  75. }
  76. // Dial dials in the LDAP
  77. // TODO: decrease cyclomatic complexity
  78. func (server *Server) Dial() error {
  79. var err error
  80. var certPool *x509.CertPool
  81. if server.Config.RootCACert != "" {
  82. certPool = x509.NewCertPool()
  83. for _, caCertFile := range strings.Split(server.Config.RootCACert, " ") {
  84. pem, err := ioutil.ReadFile(caCertFile)
  85. if err != nil {
  86. return err
  87. }
  88. if !certPool.AppendCertsFromPEM(pem) {
  89. return errors.New("Failed to append CA certificate " + caCertFile)
  90. }
  91. }
  92. }
  93. var clientCert tls.Certificate
  94. if server.Config.ClientCert != "" && server.Config.ClientKey != "" {
  95. clientCert, err = tls.LoadX509KeyPair(server.Config.ClientCert, server.Config.ClientKey)
  96. if err != nil {
  97. return err
  98. }
  99. }
  100. for _, host := range strings.Split(server.Config.Host, " ") {
  101. address := fmt.Sprintf("%s:%d", host, server.Config.Port)
  102. if server.Config.UseSSL {
  103. tlsCfg := &tls.Config{
  104. InsecureSkipVerify: server.Config.SkipVerifySSL,
  105. ServerName: host,
  106. RootCAs: certPool,
  107. }
  108. if len(clientCert.Certificate) > 0 {
  109. tlsCfg.Certificates = append(tlsCfg.Certificates, clientCert)
  110. }
  111. if server.Config.StartTLS {
  112. server.Connection, err = ldap.Dial("tcp", address)
  113. if err == nil {
  114. if err = server.Connection.StartTLS(tlsCfg); err == nil {
  115. return nil
  116. }
  117. }
  118. } else {
  119. server.Connection, err = ldap.DialTLS("tcp", address, tlsCfg)
  120. }
  121. } else {
  122. server.Connection, err = ldap.Dial("tcp", address)
  123. }
  124. if err == nil {
  125. return nil
  126. }
  127. }
  128. return err
  129. }
  130. // Close closes the LDAP connection
  131. // Dial() sets the connection with the server for this Struct. Therefore, we require a
  132. // call to Dial() before being able to execute this function.
  133. func (server *Server) Close() {
  134. server.Connection.Close()
  135. }
  136. // Login the user.
  137. // There are several cases -
  138. // 1. "admin" user
  139. // Bind the "admin" user (defined in Grafana config file) which has the search privileges
  140. // in LDAP server, then we search the targeted user through that bind, then the second
  141. // perform the bind via passed login/password.
  142. // 2. Single bind
  143. // // If all the users meant to be used with Grafana have the ability to search in LDAP server
  144. // then we bind with LDAP server with targeted login/password
  145. // and then search for the said user in order to retrive all the information about them
  146. // 3. Unauthenticated bind
  147. // For some LDAP configurations it is allowed to search the
  148. // user without login/password binding with LDAP server, in such case
  149. // we will perform "unauthenticated bind", then search for the
  150. // targeted user and then perform the bind with passed login/password.
  151. //
  152. // Dial() sets the connection with the server for this Struct. Therefore, we require a
  153. // call to Dial() before being able to execute this function.
  154. func (server *Server) Login(query *models.LoginUserQuery) (
  155. *models.ExternalUserInfo, error,
  156. ) {
  157. var err error
  158. var authAndBind bool
  159. // Check if we can use a search user
  160. if server.shouldAdminBind() {
  161. if err := server.AdminBind(); err != nil {
  162. return nil, err
  163. }
  164. } else if server.shouldSingleBind() {
  165. authAndBind = true
  166. err = server.UserBind(
  167. server.singleBindDN(query.Username),
  168. query.Password,
  169. )
  170. if err != nil {
  171. return nil, err
  172. }
  173. } else {
  174. err := server.Connection.UnauthenticatedBind(server.Config.BindDN)
  175. if err != nil {
  176. return nil, err
  177. }
  178. }
  179. // Find user entry & attributes
  180. users, err := server.Users([]string{query.Username})
  181. if err != nil {
  182. return nil, err
  183. }
  184. // If we couldn't find the user -
  185. // we should show incorrect credentials err
  186. if len(users) == 0 {
  187. return nil, ErrCouldNotFindUser
  188. }
  189. user := users[0]
  190. if err := server.validateGrafanaUser(user); err != nil {
  191. return nil, err
  192. }
  193. if !authAndBind {
  194. // Authenticate user
  195. err = server.UserBind(user.AuthId, query.Password)
  196. if err != nil {
  197. return nil, err
  198. }
  199. }
  200. return user, nil
  201. }
  202. // shouldAdminBind checks if we should use
  203. // admin username & password for LDAP bind
  204. func (server *Server) shouldAdminBind() bool {
  205. return server.Config.BindPassword != ""
  206. }
  207. // singleBindDN combines the bind with the username
  208. // in order to get the proper path
  209. func (server *Server) singleBindDN(username string) string {
  210. return fmt.Sprintf(server.Config.BindDN, username)
  211. }
  212. // shouldSingleBind checks if we can use "single bind" approach
  213. func (server *Server) shouldSingleBind() bool {
  214. return strings.Contains(server.Config.BindDN, "%s")
  215. }
  216. // Users gets LDAP users by logins
  217. // Dial() sets the connection with the server for this Struct. Therefore, we require a
  218. // call to Dial() before being able to execute this function.
  219. func (server *Server) Users(logins []string) (
  220. []*models.ExternalUserInfo,
  221. error,
  222. ) {
  223. var users []*ldap.Entry
  224. err := getUsersIteration(logins, func(previous, current int) error {
  225. entries, err := server.users(logins[previous:current])
  226. if err != nil {
  227. return err
  228. }
  229. users = append(users, entries...)
  230. return nil
  231. })
  232. if err != nil {
  233. return nil, err
  234. }
  235. if len(users) == 0 {
  236. return []*models.ExternalUserInfo{}, nil
  237. }
  238. serializedUsers, err := server.serializeUsers(users)
  239. if err != nil {
  240. return nil, err
  241. }
  242. server.log.Debug(
  243. "LDAP users found", "users", spew.Sdump(serializedUsers),
  244. )
  245. return serializedUsers, nil
  246. }
  247. // getUsersIteration is a helper function for Users() method.
  248. // It divides the users by equal parts for the anticipated requests
  249. func getUsersIteration(logins []string, fn func(int, int) error) error {
  250. lenLogins := len(logins)
  251. iterations := int(
  252. math.Ceil(
  253. float64(lenLogins) / float64(UsersMaxRequest),
  254. ),
  255. )
  256. for i := 1; i < iterations+1; i++ {
  257. previous := float64(UsersMaxRequest * (i - 1))
  258. current := math.Min(float64(i*UsersMaxRequest), float64(lenLogins))
  259. err := fn(int(previous), int(current))
  260. if err != nil {
  261. return err
  262. }
  263. }
  264. return nil
  265. }
  266. // users is helper method for the Users()
  267. func (server *Server) users(logins []string) (
  268. []*ldap.Entry,
  269. error,
  270. ) {
  271. var result *ldap.SearchResult
  272. var Config = server.Config
  273. var err error
  274. for _, base := range Config.SearchBaseDNs {
  275. result, err = server.Connection.Search(
  276. server.getSearchRequest(base, logins),
  277. )
  278. if err != nil {
  279. return nil, err
  280. }
  281. if len(result.Entries) > 0 {
  282. break
  283. }
  284. }
  285. return result.Entries, nil
  286. }
  287. // validateGrafanaUser validates user access.
  288. // If there are no ldap group mappings access is true
  289. // otherwise a single group must match
  290. func (server *Server) validateGrafanaUser(user *models.ExternalUserInfo) error {
  291. if len(server.Config.Groups) > 0 && len(user.OrgRoles) < 1 {
  292. server.log.Error(
  293. "User does not belong in any of the specified LDAP groups",
  294. "username", user.Login,
  295. "groups", user.Groups,
  296. )
  297. return ErrInvalidCredentials
  298. }
  299. return nil
  300. }
  301. // getSearchRequest returns LDAP search request for users
  302. func (server *Server) getSearchRequest(
  303. base string,
  304. logins []string,
  305. ) *ldap.SearchRequest {
  306. attributes := []string{}
  307. inputs := server.Config.Attr
  308. attributes = appendIfNotEmpty(
  309. attributes,
  310. inputs.Username,
  311. inputs.Surname,
  312. inputs.Email,
  313. inputs.Name,
  314. inputs.MemberOf,
  315. // In case for the POSIX LDAP schema server
  316. server.Config.GroupSearchFilterUserAttribute,
  317. )
  318. search := ""
  319. for _, login := range logins {
  320. query := strings.Replace(
  321. server.Config.SearchFilter,
  322. "%s", ldap.EscapeFilter(login),
  323. -1,
  324. )
  325. search = search + query
  326. }
  327. filter := fmt.Sprintf("(|%s)", search)
  328. return &ldap.SearchRequest{
  329. BaseDN: base,
  330. Scope: ldap.ScopeWholeSubtree,
  331. DerefAliases: ldap.NeverDerefAliases,
  332. Attributes: attributes,
  333. Filter: filter,
  334. }
  335. }
  336. // buildGrafanaUser extracts info from UserInfo model to ExternalUserInfo
  337. func (server *Server) buildGrafanaUser(user *ldap.Entry) (*models.ExternalUserInfo, error) {
  338. memberOf, err := server.getMemberOf(user)
  339. if err != nil {
  340. return nil, err
  341. }
  342. attrs := server.Config.Attr
  343. extUser := &models.ExternalUserInfo{
  344. AuthModule: models.AuthModuleLDAP,
  345. AuthId: user.DN,
  346. Name: strings.TrimSpace(
  347. fmt.Sprintf(
  348. "%s %s",
  349. getAttribute(attrs.Name, user),
  350. getAttribute(attrs.Surname, user),
  351. ),
  352. ),
  353. Login: getAttribute(attrs.Username, user),
  354. Email: getAttribute(attrs.Email, user),
  355. Groups: memberOf,
  356. OrgRoles: map[int64]models.RoleType{},
  357. }
  358. for _, group := range server.Config.Groups {
  359. // only use the first match for each org
  360. if extUser.OrgRoles[group.OrgId] != "" {
  361. continue
  362. }
  363. if isMemberOf(memberOf, group.GroupDN) {
  364. extUser.OrgRoles[group.OrgId] = group.OrgRole
  365. if extUser.IsGrafanaAdmin == nil || !*extUser.IsGrafanaAdmin {
  366. extUser.IsGrafanaAdmin = group.IsGrafanaAdmin
  367. }
  368. }
  369. }
  370. return extUser, nil
  371. }
  372. // UserBind binds the user with the LDAP server
  373. // Dial() sets the connection with the server for this Struct. Therefore, we require a
  374. // call to Dial() before being able to execute this function.
  375. func (server *Server) UserBind(username, password string) error {
  376. err := server.userBind(username, password)
  377. if err != nil {
  378. server.log.Error(
  379. fmt.Sprintf("Cannot bind user %s with LDAP", username),
  380. "error",
  381. err,
  382. )
  383. return err
  384. }
  385. return nil
  386. }
  387. // AdminBind binds "admin" user with LDAP
  388. // Dial() sets the connection with the server for this Struct. Therefore, we require a
  389. // call to Dial() before being able to execute this function.
  390. func (server *Server) AdminBind() error {
  391. err := server.userBind(server.Config.BindDN, server.Config.BindPassword)
  392. if err != nil {
  393. server.log.Error(
  394. "Cannot authentificate admin user in LDAP",
  395. "error",
  396. err,
  397. )
  398. return err
  399. }
  400. return nil
  401. }
  402. // userBind binds the user with the LDAP server
  403. func (server *Server) userBind(path, password string) error {
  404. err := server.Connection.Bind(path, password)
  405. if err != nil {
  406. if ldapErr, ok := err.(*ldap.Error); ok {
  407. if ldapErr.ResultCode == 49 {
  408. return ErrInvalidCredentials
  409. }
  410. }
  411. return err
  412. }
  413. return nil
  414. }
  415. // requestMemberOf use this function when POSIX LDAP
  416. // schema does not support memberOf, so it manually search the groups
  417. func (server *Server) requestMemberOf(entry *ldap.Entry) ([]string, error) {
  418. var memberOf []string
  419. var config = server.Config
  420. for _, groupSearchBase := range config.GroupSearchBaseDNs {
  421. var filterReplace string
  422. if config.GroupSearchFilterUserAttribute == "" {
  423. filterReplace = getAttribute(config.Attr.Username, entry)
  424. } else {
  425. filterReplace = getAttribute(
  426. config.GroupSearchFilterUserAttribute,
  427. entry,
  428. )
  429. }
  430. filter := strings.Replace(
  431. config.GroupSearchFilter, "%s",
  432. ldap.EscapeFilter(filterReplace),
  433. -1,
  434. )
  435. server.log.Info("Searching for user's groups", "filter", filter)
  436. // support old way of reading settings
  437. groupIDAttribute := config.Attr.MemberOf
  438. // but prefer dn attribute if default settings are used
  439. if groupIDAttribute == "" || groupIDAttribute == "memberOf" {
  440. groupIDAttribute = "dn"
  441. }
  442. groupSearchReq := ldap.SearchRequest{
  443. BaseDN: groupSearchBase,
  444. Scope: ldap.ScopeWholeSubtree,
  445. DerefAliases: ldap.NeverDerefAliases,
  446. Attributes: []string{groupIDAttribute},
  447. Filter: filter,
  448. }
  449. groupSearchResult, err := server.Connection.Search(&groupSearchReq)
  450. if err != nil {
  451. return nil, err
  452. }
  453. if len(groupSearchResult.Entries) > 0 {
  454. for _, group := range groupSearchResult.Entries {
  455. memberOf = append(
  456. memberOf,
  457. getAttribute(groupIDAttribute, group),
  458. )
  459. }
  460. break
  461. }
  462. }
  463. return memberOf, nil
  464. }
  465. // serializeUsers serializes the users
  466. // from LDAP result to ExternalInfo struct
  467. func (server *Server) serializeUsers(
  468. entries []*ldap.Entry,
  469. ) ([]*models.ExternalUserInfo, error) {
  470. var serialized []*models.ExternalUserInfo
  471. for _, user := range entries {
  472. extUser, err := server.buildGrafanaUser(user)
  473. if err != nil {
  474. return nil, err
  475. }
  476. serialized = append(serialized, extUser)
  477. }
  478. return serialized, nil
  479. }
  480. // getMemberOf finds memberOf property or request it
  481. func (server *Server) getMemberOf(result *ldap.Entry) (
  482. []string, error,
  483. ) {
  484. if server.Config.GroupSearchFilter == "" {
  485. memberOf := getArrayAttribute(server.Config.Attr.MemberOf, result)
  486. return memberOf, nil
  487. }
  488. memberOf, err := server.requestMemberOf(result)
  489. if err != nil {
  490. return nil, err
  491. }
  492. return memberOf, nil
  493. }