mirror of
https://github.com/grafana/grafana.git
synced 2025-02-09 06:56:07 -06:00
AuthProxy: Can now login with auth proxy and get a login token (#20175)
* AuthProxy: Can now login with auth proxy and get a login token * added unit tests * renamed setting and updated docs * AuthProxy: minor tweak * Fixed tests and namings * spellfix * fix * remove unused setting, probably from merge conflict * fix
This commit is contained in:
parent
818aa8eefa
commit
be2bf1a297
@ -438,6 +438,7 @@ ldap_sync_ttl = 60
|
|||||||
sync_ttl = 60
|
sync_ttl = 60
|
||||||
whitelist =
|
whitelist =
|
||||||
headers =
|
headers =
|
||||||
|
enable_login_token = false
|
||||||
|
|
||||||
#################################### Auth LDAP ###########################
|
#################################### Auth LDAP ###########################
|
||||||
[auth.ldap]
|
[auth.ldap]
|
||||||
|
@ -399,6 +399,8 @@
|
|||||||
;ldap_sync_ttl = 60
|
;ldap_sync_ttl = 60
|
||||||
;whitelist = 192.168.1.1, 192.168.2.1
|
;whitelist = 192.168.1.1, 192.168.2.1
|
||||||
;headers = Email:X-User-Email, Name:X-User-Name
|
;headers = Email:X-User-Email, Name:X-User-Name
|
||||||
|
# Read the auth proxy docs for details on what the setting below enables
|
||||||
|
;enable_login_token = false
|
||||||
|
|
||||||
#################################### Basic Auth ##########################
|
#################################### Basic Auth ##########################
|
||||||
[auth.basic]
|
[auth.basic]
|
||||||
|
42
devenv/docker/blocks/nginx_proxy/nginx_login_only.conf
Normal file
42
devenv/docker/blocks/nginx_proxy/nginx_login_only.conf
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
events { worker_connections 1024; }
|
||||||
|
|
||||||
|
http {
|
||||||
|
sendfile on;
|
||||||
|
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Host $server_name;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 10080;
|
||||||
|
|
||||||
|
location /grafana/ {
|
||||||
|
################################################################
|
||||||
|
# Enable these settings to test with basic auth and an auth proxy header
|
||||||
|
# the htpasswd file contains an admin user with password admin and
|
||||||
|
# user1: grafana and user2: grafana
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
|
||||||
|
################################################################
|
||||||
|
# To use the auth proxy header, set the following in custom.ini:
|
||||||
|
# [auth.proxy]
|
||||||
|
# enabled = true
|
||||||
|
# header_name = X-WEBAUTH-USER
|
||||||
|
# header_property = username
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
location /grafana/login {
|
||||||
|
auth_basic "Restricted Content";
|
||||||
|
auth_basic_user_file /etc/nginx/htpasswd;
|
||||||
|
proxy_set_header X-WEBAUTH-USER $remote_user;
|
||||||
|
proxy_pass http://localhost:3000/login;
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy_set_header Authorization "";
|
||||||
|
proxy_pass http://localhost:3000/;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -36,6 +36,8 @@ whitelist =
|
|||||||
# Optionally define more headers to sync other user attributes
|
# Optionally define more headers to sync other user attributes
|
||||||
# Example `headers = Name:X-WEBAUTH-NAME Email:X-WEBAUTH-EMAIL Groups:X-WEBAUTH-GROUPS`
|
# Example `headers = Name:X-WEBAUTH-NAME Email:X-WEBAUTH-EMAIL Groups:X-WEBAUTH-GROUPS`
|
||||||
headers =
|
headers =
|
||||||
|
# Checkout docs on this for more details on the below setting
|
||||||
|
enable_login_token = false
|
||||||
```
|
```
|
||||||
|
|
||||||
## Interacting with Grafana’s AuthProxy via curl
|
## Interacting with Grafana’s AuthProxy via curl
|
||||||
@ -294,3 +296,13 @@ curl -H "X-WEBAUTH-USER: leonard" -H "X-WEBAUTH-GROUPS: lokiteamOnExternalSystem
|
|||||||
With this, the user `leonard` will be automatically placed into the Loki team as part of Grafana authentication.
|
With this, the user `leonard` will be automatically placed into the Loki team as part of Grafana authentication.
|
||||||
|
|
||||||
[Learn more about Team Sync]({{< relref "auth/team-sync.md" >}})
|
[Learn more about Team Sync]({{< relref "auth/team-sync.md" >}})
|
||||||
|
|
||||||
|
|
||||||
|
## Login token and session cookie
|
||||||
|
|
||||||
|
With `enable_login_token` set to `true` Grafana will, after successful auth proxy header validation, assign the user
|
||||||
|
a login token and cookie. You only have to configure your auth proxy to provide headers for the /login route.
|
||||||
|
Requests via other routes will be authenticated using the cookie.
|
||||||
|
|
||||||
|
Use settings `login_maximum_inactive_lifetime_days` and `login_maximum_lifetime_days` under `[auth]` to control session
|
||||||
|
lifetime. [Read more about login tokens]({{< relref "auth/overview/#login-and-short-lived-tokens" >}})
|
||||||
|
2
go.mod
2
go.mod
@ -67,7 +67,7 @@ require (
|
|||||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
|
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
|
||||||
github.com/yudai/pp v2.0.1+incompatible // indirect
|
github.com/yudai/pp v2.0.1+incompatible // indirect
|
||||||
go.uber.org/atomic v1.3.2 // indirect
|
go.uber.org/atomic v1.3.2 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
|
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392
|
||||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
|
||||||
golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914
|
golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
||||||
|
@ -57,18 +57,31 @@ func (hs *HTTPServer) LoginView(c *models.ReqContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.IsSignedIn {
|
if c.IsSignedIn {
|
||||||
c.HTML(200, ViewIndex, viewData)
|
// Assign login token to auth proxy users if enable_login_token = true
|
||||||
|
if setting.AuthProxyEnabled && setting.AuthProxyEnableLoginToken {
|
||||||
|
hs.loginAuthProxyUser(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
||||||
|
c.SetCookie("redirect_to", "", -1, setting.AppSubUrl+"/")
|
||||||
|
c.Redirect(redirectTo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Redirect(setting.AppSubUrl + "/")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if redirectTo, _ := url.QueryUnescape(c.GetCookie("redirect_to")); len(redirectTo) > 0 {
|
c.HTML(200, ViewIndex, viewData)
|
||||||
c.SetCookie("redirect_to", "", -1, setting.AppSubUrl+"/")
|
}
|
||||||
c.Redirect(redirectTo)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Redirect(setting.AppSubUrl + "/")
|
func (hs *HTTPServer) loginAuthProxyUser(c *models.ReqContext) {
|
||||||
|
hs.loginUserWithUser(&models.User{
|
||||||
|
Id: c.SignedInUser.UserId,
|
||||||
|
Email: c.SignedInUser.Email,
|
||||||
|
Login: c.SignedInUser.Login,
|
||||||
|
}, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryOAuthAutoLogin(c *models.ReqContext) bool {
|
func tryOAuthAutoLogin(c *models.ReqContext) bool {
|
||||||
|
@ -10,7 +10,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/auth"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -68,8 +70,6 @@ func TestLoginErrorCookieApiEndpoint(t *testing.T) {
|
|||||||
hs.LoginView(c)
|
hs.LoginView(c)
|
||||||
})
|
})
|
||||||
|
|
||||||
setting.OAuthService = &setting.OAuther{}
|
|
||||||
setting.OAuthService.OAuthInfos = make(map[string]*setting.OAuthInfo)
|
|
||||||
setting.LoginCookieName = "grafana_session"
|
setting.LoginCookieName = "grafana_session"
|
||||||
setting.SecretKey = "login_testing"
|
setting.SecretKey = "login_testing"
|
||||||
|
|
||||||
@ -136,3 +136,59 @@ func TestLoginOAuthRedirect(t *testing.T) {
|
|||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, location[0], "/login/github")
|
assert.Equal(t, location[0], "/login/github")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthProxyLoginEnableLoginTokenDisabled(t *testing.T) {
|
||||||
|
sc := setupAuthProxyLoginTest(false)
|
||||||
|
|
||||||
|
assert.Equal(t, sc.resp.Code, 302)
|
||||||
|
location, ok := sc.resp.Header()["Location"]
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, location[0], "/")
|
||||||
|
|
||||||
|
_, ok = sc.resp.Header()["Set-Cookie"]
|
||||||
|
assert.False(t, ok, "Set-Cookie does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthProxyLoginWithEnableLoginToken(t *testing.T) {
|
||||||
|
sc := setupAuthProxyLoginTest(true)
|
||||||
|
|
||||||
|
assert.Equal(t, sc.resp.Code, 302)
|
||||||
|
location, ok := sc.resp.Header()["Location"]
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Equal(t, location[0], "/")
|
||||||
|
|
||||||
|
setCookie, ok := sc.resp.Header()["Set-Cookie"]
|
||||||
|
assert.True(t, ok, "Set-Cookie exists")
|
||||||
|
assert.Equal(t, "grafana_session=; Path=/; Max-Age=0; HttpOnly", setCookie[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupAuthProxyLoginTest(enableLoginToken bool) *scenarioContext {
|
||||||
|
mockSetIndexViewData()
|
||||||
|
defer resetSetIndexViewData()
|
||||||
|
|
||||||
|
sc := setupScenarioContext("/login")
|
||||||
|
hs := &HTTPServer{
|
||||||
|
Cfg: setting.NewCfg(),
|
||||||
|
License: models.OSSLicensingService{},
|
||||||
|
AuthTokenService: auth.NewFakeUserAuthTokenService(),
|
||||||
|
log: log.New("hello"),
|
||||||
|
}
|
||||||
|
|
||||||
|
sc.defaultHandler = Wrap(func(c *models.ReqContext) {
|
||||||
|
c.IsSignedIn = true
|
||||||
|
c.SignedInUser = &models.SignedInUser{
|
||||||
|
UserId: 10,
|
||||||
|
}
|
||||||
|
hs.LoginView(c)
|
||||||
|
})
|
||||||
|
|
||||||
|
setting.OAuthService = &setting.OAuther{}
|
||||||
|
setting.OAuthService.OAuthInfos = make(map[string]*setting.OAuthInfo)
|
||||||
|
setting.AuthProxyEnabled = true
|
||||||
|
setting.AuthProxyEnableLoginToken = enableLoginToken
|
||||||
|
|
||||||
|
sc.m.Get(sc.url, sc.defaultHandler)
|
||||||
|
sc.fakeReqNoAssertions("GET", sc.url).exec()
|
||||||
|
|
||||||
|
return sc
|
||||||
|
}
|
||||||
|
@ -8,12 +8,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
|
|
||||||
// cachePrefix is a prefix for the cache key
|
|
||||||
cachePrefix = authproxy.CachePrefix
|
|
||||||
)
|
|
||||||
|
|
||||||
var header = setting.AuthProxyHeaderName
|
var header = setting.AuthProxyHeaderName
|
||||||
|
|
||||||
func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext, orgID int64) bool {
|
func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext, orgID int64) bool {
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/api/dtos"
|
"github.com/grafana/grafana/pkg/api/dtos"
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||||
|
authproxy "github.com/grafana/grafana/pkg/middleware/auth_proxy"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/auth"
|
"github.com/grafana/grafana/pkg/services/auth"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
@ -346,7 +347,7 @@ func TestMiddlewareContext(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
key := fmt.Sprintf(cachePrefix, base32.StdEncoding.EncodeToString([]byte(name+"-"+group)))
|
key := fmt.Sprintf(authproxy.CachePrefix, base32.StdEncoding.EncodeToString([]byte(name+"-"+group)))
|
||||||
err := sc.remoteCacheService.Set(key, int64(33), 0)
|
err := sc.remoteCacheService.Set(key, int64(33), 0)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
sc.fakeReq("GET", "/")
|
sc.fakeReq("GET", "/")
|
||||||
|
@ -143,13 +143,14 @@ var (
|
|||||||
AnonymousOrgRole string
|
AnonymousOrgRole string
|
||||||
|
|
||||||
// Auth proxy settings
|
// Auth proxy settings
|
||||||
AuthProxyEnabled bool
|
AuthProxyEnabled bool
|
||||||
AuthProxyHeaderName string
|
AuthProxyHeaderName string
|
||||||
AuthProxyHeaderProperty string
|
AuthProxyHeaderProperty string
|
||||||
AuthProxyAutoSignUp bool
|
AuthProxyAutoSignUp bool
|
||||||
AuthProxySyncTtl int
|
AuthProxyEnableLoginToken bool
|
||||||
AuthProxyWhitelist string
|
AuthProxySyncTtl int
|
||||||
AuthProxyHeaders map[string]string
|
AuthProxyWhitelist string
|
||||||
|
AuthProxyHeaders map[string]string
|
||||||
|
|
||||||
// Basic Auth
|
// Basic Auth
|
||||||
BasicAuthEnabled bool
|
BasicAuthEnabled bool
|
||||||
@ -854,6 +855,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true)
|
AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true)
|
||||||
|
AuthProxyEnableLoginToken = authProxy.Key("enable_login_token").MustBool(false)
|
||||||
|
|
||||||
ldapSyncVal := authProxy.Key("ldap_sync_ttl").MustInt()
|
ldapSyncVal := authProxy.Key("ldap_sync_ttl").MustInt()
|
||||||
syncVal := authProxy.Key("sync_ttl").MustInt()
|
syncVal := authProxy.Key("sync_ttl").MustInt()
|
||||||
|
Loading…
Reference in New Issue
Block a user