ldap.go 13 KB

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