From 4c1cb7906900698f2ba792270775141a60504167 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Mon, 7 Oct 2024 15:21:29 -0400 Subject: [PATCH] Alerting: Add access control to receiver status API (#94287) --- pkg/services/ngalert/api/api.go | 1 + pkg/services/ngalert/api/api_alertmanager.go | 23 +++++++++++++++++++- pkg/services/ngalert/api/authorization.go | 6 ++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/pkg/services/ngalert/api/api.go b/pkg/services/ngalert/api/api.go index 5057b831b08..55d29355952 100644 --- a/pkg/services/ngalert/api/api.go +++ b/pkg/services/ngalert/api/api.go @@ -110,6 +110,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) { api.RuleStore, ruleAuthzService, ), + receiverAuthz: accesscontrol.NewReceiverAccess[ReceiverStatus](api.AccessControl, false), }, ), m) // Register endpoints for proxying to Prometheus-compatible backends. diff --git a/pkg/services/ngalert/api/api_alertmanager.go b/pkg/services/ngalert/api/api_alertmanager.go index 417beb74285..1d174a149c7 100644 --- a/pkg/services/ngalert/api/api_alertmanager.go +++ b/pkg/services/ngalert/api/api_alertmanager.go @@ -12,12 +12,14 @@ import ( alertingNotify "github.com/grafana/alerting/notify" "github.com/grafana/grafana/pkg/api/response" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/featuremgmt" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/notifier" + "github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage" "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/util" @@ -28,6 +30,10 @@ const ( maxTestReceiversTimeout = 30 * time.Second ) +type receiversAuthz interface { + FilterRead(ctx context.Context, user identity.Requester, receivers ...ReceiverStatus) ([]ReceiverStatus, error) +} + type AlertmanagerSrv struct { log log.Logger ac accesscontrol.AccessControl @@ -35,6 +41,7 @@ type AlertmanagerSrv struct { crypto notifier.Crypto silenceSvc SilenceService featureManager featuremgmt.FeatureToggles + receiverAuthz receiversAuthz } type UnknownReceiverError struct { @@ -237,7 +244,15 @@ func (srv AlertmanagerSrv) RouteGetReceivers(c *contextmodel.ReqContext) respons if err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to retrieve receivers") } - return response.JSON(http.StatusOK, rcvs) + statuses := make([]ReceiverStatus, 0, len(rcvs)) + for _, rcv := range rcvs { // TODO this is temporary so we can use authz filter logic. + statuses = append(statuses, ReceiverStatus(rcv)) + } + statuses, err = srv.receiverAuthz.FilterRead(c.Req.Context(), c.SignedInUser, statuses...) + if err != nil { + response.ErrOrFallback(http.StatusInternalServerError, "failed to apply permissions to the receivers", err) + } + return response.JSON(http.StatusOK, statuses) } func (srv AlertmanagerSrv) RoutePostTestReceivers(c *contextmodel.ReqContext, body apimodels.TestReceiversConfigBodyParams) response.Response { @@ -368,3 +383,9 @@ func (srv AlertmanagerSrv) AlertmanagerFor(orgID int64) (notifier.Alertmanager, srv.log.Error("Unable to obtain the org's Alertmanager", "error", err) return nil, response.Error(http.StatusInternalServerError, "unable to obtain org's Alertmanager", err) } + +type ReceiverStatus apimodels.Receiver + +func (rs ReceiverStatus) GetUID() string { + return legacy_storage.NameToUid(rs.Name) +} diff --git a/pkg/services/ngalert/api/authorization.go b/pkg/services/ngalert/api/authorization.go index f9b14ad7eea..083ae995637 100644 --- a/pkg/services/ngalert/api/authorization.go +++ b/pkg/services/ngalert/api/authorization.go @@ -210,7 +210,11 @@ func (api *API) authorize(method, path string) web.Handler { case http.MethodPost + "/api/alertmanager/grafana/config/history/{id}/_activate": eval = ac.EvalAny(ac.EvalPermission(ac.ActionAlertingNotificationsWrite)) case http.MethodGet + "/api/alertmanager/grafana/config/api/v1/receivers": - eval = ac.EvalPermission(ac.ActionAlertingNotificationsRead) + eval = ac.EvalAny( + ac.EvalPermission(ac.ActionAlertingNotificationsRead), + ac.EvalPermission(ac.ActionAlertingReceiversRead), + ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets), + ) case http.MethodPost + "/api/alertmanager/grafana/config/api/v1/receivers/test": eval = ac.EvalAny( ac.EvalPermission(ac.ActionAlertingNotificationsWrite),