mirror of https://github.com/grafana/grafana.git
AuthProxy: Allow disabling Auth Proxy cache (#83755)
* extract auth proxy settings * simplify auth proxy methods * add doc mentions
This commit is contained in:
parent
1cec975a66
commit
36a19bfa83
|
|
@ -832,7 +832,7 @@ enabled = false
|
|||
header_name = X-WEBAUTH-USER
|
||||
header_property = username
|
||||
auto_sign_up = true
|
||||
sync_ttl = 60
|
||||
sync_ttl = 15
|
||||
whitelist =
|
||||
headers =
|
||||
headers_encoded = false
|
||||
|
|
@ -1305,7 +1305,7 @@ loki_basic_auth_password =
|
|||
# mylabelkey = mylabelvalue
|
||||
|
||||
[unified_alerting.state_history.annotations]
|
||||
# Controls retention of annotations automatically created while evaluating alert rules.
|
||||
# Controls retention of annotations automatically created while evaluating alert rules.
|
||||
# Alert state history backend must be configured to be annotations (see setting [unified_alerting.state_history].backend).
|
||||
|
||||
# Configures how long alert annotations are stored for. Default is 0, which keeps them forever.
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ header_property = username
|
|||
auto_sign_up = true
|
||||
# Define cache time to live in minutes
|
||||
# If combined with Grafana LDAP integration it is also the sync interval
|
||||
sync_ttl = 60
|
||||
# Set to 0 to always fetch and sync the latest user data
|
||||
sync_ttl = 15
|
||||
# Limit where auth proxy requests come from by configuring a list of IP addresses.
|
||||
# This can be used to prevent users spoofing the X-WEBAUTH-USER header.
|
||||
# Example `whitelist = 192.168.1.1, 192.168.1.0/24, 2001::23, 2001::0/120`
|
||||
|
|
@ -231,6 +232,10 @@ ProxyPassReverse / http://grafana:3000/
|
|||
|
||||
With our Grafana and Apache containers running, you can now connect to http://localhost/ and log in using the username/password we created in the htpasswd file.
|
||||
|
||||
{{% admonition type="note" %}}
|
||||
If the user is deleted from Grafana, the user will be not be able to login and resync until after the `sync_ttl` has expired.
|
||||
{{% /admonition %}}
|
||||
|
||||
### Team Sync (Enterprise only)
|
||||
|
||||
> Only available in Grafana Enterprise v6.3+
|
||||
|
|
|
|||
|
|
@ -344,11 +344,11 @@ func validateJSONData(ctx context.Context, jsonData *simplejson.Json, cfg *setti
|
|||
return nil
|
||||
}
|
||||
|
||||
if cfg.AuthProxyEnabled {
|
||||
if cfg.AuthProxy.Enabled {
|
||||
for key, value := range jsonData.MustMap() {
|
||||
if strings.HasPrefix(key, datasources.CustomHeaderName) {
|
||||
header := fmt.Sprint(value)
|
||||
if http.CanonicalHeaderKey(header) == http.CanonicalHeaderKey(cfg.AuthProxyHeaderName) {
|
||||
if http.CanonicalHeaderKey(header) == http.CanonicalHeaderKey(cfg.AuthProxy.HeaderName) {
|
||||
datasourcesLogger.Error("Forbidden to add a data source header with a name equal to auth proxy header name", "headerName", key)
|
||||
return errors.New("validation error, invalid header name specified")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,10 +147,10 @@ func TestAddDataSource_InvalidJSONData(t *testing.T) {
|
|||
sc := setupScenarioContext(t, "/api/datasources")
|
||||
|
||||
hs.Cfg = setting.NewCfg()
|
||||
hs.Cfg.AuthProxyEnabled = true
|
||||
hs.Cfg.AuthProxyHeaderName = "X-AUTH-PROXY-HEADER"
|
||||
hs.Cfg.AuthProxy.Enabled = true
|
||||
hs.Cfg.AuthProxy.HeaderName = "X-AUTH-PROXY-HEADER"
|
||||
jsonData := simplejson.New()
|
||||
jsonData.Set("httpHeaderName1", hs.Cfg.AuthProxyHeaderName)
|
||||
jsonData.Set("httpHeaderName1", hs.Cfg.AuthProxy.HeaderName)
|
||||
|
||||
sc.m.Post(sc.url, routing.Wrap(func(c *contextmodel.ReqContext) response.Response {
|
||||
c.Req.Body = mockRequestBody(datasources.AddDataSourceCommand{
|
||||
|
|
@ -201,10 +201,10 @@ func TestUpdateDataSource_InvalidJSONData(t *testing.T) {
|
|||
}
|
||||
sc := setupScenarioContext(t, "/api/datasources/1234")
|
||||
|
||||
hs.Cfg.AuthProxyEnabled = true
|
||||
hs.Cfg.AuthProxyHeaderName = "X-AUTH-PROXY-HEADER"
|
||||
hs.Cfg.AuthProxy.Enabled = true
|
||||
hs.Cfg.AuthProxy.HeaderName = "X-AUTH-PROXY-HEADER"
|
||||
jsonData := simplejson.New()
|
||||
jsonData.Set("httpHeaderName1", hs.Cfg.AuthProxyHeaderName)
|
||||
jsonData.Set("httpHeaderName1", hs.Cfg.AuthProxy.HeaderName)
|
||||
|
||||
sc.m.Put(sc.url, routing.Wrap(func(c *contextmodel.ReqContext) response.Response {
|
||||
c.Req.Body = mockRequestBody(datasources.AddDataSourceCommand{
|
||||
|
|
@ -297,7 +297,7 @@ func TestUpdateDataSourceTeamHTTPHeaders_InvalidJSONData(t *testing.T) {
|
|||
},
|
||||
}
|
||||
sc := setupScenarioContext(t, fmt.Sprintf("/api/datasources/%s", tenantID))
|
||||
hs.Cfg.AuthProxyEnabled = true
|
||||
hs.Cfg.AuthProxy.Enabled = true
|
||||
|
||||
jsonData := simplejson.New()
|
||||
jsonData.Set("teamHttpHeaders", tc.data)
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
|||
AppUrl: hs.Cfg.AppURL,
|
||||
AppSubUrl: hs.Cfg.AppSubURL,
|
||||
AllowOrgCreate: (hs.Cfg.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
|
||||
AuthProxyEnabled: hs.Cfg.AuthProxyEnabled,
|
||||
AuthProxyEnabled: hs.Cfg.AuthProxy.Enabled,
|
||||
LdapEnabled: hs.Cfg.LDAPAuthEnabled,
|
||||
JwtHeaderName: hs.Cfg.JWTAuth.HeaderName,
|
||||
JwtUrlLogin: hs.Cfg.JWTAuth.URLLogin,
|
||||
|
|
@ -322,7 +322,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
|
|||
|
||||
oauthProviders := hs.SocialService.GetOAuthInfoProviders()
|
||||
frontendSettings.Auth = dtos.FrontendSettingsAuthDTO{
|
||||
AuthProxyEnableLoginToken: hs.Cfg.AuthProxyEnableLoginToken,
|
||||
AuthProxyEnableLoginToken: hs.Cfg.AuthProxy.EnableLoginToken,
|
||||
OAuthSkipOrgRoleUpdateSync: hs.Cfg.OAuthSkipOrgRoleUpdateSync,
|
||||
SAMLSkipOrgRoleSync: hs.Cfg.SAMLSkipOrgRoleSync,
|
||||
LDAPSkipOrgRoleSync: hs.Cfg.LDAPSkipOrgRoleSync,
|
||||
|
|
|
|||
|
|
@ -125,8 +125,8 @@ func (hs *HTTPServer) LoginView(c *contextmodel.ReqContext) {
|
|||
|
||||
if c.IsSignedIn {
|
||||
// Assign login token to auth proxy users if enable_login_token = true
|
||||
if hs.Cfg.AuthProxyEnabled &&
|
||||
hs.Cfg.AuthProxyEnableLoginToken &&
|
||||
if hs.Cfg.AuthProxy.Enabled &&
|
||||
hs.Cfg.AuthProxy.EnableLoginToken &&
|
||||
c.SignedInUser.AuthenticatedBy == loginservice.AuthProxyAuthModule {
|
||||
user := &user.User{ID: c.SignedInUser.UserID, Email: c.SignedInUser.Email, Login: c.SignedInUser.Login}
|
||||
err := hs.loginUserWithUser(user, c)
|
||||
|
|
|
|||
|
|
@ -600,8 +600,8 @@ func TestAuthProxyLoginWithEnableLoginTokenAndEnabledOauthAutoLogin(t *testing.T
|
|||
return response.Empty(http.StatusOK)
|
||||
})
|
||||
|
||||
sc.cfg.AuthProxyEnabled = true
|
||||
sc.cfg.AuthProxyEnableLoginToken = true
|
||||
sc.cfg.AuthProxy.Enabled = true
|
||||
sc.cfg.AuthProxy.EnableLoginToken = true
|
||||
|
||||
sc.m.Get(sc.url, sc.defaultHandler)
|
||||
sc.fakeReqNoAssertions("GET", sc.url).exec()
|
||||
|
|
@ -640,8 +640,8 @@ func setupAuthProxyLoginTest(t *testing.T, enableLoginToken bool) *scenarioConte
|
|||
return response.Empty(http.StatusOK)
|
||||
})
|
||||
|
||||
sc.cfg.AuthProxyEnabled = true
|
||||
sc.cfg.AuthProxyEnableLoginToken = enableLoginToken
|
||||
sc.cfg.AuthProxy.Enabled = true
|
||||
sc.cfg.AuthProxy.EnableLoginToken = enableLoginToken
|
||||
|
||||
sc.m.Get(sc.url, sc.defaultHandler)
|
||||
sc.fakeReqNoAssertions("GET", sc.url).exec()
|
||||
|
|
|
|||
|
|
@ -147,11 +147,11 @@ func (hs *HTTPServer) UpdateSignedInUser(c *contextmodel.ReqContext) response.Re
|
|||
return errResponse
|
||||
}
|
||||
|
||||
if hs.Cfg.AuthProxyEnabled {
|
||||
if hs.Cfg.AuthProxyHeaderProperty == "email" && cmd.Email != c.SignedInUser.GetEmail() {
|
||||
if hs.Cfg.AuthProxy.Enabled {
|
||||
if hs.Cfg.AuthProxy.HeaderProperty == "email" && cmd.Email != c.SignedInUser.GetEmail() {
|
||||
return response.Error(http.StatusBadRequest, "Not allowed to change email when auth proxy is using email property", nil)
|
||||
}
|
||||
if hs.Cfg.AuthProxyHeaderProperty == "username" && cmd.Login != c.SignedInUser.GetLogin() {
|
||||
if hs.Cfg.AuthProxy.HeaderProperty == "username" && cmd.Login != c.SignedInUser.GetLogin() {
|
||||
return response.Error(http.StatusBadRequest, "Not allowed to change username when auth proxy is using username property", nil)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ func TestMetrics(t *testing.T) {
|
|||
AnonymousEnabled: true,
|
||||
BasicAuthEnabled: true,
|
||||
LDAPAuthEnabled: true,
|
||||
AuthProxyEnabled: true,
|
||||
AuthProxy: setting.AuthProxySettings{Enabled: true},
|
||||
Packaging: "deb",
|
||||
ReportingDistributor: "hosted-grafana",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ func TestCollectingUsageStats(t *testing.T) {
|
|||
AnonymousEnabled: true,
|
||||
BasicAuthEnabled: true,
|
||||
LDAPAuthEnabled: true,
|
||||
AuthProxyEnabled: true,
|
||||
AuthProxy: setting.AuthProxySettings{Enabled: true},
|
||||
Packaging: "deb",
|
||||
ReportingDistributor: "hosted-grafana",
|
||||
RemoteCacheOptions: &setting.RemoteCacheOptions{
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ func ProvideService(
|
|||
}
|
||||
}
|
||||
|
||||
if s.cfg.AuthProxyEnabled && len(proxyClients) > 0 {
|
||||
if s.cfg.AuthProxy.Enabled && len(proxyClients) > 0 {
|
||||
proxy, err := clients.ProvideProxy(cfg, cache, proxyClients...)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to configure auth proxy", "err", err)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ func (s *Service) getUsageStats(ctx context.Context) (map[string]any, error) {
|
|||
authTypes := map[string]bool{}
|
||||
authTypes["basic_auth"] = s.cfg.BasicAuthEnabled
|
||||
authTypes["ldap"] = s.cfg.LDAPAuthEnabled
|
||||
authTypes["auth_proxy"] = s.cfg.AuthProxyEnabled
|
||||
authTypes["auth_proxy"] = s.cfg.AuthProxy.Enabled
|
||||
authTypes["anonymous"] = s.cfg.AnonymousEnabled
|
||||
authTypes["jwt"] = s.cfg.JWTAuth.Enabled
|
||||
authTypes["grafana_password"] = !s.cfg.DisableLogin
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func TestService_getUsageStats(t *testing.T) {
|
|||
svc.cfg.DisableLoginForm = false
|
||||
svc.cfg.DisableLogin = false
|
||||
svc.cfg.BasicAuthEnabled = true
|
||||
svc.cfg.AuthProxyEnabled = true
|
||||
svc.cfg.AuthProxy.Enabled = true
|
||||
svc.cfg.JWTAuth.Enabled = true
|
||||
svc.cfg.LDAPAuthEnabled = true
|
||||
svc.cfg.EditorsCanAdmin = true
|
||||
|
|
|
|||
|
|
@ -40,11 +40,11 @@ func (c *Grafana) AuthenticateProxy(ctx context.Context, r *authn.Request, usern
|
|||
FetchSyncedUser: true,
|
||||
SyncOrgRoles: true,
|
||||
SyncPermissions: true,
|
||||
AllowSignUp: c.cfg.AuthProxyAutoSignUp,
|
||||
AllowSignUp: c.cfg.AuthProxy.AutoSignUp,
|
||||
},
|
||||
}
|
||||
|
||||
switch c.cfg.AuthProxyHeaderProperty {
|
||||
switch c.cfg.AuthProxy.HeaderProperty {
|
||||
case "username":
|
||||
identity.Login = username
|
||||
addr, err := mail.ParseAddress(username)
|
||||
|
|
@ -55,7 +55,7 @@ func (c *Grafana) AuthenticateProxy(ctx context.Context, r *authn.Request, usern
|
|||
identity.Login = username
|
||||
identity.Email = username
|
||||
default:
|
||||
return nil, errInvalidProxyHeader.Errorf("invalid auth proxy header property, expected username or email but got: %s", c.cfg.AuthProxyHeaderProperty)
|
||||
return nil, errInvalidProxyHeader.Errorf("invalid auth proxy header property, expected username or email but got: %s", c.cfg.AuthProxy.HeaderProperty)
|
||||
}
|
||||
|
||||
if v, ok := additional[proxyFieldName]; ok {
|
||||
|
|
|
|||
|
|
@ -94,8 +94,8 @@ func TestGrafana_AuthenticateProxy(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.AuthProxyAutoSignUp = true
|
||||
cfg.AuthProxyHeaderProperty = tt.proxyProperty
|
||||
cfg.AuthProxy.AutoSignUp = true
|
||||
cfg.AuthProxy.HeaderProperty = tt.proxyProperty
|
||||
c := ProvideGrafana(cfg, usertest.NewUserServiceFake())
|
||||
|
||||
identity, err := c.AuthenticateProxy(context.Background(), tt.req, tt.username, tt.additional)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package clients
|
|||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"net"
|
||||
|
|
@ -12,6 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||
authidentity "github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
"github.com/grafana/grafana/pkg/services/authn"
|
||||
"github.com/grafana/grafana/pkg/services/login"
|
||||
|
|
@ -43,7 +45,7 @@ var (
|
|||
)
|
||||
|
||||
func ProvideProxy(cfg *setting.Cfg, cache proxyCache, clients ...authn.ProxyClient) (*Proxy, error) {
|
||||
list, err := parseAcceptList(cfg.AuthProxyWhitelist)
|
||||
list, err := parseAcceptList(cfg.AuthProxy.Whitelist)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -73,34 +75,22 @@ func (c *Proxy) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden
|
|||
return nil, errNotAcceptedIP.Errorf("request ip is not in the configured accept list")
|
||||
}
|
||||
|
||||
username := getProxyHeader(r, c.cfg.AuthProxyHeaderName, c.cfg.AuthProxyHeadersEncoded)
|
||||
username := getProxyHeader(r, c.cfg.AuthProxy.HeaderName, c.cfg.AuthProxy.HeadersEncoded)
|
||||
if len(username) == 0 {
|
||||
return nil, errEmptyProxyHeader.Errorf("no username provided in auth proxy header")
|
||||
}
|
||||
|
||||
additional := getAdditionalProxyHeaders(r, c.cfg)
|
||||
|
||||
cacheKey, ok := getProxyCacheKey(username, additional)
|
||||
if ok {
|
||||
// See if we have cached the user id, in that case we can fetch the signed-in user and skip sync.
|
||||
// Error here means that we could not find anything in cache, so we can proceed as usual
|
||||
if entry, err := c.cache.Get(ctx, cacheKey); err == nil {
|
||||
uid, err := strconv.ParseInt(string(entry), 10, 64)
|
||||
if err != nil {
|
||||
c.log.FromContext(ctx).Warn("Failed to parse user id from cache", "error", err, "userId", string(entry))
|
||||
} else {
|
||||
return &authn.Identity{
|
||||
ID: authn.NamespacedID(authn.NamespaceUser, uid),
|
||||
OrgID: r.OrgID,
|
||||
// FIXME: This does not match the actual auth module used, but should not have any impact
|
||||
// Maybe caching the auth module used with the user ID would be a good idea
|
||||
AuthenticatedBy: login.AuthProxyAuthModule,
|
||||
ClientParams: authn.ClientParams{
|
||||
FetchSyncedUser: true,
|
||||
SyncPermissions: true,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
if c.cfg.AuthProxy.SyncTTL != 0 && ok {
|
||||
identity, errCache := c.retrieveIDFromCache(ctx, cacheKey, r)
|
||||
if errCache == nil {
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
if !errors.Is(errCache, remotecache.ErrCacheItemNotFound) {
|
||||
c.log.FromContext(ctx).Warn("Failed to fetch auth proxy info from cache", "error", errCache)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -117,8 +107,34 @@ func (c *Proxy) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden
|
|||
return nil, clientErr
|
||||
}
|
||||
|
||||
// See if we have cached the user id, in that case we can fetch the signed-in user and skip sync.
|
||||
// Error here means that we could not find anything in cache, so we can proceed as usual
|
||||
func (c *Proxy) retrieveIDFromCache(ctx context.Context, cacheKey string, r *authn.Request) (*authn.Identity, error) {
|
||||
entry, err := c.cache.Get(ctx, cacheKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uid, err := strconv.ParseInt(string(entry), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse user id from cache: %w - entry: %s", err, string(entry))
|
||||
}
|
||||
|
||||
return &authn.Identity{
|
||||
ID: authn.NamespacedID(authn.NamespaceUser, uid),
|
||||
OrgID: r.OrgID,
|
||||
// FIXME: This does not match the actual auth module used, but should not have any impact
|
||||
// Maybe caching the auth module used with the user ID would be a good idea
|
||||
AuthenticatedBy: login.AuthProxyAuthModule,
|
||||
ClientParams: authn.ClientParams{
|
||||
FetchSyncedUser: true,
|
||||
SyncPermissions: true,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Proxy) Test(ctx context.Context, r *authn.Request) bool {
|
||||
return len(getProxyHeader(r, c.cfg.AuthProxyHeaderName, c.cfg.AuthProxyHeadersEncoded)) != 0
|
||||
return len(getProxyHeader(r, c.cfg.AuthProxy.HeaderName, c.cfg.AuthProxy.HeadersEncoded)) != 0
|
||||
}
|
||||
|
||||
func (c *Proxy) Priority() uint {
|
||||
|
|
@ -147,7 +163,7 @@ func (c *Proxy) Hook(ctx context.Context, identity *authn.Identity, r *authn.Req
|
|||
// 3. Name = x; Role = Admin # cache hit with key Name=x;Role=Admin, no update, the user stays with Role=Editor
|
||||
// To avoid such a problem we also cache the key used using `prefix:[username]`.
|
||||
// Then whenever we get a cache miss due to changes in any header we use it to invalidate the previous item.
|
||||
username := getProxyHeader(r, c.cfg.AuthProxyHeaderName, c.cfg.AuthProxyHeadersEncoded)
|
||||
username := getProxyHeader(r, c.cfg.AuthProxy.HeaderName, c.cfg.AuthProxy.HeadersEncoded)
|
||||
userKey := fmt.Sprintf("%s:%s", proxyCachePrefix, username)
|
||||
|
||||
// invalidate previously cached user id
|
||||
|
|
@ -159,7 +175,7 @@ func (c *Proxy) Hook(ctx context.Context, identity *authn.Identity, r *authn.Req
|
|||
|
||||
c.log.FromContext(ctx).Debug("Cache proxy user", "userId", id)
|
||||
bytes := []byte(strconv.FormatInt(id, 10))
|
||||
duration := time.Duration(c.cfg.AuthProxySyncTTL) * time.Minute
|
||||
duration := time.Duration(c.cfg.AuthProxy.SyncTTL) * time.Minute
|
||||
if err := c.cache.Set(ctx, identity.ClientParams.CacheAuthProxyKey, bytes, duration); err != nil {
|
||||
c.log.Warn("Failed to cache proxy user", "error", err, "userId", id)
|
||||
}
|
||||
|
|
@ -232,7 +248,7 @@ func getProxyHeader(r *authn.Request, headerName string, encoded bool) string {
|
|||
func getAdditionalProxyHeaders(r *authn.Request, cfg *setting.Cfg) map[string]string {
|
||||
additional := make(map[string]string, len(proxyFields))
|
||||
for _, k := range proxyFields {
|
||||
if v := getProxyHeader(r, cfg.AuthProxyHeaders[k], cfg.AuthProxyHeadersEncoded); v != "" {
|
||||
if v := getProxyHeader(r, cfg.AuthProxy.Headers[k], cfg.AuthProxy.HeadersEncoded); v != "" {
|
||||
additional[k] = v
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,9 +100,9 @@ func TestProxy_Authenticate(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.AuthProxyHeaderName = "X-Username"
|
||||
cfg.AuthProxyHeaders = tt.proxyHeaders
|
||||
cfg.AuthProxyWhitelist = tt.ips
|
||||
cfg.AuthProxy.HeaderName = "X-Username"
|
||||
cfg.AuthProxy.Headers = tt.proxyHeaders
|
||||
cfg.AuthProxy.Whitelist = tt.ips
|
||||
|
||||
calledUsername := ""
|
||||
var calledAdditional map[string]string
|
||||
|
|
@ -166,7 +166,7 @@ func TestProxy_Test(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.AuthProxyHeaderName = "Proxy-Header"
|
||||
cfg.AuthProxy.HeaderName = "Proxy-Header"
|
||||
|
||||
c, _ := ProvideProxy(cfg, nil, nil, nil)
|
||||
assert.Equal(t, tt.expectedOK, c.Test(context.Background(), tt.req))
|
||||
|
|
@ -197,8 +197,8 @@ func (f fakeCache) Delete(ctx context.Context, key string) error {
|
|||
|
||||
func TestProxy_Hook(t *testing.T) {
|
||||
cfg := setting.NewCfg()
|
||||
cfg.AuthProxyHeaderName = "X-Username"
|
||||
cfg.AuthProxyHeaders = map[string]string{
|
||||
cfg.AuthProxy.HeaderName = "X-Username"
|
||||
cfg.AuthProxy.Headers = map[string]string{
|
||||
proxyFieldRole: "X-Role",
|
||||
}
|
||||
cache := &fakeCache{data: make(map[string][]byte)}
|
||||
|
|
|
|||
|
|
@ -195,9 +195,9 @@ func WithAuthHTTPHeaders(ctx context.Context, cfg *setting.Cfg) context.Context
|
|||
}
|
||||
|
||||
// if auth proxy is enabled add the main proxy header and all configured headers
|
||||
if cfg.AuthProxyEnabled {
|
||||
list.Items = append(list.Items, cfg.AuthProxyHeaderName)
|
||||
for _, header := range cfg.AuthProxyHeaders {
|
||||
if cfg.AuthProxy.Enabled {
|
||||
list.Items = append(list.Items, cfg.AuthProxy.HeaderName)
|
||||
for _, header := range cfg.AuthProxy.Headers {
|
||||
if header != "" {
|
||||
list.Items = append(list.Items, header)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,9 +114,9 @@ func TestContextHandler(t *testing.T) {
|
|||
cfg := setting.NewCfg()
|
||||
cfg.JWTAuth.Enabled = true
|
||||
cfg.JWTAuth.HeaderName = "jwt-header"
|
||||
cfg.AuthProxyEnabled = true
|
||||
cfg.AuthProxyHeaderName = "proxy-header"
|
||||
cfg.AuthProxyHeaders = map[string]string{
|
||||
cfg.AuthProxy.Enabled = true
|
||||
cfg.AuthProxy.HeaderName = "proxy-header"
|
||||
cfg.AuthProxy.Headers = map[string]string{
|
||||
"name": "proxy-header-name",
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -675,7 +675,7 @@ func (s *Service) getCustomHeaders(jsonData *simplejson.Json, decryptedValues ma
|
|||
// skip a header with name that corresponds to auth proxy header's name
|
||||
// to make sure that data source proxy isn't used to circumvent auth proxy.
|
||||
// For more context take a look at CVE-2022-35957
|
||||
if s.cfg.AuthProxyEnabled && http.CanonicalHeaderKey(key) == http.CanonicalHeaderKey(s.cfg.AuthProxyHeaderName) {
|
||||
if s.cfg.AuthProxy.Enabled && http.CanonicalHeaderKey(key) == http.CanonicalHeaderKey(s.cfg.AuthProxy.HeaderName) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -256,15 +256,7 @@ type Cfg struct {
|
|||
Azure *azsettings.AzureSettings
|
||||
|
||||
// Auth proxy settings
|
||||
AuthProxyEnabled bool
|
||||
AuthProxyHeaderName string
|
||||
AuthProxyHeaderProperty string
|
||||
AuthProxyAutoSignUp bool
|
||||
AuthProxyEnableLoginToken bool
|
||||
AuthProxyWhitelist string
|
||||
AuthProxyHeaders map[string]string
|
||||
AuthProxyHeadersEncoded bool
|
||||
AuthProxySyncTTL int
|
||||
AuthProxy AuthProxySettings
|
||||
|
||||
// OAuth
|
||||
OAuthAutoLogin bool
|
||||
|
|
@ -1197,6 +1189,7 @@ func (cfg *Cfg) parseINIFile(iniFile *ini.File) error {
|
|||
cfg.handleAWSConfig()
|
||||
cfg.readAzureSettings()
|
||||
cfg.readAuthJWTSettings()
|
||||
cfg.readAuthProxySettings()
|
||||
cfg.readSessionConfig()
|
||||
if err := cfg.readSmtpSettings(); err != nil {
|
||||
return err
|
||||
|
|
@ -1617,31 +1610,6 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) {
|
|||
cfg.ExtendedJWTExpectAudience = authExtendedJWT.Key("expect_audience").MustString("")
|
||||
cfg.ExtendedJWTExpectIssuer = authExtendedJWT.Key("expect_issuer").MustString("")
|
||||
|
||||
// Auth Proxy
|
||||
authProxy := iniFile.Section("auth.proxy")
|
||||
cfg.AuthProxyEnabled = authProxy.Key("enabled").MustBool(false)
|
||||
|
||||
cfg.AuthProxyHeaderName = valueAsString(authProxy, "header_name", "")
|
||||
cfg.AuthProxyHeaderProperty = valueAsString(authProxy, "header_property", "")
|
||||
cfg.AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true)
|
||||
cfg.AuthProxyEnableLoginToken = authProxy.Key("enable_login_token").MustBool(false)
|
||||
|
||||
cfg.AuthProxySyncTTL = authProxy.Key("sync_ttl").MustInt()
|
||||
|
||||
cfg.AuthProxyWhitelist = valueAsString(authProxy, "whitelist", "")
|
||||
|
||||
cfg.AuthProxyHeaders = make(map[string]string)
|
||||
headers := valueAsString(authProxy, "headers", "")
|
||||
|
||||
for _, propertyAndHeader := range util.SplitString(headers) {
|
||||
split := strings.SplitN(propertyAndHeader, ":", 2)
|
||||
if len(split) == 2 {
|
||||
cfg.AuthProxyHeaders[split[0]] = split[1]
|
||||
}
|
||||
}
|
||||
|
||||
cfg.AuthProxyHeadersEncoded = authProxy.Key("headers_encoded").MustBool(false)
|
||||
|
||||
// SSO Settings
|
||||
ssoSettings := iniFile.Section("sso_settings")
|
||||
cfg.SSOSettingsReloadInterval = ssoSettings.Key("reload_interval").MustDuration(1 * time.Minute)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
package setting
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
type AuthProxySettings struct {
|
||||
// Auth Proxy
|
||||
Enabled bool
|
||||
HeaderName string
|
||||
HeaderProperty string
|
||||
AutoSignUp bool
|
||||
EnableLoginToken bool
|
||||
Whitelist string
|
||||
Headers map[string]string
|
||||
HeadersEncoded bool
|
||||
SyncTTL int
|
||||
}
|
||||
|
||||
func (cfg *Cfg) readAuthProxySettings() {
|
||||
authProxySettings := AuthProxySettings{}
|
||||
authProxy := cfg.Raw.Section("auth.proxy")
|
||||
authProxySettings.Enabled = authProxy.Key("enabled").MustBool(false)
|
||||
authProxySettings.HeaderName = valueAsString(authProxy, "header_name", "")
|
||||
authProxySettings.HeaderProperty = valueAsString(authProxy, "header_property", "")
|
||||
authProxySettings.AutoSignUp = authProxy.Key("auto_sign_up").MustBool(true)
|
||||
authProxySettings.EnableLoginToken = authProxy.Key("enable_login_token").MustBool(false)
|
||||
authProxySettings.SyncTTL = authProxy.Key("sync_ttl").MustInt(15)
|
||||
authProxySettings.Whitelist = valueAsString(authProxy, "whitelist", "")
|
||||
authProxySettings.Headers = make(map[string]string)
|
||||
headers := valueAsString(authProxy, "headers", "")
|
||||
|
||||
for _, propertyAndHeader := range util.SplitString(headers) {
|
||||
split := strings.SplitN(propertyAndHeader, ":", 2)
|
||||
if len(split) == 2 {
|
||||
authProxySettings.Headers[split[0]] = split[1]
|
||||
}
|
||||
}
|
||||
|
||||
authProxySettings.HeadersEncoded = authProxy.Key("headers_encoded").MustBool(false)
|
||||
|
||||
cfg.AuthProxy = authProxySettings
|
||||
}
|
||||
|
|
@ -13,11 +13,12 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/util/osutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/util/osutil"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -274,7 +275,7 @@ func TestLoadingSettings(t *testing.T) {
|
|||
})
|
||||
require.Nil(t, err)
|
||||
|
||||
require.Equal(t, 2, cfg.AuthProxySyncTTL)
|
||||
require.Equal(t, 2, cfg.AuthProxy.SyncTTL)
|
||||
})
|
||||
|
||||
t.Run("Test reading string values from .ini file", func(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue