Alerting: Optimize prometheus api permission checks (#106299)

* Alerting: Optimize prometheus api permission checks

This improves the performance of the Prometheus API by performing the permission checks for rule read permission in a folder upfront, rather than checking permissions for each rule group individually. This reduces the number of permission checks and should speed up the API response time.

* refactor vars

---------

Co-authored-by: Konrad Lalik <konradlalik@gmail.com>
pull/106372/head
Moustafa Baiou 3 weeks ago committed by GitHub
parent 6fd66dd690
commit 941162ca79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 57
      pkg/services/ngalert/api/prometheus/api_prometheus.go
  2. 4
      pkg/services/ngalert/api/testing.go

@ -34,7 +34,7 @@ type RuleStoreReader interface {
} }
type RuleGroupAccessControlService interface { type RuleGroupAccessControlService interface {
HasAccessToRuleGroup(ctx context.Context, user identity.Requester, rules ngmodels.RulesGroup) (bool, error) HasAccessInFolder(ctx context.Context, user identity.Requester, folder ngmodels.Namespaced) (bool, error)
} }
type StatusReader interface { type StatusReader interface {
@ -215,11 +215,10 @@ func getStatesFromQuery(v url.Values) ([]eval.State, error) {
} }
type RuleGroupStatusesOptions struct { type RuleGroupStatusesOptions struct {
Ctx context.Context Ctx context.Context
OrgID int64 OrgID int64
Query url.Values Query url.Values
Namespaces map[string]string AllowedNamespaces map[string]string
AuthorizeRuleGroup func(rules []*ngmodels.AlertRule) (bool, error)
} }
type ListAlertRulesStore interface { type ListAlertRulesStore interface {
@ -247,19 +246,26 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon
return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse) return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse)
} }
namespaces := map[string]string{} allowedNamespaces := map[string]string{}
for namespaceUID, folder := range namespaceMap { for namespaceUID, folder := range namespaceMap {
namespaces[namespaceUID] = folder.Fullpath // only add namespaces that the user has access to rules in
hasAccess, err := srv.authz.HasAccessInFolder(c.Req.Context(), c.SignedInUser, ngmodels.Namespace(*folder.ToFolderReference()))
if err != nil {
ruleResponse.Status = "error"
ruleResponse.Error = fmt.Sprintf("failed to get namespaces visible to the user: %s", err.Error())
ruleResponse.ErrorType = apiv1.ErrServer
return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse)
}
if hasAccess {
allowedNamespaces[namespaceUID] = folder.Fullpath
}
} }
ruleResponse = PrepareRuleGroupStatuses(srv.log, srv.store, RuleGroupStatusesOptions{ ruleResponse = PrepareRuleGroupStatuses(srv.log, srv.store, RuleGroupStatusesOptions{
Ctx: c.Req.Context(), Ctx: c.Req.Context(),
OrgID: c.OrgID, OrgID: c.OrgID,
Query: c.Req.Form, Query: c.Req.Form,
Namespaces: namespaces, AllowedNamespaces: allowedNamespaces,
AuthorizeRuleGroup: func(rules []*ngmodels.AlertRule) (bool, error) {
return srv.authz.HasAccessToRuleGroup(c.Req.Context(), c.SignedInUser, rules)
},
}, RuleStatusMutatorGenerator(srv.status), RuleAlertStateMutatorGenerator(srv.manager)) }, RuleStatusMutatorGenerator(srv.status), RuleAlertStateMutatorGenerator(srv.manager))
return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse) return response.JSON(ruleResponse.HTTPStatusCode(), ruleResponse)
@ -411,19 +417,19 @@ func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts Ru
labelOptions = append(labelOptions, ngmodels.WithoutInternalLabels()) labelOptions = append(labelOptions, ngmodels.WithoutInternalLabels())
} }
if len(opts.Namespaces) == 0 { if len(opts.AllowedNamespaces) == 0 {
log.Debug("User does not have access to any namespaces") log.Debug("User does not have access to any namespaces")
return ruleResponse return ruleResponse
} }
namespaceUIDs := make([]string, 0, len(opts.Namespaces)) namespaceUIDs := make([]string, 0, len(opts.AllowedNamespaces))
folderUID := opts.Query.Get("folder_uid") folderUID := opts.Query.Get("folder_uid")
_, exists := opts.Namespaces[folderUID] _, exists := opts.AllowedNamespaces[folderUID]
if folderUID != "" && exists { if folderUID != "" && exists {
namespaceUIDs = append(namespaceUIDs, folderUID) namespaceUIDs = append(namespaceUIDs, folderUID)
} else { } else {
for k := range opts.Namespaces { for k := range opts.AllowedNamespaces {
namespaceUIDs = append(namespaceUIDs, k) namespaceUIDs = append(namespaceUIDs, k)
} }
} }
@ -459,22 +465,11 @@ func PrepareRuleGroupStatuses(log log.Logger, store ListAlertRulesStore, opts Ru
} }
} }
groupedRules := getGroupedRules(log, ruleList, ruleNamesSet, opts.Namespaces) groupedRules := getGroupedRules(log, ruleList, ruleNamesSet, opts.AllowedNamespaces)
rulesTotals := make(map[string]int64, len(groupedRules)) rulesTotals := make(map[string]int64, len(groupedRules))
var newToken string var newToken string
foundToken := false foundToken := false
for _, rg := range groupedRules { for _, rg := range groupedRules {
ok, err := opts.AuthorizeRuleGroup(rg.Rules)
if err != nil {
ruleResponse.Status = "error"
ruleResponse.Error = fmt.Sprintf("cannot authorize access to rule group: %s", err.Error())
ruleResponse.ErrorType = apiv1.ErrServer
return ruleResponse
}
if !ok {
continue
}
if nextToken != "" && !foundToken { if nextToken != "" && !foundToken {
if !tokenGreaterThanOrEqual(getRuleGroupNextToken(rg.Folder, rg.GroupKey.RuleGroup), nextToken) { if !tokenGreaterThanOrEqual(getRuleGroupNextToken(rg.Folder, rg.GroupKey.RuleGroup), nextToken) {
continue continue

@ -129,6 +129,10 @@ var _ ac.AccessControl = &recordingAccessControlFake{}
type fakeRuleAccessControlService struct { type fakeRuleAccessControlService struct {
} }
func (f *fakeRuleAccessControlService) HasAccessInFolder(ctx context.Context, user identity.Requester, folder models.Namespaced) (bool, error) {
return true, nil
}
func (f fakeRuleAccessControlService) HasAccessToRuleGroup(ctx context.Context, user identity.Requester, rules models.RulesGroup) (bool, error) { func (f fakeRuleAccessControlService) HasAccessToRuleGroup(ctx context.Context, user identity.Requester, rules models.RulesGroup) (bool, error) {
return true, nil return true, nil
} }

Loading…
Cancel
Save