mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
AuthProxy: Allow disabling Auth Proxy cache (#83755)
* extract auth proxy settings * simplify auth proxy methods * add doc mentions
This commit is contained in:
@@ -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)}
|
||||
|
||||
Reference in New Issue
Block a user