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:
Torkel Ödegaard 2019-11-07 17:48:56 +01:00 committed by GitHub
parent 818aa8eefa
commit be2bf1a297
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 148 additions and 25 deletions

View File

@ -438,6 +438,7 @@ ldap_sync_ttl = 60
sync_ttl = 60
whitelist =
headers =
enable_login_token = false
#################################### Auth LDAP ###########################
[auth.ldap]

View File

@ -399,6 +399,8 @@
;ldap_sync_ttl = 60
;whitelist = 192.168.1.1, 192.168.2.1
;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 ##########################
[auth.basic]

View 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/;
}
}
}

View File

@ -36,6 +36,8 @@ whitelist =
# Optionally define more headers to sync other user attributes
# Example `headers = Name:X-WEBAUTH-NAME Email:X-WEBAUTH-EMAIL Groups:X-WEBAUTH-GROUPS`
headers =
# Checkout docs on this for more details on the below setting
enable_login_token = false
```
## Interacting with Grafanas 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.
[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
View File

@ -67,7 +67,7 @@ require (
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
github.com/yudai/pp v2.0.1+incompatible // 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/oauth2 v0.0.0-20190319182350-c85d3e98c914
golang.org/x/sync v0.0.0-20190423024810-112230192c58

View File

@ -57,9 +57,10 @@ func (hs *HTTPServer) LoginView(c *models.ReqContext) {
return
}
if !c.IsSignedIn {
c.HTML(200, ViewIndex, viewData)
return
if c.IsSignedIn {
// 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 {
@ -69,6 +70,18 @@ func (hs *HTTPServer) LoginView(c *models.ReqContext) {
}
c.Redirect(setting.AppSubUrl + "/")
return
}
c.HTML(200, ViewIndex, viewData)
}
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 {

View File

@ -10,7 +10,9 @@ import (
"testing"
"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/services/auth"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert"
@ -68,8 +70,6 @@ func TestLoginErrorCookieApiEndpoint(t *testing.T) {
hs.LoginView(c)
})
setting.OAuthService = &setting.OAuther{}
setting.OAuthService.OAuthInfos = make(map[string]*setting.OAuthInfo)
setting.LoginCookieName = "grafana_session"
setting.SecretKey = "login_testing"
@ -136,3 +136,59 @@ func TestLoginOAuthRedirect(t *testing.T) {
assert.True(t, ok)
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
}

View File

@ -8,12 +8,6 @@ import (
"github.com/grafana/grafana/pkg/setting"
)
const (
// cachePrefix is a prefix for the cache key
cachePrefix = authproxy.CachePrefix
)
var header = setting.AuthProxyHeaderName
func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext, orgID int64) bool {

View File

@ -17,6 +17,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"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/services/auth"
"github.com/grafana/grafana/pkg/services/login"
@ -346,7 +347,7 @@ func TestMiddlewareContext(t *testing.T) {
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)
So(err, ShouldBeNil)
sc.fakeReq("GET", "/")

View File

@ -147,6 +147,7 @@ var (
AuthProxyHeaderName string
AuthProxyHeaderProperty string
AuthProxyAutoSignUp bool
AuthProxyEnableLoginToken bool
AuthProxySyncTtl int
AuthProxyWhitelist string
AuthProxyHeaders map[string]string
@ -854,6 +855,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
return err
}
AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true)
AuthProxyEnableLoginToken = authProxy.Key("enable_login_token").MustBool(false)
ldapSyncVal := authProxy.Key("ldap_sync_ttl").MustInt()
syncVal := authProxy.Key("sync_ttl").MustInt()