Skip to content

feat: add silence enforcement rules (v1alpha2) - #698

Open
splattner wants to merge 5 commits into
giantswarm:mainfrom
splattner:feature/namespace-enforcement
Open

feat: add silence enforcement rules (v1alpha2)#698
splattner wants to merge 5 commits into
giantswarm:mainfrom
splattner:feature/namespace-enforcement

Conversation

@splattner

Copy link
Copy Markdown

What

Adds a selector-gated, off-by-default multi-tenancy control for the
observability.giantswarm.io/v1alpha2 API. When one or more rules are configured,
a Silence created in a namespace matching a rule's namespaceSelector gets an
authoritative namespace="<namespace>" matcher (plus any custom matchers from the
rule) injected into the Alertmanager silence — so a silence can only ever mute
alerts belonging to its own namespace.

Why

By default a Silence in one namespace can mute alerts from any namespace, since
a silence is just a set of Alertmanager matchers. This provides lightweight
namespace isolation for shared-Alertmanager, multi-tenant setups.

How it works

  • Configured via a mounted YAML file (--namespace-enforcement-config), rendered by
    Helm from namespaceEnforcement values into a ConfigMap.
  • Rules use native metav1.LabelSelector. A namespace is enforced if it matches
    any rule (OR). Empty rules ⇒ no enforcement (the default).
  • Rules are first-match-wins (top-to-bottom); the first matching rule's custom
    matchers apply.
  • Enforcement is authoritative: a user-supplied matcher on an enforced label is
    overridden, logged, and surfaced as a MatcherOverridden Kubernetes Event on the
    Silence.
  • Evaluated at reconcile time and fail-closed — if the namespace lookup fails,
    the silence is not synced.

Example

namespaceEnforcement:
  matcherLabel: "namespace"
  rules:
    - namespaceSelector:
        matchLabels:
          tenant-isolation: "enabled"
    - namespaceSelector:
        matchExpressions:
          - key: team
            operator: In
            values: [platform]
      matchers:
        - name: cluster_id
          value: prod
          matchType: "="

@splattner
splattner requested a review from a team as a code owner July 23, 2026 06:59
@TheoBrigitte

Copy link
Copy Markdown
Member

The overall direction does make some sense, and I understand the needs to be able to enforce some specific Silence matchers.
However I don't quite agree with the opinionated approach towards namespaces here. Alertmanager itself has no notion of namespace, this is very much a Kubernetes related concept. Therefore I would like to make this more generic.

I would suggest to give users the choice to add or not the specific namespace matcher. Instead I would suggest not to default to "namespace" when the 'matcherLabel` is empty, and rather not add it.

An example of this would be

  1. Input silence
apiVersion: observability.giantswarm.io/v1alpha2
kind: Silence
metadata:
  name: my-test
  namespace: my-namespace   # this namespace is labeled with team=platform
spec:
  matchers:
  - matchType: =
    name: foo
    value: bar
  1. The operator apply the following enforcement rules
namespaceEnforcement:
  matcherLabel: ""  # Can also be omitted
  rules:
    - namespaceSelector:
        matchLabels:
          tenant-isolation: "enabled"
    - namespaceSelector:
        matchExpressions:
          - key: team
            operator: In
            values: [platform]
      matchers:
        - name: cluster_id
          value: prod
          matchType: "="
  1. Output silence
apiVersion: observability.giantswarm.io/v1alpha2
kind: Silence
metadata:
  name: my-test
  namespace: my-namespace
spec:
  matchers:
  - matchType: =
    name: foo
    value: bar
  - matchType: =
    name: cluster_id
    value: prod

If going with this then some renaming might be good, example:

  • namespaceEnforcement -> enforcementRules
  • matcherLabel -> namespaceMatcherLabel

@splattner splattner changed the title feat: add namespace-scoped silence enforcement (v1alpha2) feat: add silence enforcement rules (v1alpha2) Jul 28, 2026
@splattner

Copy link
Copy Markdown
Author

Thanks for the review, @TheoBrigitte — I agree, as I am mainly in Kubernetes world, I'm probably biased a bit ;)

I've reworked it to be generic, with namespace scoping strictly opt-in.

  • namespaceMatcherLabel no longer defaults to "namespace". When it's empty (the default), no namespace matcher is injected — a matching rule only applies its own matchers.
  • Your example now behaves exactly as described: a silence with foo=bar in a team=platform namespace gets cluster_id=prod appended, foo=bar preserved, and no namespace matcher. Added unit + envtest coverage for this.
  • Namespace scoping is available by explicitly setting namespaceMatcherLabel (e.g. "namespace"), which injects the authoritative ="" matcher.

Renaming (as suggested)

  • namespaceEnforcement → enforcementRules
  • matcherLabel → namespaceMatcherLabel
  • flag --namespace-enforcement-config → --enforcement-config (ConfigMap/volume/mount renamed to match)
enforcementRules:
  namespaceMatcherLabel: ""   # opt-in; set e.g. "namespace" to enable namespace scoping
  rules:
    - namespaceSelector:
        matchLabels:
          tenant-isolation: "enabled"
    - namespaceSelector:
        matchExpressions:
          - key: team
            operator: In
            values: [platform]
      matchers:
        - name: cluster_id
          value: prod
          matchType: "="

splattner and others added 3 commits July 31, 2026 08:45
Add a selector-gated, off-by-default multi-tenancy control for the
observability.giantswarm.io/v1alpha2 API. When one or more rules are
configured, a Silence created in a namespace matching a rule's
namespaceSelector gets an authoritative "namespace=<namespace>" matcher
(plus any custom matchers from the rule) injected into the Alertmanager
silence, so it can only mute alerts from its own namespace.

- pkg/enforce: config loader (mounted YAML) + first-match-wins rule
  evaluation and override-on-collision matcher application
- v2 controller: apply enforcement at reconcile time (fail-closed on
  namespace lookup), emit a MatcherOverridden Event when a user matcher
  is overridden
- alertmanager.NewMatcher: shared match-type conversion helper
- Helm: namespaceEnforcement values, ConfigMap, volume mount, checksum
  rollout; RBAC read access to namespaces (also fixes the pre-existing
  gap for the namespaceSelector predicate)
- Unit + envtest integration tests; README documentation

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The enforcement config loader imports sigs.k8s.io/yaml directly, so
`go mod tidy` moves it out of the indirect block.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address review feedback: Alertmanager has no notion of "namespace", so the
namespace matcher should not be hardwired. Make it opt-in and generalize the
feature to arbitrary enforced matchers.

- namespaceMatcherLabel (was matcherLabel): empty now means no namespace
  matcher is injected instead of defaulting to "namespace"; a matching rule
  then only injects its own matchers
- rename config: namespaceEnforcement -> enforcementRules, flag
  --namespace-enforcement-config -> --enforcement-config, and the ConfigMap /
  volume / mount path accordingly
- warn at load time for a rule that injects nothing
- generalize log/Event wording (no longer namespace-specific)
- update Helm values/templates, README, and unit + envtest tests; add cases
  covering the empty-label (no namespace matcher) behavior

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@splattner
splattner force-pushed the feature/namespace-enforcement branch from ca5831e to c401409 Compare July 31, 2026 06:48
@TheoBrigitte

Copy link
Copy Markdown
Member

Sounds good, can you please have a look at this comment also 5249e92#r3664473297

@splattner

Copy link
Copy Markdown
Author

Sounds good, can you please have a look at this comment also 5249e92#r3664473297

sure, but 🤔 i'm not seeing any comments there? what was it?

@TheoBrigitte TheoBrigitte left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I forgot to publish it actually.

Comment thread pkg/alertmanager/types.go Outdated
Comment on lines +31 to +39
// Match type strings understood by NewMatcher. These mirror the values of
// v1alpha2.MatchType so that both CR conversion and the enforcement config
// loader can share a single conversion routine.
const (
MatchTypeEqual = "="
MatchTypeNotEqual = "!="
MatchTypeRegexMatch = "=~"
MatchTypeRegexNotMatch = "!~"
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather keep the v1alpha2.MatchType instead of using redefining those variables. Unless I missed something this routine is not shared with the previous CR version.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks — you're right, that routine is only used by the v1alpha2 path (v2 controller + enforcement loader), the v1alpha1 controller builds matchers from its own bool fields. I've dropped the redefined constants and NewMatcher now takes a v1alpha2.MatchType directly, switching on the existing v1alpha2.Match* values. Pushed in 24720ba.

@TheoBrigitte

Copy link
Copy Markdown
Member

I've merge the main branch into your PR in order to fix the CI pipeline.

Drop the duplicated match-type string constants in the alertmanager package
and have NewMatcher take a v1alpha2.MatchType directly. NewMatcher is only
used by the v1alpha2 path (v2 controller + enforcement loader), not the
v1alpha1 controller, so there is no need for version-neutral constants.

Addresses review feedback from @TheoBrigitte.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@TheoBrigitte

Copy link
Copy Markdown
Member

This looks about right to me. I'll give it a test run soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants