ldap.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. package ldap
  2. import (
  3. "crypto/tls"
  4. "crypto/x509"
  5. "errors"
  6. "fmt"
  7. "io/ioutil"
  8. "strings"
  9. "gopkg.in/ldap.v3"
  10. "github.com/grafana/grafana/pkg/bus"
  11. "github.com/grafana/grafana/pkg/infra/log"
  12. "github.com/grafana/grafana/pkg/models"
  13. )
  14. // IConnection is interface for LDAP connection manipulation
  15. type IConnection interface {
  16. Bind(username, password string) error
  17. UnauthenticatedBind(username string) error
  18. Add(*ldap.AddRequest) error
  19. Del(*ldap.DelRequest) error
  20. Search(*ldap.SearchRequest) (*ldap.SearchResult, error)
  21. StartTLS(*tls.Config) error
  22. Close()
  23. }
  24. // IServer is interface for LDAP authorization
  25. type IServer interface {
  26. Login(*models.LoginUserQuery) (*models.ExternalUserInfo, error)
  27. Add(string, map[string][]string) error
  28. Remove(string) error
  29. Users([]string) ([]*models.ExternalUserInfo, error)
  30. ExtractGrafanaUser(*UserInfo) (*models.ExternalUserInfo, 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. requireSecondBind bool
  39. log log.Logger
  40. }
  41. var (
  42. // ErrInvalidCredentials is returned if username and password do not match
  43. ErrInvalidCredentials = errors.New("Invalid Username or Password")
  44. ErrLDAPUserNotFound = errors.New("LDAP user not found")
  45. )
  46. var dial = func(network, addr string) (IConnection, error) {
  47. return ldap.Dial(network, addr)
  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 = 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 = 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. // Log in user by searching and serializing it
  114. func (server *Server) Login(query *models.LoginUserQuery) (
  115. *models.ExternalUserInfo, error,
  116. ) {
  117. // Perform initial authentication
  118. err := server.initialBind(query.Username, query.Password)
  119. if err != nil {
  120. return nil, err
  121. }
  122. // Find user entry & attributes
  123. users, err := server.Users([]string{query.Username})
  124. if err != nil {
  125. return nil, err
  126. }
  127. // If we couldn't find the user -
  128. // we should show incorrect credentials err
  129. if len(users) == 0 {
  130. server.disableExternalUser(query.Username)
  131. return nil, ErrInvalidCredentials
  132. }
  133. // Check if a second user bind is needed
  134. user := users[0]
  135. if server.requireSecondBind {
  136. err = server.secondBind(user, query.Password)
  137. if err != nil {
  138. return nil, err
  139. }
  140. }
  141. return user, nil
  142. }
  143. // Add adds stuff to LDAP
  144. func (server *Server) Add(dn string, values map[string][]string) error {
  145. err := server.initialBind(
  146. server.config.BindDN,
  147. server.config.BindPassword,
  148. )
  149. if err != nil {
  150. return err
  151. }
  152. attributes := make([]ldap.Attribute, 0)
  153. for key, value := range values {
  154. attributes = append(attributes, ldap.Attribute{
  155. Type: key,
  156. Vals: value,
  157. })
  158. }
  159. request := &ldap.AddRequest{
  160. DN: dn,
  161. Attributes: attributes,
  162. }
  163. err = server.connection.Add(request)
  164. if err != nil {
  165. return err
  166. }
  167. return nil
  168. }
  169. // Remove removes stuff from LDAP
  170. func (server *Server) Remove(dn string) error {
  171. err := server.initialBind(
  172. server.config.BindDN,
  173. server.config.BindPassword,
  174. )
  175. if err != nil {
  176. return err
  177. }
  178. request := ldap.NewDelRequest(dn, nil)
  179. err = server.connection.Del(request)
  180. if err != nil {
  181. return err
  182. }
  183. return nil
  184. }
  185. // Users gets LDAP users
  186. func (server *Server) Users(logins []string) (
  187. []*models.ExternalUserInfo,
  188. error,
  189. ) {
  190. var result *ldap.SearchResult
  191. var err error
  192. var config = server.config
  193. for _, base := range config.SearchBaseDNs {
  194. result, err = server.connection.Search(
  195. server.getSearchRequest(base, logins),
  196. )
  197. if err != nil {
  198. return nil, err
  199. }
  200. if len(result.Entries) > 0 {
  201. break
  202. }
  203. }
  204. serializedUsers, err := server.serializeUsers(result)
  205. if err != nil {
  206. return nil, err
  207. }
  208. return serializedUsers, nil
  209. }
  210. // ExtractGrafanaUser extracts external user info from LDAP user
  211. func (server *Server) ExtractGrafanaUser(user *UserInfo) (*models.ExternalUserInfo, error) {
  212. result := server.buildGrafanaUser(user)
  213. if err := server.validateGrafanaUser(result); err != nil {
  214. return nil, err
  215. }
  216. return result, nil
  217. }
  218. // validateGrafanaUser validates user access.
  219. // If there are no ldap group mappings access is true
  220. // otherwise a single group must match
  221. func (server *Server) validateGrafanaUser(user *models.ExternalUserInfo) error {
  222. if len(server.config.Groups) > 0 && len(user.OrgRoles) < 1 {
  223. server.log.Error(
  224. "user does not belong in any of the specified LDAP groups",
  225. "username", user.Login,
  226. "groups", user.Groups,
  227. )
  228. return ErrInvalidCredentials
  229. }
  230. return nil
  231. }
  232. // disableExternalUser marks external user as disabled in Grafana db
  233. func (server *Server) disableExternalUser(username string) error {
  234. // Check if external user exist in Grafana
  235. userQuery := &models.GetExternalUserInfoByLoginQuery{
  236. LoginOrEmail: username,
  237. }
  238. if err := bus.Dispatch(userQuery); err != nil {
  239. return err
  240. }
  241. userInfo := userQuery.Result
  242. if !userInfo.IsDisabled {
  243. server.log.Debug("Disabling external user", "user", userQuery.Result.Login)
  244. // Mark user as disabled in grafana db
  245. disableUserCmd := &models.DisableUserCommand{
  246. UserId: userQuery.Result.UserId,
  247. IsDisabled: true,
  248. }
  249. if err := bus.Dispatch(disableUserCmd); err != nil {
  250. server.log.Debug("Error disabling external user", "user", userQuery.Result.Login, "message", err.Error())
  251. return err
  252. }
  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 *UserInfo) *models.ExternalUserInfo {
  291. extUser := &models.ExternalUserInfo{
  292. AuthModule: models.AuthModuleLDAP,
  293. AuthId: user.DN,
  294. Name: strings.TrimSpace(
  295. fmt.Sprintf("%s %s", user.FirstName, user.LastName),
  296. ),
  297. Login: user.Username,
  298. Email: user.Email,
  299. Groups: user.MemberOf,
  300. OrgRoles: map[int64]models.RoleType{},
  301. }
  302. for _, group := range server.config.Groups {
  303. // only use the first match for each org
  304. if extUser.OrgRoles[group.OrgId] != "" {
  305. continue
  306. }
  307. if user.isMemberOf(group.GroupDN) {
  308. extUser.OrgRoles[group.OrgId] = group.OrgRole
  309. if extUser.IsGrafanaAdmin == nil || !*extUser.IsGrafanaAdmin {
  310. extUser.IsGrafanaAdmin = group.IsGrafanaAdmin
  311. }
  312. }
  313. }
  314. return extUser
  315. }
  316. func (server *Server) serverBind() error {
  317. bindFn := func() error {
  318. return server.connection.Bind(
  319. server.config.BindDN,
  320. server.config.BindPassword,
  321. )
  322. }
  323. if server.config.BindPassword == "" {
  324. bindFn = func() error {
  325. return server.connection.UnauthenticatedBind(server.config.BindDN)
  326. }
  327. }
  328. // bind_dn and bind_password to bind
  329. if err := bindFn(); err != nil {
  330. server.log.Info("LDAP initial bind failed, %v", err)
  331. if ldapErr, ok := err.(*ldap.Error); ok {
  332. if ldapErr.ResultCode == 49 {
  333. return ErrInvalidCredentials
  334. }
  335. }
  336. return err
  337. }
  338. return nil
  339. }
  340. func (server *Server) secondBind(
  341. user *models.ExternalUserInfo,
  342. userPassword string,
  343. ) error {
  344. err := server.connection.Bind(user.AuthId, userPassword)
  345. if err != nil {
  346. server.log.Info("Second bind failed", "error", err)
  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. func (server *Server) initialBind(username, userPassword string) error {
  357. if server.config.BindPassword != "" || server.config.BindDN == "" {
  358. userPassword = server.config.BindPassword
  359. server.requireSecondBind = true
  360. }
  361. bindPath := server.config.BindDN
  362. if strings.Contains(bindPath, "%s") {
  363. bindPath = fmt.Sprintf(server.config.BindDN, username)
  364. }
  365. bindFn := func() error {
  366. return server.connection.Bind(bindPath, userPassword)
  367. }
  368. if userPassword == "" {
  369. bindFn = func() error {
  370. return server.connection.UnauthenticatedBind(bindPath)
  371. }
  372. }
  373. if err := bindFn(); err != nil {
  374. server.log.Info("Initial bind failed", "error", err)
  375. if ldapErr, ok := err.(*ldap.Error); ok {
  376. if ldapErr.ResultCode == 49 {
  377. return ErrInvalidCredentials
  378. }
  379. }
  380. return err
  381. }
  382. return nil
  383. }
  384. // requestMemberOf use this function when POSIX LDAP schema does not support memberOf, so it manually search the groups
  385. func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string, error) {
  386. var memberOf []string
  387. for _, groupSearchBase := range server.config.GroupSearchBaseDNs {
  388. var filterReplace string
  389. if server.config.GroupSearchFilterUserAttribute == "" {
  390. filterReplace = getLDAPAttr(server.config.Attr.Username, searchResult)
  391. } else {
  392. filterReplace = getLDAPAttr(server.config.GroupSearchFilterUserAttribute, searchResult)
  393. }
  394. filter := strings.Replace(
  395. server.config.GroupSearchFilter, "%s",
  396. ldap.EscapeFilter(filterReplace),
  397. -1,
  398. )
  399. server.log.Info("Searching for user's groups", "filter", filter)
  400. // support old way of reading settings
  401. groupIDAttribute := server.config.Attr.MemberOf
  402. // but prefer dn attribute if default settings are used
  403. if groupIDAttribute == "" || groupIDAttribute == "memberOf" {
  404. groupIDAttribute = "dn"
  405. }
  406. groupSearchReq := ldap.SearchRequest{
  407. BaseDN: groupSearchBase,
  408. Scope: ldap.ScopeWholeSubtree,
  409. DerefAliases: ldap.NeverDerefAliases,
  410. Attributes: []string{groupIDAttribute},
  411. Filter: filter,
  412. }
  413. groupSearchResult, err := server.connection.Search(&groupSearchReq)
  414. if err != nil {
  415. return nil, err
  416. }
  417. if len(groupSearchResult.Entries) > 0 {
  418. for i := range groupSearchResult.Entries {
  419. memberOf = append(memberOf, getLDAPAttrN(groupIDAttribute, groupSearchResult, i))
  420. }
  421. break
  422. }
  423. }
  424. return memberOf, nil
  425. }
  426. // serializeUsers serializes the users
  427. // from LDAP result to ExternalInfo struct
  428. func (server *Server) serializeUsers(
  429. users *ldap.SearchResult,
  430. ) ([]*models.ExternalUserInfo, error) {
  431. var serialized []*models.ExternalUserInfo
  432. for index := range users.Entries {
  433. memberOf, err := server.getMemberOf(users)
  434. if err != nil {
  435. return nil, err
  436. }
  437. userInfo := &UserInfo{
  438. DN: getLDAPAttrN(
  439. "dn",
  440. users,
  441. index,
  442. ),
  443. LastName: getLDAPAttrN(
  444. server.config.Attr.Surname,
  445. users,
  446. index,
  447. ),
  448. FirstName: getLDAPAttrN(
  449. server.config.Attr.Name,
  450. users,
  451. index,
  452. ),
  453. Username: getLDAPAttrN(
  454. server.config.Attr.Username,
  455. users,
  456. index,
  457. ),
  458. Email: getLDAPAttrN(
  459. server.config.Attr.Email,
  460. users,
  461. index,
  462. ),
  463. MemberOf: memberOf,
  464. }
  465. serialized = append(
  466. serialized,
  467. server.buildGrafanaUser(userInfo),
  468. )
  469. }
  470. return serialized, nil
  471. }
  472. // getMemberOf finds memberOf property or request it
  473. func (server *Server) getMemberOf(search *ldap.SearchResult) (
  474. []string, error,
  475. ) {
  476. if server.config.GroupSearchFilter == "" {
  477. memberOf := getLDAPAttrArray(server.config.Attr.MemberOf, search)
  478. return memberOf, nil
  479. }
  480. memberOf, err := server.requestMemberOf(search)
  481. if err != nil {
  482. return nil, err
  483. }
  484. return memberOf, nil
  485. }
  486. func appendIfNotEmpty(slice []string, values ...string) []string {
  487. for _, v := range values {
  488. if v != "" {
  489. slice = append(slice, v)
  490. }
  491. }
  492. return slice
  493. }
  494. func getLDAPAttr(name string, result *ldap.SearchResult) string {
  495. return getLDAPAttrN(name, result, 0)
  496. }
  497. func getLDAPAttrN(name string, result *ldap.SearchResult, n int) string {
  498. if strings.ToLower(name) == "dn" {
  499. return result.Entries[n].DN
  500. }
  501. for _, attr := range result.Entries[n].Attributes {
  502. if attr.Name == name {
  503. if len(attr.Values) > 0 {
  504. return attr.Values[0]
  505. }
  506. }
  507. }
  508. return ""
  509. }
  510. func getLDAPAttrArray(name string, result *ldap.SearchResult) []string {
  511. return getLDAPAttrArrayN(name, result, 0)
  512. }
  513. func getLDAPAttrArrayN(name string, result *ldap.SearchResult, n int) []string {
  514. for _, attr := range result.Entries[n].Attributes {
  515. if attr.Name == name {
  516. return attr.Values
  517. }
  518. }
  519. return []string{}
  520. }