Alerting: Remove option to return settings from api/v1/receivers and restrict provisioning action access (#90861)

* Remove provisioning action access to v1/receivers api

* Separate ListOnly functionality to its own method without decryption
This commit is contained in:
Matthew Jacobson
2024-08-05 11:49:23 -04:00
committed by GitHub
parent 4d23382497
commit 53cfdf0ef8
8 changed files with 145 additions and 105 deletions
+36 -32
View File
@@ -48,48 +48,52 @@ func PostableApiAlertingConfigToApiReceivers(c apimodels.PostableApiAlertingConf
type DecryptFn = func(value string) string
func PostableToGettableGrafanaReceiver(r *apimodels.PostableGrafanaReceiver, provenance *models.Provenance, decryptFn DecryptFn, listOnly bool) (apimodels.GettableGrafanaReceiver, error) {
func PostableToGettableGrafanaReceiver(r *apimodels.PostableGrafanaReceiver, provenance *models.Provenance, decryptFn DecryptFn) (apimodels.GettableGrafanaReceiver, error) {
out := apimodels.GettableGrafanaReceiver{
UID: r.UID,
Name: r.Name,
Type: r.Type,
UID: r.UID,
Name: r.Name,
Type: r.Type,
DisableResolveMessage: r.DisableResolveMessage,
SecureFields: make(map[string]bool, len(r.SecureSettings)),
}
if provenance != nil {
out.Provenance = apimodels.Provenance(*provenance)
}
// if we aren't only listing, include the settings in the output
if !listOnly {
secureFields := make(map[string]bool, len(r.SecureSettings))
settings, err := simplejson.NewJson([]byte(r.Settings))
if err != nil {
return apimodels.GettableGrafanaReceiver{}, err
}
for k, v := range r.SecureSettings {
decryptedValue := decryptFn(v)
if decryptedValue == "" {
continue
} else {
settings.Set(k, decryptedValue)
}
secureFields[k] = true
}
jsonBytes, err := settings.MarshalJSON()
if err != nil {
return apimodels.GettableGrafanaReceiver{}, err
}
out.Settings = jsonBytes
out.SecureFields = secureFields
out.DisableResolveMessage = r.DisableResolveMessage
if r.Settings == nil && r.SecureSettings == nil {
return out, nil
}
settings := simplejson.New()
if r.Settings != nil {
var err error
settings, err = simplejson.NewJson(r.Settings)
if err != nil {
return apimodels.GettableGrafanaReceiver{}, err
}
}
for k, v := range r.SecureSettings {
decryptedValue := decryptFn(v)
if decryptedValue == "" {
continue
} else {
settings.Set(k, decryptedValue)
}
out.SecureFields[k] = true
}
jsonBytes, err := settings.MarshalJSON()
if err != nil {
return apimodels.GettableGrafanaReceiver{}, err
}
out.Settings = jsonBytes
return out, nil
}
func PostableToGettableApiReceiver(r *apimodels.PostableApiReceiver, provenances map[string]models.Provenance, decryptFn DecryptFn, listOnly bool) (apimodels.GettableApiReceiver, error) {
func PostableToGettableApiReceiver(r *apimodels.PostableApiReceiver, provenances map[string]models.Provenance, decryptFn DecryptFn) (apimodels.GettableApiReceiver, error) {
out := apimodels.GettableApiReceiver{
Receiver: config.Receiver{
Name: r.Receiver.Name,
@@ -102,7 +106,7 @@ func PostableToGettableApiReceiver(r *apimodels.PostableApiReceiver, provenances
prov = &p
}
gettable, err := PostableToGettableGrafanaReceiver(gr, prov, decryptFn, listOnly)
gettable, err := PostableToGettableGrafanaReceiver(gr, prov, decryptFn)
if err != nil {
return apimodels.GettableApiReceiver{}, err
}
+62 -8
View File
@@ -107,7 +107,7 @@ func (rs *ReceiverService) GetReceiver(ctx context.Context, q models.GetReceiver
return definitions.GettableApiReceiver{}, err
}
return PostableToGettableApiReceiver(postable, storedProvenances, decryptFn, false)
return PostableToGettableApiReceiver(postable, storedProvenances, decryptFn)
}
// GetReceivers returns a list of receivers a user has access to.
@@ -139,11 +139,64 @@ func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceive
return nil, err
}
// User doesn't have any permissions on the receivers.
// This is mostly a safeguard as it should not be possible with current API endpoints + middleware authentication.
if !readRedactedAccess {
return nil, nil
}
var output []definitions.GettableApiReceiver
for i := q.Offset; i < len(postables); i++ {
r := postables[i]
decryptFn := rs.decryptOrRedact(ctx, decrypt, r.Name, "")
res, err := PostableToGettableApiReceiver(r, storedProvenances, decryptFn)
if err != nil {
return nil, err
}
output = append(output, res)
// stop if we have reached the limit or we have found all the requested receivers
if (len(output) == q.Limit && q.Limit > 0) || (len(output) == len(q.Names)) {
break
}
}
return output, nil
}
// ListReceivers returns a list of receivers a user has access to.
// Receivers can be filtered by name.
// This offers an looser permissions compared to GetReceivers. When a user doesn't have read access it will check for list access instead of returning an empty list.
// If the users has list access, all receiver settings will be removed from the response. This option is for backwards compatibility with the v1/receivers endpoint
// and should be removed when FGAC is fully implemented.
func (rs *ReceiverService) ListReceivers(ctx context.Context, q models.ListReceiversQuery, user identity.Requester) ([]definitions.GettableApiReceiver, error) { // TODO: Remove this method with FGAC.
listAccess, err := rs.authz.HasList(ctx, user)
if err != nil {
return nil, err
}
readRedactedAccess, err := rs.authz.HasReadAll(ctx, user)
if err != nil {
return nil, err
}
uids := make([]string, 0, len(q.Names))
for _, name := range q.Names {
uids = append(uids, legacy_storage.NameToUid(name))
}
revision, err := rs.cfgStore.Get(ctx, q.OrgID)
if err != nil {
return nil, err
}
postables := revision.GetReceivers(uids)
storedProvenances, err := rs.provisioningStore.GetProvenances(ctx, q.OrgID, (&definitions.EmbeddedContactPoint{}).ResourceType())
if err != nil {
return nil, err
}
// User doesn't have any permissions on the receivers.
// This is mostly a safeguard as it should not be possible with current API endpoints + middleware authentication.
if !listAccess && !readRedactedAccess {
@@ -154,14 +207,15 @@ func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceive
for i := q.Offset; i < len(postables); i++ {
r := postables[i]
decryptFn := rs.decryptOrRedact(ctx, decrypt, r.Name, "")
// Remove settings.
for _, integration := range r.GrafanaManagedReceivers {
integration.Settings = nil
integration.SecureSettings = nil
integration.DisableResolveMessage = false
}
// Only has permission to list. This reduces from:
// - Has List permission
// - Doesn't have ReadRedacted (or ReadDecrypted permission since it's a subset).
listOnly := !readRedactedAccess
res, err := PostableToGettableApiReceiver(r, storedProvenances, decryptFn, listOnly)
decryptFn := rs.decryptOrRedact(ctx, false, r.Name, "")
res, err := PostableToGettableApiReceiver(r, storedProvenances, decryptFn)
if err != nil {
return nil, err
}
@@ -33,7 +33,7 @@ func TestReceiverService_GetReceiver(t *testing.T) {
redactedUser := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
1: {
accesscontrol.ActionAlertingProvisioningRead: nil,
accesscontrol.ActionAlertingNotificationsRead: nil,
},
}}
@@ -61,7 +61,7 @@ func TestReceiverService_GetReceivers(t *testing.T) {
redactedUser := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
1: {
accesscontrol.ActionAlertingProvisioningRead: nil,
accesscontrol.ActionAlertingNotificationsRead: nil,
},
}}
@@ -94,7 +94,7 @@ func TestReceiverService_DecryptRedact(t *testing.T) {
readUser := &user.SignedInUser{
OrgID: 1,
Permissions: map[int64]map[string][]string{
1: {accesscontrol.ActionAlertingProvisioningRead: nil},
1: {accesscontrol.ActionAlertingNotificationsRead: nil},
},
}
@@ -102,8 +102,8 @@ func TestReceiverService_DecryptRedact(t *testing.T) {
OrgID: 1,
Permissions: map[int64]map[string][]string{
1: {
accesscontrol.ActionAlertingProvisioningRead: nil,
accesscontrol.ActionAlertingProvisioningReadSecrets: nil,
accesscontrol.ActionAlertingNotificationsRead: nil,
accesscontrol.ActionAlertingReceiversReadSecrets: nil,
},
},
}
@@ -190,7 +190,7 @@ func createReceiverServiceSut(t *testing.T, encryptSvc secrets.Service) *Receive
provisioningStore := fakes.NewFakeProvisioningStore()
return NewReceiverService(
ac.NewReceiverAccess[*models.Receiver](acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), true),
ac.NewReceiverAccess[*models.Receiver](acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), false),
legacy_storage.NewAlertmanagerConfigStore(store),
provisioningStore,
encryptSvc,