ldap.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  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. // Do we need to authenticate the "admin" user first?
  129. // Admin user should have access for the user search in LDAP server
  130. if server.shouldAuthAdmin() {
  131. if err := server.AuthAdmin(); err != nil {
  132. return nil, err
  133. }
  134. // Or if anyone can perform the search in LDAP?
  135. } else {
  136. err := server.Connection.UnauthenticatedBind(server.Config.BindDN)
  137. if err != nil {
  138. return nil, err
  139. }
  140. }
  141. // Find user entry & attributes
  142. users, err := server.Users([]string{query.Username})
  143. if err != nil {
  144. return nil, err
  145. }
  146. // If we couldn't find the user -
  147. // we should show incorrect credentials err
  148. if len(users) == 0 {
  149. return nil, ErrCouldNotFindUser
  150. }
  151. user := users[0]
  152. if err := server.validateGrafanaUser(user); err != nil {
  153. return nil, err
  154. }
  155. // Authenticate user
  156. err = server.Auth(user.AuthId, query.Password)
  157. if err != nil {
  158. return nil, err
  159. }
  160. return user, nil
  161. }
  162. // getUsersIteration is a helper function for Users() method.
  163. // It divides the users by equal parts for the anticipated requests
  164. func getUsersIteration(logins []string, fn func(int, int) error) error {
  165. lenLogins := len(logins)
  166. iterations := int(
  167. math.Ceil(
  168. float64(lenLogins) / float64(UsersMaxRequest),
  169. ),
  170. )
  171. for i := 1; i < iterations+1; i++ {
  172. previous := float64(UsersMaxRequest * (i - 1))
  173. current := math.Min(float64(i*UsersMaxRequest), float64(lenLogins))
  174. err := fn(int(previous), int(current))
  175. if err != nil {
  176. return err
  177. }
  178. }
  179. return nil
  180. }
  181. // Users gets LDAP users
  182. func (server *Server) Users(logins []string) (
  183. []*models.ExternalUserInfo,
  184. error,
  185. ) {
  186. var users []*ldap.Entry
  187. err := getUsersIteration(logins, func(previous, current int) error {
  188. entries, err := server.users(logins[previous:current])
  189. if err != nil {
  190. return err
  191. }
  192. users = append(users, entries...)
  193. return nil
  194. })
  195. if err != nil {
  196. return nil, err
  197. }
  198. if len(users) == 0 {
  199. return []*models.ExternalUserInfo{}, nil
  200. }
  201. serializedUsers, err := server.serializeUsers(users)
  202. if err != nil {
  203. return nil, err
  204. }
  205. server.log.Debug("LDAP users found", "users", spew.Sdump(serializedUsers))
  206. return serializedUsers, nil
  207. }
  208. // users is helper method for the Users()
  209. func (server *Server) users(logins []string) (
  210. []*ldap.Entry,
  211. error,
  212. ) {
  213. var result *ldap.SearchResult
  214. var Config = server.Config
  215. var err error
  216. for _, base := range Config.SearchBaseDNs {
  217. result, err = server.Connection.Search(
  218. server.getSearchRequest(base, logins),
  219. )
  220. if err != nil {
  221. return nil, err
  222. }
  223. if len(result.Entries) > 0 {
  224. break
  225. }
  226. }
  227. return result.Entries, nil
  228. }
  229. // validateGrafanaUser validates user access.
  230. // If there are no ldap group mappings access is true
  231. // otherwise a single group must match
  232. func (server *Server) validateGrafanaUser(user *models.ExternalUserInfo) error {
  233. if len(server.Config.Groups) > 0 && len(user.OrgRoles) < 1 {
  234. server.log.Error(
  235. "user does not belong in any of the specified LDAP groups",
  236. "username", user.Login,
  237. "groups", user.Groups,
  238. )
  239. return ErrInvalidCredentials
  240. }
  241. return nil
  242. }
  243. // getSearchRequest returns LDAP search request for users
  244. func (server *Server) getSearchRequest(
  245. base string,
  246. logins []string,
  247. ) *ldap.SearchRequest {
  248. attributes := []string{}
  249. inputs := server.Config.Attr
  250. attributes = appendIfNotEmpty(
  251. attributes,
  252. inputs.Username,
  253. inputs.Surname,
  254. inputs.Email,
  255. inputs.Name,
  256. inputs.MemberOf,
  257. )
  258. search := ""
  259. for _, login := range logins {
  260. query := strings.Replace(
  261. server.Config.SearchFilter,
  262. "%s", ldap.EscapeFilter(login),
  263. -1,
  264. )
  265. search = search + query
  266. }
  267. filter := fmt.Sprintf("(|%s)", search)
  268. return &ldap.SearchRequest{
  269. BaseDN: base,
  270. Scope: ldap.ScopeWholeSubtree,
  271. DerefAliases: ldap.NeverDerefAliases,
  272. Attributes: attributes,
  273. Filter: filter,
  274. }
  275. }
  276. // buildGrafanaUser extracts info from UserInfo model to ExternalUserInfo
  277. func (server *Server) buildGrafanaUser(user *ldap.Entry) (*models.ExternalUserInfo, error) {
  278. memberOf, err := server.getMemberOf(user)
  279. if err != nil {
  280. return nil, err
  281. }
  282. attrs := server.Config.Attr
  283. extUser := &models.ExternalUserInfo{
  284. AuthModule: models.AuthModuleLDAP,
  285. AuthId: user.DN,
  286. Name: strings.TrimSpace(
  287. fmt.Sprintf(
  288. "%s %s",
  289. getAttribute(attrs.Name, user),
  290. getAttribute(attrs.Surname, user),
  291. ),
  292. ),
  293. Login: getAttribute(attrs.Username, user),
  294. Email: getAttribute(attrs.Email, user),
  295. Groups: memberOf,
  296. OrgRoles: map[int64]models.RoleType{},
  297. }
  298. for _, group := range server.Config.Groups {
  299. // only use the first match for each org
  300. if extUser.OrgRoles[group.OrgID] != "" {
  301. continue
  302. }
  303. if isMemberOf(memberOf, group.GroupDN) {
  304. extUser.OrgRoles[group.OrgID] = group.OrgRole
  305. if extUser.IsGrafanaAdmin == nil || !*extUser.IsGrafanaAdmin {
  306. extUser.IsGrafanaAdmin = group.IsGrafanaAdmin
  307. }
  308. }
  309. }
  310. return extUser, nil
  311. }
  312. // shouldAuthAdmin checks if we should use
  313. // admin username & password for LDAP bind
  314. func (server *Server) shouldAuthAdmin() bool {
  315. return server.Config.BindPassword != ""
  316. }
  317. // Auth authentificates user in LDAP
  318. func (server *Server) Auth(username, password string) error {
  319. err := server.auth(username, password)
  320. if err != nil {
  321. server.log.Error(
  322. fmt.Sprintf("Cannot authentificate user %s in LDAP", username),
  323. "error",
  324. err,
  325. )
  326. return err
  327. }
  328. return nil
  329. }
  330. // AuthAdmin authentificates LDAP admin user
  331. func (server *Server) AuthAdmin() error {
  332. err := server.auth(server.Config.BindDN, server.Config.BindPassword)
  333. if err != nil {
  334. server.log.Error(
  335. "Cannot authentificate admin user in LDAP",
  336. "error",
  337. err,
  338. )
  339. return err
  340. }
  341. return nil
  342. }
  343. // auth is helper for several types of LDAP authentification
  344. func (server *Server) auth(path, password string) error {
  345. err := server.Connection.Bind(path, password)
  346. if err != nil {
  347. if ldapErr, ok := err.(*ldap.Error); ok {
  348. if ldapErr.ResultCode == 49 {
  349. return ErrInvalidCredentials
  350. }
  351. }
  352. return err
  353. }
  354. return nil
  355. }
  356. // requestMemberOf use this function when POSIX LDAP schema does not support memberOf, so it manually search the groups
  357. func (server *Server) requestMemberOf(entry *ldap.Entry) ([]string, error) {
  358. var memberOf []string
  359. var config = server.Config
  360. for _, groupSearchBase := range config.GroupSearchBaseDNs {
  361. var filterReplace string
  362. if config.GroupSearchFilterUserAttribute == "" {
  363. filterReplace = getAttribute(config.Attr.Username, entry)
  364. } else {
  365. filterReplace = getAttribute(
  366. config.GroupSearchFilterUserAttribute,
  367. entry,
  368. )
  369. }
  370. filter := strings.Replace(
  371. config.GroupSearchFilter, "%s",
  372. ldap.EscapeFilter(filterReplace),
  373. -1,
  374. )
  375. server.log.Info("Searching for user's groups", "filter", filter)
  376. // support old way of reading settings
  377. groupIDAttribute := config.Attr.MemberOf
  378. // but prefer dn attribute if default settings are used
  379. if groupIDAttribute == "" || groupIDAttribute == "memberOf" {
  380. groupIDAttribute = "dn"
  381. }
  382. groupSearchReq := ldap.SearchRequest{
  383. BaseDN: groupSearchBase,
  384. Scope: ldap.ScopeWholeSubtree,
  385. DerefAliases: ldap.NeverDerefAliases,
  386. Attributes: []string{groupIDAttribute},
  387. Filter: filter,
  388. }
  389. groupSearchResult, err := server.Connection.Search(&groupSearchReq)
  390. if err != nil {
  391. return nil, err
  392. }
  393. if len(groupSearchResult.Entries) > 0 {
  394. for _, group := range groupSearchResult.Entries {
  395. memberOf = append(
  396. memberOf,
  397. getAttribute(groupIDAttribute, group),
  398. )
  399. }
  400. break
  401. }
  402. }
  403. return memberOf, nil
  404. }
  405. // serializeUsers serializes the users
  406. // from LDAP result to ExternalInfo struct
  407. func (server *Server) serializeUsers(
  408. entries []*ldap.Entry,
  409. ) ([]*models.ExternalUserInfo, error) {
  410. var serialized []*models.ExternalUserInfo
  411. for _, user := range entries {
  412. extUser, err := server.buildGrafanaUser(user)
  413. if err != nil {
  414. return nil, err
  415. }
  416. serialized = append(serialized, extUser)
  417. }
  418. return serialized, nil
  419. }
  420. // getMemberOf finds memberOf property or request it
  421. func (server *Server) getMemberOf(result *ldap.Entry) (
  422. []string, error,
  423. ) {
  424. if server.Config.GroupSearchFilter == "" {
  425. memberOf := getArrayAttribute(server.Config.Attr.MemberOf, result)
  426. return memberOf, nil
  427. }
  428. memberOf, err := server.requestMemberOf(result)
  429. if err != nil {
  430. return nil, err
  431. }
  432. return memberOf, nil
  433. }