ldap.go 13 KB

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