Alerting: Remove requirement for datasource query on rule read (#87349)
* Remove requirement for datasource query for rule read * Address PR comments
This commit is contained in:
@@ -18,7 +18,7 @@ import (
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
authz "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
@@ -110,22 +110,36 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceU
|
||||
deletionCandidates[key] = rules
|
||||
} else {
|
||||
var totalGroups int
|
||||
deletionCandidates, totalGroups, err = srv.searchAuthorizedAlertRules(ctx, c, []string{namespace.UID}, "", 0)
|
||||
deletionCandidates, totalGroups, err = srv.searchAuthorizedAlertRules(ctx, authorizedRuleGroupQuery{
|
||||
User: c.SignedInUser,
|
||||
NamespaceUIDs: []string{namespace.UID},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if totalGroups > 0 && len(deletionCandidates) == 0 {
|
||||
return accesscontrol.NewAuthorizationErrorGeneric("delete any existing rules in the namespace")
|
||||
return authz.NewAuthorizationErrorGeneric("delete any existing rules in the namespace due to missing data source query permissions")
|
||||
}
|
||||
}
|
||||
rulesToDelete := make([]string, 0)
|
||||
provisioned := false
|
||||
auth := true
|
||||
for groupKey, rules := range deletionCandidates {
|
||||
if containsProvisionedAlerts(provenances, rules) {
|
||||
logger.Debug("Alert group cannot be deleted because it is provisioned", "group", groupKey.RuleGroup)
|
||||
provisioned = true
|
||||
continue
|
||||
}
|
||||
// XXX: Currently delete requires data source query access to all rules in the group.
|
||||
if err := srv.authz.AuthorizeDatasourceAccessForRuleGroup(ctx, c.SignedInUser, rules); err != nil {
|
||||
if errors.Is(err, authz.ErrAuthorizationBase) {
|
||||
logger.Debug("User is not authorized to delete rules in the group", "group", groupKey.RuleGroup)
|
||||
auth = false
|
||||
continue
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
uid := make([]string, 0, len(rules))
|
||||
for _, rule := range rules {
|
||||
uid = append(uid, rule.UID)
|
||||
@@ -141,10 +155,17 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceU
|
||||
return nil
|
||||
}
|
||||
// if none rules were deleted return an error.
|
||||
|
||||
// Check whether provisioned check failed first because if it is true, then all rules that the user can access (actually read via GET API) are provisioned.
|
||||
if provisioned {
|
||||
return errProvisionedResource
|
||||
}
|
||||
|
||||
// If auth is false, then the user is not authorized to delete any of the rules.
|
||||
if !auth {
|
||||
return authz.NewAuthorizationErrorGeneric("delete any existing rules in the namespace")
|
||||
}
|
||||
|
||||
logger.Info("No alert rules were deleted")
|
||||
return nil
|
||||
})
|
||||
@@ -168,7 +189,10 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *contextmodel.ReqContext, nam
|
||||
return toNamespaceErrorResponse(err)
|
||||
}
|
||||
|
||||
ruleGroups, _, err := srv.searchAuthorizedAlertRules(c.Req.Context(), c, []string{namespace.UID}, "", 0)
|
||||
ruleGroups, _, err := srv.searchAuthorizedAlertRules(c.Req.Context(), authorizedRuleGroupQuery{
|
||||
User: c.SignedInUser,
|
||||
NamespaceUIDs: []string{namespace.UID},
|
||||
})
|
||||
if err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
@@ -242,7 +266,12 @@ func (srv RulerSrv) RouteGetRulesConfig(c *contextmodel.ReqContext) response.Res
|
||||
return ErrResp(http.StatusBadRequest, errors.New("panel_id must be set with dashboard_uid"), "")
|
||||
}
|
||||
|
||||
configs, _, err := srv.searchAuthorizedAlertRules(c.Req.Context(), c, namespaceUIDs, dashboardUID, panelID)
|
||||
configs, _, err := srv.searchAuthorizedAlertRules(c.Req.Context(), authorizedRuleGroupQuery{
|
||||
User: c.SignedInUser,
|
||||
NamespaceUIDs: namespaceUIDs,
|
||||
DashboardUID: dashboardUID,
|
||||
PanelID: panelID,
|
||||
})
|
||||
if err != nil {
|
||||
return errorToResponse(err)
|
||||
}
|
||||
@@ -635,7 +664,6 @@ func (srv RulerSrv) getAuthorizedRuleByUid(ctx context.Context, c *contextmodel.
|
||||
}
|
||||
|
||||
// getAuthorizedRuleGroup fetches rules that belong to the specified models.AlertRuleGroupKey and validate user's authorization.
|
||||
// A user is authorized to access a group of rules only when it has permission to query all data sources used by all rules in this group.
|
||||
// Returns models.RuleGroup if authorization passed or ErrAuthorization if user is not authorized to access the rule.
|
||||
func (srv RulerSrv) getAuthorizedRuleGroup(ctx context.Context, c *contextmodel.ReqContext, ruleGroupKey ngmodels.AlertRuleGroupKey) (ngmodels.RulesGroup, error) {
|
||||
q := ngmodels.ListAlertRulesQuery{
|
||||
@@ -653,15 +681,21 @@ func (srv RulerSrv) getAuthorizedRuleGroup(ctx context.Context, c *contextmodel.
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
type authorizedRuleGroupQuery struct {
|
||||
User identity.Requester
|
||||
NamespaceUIDs []string
|
||||
DashboardUID string
|
||||
PanelID int64
|
||||
}
|
||||
|
||||
// searchAuthorizedAlertRules fetches rules according to the filters, groups them by models.AlertRuleGroupKey and filters out groups that the current user is not authorized to access.
|
||||
// A user is authorized to access a group of rules only when it has permission to query all data sources used by all rules in this group.
|
||||
// Returns groups that user is authorized to access, and total count of groups returned by query
|
||||
func (srv RulerSrv) searchAuthorizedAlertRules(ctx context.Context, c *contextmodel.ReqContext, folderUIDs []string, dashboardUID string, panelID int64) (map[ngmodels.AlertRuleGroupKey]ngmodels.RulesGroup, int, error) {
|
||||
func (srv RulerSrv) searchAuthorizedAlertRules(ctx context.Context, q authorizedRuleGroupQuery) (map[ngmodels.AlertRuleGroupKey]ngmodels.RulesGroup, int, error) {
|
||||
query := ngmodels.ListAlertRulesQuery{
|
||||
OrgID: c.SignedInUser.GetOrgID(),
|
||||
NamespaceUIDs: folderUIDs,
|
||||
DashboardUID: dashboardUID,
|
||||
PanelID: panelID,
|
||||
OrgID: q.User.GetOrgID(),
|
||||
NamespaceUIDs: q.NamespaceUIDs,
|
||||
DashboardUID: q.DashboardUID,
|
||||
PanelID: q.PanelID,
|
||||
}
|
||||
rules, err := srv.store.ListAlertRules(ctx, &query)
|
||||
if err != nil {
|
||||
@@ -671,7 +705,7 @@ func (srv RulerSrv) searchAuthorizedAlertRules(ctx context.Context, c *contextmo
|
||||
byGroupKey := ngmodels.GroupByAlertRuleGroupKey(rules)
|
||||
totalGroups := len(byGroupKey)
|
||||
for groupKey, rulesGroup := range byGroupKey {
|
||||
if ok, err := srv.authz.HasAccessToRuleGroup(ctx, c.SignedInUser, rulesGroup); !ok || err != nil {
|
||||
if ok, err := srv.authz.HasAccessToRuleGroup(ctx, q.User, rulesGroup); !ok || err != nil {
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user