You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

grafana.go 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. // Copyright 2014-2018 Grafana Labs
  2. // Released under the Apache 2.0 license
  3. // Modification notice:
  4. // 1. `serverConn` was substituted for `Server` as the type of the server object
  5. // 2. Debug loglines were altered to work with Oragono's logging system
  6. package ldap
  7. import (
  8. "crypto/tls"
  9. "crypto/x509"
  10. "errors"
  11. "fmt"
  12. "io/ioutil"
  13. "strings"
  14. ldap "github.com/go-ldap/ldap/v3"
  15. )
  16. var (
  17. // ErrInvalidCredentials is returned if username and password do not match
  18. ErrInvalidCredentials = errors.New("Invalid Username or Password")
  19. // ErrCouldNotFindUser is returned when username hasn't been found (not username+password)
  20. ErrCouldNotFindUser = errors.New("Can't find user in LDAP")
  21. )
  22. // shouldAdminBind checks if we should use
  23. // admin username & password for LDAP bind
  24. func (server *serverConn) shouldAdminBind() bool {
  25. return server.Config.BindPassword != ""
  26. }
  27. // singleBindDN combines the bind with the username
  28. // in order to get the proper path
  29. func (server *serverConn) singleBindDN(username string) string {
  30. return fmt.Sprintf(server.Config.BindDN, username)
  31. }
  32. // shouldSingleBind checks if we can use "single bind" approach
  33. func (server *serverConn) shouldSingleBind() bool {
  34. return strings.Contains(server.Config.BindDN, "%s")
  35. }
  36. // Dial dials in the LDAP
  37. // TODO: decrease cyclomatic complexity
  38. func (server *serverConn) Dial() error {
  39. var err error
  40. var certPool *x509.CertPool
  41. if server.Config.RootCACert != "" {
  42. certPool = x509.NewCertPool()
  43. for _, caCertFile := range strings.Split(server.Config.RootCACert, " ") {
  44. pem, err := ioutil.ReadFile(caCertFile)
  45. if err != nil {
  46. return err
  47. }
  48. if !certPool.AppendCertsFromPEM(pem) {
  49. return errors.New("Failed to append CA certificate " + caCertFile)
  50. }
  51. }
  52. }
  53. var clientCert tls.Certificate
  54. if server.Config.ClientCert != "" && server.Config.ClientKey != "" {
  55. clientCert, err = tls.LoadX509KeyPair(server.Config.ClientCert, server.Config.ClientKey)
  56. if err != nil {
  57. return err
  58. }
  59. }
  60. for _, host := range strings.Split(server.Config.Host, " ") {
  61. address := fmt.Sprintf("%s:%d", host, server.Config.Port)
  62. if server.Config.UseSSL {
  63. tlsCfg := &tls.Config{
  64. InsecureSkipVerify: server.Config.SkipVerifySSL,
  65. ServerName: host,
  66. RootCAs: certPool,
  67. }
  68. if len(clientCert.Certificate) > 0 {
  69. tlsCfg.Certificates = append(tlsCfg.Certificates, clientCert)
  70. }
  71. if server.Config.StartTLS {
  72. server.Connection, err = ldap.Dial("tcp", address)
  73. if err == nil {
  74. if err = server.Connection.StartTLS(tlsCfg); err == nil {
  75. return nil
  76. }
  77. }
  78. } else {
  79. server.Connection, err = ldap.DialTLS("tcp", address, tlsCfg)
  80. }
  81. } else {
  82. server.Connection, err = ldap.Dial("tcp", address)
  83. }
  84. if err == nil {
  85. return nil
  86. }
  87. }
  88. return err
  89. }
  90. // Close closes the LDAP connection
  91. // Dial() sets the connection with the server for this Struct. Therefore, we require a
  92. // call to Dial() before being able to execute this function.
  93. func (server *serverConn) Close() {
  94. server.Connection.Close()
  95. }
  96. // userBind binds the user with the LDAP server
  97. func (server *serverConn) userBind(path, password string) error {
  98. err := server.Connection.Bind(path, password)
  99. if err != nil {
  100. if ldapErr, ok := err.(*ldap.Error); ok {
  101. if ldapErr.ResultCode == 49 {
  102. return ErrInvalidCredentials
  103. }
  104. }
  105. return err
  106. }
  107. return nil
  108. }
  109. // users is helper method for the Users()
  110. func (server *serverConn) users(logins []string) (
  111. []*ldap.Entry,
  112. error,
  113. ) {
  114. var result *ldap.SearchResult
  115. var Config = server.Config
  116. var err error
  117. for _, base := range Config.SearchBaseDNs {
  118. result, err = server.Connection.Search(
  119. server.getSearchRequest(base, logins),
  120. )
  121. if err != nil {
  122. return nil, err
  123. }
  124. if len(result.Entries) > 0 {
  125. break
  126. }
  127. }
  128. return result.Entries, nil
  129. }
  130. // getSearchRequest returns LDAP search request for users
  131. func (server *serverConn) getSearchRequest(
  132. base string,
  133. logins []string,
  134. ) *ldap.SearchRequest {
  135. attributes := []string{}
  136. inputs := server.Config.Attr
  137. attributes = appendIfNotEmpty(
  138. attributes,
  139. inputs.Username,
  140. inputs.Surname,
  141. inputs.Email,
  142. inputs.Name,
  143. inputs.MemberOf,
  144. // In case for the POSIX LDAP schema server
  145. server.Config.GroupSearchFilterUserAttribute,
  146. )
  147. search := ""
  148. for _, login := range logins {
  149. query := strings.Replace(
  150. server.Config.SearchFilter,
  151. "%s", ldap.EscapeFilter(login),
  152. -1,
  153. )
  154. search = search + query
  155. }
  156. filter := fmt.Sprintf("(|%s)", search)
  157. return &ldap.SearchRequest{
  158. BaseDN: base,
  159. Scope: ldap.ScopeWholeSubtree,
  160. DerefAliases: ldap.NeverDerefAliases,
  161. Attributes: attributes,
  162. Filter: filter,
  163. }
  164. }
  165. // requestMemberOf use this function when POSIX LDAP
  166. // schema does not support memberOf, so it manually search the groups
  167. func (server *serverConn) requestMemberOf(entry *ldap.Entry) ([]string, error) {
  168. var memberOf []string
  169. var config = server.Config
  170. for _, groupSearchBase := range config.GroupSearchBaseDNs {
  171. var filterReplace string
  172. if config.GroupSearchFilterUserAttribute == "" {
  173. filterReplace = getAttribute(config.Attr.Username, entry)
  174. } else {
  175. filterReplace = getAttribute(
  176. config.GroupSearchFilterUserAttribute,
  177. entry,
  178. )
  179. }
  180. filter := strings.Replace(
  181. config.GroupSearchFilter, "%s",
  182. ldap.EscapeFilter(filterReplace),
  183. -1,
  184. )
  185. server.logger.Debug("ldap", "Searching for groups with filter", filter)
  186. // support old way of reading settings
  187. groupIDAttribute := config.Attr.MemberOf
  188. // but prefer dn attribute if default settings are used
  189. if groupIDAttribute == "" || groupIDAttribute == "memberOf" {
  190. groupIDAttribute = "dn"
  191. }
  192. groupSearchReq := ldap.SearchRequest{
  193. BaseDN: groupSearchBase,
  194. Scope: ldap.ScopeWholeSubtree,
  195. DerefAliases: ldap.NeverDerefAliases,
  196. Attributes: []string{groupIDAttribute},
  197. Filter: filter,
  198. }
  199. groupSearchResult, err := server.Connection.Search(&groupSearchReq)
  200. if err != nil {
  201. return nil, err
  202. }
  203. if len(groupSearchResult.Entries) > 0 {
  204. for _, group := range groupSearchResult.Entries {
  205. memberOf = append(
  206. memberOf,
  207. getAttribute(groupIDAttribute, group),
  208. )
  209. }
  210. break
  211. }
  212. }
  213. return memberOf, nil
  214. }
  215. // getMemberOf finds memberOf property or request it
  216. func (server *serverConn) getMemberOf(result *ldap.Entry) (
  217. []string, error,
  218. ) {
  219. if server.Config.GroupSearchFilter == "" {
  220. memberOf := getArrayAttribute(server.Config.Attr.MemberOf, result)
  221. return memberOf, nil
  222. }
  223. memberOf, err := server.requestMemberOf(result)
  224. if err != nil {
  225. return nil, err
  226. }
  227. return memberOf, nil
  228. }