Alerting: Update rule access control to return errutil errors (#78284)

* update rule access control to return errutil errors
* use alerting in msgID
This commit is contained in:
Yuri Tseretyan
2023-12-02 01:42:11 +02:00
committed by GitHub
parent 6644e5e676
commit 64feeddc23
12 changed files with 187 additions and 102 deletions
+3 -1
View File
@@ -40,8 +40,10 @@ type AlertingStore interface {
}
type RuleAccessControlService interface {
AuthorizeAccessToRuleGroup(ctx context.Context, user identity.Requester, rules models.RulesGroup) bool
HasAccessToRuleGroup(ctx context.Context, user identity.Requester, rules models.RulesGroup) (bool, error)
AuthorizeAccessToRuleGroup(ctx context.Context, user identity.Requester, rules models.RulesGroup) error
AuthorizeRuleChanges(ctx context.Context, user identity.Requester, change *store.GroupDelta) error
AuthorizeDatasourceAccessForRule(ctx context.Context, user identity.Requester, rule *models.AlertRule) error
}
// API handlers.
+5 -1
View File
@@ -235,7 +235,11 @@ func (srv PrometheusSrv) RouteGetRuleStatuses(c *contextmodel.ReqContext) respon
srv.log.Warn("Query returned rules that belong to folder the user does not have access to. All rules that belong to that namespace will not be added to the response", "folder_uid", groupKey.NamespaceUID)
continue
}
if !srv.authz.AuthorizeAccessToRuleGroup(c.Req.Context(), c.SignedInUser, rules) {
ok, err := srv.authz.HasAccessToRuleGroup(c.Req.Context(), c.SignedInUser, rules)
if err != nil {
return response.ErrOrFallback(http.StatusInternalServerError, "cannot authorize access to rule group", err)
}
if !ok {
continue
}
ruleGroup, totals := srv.toRuleGroup(groupKey, folder, rules, limitAlertsPerRule, withStatesFast, matchers, labelOptions)
+15 -11
View File
@@ -25,6 +25,7 @@ import (
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/errutil"
)
type ConditionValidator interface {
@@ -96,7 +97,7 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceT
return err
}
if totalGroups > 0 && len(deletionCandidates) == 0 {
return fmt.Errorf("%w to delete any existing rules in the namespace", accesscontrol.ErrAuthorization)
return accesscontrol.NewAuthorizationErrorGeneric("delete any existing rules in the namespace")
}
}
rulesToDelete := make([]string, 0)
@@ -131,8 +132,8 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceT
})
if err != nil {
if errors.Is(err, accesscontrol.ErrAuthorization) {
return ErrResp(http.StatusUnauthorized, err, "failed to delete rule group")
if errors.As(err, &errutil.Error{}) {
return response.Err(err)
}
if errors.Is(err, errProvisionedResource) {
return ErrResp(http.StatusBadRequest, err, "failed to delete rule group")
@@ -365,14 +366,14 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey
})
if err != nil {
if errors.Is(err, ngmodels.ErrAlertRuleNotFound) {
if errors.As(err, &errutil.Error{}) {
return response.Err(err)
} else if errors.Is(err, ngmodels.ErrAlertRuleNotFound) {
return ErrResp(http.StatusNotFound, err, "failed to update rule group")
} else if errors.Is(err, ngmodels.ErrAlertRuleFailedValidation) || errors.Is(err, errProvisionedResource) {
return ErrResp(http.StatusBadRequest, err, "failed to update rule group")
} else if errors.Is(err, ngmodels.ErrQuotaReached) {
return ErrResp(http.StatusForbidden, err, "")
} else if errors.Is(err, accesscontrol.ErrAuthorization) {
return ErrResp(http.StatusUnauthorized, err, "")
} else if errors.Is(err, store.ErrOptimisticLock) {
return ErrResp(http.StatusConflict, err, "")
}
@@ -521,8 +522,8 @@ func (srv RulerSrv) getAuthorizedRuleByUid(ctx context.Context, c *contextmodel.
if err != nil {
return ngmodels.AlertRule{}, err
}
if !srv.authz.AuthorizeAccessToRuleGroup(ctx, c.SignedInUser, rules) {
return ngmodels.AlertRule{}, fmt.Errorf("%w to access rules in this group", accesscontrol.ErrAuthorization)
if err := srv.authz.AuthorizeAccessToRuleGroup(ctx, c.SignedInUser, rules); err != nil {
return ngmodels.AlertRule{}, err
}
for _, rule := range rules {
if rule.UID == ruleUID {
@@ -545,8 +546,8 @@ func (srv RulerSrv) getAuthorizedRuleGroup(ctx context.Context, c *contextmodel.
if err != nil {
return nil, err
}
if !srv.authz.AuthorizeAccessToRuleGroup(ctx, c.SignedInUser, rules) {
return nil, fmt.Errorf("%w to access rules in this group", accesscontrol.ErrAuthorization)
if err := srv.authz.AuthorizeAccessToRuleGroup(ctx, c.SignedInUser, rules); err != nil {
return nil, err
}
return rules, nil
}
@@ -569,7 +570,10 @@ func (srv RulerSrv) searchAuthorizedAlertRules(ctx context.Context, c *contextmo
byGroupKey := ngmodels.GroupByAlertRuleGroupKey(rules)
totalGroups := len(byGroupKey)
for groupKey, rulesGroup := range byGroupKey {
if !srv.authz.AuthorizeAccessToRuleGroup(ctx, c.SignedInUser, rulesGroup) {
if ok, err := srv.authz.HasAccessToRuleGroup(ctx, c.SignedInUser, rulesGroup); !ok || err != nil {
if err != nil {
return nil, 0, err
}
delete(byGroupKey, groupKey)
}
}
+1 -1
View File
@@ -148,7 +148,7 @@ func (srv RulerSrv) getRulesWithFolderTitleInFolders(c *contextmodel.ReqContext,
}
}
if len(query.NamespaceUIDs) == 0 {
return nil, fmt.Errorf("%w access rules in the specified folders", accesscontrol.ErrAuthorization)
return nil, accesscontrol.NewAuthorizationErrorGeneric("access rules in the specified folders")
}
} else {
for _, folder := range folders {
+6 -8
View File
@@ -2,7 +2,6 @@ package api
import (
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
@@ -21,7 +20,6 @@ import (
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"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/backtesting"
"github.com/grafana/grafana/pkg/services/ngalert/eval"
@@ -65,8 +63,8 @@ func (srv TestingApiSrv) RouteTestGrafanaRuleConfig(c *contextmodel.ReqContext,
return ErrResp(http.StatusBadRequest, err, "")
}
if !srv.authz.AuthorizeAccessToRuleGroup(c.Req.Context(), c.SignedInUser, ngmodels.RulesGroup{rule}) {
return errorToResponse(fmt.Errorf("%w to query one or many data sources used by the rule", accesscontrol.ErrAuthorization))
if err := srv.authz.AuthorizeAccessToRuleGroup(c.Req.Context(), c.SignedInUser, ngmodels.RulesGroup{rule}); err != nil {
return response.ErrOrFallback(http.StatusInternalServerError, "failed to authorize access to rule group", err)
}
if _, err := store.OptimizeAlertQueries(rule.Data); err != nil {
@@ -153,8 +151,8 @@ func (srv TestingApiSrv) RouteTestRuleConfig(c *contextmodel.ReqContext, body ap
func (srv TestingApiSrv) RouteEvalQueries(c *contextmodel.ReqContext, cmd apimodels.EvalQueriesPayload) response.Response {
queries := AlertQueriesFromApiAlertQueries(cmd.Data)
if !srv.authz.AuthorizeAccessToRuleGroup(c.Req.Context(), c.SignedInUser, ngmodels.RulesGroup{&ngmodels.AlertRule{Data: queries}}) {
return ErrResp(http.StatusUnauthorized, fmt.Errorf("%w to query one or many data sources used by the rule", accesscontrol.ErrAuthorization), "")
if err := srv.authz.AuthorizeDatasourceAccessForRule(c.Req.Context(), c.SignedInUser, &ngmodels.AlertRule{Data: queries}); err != nil {
return response.ErrOrFallback(http.StatusInternalServerError, "failed to authorize access to data sources", err)
}
cond := ngmodels.Condition{
@@ -215,8 +213,8 @@ func (srv TestingApiSrv) BacktestAlertRule(c *contextmodel.ReqContext, cmd apimo
}
queries := AlertQueriesFromApiAlertQueries(cmd.Data)
if !srv.authz.AuthorizeAccessToRuleGroup(c.Req.Context(), c.SignedInUser, ngmodels.RulesGroup{&ngmodels.AlertRule{Data: queries}}) {
return errorToResponse(fmt.Errorf("%w to query one or many data sources used by the rule", accesscontrol.ErrAuthorization))
if err := srv.authz.AuthorizeAccessToRuleGroup(c.Req.Context(), c.SignedInUser, ngmodels.RulesGroup{&ngmodels.AlertRule{Data: queries}}); err != nil {
return errorToResponse(err)
}
rule := &ngmodels.AlertRule{
+4 -4
View File
@@ -6,8 +6,8 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/util/errutil"
)
var (
@@ -29,15 +29,15 @@ func backendTypeDoesNotMatchPayloadTypeError(backendType apimodels.Backend, payl
}
func errorToResponse(err error) response.Response {
if errors.As(err, &errutil.Error{}) {
return response.Err(err)
}
if errors.Is(err, datasources.ErrDataSourceNotFound) {
return ErrResp(404, err, "")
}
if errors.Is(err, errUnexpectedDatasourceType) {
return ErrResp(400, err, "")
}
if errors.Is(err, accesscontrol.ErrAuthorization) {
return ErrResp(401, err, "")
}
if errors.Is(err, errFolderAccess) {
return toNamespaceErrorResponse(err)
}
+10 -2
View File
@@ -145,10 +145,18 @@ var _ accesscontrol.AccessControl = &recordingAccessControlFake{}
type fakeRuleAccessControlService struct {
}
func (f fakeRuleAccessControlService) AuthorizeAccessToRuleGroup(ctx context.Context, user identity.Requester, rules models.RulesGroup) bool {
return true
func (f fakeRuleAccessControlService) HasAccessToRuleGroup(ctx context.Context, user identity.Requester, rules models.RulesGroup) (bool, error) {
return true, nil
}
func (f fakeRuleAccessControlService) AuthorizeAccessToRuleGroup(ctx context.Context, user identity.Requester, rules models.RulesGroup) error {
return nil
}
func (f fakeRuleAccessControlService) AuthorizeRuleChanges(ctx context.Context, user identity.Requester, change *store.GroupDelta) error {
return nil
}
func (f fakeRuleAccessControlService) AuthorizeDatasourceAccessForRule(ctx context.Context, user identity.Requester, rule *models.AlertRule) error {
return nil
}