Alerting: modify DB table, accessors and migration to restrict org access (#37414)

* Alerting: modify table and accessors to limit org access appropriately

* Update migration to create multiple Alertmanager configs

* Apply suggestions from code review

Co-authored-by: gotjosh <josue@grafana.com>

* replace mg.ClearMigrationEntry()

mg.ClearMigrationEntry() would create a new session.
This commit introduces a new migration for clearing an entry from migration log for replacing  mg.ClearMigrationEntry() so that all dashboard alert migration operations will run inside the same transaction.
It adds also `SkipMigrationLog()` in Migrator interface for skipping adding an entry in the migration_log.

Co-authored-by: gotjosh <josue@grafana.com>
This commit is contained in:
Sofia Papagiannaki
2021-08-12 16:04:09 +03:00
committed by GitHub
parent 4a9fdb8b76
commit 04d5dcb7c8
22 changed files with 460 additions and 167 deletions
@@ -4,8 +4,10 @@ import (
"encoding/json"
"fmt"
"net/http"
"regexp"
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/tests/testinfra"
@@ -16,12 +18,34 @@ import (
func TestAlertmanagerConfigurationIsTransactional(t *testing.T) {
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
EnableFeatureToggles: []string{"ngalert"},
AnonymousUserRole: models.ROLE_EDITOR,
DisableAnonymous: true,
})
store := testinfra.SetUpDatabase(t, dir)
// override bus to get the GetSignedInUserQuery handler
store.Bus = bus.GetBus()
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
alertConfigURL := fmt.Sprintf("http://%s/api/alertmanager/grafana/config/api/v1/alerts", grafanaListedAddr)
// create user under main organisation
userID := createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_EDITOR),
Password: "editor",
Login: "editor",
})
// create another organisation
orgID := createOrg(t, store, "another org", userID)
// create user under different organisation
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_EDITOR),
Password: "editor-42",
Login: "editor-42",
OrgId: orgID,
})
// editor from main organisation requests configuration
alertConfigURL := fmt.Sprintf("http://editor:editor@%s/api/alertmanager/grafana/config/api/v1/alerts", grafanaListedAddr)
// On a blank start with no configuration, it saves and delivers the default configuration.
{
@@ -66,17 +90,48 @@ func TestAlertmanagerConfigurationIsTransactional(t *testing.T) {
resp = getRequest(t, alertConfigURL, http.StatusOK) // nolint
require.JSONEq(t, defaultAlertmanagerConfigJSON, getBody(t, resp.Body))
}
// editor42 from organisation 42 posts configuration
alertConfigURL = fmt.Sprintf("http://editor-42:editor-42@%s/api/alertmanager/grafana/config/api/v1/alerts", grafanaListedAddr)
// Post the alertmanager config.
{
mockChannel := newMockNotificationChannel(t, grafanaListedAddr)
amConfig := getAlertmanagerConfig(mockChannel.server.Addr)
postRequest(t, alertConfigURL, amConfig, http.StatusAccepted) // nolint
// Verifying that the new configuration is returned
resp := getRequest(t, alertConfigURL, http.StatusOK) // nolint
b := getBody(t, resp.Body)
re := regexp.MustCompile(`"uid":"([\w|-]*)"`)
e := getExpAlertmanagerConfigFromAPI(mockChannel.server.Addr)
require.JSONEq(t, e, string(re.ReplaceAll([]byte(b), []byte(`"uid":""`))))
}
// verify that main organisation still gets the default configuration
alertConfigURL = fmt.Sprintf("http://editor:editor@%s/api/alertmanager/grafana/config/api/v1/alerts", grafanaListedAddr)
{
resp := getRequest(t, alertConfigURL, http.StatusOK) // nolint
require.JSONEq(t, defaultAlertmanagerConfigJSON, getBody(t, resp.Body))
}
}
func TestAlertmanagerConfigurationPersistSecrets(t *testing.T) {
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
EnableFeatureToggles: []string{"ngalert"},
AnonymousUserRole: models.ROLE_EDITOR,
DisableAnonymous: true,
})
store := testinfra.SetUpDatabase(t, dir)
// override bus to get the GetSignedInUserQuery handler
store.Bus = bus.GetBus()
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
alertConfigURL := fmt.Sprintf("http://%s/api/alertmanager/grafana/config/api/v1/alerts", grafanaListedAddr)
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_EDITOR),
Password: "editor",
Login: "editor",
})
alertConfigURL := fmt.Sprintf("http://editor:editor@%s/api/alertmanager/grafana/config/api/v1/alerts", grafanaListedAddr)
generatedUID := ""
// create a new configuration that has a secret
+71 -23
View File
@@ -39,9 +39,21 @@ func TestAMConfigAccess(t *testing.T) {
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
// Create a users to make authenticated requests
require.NoError(t, createUser(t, store, models.ROLE_VIEWER, "viewer", "viewer"))
require.NoError(t, createUser(t, store, models.ROLE_EDITOR, "editor", "editor"))
require.NoError(t, createUser(t, store, models.ROLE_ADMIN, "admin", "admin"))
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_VIEWER),
Password: "viewer",
Login: "viewer",
})
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_EDITOR),
Password: "editor",
Login: "editor",
})
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_ADMIN),
Password: "admin",
Login: "admin",
})
type testCase struct {
desc string
@@ -402,7 +414,11 @@ func TestAlertAndGroupsQuery(t *testing.T) {
}
// Create a user to make authenticated requests
require.NoError(t, createUser(t, store, models.ROLE_EDITOR, "grafana", "password"))
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_EDITOR),
Password: "password",
Login: "grafana",
})
// invalid credentials request to get the alerts should fail
{
@@ -554,9 +570,21 @@ func TestRulerAccess(t *testing.T) {
require.NoError(t, err)
// Create a users to make authenticated requests
require.NoError(t, createUser(t, store, models.ROLE_VIEWER, "viewer", "viewer"))
require.NoError(t, createUser(t, store, models.ROLE_EDITOR, "editor", "editor"))
require.NoError(t, createUser(t, store, models.ROLE_ADMIN, "admin", "admin"))
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_VIEWER),
Password: "viewer",
Login: "viewer",
})
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_EDITOR),
Password: "editor",
Login: "editor",
})
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_ADMIN),
Password: "admin",
Login: "admin",
})
// Now, let's test the access policies.
testCases := []struct {
@@ -668,8 +696,16 @@ func TestDeleteFolderWithRules(t *testing.T) {
namespaceUID, err := createFolder(t, store, 0, "default")
require.NoError(t, err)
require.NoError(t, createUser(t, store, models.ROLE_VIEWER, "viewer", "viewer"))
require.NoError(t, createUser(t, store, models.ROLE_EDITOR, "editor", "editor"))
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_VIEWER),
Password: "viewer",
Login: "viewer",
})
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_EDITOR),
Password: "editor",
Login: "editor",
})
createRule(t, grafanaListedAddr, "default", "editor", "editor")
@@ -815,12 +851,14 @@ func TestAlertRuleCRUD(t *testing.T) {
store.Bus = bus.GetBus()
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
err := createUser(t, store, models.ROLE_EDITOR, "grafana", "password")
require.NoError(t, err)
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_EDITOR),
Password: "password",
Login: "grafana",
})
// Create the namespace we'll save our alerts to.
_, err = createFolder(t, store, 0, "default")
_, err := createFolder(t, store, 0, "default")
require.NoError(t, err)
interval, err := model.ParseDuration("1m")
@@ -1827,7 +1865,11 @@ func TestQuota(t *testing.T) {
require.NoError(t, err)
// Create a user to make authenticated requests
require.NoError(t, createUser(t, store, models.ROLE_EDITOR, "grafana", "password"))
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_EDITOR),
Password: "password",
Login: "grafana",
})
interval, err := model.ParseDuration("1m")
require.NoError(t, err)
@@ -1921,7 +1963,11 @@ func TestEval(t *testing.T) {
store.Bus = bus.GetBus()
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
require.NoError(t, createUser(t, store, models.ROLE_EDITOR, "grafana", "password"))
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_EDITOR),
Password: "password",
Login: "grafana",
})
// Create the namespace we'll save our alerts to.
_, err := createFolder(t, store, 0, "default")
@@ -2338,16 +2384,18 @@ func rulesNamespaceWithoutVariableValues(t *testing.T, b []byte) (string, map[st
return string(json), m
}
func createUser(t *testing.T, store *sqlstore.SQLStore, role models.RoleType, username, password string) error {
func createUser(t *testing.T, store *sqlstore.SQLStore, cmd models.CreateUserCommand) int64 {
t.Helper()
cmd := models.CreateUserCommand{
Login: username,
Password: password,
DefaultOrgRole: string(role),
}
_, err := store.CreateUser(context.Background(), cmd)
return err
u, err := store.CreateUser(context.Background(), cmd)
require.NoError(t, err)
return u.Id
}
func createOrg(t *testing.T, store *sqlstore.SQLStore, name string, userID int64) int64 {
org, err := store.CreateOrgWithMember(name, userID)
require.NoError(t, err)
return org.Id
}
func getLongString(t *testing.T, n int) string {
@@ -24,7 +24,11 @@ func TestAvailableChannels(t *testing.T) {
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
// Create a user to make authenticated requests
require.NoError(t, createUser(t, store, models.ROLE_EDITOR, "grafana", "password"))
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_EDITOR),
Password: "password",
Login: "grafana",
})
alertsURL := fmt.Sprintf("http://grafana:password@%s/api/alert-notifiers", grafanaListedAddr)
// nolint:gosec
@@ -68,7 +68,11 @@ func TestNotificationChannels(t *testing.T) {
bus.AddHandlerCtx("", mockEmail.sendEmailCommandHandlerSync)
// Create a user to make authenticated requests
require.NoError(t, createUser(t, s, models.ROLE_EDITOR, "grafana", "password"))
createUser(t, s, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_EDITOR),
Password: "password",
Login: "grafana",
})
{
// There are no notification channel config initially - so it returns the default configuration.
+10 -2
View File
@@ -34,7 +34,11 @@ func TestPrometheusRules(t *testing.T) {
require.NoError(t, err)
// Create a user to make authenticated requests
require.NoError(t, createUser(t, store, models.ROLE_EDITOR, "grafana", "password"))
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_EDITOR),
Password: "password",
Login: "grafana",
})
interval, err := model.ParseDuration("10s")
require.NoError(t, err)
@@ -270,7 +274,11 @@ func TestPrometheusRulesPermissions(t *testing.T) {
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
// Create a user to make authenticated requests
require.NoError(t, createUser(t, store, models.ROLE_EDITOR, "grafana", "password"))
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_EDITOR),
Password: "password",
Login: "grafana",
})
// Create a namespace under default organisation (orgID = 1) where we'll save some alerts.
_, err := createFolder(t, store, 0, "folder1")
+10 -2
View File
@@ -31,7 +31,11 @@ func TestAlertRulePermissions(t *testing.T) {
grafanaListedAddr := testinfra.StartGrafana(t, dir, path, store)
// Create a user to make authenticated requests
require.NoError(t, createUser(t, store, models.ROLE_EDITOR, "grafana", "password"))
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_EDITOR),
Password: "password",
Login: "grafana",
})
// Create the namespace we'll save our alerts to.
_, err := createFolder(t, store, 0, "folder1")
@@ -320,7 +324,11 @@ func TestAlertRuleConflictingTitle(t *testing.T) {
require.NoError(t, err)
// Create user
require.NoError(t, createUser(t, store, models.ROLE_ADMIN, "admin", "admin"))
createUser(t, store, models.CreateUserCommand{
DefaultOrgRole: string(models.ROLE_ADMIN),
Password: "admin",
Login: "admin",
})
interval, err := model.ParseDuration("1m")
require.NoError(t, err)