Alerting: Managed receiver resource permission in config api (#93632)

* Alerting: Managed receiver resource permission in config api
This commit is contained in:
Matthew Jacobson 2024-09-25 09:39:36 -04:00 committed by GitHub
parent 10582e48f7
commit e86929eb0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 181 additions and 16 deletions

View File

@ -609,7 +609,20 @@ func createMultiOrgAlertmanager(t *testing.T, configs map[int64]*ngmodels.AlertC
}, // do not poll in tests.
}
mam, err := notifier.NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, provStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService, featuremgmt.WithManager(featuremgmt.FlagAlertingSimplifiedRouting))
mam, err := notifier.NewMultiOrgAlertmanager(
cfg,
configStore,
orgStore,
kvStore,
provStore,
decryptFn,
m.GetMultiOrgAlertmanagerMetrics(),
nil,
ngfakes.NewFakeReceiverPermissionsService(),
log.New("testlogger"),
secretsService,
featuremgmt.WithManager(featuremgmt.FlagAlertingSimplifiedRouting),
)
require.NoError(t, err)
err = mam.LoadAndSyncAlertmanagersForOrgs(context.Background())
require.NoError(t, err)

View File

@ -311,7 +311,21 @@ func (ng *AlertNG) init() error {
decryptFn := ng.SecretsService.GetDecryptedValue
multiOrgMetrics := ng.Metrics.GetMultiOrgAlertmanagerMetrics()
moa, err := notifier.NewMultiOrgAlertmanager(ng.Cfg, ng.store, ng.store, ng.KVStore, ng.store, decryptFn, multiOrgMetrics, ng.NotificationService, moaLogger, ng.SecretsService, ng.FeatureToggles, overrides...)
moa, err := notifier.NewMultiOrgAlertmanager(
ng.Cfg,
ng.store,
ng.store,
ng.KVStore,
ng.store,
decryptFn,
multiOrgMetrics,
ng.NotificationService,
ng.ResourcePermissions,
moaLogger,
ng.SecretsService,
ng.FeatureToggles,
overrides...,
)
if err != nil {
return err
}

View File

@ -2,16 +2,19 @@ package notifier
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/go-openapi/strfmt"
"k8s.io/apimachinery/pkg/util/sets"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/util"
)
@ -58,10 +61,32 @@ func (moa *MultiOrgAlertmanager) SaveAndApplyDefaultConfig(ctx context.Context,
return err
}
previousConfig, cleanPermissionsErr := moa.configStore.GetLatestAlertmanagerConfiguration(ctx, orgId)
err = orgAM.SaveAndApplyDefaultConfig(ctx)
if err != nil {
return err
}
// Attempt to cleanup permissions for receivers that are no longer defined and add defaults for new receivers.
// Failure should not prevent the default config from being applied.
if cleanPermissionsErr == nil {
cleanPermissionsErr = func() error {
defaultedConfig, err := moa.configStore.GetLatestAlertmanagerConfiguration(ctx, orgId)
if err != nil {
return err
}
newReceiverNames, err := extractReceiverNames(defaultedConfig.AlertmanagerConfiguration)
if err != nil {
return err
}
return moa.cleanPermissions(ctx, orgId, previousConfig, newReceiverNames)
}()
}
if cleanPermissionsErr != nil {
moa.logger.Error("Failed to clean permissions for receivers", "error", cleanPermissionsErr)
}
return nil
}
@ -130,12 +155,29 @@ func (moa *MultiOrgAlertmanager) ActivateHistoricalConfiguration(ctx context.Con
}
}
previousConfig, cleanPermissionsErr := moa.configStore.GetLatestAlertmanagerConfiguration(ctx, orgId)
if err := am.SaveAndApplyConfig(ctx, cfg); err != nil {
moa.logger.Error("Unable to save and apply historical alertmanager configuration", "error", err, "org", orgId, "id", id)
return AlertmanagerConfigRejectedError{err}
}
moa.logger.Info("Applied historical alertmanager configuration", "org", orgId, "id", id)
// Attempt to cleanup permissions for receivers that are no longer defined and add defaults for new receivers.
// Failure should not prevent the default config from being applied.
if cleanPermissionsErr == nil {
cleanPermissionsErr = func() error {
newReceiverNames, err := extractReceiverNames(config.AlertmanagerConfiguration)
if err != nil {
return err
}
return moa.cleanPermissions(ctx, orgId, previousConfig, newReceiverNames)
}()
}
if cleanPermissionsErr != nil {
moa.logger.Error("Failed to clean permissions for receivers", "error", cleanPermissionsErr)
}
return nil
}
@ -231,13 +273,14 @@ func (moa *MultiOrgAlertmanager) SaveAndApplyAlertmanagerConfiguration(ctx conte
}
// Get the last known working configuration
_, err := moa.configStore.GetLatestAlertmanagerConfiguration(ctx, org)
previousConfig, err := moa.configStore.GetLatestAlertmanagerConfiguration(ctx, org)
if err != nil {
// If we don't have a configuration there's nothing for us to know and we should just continue saving the new one
if !errors.Is(err, store.ErrNoAlertmanagerConfiguration) {
return fmt.Errorf("failed to get latest configuration %w", err)
}
}
cleanPermissionsErr := err
if err := moa.Crypto.ProcessSecureSettings(ctx, org, config.AlertmanagerConfig.Receivers); err != nil {
return fmt.Errorf("failed to post process Alertmanager configuration: %w", err)
@ -268,6 +311,21 @@ func (moa *MultiOrgAlertmanager) SaveAndApplyAlertmanagerConfiguration(ctx conte
return AlertmanagerConfigRejectedError{err}
}
// Attempt to cleanup permissions for receivers that are no longer defined and add defaults for new receivers.
// Failure should not prevent the default config from being applied.
if cleanPermissionsErr == nil {
cleanPermissionsErr = func() error {
newReceiverNames := make(sets.Set[string], len(config.AlertmanagerConfig.Receivers))
for _, r := range config.AlertmanagerConfig.Receivers {
newReceiverNames.Insert(r.Name)
}
return moa.cleanPermissions(ctx, org, previousConfig, newReceiverNames)
}()
}
if cleanPermissionsErr != nil {
moa.logger.Error("Failed to clean permissions for receivers", "error", cleanPermissionsErr)
}
return nil
}
@ -352,3 +410,51 @@ func (moa *MultiOrgAlertmanager) mergeProvenance(ctx context.Context, config def
return config, nil
}
// cleanPermissions will remove permissions for receivers that are no longer defined in the new configuration and
// set default permissions for new receivers.
func (moa *MultiOrgAlertmanager) cleanPermissions(ctx context.Context, orgID int64, previousConfig *models.AlertConfiguration, newReceiverNames sets.Set[string]) error {
previousReceiverNames, err := extractReceiverNames(previousConfig.AlertmanagerConfiguration)
if err != nil {
return fmt.Errorf("failed to extract receiver names from previous configuration: %w", err)
}
var errs []error
for receiverName := range previousReceiverNames.Difference(newReceiverNames) { // Deleted receivers.
if err := moa.receiverResourcePermissions.DeleteResourcePermissions(ctx, orgID, legacy_storage.NameToUid(receiverName)); err != nil {
errs = append(errs, fmt.Errorf("failed to delete permissions for receiver %s: %w", receiverName, err))
}
}
for receiverName := range newReceiverNames.Difference(previousReceiverNames) { // Added receivers.
moa.receiverResourcePermissions.SetDefaultPermissions(ctx, orgID, nil, legacy_storage.NameToUid(receiverName))
}
return errors.Join(errs...)
}
// extractReceiverNames extracts receiver names from the raw Alertmanager configuration. Unmarshalling ignores fields
// unrelated to receiver names, making it more resilient to invalid configurations.
func extractReceiverNames(rawConfig string) (sets.Set[string], error) {
// Slimmed down version of the Alertmanager configuration to extract receiver names. This is more resilient to
// invalid configurations when all we are interested in is the receiver names.
type receiverUserConfig struct {
AlertmanagerConfig struct {
Receivers []struct {
Name string `yaml:"name" json:"name"`
} `yaml:"receivers,omitempty" json:"receivers,omitempty"`
} `yaml:"alertmanager_config" json:"alertmanager_config"`
}
cfg := &receiverUserConfig{}
if err := json.Unmarshal([]byte(rawConfig), cfg); err != nil {
return nil, fmt.Errorf("unable to parse Alertmanager configuration: %w", err)
}
receiverNames := make(sets.Set[string], len(cfg.AlertmanagerConfig.Receivers))
for _, r := range cfg.AlertmanagerConfig.Receivers {
receiverNames[r.Name] = struct{}{}
}
return receiverNames, nil
}

View File

@ -12,6 +12,7 @@ import (
alertingCluster "github.com/grafana/alerting/cluster"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
alertingNotify "github.com/grafana/alerting/notify"
@ -100,6 +101,8 @@ type MultiOrgAlertmanager struct {
metrics *metrics.MultiOrgAlertmanager
ns notifications.Service
receiverResourcePermissions ac.ReceiverPermissionsService
}
type OrgAlertmanagerFactory func(ctx context.Context, orgID int64) (Alertmanager, error)
@ -121,6 +124,7 @@ func NewMultiOrgAlertmanager(
decryptFn alertingNotify.GetDecryptedValueFn,
m *metrics.MultiOrgAlertmanager,
ns notifications.Service,
receiverResourcePermissions ac.ReceiverPermissionsService,
l log.Logger,
s secrets.Service,
featureManager featuremgmt.FeatureToggles,
@ -130,17 +134,18 @@ func NewMultiOrgAlertmanager(
Crypto: NewCrypto(s, configStore, l),
ProvStore: provStore,
logger: l,
settings: cfg,
featureManager: featureManager,
alertmanagers: map[int64]Alertmanager{},
configStore: configStore,
orgStore: orgStore,
kvStore: kvStore,
decryptFn: decryptFn,
metrics: m,
ns: ns,
peer: &NilPeer{},
logger: l,
settings: cfg,
featureManager: featureManager,
alertmanagers: map[int64]Alertmanager{},
configStore: configStore,
orgStore: orgStore,
kvStore: kvStore,
decryptFn: decryptFn,
receiverResourcePermissions: receiverResourcePermissions,
metrics: m,
ns: ns,
peer: &NilPeer{},
}
if cfg.UnifiedAlerting.SkipClustering {

View File

@ -98,6 +98,7 @@ func TestMultiorgAlertmanager_RemoteSecondaryMode(t *testing.T) {
secretsService.GetDecryptedValue,
m.GetMultiOrgAlertmanagerMetrics(),
nil,
ngfakes.NewFakeReceiverPermissionsService(),
nopLogger,
secretsService,
featuremgmt.WithFeatures(),

View File

@ -383,7 +383,20 @@ func setupMam(t *testing.T, cfg *setting.Cfg) *MultiOrgAlertmanager {
decryptFn := secretsService.GetDecryptedValue
reg := prometheus.NewPedanticRegistry()
m := metrics.NewNGAlert(reg)
mam, err := NewMultiOrgAlertmanager(cfg, cs, orgStore, kvStore, provStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService, featuremgmt.WithFeatures())
mam, err := NewMultiOrgAlertmanager(
cfg,
cs,
orgStore,
kvStore,
provStore,
decryptFn,
m.GetMultiOrgAlertmanagerMetrics(),
nil,
ngfakes.NewFakeReceiverPermissionsService(),
log.New("testlogger"),
secretsService,
featuremgmt.WithFeatures(),
)
require.NoError(t, err)
return mam
}

View File

@ -491,7 +491,20 @@ func createMultiOrgAlertmanager(t *testing.T, orgs []int64) *notifier.MultiOrgAl
m := metrics.NewNGAlert(registry)
secretsService := secretsManager.SetupTestService(t, fake_secrets.NewFakeSecretsStore())
decryptFn := secretsService.GetDecryptedValue
moa, err := notifier.NewMultiOrgAlertmanager(cfg, cfgStore, orgStore, kvStore, fakes.NewFakeProvisioningStore(), decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService, featuremgmt.WithFeatures())
moa, err := notifier.NewMultiOrgAlertmanager(
cfg,
cfgStore,
orgStore,
kvStore,
fakes.NewFakeProvisioningStore(),
decryptFn,
m.GetMultiOrgAlertmanagerMetrics(),
nil,
fakes.NewFakeReceiverPermissionsService(),
log.New("testlogger"),
secretsService,
featuremgmt.WithFeatures(),
)
require.NoError(t, err)
require.NoError(t, moa.LoadAndSyncAlertmanagersForOrgs(context.Background()))
require.Eventually(t, func() bool {