diff --git a/plugins/rule-flood-guard/README.md b/plugins/rule-flood-guard/README.md index e1ee6c422..b107401d5 100644 --- a/plugins/rule-flood-guard/README.md +++ b/plugins/rule-flood-guard/README.md @@ -17,10 +17,9 @@ Everything is scoped per tenant: are each below it never add up to a flood between them. - **Disabling.** The rule is disabled only for the tenant that flooded. Every other tenant keeps it running. -- **Notifying.** The offending tenant is notified, since the disable applies to - them and the remediation is theirs to apply. The platform tenant receives a - copy so the operator keeps instance-wide visibility — unless it is already - the offending tenant, in which case a single notification is sent. +- **Notifying.** Only the tenant that flooded is notified, since the disable + applies to them and the remediation is theirs to apply. No other tenant hears + about it. Alerts that carry no tenant are dropped rather than attributed to a default tenant. diff --git a/plugins/rule-flood-guard/backend.go b/plugins/rule-flood-guard/backend.go index c199d14e4..00da9e9f1 100644 --- a/plugins/rule-flood-guard/backend.go +++ b/plugins/rule-flood-guard/backend.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" "io" "net/http" @@ -15,9 +14,10 @@ import ( "github.com/threatwinds/go-sdk/catcher" ) -// The window is interpolated rather than hardcoded: windowHours is -// configurable, so a fixed "24h" would misreport the period the count covers. -const notificationMessageTemplate = "Correlation rule '%s' generated %d open, un-deduplicated alerts from data source '%s' for tenant '%s' in the last %dh and was automatically disabled for that tenant to prevent alert flooding. If this volume is expected, use an Alert Tag Rule to mark it 'False positive', or add deduplicateBy/groupBy to the rule, then re-enable it." +// Template for the notification message sent to the tenant that flooded. It is +// formatted with the rule name, the number of alerts, the data source, and the +// window in hours. +const notificationMessageTemplate = "Correlation rule '%s' generated %d open, un-deduplicated alerts from data source '%s' in the last %dh and was automatically disabled to prevent alert flooding. If this volume is expected, use an Alert Tag Rule to mark it 'False positive', or add deduplicateBy/groupBy to the rule, then re-enable it." // tenantHeader scopes every backend call. Without it the middleware treats an // internal caller as tenantless and the backend falls back to the platform @@ -154,20 +154,7 @@ type notifyRequest struct { Message string `json:"message"` } -// platformTenant is the operator's tenant. It gets a copy of every flood -// notification so the operator keeps the instance-wide visibility they had -// before the disable became per-tenant. -const platformTenant = "ce66672c-e36d-4761-a8c8-90058fee1a24" - func (c *backendClient) Notify(ctx context.Context, tenantID, message string) error { - err := c.notifyTenant(ctx, tenantID, message) - if tenantID != platformTenant { - err = errors.Join(err, c.notifyTenant(ctx, platformTenant, message)) - } - return err -} - -func (c *backendClient) notifyTenant(ctx context.Context, tenantID, message string) error { payload, err := json.Marshal(notifyRequest{Source: "SYSTEM", Type: "WARNING", Message: message}) if err != nil { return err @@ -196,6 +183,6 @@ func (c *backendClient) notifyTenant(ctx context.Context, tenantID, message stri return nil } -func floodNotificationMessage(tenantID, ruleName string, count int64, dataSource string, windowHours int) string { - return fmt.Sprintf(notificationMessageTemplate, ruleName, count, dataSource, tenantID, windowHours) +func floodNotificationMessage(ruleName string, count int64, dataSource string, windowHours int) string { + return fmt.Sprintf(notificationMessageTemplate, ruleName, count, dataSource, windowHours) } diff --git a/plugins/rule-flood-guard/guard.go b/plugins/rule-flood-guard/guard.go index e870facef..cc67a4b98 100644 --- a/plugins/rule-flood-guard/guard.go +++ b/plugins/rule-flood-guard/guard.go @@ -50,7 +50,7 @@ func evaluateOnce(ctx context.Context, search searchFunc, client disableNotifier continue } - msg := floodNotificationMessage(b.TenantID, b.RuleName, b.Count, b.DataSource, cfg.WindowHours) + msg := floodNotificationMessage(b.RuleName, b.Count, b.DataSource, cfg.WindowHours) if err := client.Notify(ctx, b.TenantID, msg); err != nil { _ = catcher.Error("rule-flood-guard: failed to send notification", err, map[string]any{ "tenantId": b.TenantID, "ruleName": b.RuleName, "dataSource": b.DataSource,