ldap.go 13 KB

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