mirror of https://github.com/grafana/grafana.git
				
				
				
			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 | ||||
| whitelist = | ||||
| headers = | ||||
| enable_login_token = false | ||||
| 
 | ||||
| #################################### Auth LDAP ########################### | ||||
| [auth.ldap] | ||||
|  |  | |||
|  | @ -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] | ||||
|  |  | |||
|  | @ -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 | ||||
| # 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 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. | ||||
| 
 | ||||
| [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/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 | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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 | ||||
| } | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
|  | @ -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", "/") | ||||
|  |  | |||
|  | @ -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() | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue