From 028e8ac59e9fad0d72654e85ad53a7f90196cfd2 Mon Sep 17 00:00:00 2001 From: Jeff Levin Date: Fri, 16 Aug 2024 14:08:19 -0800 Subject: [PATCH] Instrument tracing across accesscontrol (#91864) Instrument tracing across accesscontrol --------- Co-authored-by: Dave Henderson --- pkg/api/ds_query_test.go | 60 +++++++++++++++---- pkg/api/plugin_resource_test.go | 11 ++-- .../accesscontrol/acimpl/accesscontrol.go | 17 ++++++ pkg/services/accesscontrol/acimpl/service.go | 46 +++++++------- .../accesscontrol/acimpl/service_test.go | 1 - pkg/services/accesscontrol/api/api.go | 20 +++++-- .../accesscontrol/database/database.go | 21 +++++++ .../database/externalservices.go | 24 ++++++++ .../accesscontrol/database/policies.go | 9 ++- pkg/services/accesscontrol/evaluator.go | 9 +++ pkg/services/accesscontrol/metadata.go | 3 + pkg/services/accesscontrol/middleware.go | 16 ++++- .../accesscontrol/migrator/zanzana.go | 15 +++++ .../ossaccesscontrol/dashboard.go | 3 + .../ossaccesscontrol/datasource.go | 9 +++ .../accesscontrol/ossaccesscontrol/folder.go | 3 + .../ossaccesscontrol/service_account.go | 3 + .../accesscontrol/ossaccesscontrol/team.go | 5 +- pkg/services/accesscontrol/resolvers.go | 3 + .../accesscontrol/resourcepermissions/api.go | 24 +++++++- .../resourcepermissions/service.go | 32 +++++++++- .../resourcepermissions/store.go | 18 ++++++ 22 files changed, 301 insertions(+), 51 deletions(-) diff --git a/pkg/api/ds_query_test.go b/pkg/api/ds_query_test.go index 7031ce542ea..f374a413bb9 100644 --- a/pkg/api/ds_query_test.go +++ b/pkg/api/ds_query_test.go @@ -211,49 +211,79 @@ var reqDatasourceByIdNotFound = `{ }` func TestDataSourceQueryError(t *testing.T) { + type body struct { + Message string `json:"message"` + MessageId string `json:"messageId"` + StatusCode int `json:"statusCode"` + } + tcs := []struct { request string clientErr error expectedStatus int - expectedBody string + expectedBody body }{ { request: reqValid, clientErr: plugins.ErrPluginUnavailable, expectedStatus: http.StatusInternalServerError, - expectedBody: `{"message":"Plugin unavailable","messageId":"plugin.unavailable","statusCode":500,"traceID":""}`, + expectedBody: body{ + Message: "Plugin unavailable", + MessageId: "plugin.unavailable", + StatusCode: 500, + }, }, { request: reqValid, clientErr: plugins.ErrMethodNotImplemented, expectedStatus: http.StatusNotFound, - expectedBody: `{"message":"Method not implemented","messageId":"plugin.notImplemented","statusCode":404,"traceID":""}`, + expectedBody: body{ + Message: "Method not implemented", + MessageId: "plugin.notImplemented", + StatusCode: 404, + }, }, { request: reqValid, clientErr: errors.New("surprise surprise"), expectedStatus: errutil.StatusInternal.HTTPStatus(), - expectedBody: `{"message":"An error occurred within the plugin","messageId":"plugin.downstreamError","statusCode":500,"traceID":""}`, + expectedBody: body{ + Message: "An error occurred within the plugin", + MessageId: "plugin.downstreamError", + StatusCode: 500, + }, }, { request: reqNoQueries, expectedStatus: http.StatusBadRequest, - expectedBody: `{"message":"No queries found","messageId":"query.noQueries","statusCode":400,"traceID":""}`, + expectedBody: body{ + Message: "No queries found", + MessageId: "query.noQueries", + StatusCode: 400, + }, }, { request: reqQueryWithInvalidDatasourceID, expectedStatus: http.StatusBadRequest, - expectedBody: `{"message":"Query does not contain a valid data source identifier","messageId":"query.invalidDatasourceId","statusCode":400,"traceID":""}`, + expectedBody: body{ + Message: "Query does not contain a valid data source identifier", + MessageId: "query.invalidDatasourceId", + StatusCode: 400, + }, }, { request: reqDatasourceByUidNotFound, expectedStatus: http.StatusNotFound, - expectedBody: `{"message":"Data source not found","traceID":""}`, + expectedBody: body{ + Message: "Data source not found", + }, }, { request: reqDatasourceByIdNotFound, expectedStatus: http.StatusNotFound, - expectedBody: `{"message":"Data source not found","traceID":""}`, + expectedBody: body{ + Message: "Data source not found", + }, }, } @@ -291,14 +321,24 @@ func TestDataSourceQueryError(t *testing.T) { hs.QuotaService = quotatest.New(false, nil) }) req := srv.NewPostRequest("/api/ds/query", strings.NewReader(tc.request)) + webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {datasources.ActionQuery: []string{datasources.ScopeAll}}}}) resp, err := srv.SendJSON(req) require.NoError(t, err) require.Equal(t, tc.expectedStatus, resp.StatusCode) - body, err := io.ReadAll(resp.Body) + + bodyBytes, err := io.ReadAll(resp.Body) require.NoError(t, err) - require.Equal(t, tc.expectedBody, string(body)) + + var responseBody body + err = json.Unmarshal(bodyBytes, &responseBody) + require.NoError(t, err) + + require.Equal(t, tc.expectedBody.Message, responseBody.Message) + require.Equal(t, tc.expectedBody.MessageId, responseBody.MessageId) + require.Equal(t, tc.expectedBody.StatusCode, responseBody.StatusCode) + require.NoError(t, resp.Body.Close()) }) } diff --git a/pkg/api/plugin_resource_test.go b/pkg/api/plugin_resource_test.go index c966ae8e97e..a83a9400cc5 100644 --- a/pkg/api/plugin_resource_test.go +++ b/pkg/api/plugin_resource_test.go @@ -199,12 +199,15 @@ func TestCallResource(t *testing.T) { resp, err := srv.SendJSON(req) require.NoError(t, err) - body := new(strings.Builder) - _, err = io.Copy(body, resp.Body) + bodyBytes, err := io.ReadAll(resp.Body) require.NoError(t, err) - expectedBody := `{ "message": "Failed to call resource", "traceID": "" }` - require.JSONEq(t, expectedBody, body.String()) + var responseBody struct { + Message string `json:"message"` + } + err = json.Unmarshal(bodyBytes, &responseBody) + require.NoError(t, err) + require.Equal(t, responseBody.Message, "Failed to call resource") require.NoError(t, resp.Body.Close()) require.Equal(t, 500, resp.StatusCode) }) diff --git a/pkg/services/accesscontrol/acimpl/accesscontrol.go b/pkg/services/accesscontrol/acimpl/accesscontrol.go index a643e02f766..4a6b8a99e3e 100644 --- a/pkg/services/accesscontrol/acimpl/accesscontrol.go +++ b/pkg/services/accesscontrol/acimpl/accesscontrol.go @@ -7,6 +7,7 @@ import ( openfgav1 "github.com/openfga/api/proto/openfga/v1" "github.com/prometheus/client_golang/prometheus" + "go.opentelemetry.io/otel" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" @@ -18,6 +19,7 @@ import ( var ( errAccessNotImplemented = errors.New("access control not implemented for resource") + tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/accesscontrol/acimpl") ) var _ accesscontrol.AccessControl = new(AccessControl) @@ -52,6 +54,9 @@ type AccessControl struct { } func (a *AccessControl) Evaluate(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.Evaluate") + defer span.End() + if a.features.IsEnabledGlobally(featuremgmt.FlagZanzana) { return a.evaluateCompare(ctx, user, evaluator) } @@ -60,6 +65,9 @@ func (a *AccessControl) Evaluate(ctx context.Context, user identity.Requester, e } func (a *AccessControl) evaluate(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.evaluate") + defer span.End() + timer := prometheus.NewTimer(metrics.MAccessEvaluationsSummary) defer timer.ObserveDuration() metrics.MAccessEvaluationCount.Inc() @@ -98,6 +106,9 @@ func (a *AccessControl) evaluate(ctx context.Context, user identity.Requester, e } func (a *AccessControl) evaluateZanzana(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.evaluateZanzana") + defer span.End() + eval, err := evaluator.MutateScopes(ctx, a.resolvers.GetScopeAttributeMutator(user.GetOrgID())) if err != nil { if !errors.Is(err, accesscontrol.ErrResolverNotFound) { @@ -140,6 +151,9 @@ type evalResult struct { // evaluateCompare run RBAC and zanzana checks in parallel and then compare result func (a *AccessControl) evaluateCompare(ctx context.Context, user identity.Requester, evaluator accesscontrol.Evaluator) (bool, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.evaluateCompare") + defer span.End() + res := make(chan evalResult, 2) go func() { timer := prometheus.NewTimer(a.metrics.mAccessEngineEvaluationsSeconds.WithLabelValues("zanzana")) @@ -192,5 +206,8 @@ func (a *AccessControl) RegisterScopeAttributeResolver(prefix string, resolver a } func (a *AccessControl) debug(ctx context.Context, ident identity.Requester, msg string, eval accesscontrol.Evaluator) { + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.debug") + defer span.End() + a.log.FromContext(ctx).Debug(msg, "id", ident.GetID(), "orgID", ident.GetOrgID(), "permissions", eval.GoString()) } diff --git a/pkg/services/accesscontrol/acimpl/service.go b/pkg/services/accesscontrol/acimpl/service.go index 875ec43e234..77e44244bf0 100644 --- a/pkg/services/accesscontrol/acimpl/service.go +++ b/pkg/services/accesscontrol/acimpl/service.go @@ -85,7 +85,6 @@ func ProvideOSSService( log: log.New("accesscontrol.service"), roles: accesscontrol.BuildBasicRoleDefinitions(), store: store, - tracer: tracer, sync: migrator.NewZanzanaSynchroniser(zclient, db), permRegistry: permRegistry, } @@ -103,7 +102,6 @@ type Service struct { registrations accesscontrol.RegistrationList roles map[string]*accesscontrol.RoleDTO store accesscontrol.Store - tracer tracing.Tracer sync *migrator.ZanzanaSynchroniser permRegistry permreg.PermissionRegistry } @@ -116,7 +114,7 @@ func (s *Service) GetUsageStats(_ context.Context) map[string]any { // GetUserPermissions returns user permissions based on built-in roles func (s *Service) GetUserPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) { - ctx, span := s.tracer.Start(ctx, "authz.GetUserPermissionsOSS") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.GetUserPermissions") defer span.End() timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary) @@ -130,7 +128,7 @@ func (s *Service) GetUserPermissions(ctx context.Context, user identity.Requeste } func (s *Service) getUserPermissions(ctx context.Context, user identity.Requester, _ accesscontrol.Options) ([]accesscontrol.Permission, error) { - ctx, span := s.tracer.Start(ctx, "authz.getUserPermissions") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getUserPermissions") defer span.End() permissions := make([]accesscontrol.Permission, 0) @@ -166,7 +164,7 @@ func (s *Service) getUserPermissions(ctx context.Context, user identity.Requeste } func (s *Service) getBasicRolePermissions(ctx context.Context, role string, orgID int64) ([]accesscontrol.Permission, error) { - ctx, span := s.tracer.Start(ctx, "authz.getBasicRolePermissions") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getBasicRolePermissions") defer span.End() var permissions []accesscontrol.Permission @@ -188,7 +186,7 @@ func (s *Service) getBasicRolePermissions(ctx context.Context, role string, orgI } func (s *Service) getTeamsPermissions(ctx context.Context, teamIDs []int64, orgID int64) (map[int64][]accesscontrol.Permission, error) { - ctx, span := s.tracer.Start(ctx, "authz.getTeamsPermissions") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getTeamsPermissions") defer span.End() teamPermissions, err := s.store.GetTeamsPermissions(ctx, accesscontrol.GetUserPermissionsQuery{ @@ -208,7 +206,7 @@ func (s *Service) getTeamsPermissions(ctx context.Context, teamIDs []int64, orgI // Returns only permissions directly assigned to user, without basic role and team permissions func (s *Service) getUserDirectPermissions(ctx context.Context, user identity.Requester) ([]accesscontrol.Permission, error) { - ctx, span := s.tracer.Start(ctx, "authz.getUserDirectPermissions") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getUserDirectPermissions") defer span.End() var userID int64 @@ -240,7 +238,7 @@ func (s *Service) getUserDirectPermissions(ctx context.Context, user identity.Re } func (s *Service) getCachedUserPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) { - ctx, span := s.tracer.Start(ctx, "authz.getCachedUserPermissions") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getCachedUserPermissions") defer span.End() permissions := []accesscontrol.Permission{} @@ -266,7 +264,7 @@ func (s *Service) getCachedUserPermissions(ctx context.Context, user identity.Re } func (s *Service) getCachedBasicRolesPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options, permissions []accesscontrol.Permission) ([]accesscontrol.Permission, error) { - ctx, span := s.tracer.Start(ctx, "authz.getCachedBasicRolesPermissions") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getCachedBasicRolesPermissions") defer span.End() basicRoles := accesscontrol.GetOrgRoles(user) @@ -283,7 +281,7 @@ func (s *Service) getCachedBasicRolesPermissions(ctx context.Context, user ident } func (s *Service) getCachedBasicRolePermissions(ctx context.Context, role string, orgID int64, options accesscontrol.Options) ([]accesscontrol.Permission, error) { - ctx, span := s.tracer.Start(ctx, "authz.getCachedBasicRolePermissions") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getCachedBasicRolePermissions") defer span.End() key := accesscontrol.GetBasicRolePermissionCacheKey(role, orgID) @@ -294,7 +292,7 @@ func (s *Service) getCachedBasicRolePermissions(ctx context.Context, role string } func (s *Service) getCachedUserDirectPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options) ([]accesscontrol.Permission, error) { - ctx, span := s.tracer.Start(ctx, "authz.getCachedUserDirectPermissions") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getCachedUserDirectPermissions") defer span.End() key := accesscontrol.GetUserDirectPermissionCacheKey(user) @@ -308,7 +306,7 @@ type getPermissionsFunc = func(ctx context.Context) ([]accesscontrol.Permission, // Generic method for getting various permissions from cache func (s *Service) getCachedPermissions(ctx context.Context, key string, getPermissionsFn getPermissionsFunc, options accesscontrol.Options) ([]accesscontrol.Permission, error) { - ctx, span := s.tracer.Start(ctx, "authz.getCachedPermissions") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getCachedPermissions") defer span.End() if !options.ReloadCache { @@ -333,7 +331,7 @@ func (s *Service) getCachedPermissions(ctx context.Context, key string, getPermi } func (s *Service) getCachedTeamsPermissions(ctx context.Context, user identity.Requester, options accesscontrol.Options, permissions []accesscontrol.Permission) ([]accesscontrol.Permission, error) { - ctx, span := s.tracer.Start(ctx, "authz.getCachedTeamsPermissions") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.getCachedTeamsPermissions") defer span.End() teams := user.GetTeams() @@ -380,14 +378,14 @@ func (s *Service) ClearUserPermissionCache(user identity.Requester) { } func (s *Service) DeleteUserPermissions(ctx context.Context, orgID int64, userID int64) error { - ctx, span := s.tracer.Start(ctx, "authz.DeleteUserPermissions") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.DeleteUserPermissions") defer span.End() return s.store.DeleteUserPermissions(ctx, orgID, userID) } func (s *Service) DeleteTeamPermissions(ctx context.Context, orgID int64, teamID int64) error { - ctx, span := s.tracer.Start(ctx, "authz.DeleteTeamPermissions") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.DeleteTeamPermissions") defer span.End() return s.store.DeleteTeamPermissions(ctx, orgID, teamID) @@ -419,7 +417,7 @@ func (s *Service) DeclareFixedRoles(registrations ...accesscontrol.RoleRegistrat // RegisterFixedRoles registers all declared roles in RAM func (s *Service) RegisterFixedRoles(ctx context.Context) error { - _, span := s.tracer.Start(ctx, "authz.RegisterFixedRoles") + _, span := tracer.Start(ctx, "accesscontrol.acimpl.RegisterFixedRoles") defer span.End() s.registrations.Range(func(registration accesscontrol.RoleRegistration) bool { @@ -445,7 +443,7 @@ func (s *Service) RegisterFixedRoles(ctx context.Context) error { // DeclarePluginRoles allow the caller to declare, to the service, plugin roles and their assignments // to organization roles ("Viewer", "Editor", "Admin") or "Grafana Admin" func (s *Service) DeclarePluginRoles(ctx context.Context, ID, name string, regs []plugins.RoleRegistration) error { - ctx, span := s.tracer.Start(ctx, "authz.DeclarePluginRoles") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.DeclarePluginRoles") defer span.End() // Protect behind feature toggle @@ -488,7 +486,7 @@ func GetActionFilter(options accesscontrol.SearchOptions) func(action string) bo // SearchUsersPermissions returns all users' permissions filtered by action prefixes func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Requester, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) { - ctx, span := s.tracer.Start(ctx, "authz.SearchUsersPermissions") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.SearchUsersPermissions") defer span.End() // Limit roles to available in OSS @@ -602,7 +600,7 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque } func (s *Service) SearchUserPermissions(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, error) { - ctx, span := s.tracer.Start(ctx, "authz.SearchUserPermissions") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.SearchUserPermissions") defer span.End() timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary) @@ -619,7 +617,7 @@ func (s *Service) SearchUserPermissions(ctx context.Context, orgID int64, search } func (s *Service) searchUserPermissions(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, error) { - ctx, span := s.tracer.Start(ctx, "authz.searchUserPermissions") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.searchUserPermissions") defer span.End() userID, err := searchOptions.ComputeUserID() @@ -672,7 +670,7 @@ func (s *Service) searchUserPermissions(ctx context.Context, orgID int64, search } func (s *Service) searchUserPermissionsFromCache(ctx context.Context, orgID int64, searchOptions accesscontrol.SearchOptions) ([]accesscontrol.Permission, bool) { - _, span := s.tracer.Start(ctx, "authz.searchUserPermissionsFromCache") + _, span := tracer.Start(ctx, "accesscontrol.acimpl.searchUserPermissionsFromCache") defer span.End() userID, err := searchOptions.ComputeUserID() @@ -714,7 +712,7 @@ func PermissionMatchesSearchOptions(permission accesscontrol.Permission, searchO } func (s *Service) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error { - ctx, span := s.tracer.Start(ctx, "authz.SaveExternalServiceRole") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.SaveExternalServiceRole") defer span.End() if !s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { @@ -730,7 +728,7 @@ func (s *Service) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol } func (s *Service) DeleteExternalServiceRole(ctx context.Context, externalServiceID string) error { - ctx, span := s.tracer.Start(ctx, "authz.DeleteExternalServiceRole") + ctx, span := tracer.Start(ctx, "accesscontrol.acimpl.DeleteExternalServiceRole") defer span.End() if !s.features.IsEnabled(ctx, featuremgmt.FlagExternalServiceAccounts) { @@ -748,7 +746,7 @@ func (*Service) SyncUserRoles(ctx context.Context, orgID int64, cmd accesscontro } func (s *Service) GetRoleByName(ctx context.Context, orgID int64, roleName string) (*accesscontrol.RoleDTO, error) { - _, span := s.tracer.Start(ctx, "authz.GetRoleByName") + _, span := tracer.Start(ctx, "accesscontrol.acimpl.GetRoleByName") defer span.End() err := accesscontrol.ErrRoleNotFound diff --git a/pkg/services/accesscontrol/acimpl/service_test.go b/pkg/services/accesscontrol/acimpl/service_test.go index a9713529f6b..6ac4e258c8f 100644 --- a/pkg/services/accesscontrol/acimpl/service_test.go +++ b/pkg/services/accesscontrol/acimpl/service_test.go @@ -42,7 +42,6 @@ func setupTestEnv(t testing.TB) *Service { log: log.New("accesscontrol"), registrations: accesscontrol.RegistrationList{}, roles: accesscontrol.BuildBasicRoleDefinitions(), - tracer: tracing.InitializeTracerForTest(), store: database.ProvideService(db.InitTestReplDB(t)), permRegistry: permreg.ProvidePermissionRegistry(), } diff --git a/pkg/services/accesscontrol/api/api.go b/pkg/services/accesscontrol/api/api.go index 88241e0d1cf..a35a3d88b81 100644 --- a/pkg/services/accesscontrol/api/api.go +++ b/pkg/services/accesscontrol/api/api.go @@ -10,8 +10,11 @@ import ( ac "github.com/grafana/grafana/pkg/services/accesscontrol" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/featuremgmt" + "go.opentelemetry.io/otel" ) +var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/accesscontrol/api") + func NewAccessControlAPI(router routing.RouteRegister, accesscontrol ac.AccessControl, service ac.Service, features featuremgmt.FeatureToggles) *AccessControlAPI { return &AccessControlAPI{ @@ -43,8 +46,11 @@ func (api *AccessControlAPI) RegisterAPIEndpoints() { // GET /api/access-control/user/actions func (api *AccessControlAPI) getUserActions(c *contextmodel.ReqContext) response.Response { + ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.api.getUserActions") + defer span.End() + reloadCache := c.QueryBool("reloadcache") - permissions, err := api.Service.GetUserPermissions(c.Req.Context(), + permissions, err := api.Service.GetUserPermissions(ctx, c.SignedInUser, ac.Options{ReloadCache: reloadCache}) if err != nil { return response.JSON(http.StatusInternalServerError, err) @@ -55,18 +61,24 @@ func (api *AccessControlAPI) getUserActions(c *contextmodel.ReqContext) response // GET /api/access-control/user/permissions func (api *AccessControlAPI) getUserPermissions(c *contextmodel.ReqContext) response.Response { + ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.api.getUserPermissions") + defer span.End() + reloadCache := c.QueryBool("reloadcache") - permissions, err := api.Service.GetUserPermissions(c.Req.Context(), + permissions, err := api.Service.GetUserPermissions(ctx, c.SignedInUser, ac.Options{ReloadCache: reloadCache}) if err != nil { return response.JSON(http.StatusInternalServerError, err) } - return response.JSON(http.StatusOK, ac.GroupScopesByActionContext(c.Req.Context(), permissions)) + return response.JSON(http.StatusOK, ac.GroupScopesByActionContext(ctx, permissions)) } // GET /api/access-control/users/permissions/search func (api *AccessControlAPI) searchUsersPermissions(c *contextmodel.ReqContext) response.Response { + ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.api.searchUsersPermissions") + defer span.End() + searchOptions := ac.SearchOptions{ ActionPrefix: c.Query("actionPrefix"), Action: c.Query("action"), @@ -84,7 +96,7 @@ func (api *AccessControlAPI) searchUsersPermissions(c *contextmodel.ReqContext) } // Compute metadata - permissions, err := api.Service.SearchUsersPermissions(c.Req.Context(), c.SignedInUser, searchOptions) + permissions, err := api.Service.SearchUsersPermissions(ctx, c.SignedInUser, searchOptions) if err != nil { return response.Error(http.StatusInternalServerError, "could not get org user permissions", err) } diff --git a/pkg/services/accesscontrol/database/database.go b/pkg/services/accesscontrol/database/database.go index afa8814c109..8443af055f7 100644 --- a/pkg/services/accesscontrol/database/database.go +++ b/pkg/services/accesscontrol/database/database.go @@ -8,8 +8,11 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/accesscontrol" + "go.opentelemetry.io/otel" ) +var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/accesscontrol/database") + const ( // userAssignsSQL is a query to select all users assignments. userAssignsSQL = `SELECT ur.user_id, ur.org_id, ur.role_id @@ -45,6 +48,9 @@ type AccessControlStore struct { } func (s *AccessControlStore) GetUserPermissions(ctx context.Context, query accesscontrol.GetUserPermissionsQuery) ([]accesscontrol.Permission, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.database.GetUserPermissions") + defer span.End() + result := make([]accesscontrol.Permission, 0) err := s.sql.ReadReplica().WithDbSession(ctx, func(sess *db.Session) error { if query.UserID == 0 && len(query.TeamIDs) == 0 && len(query.Roles) == 0 { @@ -100,6 +106,9 @@ func (p teamPermission) Permission() accesscontrol.Permission { } func (s *AccessControlStore) GetTeamsPermissions(ctx context.Context, query accesscontrol.GetUserPermissionsQuery) (map[int64][]accesscontrol.Permission, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.database.GetTeamsPermissions") + defer span.End() + teams := query.TeamIDs orgID := query.OrgID rolePrefixes := query.RolePrefixes @@ -156,6 +165,9 @@ func (s *AccessControlStore) GetTeamsPermissions(ctx context.Context, query acce // SearchUsersPermissions returns the list of user permissions in specific organization indexed by UserID func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID int64, options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.database.SearchUsersPermissions") + defer span.End() + type UserRBACPermission struct { UserID int64 `xorm:"user_id"` Action string `xorm:"action"` @@ -278,6 +290,9 @@ func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID i // GetUsersBasicRoles returns the list of user basic roles (Admin, Editor, Viewer, Grafana Admin) indexed by UserID func (s *AccessControlStore) GetUsersBasicRoles(ctx context.Context, userFilter []int64, orgID int64) (map[int64][]string, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.database.GetUsersBasicRoles") + defer span.End() + type UserOrgRole struct { UserID int64 `xorm:"id"` OrgRole string `xorm:"role"` @@ -318,6 +333,9 @@ func (s *AccessControlStore) GetUsersBasicRoles(ctx context.Context, userFilter } func (s *AccessControlStore) DeleteUserPermissions(ctx context.Context, orgID, userID int64) error { + ctx, span := tracer.Start(ctx, "accesscontrol.database.DeleteUserPermissions") + defer span.End() + err := s.sql.DB().WithDbSession(ctx, func(sess *db.Session) error { roleDeleteQuery := "DELETE FROM user_role WHERE user_id = ?" roleDeleteParams := []any{roleDeleteQuery, userID} @@ -383,6 +401,9 @@ func (s *AccessControlStore) DeleteUserPermissions(ctx context.Context, orgID, u } func (s *AccessControlStore) DeleteTeamPermissions(ctx context.Context, orgID, teamID int64) error { + ctx, span := tracer.Start(ctx, "accesscontrol.database.DeleteTeamPermissions") + defer span.End() + err := s.sql.DB().WithDbSession(ctx, func(sess *db.Session) error { roleDeleteQuery := "DELETE FROM team_role WHERE team_id = ? AND org_id = ?" roleDeleteParams := []any{roleDeleteQuery, teamID, orgID} diff --git a/pkg/services/accesscontrol/database/externalservices.go b/pkg/services/accesscontrol/database/externalservices.go index 622db51684e..0ac3ec9f4f2 100644 --- a/pkg/services/accesscontrol/database/externalservices.go +++ b/pkg/services/accesscontrol/database/externalservices.go @@ -17,6 +17,9 @@ func extServiceRoleName(externalServiceID string) string { } func (s *AccessControlStore) DeleteExternalServiceRole(ctx context.Context, externalServiceID string) error { + ctx, span := tracer.Start(ctx, "accesscontrol.database.DeleteExternalServiceRole") + defer span.End() + uid := accesscontrol.PrefixedRoleUID(extServiceRoleName(externalServiceID)) return s.sql.DB().WithDbSession(ctx, func(sess *db.Session) error { stored, errGet := getRoleByUID(ctx, sess, uid) @@ -52,6 +55,9 @@ func (s *AccessControlStore) DeleteExternalServiceRole(ctx context.Context, exte } func (s *AccessControlStore) SaveExternalServiceRole(ctx context.Context, cmd accesscontrol.SaveExternalServiceRoleCommand) error { + ctx, span := tracer.Start(ctx, "accesscontrol.database.SaveExternalServiceRole") + defer span.End() + role := genExternalServiceRole(cmd) assignment := genExternalServiceAssignment(cmd) @@ -103,6 +109,9 @@ func genExternalServiceAssignment(cmd accesscontrol.SaveExternalServiceRoleComma } func getRoleByUID(ctx context.Context, sess *db.Session, uid string) (*accesscontrol.Role, error) { + _, span := tracer.Start(ctx, "accesscontrol.database.getRoleByUID") + defer span.End() + var role accesscontrol.Role has, err := sess.Where("uid = ?", uid).Get(&role) if err != nil { @@ -115,6 +124,9 @@ func getRoleByUID(ctx context.Context, sess *db.Session, uid string) (*accesscon } func getRoleAssignments(ctx context.Context, sess *db.Session, roleID int64) ([]accesscontrol.UserRole, error) { + _, span := tracer.Start(ctx, "accesscontrol.database.GgetRoleAssignments") + defer span.End() + var assignements []accesscontrol.UserRole if err := sess.Where("role_id = ?", roleID).Find(&assignements); err != nil { return nil, err @@ -123,6 +135,9 @@ func getRoleAssignments(ctx context.Context, sess *db.Session, roleID int64) ([] } func getRolePermissions(ctx context.Context, sess *db.Session, id int64) ([]accesscontrol.Permission, error) { + _, span := tracer.Start(ctx, "accesscontrol.database.getRolePermissions") + defer span.End() + var permissions []accesscontrol.Permission if err := sess.Where("role_id = ?", id).Find(&permissions); err != nil { return nil, err @@ -157,6 +172,9 @@ func permissionDiff(previous, new []accesscontrol.Permission) (added, removed [] } func (*AccessControlStore) saveRole(ctx context.Context, sess *db.Session, role *accesscontrol.Role) (*accesscontrol.Role, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.database.saveRole") + defer span.End() + existingRole, err := getRoleByUID(ctx, sess, role.UID) if err != nil && !errors.Is(err, accesscontrol.ErrRoleNotFound) { return nil, err @@ -177,6 +195,9 @@ func (*AccessControlStore) saveRole(ctx context.Context, sess *db.Session, role } func (*AccessControlStore) savePermissions(ctx context.Context, sess *db.Session, roleID int64, permissions []accesscontrol.Permission) error { + ctx, span := tracer.Start(ctx, "accesscontrol.database.savePermissions") + defer span.End() + now := time.Now() storedPermissions, err := getRolePermissions(ctx, sess, roleID) if err != nil { @@ -210,6 +231,9 @@ func (*AccessControlStore) savePermissions(ctx context.Context, sess *db.Session } func (*AccessControlStore) saveUserAssignment(ctx context.Context, sess *db.Session, assignment accesscontrol.UserRole) error { + ctx, span := tracer.Start(ctx, "accesscontrol.database.saveUserAssignment") + defer span.End() + // alreadyAssigned checks if the assignment already exists without accounting for the organization assignments, errGetAssigns := getRoleAssignments(ctx, sess, assignment.RoleID) if errGetAssigns != nil { diff --git a/pkg/services/accesscontrol/database/policies.go b/pkg/services/accesscontrol/database/policies.go index b31329886e5..6fec2c2c483 100644 --- a/pkg/services/accesscontrol/database/policies.go +++ b/pkg/services/accesscontrol/database/policies.go @@ -13,6 +13,9 @@ import ( ) func GetAccessPolicies(ctx context.Context, orgID int64, sql *session.SessionDB, resolver accesscontrol.ScopeAttributeResolverFunc) ([]accesspolicy.Resource, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.database.GetAccessPolicies") + defer span.End() + type permissionInfo struct { RoleUID string RoleName string @@ -25,14 +28,14 @@ func GetAccessPolicies(ctx context.Context, orgID int64, sql *session.SessionDB, policies := make([]accesspolicy.Resource, 0) current := &accesspolicy.Resource{} prevKey := "" - rows, err := sql.Query(ctx, `SELECT + rows, err := sql.Query(ctx, `SELECT role.uid as role_uid, role.name as role_name, scope, action, permission.created, - permission.updated - FROM permission + permission.updated + FROM permission JOIN role ON permission.role_id = role.id WHERE org_id=? ORDER BY role.id ASC, scope ASC, action ASC`, orgID) diff --git a/pkg/services/accesscontrol/evaluator.go b/pkg/services/accesscontrol/evaluator.go index 9a49cc15f0e..d07ee295bee 100644 --- a/pkg/services/accesscontrol/evaluator.go +++ b/pkg/services/accesscontrol/evaluator.go @@ -104,6 +104,9 @@ func (p permissionEvaluator) EvaluateCustom(fn CheckerFn) (bool, error) { } func (p permissionEvaluator) MutateScopes(ctx context.Context, mutate ScopeAttributeMutator) (Evaluator, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.permissionEvaluatorMutateScopes") + defer span.End() + if p.Scopes == nil { return EvalPermission(p.Action), nil } @@ -172,6 +175,9 @@ func (a allEvaluator) EvaluateCustom(fn CheckerFn) (bool, error) { } func (a allEvaluator) MutateScopes(ctx context.Context, mutate ScopeAttributeMutator) (Evaluator, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.allEvaluator.MutateScopes") + defer span.End() + resolved := false modified := make([]Evaluator, 0, len(a.allOf)) for _, e := range a.allOf { @@ -245,6 +251,9 @@ func (a anyEvaluator) EvaluateCustom(fn CheckerFn) (bool, error) { } func (a anyEvaluator) MutateScopes(ctx context.Context, mutate ScopeAttributeMutator) (Evaluator, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.anyEvaluator.MutateScopes") + defer span.End() + resolved := false modified := make([]Evaluator, 0, len(a.anyOf)) for _, e := range a.anyOf { diff --git a/pkg/services/accesscontrol/metadata.go b/pkg/services/accesscontrol/metadata.go index 15dbde04153..13b39138f26 100644 --- a/pkg/services/accesscontrol/metadata.go +++ b/pkg/services/accesscontrol/metadata.go @@ -11,6 +11,9 @@ type Metadata map[string]bool // GetResourcesMetadata returns a map of accesscontrol metadata, listing for each resource, users available actions func GetResourcesMetadata(ctx context.Context, permissions map[string][]string, prefix string, resourceIDs map[string]bool) map[string]Metadata { + _, span := tracer.Start(ctx, "accesscontrol.GetResourcesMetadata") + defer span.End() + wildcards := WildcardsFromPrefix(prefix) // index of the prefix in the scope diff --git a/pkg/services/accesscontrol/middleware.go b/pkg/services/accesscontrol/middleware.go index 7c77b232247..5a2aaf024d4 100644 --- a/pkg/services/accesscontrol/middleware.go +++ b/pkg/services/accesscontrol/middleware.go @@ -29,6 +29,10 @@ import ( func Middleware(ac AccessControl) func(Evaluator) web.Handler { return func(evaluator Evaluator) web.Handler { return func(c *contextmodel.ReqContext) { + ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.Middleware") + defer span.End() + c.Req = c.Req.WithContext(ctx) + if c.AllowAnonymous { forceLogin, _ := strconv.ParseBool(c.Req.URL.Query().Get("forceLogin")) // ignoring error, assuming false for non-true values is ok. orgID, err := strconv.ParseInt(c.Req.URL.Query().Get("orgId"), 10, 64) @@ -59,7 +63,11 @@ func Middleware(ac AccessControl) func(Evaluator) web.Handler { } func authorize(c *contextmodel.ReqContext, ac AccessControl, user identity.Requester, evaluator Evaluator) { - injected, err := evaluator.MutateScopes(c.Req.Context(), scopeInjector(scopeParams{ + ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.authorize") + defer span.End() + c.Req = c.Req.WithContext(ctx) + + injected, err := evaluator.MutateScopes(ctx, scopeInjector(scopeParams{ OrgID: user.GetOrgID(), URLParams: web.Params(c.Req), })) @@ -68,7 +76,7 @@ func authorize(c *contextmodel.ReqContext, ac AccessControl, user identity.Reque return } - hasAccess, err := ac.Evaluate(c.Req.Context(), user, injected) + hasAccess, err := ac.Evaluate(ctx, user, injected) if !hasAccess || err != nil { deny(c, injected, err) return @@ -177,6 +185,10 @@ type OrgIDGetter func(c *contextmodel.ReqContext) (int64, error) func AuthorizeInOrgMiddleware(ac AccessControl, authnService authn.Service) func(OrgIDGetter, Evaluator) web.Handler { return func(getTargetOrg OrgIDGetter, evaluator Evaluator) web.Handler { return func(c *contextmodel.ReqContext) { + ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.AuthorizeInOrgMiddleware") + defer span.End() + c.Req = c.Req.WithContext(ctx) + targetOrgID, err := getTargetOrg(c) if err != nil { if errors.Is(err, ErrInvalidRequestBody) { diff --git a/pkg/services/accesscontrol/migrator/zanzana.go b/pkg/services/accesscontrol/migrator/zanzana.go index 437525e6724..e65997ed86e 100644 --- a/pkg/services/accesscontrol/migrator/zanzana.go +++ b/pkg/services/accesscontrol/migrator/zanzana.go @@ -8,12 +8,15 @@ import ( "strings" openfgav1 "github.com/openfga/api/proto/openfga/v1" + "go.opentelemetry.io/otel" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/authz/zanzana" ) +var tracer = otel.Tracer("github.com/grafana/grafana/pkg/accesscontrol/migrator") + // A TupleCollector is responsible to build and store [openfgav1.TupleKey] into provided tuple map. // They key used should be a unique group key for the collector so we can skip over an already synced group. type TupleCollector func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error @@ -54,6 +57,9 @@ func NewZanzanaSynchroniser(client zanzana.Client, store db.DB, collectors ...Tu // Sync runs all collectors and tries to write all collected tuples. // It will skip over any "sync group" that has already been written. func (z *ZanzanaSynchroniser) Sync(ctx context.Context) error { + ctx, span := tracer.Start(ctx, "accesscontrol.migrator.Sync") + defer span.End() + tuplesMap := make(map[string][]*openfgav1.TupleKey) for _, c := range z.collectors { @@ -145,6 +151,9 @@ func managedPermissionsCollector(store db.DB) TupleCollector { func teamMembershipCollector(store db.DB) TupleCollector { return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { + ctx, span := tracer.Start(ctx, "accesscontrol.migrator.teamMembershipCollector") + defer span.End() + const collectorID = "team_membership" const query = ` SELECT t.uid as team_uid, u.uid as user_uid, tm.permission @@ -191,6 +200,9 @@ func teamMembershipCollector(store db.DB) TupleCollector { // folderTreeCollector collects folder tree structure and writes it as relation tuples func folderTreeCollector(store db.DB) TupleCollector { return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { + ctx, span := tracer.Start(ctx, "accesscontrol.migrator.folderTreeCollector") + defer span.End() + const collectorID = "folder" const query = ` SELECT uid, parent_uid, org_id FROM folder @@ -236,6 +248,9 @@ func folderTreeCollector(store db.DB) TupleCollector { // dashboardFolderCollector collects information about dashboards parent folders func dashboardFolderCollector(store db.DB) TupleCollector { return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { + ctx, span := tracer.Start(ctx, "accesscontrol.migrator.dashboardFolderCollector") + defer span.End() + const collectorID = "folder" const query = ` SELECT org_id, uid, folder_uid, is_folder FROM dashboard WHERE is_folder = 0 AND folder_uid IS NOT NULL diff --git a/pkg/services/accesscontrol/ossaccesscontrol/dashboard.go b/pkg/services/accesscontrol/ossaccesscontrol/dashboard.go index 8f4636c7564..85e5d5a4960 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/dashboard.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/dashboard.go @@ -113,6 +113,9 @@ func ProvideDashboardPermissions( Resource: "dashboards", ResourceAttribute: "uid", ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error { + ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.ProvideDashboardPermissions.ResourceValidator") + defer span.End() + dashboard, err := getDashboard(ctx, orgID, resourceID) if err != nil { return err diff --git a/pkg/services/accesscontrol/ossaccesscontrol/datasource.go b/pkg/services/accesscontrol/ossaccesscontrol/datasource.go index d51fcbe8ceb..e97d0721e36 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/datasource.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/datasource.go @@ -10,8 +10,11 @@ import ( "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" + "go.opentelemetry.io/otel" ) +var tracer = otel.Tracer("github.com/grafana/grafana/pkg/accesscontrol/ossaccesscontrol") + // DatasourceQueryActions contains permissions to read information // about a data source and submit arbitrary queries to it. var DatasourceQueryActions = []string{ @@ -51,6 +54,9 @@ func (e DatasourcePermissionsService) SetBuiltInRolePermission(ctx context.Conte // if an OSS/unlicensed instance is upgraded to Enterprise/licensed. // https://github.com/grafana/identity-access-team/issues/672 func (e DatasourcePermissionsService) SetPermissions(ctx context.Context, orgID int64, resourceID string, commands ...accesscontrol.SetResourcePermissionCommand) ([]accesscontrol.ResourcePermission, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.SetPermissions") + defer span.End() + dbCommands := make([]resourcepermissions.SetResourcePermissionsCommand, 0, len(commands)) for _, cmd := range commands { // Only set query permissions for built-in roles; do not set permissions for data sources with * as UID, as this would grant wildcard permissions @@ -75,6 +81,9 @@ func (e DatasourcePermissionsService) SetPermissions(ctx context.Context, orgID } func (e DatasourcePermissionsService) DeleteResourcePermissions(ctx context.Context, orgID int64, resourceID string) error { + ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.DeleteResourcePermissions") + defer span.End() + return e.store.DeleteResourcePermissions(ctx, orgID, &resourcepermissions.DeleteResourcePermissionsCmd{ Resource: datasources.ScopeRoot, ResourceAttribute: "uid", diff --git a/pkg/services/accesscontrol/ossaccesscontrol/folder.go b/pkg/services/accesscontrol/ossaccesscontrol/folder.go index b24e3ce5ef5..8904567d935 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/folder.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/folder.go @@ -95,6 +95,9 @@ func ProvideFolderPermissions( Resource: "folders", ResourceAttribute: "uid", ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error { + ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.ProvideFolderPermissions.ResourceValidator") + defer span.End() + query := &dashboards.GetDashboardQuery{UID: resourceID, OrgID: orgID} queryResult, err := dashboardStore.GetDashboard(ctx, query) if err != nil { diff --git a/pkg/services/accesscontrol/ossaccesscontrol/service_account.go b/pkg/services/accesscontrol/ossaccesscontrol/service_account.go index 077ed4609f0..7925a564690 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/service_account.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/service_account.go @@ -44,6 +44,9 @@ func ProvideServiceAccountPermissions( Resource: "serviceaccounts", ResourceAttribute: "id", ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error { + ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.ProvideServiceAccountPermissions.ResourceValidator") + defer span.End() + id, err := strconv.ParseInt(resourceID, 10, 64) if err != nil { return err diff --git a/pkg/services/accesscontrol/ossaccesscontrol/team.go b/pkg/services/accesscontrol/ossaccesscontrol/team.go index b6c185895d5..27a2914ecd8 100644 --- a/pkg/services/accesscontrol/ossaccesscontrol/team.go +++ b/pkg/services/accesscontrol/ossaccesscontrol/team.go @@ -46,12 +46,15 @@ func ProvideTeamPermissions( ResourceAttribute: "id", OnlyManaged: true, ResourceValidator: func(ctx context.Context, orgID int64, resourceID string) error { + ctx, span := tracer.Start(ctx, "accesscontrol.ossaccesscontrol.ProvideTeamerPermissions.ResourceValidator") + defer span.End() + id, err := strconv.ParseInt(resourceID, 10, 64) if err != nil { return err } - _, err = teamService.GetTeamByID(context.Background(), &team.GetTeamByIDQuery{ + _, err = teamService.GetTeamByID(ctx, &team.GetTeamByIDQuery{ OrgID: orgID, ID: id, }) diff --git a/pkg/services/accesscontrol/resolvers.go b/pkg/services/accesscontrol/resolvers.go index c7ec78eff4a..56f29037351 100644 --- a/pkg/services/accesscontrol/resolvers.go +++ b/pkg/services/accesscontrol/resolvers.go @@ -62,6 +62,9 @@ func (s *Resolvers) AddScopeAttributeResolver(prefix string, resolver ScopeAttri func (s *Resolvers) GetScopeAttributeMutator(orgID int64) ScopeAttributeMutator { return func(ctx context.Context, scope string) ([]string, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.GetScopeAttributeMutator") + defer span.End() + key := getScopeCacheKey(orgID, scope) // Check cache before computing the scope if cachedScope, ok := s.cache.Get(key); ok { diff --git a/pkg/services/accesscontrol/resourcepermissions/api.go b/pkg/services/accesscontrol/resourcepermissions/api.go index b8c05dfc775..c979b8ade0b 100644 --- a/pkg/services/accesscontrol/resourcepermissions/api.go +++ b/pkg/services/accesscontrol/resourcepermissions/api.go @@ -13,8 +13,11 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" + "go.opentelemetry.io/otel" ) +var tracer = otel.Tracer("github.com/grafana/grafana/pkg/accesscontrol/resourcepermissions") + type api struct { cfg *setting.Cfg ac accesscontrol.AccessControl @@ -140,6 +143,10 @@ type getResourcePermissionsResponse []resourcePermissionDTO // 404: notFoundError // 500: internalServerError func (a *api) getPermissions(c *contextmodel.ReqContext) response.Response { + ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.resourcepermissions.getPermissions") + defer span.End() + c.Req = c.Req.WithContext(ctx) + resourceID := web.Params(c.Req)[":resourceID"] permissions, err := a.service.GetPermissions(c.Req.Context(), c.SignedInUser, resourceID) @@ -227,6 +234,10 @@ type SetResourcePermissionsForUserParams struct { // 404: notFoundError // 500: internalServerError func (a *api) setUserPermission(c *contextmodel.ReqContext) response.Response { + ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.resourcepermissions.setUserPermission") + defer span.End() + c.Req = c.Req.WithContext(ctx) + userID, err := strconv.ParseInt(web.Params(c.Req)[":userID"], 10, 64) if err != nil { return response.Err(ErrInvalidParam.Build(ErrInvalidParamData("userID", err))) @@ -280,6 +291,10 @@ type SetResourcePermissionsForTeamParams struct { // 404: notFoundError // 500: internalServerError func (a *api) setTeamPermission(c *contextmodel.ReqContext) response.Response { + ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.resourcepermissions.setTeamPermission") + defer span.End() + c.Req = c.Req.WithContext(ctx) + teamID, err := strconv.ParseInt(web.Params(c.Req)[":teamID"], 10, 64) if err != nil { return response.Err(ErrInvalidParam.Build(ErrInvalidParamData("teamID", err))) @@ -333,6 +348,10 @@ type SetResourcePermissionsForBuiltInRoleParams struct { // 404: notFoundError // 500: internalServerError func (a *api) setBuiltinRolePermission(c *contextmodel.ReqContext) response.Response { + ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.resourcepermissions.setBuiltinRolePermission") + defer span.End() + c.Req = c.Req.WithContext(ctx) + builtInRole := web.Params(c.Req)[":builtInRole"] resourceID := web.Params(c.Req)[":resourceID"] @@ -379,6 +398,9 @@ type SetResourcePermissionsParams struct { // 404: notFoundError // 500: internalServerError func (a *api) setPermissions(c *contextmodel.ReqContext) response.Response { + ctx, span := tracer.Start(c.Req.Context(), "accesscontrol.resourcepermissions.setPermissions") + defer span.End() + resourceID := web.Params(c.Req)[":resourceID"] cmd := setPermissionsCommand{} @@ -386,7 +408,7 @@ func (a *api) setPermissions(c *contextmodel.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "Bad request data: "+err.Error(), err) } - _, err := a.service.SetPermissions(c.Req.Context(), c.SignedInUser.GetOrgID(), resourceID, cmd.Permissions...) + _, err := a.service.SetPermissions(ctx, c.SignedInUser.GetOrgID(), resourceID, cmd.Permissions...) if err != nil { return response.Err(err) } diff --git a/pkg/services/accesscontrol/resourcepermissions/service.go b/pkg/services/accesscontrol/resourcepermissions/service.go index 82fe2c6e99e..a80046877b5 100644 --- a/pkg/services/accesscontrol/resourcepermissions/service.go +++ b/pkg/services/accesscontrol/resourcepermissions/service.go @@ -138,6 +138,9 @@ type Service struct { } func (s *Service) GetPermissions(ctx context.Context, user identity.Requester, resourceID string) ([]accesscontrol.ResourcePermission, error) { + ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.GetPermissions") + defer span.End() + var inheritedScopes []string if s.options.InheritedScopesSolver != nil { var err error @@ -206,6 +209,9 @@ func (s *Service) GetPermissions(ctx context.Context, user identity.Requester, r } func (s *Service) SetUserPermission(ctx context.Context, orgID int64, user accesscontrol.User, resourceID, permission string) (*accesscontrol.ResourcePermission, error) { + ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.SetUserPermission") + defer span.End() + actions, err := s.mapPermission(permission) if err != nil { return nil, err @@ -229,6 +235,9 @@ func (s *Service) SetUserPermission(ctx context.Context, orgID int64, user acces } func (s *Service) SetTeamPermission(ctx context.Context, orgID, teamID int64, resourceID, permission string) (*accesscontrol.ResourcePermission, error) { + ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.SetTeamPermission") + defer span.End() + actions, err := s.mapPermission(permission) if err != nil { return nil, err @@ -252,6 +261,9 @@ func (s *Service) SetTeamPermission(ctx context.Context, orgID, teamID int64, re } func (s *Service) SetBuiltInRolePermission(ctx context.Context, orgID int64, builtInRole, resourceID, permission string) (*accesscontrol.ResourcePermission, error) { + ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.SetBuiltInRolePermission") + defer span.End() + actions, err := s.mapPermission(permission) if err != nil { return nil, err @@ -278,6 +290,9 @@ func (s *Service) SetPermissions( ctx context.Context, orgID int64, resourceID string, commands ...accesscontrol.SetResourcePermissionCommand, ) ([]accesscontrol.ResourcePermission, error) { + ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.SetPermissions") + defer span.End() + if err := s.validateResource(ctx, orgID, resourceID); err != nil { return nil, err } @@ -355,6 +370,9 @@ func (s *Service) mapPermission(permission string) ([]string, error) { } func (s *Service) validateResource(ctx context.Context, orgID int64, resourceID string) error { + ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.validateResource") + defer span.End() + if s.options.ResourceValidator != nil { return s.options.ResourceValidator(ctx, orgID, resourceID) } @@ -362,6 +380,9 @@ func (s *Service) validateResource(ctx context.Context, orgID int64, resourceID } func (s *Service) validateUser(ctx context.Context, orgID, userID int64) error { + ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.validateUser") + defer span.End() + if !s.options.Assignments.Users { return ErrInvalidAssignment.Build(ErrInvalidAssignmentData("users")) } @@ -376,6 +397,9 @@ func (s *Service) validateUser(ctx context.Context, orgID, userID int64) error { } func (s *Service) validateTeam(ctx context.Context, orgID, teamID int64) error { + ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.validateTeam") + defer span.End() + if !s.options.Assignments.Teams { return ErrInvalidAssignment.Build(ErrInvalidAssignmentData("teams")) } @@ -391,7 +415,10 @@ func (s *Service) validateTeam(ctx context.Context, orgID, teamID int64) error { return nil } -func (s *Service) validateBuiltinRole(_ context.Context, builtinRole string) error { +func (s *Service) validateBuiltinRole(ctx context.Context, builtinRole string) error { + _, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.validateBuiltinRole") + defer span.End() + if !s.options.Assignments.BuiltInRoles { return ErrInvalidAssignment.Build(ErrInvalidAssignmentData("builtInRoles")) } @@ -553,6 +580,9 @@ func (a *ActionSetSvc) ExpandActionSetsWithFilter(permissions []accesscontrol.Pe // RegisterActionSets allow the caller to expand the existing action sets with additional permissions // This is intended to be used by plugins, and currently supports extending folder and dashboard action sets func (a *ActionSetSvc) RegisterActionSets(ctx context.Context, pluginID string, registrations []plugins.ActionSet) error { + ctx, span := tracer.Start(ctx, "accesscontroll.resourcepermissions.RegisterActionSets") + defer span.End() + if !a.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) || !a.features.IsEnabled(ctx, featuremgmt.FlagAccessControlOnCall) { return nil } diff --git a/pkg/services/accesscontrol/resourcepermissions/store.go b/pkg/services/accesscontrol/resourcepermissions/store.go index 755e1977ff9..9ded47f4ab0 100644 --- a/pkg/services/accesscontrol/resourcepermissions/store.go +++ b/pkg/services/accesscontrol/resourcepermissions/store.go @@ -63,6 +63,9 @@ type DeleteResourcePermissionsCmd struct { } func (s *store) DeleteResourcePermissions(ctx context.Context, orgID int64, cmd *DeleteResourcePermissionsCmd) error { + ctx, span := tracer.Start(ctx, "accesscontrol.resourcepermissions.DeleteResourcePermissions") + defer span.End() + scope := accesscontrol.Scope(cmd.Resource, cmd.ResourceAttribute, cmd.ResourceID) err := s.sql.WithTransactionalDbSession(ctx, func(sess *db.Session) error { @@ -88,6 +91,9 @@ func (s *store) SetUserResourcePermission( cmd SetResourcePermissionCommand, hook UserResourceHookFunc, ) (*accesscontrol.ResourcePermission, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.resourcepermissions.SetUserResourcePermission") + defer span.End() + if usr.ID == 0 { return nil, user.ErrUserNotFound } @@ -125,6 +131,9 @@ func (s *store) SetTeamResourcePermission( cmd SetResourcePermissionCommand, hook TeamResourceHookFunc, ) (*accesscontrol.ResourcePermission, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.resourcepermissions.SetTeamResourcePermission") + defer span.End() + if teamID == 0 { return nil, team.ErrTeamNotFound } @@ -164,6 +173,9 @@ func (s *store) SetBuiltInResourcePermission( cmd SetResourcePermissionCommand, hook BuiltinResourceHookFunc, ) (*accesscontrol.ResourcePermission, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.resourcepermissions.SetBuiltInResourcePermission") + defer span.End() + if !org.RoleType(builtInRole).IsValid() || builtInRole == accesscontrol.RoleGrafanaAdmin { return nil, fmt.Errorf("invalid role: %s", builtInRole) } @@ -207,6 +219,9 @@ func (s *store) SetResourcePermissions( commands []SetResourcePermissionsCommand, hooks ResourceHooks, ) ([]accesscontrol.ResourcePermission, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.resourcepermissions.SetResourcePermissions") + defer span.End() + var err error var permissions []accesscontrol.ResourcePermission @@ -288,6 +303,9 @@ func (s *store) setResourcePermission( } func (s *store) GetResourcePermissions(ctx context.Context, orgID int64, query GetResourcePermissionsQuery) ([]accesscontrol.ResourcePermission, error) { + ctx, span := tracer.Start(ctx, "accesscontrol.resourcepermissions.GetResourcePermissions") + defer span.End() + var result []accesscontrol.ResourcePermission err := s.sql.WithDbSession(ctx, func(sess *db.Session) error {