diff --git a/CHANGELOG.md b/CHANGELOG.md index 28e50e86458..6d42807e88f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -115,6 +115,7 @@ * [BUGFIX] Alertmanager: Tighten per-tenant config validation to reject additional file-based settings. #7767 * [BUGFIX] Querier: Fix panic (`index out of range [-1]`) in the active request tracker when truncating a `match[]`/`query` value made entirely of invalid UTF-8 continuation bytes. The backwards scan for a rune boundary now stops at index 0 instead of underflowing. #7743 * [BUGFIX] Config: Fix CSV-list flags/YAML fields (e.g. `-compactor.enabled-tenants`) treating an explicitly empty string as a one-element list containing an empty tenant name instead of an empty list. #7714 +* [BUGFIX] Alertmanager: Fix per-tenant `alertmanager_receivers_firewall_block_cidr_networks` overrides being appended to the global default list instead of replacing it, so CIDRs a tenant left out of its list stayed blocked. #7831 ## 1.21.1 2026-06-04 diff --git a/pkg/util/flagext/cidr.go b/pkg/util/flagext/cidr.go index bb7a19c537e..bc7e91a43e7 100644 --- a/pkg/util/flagext/cidr.go +++ b/pkg/util/flagext/cidr.go @@ -46,17 +46,19 @@ func (c CIDRSliceCSV) String() string { // Set implements flag.Value func (c *CIDRSliceCSV) Set(s string) error { - parts := strings.SplitSeq(s, ",") + // Build into a fresh slice so the value replaces the previous one and a bad entry leaves it untouched. + var values CIDRSliceCSV - for part := range parts { + for part := range strings.SplitSeq(s, ",") { cidr := &CIDR{} if err := cidr.Set(part); err != nil { return errors.Wrapf(err, "cidr: %s", part) } - *c = append(*c, *cidr) + values = append(values, *cidr) } + *c = values return nil } diff --git a/pkg/util/flagext/cidr_test.go b/pkg/util/flagext/cidr_test.go index 1e1209adfc4..270a34d4bcd 100644 --- a/pkg/util/flagext/cidr_test.go +++ b/pkg/util/flagext/cidr_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "gopkg.in/yaml.v2" ) @@ -49,3 +50,20 @@ func Test_CIDRSliceCSV_YamlMarshalling(t *testing.T) { }) } } + +func Test_CIDRSliceCSV_SetReplacesPreviousValue(t *testing.T) { + c := CIDRSliceCSV{} + require.NoError(t, c.Set("10.0.0.0/8,192.168.0.0/16")) + require.NoError(t, c.Set("172.16.0.0/12")) + + assert.Equal(t, "172.16.0.0/12", c.String()) +} + +func Test_CIDRSliceCSV_SetDoesNotMutateOnError(t *testing.T) { + c := CIDRSliceCSV{} + require.NoError(t, c.Set("10.0.0.0/8")) + + // The first entry parses, the second one doesn't: the value must be left untouched. + require.Error(t, c.Set("192.168.0.0/16,not-a-cidr")) + assert.Equal(t, "10.0.0.0/8", c.String()) +} diff --git a/pkg/util/validation/limits_test.go b/pkg/util/validation/limits_test.go index 31c832dcc73..82784672f2d 100644 --- a/pkg/util/validation/limits_test.go +++ b/pkg/util/validation/limits_test.go @@ -1278,3 +1278,25 @@ func TestQueryLimits_TenantOverridesValidation(t *testing.T) { }) } } + +func TestAlertmanagerReceiversBlockCIDRNetworksPerTenantOverrideReplacesDefault(t *testing.T) { + defaults := Limits{} + require.NoError(t, defaults.AlertmanagerReceiversBlockCIDRNetworks.Set("10.0.0.0/8,192.168.0.0/16")) + SetDefaultLimitsForYAMLUnmarshalling(defaults) + t.Cleanup(func() { SetDefaultLimitsForYAMLUnmarshalling(Limits{}) }) + + tenantLimits := map[string]*Limits{} + require.NoError(t, yaml.Unmarshal([]byte(` +user1: + alertmanager_receivers_firewall_block_cidr_networks: 172.16.0.0/12 +`), &tenantLimits)) + + ov := NewOverrides(defaults, newMockTenantLimits(tenantLimits)) + + blocked := []string{} + for _, c := range ov.AlertmanagerReceiversBlockCIDRNetworks("user1") { + blocked = append(blocked, c.String()) + } + + assert.Equal(t, []string{"172.16.0.0/12"}, blocked) +}