ldap.go 12 KB

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