mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Managed receiver resource permission in config api (#93632)
* Alerting: Managed receiver resource permission in config api
This commit is contained in:
parent
10582e48f7
commit
e86929eb0a
@ -609,7 +609,20 @@ func createMultiOrgAlertmanager(t *testing.T, configs map[int64]*ngmodels.AlertC
|
|||||||
}, // do not poll in tests.
|
}, // 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)
|
require.NoError(t, err)
|
||||||
err = mam.LoadAndSyncAlertmanagersForOrgs(context.Background())
|
err = mam.LoadAndSyncAlertmanagersForOrgs(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -311,7 +311,21 @@ func (ng *AlertNG) init() error {
|
|||||||
|
|
||||||
decryptFn := ng.SecretsService.GetDecryptedValue
|
decryptFn := ng.SecretsService.GetDecryptedValue
|
||||||
multiOrgMetrics := ng.Metrics.GetMultiOrgAlertmanagerMetrics()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,19 @@ package notifier
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-openapi/strfmt"
|
"github.com/go-openapi/strfmt"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"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/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
@ -58,10 +61,32 @@ func (moa *MultiOrgAlertmanager) SaveAndApplyDefaultConfig(ctx context.Context,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previousConfig, cleanPermissionsErr := moa.configStore.GetLatestAlertmanagerConfiguration(ctx, orgId)
|
||||||
|
|
||||||
err = orgAM.SaveAndApplyDefaultConfig(ctx)
|
err = orgAM.SaveAndApplyDefaultConfig(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
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 {
|
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)
|
moa.logger.Error("Unable to save and apply historical alertmanager configuration", "error", err, "org", orgId, "id", id)
|
||||||
return AlertmanagerConfigRejectedError{err}
|
return AlertmanagerConfigRejectedError{err}
|
||||||
}
|
}
|
||||||
moa.logger.Info("Applied historical alertmanager configuration", "org", orgId, "id", id)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,13 +273,14 @@ func (moa *MultiOrgAlertmanager) SaveAndApplyAlertmanagerConfiguration(ctx conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the last known working configuration
|
// Get the last known working configuration
|
||||||
_, err := moa.configStore.GetLatestAlertmanagerConfiguration(ctx, org)
|
previousConfig, err := moa.configStore.GetLatestAlertmanagerConfiguration(ctx, org)
|
||||||
if err != nil {
|
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 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) {
|
if !errors.Is(err, store.ErrNoAlertmanagerConfiguration) {
|
||||||
return fmt.Errorf("failed to get latest configuration %w", err)
|
return fmt.Errorf("failed to get latest configuration %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cleanPermissionsErr := err
|
||||||
|
|
||||||
if err := moa.Crypto.ProcessSecureSettings(ctx, org, config.AlertmanagerConfig.Receivers); err != nil {
|
if err := moa.Crypto.ProcessSecureSettings(ctx, org, config.AlertmanagerConfig.Receivers); err != nil {
|
||||||
return fmt.Errorf("failed to post process Alertmanager configuration: %w", err)
|
return fmt.Errorf("failed to post process Alertmanager configuration: %w", err)
|
||||||
@ -268,6 +311,21 @@ func (moa *MultiOrgAlertmanager) SaveAndApplyAlertmanagerConfiguration(ctx conte
|
|||||||
return AlertmanagerConfigRejectedError{err}
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,3 +410,51 @@ func (moa *MultiOrgAlertmanager) mergeProvenance(ctx context.Context, config def
|
|||||||
|
|
||||||
return config, nil
|
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
|
||||||
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
alertingCluster "github.com/grafana/alerting/cluster"
|
alertingCluster "github.com/grafana/alerting/cluster"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||||
|
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
|
|
||||||
alertingNotify "github.com/grafana/alerting/notify"
|
alertingNotify "github.com/grafana/alerting/notify"
|
||||||
|
|
||||||
@ -100,6 +101,8 @@ type MultiOrgAlertmanager struct {
|
|||||||
|
|
||||||
metrics *metrics.MultiOrgAlertmanager
|
metrics *metrics.MultiOrgAlertmanager
|
||||||
ns notifications.Service
|
ns notifications.Service
|
||||||
|
|
||||||
|
receiverResourcePermissions ac.ReceiverPermissionsService
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrgAlertmanagerFactory func(ctx context.Context, orgID int64) (Alertmanager, error)
|
type OrgAlertmanagerFactory func(ctx context.Context, orgID int64) (Alertmanager, error)
|
||||||
@ -121,6 +124,7 @@ func NewMultiOrgAlertmanager(
|
|||||||
decryptFn alertingNotify.GetDecryptedValueFn,
|
decryptFn alertingNotify.GetDecryptedValueFn,
|
||||||
m *metrics.MultiOrgAlertmanager,
|
m *metrics.MultiOrgAlertmanager,
|
||||||
ns notifications.Service,
|
ns notifications.Service,
|
||||||
|
receiverResourcePermissions ac.ReceiverPermissionsService,
|
||||||
l log.Logger,
|
l log.Logger,
|
||||||
s secrets.Service,
|
s secrets.Service,
|
||||||
featureManager featuremgmt.FeatureToggles,
|
featureManager featuremgmt.FeatureToggles,
|
||||||
@ -130,17 +134,18 @@ func NewMultiOrgAlertmanager(
|
|||||||
Crypto: NewCrypto(s, configStore, l),
|
Crypto: NewCrypto(s, configStore, l),
|
||||||
ProvStore: provStore,
|
ProvStore: provStore,
|
||||||
|
|
||||||
logger: l,
|
logger: l,
|
||||||
settings: cfg,
|
settings: cfg,
|
||||||
featureManager: featureManager,
|
featureManager: featureManager,
|
||||||
alertmanagers: map[int64]Alertmanager{},
|
alertmanagers: map[int64]Alertmanager{},
|
||||||
configStore: configStore,
|
configStore: configStore,
|
||||||
orgStore: orgStore,
|
orgStore: orgStore,
|
||||||
kvStore: kvStore,
|
kvStore: kvStore,
|
||||||
decryptFn: decryptFn,
|
decryptFn: decryptFn,
|
||||||
metrics: m,
|
receiverResourcePermissions: receiverResourcePermissions,
|
||||||
ns: ns,
|
metrics: m,
|
||||||
peer: &NilPeer{},
|
ns: ns,
|
||||||
|
peer: &NilPeer{},
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.UnifiedAlerting.SkipClustering {
|
if cfg.UnifiedAlerting.SkipClustering {
|
||||||
|
@ -98,6 +98,7 @@ func TestMultiorgAlertmanager_RemoteSecondaryMode(t *testing.T) {
|
|||||||
secretsService.GetDecryptedValue,
|
secretsService.GetDecryptedValue,
|
||||||
m.GetMultiOrgAlertmanagerMetrics(),
|
m.GetMultiOrgAlertmanagerMetrics(),
|
||||||
nil,
|
nil,
|
||||||
|
ngfakes.NewFakeReceiverPermissionsService(),
|
||||||
nopLogger,
|
nopLogger,
|
||||||
secretsService,
|
secretsService,
|
||||||
featuremgmt.WithFeatures(),
|
featuremgmt.WithFeatures(),
|
||||||
|
@ -383,7 +383,20 @@ func setupMam(t *testing.T, cfg *setting.Cfg) *MultiOrgAlertmanager {
|
|||||||
decryptFn := secretsService.GetDecryptedValue
|
decryptFn := secretsService.GetDecryptedValue
|
||||||
reg := prometheus.NewPedanticRegistry()
|
reg := prometheus.NewPedanticRegistry()
|
||||||
m := metrics.NewNGAlert(reg)
|
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)
|
require.NoError(t, err)
|
||||||
return mam
|
return mam
|
||||||
}
|
}
|
||||||
|
@ -491,7 +491,20 @@ func createMultiOrgAlertmanager(t *testing.T, orgs []int64) *notifier.MultiOrgAl
|
|||||||
m := metrics.NewNGAlert(registry)
|
m := metrics.NewNGAlert(registry)
|
||||||
secretsService := secretsManager.SetupTestService(t, fake_secrets.NewFakeSecretsStore())
|
secretsService := secretsManager.SetupTestService(t, fake_secrets.NewFakeSecretsStore())
|
||||||
decryptFn := secretsService.GetDecryptedValue
|
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, err)
|
||||||
require.NoError(t, moa.LoadAndSyncAlertmanagersForOrgs(context.Background()))
|
require.NoError(t, moa.LoadAndSyncAlertmanagersForOrgs(context.Background()))
|
||||||
require.Eventually(t, func() bool {
|
require.Eventually(t, func() bool {
|
||||||
|
Loading…
Reference in New Issue
Block a user