ldap.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  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. Users([]string) ([]*models.ExternalUserInfo, error)
  28. ExtractGrafanaUser(*UserInfo) (*models.ExternalUserInfo, error)
  29. InitialBind(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. 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 user by searching and serializing it
  112. func (server *Server) Login(query *models.LoginUserQuery) (
  113. *models.ExternalUserInfo, error,
  114. ) {
  115. // Perform initial authentication
  116. err := server.InitialBind(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. server.disableExternalUser(query.Username)
  129. return nil, ErrInvalidCredentials
  130. }
  131. // Check if a second user bind is needed
  132. user := users[0]
  133. if server.requireSecondBind {
  134. err = server.secondBind(user, query.Password)
  135. if err != nil {
  136. return nil, err
  137. }
  138. }
  139. return user, nil
  140. }
  141. // Users gets LDAP users
  142. func (server *Server) Users(logins []string) (
  143. []*models.ExternalUserInfo,
  144. error,
  145. ) {
  146. var result *ldap.SearchResult
  147. var err error
  148. var Config = server.Config
  149. for _, base := range Config.SearchBaseDNs {
  150. result, err = server.Connection.Search(
  151. server.getSearchRequest(base, logins),
  152. )
  153. if err != nil {
  154. return nil, err
  155. }
  156. if len(result.Entries) > 0 {
  157. break
  158. }
  159. }
  160. serializedUsers, err := server.serializeUsers(result)
  161. if err != nil {
  162. return nil, err
  163. }
  164. return serializedUsers, nil
  165. }
  166. // ExtractGrafanaUser extracts external user info from LDAP user
  167. func (server *Server) ExtractGrafanaUser(user *UserInfo) (*models.ExternalUserInfo, error) {
  168. result := server.buildGrafanaUser(user)
  169. if err := server.validateGrafanaUser(result); err != nil {
  170. return nil, err
  171. }
  172. return result, nil
  173. }
  174. // validateGrafanaUser validates user access.
  175. // If there are no ldap group mappings access is true
  176. // otherwise a single group must match
  177. func (server *Server) validateGrafanaUser(user *models.ExternalUserInfo) error {
  178. if len(server.Config.Groups) > 0 && len(user.OrgRoles) < 1 {
  179. server.log.Error(
  180. "user does not belong in any of the specified LDAP groups",
  181. "username", user.Login,
  182. "groups", user.Groups,
  183. )
  184. return ErrInvalidCredentials
  185. }
  186. return nil
  187. }
  188. // disableExternalUser marks external user as disabled in Grafana db
  189. func (server *Server) disableExternalUser(username string) error {
  190. // Check if external user exist in Grafana
  191. userQuery := &models.GetExternalUserInfoByLoginQuery{
  192. LoginOrEmail: username,
  193. }
  194. if err := bus.Dispatch(userQuery); err != nil {
  195. return err
  196. }
  197. userInfo := userQuery.Result
  198. if !userInfo.IsDisabled {
  199. server.log.Debug("Disabling external user", "user", userQuery.Result.Login)
  200. // Mark user as disabled in grafana db
  201. disableUserCmd := &models.DisableUserCommand{
  202. UserId: userQuery.Result.UserId,
  203. IsDisabled: true,
  204. }
  205. if err := bus.Dispatch(disableUserCmd); err != nil {
  206. server.log.Debug("Error disabling external user", "user", userQuery.Result.Login, "message", err.Error())
  207. return err
  208. }
  209. }
  210. return nil
  211. }
  212. // getSearchRequest returns LDAP search request for users
  213. func (server *Server) getSearchRequest(
  214. base string,
  215. logins []string,
  216. ) *ldap.SearchRequest {
  217. attributes := []string{}
  218. inputs := server.Config.Attr
  219. attributes = appendIfNotEmpty(
  220. attributes,
  221. inputs.Username,
  222. inputs.Surname,
  223. inputs.Email,
  224. inputs.Name,
  225. inputs.MemberOf,
  226. )
  227. search := ""
  228. for _, login := range logins {
  229. query := strings.Replace(
  230. server.Config.SearchFilter,
  231. "%s", ldap.EscapeFilter(login),
  232. -1,
  233. )
  234. search = search + query
  235. }
  236. filter := fmt.Sprintf("(|%s)", search)
  237. return &ldap.SearchRequest{
  238. BaseDN: base,
  239. Scope: ldap.ScopeWholeSubtree,
  240. DerefAliases: ldap.NeverDerefAliases,
  241. Attributes: attributes,
  242. Filter: filter,
  243. }
  244. }
  245. // buildGrafanaUser extracts info from UserInfo model to ExternalUserInfo
  246. func (server *Server) buildGrafanaUser(user *UserInfo) *models.ExternalUserInfo {
  247. extUser := &models.ExternalUserInfo{
  248. AuthModule: models.AuthModuleLDAP,
  249. AuthId: user.DN,
  250. Name: strings.TrimSpace(
  251. fmt.Sprintf("%s %s", user.FirstName, user.LastName),
  252. ),
  253. Login: user.Username,
  254. Email: user.Email,
  255. Groups: user.MemberOf,
  256. OrgRoles: map[int64]models.RoleType{},
  257. }
  258. for _, group := range server.Config.Groups {
  259. // only use the first match for each org
  260. if extUser.OrgRoles[group.OrgId] != "" {
  261. continue
  262. }
  263. if user.isMemberOf(group.GroupDN) {
  264. extUser.OrgRoles[group.OrgId] = group.OrgRole
  265. if extUser.IsGrafanaAdmin == nil || !*extUser.IsGrafanaAdmin {
  266. extUser.IsGrafanaAdmin = group.IsGrafanaAdmin
  267. }
  268. }
  269. }
  270. return extUser
  271. }
  272. func (server *Server) serverBind() error {
  273. bindFn := func() error {
  274. return server.Connection.Bind(
  275. server.Config.BindDN,
  276. server.Config.BindPassword,
  277. )
  278. }
  279. if server.Config.BindPassword == "" {
  280. bindFn = func() error {
  281. return server.Connection.UnauthenticatedBind(server.Config.BindDN)
  282. }
  283. }
  284. // bind_dn and bind_password to bind
  285. if err := bindFn(); err != nil {
  286. server.log.Info("LDAP initial bind failed, %v", err)
  287. if ldapErr, ok := err.(*ldap.Error); ok {
  288. if ldapErr.ResultCode == 49 {
  289. return ErrInvalidCredentials
  290. }
  291. }
  292. return err
  293. }
  294. return nil
  295. }
  296. func (server *Server) secondBind(
  297. user *models.ExternalUserInfo,
  298. userPassword string,
  299. ) error {
  300. err := server.Connection.Bind(user.AuthId, userPassword)
  301. if err != nil {
  302. server.log.Info("Second bind failed", "error", err)
  303. if ldapErr, ok := err.(*ldap.Error); ok {
  304. if ldapErr.ResultCode == 49 {
  305. return ErrInvalidCredentials
  306. }
  307. }
  308. return err
  309. }
  310. return nil
  311. }
  312. // InitialBind intiates first bind to LDAP server
  313. func (server *Server) InitialBind(username, userPassword string) error {
  314. if server.Config.BindPassword != "" || server.Config.BindDN == "" {
  315. userPassword = server.Config.BindPassword
  316. server.requireSecondBind = true
  317. }
  318. bindPath := server.Config.BindDN
  319. if strings.Contains(bindPath, "%s") {
  320. bindPath = fmt.Sprintf(server.Config.BindDN, username)
  321. }
  322. bindFn := func() error {
  323. return server.Connection.Bind(bindPath, userPassword)
  324. }
  325. if userPassword == "" {
  326. bindFn = func() error {
  327. return server.Connection.UnauthenticatedBind(bindPath)
  328. }
  329. }
  330. if err := bindFn(); err != nil {
  331. server.log.Info("Initial bind failed", "error", err)
  332. if ldapErr, ok := err.(*ldap.Error); ok {
  333. if ldapErr.ResultCode == 49 {
  334. return ErrInvalidCredentials
  335. }
  336. }
  337. return err
  338. }
  339. return nil
  340. }
  341. // requestMemberOf use this function when POSIX LDAP schema does not support memberOf, so it manually search the groups
  342. func (server *Server) requestMemberOf(searchResult *ldap.SearchResult) ([]string, error) {
  343. var memberOf []string
  344. for _, groupSearchBase := range server.Config.GroupSearchBaseDNs {
  345. var filterReplace string
  346. if server.Config.GroupSearchFilterUserAttribute == "" {
  347. filterReplace = getLDAPAttr(server.Config.Attr.Username, searchResult)
  348. } else {
  349. filterReplace = getLDAPAttr(server.Config.GroupSearchFilterUserAttribute, searchResult)
  350. }
  351. filter := strings.Replace(
  352. server.Config.GroupSearchFilter, "%s",
  353. ldap.EscapeFilter(filterReplace),
  354. -1,
  355. )
  356. server.log.Info("Searching for user's groups", "filter", filter)
  357. // support old way of reading settings
  358. groupIDAttribute := server.Config.Attr.MemberOf
  359. // but prefer dn attribute if default settings are used
  360. if groupIDAttribute == "" || groupIDAttribute == "memberOf" {
  361. groupIDAttribute = "dn"
  362. }
  363. groupSearchReq := ldap.SearchRequest{
  364. BaseDN: groupSearchBase,
  365. Scope: ldap.ScopeWholeSubtree,
  366. DerefAliases: ldap.NeverDerefAliases,
  367. Attributes: []string{groupIDAttribute},
  368. Filter: filter,
  369. }
  370. groupSearchResult, err := server.Connection.Search(&groupSearchReq)
  371. if err != nil {
  372. return nil, err
  373. }
  374. if len(groupSearchResult.Entries) > 0 {
  375. for i := range groupSearchResult.Entries {
  376. memberOf = append(memberOf, getLDAPAttrN(groupIDAttribute, groupSearchResult, i))
  377. }
  378. break
  379. }
  380. }
  381. return memberOf, nil
  382. }
  383. // serializeUsers serializes the users
  384. // from LDAP result to ExternalInfo struct
  385. func (server *Server) serializeUsers(
  386. users *ldap.SearchResult,
  387. ) ([]*models.ExternalUserInfo, error) {
  388. var serialized []*models.ExternalUserInfo
  389. for index := range users.Entries {
  390. memberOf, err := server.getMemberOf(users)
  391. if err != nil {
  392. return nil, err
  393. }
  394. userInfo := &UserInfo{
  395. DN: getLDAPAttrN(
  396. "dn",
  397. users,
  398. index,
  399. ),
  400. LastName: getLDAPAttrN(
  401. server.Config.Attr.Surname,
  402. users,
  403. index,
  404. ),
  405. FirstName: getLDAPAttrN(
  406. server.Config.Attr.Name,
  407. users,
  408. index,
  409. ),
  410. Username: getLDAPAttrN(
  411. server.Config.Attr.Username,
  412. users,
  413. index,
  414. ),
  415. Email: getLDAPAttrN(
  416. server.Config.Attr.Email,
  417. users,
  418. index,
  419. ),
  420. MemberOf: memberOf,
  421. }
  422. serialized = append(
  423. serialized,
  424. server.buildGrafanaUser(userInfo),
  425. )
  426. }
  427. return serialized, nil
  428. }
  429. // getMemberOf finds memberOf property or request it
  430. func (server *Server) getMemberOf(search *ldap.SearchResult) (
  431. []string, error,
  432. ) {
  433. if server.Config.GroupSearchFilter == "" {
  434. memberOf := getLDAPAttrArray(server.Config.Attr.MemberOf, search)
  435. return memberOf, nil
  436. }
  437. memberOf, err := server.requestMemberOf(search)
  438. if err != nil {
  439. return nil, err
  440. }
  441. return memberOf, nil
  442. }
  443. func appendIfNotEmpty(slice []string, values ...string) []string {
  444. for _, v := range values {
  445. if v != "" {
  446. slice = append(slice, v)
  447. }
  448. }
  449. return slice
  450. }
  451. func getLDAPAttr(name string, result *ldap.SearchResult) string {
  452. return getLDAPAttrN(name, result, 0)
  453. }
  454. func getLDAPAttrN(name string, result *ldap.SearchResult, n int) string {
  455. if strings.ToLower(name) == "dn" {
  456. return result.Entries[n].DN
  457. }
  458. for _, attr := range result.Entries[n].Attributes {
  459. if attr.Name == name {
  460. if len(attr.Values) > 0 {
  461. return attr.Values[0]
  462. }
  463. }
  464. }
  465. return ""
  466. }
  467. func getLDAPAttrArray(name string, result *ldap.SearchResult) []string {
  468. return getLDAPAttrArrayN(name, result, 0)
  469. }
  470. func getLDAPAttrArrayN(name string, result *ldap.SearchResult, n int) []string {
  471. for _, attr := range result.Entries[n].Attributes {
  472. if attr.Name == name {
  473. return attr.Values
  474. }
  475. }
  476. return []string{}
  477. }