Alerting: Disallow invalid rule namespace UIDs in provisioning API (#83938)

* Disallow invalid rule namespace UIDs in provisioning

Reject requests with rules that reference a nonexistent folder or have an empty folder uid
This commit is contained in:
William Wernert
2024-03-14 09:58:25 -04:00
committed by GitHub
parent 8e90e02db2
commit 8690a42e33
9 changed files with 134 additions and 21 deletions
+2 -3
View File
@@ -60,7 +60,7 @@ type MuteTimingService interface {
type AlertRuleService interface {
GetAlertRules(ctx context.Context, orgID int64) ([]*alerting_models.AlertRule, map[string]alerting_models.Provenance, error)
GetAlertRule(ctx context.Context, orgID int64, ruleUID string) (alerting_models.AlertRule, alerting_models.Provenance, error)
CreateAlertRule(ctx context.Context, rule alerting_models.AlertRule, provenance alerting_models.Provenance, userID int64) (alerting_models.AlertRule, error)
CreateAlertRule(ctx context.Context, user identity.Requester, rule alerting_models.AlertRule, provenance alerting_models.Provenance) (alerting_models.AlertRule, error)
UpdateAlertRule(ctx context.Context, rule alerting_models.AlertRule, provenance alerting_models.Provenance) (alerting_models.AlertRule, error)
DeleteAlertRule(ctx context.Context, orgID int64, ruleUID string, provenance alerting_models.Provenance) error
GetRuleGroup(ctx context.Context, orgID int64, folder, group string) (alerting_models.AlertRuleGroup, error)
@@ -334,8 +334,7 @@ func (srv *ProvisioningSrv) RoutePostAlertRule(c *contextmodel.ReqContext, ar de
return ErrResp(http.StatusBadRequest, err, "")
}
provenance := determineProvenance(c)
userID, _ := identity.UserIdentifier(c.SignedInUser.GetNamespacedID())
createdAlertRule, err := srv.alertRules.CreateAlertRule(c.Req.Context(), upstreamModel, alerting_models.Provenance(provenance), userID)
createdAlertRule, err := srv.alertRules.CreateAlertRule(c.Req.Context(), c.SignedInUser, upstreamModel, alerting_models.Provenance(provenance))
if errors.Is(err, alerting_models.ErrAlertRuleFailedValidation) {
return ErrResp(http.StatusBadRequest, err, "")
}
@@ -26,6 +26,8 @@ import (
"github.com/grafana/grafana/pkg/services/accesscontrol"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/foldertest"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
@@ -271,6 +273,39 @@ func TestProvisioningApi(t *testing.T) {
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "invalid alert rule")
})
t.Run("POST returns 400 when folderUID not set", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
rule := createTestAlertRule("rule", 1)
rule.FolderUID = ""
response := sut.RoutePostAlertRule(&rc, rule)
require.Equal(t, 400, response.Status())
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "invalid alert rule")
require.Contains(t, string(response.Body()), "folderUID must be set")
})
t.Run("POST returns 400 if folder does not exist", func(t *testing.T) {
testEnv := createTestEnv(t, testConfig)
// Create a fake folder service that will return an error when trying to get a folder.
folderService := foldertest.NewFakeService()
folderService.ExpectedError = dashboards.ErrFolderNotFound
testEnv.folderService = folderService
sut := createProvisioningSrvSutFromEnv(t, &testEnv)
rc := createTestRequestCtx()
rule := createTestAlertRule("rule", 1)
response := sut.RoutePostAlertRule(&rc, rule)
require.Equal(t, 400, response.Status())
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "invalid alert rule")
require.Contains(t, string(response.Body()), "folder does not exist")
})
})
t.Run("exist in non-default orgs", func(t *testing.T) {
@@ -1571,6 +1606,7 @@ type testEnvironment struct {
secrets secrets.Service
log log.Logger
store store.DBstore
folderService folder.Service
dashboardService dashboards.DashboardService
configs provisioning.AMConfigStore
xact provisioning.TransactionManager
@@ -1602,12 +1638,18 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
AlertmanagerConfiguration: string(raw),
})
sqlStore := db.InitTestDB(t)
// init folder service with default folder
folderService := foldertest.NewFakeService()
folderService.ExpectedFolder = &folder.Folder{}
store := store.DBstore{
Logger: log,
SQLStore: sqlStore,
Cfg: setting.UnifiedAlertingSettings{
BaseInterval: time.Second * 10,
},
FolderService: folderService,
}
quotas := &provisioning.MockQuotaChecker{}
quotas.EXPECT().LimitOK()
@@ -1637,6 +1679,7 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
log: log,
configs: configs,
store: store,
folderService: folderService,
dashboardService: dashboardService,
xact: xact,
prov: prov,
@@ -1662,7 +1705,7 @@ func createProvisioningSrvSutFromEnv(t *testing.T, env *testEnvironment) Provisi
contactPointService: provisioning.NewContactPointService(env.configs, env.secrets, env.prov, env.xact, receiverSvc, env.log, env.store),
templates: provisioning.NewTemplateService(env.configs, env.prov, env.xact, env.log),
muteTimings: provisioning.NewMuteTimingService(env.configs, env.prov, env.xact, env.log),
alertRules: provisioning.NewAlertRuleService(env.store, env.prov, env.dashboardService, env.quotas, env.xact, 60, 10, 100, env.log, &provisioning.NotificationSettingsValidatorProviderFake{}),
alertRules: provisioning.NewAlertRuleService(env.store, env.prov, env.folderService, env.dashboardService, env.quotas, env.xact, 60, 10, 100, env.log, &provisioning.NotificationSettingsValidatorProviderFake{}),
}
}
+1
View File
@@ -14,6 +14,7 @@ type RuleStore interface {
// by returning map[string]struct{} instead of map[string]*folder.Folder
GetUserVisibleNamespaces(context.Context, int64, identity.Requester) (map[string]*folder.Folder, error)
GetNamespaceByUID(ctx context.Context, uid string, orgID int64, user identity.Requester) (*folder.Folder, error)
GetAlertRulesGroupByRuleUID(ctx context.Context, query *ngmodels.GetAlertRulesGroupByRuleUIDQuery) ([]*ngmodels.AlertRule, error)
ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) (ngmodels.RulesGroup, error)