ldap.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  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("LDAP users found", "users", spew.Sdump(serializedUsers))
  236. return serializedUsers, nil
  237. }
  238. // users is helper method for the Users()
  239. func (server *Server) users(logins []string) (
  240. []*ldap.Entry,
  241. error,
  242. ) {
  243. var result *ldap.SearchResult
  244. var Config = server.Config
  245. var err error
  246. for _, base := range Config.SearchBaseDNs {
  247. result, err = server.Connection.Search(
  248. server.getSearchRequest(base, logins),
  249. )
  250. if err != nil {
  251. return nil, err
  252. }
  253. if len(result.Entries) > 0 {
  254. break
  255. }
  256. }
  257. return result.Entries, nil
  258. }
  259. // validateGrafanaUser validates user access.
  260. // If there are no ldap group mappings access is true
  261. // otherwise a single group must match
  262. func (server *Server) validateGrafanaUser(user *models.ExternalUserInfo) error {
  263. if len(server.Config.Groups) > 0 && len(user.OrgRoles) < 1 {
  264. server.log.Error(
  265. "user does not belong in any of the specified LDAP groups",
  266. "username", user.Login,
  267. "groups", user.Groups,
  268. )
  269. return ErrInvalidCredentials
  270. }
  271. return nil
  272. }
  273. // getSearchRequest returns LDAP search request for users
  274. func (server *Server) getSearchRequest(
  275. base string,
  276. logins []string,
  277. ) *ldap.SearchRequest {
  278. attributes := []string{}
  279. inputs := server.Config.Attr
  280. attributes = appendIfNotEmpty(
  281. attributes,
  282. inputs.Username,
  283. inputs.Surname,
  284. inputs.Email,
  285. inputs.Name,
  286. inputs.MemberOf,
  287. )
  288. search := ""
  289. for _, login := range logins {
  290. query := strings.Replace(
  291. server.Config.SearchFilter,
  292. "%s", ldap.EscapeFilter(login),
  293. -1,
  294. )
  295. search = search + query
  296. }
  297. filter := fmt.Sprintf("(|%s)", search)
  298. return &ldap.SearchRequest{
  299. BaseDN: base,
  300. Scope: ldap.ScopeWholeSubtree,
  301. DerefAliases: ldap.NeverDerefAliases,
  302. Attributes: attributes,
  303. Filter: filter,
  304. }
  305. }
  306. // buildGrafanaUser extracts info from UserInfo model to ExternalUserInfo
  307. func (server *Server) buildGrafanaUser(user *ldap.Entry) (*models.ExternalUserInfo, error) {
  308. memberOf, err := server.getMemberOf(user)
  309. if err != nil {
  310. return nil, err
  311. }
  312. attrs := server.Config.Attr
  313. extUser := &models.ExternalUserInfo{
  314. AuthModule: models.AuthModuleLDAP,
  315. AuthId: user.DN,
  316. Name: strings.TrimSpace(
  317. fmt.Sprintf(
  318. "%s %s",
  319. getAttribute(attrs.Name, user),
  320. getAttribute(attrs.Surname, user),
  321. ),
  322. ),
  323. Login: getAttribute(attrs.Username, user),
  324. Email: getAttribute(attrs.Email, user),
  325. Groups: memberOf,
  326. OrgRoles: map[int64]models.RoleType{},
  327. }
  328. for _, group := range server.Config.Groups {
  329. // only use the first match for each org
  330. if extUser.OrgRoles[group.OrgID] != "" {
  331. continue
  332. }
  333. if isMemberOf(memberOf, group.GroupDN) {
  334. extUser.OrgRoles[group.OrgID] = group.OrgRole
  335. if extUser.IsGrafanaAdmin == nil || !*extUser.IsGrafanaAdmin {
  336. extUser.IsGrafanaAdmin = group.IsGrafanaAdmin
  337. }
  338. }
  339. }
  340. return extUser, nil
  341. }
  342. // shouldAuthAdmin checks if we should use
  343. // admin username & password for LDAP bind
  344. func (server *Server) shouldAuthAdmin() bool {
  345. return server.Config.BindPassword != ""
  346. }
  347. // UserBind authenticates the connection with the LDAP server
  348. func (server *Server) UserBind(username, password string) error {
  349. err := server.userBind(username, password)
  350. if err != nil {
  351. server.log.Error(
  352. fmt.Sprintf("Cannot authentificate user %s in LDAP", username),
  353. "error",
  354. err,
  355. )
  356. return err
  357. }
  358. return nil
  359. }
  360. // AuthAdmin authentificates LDAP admin user
  361. func (server *Server) AuthAdmin() error {
  362. err := server.userBind(server.Config.BindDN, server.Config.BindPassword)
  363. if err != nil {
  364. server.log.Error(
  365. "Cannot authentificate admin user in LDAP",
  366. "error",
  367. err,
  368. )
  369. return err
  370. }
  371. return nil
  372. }
  373. // userBind authenticates the connection with the LDAP server
  374. func (server *Server) userBind(path, password string) error {
  375. err := server.Connection.Bind(path, password)
  376. if err != nil {
  377. if ldapErr, ok := err.(*ldap.Error); ok {
  378. if ldapErr.ResultCode == 49 {
  379. return ErrInvalidCredentials
  380. }
  381. }
  382. return err
  383. }
  384. return nil
  385. }
  386. // requestMemberOf use this function when POSIX LDAP schema does not support memberOf, so it manually search the groups
  387. func (server *Server) requestMemberOf(entry *ldap.Entry) ([]string, error) {
  388. var memberOf []string
  389. var config = server.Config
  390. for _, groupSearchBase := range config.GroupSearchBaseDNs {
  391. var filterReplace string
  392. if config.GroupSearchFilterUserAttribute == "" {
  393. filterReplace = getAttribute(config.Attr.Username, entry)
  394. } else {
  395. filterReplace = getAttribute(
  396. config.GroupSearchFilterUserAttribute,
  397. entry,
  398. )
  399. }
  400. filter := strings.Replace(
  401. config.GroupSearchFilter, "%s",
  402. ldap.EscapeFilter(filterReplace),
  403. -1,
  404. )
  405. server.log.Info("Searching for user's groups", "filter", filter)
  406. // support old way of reading settings
  407. groupIDAttribute := config.Attr.MemberOf
  408. // but prefer dn attribute if default settings are used
  409. if groupIDAttribute == "" || groupIDAttribute == "memberOf" {
  410. groupIDAttribute = "dn"
  411. }
  412. groupSearchReq := ldap.SearchRequest{
  413. BaseDN: groupSearchBase,
  414. Scope: ldap.ScopeWholeSubtree,
  415. DerefAliases: ldap.NeverDerefAliases,
  416. Attributes: []string{groupIDAttribute},
  417. Filter: filter,
  418. }
  419. groupSearchResult, err := server.Connection.Search(&groupSearchReq)
  420. if err != nil {
  421. return nil, err
  422. }
  423. if len(groupSearchResult.Entries) > 0 {
  424. for _, group := range groupSearchResult.Entries {
  425. memberOf = append(
  426. memberOf,
  427. getAttribute(groupIDAttribute, group),
  428. )
  429. }
  430. break
  431. }
  432. }
  433. return memberOf, nil
  434. }
  435. // serializeUsers serializes the users
  436. // from LDAP result to ExternalInfo struct
  437. func (server *Server) serializeUsers(
  438. entries []*ldap.Entry,
  439. ) ([]*models.ExternalUserInfo, error) {
  440. var serialized []*models.ExternalUserInfo
  441. for _, user := range entries {
  442. extUser, err := server.buildGrafanaUser(user)
  443. if err != nil {
  444. return nil, err
  445. }
  446. serialized = append(serialized, extUser)
  447. }
  448. return serialized, nil
  449. }
  450. // getMemberOf finds memberOf property or request it
  451. func (server *Server) getMemberOf(result *ldap.Entry) (
  452. []string, error,
  453. ) {
  454. if server.Config.GroupSearchFilter == "" {
  455. memberOf := getArrayAttribute(server.Config.Attr.MemberOf, result)
  456. return memberOf, nil
  457. }
  458. memberOf, err := server.requestMemberOf(result)
  459. if err != nil {
  460. return nil, err
  461. }
  462. return memberOf, nil
  463. }