Alerting: Stop returning autogen routes for non-admin on api/v2/status (#84864)

* Alerting: Stop returning autogen routes for non-admin on api/v2/status

* Improve api/v2/status integration tests for user roles
This commit is contained in:
Matthew Jacobson 2024-03-20 16:04:35 -04:00 committed by GitHub
parent 7eee34311d
commit fbd057b258
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 125 additions and 17 deletions

View File

@ -50,7 +50,12 @@ func (srv AlertmanagerSrv) RouteGetAMStatus(c *contextmodel.ReqContext) response
return errResp
}
return response.JSON(http.StatusOK, am.GetStatus())
status := am.GetStatus()
if !c.SignedInUser.HasRole(org.RoleAdmin) {
notifier.RemoveAutogenConfigIfExists(status.Config.Route)
}
return response.JSON(http.StatusOK, status)
}
func (srv AlertmanagerSrv) RouteCreateSilence(c *contextmodel.ReqContext, postableSilence apimodels.PostableSilence) response.Response {

View File

@ -405,6 +405,53 @@ func TestAlertmanagerAutogenConfig(t *testing.T) {
compare(t, validConfigWithoutAutogen, string(response.Body()))
})
})
t.Run("route GET status", func(t *testing.T) {
t.Run("when admin return autogen routes", func(t *testing.T) {
sut, _ := createSutForAutogen(t)
rc := createRequestCtxInOrg(2)
rc.SignedInUser.OrgRole = org.RoleAdmin
response := sut.RouteGetAMStatus(rc)
require.Equal(t, 200, response.Status())
var status struct {
Config apimodels.PostableApiAlertingConfig `json:"config"`
}
err := json.Unmarshal(response.Body(), &status)
require.NoError(t, err)
configBody, err := json.Marshal(apimodels.PostableUserConfig{
TemplateFiles: map[string]string{"a": "template"},
AlertmanagerConfig: status.Config,
})
require.NoError(t, err)
compare(t, validConfigWithAutogen, string(configBody))
})
t.Run("when not admin return no autogen routes", func(t *testing.T) {
sut, _ := createSutForAutogen(t)
rc := createRequestCtxInOrg(2)
response := sut.RouteGetAMStatus(rc)
require.Equal(t, 200, response.Status())
var status struct {
Config apimodels.PostableApiAlertingConfig `json:"config"`
}
err := json.Unmarshal(response.Body(), &status)
require.NoError(t, err)
configBody, err := json.Marshal(apimodels.PostableUserConfig{
TemplateFiles: map[string]string{"a": "template"},
AlertmanagerConfig: status.Config,
})
require.NoError(t, err)
compare(t, validConfigWithoutAutogen, string(configBody))
})
})
}
func TestRouteGetAlertingConfigHistory(t *testing.T) {

View File

@ -2000,25 +2000,37 @@ func TestIntegrationAlertmanagerStatus(t *testing.T) {
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
})
grafanaListedAddr, _ := testinfra.StartGrafana(t, dir, path)
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
// Get the Alertmanager current status.
{
alertsURL := fmt.Sprintf("http://%s/api/alertmanager/grafana/api/v2/status", grafanaListedAddr)
// nolint:gosec
resp, err := http.Get(alertsURL)
require.NoError(t, err)
t.Cleanup(func() {
err := resp.Body.Close()
require.NoError(t, err)
})
b, err := io.ReadAll(resp.Body)
require.NoError(t, err)
require.Equal(t, 200, resp.StatusCode)
require.JSONEq(t, `
// Create a users to make authenticated requests
createUser(t, store, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleViewer),
Password: "viewer",
Login: "viewer",
})
createUser(t, store, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleEditor),
Password: "editor",
Login: "editor",
})
createUser(t, store, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleAdmin),
Password: "admin",
Login: "admin",
})
type testCase struct {
desc string
url string
expStatus int
expBody string
}
cfgBody := `
{
"cluster": {
"peers": [],
@ -2054,7 +2066,51 @@ func TestIntegrationAlertmanagerStatus(t *testing.T) {
"version": "N/A"
}
}
`, string(b))
`
testCases := []testCase{
{
desc: "un-authenticated request should fail",
url: "http://%s/api/alertmanager/grafana/api/v2/status",
expStatus: http.StatusUnauthorized,
expBody: `{"extra":null,"message":"Unauthorized","messageId":"auth.unauthorized","statusCode":401,"traceID":""}`,
},
{
desc: "viewer request should succeed",
url: "http://viewer:viewer@%s/api/alertmanager/grafana/api/v2/status",
expStatus: http.StatusOK,
expBody: cfgBody,
},
{
desc: "editor request should succeed",
url: "http://editor:editor@%s/api/alertmanager/grafana/api/v2/status",
expStatus: http.StatusOK,
expBody: cfgBody,
},
{
desc: "admin request should succeed",
url: "http://admin:admin@%s/api/alertmanager/grafana/api/v2/status",
expStatus: http.StatusOK,
expBody: cfgBody,
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
resp, err := http.Get(fmt.Sprintf(tc.url, grafanaListedAddr))
t.Cleanup(func() {
require.NoError(t, resp.Body.Close())
})
require.NoError(t, err)
require.Equal(t, tc.expStatus, resp.StatusCode)
b, err := io.ReadAll(resp.Body)
if tc.expStatus == http.StatusOK {
re := regexp.MustCompile(`"uid":"([\w|-]+)"`)
b = re.ReplaceAll(b, []byte(`"uid":""`))
}
require.NoError(t, err)
require.JSONEq(t, tc.expBody, string(b))
})
}
}