Skip to content

feat: CEL-based mutating admission webhook for v1alpha2 Silences#660

Open
QuentinBisson wants to merge 8 commits into
mainfrom
feat/mutating-webhook-forced-matchers
Open

feat: CEL-based mutating admission webhook for v1alpha2 Silences#660
QuentinBisson wants to merge 8 commits into
mainfrom
feat/mutating-webhook-forced-matchers

Conversation

@QuentinBisson

@QuentinBisson QuentinBisson commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Introduces a mutating admission webhook that injects matchers, labels, and annotations into v1alpha2 Silence resources on CREATE/UPDATE, replacing the Kyverno policy in kyverno-policies-observability
  • Rules are expressed in CEL: empty condition = always apply (Kyverno parity); non-empty condition gates the rule to matching objects (e.g. object.metadata.namespace == "prod")
  • Webhook is disabled by default; enabled via webhook.enabled=true and webhook.mutating.celRules in Helm values

Kyverno parity config

webhook:
  enabled: true
  mutating:
    celRules:
    - name: exclude-heartbeat
      condition: ""
      matchers:
      - name: alertname
        value: Heartbeat
        matchType: "!="
    - name: exclude-all-pipelines
      condition: ""
      matchers:
      - name: all_pipelines
        value: "true"
        matchType: "!="

⚠️ Kyverno incompatibility

The mutating webhook and the Kyverno always-allow-heartbeats-and-all-pipelines-alerts ClusterPolicy cannot coexist on UPDATE operations.

Why: Kyverno's policy uses patchesJson6902 with op: add and path: /spec/matchers/-, which unconditionally appends matchers on every admission request — including UPDATEs where the matchers are already present. This creates duplicate matchers. The validating webhook (PR #661) then correctly rejects the object.

The webhook itself is idempotent (it checks before adding), but Kyverno bypasses that guarantee by appending after the mutating webhook has already run.

Required action before enabling the webhook: suspend or remove the Kyverno policy. In Kyverno v1.17+ this can be done via a never-true precondition:

kubectl patch clusterpolicy always-allow-heartbeats-and-all-pipelines-alerts --type=merge -p '{
  "spec": {
    "rules": [{
      "name": "always-allow-heartbeats-and-all-pipelines-alerts-v1alpha2",
      "preconditions": {"any": [{"key": "false", "operator": "Equals", "value": "true"}]}
    }]
  }
}'

Introduces a mutating webhook that injects matchers, labels, and
annotations into v1alpha2 Silence resources on CREATE/UPDATE, replacing
the Kyverno policy in kyverno-policies-observability.

Rules are expressed in CEL: an empty condition applies unconditionally
(Kyverno parity), while a non-empty condition gates injection to matching
objects. Webhook is disabled by default; enabled via webhook.enabled=true
and webhook.celRules in Helm values.

Helm supports both cert-manager-managed TLS (configurable issuer name
and kind) and BYO TLS secret. Adds unit tests, controller integration
tests, and e2e tests.
- Add required guard for webhook.caBundle when certManager is disabled;
  previously an empty CA bundle would silently deploy an unusable webhook
- Clarify e2e conditional rule test comment (test validates always-apply
  rules; extend comment explains how to test conditional rules manually)
- Expand CHANGELOG entry with TLS options and failurePolicy warning
- Replace github.com/pkg/errors with fmt.Errorf for consistency with the rest of the codebase
- Replace interface{} with any (Go 1.18+ alias)
- Add TestCELRuntimeError_MissingField: confirms that accessing an absent field (labels) propagates as an error instead of silently skipping
- Add TestCELRuntimeError_SafeGuardWithHas: documents the safe pattern (has() macro) for optional-field conditions
- Add TestMultipleRules_AppliedInOrder: verifies rules fire in declaration order and matchers are appended deterministically
@QuentinBisson
QuentinBisson force-pushed the feat/mutating-webhook-forced-matchers branch from b545ef1 to c32a956 Compare April 8, 2026 09:49
- Remove | b64enc from caBundle in MutatingWebhookConfiguration: users
  provide the value already base64-encoded (as kubectl outputs it), so
  encoding again would corrupt it; update comment accordingly
- Add webhook section to values.schema.json so Helm validates webhook
  configuration including celRules structure and failurePolicy enum
- Add TestLabelOverwrite / TestAnnotationOverwrite: document and verify
  that labels/annotations are always overwritten (unlike matchers which
  are deduplicated)
- Add TestRegexMatcherInjection: verify =~ match type passes through
  correctly

Signed-off-by: QuentinBisson <quentin@giantswarm.io>
@QuentinBisson
QuentinBisson force-pushed the feat/mutating-webhook-forced-matchers branch from bf190e8 to d40387c Compare April 8, 2026 10:00
Two root causes:

1. Tests timed out when webhook wasn't deployed because there was no skip
   mechanism despite the comment saying "skip automatically". Now checks
   for the MutatingWebhookConfiguration by name at BeforeEach and calls
   Skip() if absent, so the suite gracefully skips when webhook.enabled=false.

2. Tests 2 and 3 failed with "namespace is terminating" because all three
   tests share the namespace "test-webhook" and BeforeEach was calling
   createNamespace without waiting for the previous test's AfterEach
   deletion to complete. Added an Eventually wait in BeforeEach that polls
   until the namespace no longer exists before recreating it.
@QuentinBisson
QuentinBisson marked this pull request as ready for review April 8, 2026 12:57
@QuentinBisson
QuentinBisson requested a review from a team as a code owner April 8, 2026 12:57
@QuentinBisson QuentinBisson self-assigned this Apr 8, 2026
@QuentinBisson

Copy link
Copy Markdown
Contributor Author

This was tested with #661 on graveler

@hervenicol

Copy link
Copy Markdown
Contributor
  • Webhook is disabled by default; enabled via webhook.enabled=true and webhook.mutating.celRules in Helm values

The current code seems to use webhook.celRules, which is simpler but not similar to the kyverno example.

@hervenicol

Copy link
Copy Markdown
Contributor

Required action before enabling the webhook

Could we automate it? Or test it?
If not, it should be clearly explained is the values, so we try to avoid somebody just enables the webhook without doing the kyverno cleanup.

@QuentinBisson

Copy link
Copy Markdown
Contributor Author

You're right, it is done here #661 but I would rather we release the 2 together

@QuentinBisson

Copy link
Copy Markdown
Contributor Author

I think it might be easier to just delete it via a script on all MCs manually as it is a one time migration

@hervenicol

Copy link
Copy Markdown
Contributor

I think it might be easier to just delete it via a script on all MCs manually as it is a one time migration

Is there any chance someone out of giantswarm needs to do this migration? Or is it specific to our internal config?

@QuentinBisson

Copy link
Copy Markdown
Contributor Author

We are the only one that I know that currently uses kyverno policy for silences but this is tightly coupled with our delivery

@hervenicol

Copy link
Copy Markdown
Contributor

We are the only one that I know that currently uses kyverno policy for silences

Fine then, we're the only ones impacted. 👍

but this is tightly coupled with our delivery

You're making reference to this message saying we should run a script at release time?

I think it might be easier to just delete it via a script on all MCs manually as it is a one time migration

So, "a script" would be the kubectl patch command that is in the PR's description?

@QuentinBisson

QuentinBisson commented May 7, 2026

Copy link
Copy Markdown
Contributor Author

No this script disables the policy, the safest bet would be to remove the policy from the kyverno-observability-policies and then merge and release the operator, then update the config with the kyverno parity config

@hervenicol

Copy link
Copy Markdown
Contributor

No this script disables the policy, the safest bet would be to remove the policy from the kyverno-observability-policies and then merge and release the operator, then update the config with the kyverno parity config

I'm not comfortable with having to do "the safest bet"; I'd prefer we don't bet on whatever during upgrades, and rather rely on tested procedures with a fallback/rollback plan as well 😅

Could you write and append a proper deployment/upgrade procedure to the PR description?

@QuentinBisson

Copy link
Copy Markdown
Contributor Author

Tested PR #661 (mutating + validating webhooks) on graveler. Works as expected — config is already in place via konfigure, no additional changes needed to deploy.

Upgrade plan:

  1. Disable the Kyverno policy before the chart upgrade (patch never-true preconditions on always-allow-heartbeats-and-all-pipelines-alerts, same as what was done for testing)
  2. Merge feat: CEL-based mutating admission webhook for v1alpha2 Silences #660 then feat: validating admission webhook for v1alpha2 Silences #661, cut a release, Flux picks up the new chart automatically
  3. Verify webhook is injecting matchers on new and updated Silences

Rollback plan:

  1. Suspend the silence-operator HelmRelease
  2. helm rollback silence-operator -n monitoring — reverts to 0.20.1, no webhook
  3. Resume the HelmRelease
  4. Re-enable Kyverno: flux reconcile helmrelease kyverno-policies-observability -n giantswarm

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