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:
committed by
GitHub
parent
4a9fdb8b76
commit
04d5dcb7c8
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user