auth_proxy.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. package authproxy
  2. import (
  3. "fmt"
  4. "net"
  5. "net/mail"
  6. "reflect"
  7. "strings"
  8. "time"
  9. "github.com/grafana/grafana/pkg/bus"
  10. "github.com/grafana/grafana/pkg/infra/remotecache"
  11. "github.com/grafana/grafana/pkg/models"
  12. "github.com/grafana/grafana/pkg/services/ldap"
  13. "github.com/grafana/grafana/pkg/services/multildap"
  14. "github.com/grafana/grafana/pkg/setting"
  15. )
  16. const (
  17. // CachePrefix is a prefix for the cache key
  18. CachePrefix = "auth-proxy-sync-ttl:%s"
  19. )
  20. // getLDAPConfig gets LDAP config
  21. var getLDAPConfig = ldap.GetConfig
  22. // isLDAPEnabled checks if LDAP is enabled
  23. var isLDAPEnabled = ldap.IsEnabled
  24. // newLDAP creates multiple LDAP instance
  25. var newLDAP = multildap.New
  26. // AuthProxy struct
  27. type AuthProxy struct {
  28. store *remotecache.RemoteCache
  29. ctx *models.ReqContext
  30. orgID int64
  31. header string
  32. enabled bool
  33. LDAPAllowSignup bool
  34. AuthProxyAutoSignUp bool
  35. whitelistIP string
  36. headerType string
  37. headers map[string]string
  38. cacheTTL int
  39. }
  40. // Error auth proxy specific error
  41. type Error struct {
  42. Message string
  43. DetailsError error
  44. }
  45. // newError creates the Error
  46. func newError(message string, err error) *Error {
  47. return &Error{
  48. Message: message,
  49. DetailsError: err,
  50. }
  51. }
  52. // Error returns a Error error string
  53. func (err *Error) Error() string {
  54. return err.Message
  55. }
  56. // Options for the AuthProxy
  57. type Options struct {
  58. Store *remotecache.RemoteCache
  59. Ctx *models.ReqContext
  60. OrgID int64
  61. }
  62. // New instance of the AuthProxy
  63. func New(options *Options) *AuthProxy {
  64. header := options.Ctx.Req.Header.Get(setting.AuthProxyHeaderName)
  65. return &AuthProxy{
  66. store: options.Store,
  67. ctx: options.Ctx,
  68. orgID: options.OrgID,
  69. header: header,
  70. enabled: setting.AuthProxyEnabled,
  71. headerType: setting.AuthProxyHeaderProperty,
  72. headers: setting.AuthProxyHeaders,
  73. whitelistIP: setting.AuthProxyWhitelist,
  74. cacheTTL: setting.AuthProxyLDAPSyncTtl,
  75. LDAPAllowSignup: setting.LDAPAllowSignup,
  76. AuthProxyAutoSignUp: setting.AuthProxyAutoSignUp,
  77. }
  78. }
  79. // IsEnabled checks if the proxy auth is enabled
  80. func (auth *AuthProxy) IsEnabled() bool {
  81. // Bail if the setting is not enabled
  82. return auth.enabled
  83. }
  84. // HasHeader checks if the we have specified header
  85. func (auth *AuthProxy) HasHeader() bool {
  86. return len(auth.header) != 0
  87. }
  88. // IsAllowedIP compares presented IP with the whitelist one
  89. func (auth *AuthProxy) IsAllowedIP() (bool, *Error) {
  90. ip := auth.ctx.Req.RemoteAddr
  91. if len(strings.TrimSpace(auth.whitelistIP)) == 0 {
  92. return true, nil
  93. }
  94. proxies := strings.Split(auth.whitelistIP, ",")
  95. var proxyObjs []*net.IPNet
  96. for _, proxy := range proxies {
  97. result, err := coerceProxyAddress(proxy)
  98. if err != nil {
  99. return false, newError("Could not get the network", err)
  100. }
  101. proxyObjs = append(proxyObjs, result)
  102. }
  103. sourceIP, _, _ := net.SplitHostPort(ip)
  104. sourceObj := net.ParseIP(sourceIP)
  105. for _, proxyObj := range proxyObjs {
  106. if proxyObj.Contains(sourceObj) {
  107. return true, nil
  108. }
  109. }
  110. err := fmt.Errorf(
  111. "Request for user (%s) from %s is not from the authentication proxy", auth.header,
  112. sourceIP,
  113. )
  114. return false, newError("Proxy authentication required", err)
  115. }
  116. // getKey forms a key for the cache
  117. func (auth *AuthProxy) getKey() string {
  118. return fmt.Sprintf(CachePrefix, auth.header)
  119. }
  120. // Login logs in user id with whatever means possible
  121. func (auth *AuthProxy) Login() (int64, *Error) {
  122. id, _ := auth.GetUserViaCache()
  123. if id != 0 {
  124. // Error here means absent cache - we don't need to handle that
  125. return id, nil
  126. }
  127. if isLDAPEnabled() {
  128. id, err := auth.LoginViaLDAP()
  129. if err == ldap.ErrInvalidCredentials {
  130. return 0, newError(
  131. "Proxy authentication required",
  132. ldap.ErrInvalidCredentials,
  133. )
  134. }
  135. if err != nil {
  136. return 0, newError("Failed to get the user", err)
  137. }
  138. return id, nil
  139. }
  140. id, err := auth.LoginViaHeader()
  141. if err != nil {
  142. return 0, newError(
  143. "Failed to log in as user, specified in auth proxy header",
  144. err,
  145. )
  146. }
  147. return id, nil
  148. }
  149. // GetUserViaCache gets user id from cache
  150. func (auth *AuthProxy) GetUserViaCache() (int64, error) {
  151. var (
  152. cacheKey = auth.getKey()
  153. userID, err = auth.store.Get(cacheKey)
  154. )
  155. if err != nil {
  156. return 0, err
  157. }
  158. return userID.(int64), nil
  159. }
  160. // LoginViaLDAP logs in user via LDAP request
  161. func (auth *AuthProxy) LoginViaLDAP() (int64, *Error) {
  162. config, err := getLDAPConfig()
  163. if err != nil {
  164. return 0, newError("Failed to get LDAP config", nil)
  165. }
  166. extUser, err := newLDAP(config.Servers).User(auth.header)
  167. if err != nil {
  168. return 0, newError(err.Error(), nil)
  169. }
  170. // Have to sync grafana and LDAP user during log in
  171. upsert := &models.UpsertUserCommand{
  172. ReqContext: auth.ctx,
  173. SignupAllowed: auth.LDAPAllowSignup,
  174. ExternalUser: extUser,
  175. }
  176. err = bus.Dispatch(upsert)
  177. if err != nil {
  178. return 0, newError(err.Error(), nil)
  179. }
  180. return upsert.Result.Id, nil
  181. }
  182. // LoginViaHeader logs in user from the header only
  183. // TODO: refactor - cyclomatic complexity should be much lower
  184. func (auth *AuthProxy) LoginViaHeader() (int64, error) {
  185. extUser := &models.ExternalUserInfo{
  186. AuthModule: "authproxy",
  187. AuthId: auth.header,
  188. }
  189. if auth.headerType == "username" {
  190. extUser.Login = auth.header
  191. // only set Email if it can be parsed as an email address
  192. emailAddr, emailErr := mail.ParseAddress(auth.header)
  193. if emailErr == nil {
  194. extUser.Email = emailAddr.Address
  195. }
  196. } else if auth.headerType == "email" {
  197. extUser.Email = auth.header
  198. extUser.Login = auth.header
  199. } else {
  200. return 0, newError("Auth proxy header property invalid", nil)
  201. }
  202. for _, field := range []string{"Name", "Email", "Login"} {
  203. if auth.headers[field] == "" {
  204. continue
  205. }
  206. if val := auth.ctx.Req.Header.Get(auth.headers[field]); val != "" {
  207. reflect.ValueOf(extUser).Elem().FieldByName(field).SetString(val)
  208. }
  209. }
  210. upsert := &models.UpsertUserCommand{
  211. ReqContext: auth.ctx,
  212. SignupAllowed: true,
  213. ExternalUser: extUser,
  214. }
  215. err := bus.Dispatch(upsert)
  216. if err != nil {
  217. return 0, err
  218. }
  219. return upsert.Result.Id, nil
  220. }
  221. // GetSignedUser get full signed user info
  222. func (auth *AuthProxy) GetSignedUser(userID int64) (*models.SignedInUser, *Error) {
  223. query := &models.GetSignedInUserQuery{
  224. OrgId: auth.orgID,
  225. UserId: userID,
  226. }
  227. if err := bus.Dispatch(query); err != nil {
  228. return nil, newError(err.Error(), nil)
  229. }
  230. return query.Result, nil
  231. }
  232. // Remember user in cache
  233. func (auth *AuthProxy) Remember(id int64) *Error {
  234. key := auth.getKey()
  235. // Check if user already in cache
  236. userID, _ := auth.store.Get(key)
  237. if userID != nil {
  238. return nil
  239. }
  240. expiration := time.Duration(auth.cacheTTL) * time.Minute
  241. err := auth.store.Set(key, id, expiration)
  242. if err != nil {
  243. return newError(err.Error(), nil)
  244. }
  245. return nil
  246. }
  247. // coerceProxyAddress gets network of the presented CIDR notation
  248. func coerceProxyAddress(proxyAddr string) (*net.IPNet, error) {
  249. proxyAddr = strings.TrimSpace(proxyAddr)
  250. if !strings.Contains(proxyAddr, "/") {
  251. proxyAddr = strings.Join([]string{proxyAddr, "32"}, "/")
  252. }
  253. _, network, err := net.ParseCIDR(proxyAddr)
  254. return network, err
  255. }