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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user