Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alerting: Modify alertmanager endpoints for proxying using the datasource UID #47978

Merged
merged 10 commits into from Apr 29, 2022
2 changes: 1 addition & 1 deletion pkg/api/dataproxy.go
Expand Up @@ -7,5 +7,5 @@ func (hs *HTTPServer) ProxyDataSourceRequest(c *models.ReqContext) {
}

func (hs *HTTPServer) ProxyDataSourceRequestWithUID(c *models.ReqContext) {
hs.DataProxy.ProxyDatasourceRequestWithUID(c)
hs.DataProxy.ProxyDatasourceRequestWithUID(c, "")
}
7 changes: 5 additions & 2 deletions pkg/services/datasourceproxy/datasourceproxy.go
Expand Up @@ -60,10 +60,13 @@ func (p *DataSourceProxyService) ProxyDataSourceRequest(c *models.ReqContext) {
p.ProxyDatasourceRequestWithID(c, id)
}

func (p *DataSourceProxyService) ProxyDatasourceRequestWithUID(c *models.ReqContext) {
func (p *DataSourceProxyService) ProxyDatasourceRequestWithUID(c *models.ReqContext, dsUID string) {
c.TimeRequest(metrics.MDataSourceProxyReqTimer)

dsUID := web.Params(c.Req)[":uid"]
if dsUID == "" { // if datasource UID is not provided, fetch it from the uid path parameter
dsUID = web.Params(c.Req)[":uid"]
}

if !util.IsValidShortUID(dsUID) {
c.JsonApiErr(http.StatusBadRequest, "UID is invalid", nil)
return
Expand Down
24 changes: 24 additions & 0 deletions pkg/services/ngalert/api/authorization.go
Expand Up @@ -125,20 +125,34 @@ func (api *API) authorize(method, path string) web.Handler {
// Silences. External AM.
case http.MethodDelete + "/api/alertmanager/{DatasourceID}/api/v2/silence/{SilenceId}":
eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID")))
case http.MethodDelete + "/api/alertmanager/uid/{DatasourceUID}/api/v2/silence/{SilenceId}":
eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceUID")))
case http.MethodPost + "/api/alertmanager/{DatasourceID}/api/v2/silences":
eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID")))
case http.MethodPost + "/api/alertmanager/uid/{DatasourceUID}/api/v2/silences":
papagian marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're up for it, it would be great to make a path function that at least provides the prefix:

AMV2Path(pathSuffix) string {
  return path.Join("/api/alertmanager/datasource/{DatasourceUID}/api/v2", pathSuffix)
}

...

AMV2Path("silence/{SIlenceID")

that makes it easier to scan which API is being evaluated.

If not, I'll create an issue and I can tackle it later.

eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceUID")))
case http.MethodGet + "/api/alertmanager/{DatasourceID}/api/v2/silence/{SilenceId}":
eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID")))
case http.MethodGet + "/api/alertmanager/uid/{DatasourceUID}/api/v2/silence/{SilenceId}":
eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceUID")))
case http.MethodGet + "/api/alertmanager/{DatasourceID}/api/v2/silences":
eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID")))
case http.MethodGet + "/api/alertmanager/uid/{DatasourceUID}/api/v2/silences":
eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceUID")))

// Alert instances. External AM.
case http.MethodGet + "/api/alertmanager/{DatasourceID}/api/v2/alerts/groups":
eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID")))
case http.MethodGet + "/api/alertmanager/uid/{DatasourceUID}/api/v2/alerts/groups":
eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceUID")))
case http.MethodGet + "/api/alertmanager/{DatasourceID}/api/v2/alerts":
eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID")))
case http.MethodGet + "/api/alertmanager/uid/{DatasourceUID}/api/v2/alerts":
eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceUID")))
case http.MethodPost + "/api/alertmanager/{DatasourceID}/api/v2/alerts":
eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID")))
case http.MethodPost + "/api/alertmanager/uid/{DatasourceUID}/api/v2/alerts":
eval = ac.EvalPermission(ac.ActionAlertingInstancesExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceUID")))

// Prometheus-compatible Paths
case http.MethodGet + "/api/prometheus/{DatasourceID}/api/v1/alerts":
Expand All @@ -164,14 +178,24 @@ func (api *API) authorize(method, path string) web.Handler {
// External Alertmanager Paths
case http.MethodDelete + "/api/alertmanager/{DatasourceID}/config/api/v1/alerts":
eval = ac.EvalPermission(ac.ActionAlertingNotificationsDelete, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID")))
case http.MethodDelete + "/api/alertmanager/uid/{DatasourceUID}/config/api/v1/alerts":
eval = ac.EvalPermission(ac.ActionAlertingNotificationsDelete, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceUID")))
case http.MethodGet + "/api/alertmanager/{DatasourceID}/api/v2/status":
eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID")))
case http.MethodGet + "/api/alertmanager/uid/{DatasourceUID}/api/v2/status":
eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceUID")))
case http.MethodGet + "/api/alertmanager/{DatasourceID}/config/api/v1/alerts":
eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID")))
case http.MethodGet + "/api/alertmanager/uid/{DatasourceUID}/config/api/v1/alerts":
eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceUID")))
case http.MethodPost + "/api/alertmanager/{DatasourceID}/config/api/v1/alerts":
eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID")))
case http.MethodPost + "/api/alertmanager/uid/{DatasourceUID}/config/api/v1/alerts":
eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID")))
case http.MethodPost + "/api/alertmanager/{DatasourceID}/config/api/v1/receivers/test":
eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceID")))
case http.MethodPost + "/api/alertmanager/uid/{DatasourceUID}/config/api/v1/receivers/test":
eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":DatasourceUID")))

// Raw Alertmanager Config Paths
case http.MethodDelete + "/api/v1/ngalert/admin_config",
Expand Down
2 changes: 1 addition & 1 deletion pkg/services/ngalert/api/authorization_test.go
Expand Up @@ -47,7 +47,7 @@ func TestAuthorize(t *testing.T) {
}
paths[p] = methods
}
require.Len(t, paths, 32)
require.Len(t, paths, 39)

ac := acmock.New()
api := &API{AccessControl: ac}
Expand Down
131 changes: 131 additions & 0 deletions pkg/services/ngalert/api/forked_am.go
Expand Up @@ -38,6 +38,20 @@ func (f *ForkedAlertmanagerApi) getService(ctx *models.ReqContext) (*LotexAM, er
}
}

func (f *ForkedAlertmanagerApi) getServiceByUID(ctx *models.ReqContext) (*LotexAM, error) {
t, err := backendTypeByUID(ctx, f.DatasourceCache)
if err != nil {
return nil, err
}

switch t {
case apimodels.AlertmanagerBackend:
return f.AMSvc, nil
default:
return nil, fmt.Errorf("unexpected backend type (%v)", t)
}
}

func (f *ForkedAlertmanagerApi) forkRouteGetAMStatus(ctx *models.ReqContext) response.Response {
s, err := f.getService(ctx)
if err != nil {
Expand All @@ -47,6 +61,15 @@ func (f *ForkedAlertmanagerApi) forkRouteGetAMStatus(ctx *models.ReqContext) res
return s.RouteGetAMStatus(ctx)
}

func (f *ForkedAlertmanagerApi) forkRouteGetAMStatusWithUID(ctx *models.ReqContext) response.Response {
s, err := f.getServiceByUID(ctx)
if err != nil {
return response.Error(400, err.Error(), nil)
}

return s.RouteGetAMStatus(ctx)
}

func (f *ForkedAlertmanagerApi) forkRouteCreateSilence(ctx *models.ReqContext, body apimodels.PostableSilence) response.Response {
s, err := f.getService(ctx)
if err != nil {
Expand All @@ -56,6 +79,15 @@ func (f *ForkedAlertmanagerApi) forkRouteCreateSilence(ctx *models.ReqContext, b
return s.RouteCreateSilence(ctx, body)
}

func (f *ForkedAlertmanagerApi) forkRouteCreateSilenceWithUID(ctx *models.ReqContext, body apimodels.PostableSilence) response.Response {
s, err := f.getServiceByUID(ctx)
if err != nil {
return ErrResp(400, err, "")
}

return s.RouteCreateSilence(ctx, body)
}

func (f *ForkedAlertmanagerApi) forkRouteDeleteAlertingConfig(ctx *models.ReqContext) response.Response {
s, err := f.getService(ctx)
if err != nil {
Expand All @@ -65,6 +97,15 @@ func (f *ForkedAlertmanagerApi) forkRouteDeleteAlertingConfig(ctx *models.ReqCon
return s.RouteDeleteAlertingConfig(ctx)
}

func (f *ForkedAlertmanagerApi) forkRouteDeleteAlertingConfigWithUID(ctx *models.ReqContext) response.Response {
s, err := f.getServiceByUID(ctx)
if err != nil {
return ErrResp(400, err, "")
}

return s.RouteDeleteAlertingConfig(ctx)
}

func (f *ForkedAlertmanagerApi) forkRouteDeleteSilence(ctx *models.ReqContext) response.Response {
s, err := f.getService(ctx)
if err != nil {
Expand All @@ -74,6 +115,15 @@ func (f *ForkedAlertmanagerApi) forkRouteDeleteSilence(ctx *models.ReqContext) r
return s.RouteDeleteSilence(ctx)
}

func (f *ForkedAlertmanagerApi) forkRouteDeleteSilenceWithUID(ctx *models.ReqContext) response.Response {
s, err := f.getServiceByUID(ctx)
if err != nil {
return ErrResp(400, err, "")
}

return s.RouteDeleteSilence(ctx)
}

func (f *ForkedAlertmanagerApi) forkRouteGetAlertingConfig(ctx *models.ReqContext) response.Response {
s, err := f.getService(ctx)
if err != nil {
Expand All @@ -83,6 +133,15 @@ func (f *ForkedAlertmanagerApi) forkRouteGetAlertingConfig(ctx *models.ReqContex
return s.RouteGetAlertingConfig(ctx)
}

func (f *ForkedAlertmanagerApi) forkRouteGetAlertingConfigWithUID(ctx *models.ReqContext) response.Response {
s, err := f.getServiceByUID(ctx)
if err != nil {
return ErrResp(400, err, "")
}

return s.RouteGetAlertingConfig(ctx)
}

func (f *ForkedAlertmanagerApi) forkRouteGetAMAlertGroups(ctx *models.ReqContext) response.Response {
s, err := f.getService(ctx)
if err != nil {
Expand All @@ -92,6 +151,15 @@ func (f *ForkedAlertmanagerApi) forkRouteGetAMAlertGroups(ctx *models.ReqContext
return s.RouteGetAMAlertGroups(ctx)
}

func (f *ForkedAlertmanagerApi) forkRouteGetAMAlertGroupsWithUID(ctx *models.ReqContext) response.Response {
s, err := f.getServiceByUID(ctx)
if err != nil {
return ErrResp(400, err, "")
}

return s.RouteGetAMAlertGroups(ctx)
}

func (f *ForkedAlertmanagerApi) forkRouteGetAMAlerts(ctx *models.ReqContext) response.Response {
s, err := f.getService(ctx)
if err != nil {
Expand All @@ -101,6 +169,15 @@ func (f *ForkedAlertmanagerApi) forkRouteGetAMAlerts(ctx *models.ReqContext) res
return s.RouteGetAMAlerts(ctx)
}

func (f *ForkedAlertmanagerApi) forkRouteGetAMAlertsWithUID(ctx *models.ReqContext) response.Response {
s, err := f.getServiceByUID(ctx)
if err != nil {
return ErrResp(400, err, "")
}

return s.RouteGetAMAlerts(ctx)
}

func (f *ForkedAlertmanagerApi) forkRouteGetSilence(ctx *models.ReqContext) response.Response {
s, err := f.getService(ctx)
if err != nil {
Expand All @@ -110,6 +187,15 @@ func (f *ForkedAlertmanagerApi) forkRouteGetSilence(ctx *models.ReqContext) resp
return s.RouteGetSilence(ctx)
}

func (f *ForkedAlertmanagerApi) forkRouteGetSilenceWithUID(ctx *models.ReqContext) response.Response {
s, err := f.getServiceByUID(ctx)
if err != nil {
return ErrResp(400, err, "")
}

return s.RouteGetSilence(ctx)
}

func (f *ForkedAlertmanagerApi) forkRouteGetSilences(ctx *models.ReqContext) response.Response {
s, err := f.getService(ctx)
if err != nil {
Expand All @@ -119,6 +205,15 @@ func (f *ForkedAlertmanagerApi) forkRouteGetSilences(ctx *models.ReqContext) res
return s.RouteGetSilences(ctx)
}

func (f *ForkedAlertmanagerApi) forkRouteGetSilencesWithUID(ctx *models.ReqContext) response.Response {
s, err := f.getServiceByUID(ctx)
if err != nil {
return ErrResp(400, err, "")
}

return s.RouteGetSilences(ctx)
}

func (f *ForkedAlertmanagerApi) forkRoutePostAlertingConfig(ctx *models.ReqContext, body apimodels.PostableUserConfig) response.Response {
s, err := f.getService(ctx)
if err != nil {
Expand All @@ -137,6 +232,24 @@ func (f *ForkedAlertmanagerApi) forkRoutePostAlertingConfig(ctx *models.ReqConte
return s.RoutePostAlertingConfig(ctx, body)
}

func (f *ForkedAlertmanagerApi) forkRoutePostAlertingConfigWithUID(ctx *models.ReqContext, body apimodels.PostableUserConfig) response.Response {
s, err := f.getServiceByUID(ctx)
if err != nil {
return ErrResp(400, err, "")
}

b, err := backendTypeByUID(ctx, f.DatasourceCache)
if err != nil {
return ErrResp(400, err, "")
}

if err := body.AlertmanagerConfig.ReceiverType().MatchesBackend(b); err != nil {
return ErrResp(400, err, "bad match")
}

return s.RoutePostAlertingConfig(ctx, body)
}

func (f *ForkedAlertmanagerApi) forkRoutePostAMAlerts(ctx *models.ReqContext, body apimodels.PostableAlerts) response.Response {
s, err := f.getService(ctx)
if err != nil {
Expand All @@ -146,6 +259,15 @@ func (f *ForkedAlertmanagerApi) forkRoutePostAMAlerts(ctx *models.ReqContext, bo
return s.RoutePostAMAlerts(ctx, body)
}

func (f *ForkedAlertmanagerApi) forkRoutePostAMAlertsWithUID(ctx *models.ReqContext, body apimodels.PostableAlerts) response.Response {
s, err := f.getServiceByUID(ctx)
if err != nil {
return ErrResp(400, err, "")
}

return s.RoutePostAMAlerts(ctx, body)
}

func (f *ForkedAlertmanagerApi) forkRoutePostTestReceivers(ctx *models.ReqContext, body apimodels.TestReceiversConfigBodyParams) response.Response {
s, err := f.getService(ctx)
if err != nil {
Expand All @@ -155,6 +277,15 @@ func (f *ForkedAlertmanagerApi) forkRoutePostTestReceivers(ctx *models.ReqContex
return s.RoutePostTestReceivers(ctx, body)
}

func (f *ForkedAlertmanagerApi) forkRoutePostTestReceiversWithUID(ctx *models.ReqContext, body apimodels.TestReceiversConfigBodyParams) response.Response {
s, err := f.getServiceByUID(ctx)
if err != nil {
return ErrResp(400, err, "")
}

return s.RoutePostTestReceivers(ctx, body)
}

func (f *ForkedAlertmanagerApi) forkRouteDeleteGrafanaSilence(ctx *models.ReqContext) response.Response {
return f.GrafanaSvc.RouteDeleteSilence(ctx)
}
Expand Down