ldap.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  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/infra/log"
  11. "github.com/grafana/grafana/pkg/models"
  12. )
  13. // IConnection is interface for LDAP connection manipulation
  14. type IConnection interface {
  15. Bind(username, password string) error
  16. UnauthenticatedBind(username string) error
  17. Add(*ldap.AddRequest) error
  18. Del(*ldap.DelRequest) error
  19. Search(*ldap.SearchRequest) (*ldap.SearchResult, error)
  20. StartTLS(*tls.Config) error
  21. Close()
  22. }
  23. // IServer is interface for LDAP authorization
  24. type IServer interface {
  25. Login(*models.LoginUserQuery) (*models.ExternalUserInfo, error)
  26. Add(string, map[string][]string) error
  27. Remove(string) error
  28. Users([]string) ([]*models.ExternalUserInfo, error)
  29. ExtractGrafanaUser(*UserInfo) (*models.ExternalUserInfo, 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. requireSecondBind bool
  38. log log.Logger
  39. }
  40. var (
  41. // ErrInvalidCredentials is returned if username and password do not match
  42. ErrInvalidCredentials = errors.New("Invalid Username or Password")
  43. )
  44. var dial = func(network, addr string) (IConnection, error) {
  45. return ldap.Dial(network, addr)
  46. }
  47. // New creates the new LDAP auth
  48. func New(config *ServerConfig) IServer {
  49. return &Server{
  50. config: config,
  51. log: log.New("ldap"),
  52. }
  53. }
  54. // Dial dials in the LDAP
  55. func (server *Server) Dial() error {
  56. var err error
  57. var certPool *x509.CertPool
  58. if server.config.RootCACert != "" {
  59. certPool = x509.NewCertPool()
  60. for _, caCertFile := range strings.Split(server.config.RootCACert, " ") {
  61. pem, err := ioutil.ReadFile(caCertFile)
  62. if err != nil {
  63. return err
  64. }
  65. if !certPool.AppendCertsFromPEM(pem) {
  66. return errors.New("Failed to append CA certificate " + caCertFile)
  67. }
  68. }
  69. }
  70. var clientCert tls.Certificate
  71. if server.config.ClientCert != "" && server.config.ClientKey != "" {
  72. clientCert, err = tls.LoadX509KeyPair(server.config.ClientCert, server.config.ClientKey)
  73. if err != nil {
  74. return err
  75. }
  76. }
  77. for _, host := range strings.Split(server.config.Host, " ") {
  78. address := fmt.Sprintf("%s:%d", host, server.config.Port)
  79. if server.config.UseSSL {
  80. tlsCfg := &tls.Config{
  81. InsecureSkipVerify: server.config.SkipVerifySSL,
  82. ServerName: host,
  83. RootCAs: certPool,
  84. }
  85. if len(clientCert.Certificate) > 0 {
  86. tlsCfg.Certificates = append(tlsCfg.Certificates, clientCert)
  87. }
  88. if server.config.StartTLS {
  89. server.connection, err = dial("tcp", address)
  90. if err == nil {
  91. if err = server.connection.StartTLS(tlsCfg); err == nil {
  92. return nil
  93. }
  94. }
  95. } else {
  96. server.connection, err = ldap.DialTLS("tcp", address, tlsCfg)
  97. }
  98. } else {
  99. server.connection, err = dial("tcp", address)
  100. }
  101. if err == nil {
  102. return nil
  103. }
  104. }
  105. return err
  106. }
  107. // Close closes the LDAP connection
  108. func (server *Server) Close() {
  109. server.connection.Close()
  110. }
  111. // Login intialBinds the user, search it and then serialize it
  112. func (server *Server) Login(query *models.LoginUserQuery) (
  113. *models.ExternalUserInfo, error,
  114. ) {
  115. // Perform initial authentication
  116. err := server.intialBind(query.Username, query.Password)
  117. if err != nil {
  118. return nil, err
  119. }
  120. // Find user entry & attributes
  121. users, err := server.Users([]string{query.Username})
  122. if err != nil {
  123. return nil, err
  124. }
  125. // If we couldn't find the user -
  126. // we should show incorrect credentials err
  127. if len(users) == 0 {
  128. return nil, ErrInvalidCredentials
  129. }
  130. // Check if a second user bind is needed
  131. user := users[0]
  132. if server.requireSecondBind {
  133. err = server.secondBind(user, query.Password)
  134. if err != nil {
  135. return nil, err
  136. }
  137. }
  138. return user, nil
  139. }
  140. // Add adds stuff to LDAP
  141. func (server *Server) Add(dn string, values map[string][]string) error {
  142. err := server.intialBind(
  143. server.config.BindDN,
  144. server.config.BindPassword,
  145. )
  146. if err != nil {
  147. return err
  148. }
  149. attributes := make([]ldap.Attribute, 0)
  150. for key, value := range values {
  151. attributes = append(attributes, ldap.Attribute{
  152. Type: key,
  153. Vals: value,
  154. })
  155. }
  156. request := &ldap.AddRequest{
  157. DN: dn,
  158. Attributes: attributes,
  159. }
  160. err = server.connection.Add(request)
  161. if err != nil {
  162. return err
  163. }
  164. return nil
  165. }
  166. // Remove removes stuff from LDAP
  167. func (server *Server) Remove(dn string) error {
  168. err := server.intialBind(
  169. server.config.BindDN,
  170. server.config.BindPassword,
  171. )
  172. if err != nil {
  173. return err
  174. }
  175. request := ldap.NewDelRequest(dn, nil)
  176. err = server.connection.Del(request)
  177. if err != nil {
  178. return err
  179. }
  180. return nil
  181. }
  182. // Users gets LDAP users
  183. func (server *Server) Users(logins []string) (
  184. []*models.ExternalUserInfo,
  185. error,
  186. ) {
  187. var result *ldap.SearchResult
  188. var err error
  189. var config = server.config
  190. for _, base := range config.SearchBaseDNs {
  191. result, err = server.connection.Search(
  192. server.getSearchRequest(base, logins),
  193. )
  194. if err != nil {
  195. return nil, err
  196. }
  197. if len(result.Entries) > 0 {
  198. break
  199. }
  200. }
  201. serializedUsers, err := server.serializeUsers(result)
  202. if err != nil {
  203. return nil, err
  204. }
  205. return serializedUsers, nil
  206. }
  207. // ExtractGrafanaUser extracts external user info from LDAP user
  208. func (server *Server) ExtractGrafanaUser(user *UserInfo) (*models.ExternalUserInfo, error) {
  209. result := server.buildGrafanaUser(user)
  210. if err := server.validateGrafanaUser(result); err != nil {
  211. return nil, err
  212. }
  213. return result, nil
  214. }
  215. // validateGrafanaUser validates user access.
  216. // If there are no ldap group mappings access is true
  217. // otherwise a single group must match
  218. func (server *Server) validateGrafanaUser(user *models.ExternalUserInfo) error {
  219. if len(server.config.Groups) > 0 && len(user.OrgRoles) < 1 {
  220. server.log.Error(
  221. "user does not belong in any of the specified LDAP groups",
  222. "username", user.Login,
  223. "groups", user.Groups,
  224. )
  225. return ErrInvalidCredentials
  226. }
  227. return nil
  228. }
  229. // getSearchRequest returns LDAP search request for users
  230. func (server *Server) getSearchRequest(
  231. base string,
  232. logins []string,
  233. ) *ldap.SearchRequest {
  234. attributes := []string{}
  235. inputs := server.config.Attr
  236. attributes = appendIfNotEmpty(
  237. attributes,
  238. inputs.Username,
  239. inputs.Surname,
  240. inputs.Email,
  241. inputs.Name,
  242. inputs.MemberOf,
  243. )
  244. search := ""
  245. for _, login := range logins {
  246. query := strings.Replace(
  247. server.config.SearchFilter,
  248. "%s", ldap.EscapeFilter(login),
  249. -1,
  250. )
  251. search = search + query
  252. }
  253. filter := fmt.Sprintf("(|%s)", search)
  254. return &ldap.SearchRequest{
  255. BaseDN: base,
  256. Scope: ldap.ScopeWholeSubtree,
  257. DerefAliases: ldap.NeverDerefAliases,
  258. Attributes: attributes,
  259. Filter: filter,
  260. }
  261. }
  262. // buildGrafanaUser extracts info from UserInfo model to ExternalUserInfo
  263. func (server *Server) buildGrafanaUser(user *UserInfo) *models.ExternalUserInfo {
  264. extUser := &models.ExternalUserInfo{
  265. AuthModule: "ldap",
  266. AuthId: user.DN,
  267. Name: strings.TrimSpace(
  268. fmt.Sprintf("%s %s", user.FirstName, user.LastName),
  269. ),
  270. Login: user.Username,
  271. Email: user.Email,
  272. Groups: user.MemberOf,
  273. OrgRoles: map[int64]models.RoleType{},
  274. }
  275. for _, group := range server.config.Groups {
  276. // only use the first match for each org
  277. if extUser.OrgRoles[group.OrgId] != "" {
  278. continue
  279. }
  280. if user.isMemberOf(group.GroupDN) {
  281. extUser.OrgRoles[group.OrgId] = group.OrgRole
  282. if extUser.IsGrafanaAdmin == nil || !*extUser.IsGrafanaAdmin {
  283. extUser.IsGrafanaAdmin = group.IsGrafanaAdmin
  284. }
  285. }
  286. }
  287. return extUser
  288. }
  289. func (server *Server) serverBind() error {
  290. bindFn := func() error {
  291. return server.connection.Bind(
  292. server.config.BindDN,
  293. server.config.BindPassword,
  294. )
  295. }
  296. if server.config.BindPassword == "" {
  297. bindFn = func() error {
  298. return server.connection.UnauthenticatedBind(server.config.BindDN)
  299. }
  300. }
  301. // bind_dn and bind_password to bind
  302. if err := bindFn(); err != nil {
  303. server.log.Info("LDAP initial bind failed, %v", err)
  304. if ldapErr, ok := err.(*ldap.Error); ok {
  305. if ldapErr.ResultCode == 49 {
  306. return ErrInvalidCredentials
  307. }
  308. }
  309. return err
  310. }
  311. return nil
  312. }
  313. func (server *Server) secondBind(
  314. user *models.ExternalUserInfo,
  315. userPassword string,
  316. ) error {
  317. err := server.connection.Bind(user.AuthId, userPassword)
  318. if err != nil {
  319. server.log.Info("Second bind failed", "error", err)
  320. if ldapErr, ok := err.(*ldap.Error); ok {
  321. if ldapErr.ResultCode == 49 {
  322. return ErrInvalidCredentials
  323. }
  324. }
  325. return err
  326. }
  327. return nil
  328. }
  329. func (server *Server) intialBind(username, userPassword string) error {
  330. if server.config.BindPassword != "" || server.config.BindDN == "" {
  331. userPassword = server.config.BindPassword
  332. server.requireSecondBind = true
  333. }
  334. bindPath := server.config.BindDN
  335. if strings.Contains(bindPath, "%s") {
  336. bindPath = fmt.Sprintf(server.config.BindDN, username)
  337. }
  338. bindFn := func() error {
  339. return server.connection.Bind(bindPath, userPassword)
  340. }
  341. if userPassword == "" {
  342. bindFn = func() error {
  343. return server.connection.UnauthenticatedBind(bindPath)
  344. }
  345. }
  346. if err := bindFn(); err != nil {
  347. server.log.Info("Initial bind failed", "error", err)
  348. if ldapErr, ok := err.(*ldap.Error); ok {
  349. if ldapErr.ResultCode == 49 {
  350. return ErrInvalidCredentials
  351. }
  352. }
  353. return err
  354. }
  355. return nil
  356. }
  357. // requestMemberOf use this function when POSIX LDAP schema does not support memberOf, so it manually search the groups
  358. func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string, error) {
  359. var memberOf []string
  360. for _, groupSearchBase := range server.config.GroupSearchBaseDNs {
  361. var filterReplace string
  362. if server.config.GroupSearchFilterUserAttribute == "" {
  363. filterReplace = getLdapAttr(server.config.Attr.Username, searchResult)
  364. } else {
  365. filterReplace = getLdapAttr(server.config.GroupSearchFilterUserAttribute, searchResult)
  366. }
  367. filter := strings.Replace(
  368. server.config.GroupSearchFilter, "%s",
  369. ldap.EscapeFilter(filterReplace),
  370. -1,
  371. )
  372. server.log.Info("Searching for user's groups", "filter", filter)
  373. // support old way of reading settings
  374. groupIDAttribute := server.config.Attr.MemberOf
  375. // but prefer dn attribute if default settings are used
  376. if groupIDAttribute == "" || groupIDAttribute == "memberOf" {
  377. groupIDAttribute = "dn"
  378. }
  379. groupSearchReq := ldap.SearchRequest{
  380. BaseDN: groupSearchBase,
  381. Scope: ldap.ScopeWholeSubtree,
  382. DerefAliases: ldap.NeverDerefAliases,
  383. Attributes: []string{groupIDAttribute},
  384. Filter: filter,
  385. }
  386. groupSearchResult, err := server.connection.Search(&groupSearchReq)
  387. if err != nil {
  388. return nil, err
  389. }
  390. if len(groupSearchResult.Entries) > 0 {
  391. for i := range groupSearchResult.Entries {
  392. memberOf = append(memberOf, getLdapAttrN(groupIDAttribute, groupSearchResult, i))
  393. }
  394. break
  395. }
  396. }
  397. return memberOf, nil
  398. }
  399. // serializeUsers serializes the users
  400. // from LDAP result to ExternalInfo struct
  401. func (server *Server) serializeUsers(
  402. users *ldap.SearchResult,
  403. ) ([]*models.ExternalUserInfo, error) {
  404. var serialized []*models.ExternalUserInfo
  405. for index := range users.Entries {
  406. memberOf, err := server.getMemberOf(users)
  407. if err != nil {
  408. return nil, err
  409. }
  410. userInfo := &UserInfo{
  411. DN: getLdapAttrN(
  412. "dn",
  413. users,
  414. index,
  415. ),
  416. LastName: getLdapAttrN(
  417. server.config.Attr.Surname,
  418. users,
  419. index,
  420. ),
  421. FirstName: getLdapAttrN(
  422. server.config.Attr.Name,
  423. users,
  424. index,
  425. ),
  426. Username: getLdapAttrN(
  427. server.config.Attr.Username,
  428. users,
  429. index,
  430. ),
  431. Email: getLdapAttrN(
  432. server.config.Attr.Email,
  433. users,
  434. index,
  435. ),
  436. MemberOf: memberOf,
  437. }
  438. serialized = append(
  439. serialized,
  440. server.buildGrafanaUser(userInfo),
  441. )
  442. }
  443. return serialized, nil
  444. }
  445. // getMemberOf finds memberOf property or request it
  446. func (server *Server) getMemberOf(search *ldap.SearchResult) (
  447. []string, error,
  448. ) {
  449. if server.config.GroupSearchFilter == "" {
  450. memberOf := getLdapAttrArray(server.config.Attr.MemberOf, search)
  451. return memberOf, nil
  452. }
  453. memberOf, err := server.requestMemberOf(search)
  454. if err != nil {
  455. return nil, err
  456. }
  457. return memberOf, nil
  458. }
  459. func appendIfNotEmpty(slice []string, values ...string) []string {
  460. for _, v := range values {
  461. if v != "" {
  462. slice = append(slice, v)
  463. }
  464. }
  465. return slice
  466. }
  467. func getLdapAttr(name string, result *ldap.SearchResult) string {
  468. return getLdapAttrN(name, result, 0)
  469. }
  470. func getLdapAttrN(name string, result *ldap.SearchResult, n int) string {
  471. if strings.ToLower(name) == "dn" {
  472. return result.Entries[n].DN
  473. }
  474. for _, attr := range result.Entries[n].Attributes {
  475. if attr.Name == name {
  476. if len(attr.Values) > 0 {
  477. return attr.Values[0]
  478. }
  479. }
  480. }
  481. return ""
  482. }
  483. func getLdapAttrArray(name string, result *ldap.SearchResult) []string {
  484. return getLdapAttrArrayN(name, result, 0)
  485. }
  486. func getLdapAttrArrayN(name string, result *ldap.SearchResult, n int) []string {
  487. for _, attr := range result.Entries[n].Attributes {
  488. if attr.Name == name {
  489. return attr.Values
  490. }
  491. }
  492. return []string{}
  493. }