Alerting: Update authorization rules for RouteGetNamespaceRulesConfig (#50965)

* use authorizeAccessToRuleGroup
* use toGettableRuleGroupConfig in get by namespace
* add comments for controller methods
This commit is contained in:
Yuriy Tseretyan 2022-06-17 13:55:31 -04:00 committed by GitHub
parent 4489f331b8
commit 81089b956a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 97 additions and 28 deletions

View File

@ -157,6 +157,7 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *models.ReqContext) response.Respons
return response.JSON(http.StatusAccepted, util.DynMap{"message": "rules deleted"})
}
// RouteGetNamespaceRulesConfig returns all rules in a specific folder that user has access to
func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.Response {
namespaceTitle := web.Params(c.Req)[":Namespace"]
namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, false)
@ -173,7 +174,6 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.
}
result := apimodels.NamespaceConfigResponse{}
ruleGroupConfigs := make(map[string]apimodels.GettableRuleGroupConfig)
hasAccess := func(evaluator accesscontrol.Evaluator) bool {
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqViewer, evaluator)
@ -184,33 +184,23 @@ func (srv RulerSrv) RouteGetNamespaceRulesConfig(c *models.ReqContext) response.
return ErrResp(http.StatusInternalServerError, err, "failed to get provenance for rule group")
}
ruleGroups := make(map[string][]*ngmodels.AlertRule)
for _, r := range q.Result {
if !authorizeDatasourceAccessForRule(r, hasAccess) {
continue
}
ruleGroupConfig, ok := ruleGroupConfigs[r.RuleGroup]
if !ok {
ruleGroupInterval := model.Duration(time.Duration(r.IntervalSeconds) * time.Second)
ruleGroupConfigs[r.RuleGroup] = apimodels.GettableRuleGroupConfig{
Name: r.RuleGroup,
Interval: ruleGroupInterval,
Rules: []apimodels.GettableExtendedRuleNode{
toGettableExtendedRuleNode(*r, namespace.Id, provenanceRecords),
},
}
} else {
ruleGroupConfig.Rules = append(ruleGroupConfig.Rules, toGettableExtendedRuleNode(*r, namespace.Id, provenanceRecords))
ruleGroupConfigs[r.RuleGroup] = ruleGroupConfig
}
ruleGroups[r.RuleGroup] = append(ruleGroups[r.RuleGroup], r)
}
for _, ruleGroupConfig := range ruleGroupConfigs {
result[namespaceTitle] = append(result[namespaceTitle], ruleGroupConfig)
for groupName, rules := range ruleGroups {
if !authorizeAccessToRuleGroup(rules, hasAccess) {
continue
}
result[namespaceTitle] = append(result[namespaceTitle], toGettableRuleGroupConfig(groupName, rules, namespace.Id, provenanceRecords))
}
return response.JSON(http.StatusAccepted, result)
}
// RouteGetRulesGroupConfig returns rules that belong to a specific group in a specific namespace (folder).
// If user does not have access to at least one of the rule in the group, returns status 401 Unauthorized
func (srv RulerSrv) RouteGetRulesGroupConfig(c *models.ReqContext) response.Response {
namespaceTitle := web.Params(c.Req)[":Namespace"]
namespace, err := srv.store.GetNamespaceByTitle(c.Req.Context(), namespaceTitle, c.SignedInUser.OrgId, c.SignedInUser, false)
@ -237,20 +227,17 @@ func (srv RulerSrv) RouteGetRulesGroupConfig(c *models.ReqContext) response.Resp
return ErrResp(http.StatusInternalServerError, err, "failed to get group alert rules")
}
groupRules := make([]*ngmodels.AlertRule, 0, len(q.Result))
for _, r := range q.Result {
if !authorizeDatasourceAccessForRule(r, hasAccess) {
return ErrResp(http.StatusUnauthorized, fmt.Errorf("%w to access the group because it does not have access to one or many data sources one or many rules in the group use", ErrAuthorization), "")
}
groupRules = append(groupRules, r)
if !authorizeAccessToRuleGroup(q.Result, hasAccess) {
return ErrResp(http.StatusUnauthorized, fmt.Errorf("%w to access the group because it does not have access to one or many data sources one or many rules in the group use", ErrAuthorization), "")
}
result := apimodels.RuleGroupConfigResponse{
GettableRuleGroupConfig: toGettableRuleGroupConfig(ruleGroup, groupRules, namespace.Id, provenanceRecords),
GettableRuleGroupConfig: toGettableRuleGroupConfig(ruleGroup, q.Result, namespace.Id, provenanceRecords),
}
return response.JSON(http.StatusAccepted, result)
}
// RouteGetRulesConfig returns all alert rules that are available to the current user
func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response {
namespaceMap, err := srv.store.GetUserVisibleNamespaces(c.Req.Context(), c.OrgId, c.SignedInUser)
if err != nil {

View File

@ -6,6 +6,7 @@ import (
"errors"
"math/rand"
"net/http"
"net/url"
"testing"
"time"
@ -659,6 +660,84 @@ func TestRouteGetNamespaceRulesConfig(t *testing.T) {
})
}
func TestRouteGetRulesConfig(t *testing.T) {
t.Run("fine-grained access is enabled", func(t *testing.T) {
t.Run("should check access to data source", func(t *testing.T) {
orgID := rand.Int63()
ruleStore := store.NewFakeRuleStore(t)
folder1 := randFolder()
folder2 := randFolder()
ruleStore.Folders[orgID] = []*models2.Folder{folder1, folder2}
group1Key := models.GenerateGroupKey(orgID)
group1Key.NamespaceUID = folder1.Uid
group2Key := models.GenerateGroupKey(orgID)
group2Key.NamespaceUID = folder2.Uid
group1 := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(group1Key)))
group2 := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(group2Key)))
ruleStore.PutRule(context.Background(), append(group1, group2...)...)
request := createRequestContext(orgID, "", nil)
t.Run("and do not return group if user does not have access to one of rules", func(t *testing.T) {
ac := acMock.New().WithPermissions(createPermissionsForRules(append(group1, group2[1:]...)))
response := createService(ac, ruleStore, nil).RouteGetRulesConfig(request)
require.Equal(t, http.StatusOK, response.Status())
result := &apimodels.NamespaceConfigResponse{}
require.NoError(t, json.Unmarshal(response.Body(), result))
require.NotNil(t, result)
require.Contains(t, *result, folder1.Title)
require.NotContains(t, *result, folder2.Title)
groups := (*result)[folder1.Title]
require.Len(t, groups, 1)
require.Equal(t, group1Key.RuleGroup, groups[0].Name)
require.Len(t, groups[0].Rules, len(group1))
})
})
})
}
func TestRouteGetRulesGroupConfig(t *testing.T) {
t.Run("fine-grained access is enabled", func(t *testing.T) {
t.Run("should check access to data source", func(t *testing.T) {
orgID := rand.Int63()
folder := randFolder()
ruleStore := store.NewFakeRuleStore(t)
ruleStore.Folders[orgID] = append(ruleStore.Folders[orgID], folder)
groupKey := models.GenerateGroupKey(orgID)
groupKey.NamespaceUID = folder.Uid
expectedRules := models.GenerateAlertRules(rand.Intn(4)+2, models.AlertRuleGen(withGroupKey(groupKey)))
ruleStore.PutRule(context.Background(), expectedRules...)
request := createRequestContext(orgID, "", map[string]string{
":Namespace": folder.Title,
":Groupname": groupKey.RuleGroup,
})
t.Run("and return 401 if user does not have access one of rules", func(t *testing.T) {
ac := acMock.New().WithPermissions(createPermissionsForRules(expectedRules[1:]))
response := createService(ac, ruleStore, nil).RouteGetRulesGroupConfig(request)
require.Equal(t, http.StatusUnauthorized, response.Status())
})
t.Run("and return rules if user has access to all of them", func(t *testing.T) {
ac := acMock.New().WithPermissions(createPermissionsForRules(expectedRules))
response := createService(ac, ruleStore, nil).RouteGetRulesGroupConfig(request)
require.Equal(t, http.StatusAccepted, response.Status())
result := &apimodels.RuleGroupConfigResponse{}
require.NoError(t, json.Unmarshal(response.Body(), result))
require.NotNil(t, result)
require.Len(t, result.Rules, len(expectedRules))
})
})
})
}
func TestVerifyProvisionedRulesNotAffected(t *testing.T) {
orgID := rand.Int63()
group := models.GenerateGroupKey(orgID)
@ -735,7 +814,10 @@ func createService(ac *acMock.Mock, store *store.FakeRuleStore, scheduler schedu
}
func createRequestContext(orgID int64, role models2.RoleType, params map[string]string) *models2.ReqContext {
ctx := web.Context{Req: &http.Request{}}
uri, _ := url.Parse("http://localhost")
ctx := web.Context{Req: &http.Request{
URL: uri,
}}
ctx.Req = web.SetURLParams(ctx.Req, params)
return &models2.ReqContext{