diff --git a/pkg/services/authn/grpcutils/grpc_authenticator.go b/pkg/services/authn/grpcutils/grpc_authenticator.go index bc718b5cc9a..bb3256955a1 100644 --- a/pkg/services/authn/grpcutils/grpc_authenticator.go +++ b/pkg/services/authn/grpcutils/grpc_authenticator.go @@ -66,6 +66,8 @@ func NewGrpcAuthenticator(cfg *setting.Cfg, tracer tracing.Tracer) (*authnlib.Gr ) } +type contextFallbackKey struct{} + type AuthenticatorWithFallback struct { authenticator *authnlib.GrpcAuthenticator fallback interceptors.Authenticator @@ -96,6 +98,10 @@ func NewGrpcAuthenticatorWithFallback(cfg *setting.Cfg, reg prometheus.Registere }, nil } +func FallbackUsed(ctx context.Context) bool { + return ctx.Value(contextFallbackKey{}) != nil +} + func (f *AuthenticatorWithFallback) Authenticate(ctx context.Context) (context.Context, error) { ctx, span := f.tracer.Start(ctx, "grpcutils.AuthenticatorWithFallback.Authenticate") defer span.End() @@ -107,6 +113,7 @@ func (f *AuthenticatorWithFallback) Authenticate(ctx context.Context) (context.C newCtx, err = f.fallback.Authenticate(ctx) f.metrics.fallbackCounter.WithLabelValues(fmt.Sprintf("%t", err == nil)).Inc() span.SetAttributes(attribute.Bool("fallback_used", true)) + newCtx = context.WithValue(newCtx, contextFallbackKey{}, true) } return newCtx, err } diff --git a/pkg/storage/unified/resource/access.go b/pkg/storage/unified/resource/access.go index 97781a7c9d8..97a8082373e 100644 --- a/pkg/storage/unified/resource/access.go +++ b/pkg/storage/unified/resource/access.go @@ -7,6 +7,10 @@ import ( "github.com/grafana/authlib/authz" "github.com/grafana/authlib/claims" + "github.com/grafana/grafana/pkg/services/authn/grpcutils" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" ) type staticAuthzClient struct { @@ -38,11 +42,19 @@ type authzLimitedClient struct { // whitelist is a map of group to resources that are compatible with RBAC. whitelist groupResource logger *slog.Logger + tracer trace.Tracer +} + +type AuthzOptions struct { + Tracer trace.Tracer } // NewAuthzLimitedClient creates a new authzLimitedClient. -func NewAuthzLimitedClient(client authz.AccessChecker) authz.AccessClient { +func NewAuthzLimitedClient(client authz.AccessChecker, opts AuthzOptions) authz.AccessClient { logger := slog.Default().With("logger", "limited-authz-client") + if opts.Tracer == nil { + opts.Tracer = noop.NewTracerProvider().Tracer("limited-authz-client") + } return &authzLimitedClient{ client: client, whitelist: groupResource{ @@ -50,31 +62,57 @@ func NewAuthzLimitedClient(client authz.AccessChecker) authz.AccessClient { "folder.grafana.app": map[string]interface{}{"folders": nil}, }, logger: logger, + tracer: opts.Tracer, } } // Check implements authz.AccessClient. func (c authzLimitedClient) Check(ctx context.Context, id claims.AuthInfo, req authz.CheckRequest) (authz.CheckResponse, error) { + ctx, span := c.tracer.Start(ctx, "authzLimitedClient.Check", trace.WithAttributes( + attribute.String("group", req.Group), + attribute.String("resource", req.Resource), + attribute.Bool("fallback", grpcutils.FallbackUsed(ctx)), + )) + defer span.End() + if grpcutils.FallbackUsed(ctx) { + c.logger.Debug("Check", "group", req.Group, "resource", req.Resource, "fallback", true, "rbac", false, "allowed", true) + return authz.CheckResponse{Allowed: true}, nil + } if !c.IsCompatibleWithRBAC(req.Group, req.Resource) { - c.logger.Debug("Check", "group", req.Group, "resource", req.Resource, "rbac", false, "allowed", true) + c.logger.Debug("Check", "group", req.Group, "resource", req.Resource, "fallback", false, "rbac", false, "allowed", true) return authz.CheckResponse{Allowed: true}, nil } t := time.Now() resp, err := c.client.Check(ctx, id, req) if err != nil { - c.logger.Error("Check", "group", req.Group, "resource", req.Resource, "rbac", true, "error", err, "duration", time.Since(t)) + c.logger.Error("Check", "group", req.Group, "resource", req.Resource, "fallback", false, "rbac", true, "error", err, "duration", time.Since(t)) return resp, err } - c.logger.Debug("Check", "group", req.Group, "resource", req.Resource, "rbac", true, "allowed", resp.Allowed, "duration", time.Since(t)) + c.logger.Debug("Check", "group", req.Group, "resource", req.Resource, "fallback", false, "rbac", true, "allowed", resp.Allowed, "duration", time.Since(t)) return resp, nil } // Compile implements authz.AccessClient. func (c authzLimitedClient) Compile(ctx context.Context, id claims.AuthInfo, req authz.ListRequest) (authz.ItemChecker, error) { + ctx, span := c.tracer.Start(ctx, "authzLimitedClient.Compile", trace.WithAttributes( + attribute.String("group", req.Group), + attribute.String("resource", req.Resource), + )) + defer span.End() return func(namespace string, name, folder string) bool { + ctx, span := c.tracer.Start(ctx, "authzLimitedClient.Compile.Check", trace.WithAttributes( + attribute.String("group", req.Group), + attribute.String("resource", req.Resource), + attribute.Bool("fallback", grpcutils.FallbackUsed(ctx)), + )) + defer span.End() + if grpcutils.FallbackUsed(ctx) { + c.logger.Debug("Check", "group", req.Group, "resource", req.Resource, "fallback", true, "rbac", false, "allowed", true) + return true + } // TODO: Implement For now we perform the check for each item. if !c.IsCompatibleWithRBAC(req.Group, req.Resource) { - c.logger.Debug("Compile.Check", "group", req.Group, "resource", req.Resource, "namespace", namespace, "name", name, "folder", folder, "rbac", false, "allowed", true) + c.logger.Debug("Compile.Check", "group", req.Group, "resource", req.Resource, "namespace", namespace, "name", name, "folder", folder, "fallback", false, "rbac", false, "allowed", true) return true } t := time.Now() @@ -87,10 +125,10 @@ func (c authzLimitedClient) Compile(ctx context.Context, id claims.AuthInfo, req Folder: folder, }) if err != nil { - c.logger.Error("Compile.Check", "group", req.Group, "resource", req.Resource, "namespace", namespace, "name", name, "folder", folder, "rbac", true, "error", err, "duration", time.Since(t)) + c.logger.Error("Compile.Check", "group", req.Group, "resource", req.Resource, "namespace", namespace, "name", name, "folder", folder, "fallback", false, "rbac", true, "error", err, "duration", time.Since(t)) return false } - c.logger.Debug("Compile.Check", "group", req.Group, "resource", req.Resource, "namespace", namespace, "name", name, "folder", folder, "rbac", true, "allowed", r.Allowed, "duration", time.Since(t)) + c.logger.Debug("Compile.Check", "group", req.Group, "resource", req.Resource, "namespace", namespace, "name", name, "folder", folder, "fallback", false, "rbac", true, "allowed", r.Allowed, "duration", time.Since(t)) return r.Allowed }, nil } diff --git a/pkg/storage/unified/resource/access_test.go b/pkg/storage/unified/resource/access_test.go index 129dd28f696..95192bd80c8 100644 --- a/pkg/storage/unified/resource/access_test.go +++ b/pkg/storage/unified/resource/access_test.go @@ -10,7 +10,7 @@ import ( func TestAuthzLimitedClient_Check(t *testing.T) { mockClient := &staticAuthzClient{allowed: false} - client := NewAuthzLimitedClient(mockClient) + client := NewAuthzLimitedClient(mockClient, AuthzOptions{}) tests := []struct { group string @@ -35,7 +35,7 @@ func TestAuthzLimitedClient_Check(t *testing.T) { func TestAuthzLimitedClient_Compile(t *testing.T) { mockClient := &staticAuthzClient{allowed: false} - client := NewAuthzLimitedClient(mockClient) + client := NewAuthzLimitedClient(mockClient, AuthzOptions{}) tests := []struct { group string diff --git a/pkg/storage/unified/sql/server.go b/pkg/storage/unified/sql/server.go index 796793f2a79..60e604e9209 100644 --- a/pkg/storage/unified/sql/server.go +++ b/pkg/storage/unified/sql/server.go @@ -31,7 +31,7 @@ func NewResourceServer(ctx context.Context, db infraDB.DB, cfg *setting.Cfg, Reg: reg, } if ac != nil { - opts.AccessClient = resource.NewAuthzLimitedClient(ac) + opts.AccessClient = resource.NewAuthzLimitedClient(ac, resource.AuthzOptions{Tracer: tracer}) } // Support local file blob if strings.HasPrefix(opts.Blob.URL, "./data/") {